Fit.UI Apps Architecture
Fit.UI does not force developers to build JavaScript based apps in a certain way. However, we found that the approach described on this page works really well for us - you might like it too.
Separation of Concerns (SoC)
When building Apps, Separation of Concerns should always be considered to ease maintanance, reduce complexity, make code reusable, and increase testability. Model-View-Controller (MVC) is a great pattern to achieve this. We use a variant of MVC called Model-View-Presenter (MVP) with a Passive View. Using a Passive View means no coupling between the View and Model, which fits perfectly with the use of HTML templates in web applications.
MVC versus MVP
The images below demonstrates the difference between MVC (Model-View-Controller) and MVP (Model-View-Presenter) with a Passive View.
Building the App
Now, let's build an App with Fit.UI.
If you want to fiddle with the code yourself, the demo app is available for download here:
-
AppDemo.zip (lists employees) - or
try it online -
AppDemoEditing.zip (added support for editing) -
or try it online
The front-end for our App contains the following files:
- View.html
Our passive View
- View.css
StyleSheet associated with our View
- App.js
The actual App
- index.html
The page hosting our App
Our backend consists of GetEmployees.php (
see data returned) and UpdateEmployees.php. Neither will be covered any further.
Let's start by defining our Passive View (HTML template).
View.html
<table>
<tr>
<td><b>User ID</b></td>
<td><b>Name</b></td>
<td><b>E-mail</b></td>
<td><b>Department</b></td>
</tr>
<!-- LIST Users -->
<tr>
<td>{[Id]}</td>
<td>{[Name]}</td>
<td>{[Email]}</td>
<td>{[Department]}</td>
</tr>
<!-- /LIST Users -->
</table>
The view exposes three things, the obvious being the layout. The second thing is a repeating block (<!-- LIST Users -->) which defines a block with data being dublicated for every user found in our Model. The third thing is placeholdes (e.g. {[Name]}) which are replaced by the actual data when the application executes.
The App loads our View and Model from the server, and binds it all together. For clarity we use a simple Array as our Model in this example. What basically happens is this:
- View (and associated StyleSheet) and Model is loaded. Everything loads asynchronously and non-blocking.
- When both the View and Model has been loaded, the data is populated into the View.
App.js
MyApp = function()
{
var container = null;
var view = null;
var model = null;
function init()
{
container = document.createElement("div");
Fit.Dom.AddClass(container, "MyApp");
// Load StyleSheet
Fit.Loader.LoadStyleSheet("View.css");
// Load View (HTML template)
view = new Fit.Template(true);
view.LoadUrl("View.html", function(sender)
{
// View loaded and ready to be populated.
view.Render(container);
populateTemplate();
});
// Load Model
var req = new Fit.Http.JsonRequest("GetEmployees.php");
req.OnSuccess(function(sender)
{
// Data loaded and ready to be added to View.
model = req.GetResponseJson();
populateTemplate();
});
req.Start();
}
function populateTemplate()
{
// Add data from Model to View
if (view.Content === null || model === null)
return; // Skip, either View or Model has not been loaded yet
Fit.Array.ForEach(model, function(user)
{
// Add item to block in View
var u = view.Content.Users.AddItem();
// Update placeholders defined in View for our new user
u.Id = user.Id;
u.Name = user.Name;
u.Email = user.Email;
u.Department = user.Department;
});
view.Update(); // Push updates to DOM
}
this.Render = function(target)
{
Fit.Dom.Add(target, container);
}
init();
}
Our App is now basically done, and all we need to do, is add it to an ordinary HTML page.
index.html
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
<link rel="stylesheet" type="text/css" href=“Fit.UI/Fit.UI.css"></link>
<script type="text/javascript" src=“Fit.UI/Fit.UI.js"></script>
<script type="text/javascript" src="App.js"></script>
</head>
<body>
<script type="text/javascript">
var app = new MyApp();
app.Render(document.body);
</script>
</body>
</html>
An example of the our App can be found
here.
Naturally we can do more complex things like adding support for editing like shown
in this example. The required changes to achieve this is found below.
MyApp = function()
{
var container = null;
var view = null;
var model = null;
var cmdSave = null;
function init()
{
container = document.createElement("div");
Fit.Dom.AddClass(container, "MyApp");
// Load StyleSheet
Fit.Loader.LoadStyleSheet("View.css");
// Load View (HTML template)
view = new Fit.Template(true);
view.LoadUrl("View.html", function(sender)
{
// View loaded and ready to be populated.
view.Render(container);
populateTemplate();
});
// Load Model
var req = new Fit.Http.JsonRequest("GetEmployees.php");
req.OnSuccess(function(sender)
{
// Data loaded and ready to be added to View.
model = req.GetResponseJson();
populateTemplate();
});
req.Start();
// Create save button
cmdSave = new Fit.Controls.Button("SaveUsers");
cmdSave.Title("Save changes");
cmdSave.Icon("save");
cmdSave.Type(Fit.Controls.Button.Type.Success);
cmdSave.Enabled(false);
cmdSave.OnClick(function(sender)
{
saveUsers();
});
}
function populateTemplate()
{
// Add data from Model to View
if (view.Content === null || model === null)
return; // Skip, either View or Model has not been loaded yet
Fit.Array.ForEach(model, function(user)
{
// Add item to block in View
var u = view.Content.Users.AddItem();
// Update placeholders defined in View for our new user
u.Id = user.Id;
u.Name = createInput(user.Name);
u.Email = createInput(user.Email);
u.Department = createInput(user.Department);
// Associate user from Model with user in View.
// We need this reference later when sending
// changes to the server (see saveUsers()).
u._userObject = user;
});
view.Content.SaveButton = cmdSave.GetDomElement();
view.Update(); // Push updates to DOM
cmdSave.Enabled(true);
}
function createInput(value)
{
// Create edit control
var input = new Fit.Controls.Input(Fit.Data.CreateGuid());
input.Value(value);
// Get DOM element representing edit control.
// Associate edit control with DOM element.
// We need the reference to the edit control when
// saving changes to the server (see saveUsers()).
var elm = input.GetDomElement();
elm._control = input;
return elm;
}
function saveUsers()
{
cmdSave.Enabled(false);
// Loop through all users in View
Fit.Array.ForEach(view.Content.Users.GetItems(), function(user)
{
// Update each user from our Model with
// the value entered in the edit control.
user._userObject.Name = user.Name._control.Value();
user._userObject.Email = user.Email._control.Value();
user._userObject.Department = user.Department._control.Value();
});
// Send the updated data to the server
var req = new Fit.Http.JsonRequest("UpdateEmployees.php");
req.SetData(model);
req.OnSuccess(function(sender)
{
Fit.Controls.Dialog.Alert("Done - the server received the following data:<br><pre>" + req.GetResponseText() + "</pre>");
cmdSave.Enabled(true);
});
req.Start();
}
this.Render = function(target)
{
Fit.Dom.Add(target, container);
}
init();
}