So as promised, I am diving deeper into my Appcelerator Mvc framework. In this post I will give more complex examples on how to use my framework in conjunction with joli so you can truely see how to integrate my framework in a complex, data-driven application. If you haven’t read over the basics or how the routing module works, I recommend you do so. You may also want to read my post on jsAutomapper.

Appcelerator Mvc Introduction
Appcelerator Mvc Routing
jsAutomapper


Setup

If you follow along with this post you will end up with the same working example I have on the Appcelerator Marketplace. If you just want to walk through the example you can download the project here. The first thing you are going to want to do is create a new, blank Appcelerator mobile project. Next, save these files and add them to your project:

You can place the files in the “~/Resources” folder or create a subfolder if you prefer. Through this example I will base all my code on the idea that you placed these files in this folder.

app.js

Now that you have added the files to the project we are ready to start writing code. Delete everything inside of the app.js file so we can start from scratch. Next add the following code. This will setup any includes we need for the mvc framework. The includes listed here take care of the Mvc framework itself as well as any Controllers you plan on using in your project. I also include a file named “dal.js” which is used to access the database and any data models we will need. We will uncomment this line later. We haven’t created these Controllers yet but list them here anyway — that’s coming up in our next step. The last line initializes the Mvc framework and sets up your Controllers as well as Views.

	Ti.include('mvc.js'); 

	//Ti.include("/dal.js");

	Ti.include('/controllers/HomeController.js');
	//Ti.include('/controllers/AccountsController.js');

	Mvc.init();

The next portion we have is adding any routes you may want. In this example I include a default route as well as a specific route. I won’t explain the routing here as I have already gone over it in my routing post

	// make a specific route
	Mvc.mapRoute(
		"DoStuff_Route",
		"DS",
		{
			controller : "Home",
			action : "DoStuff"
		}
	)

	// map a default route
	Mvc.mapRoute(
		"Default_Route",
		"{controller}.{action}.{text}", // could also be {controller}/{action}/{id}
		{
			controller : "Home",
			action : "Default",
			text : "Default Text"
		});

The last major portion of the code sets up your window management. I use this code as a hook in case you want to render your windows differently or even use this Mvc framework in another context (i.e. the web). In my implementation I basically keep track of a window stack and open/close the windows when appropriate. It should be fairly easy to create your own implementation if you desire alternative behavior… Anyway, here is the code. I have commented it pretty extensively so you should be able to get the gist of it.

// manage the windows -- window stack
var openWindows = [];

Mvc.render = function(ui, routeData) {
	var win, index, routeName = routeData.controller + "." + routeData.action;

	// find the index in the stack of windows
	for(var i = openWindows.length - 1; i >= 0; i -= 1) {
		if(openWindows[i]._title === routeName) {
			win = openWindows[i];
			index = i + 1;
			break;
		}
	}
	function cleanup(index) {
		for(var i = index; i < openWindows.length; i += 1) {
			openWindows[i].close();
			openWindows[i] = null;
		}
		openWindows = openWindows.splice(0, index);
	}
	// if the window already exists in the stack
	if(win) {
		// clean up array -- remove all windows
		cleanup(index);

		// remove children from current view
		var children = win.children;
		for(var i = 0; i < children.length; i += 1) {
			win.remove(children[i])
		}
	} else {
		// create a new window
		win = Ti.UI.createWindow({
			_title : routeName,
			fullscreen : true
		});

		win.addEventListener('close', function() {
			// find the index of this window so we can remove it when the back button is pressed
			for(var i = openWindows.length - 1; i >= 0; i -= 1) {
				if(openWindows[i]._title === routeName) {
					index = i;
					break;
				}
			}
			cleanup(index);
		});

		// This handles the creation of the first window so that
		// Appcelerator will exit your app when this window is closed.
		if(openWindows.length === 0) {
			win.exitOnClose = true;
		}

		openWindows.push(win);
		win.open();
	}

	// This adds the controls you created in your view
	win.add(ui);

	// return the window incase you need it for something in your view
	return win;
};

The last portion of code we need to add to app.js is used to start up the app and navigate to the correct window. If you do not pass any parameters it will resolve to your default route. If you want a specific route then simply pass the appropriate parameters.

	// This will go off and attempt to render the default route and will
	// fail since we have not created the 'Home' controller yet
	Mvc.start();

The Controllers

Next up we will create the our Controllers. First create a folder named “controllers” and place it under the “~/Resources” folder. Next create javascript files named “HomeController.js” and “AccountsController.js” under the “controllers” folder. First we will implement the “HomeController”. The code in this portion is very straight forward as this Controller does not use the Data Access Layer (dal.js) so we can implement the entire Controller. The “AccountsController” on the other hand is fairly complex and uses the Data Access Layer (dal.js) so we will focus on that later.

HomeController

Here is the implementation of the “HomeController”. The first portion is simply the includes the the Views used by this controller. I structure my code so each Action of the Controller has its own View.

	// This will be the location for the 'Default' view
	Ti.include('/views/Home/Default.js');
	// another view
	Ti.include('/views/Home/DoStuff.js');

The next portion is the shell of the Controller. It is a closure which takes in Mvc.Controllers as its parameter so we can populate the framework with all the controllers.

	(function(c) {
		c.HomeController = {
			// Actions go here...
		};
	}(Mvc.Controllers));

Inside the HomeController object we now add the Actions “Default” and “DoStuff”. In the “Default” method we create a Model to populate our UI. It populates the Model with things passed to the Action or default values defined in any routes we add in app.js. The “DoStuff” Action takes in a string as a parameter, modifies it, and sends it off to its view for rendering. I will show the implementation of “DefaultModel” in the next step. We return this model in the “this.view” function call of the “Default” Action which will send the Model to the view to render out.

	Default : function(text, other) {
		// Create the model and populate it with some values
		var model = DefaultModel();
		model.text = text;
		model.value = text;

		// This returns a view and tells the framework to render the
		// 'Default' view using the model we created.
		return this.view('Default', model);
	},
	// we could add another action here
	DoStuff : function (value) {
		// manipulate the value parameter and return the appropriate view
		return this.view('DoStuff', value + "!!!");
	}

Here is the implementation for “DefaultModel”. It is simply a factory to create an object.

	function DefaultModel() {
		return {
			text : null,
			value : 0
		};
	}

Here is what the “HomeController.js” file should look like in the end:

	// This will be the location for the 'Default' view
	Ti.include('/views/Home/Default.js');
	// another view
	Ti.include('/views/Home/DoStuff.js');

	(function(c) {
		// This is just a basic model used to render
		// the 'Default' view.
		function DefaultModel() {
			return {
				text : null,
				value : 0
			};
		}
		// Initializes the 'HomeController' in Mvc.Controllers
		c.HomeController = {
			Default : function(text, other) {
				// Create the model and populate it with some values
				var model = DefaultModel();
				model.text = text;
				model.value = text;

				// This returns a view and tells the framework to render the
				// 'Default' view using the model we created.
				return this.view('Default', model);
			},
			// we could add another action here
			DoStuff : function (value) {
				// manipulate the value parameter and return the appropriate view
				return this.view('DoStuff', value + "!!!");
			}
		};
	}(Mvc.Controllers));

That is it for our controllers for the moment. Like I said, the AccountsController has more complexities we need to tackle first so we will ignore the file for now.

Home Controller Views

The first thing we need to do here is create a “views” folder under “~/Resources” and then a “Home” folder under the “views” folder. Now create “Default.js” and “DoStuff.js” at “~/Resources/views/Home”. We start off by implementing the shell for the “Default” view. This works similar to Controllers in the fact that we pass “Mvc.Views” into a closure. We make sure that the object in which the View resides (in this case Home) exists and create it if it doesn’t. We then define our View as a function.

	(function(v) {
		// Create the 'Home' object if it doesn't already exist
		if(!v.Home) {
			v.Home = {};
		}

		/*
		 * Render the default page
		 */
		v.Home.Default = function(model) {

		};
	}(Mvc.Views));

The next portion of code will set up the actual UI elements of the window. We create a label and textfield which are populated from the model that was returned from the “HomeController.Default” Action and is passed into the function we defined in “Home.Default” View. There is also a button that we will use to navigate to the “HomeController.DoStuff” Action we previously defined. Since we added the “DoStuff_Route” in the app.js we can use the alias of “DS” to navigate there. The text value from the textbox is also passed to the Action. Lastly we return the View which contains all the controls used in the window. It is important that we have a single UI object to contain the entire View so it can be added appropriately to the window. This UI object will be used to populate a window created in the “Mvc.render” function we also defined in app.js.

	// get a reference to the view
		// get a reference to the view
		var self = this;
		 
		var view = Ti.UI.createView();
		var label = Ti.UI.createLabel({
		    text : model.text,
		    top: "5%",
		    height : "5%"
		});
		var textField = Ti.UI.createTextField({
		    value : model.value,
		    top : "10%",
		    width: "50%",
		    height: "5%",
			backgroundColor: "#ffffff",
		    keyboardType:Ti.UI.KEYBOARD_DEFAULT,
		});
		var button = Ti.UI.createButton({
		    title : "Submit",
		    width: "50%",
		    height : "15%"
		});
		 
		// This will call DoStuff
		button.addEventListener("click", function(e) {
		    // Uses a specific route ('DS') we defined in app.js
		    self.action("DS", textField.value);
		});
		view.add(label);
		view.add(textField);
		view.add(button);
		 
		return view;

In the end your code for the “Home.Default” view should look like this:

	(function(v) {
    // Create the 'Home' object if it doesn't already exist
    if(!v.Home) {
        v.Home = {};
    }
 
    /*
     * Render the default page
     */
    v.Home.Default = function(model) {
		// get a reference to the view
		var self = this;
		 
		var view = Ti.UI.createView();
		var label = Ti.UI.createLabel({
		    text : model.text,
		    top: "5%",
		    height : "5%"
		});
		var textField = Ti.UI.createTextField({
		    value : model.value,
		    top : "10%",
		    width: "50%",
		    height: "5%",
			backgroundColor: "#ffffff",
		    keyboardType:Ti.UI.KEYBOARD_DEFAULT,
		});
		var button = Ti.UI.createButton({
		    title : "Submit",
		    width: "50%",
		    height : "15%"
		});
		 
		// This will call DoStuff
		button.addEventListener("click", function(e) {
		    // Uses a specific route ('DS') we defined in app.js
		    self.action("DS", textField.value);
		});
		view.add(label);
		view.add(textField);
		view.add(button);
		 
		return view;
    };
}(Mvc.Views));

Now we can start in on the “Home.DoStuff” View. This view is very simple and just displays the text in the textbox of the “Home.Default” View in addition to a modification (the ‘!!!’) in the “HomeController.DoStuff” Action.

	(function(v) {
		// Create the 'Home' object if it doesn't already exist
		if(!v.Home) {
			v.Home = {};
		}

		/*
		 * Render the DoStuff page
		 */
		v.Home.DoStuff = function(model) {
			var view = Ti.UI.createView({
				backgroundColor : "#fff"
			});
			var label = Ti.UI.createLabel({
				text : model,
				color : "#000"
			});

			view.add(label);

			return view;
		};
	}(Mvc.Views));

That covers the source up to getting the HomeController running. You should now be able to run the project and see the framework in action although it is a boringly simple app at this point. When you run you should see some text and a textbox. If you modify the text it will show up in the next window along with three exclamation points. Next up we will get into working with joli and the DAL. If you followed all the steps correctly you should end up with this.

Data Access and Data Models

Let me start off first by saying that the Data Access Layer can be a nasty thing if you let it. This is why I prefer to user an ORM such as joli. This allows you to avoid writing data access code which may end up getting placed in your controller Actions which really defeats separation of concerns. It also allows you to reuse a ton of common functionality with a common API. In addition you can get down and dirty with custom SQL queries when you need it. Anyways, if you haven’t already, create a “dal.js” file and save it under the “~/Resources” folder. You also need to go to app.js and uncomment the “//Ti.include(“/dal.js”);” line. The source in this file will be fairly simple as we want to separate out the Data Access code to keep everything neat and tidy. We also will create a global object for accessing the DAL from any other file. So again we create our closure passing in the “Ti.App” object.

	var dal = (function(app) {
		// if the dal has already been defined, return
		if(app.dal) {
			return;
		}

		// initialize the dal object and get a local reference
		var d = app.dal = {};

		return d;
	}(Ti.App));

Next up we will add all our references to libraries/models we will need and set up our database. After this, add the following code.

	Ti.include("/joli.js");
	Ti.include("/jsAutomapper.js");

	// set up our database
	joli.connection = new joli.Connection('SampleDatabase');

	// run setup for all models
	Ti.include("/models/Accounts.js");

	// models have been setup. initialize the database
	joli.models.initialize();

This code creates a database called “SampleDatabase” you can name the database whatever you want here. We will do setup for the various tables in the models files — “/models/Accounts.js” for example. When we finally call “joli.models.initialize” we have completed our setup of the DAL.

Here is the final source for “dal.js”

	var dal = (function(app) {
	// if the dal has already been defined, return
	if(app.dal) {
		return;
	}

	// initialize the dal object and get a local reference
	var d = app.dal = {};

	return d;
}(Ti.App));

Ti.include("/joli.js");
Ti.include("/jsAutomapper.js");

// set up our database
joli.connection = new joli.Connection('SampleDatabase');

// run setup for all models
Ti.include("/models/Accounts.js");

// models have been setup. initialize the database
joli.models.initialize();

Now with that out of the way, we can focus on setting up our Accounts Model. First off, create a folder under “~/Resources” called “models”. Next create a javascript file called “Accounts.js”. This file will be used to house all database initialization code as well as and automapper code used to map between Models and View Models. Models are used to transfer data from your database tables and View Models are objects which map directly to a specific view.

So start off your model implementation by passing “Ti.App.dal” into a closure and validating that the “dal” object has defined and the “dal.accounts” property has not.

	(function() {
		// dal has not been initialized or accounts have already been initailized
		if(!dal || dal.accounts) {
			return;
		}

	}());

Next up we will actuall setup our table in the database using joli. This creates a simple table named “Accounts” with a few columns (firstName, lastName, email). If you read through the joli API, it explains you can also add prototype functions to any Account Models as well as helper functions to the dal.accounts object when you do your initialization. I won’t get into that here but I have found it very useful for extending the existing API.

	dal.accounts = new joli.model({
		table : 'Accounts',
		columns : {
			id : 'INTEGER PRIMARY KEY AUTOINCREMENT',
			firstName : 'TEXT',
			lastName : 'TEXT',
			email : 'TEXT',
		}
	});

The next portion is mapping your View Models to your Models and vice-versa. For a Mvc framework I think this is a very important process. I am using jsAutomapper although I am sure there are similar solutions out there. It creates a single place for setting up a transformation between two objects and forms a contract between them. This way if you change a property in your database or in your View Model, you will get immediate feedback when the objects no longer map correctly. You can write unit tests that simply check that the two objects map correctly. This way you don’t have “undefined” showing up in your UI with no knowledge of where it was supposed to be populated or have a random “object is undefined” exception from a View Model property show up on some Action or View you haven’t worked on in weeks.

Here is the next bit of code you need for the Accounts Model. I will let you read through the code/comments and then go over what is actually going on here.

	// automapper mappings for this model

	// include view models - models can be mapped to multiple view models
	Ti.include("/viewModels/AccountViewModel.js");
	Ti.include("/viewModels/AccountsViewModel.js");
	Ti.include("/viewModels/CreateAccountViewModel.js");

	// This maps an Account to an AccountViewModel -- I pass in the destination key
	// as a function so automapper can use it to create viewmodels
	automapper.createMap("Account", AccountViewModel)
		// we don't actually need the 'firstName' since the column name matches the view model name 1-to-1
		// I just include it as an example
		.forMember("firstName", function() { return this.mapFrom("firstName") })
		// ignore this field since we already have it implemented
		.forMember("fullName", function() { return this.ignore(); });

	// model maps 1-to-1 -- we don't need to set up any members
	automapper.createMap("Account", "CreateAccountViewModel");

	// This maps an AccountViewModel to an Account
	automapper.createMap("CreateAccountViewModel", "Account")
		/*
		 * forAllMembers is run for every mapping
		 * dest - the destination object
		 * prop - the property name to set
		 * value - the value from the source
		*/
		.forAllMembers(function(dest, prop, value) {
			// tell joli to properly set the values
			dest.set(prop, value);
		})
		// ignore joli internal properties
		.forMember("_data", function() { this.ignore(); })
		.forMember("_metadata", function() { this.ignore(); })
		.forMember("_options", function() { this.ignore(); })
		.forMember("_originalData", function() { this.ignore(); })
		.forMember("isNew", function() { this.ignore(); });

So first off we include references to View Model objects we have yet to define. These are simple files and we will get to them later. Next we create a map between an “Account” and a “AccountViewModel”. Doing so we can later on take two objects and map all the properties between the two. We add a “ignore” to the “fullName” property of the “AccountViewModel” — this is a function I have defined in “AccountViewModel” and we don’t want to override it. The next mapping between “Account” and “CreateAccountViewModel” is pretty self-explanatory. The last mapping between “CreateAccountViewModel” and “Account” is special in that we use the “forAllMembers” call. What this does is when we set any properties in the “Account” model, we call this function to do so. This must be done since “Account” is a joli object and you must call “set” in order to properly set the property on the object to let joli know that we have made changes to the object. If you don’t when you call “accountObj.save”, the database will not pick up our changes. The ignores on this mapping are simply properties which get added to ever joli object (Model). Here is the combined source for the “Accounts.js” file.

	(function() {
		// dal has not been initialized or accounts have already been initailized
		if(!dal || dal.accounts) {
			return;
		}

		// create the accounts table
		dal.accounts = new joli.model({
			table : 'Accounts',
			columns : {
				id : 'INTEGER PRIMARY KEY AUTOINCREMENT',
				firstName : 'TEXT',
				lastName : 'TEXT',
				email : 'TEXT',
			}
		});

		// automapper mappings for this model

		// include view models - models can be mapped to multiple view models
		Ti.include("/viewModels/AccountViewModel.js");
		Ti.include("/viewModels/AccountsViewModel.js");
		Ti.include("/viewModels/CreateAccountViewModel.js");

		// This maps an Account to an AccountViewModel -- I pass in the destination key
		// as a function so automapper can use it to create viewmodels
		automapper.createMap("Account", AccountViewModel)
			// we don't actually need the 'firstName' since the column name matches the view model name 1-to-1
			// I just include it as an example
			.forMember("firstName", function() { return this.mapFrom("firstName") })
			// ignore this field since we already have it implemented
			.forMember("fullName", function() { return this.ignore(); });

		// model maps 1-to-1 -- we don't need to set up any members
		automapper.createMap("Account", "CreateAccountViewModel");

		// This maps an AccountViewModel to an Account
		automapper.createMap("CreateAccountViewModel", "Account")
			/*
			 * forAllMembers is run for every mapping
			 * dest - the destination object
			 * prop - the property name to set
			 * value - the value from the source
			*/
			.forAllMembers(function(dest, prop, value) {
				// tell joli to properly set the values
				dest.set(prop, value);
			})
			// ignore joli internal properties
			.forMember("_data", function() { this.ignore(); })
		        .forMember("_metadata", function() { this.ignore(); })
			.forMember("_options", function() { this.ignore(); })
			.forMember("_originalData", function() { this.ignore(); })
			.forMember("isNew", function() { this.ignore(); });
	}());

With that completed we are nearly ready to start working on the “AccountsController”. The only thing left is to create a “viewmodels” folder under the “~/Resources” folder and the javascript files “AccountViewModel.js”, “AccountsViewModel.js”, and “CreateAccountViewModel.js”.

AccountViewModel.js

	/*
	 * Simple Account model used for displaying Accounts in a list
	 */
	function AccountViewModel() {
		return {
			id : null,
			firstName : null,
			lastName : null,
			fullName : function() {
				return this.firstName + " " + this.lastName;
			}
		};
	}

AccountsViewModel.js

	/*
	 * A viewmodel which contains a collection of
	 * AccountViewModels
	 *
	 * In this case we could return a collection of
	 * AccountViewModels from the Accounts.Default action
	 * and I usually end up needing to pass additional data
	 */
	function AccountsViewModel() {
		return {
			accounts : []
		};
	}

CreateAccountViewModel.js

	function CreateAccountViewModel() {
		return {
			id : null,
			firstName : null,
			lastName : null,
			email : null
		};
	}

AccountsController

We’ll start off the “AccountsController” much like the “HomeController”. If you haven’t already created a file for this, create one under “~/Resources/controllers” as “AccountsController.js”. After this you must uncomment “Ti.include(‘/controllers/AccountsController.js’);” the line in app.js. Next add the stub controller code.

	(function(c) {
		// include the views
		Ti.include("/views/Accounts/Default.js");
		Ti.include("/views/Accounts/Create.js");

		c.AccountsController = {
		};
	}(Mvc.Controllers));

Again, we include the View files that we’ll create later. Next up come the Actions. I will go over each seperately. Stub out the “AccountsController” definition like this:

	c.AccountsController = {
		Default : function() {
		},
		Create : function(id) {
		},
		CreateFinish : function(model) {
		},
		Delete : function(id) {
		}
	};

So first off is the “Default” Action. In this view we want to allow the user to create new Accounts and edit/delete previously created “Accounts”. In order to do that we need to list all accounts in the view. So first off we use the DAL to get all Accounts. We then create an AccountsViewModel to house all the AccountViewModels. We then will use automapper to map between the two collections and then return the result to the View. Notice in the code that we pass the “AccountViewModel” to the “automapper.map” call. This is done in order for automapper to have a factory method to populate the “model.accounts” array with the appropriate View Model.

	/*
	 * Default action which simply shows a list of Accounts
	 */
	Default : function() {
		// use the dal as a repository to grab all accounts
		var accounts = dal.accounts.all();

		var model = AccountsViewModel();

		// automapper knows to map the two collections
		automapper.map("Account", AccountViewModel, accounts, model.accounts);

		// return the model and render the view
		return this.view("Default", model);
	}

Next up comes the “Create” Action. Notice how simple the function becomes now that we are using jsAutomapper to map the Model to View Model and joli for accessing the database. The “Create” function is essentialy a create/update method. If you pass in the “id” parameter we know that we are updating an existing “Account”. This action is used to simply populate the create/update UI.

	/*
	 * Basic create/update operation for a new Account
	 */
	Create : function(id) {
		var model = CreateAccountViewModel();

		if(id) {
			automapper.map("Account", "CreateAccountViewModel", dal.accounts.findOneById(id), model)
		}

		return this.view("Create", model);
	}

Next up is the “CreateFinish” Action (for lack of a better name). This Action writes any changes you made to the Account model to the database. If it is a new Account we will have joli instantiate an Account for us. If we are updating we query for the appropriate Account.

	/*
	 * Finishes up the creation/update process by inserting the object into the database
	 */
	CreateFinish : function(model) {
		var account;
		if(!model.id) {
			account = dal.accounts.newRecord();
		} else {
			account = dal.accounts.findOneById(model.id);
		}
		automapper.map("CreateAccountViewModel", "Account", model, account);
		account.save();
	}

Lastly is the “Delete” Action. This is a pretty boring and simple implementation and in this case, boring is good.

	/*
	 * Delete an Account based on an id
	 */
	Delete : function(id) {
		dal.accounts.deleteRecords(id);
	}

So that is the “AccountsController” in a nutshell. By abstracting much of the logic using a repository pattern (DAL) and using jsAutomapper for object transformation, we ended up with nice, slim controllers. Here is the “AccountsController.js” in its entirety.

	(function(c) {
		// include the views
		Ti.include("/views/Accounts/Default.js");
		Ti.include("/views/Accounts/Create.js");

		c.AccountsController = {
			/*
			 * Default action which simply shows a list of Accounts
			 */
			Default : function() {
				// use the dal as a repository to grab all accounts
				var accounts = dal.accounts.all();

				var model = AccountsViewModel();

				// automapper knows to map the two collections
				automapper.map("Account", AccountViewModel, accounts, model.accounts);

				// return the model and render the view
				return this.view("Default", model);
			},
			/*
			 * Basic create/update operation for a new Account
			 */
			Create : function(id) {
				var model = CreateAccountViewModel();

				if(id) {
					automapper.map("Account", "CreateAccountViewModel", dal.accounts.findOneById(id), model)
				}

				return this.view("Create", model);
			},
			/*
			 * Finishes up the creation/update process by inserting the object into the database
			 */
			CreateFinish : function(model) {
				var account;
				if(!model.id) {
					account = dal.accounts.newRecord();
				} else {
					account = dal.accounts.findOneById(model.id);
				}
				automapper.map("CreateAccountViewModel", "Account", model, account);
				account.save();
			},
			/*
			 * Delete an Account based on an id
			 */
			Delete : function(id) {
				dal.accounts.deleteRecords(id);
			}
		};
	}(Mvc.Controllers));

Accounts Views

The Accounts Views are much more advanced and paint a better picture on how to use the framework. First off is the “Default” View. This View allows CRUD on all Accounts. We will have a button to create new Accounts as well as a list of all Accounts that were previously created. This list allows you to select an Account for editing or deleting. This is accomplished by popping up an alert dialog with the options of “Edit”, “Delete”, and “Cancel”. So create an “Accounts” folder under “~/Resources/views” and create “Default.js” and “Create.js” javascript file under “~/Resources/views/Accounts”. Start off the view by creating its stub.

	(function(v) {
		// Create the 'Accounts' object if it doesn't already exist
		if(!v.Accounts) {
			v.Accounts = {};
		}
		v.Accounts.Default = function(model) {
		};
	}(Mvc.Views));

Next we can implement the “Default” function. I make a reference to the View as “self”. Next I create the view UI control in which we will house the rest of the controls. A button is then added to the view which has a click event which will navigate to the “Accounts.Create” action.

	var self = this;

		var view = Ti.UI.createView({
			backgroundColor : "#000"
		});
	
		// a button to add a new account
		var createButton = Ti.UI.createButton({
			title : "Create Account",
			top : 0,
			height: "10%"
		});
	
		createButton.addEventListener("click", function(e) {
			self.action("Create");
		});
	
		view.add(createButton);

Now that we have the create portion complete in this CRUD interface, we will implement the read portion. To do so I make a reference to the accounts passed through the View Model and start creating “TableViewRows” which are added to the collection. I give each row a property of “_id” which we will use when we want to update/delete any Account. Then I create simple “Alert” dialog — I will get to the implementation of the “click” event later. In the next portion of this code I create a table and populate it with the collection of rows. I also added a “click” event to the table in order to catch any click events on the table rows. I get the Account id I set in the row and assign it to the alert box. This will allow us to pass the id to the “AccountsController.Create” or “AccountsController.Delete” when we click one of the buttons in the “Alert” dialog. The last portion of the code is the “click” event for the “Alert” dialog. We use the index of our buttons to decide which action we wish to make when this event is fired. When someone clicks the delete button we call two actions. Since the “Delete” action does not return anything (Look at “AccountsController.Delete”), we know that no content should be displayed and that we shouldn’t open a new window. Right after that we call the “Accounts” action which resolves to “AccountsController.Default”. Since we are already displaying this window it simply refreshes the current page. This makes sense since we have now deleted one of the Accounts from the list.

var accounts = model.accounts,
			data = [];
		for(var i = 0; i < accounts.length; i += 1) {
			data.push(Ti.UI.createTableViewRow({
				title : accounts[i].fullName(),
				// used to store data in the row so we can update later
				_id : accounts[i].id
			}));
		}
	
		// alert so you can choose to edit or delete
		var alertDialog = Ti.UI.createAlertDialog({
			buttonNames : ["Edit", "Delete", "Cancel"],
			cancel : 2
		});
	
		var table = Ti.UI.createTableView({
			data : data,
			top : "10%",
			height: "90%"
		});
	
		// click event for updating a row
		table.addEventListener("click", function(e) {
			// get the id of the Account from the row
			var id = e.row._id;
			alertDialog._id = id;
			alertDialog.show();
		});
	
		alertDialog.addEventListener("click", function(e) {
			// get the id from the alert box set in the table click event
			var id = e.source._id;
			if(e.index === 0) {
				self.action("Create", id);
			} else if(e.index === 1) {
				self.action("Delete", id);
				self.action("Accounts");  // routing will resolve to Accounts.Default
			}
		});
	
		view.add(table);
		
		return view;

Here is the all the code for the View

(function(v) {
	// Create the 'Accounts' object if it doesn't already exist
	if(!v.Accounts) {
		v.Accounts = {};
	}
	v.Accounts.Default = function(model) {
		var self = this;

		var view = Ti.UI.createView({
			backgroundColor : "#000"
		});
	
		// a button to add a new account
		var createButton = Ti.UI.createButton({
			title : "Create Account",
			top : 0,
			height: "10%"
		});
	
		createButton.addEventListener("click", function(e) {
			self.action("Create");
		});
	
		view.add(createButton);
		
		var accounts = model.accounts,
			data = [];
		for(var i = 0; i < accounts.length; i += 1) {
			data.push(Ti.UI.createTableViewRow({
				title : accounts[i].fullName(),
				// used to store data in the row so we can update later
				_id : accounts[i].id
			}));
		}
	
		// alert so you can choose to edit or delete
		var alertDialog = Ti.UI.createAlertDialog({
			buttonNames : ["Edit", "Delete", "Cancel"],
			cancel : 2
		});
	
		var table = Ti.UI.createTableView({
			data : data,
			top : "10%",
			height: "90%"
		});
	
		// click event for updating a row
		table.addEventListener("click", function(e) {
			// get the id of the Account from the row
			var id = e.row._id;
			alertDialog._id = id;
			alertDialog.show();
		});
	
		alertDialog.addEventListener("click", function(e) {
			// get the id from the alert box set in the table click event
			var id = e.source._id;
			if(e.index === 0) {
				self.action("Create", id);
			} else if(e.index === 1) {
				self.action("Delete", id);
				self.action("Accounts");  // routing will resolve to Accounts.Default
			}
		});
	
		view.add(table);
		
		return view;
	};
}(Mvc.Views));

The last View we need to display is the Accounts "Create" View. This view is very straight forward and has only one major thing I need to talk about. If you haven't created a file for this, create a "Create.js" under "~/Resources/views/Accounts". Here is the source for the "Create" View.

	 (function(v) {
		// Create the 'Accounts' object if it doesn't already exist
	if(!v.Accounts) {
		v.Accounts = {};
	}
	Ti.include("/utils.js");

	/*
	 * Render the Create page
	 */
	v.Accounts.Create = function(model) {
		var self = this;

		var view = Ti.UI.createView({
			backgroundColor : "#777777",
			height: "100%"
		});

		var fnLabel = Ti.UI.createLabel({
			text : "First Name",
			top: "5%",
			height : "5%"
		});
		var fnTextField = Ti.UI.createTextField({
			width:'50%', 
			top : "10%", 
			height:"5%", 
			backgroundColor: "#ffffff",
		    keyboardType:Ti.UI.KEYBOARD_DEFAULT,
		});
		model.firstName = utils.bind(fnTextField, model.firstName); // bind the textfield to the view model

		var lnLabel = Ti.UI.createLabel({
			text : "Last Name",
			top : "20%",
			height : "5%"
		});
		var lnTextField = Ti.UI.createTextField({
			width:'50%', 
			top : "25%", 
			height:"5%", 
			backgroundColor: "#ffffff",
		    keyboardType:Ti.UI.KEYBOARD_DEFAULT,
		});
		model.lastName = utils.bind(lnTextField, model.lastName); // bind the textfield to the view model

		var emailLabel = Ti.UI.createLabel({
			text : "Email",
			top : "35%",
			height : "5%"
		});
		var emailTextField = Ti.UI.createTextField({
			width:'50%', 
			top : "40%", 
			height:"5%", 
			backgroundColor: "#ffffff",
		    keyboardType:Ti.UI.KEYBOARD_DEFAULT,
		});
		model.email = utils.bind(emailTextField, model.email); // bind the textfield to the view model

		var submit = Ti.UI.createButton({top : "50%", height: "10%"});

		if(model.id) {
			submit.title = "Update";
		} else {
			submit.title = "Create";
		}

		submit.addEventListener("click", function() {
			self.action("CreateFinish", model);
			self.action("Accounts");  // routing will resolve to Accounts.Default
		});

		view.add(fnLabel);
		view.add(fnTextField);
		view.add(lnLabel);
		view.add(lnTextField);
		view.add(emailLabel);
		view.add(emailTextField);

		view.add(submit);

		return view;
	};
}(Mvc.Views));

The next thing you need to do is create a simple "utils.js" file. I plan on extending the functionality of this class to include many helper functions but for now, it just enables two-way binding of the View Models to the controls.

var utils = (function(app) {
	if(app.utils) {
		return app.utils;
	}
	app.utils = {
		/*
		 * Very basic databinding with an Appcelerator control.
		 */
		bind : function(control, value, fieldName) {
			var self=control;
			if(!fieldName) {
				fieldName = "value";
			}
			function setValue(val) {
				if(typeof val === "undefined") {
					val = "";
				}
				self[fieldName] = val;
			}
			setValue(value);
			return function(input) {
				if(!input) {
					return self[fieldName];
				}
				setValue(value);
			};
		}
	};
	return app.utils;
}(Ti.App));

Now that we have completed the AccountsController, let's set up the app so it navigates to the AccountsController by default. Open up app.js and change the last line of the file to Mvc.start("Accounts");

Run It!

That should be everything you need to run the application. This example application should be more than enough to get you going in developing your own application using my framework. As always leave any comments, suggestions, or bugs in my comments section.

Here is the Completed Example

UPDATE

Joel Brage was kind enough to port jsAutomapper to coffeescript. You can download it here

52 thoughts on “Appcelerator Mvc Example

  1. Hey John,

    Getting an error on iOS:

    [ERROR] Script Error = Result of expression ‘dal.accounts’ [undefined] is not an object. at AccountsController.js (line 13).

    Not sure why Android was able to see it and iOS isnt…?

    By the way I had to make some style changes to the Home Default view to be able to see the elements properly, it doesnt quite work as is.

    Thanks for doing all this, reminds me of CFWheels(which Im familiar with) plus I think your docs are better than Appcelerator’s… what a pain.

  2. Thanks for the input. My boss is getting me a Mac to test on so I’ll have these issues sorted out next week. I am guessing that iOS doesn’t like joli… The rest of the framework seems to be working though… Which is nice.

    • Hey, sorry I took so long to get back to you. I have been swamped lately…

      Hmm, I thought I had included utils.js in the example. I have updated the post. Thanks for catching that.

  3. This is really good. I was able to follow it but had to read a few more times. How do you plan to handle database schema updates in future app roll-outs. I notice the ruby example appears to have a simple way of doing this. Regardless I like the simplicity of your approach and I am going to use it in my mobile apps. Can you show I would add two tabs in my application in your example; would this affect the routing mechanism?

    • I believe (I haven’t tested this) that when you make minor schema changes such as adding/removing columns, joli will automatically update the database.

      As for adding tabs, if you look at this pull you can see he has changed the Mvc.render function to something much more iPhone friendly. I will be merging these changes into the trunk at some point and writing a tutorial on how to use it.

      Something I forgot to mention in this example is the View.partial function which is a member function of your view, like View.action. You can use this to render partial views for you tabs.

  4. When run this on the iOS iphone simulator it works fine but when I install the same on the iphone 4s device I get Application Error “Could not find the file AccountViewModel.js”

    Has anyone seen this.

    • Sorry, I don’t have an iPhone so I cannot test and figure out why it doesn’t like the javascript. I have run into a few issues in the past with the iPhone and perfectly valid javascript so I cannot be much help. If you figure this out, be sure to post your workaround in the comments.

  5. Hi,

    The framework look really good ! I’ve been looking for this for a long time.

    Did anyone manage to have 2 or 3 tabs ? I’m trying to have the “HomeController” display on tab1 and the “AccountsController” on tab2 ?

    Thx
    Amine

    • Yeah, I am going to add on to this example when I have time. I am currently in crunch at work and have no motivation to write or update the source when I get home. If you look at this file by Morgan you can see how he did tabbed views by modifying the render portion of the code. I plan on using tabs by default in the next example I write to make it more iPhone friendly.

      From there you can use View.partial which will render a partial view based on a model you pass in. If you check out my original post on the framework it explains this in more detail.

    • I noticed the same thing, only using a simple viewmodel with the binding function. It did work once I had a full jsautomapped model going.

    • The utils bind function takes a field in an object and turns it into a function. If you look at the source you can see that it is pretty simple — it just uses a closure to keep track of the control and a function to grab the control’s value. If you have something like

      var obj = {
      foo : 12
      };
      var tb = Ti.UI.createTextField();
      obj.foo = utils.bind(tb, obj.foo);

      and you use the utils.bind function you will need to get/set the value by using

      var val = obj.foo(); // get
      obj.foo(10); // set

  6. My data for my default home view is initially loading from an xhr request. The problem I am having is that when the action returns the view, the data doesn’t exist yet because the xhr request hasn’t fired off.

    I can’t seem to figure out how to return the view with the Default action and the model in a callback.

    Any ideas?

  7. I haven’t had time to come up with a good solution for this (I have been getting nailed with small projects at work lately) but I believe something like this should work.


    // some action in a controller
    SomeAction: function(id) {
    var model = {};
    var view = View(); // returns a view that is in a 'loading' state
    Ajax.request( {
    // bunch of params
    success: function(value) {
    model.someValue = value;
    view.RenderAjaxData(model);
    }
    }
    return view;
    }

    // some view
    v.SomeController.SomeAction = function() {
    var view = Ti.UI.createView();
    view.RenderAjaxData = function(model) {
    // remove all children ui elements from 'view'
    // do your rendering for after ajax here
    };

    // render a 'waiting' message here

    return view;
    };

    This is by no means a good solution but it is a solution. I will be looking into extending the framework to handle asynchronous requests as soon as I get time.

    John

    • Thanks for the reply.

      Calling var view = View(); inside my Default action in the controller throws and error that it doesn’t exist.

        • My action is defined as:

          Default : function() {
          var self = this;
          var q = new joli.query()
          .select('Brands.*')
          .from('Brands')
          .order(['sequence asc']);

          var brands = q.execute();
          var model = BrandsViewModel();
          var view = this.view('Brand', model);

          if (cache.isExpired(cache.getExpirationTime()) || brands.length == 0) {
          // cache is expired or brands are empty
          // go get stuff and save it
          abby.brands.getBrandList(function(result) {
          var model = CreateBrandViewModel();
          for (i=0;i<result.length;i++) {
          model.name = result[i].name;
          model.path = result[i].path;
          model.sequence = result[i].sequence;

          var brand = dal.brands.newRecord();
          automapper.map('CreateBrandViewModel', 'Brand', model, brand);

          brand.save();
          }
          view.RenderAjaxData(model);

          });
          }
          return view;
          }

          and my view is :


          (function(v) {
          // create the Brands object if it doesn't exist
          if (!v.Brands) {
          v.Brands = {}
          }
          v.Brands.Default = function(model) {
          var self = this;

          var view = Ti.UI.createView();
          view.RenderAjaxData = function(model) {
          // remove all children ui elements from 'view'
          // do your rendering for after ajax here
          Ti.API.info('render');
          //Ti.API.info(model.brands);
          };

          return view;
          }
          }(Mvc.Views));

          the RenderAjaxData never fires off.

          (abby.brands.getBrandList is just an abstraction method that makes an ajax call and runs the function passed in the success)

          • Hi John, Hi Rob,

            I’m struggling to create a proper solution for asynchronous requests. Even the above approach doesn’t seem to work for me either.

            Has anyone booked any progress on this they’d like to share?

            Cheers!
            Wouter

  8. This looks really good (just repeating the praise, it’s worth it)! I fetched the latest from Github and also worked on a project with the file downloadable from your website here.

    I’m looking forward to the tabs and deck management which seems to be improving at the moment.

    Thank you for the great work.

  9. Dear John,

    First we want to thank you for this amazing work. Titanium community needs guys like you who push things to the next level and take time to help other dev.
    We have a problem, something that we don’t succeed. In our view we have created a “pull down to refresh” scroll this calls an action from our controller called UpdateData. This action request new data in our db, map the data to our viewmodel and then we have a problem.
    The problem is that we don’t want to return a new view but we want to call a function inside our current view ( or update the current view)

    Thanks a lot, regards,

    Alexis (France)

  10. Hi John,

    I’m using this great framework of yours on a project, on which we need to implement a menu similar to the one on the facebook or path applications, revealed under the main window as it slides to the right ( http://cl.ly/231M0O3v3S2W1r1u1R0Bhttp://cl.ly/1a0Z0K1a0f2X0m1F013B )

    Problem is, I don’t understand well enough how your Mvc.render function works.
    I’ve successfully added some views to the main window inside this function, but it just doesn’t seem right to add specific UI elements in this generic function…

    How would you recommend including app-wide UI components, or using a templating system with this MVC ?

    • Yeah, you are on the right path. I would write a helper function that creates all the UI components as well as a “container” view. Then inside of render instead of the line:

      win.add(ui);

      You do something like:

      var template = CreateTemplateHelper(); // creates the standard view for every page..
      template.add(ui);
      win.add(template);

      If you need additional help on this, just add some source code and I can help you out via email.

      John

      • Thanks a lot for your quick reply, for the moment we have several “templating” functions but we’re gonna re-factor the code to have a unified function and use it as you suggest, as it makes perfect sense : )

  11. Awesome stuff! Now to try it all out :)

    A quick suggestion.. instead of doing this:
    if(!v.Accounts) {
    v.Accounts = {};
    }

    You can use this shorthand:
    v.Accounts = v.Accounts || {};

    One of “the good parts” of JavaScript that I learned from Douglas Crockford’s book with the same name.

  12. This has to do with the fact that the dir viewModels is named “viewmodels”. Change the m of models to M and I guess you would be good to go?

  13. I’m currently just trying to get your example running and when the app loads I get greeted with an error.

    Location: [24,34] models/Accounts.js
    Message: Uncaught ReferenceError: AccountViewModel is not defined
    Source: automapper.createMap(“Account”,AccountViewModel)

    Running appcelerator 1.8.2

  14. How would you access the window from the controller or view? For example if I needed to add a listener for window open…?

    Thanks for your help, and great post by the way!

      • When you navigate from the event:

        this.navigate(“action”, { view: view});

        That will get passed to the controller so you can access the window via that property. If you need access to the window itself, change the Mvc.render function that gets set so that it sets the model being passed in the route data or adds the property to the ‘ui’ parameter.

  15. Hi John,

    Thanks so much for writing this up. It is very great and nice of you :)

    Mickey (Bangkok, Thailand)

  16. hi John

    thanks for the great framework. I am struggeling a little with implementing test against a controller class. Can you please let me know how I can call functions from the accounts controller from another class with is not the accounts model or view.

    Thanks Sam

    • I am not sure what you want to do exactly but it sounds like you would be coupling your code by calling a function in a controller that way. Perhaps you should move the logic to a file containing helper functions and call the code that way.

  17. Hi John,

    Thanks for the very good tutorial.

    I started using the framework and there is one problem (I’m new to Titanium) so your help would be very appreciated:

    how to get an access to currentWindow from the view?

    For example: how to create a date picker which must be added to a window on the Default view from your example?

    I’m aware there are openedWindows[] but could’t find the solution to get a current one (like you can get using Ti.UI.currentWindow).

    Thanks in advance,
    Tom

  18. Hello,
    thank you for sharing your solution about mvc in titanium.

    I’ve a question: how do you manage a view with a list? suppose list items retrieved from net.

    My controller is

    (function(controller) {
    Ti.include(‘/app/views/Home/Default.js’);

    controller.HomeController = {
    Default : function(text, other) {
    var xhr = Ti.Network.createHTTPClient({
    onload: function() {
    var xml = this.responseXML.documentElement;
    var data = [];
    var items = xml.getElementsByTagName(“item”);
    for(var i=0; i<items.length; i++) {
    var item = items.item(i);
    data.push({
    title : item.getElementsByTagName("title").item(0).getText()
    });
    }
    var model = {};
    model.items = data;
    return this.view('Default', model, {tabBarHidden: true, title: 'Home'});
    }
    });
    xhr.open("GET", "localhost/users.json");
    xhr.send();

    }
    };
    }(Mvc.Controllers));

    My solutions doesn't works :( i'm unable to display data.

    I tried to move items download from controller to view, and it works.

    Which solution do you suggest, please?

    Thank you.

    • Try something like this:

      (function (controller) {
      Ti.include(‘ / app / views / Home / Default.js’);

      controller.HomeController = {
      Default: function (text, other) {
      var model = {};
      var xhr = Ti.Network.createHTTPClient({
      onload: function () {
      var xml = this.responseXML.documentElement;
      var data = [];
      var items = xml.getElementsByTagName(“item”);
      for (var i = 0; i < items.length; i++) {
      var item = items.item(i);
      data.push({
      title: item.getElementsByTagName(“title”).item(0).getText()
      });
      }
      model.RenderNewItems(data);
      }
      });
      xhr.open(“GET”, “localhost/users.json”);
      xhr.send();
      return this.view(‘Default’, model, {
      tabBarHidden: true,
      title: ‘Home’
      });
      }
      };
      }(c);

      Then in your view you set the RenderNewItems function to a rendering call like this:

      // some rendering code
      var container = Ti.UI.createView();
      model.RenderNewItems = function(items) {
      for(var i = 0; i < items.length; i += 1) {
      container.add( …..);
      }
      };
      return container;

  19. There is a problem when you intend to show 2 times the same view. This is the flow

    1) display view A
    2) click a button in view A to go to view B
    3) go back from view B to A
    4) click again on button in view A to go to view B ->>>>> It doesn’t work.

    Any idea?

  20. Hi John,

    Thanks for the very good tutorial.

    I started using the framework and there is one problem (I’m new to Titanium) so your help would be very appreciated:

    In the next link talk about the correct use of “Ti.App”, and they say that

    “Additionally, you should not modify the Titanium proxy objects, such as Ti.App, by adding properties or methods.”

    In your sample you use this code, “Ti.App.dal”

    Could have bad consequences for my project uses similar code extending the method dal from Ti.App?

    http://docs.appcelerator.com/titanium/2.1/index.html#!/guide/MVC_Style_App_Framework

    thanks and Best regards

    PS: Sorry About the English..

  21. Hi,

    you say in the winows stack manager that we can access to the current window in the view :

    // return the window incase you need it for something in your view

    Can you please tell us how?

    Tk u very much !!!!

    • Hi,

      I haven’t touched Appcelerator in a long time as we decided to go a different route with our apps. I took this framework off the marketplace as I do not have time to update it or support it.

      I’m sorry, I can’t remember how to gain access to the view but I am assuming I pass it in with the model when the view is rendered.

      John

      • Ok tks a lot, we use your framework for our app it is cool :-) we will migrate to commonJS and alloy. Maybe i am a little bit curious but what you use now to developp your apps?

        Tk Again !!!!!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>