In Part 2 of this series we created the ability to use a cookie for a lists data source. In this part we are going to clean-up our code a bit and try to restructure things so that it is easier to expand on our set of code.
After looking at the code I saw that we created the WBCookie model object that was not very abstract. We wish to keep things somewhat abstract so that the application is not concerned too much about where the data is coming from. We are going to be creating a few files that will be in our lib/ directory and I will explain why we are using them and what they do as we continue through the tutorial.
The first thing I want you to do is open up your sources.json file and add 3 lines at the top. We will want to include the files that we will be creating so that Mojo will know where to find them. Open your file and make it look like the following. Note that I only added the first three lines to this file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
[ {“source”: “app/lib/utils.js”}, {“source”: “app/lib/WBModel.js”}, {“source”: “app/lib/WBBase.js”}, {“source”: “app/lib/WBCookie.js”}, {“source”: “app/assistants/stage-assistant.js”}, { “source”: “app/assistants/main-assistant.js”, “scenes”: “main” }, { “source”: “app/assistants/cookie-assistant.js”, “scenes”: “cookie” }, { “source”: “app/assistants/depot-assistant.js”, “scenes”: “depot” }, { “source”: “app/assistants/sqlite-assistant.js”, “scenes”: “sqlite” }, { “source”: “app/assistants/altervalue-assistant.js”, “scenes”: “altervalue” } ] |
We have also created a utility function named in_array which works just like that function in PHP. JavaScript does not have this capability natively so we needed to add it for our use. Create the utils.js file under our lib/ directory and make it have the following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
/* Utility Functions */ function in_array (needle, haystack, argStrict) { // http://kevin.vanzonneveld.net // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // + improved by: vlado houba // * example 1: in_array(‘van’, ['Kevin', 'van', 'Zonneveld']); // * returns 1: true // * example 2: in_array(‘vlado’, {0: ‘Kevin’, vlado: ‘van’, 1: ‘Zonneveld’}); // * returns 2: false // * example 3: in_array(1, ['1', '2', '3']); // * returns 3: true // * example 4: in_array(1, ['1', '2', '3'], false); // * returns 4: true // * example 5: in_array(1, ['1', '2', '3'], true); // * returns 5: false var key = ”, strict = !!argStrict; if (strict) { for (key in haystack) { if (haystack[key] === needle) { return true; } } } else { for (key in haystack) { if (haystack[key] == needle) { return true; } } } return false; } |
Now that we have our utils.js out of the way it is time to create your WBModel.js file under lib/ and make it look like the following.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
function WBModel(storageType) { /* * Default storageType * * This is set so that if the developer does not send in a storageType type value it will default to a cookie. * It will also default to cookie if the storageType type specified is not in our supported list of supported storageType types. */ this.defaultStorageType = ‘cookie’; this.supportedStorageTypes = [‘cookie’]; // set the initial storageType type to the default storageType type this.storageType = this.defaultStorageType; // check to make sure that our specified storageType type is in our supported storageType types – The in_array function is in our utils.js file. if(in_array(storageType, this.supportedStorageTypes)) { // set the storageType type to the specified storageType type. this.storageType = storageType; } else { Mojo.Log.info(“Unsupported storage type ‘%s’ using the ‘%s’ as default”, storageType, this.defaultStorageType); } return this.factory(storageType); } WBModel.prototype.factory = function(storageType) { switch(this.storageType) { case ‘cookie’: default: // Cookie storage by default return new WBCookie(); } } |
With our new WBModel object we have the ability to use any type of data source we wish to use all we have to do is tell it which one we want. In the constructor we set the defaultStorageType to be a cookie and we also set the array of our supported storage types. If palm were to introduce another storage mechanism in the future it would be pretty easy to add it to this application. The main portion I want to point out is the in_array function call. JavaScript does not have a native method of checking to see if an item is in an array or not without looping over everything each time. I added a utility function that I found online called in_array which mimics how PHP’s in_array function works. This allows us to check to make sure that the developer sent in a valid storage type before we try to get very far in the code. If we do find that a developer is trying to use an unrecognized storage type we write a nice message to the log and use the defaultStorageType instead.
You will notice that we have a factory method for the WBModel object. This allows us anywhere in our code to call WBModel.factory(‘depot’); and have it return to us a depot object model. If you would like to read more about this method you can read about the factory pattern in JavaScript.
So now that we have our WBModel object out of the way it’s time to take a look at the last object i have created. The WBBase object will never actually be instantiated directly. This object is used as the base for all of our model classes and the functionality it provides is inherited by our model classes. Create the file WBBase.js in your lib directory and put the following code in the file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
function WBBase() { this.defaultContents = [ { title: “Default 1″ }, { title: “Default 2″ }, { title: “Default 3″ } ]; this.contents = []; } WBBase.prototype.getListContents = function() { return this.contents; } WBBase.prototype.setListContents = function(listContents) { this.contents = listContents; } /** * The folowing are methods that should be overridden in the objects that extend from WBBase * Think of them as abstract methods. */ WBBase.prototype.save = function(listContents) { Mojo.Log.error(“You have failed to override the save method in your model object.”); }; |
You might notice that this object does not have a lot of code and it does not have to have much because this functionality is inherited. In the constructor I am setting the defaultContents to be our default items which we hard coded in place. There are two methods that get inherited and they should be pretty explanatory as they are just for setting and getting the listContents. There is one method that I want to talk about and that is the save method. You might be wondering why we have a log line in there stating that you failed to override the save method. This is because each model object should know how to save it’s data this is not something that can be inherited. I have this method in place here because if a developer fails to put a save method in their model they will have something in the log alerting them to that fact. This might look familiar to you if you are familiar with abstract methods from other languages.
Now we need to alter our WBCookie object so that it will work with the code we just created. Open the WBCookie.js file and make it look like the code below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// Inherit from WBBase(); WBCookie.prototype = new WBBase; function WBCookie() { this.cookie = new Mojo.Model.Cookie(‘wb_cookie_list_demo’); this.contents = this.cookie.get(‘listContents’); if(this.contents === undefined) { this.contents = this.defaultContents; } } WBCookie.prototype.save = function(listContents) { this.cookie.put(listContents); }; |
In our WBCookie object we have greatly reduced the code actually necessary for interacting with our cookie. The first thing I want to note is the very first line where we set the prototype to be assigned a new instance of the WBBase object. In JavaScript this is just one of the ways that you can tell it you are going to inherit the functionality from the WBBase object. You can see that we open our cookie in the constructor and set it to this.cookie and in our get method we just grab the data from the cookie. The final method is the save method and you can see that we accept a parameter. It is the listContents object which we want to save so we pass it to the this.cookie.put method which puts it in our cookie. Pretty basic huh!
The final change I made was to rename our altercookie scene and assistant so that it could be used with any of our data storage mechanisms. Go ahead and rename /assistants/altercookie-assistant.js to /assistants/altervalue-assistant.js and make sure the code looks like the following.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
function AltervalueAssistant( storageType, index, listContents ) { this.itemIndex = index; this.listContents = listContents; this.storageType = storageType; } AltervalueAssistant.prototype.setup = function() { this.model = new WBModel(this.storageType); /* set up our text field */ var txtFieldAttributes = { hintText: ”, modelProperty: ‘original’, autoFocus: true }; this.txtFieldModel = { ‘original’ : this.listContents[this.itemIndex].title, disabled: false }; // Setup our Add Value and Event Handler this.saveBtnAttributes = {}; this.saveBtnModel = { buttonLabel : ‘Save’, buttonClass : ‘affirmative’, disabled : false }; this.controller.setupWidget(“save_button”, this.saveBtnAttributes, this.saveBtnModel); this.controller.setupWidget(‘textField’, txtFieldAttributes, this.txtFieldModel); this.saveHandler = this.save.bindAsEventListener(this); Mojo.Event.listen(this.controller.get(‘save_button’), Mojo.Event.tap, this.saveHandler); }; AltervalueAssistant.prototype.save = function(){ // change the item and then store the model again this.listContents[this.itemIndex].title = this.txtFieldModel.original; this.model.setListContents( this.listContents ); // swap back to the storageType scene Mojo.Controller.stageController.popScene(); }; AltervalueAssistant.prototype.activate = function(event) { this.txtFieldModel.original = this.listContents[this.itemIndex].title; this.controller.modelChanged(this.txtFieldModel); }; AltervalueAssistant.prototype.deactivate = function(event) { }; AltervalueAssistant.prototype.cleanup = function(event) { Mojo.Event.stopListening(this.controller.get(‘save_button’), Mojo.Event.tap, this.saveHandler); }; |
You will also notice that we have not changed this too much other than changing the object name. We did introduce a few new things and that is that we now pass in the storageType when we make a call to the altervalue object. This is so that we can actually make sure the proper model is being updated. We are also passing in the list contents and I am doing this because it is much easier to implement by passing the value in and when we get to Part 4 and using the Depot you will understand why. In the save event handler I also changed the swapScene to a popScene. There was a small bug with the way I had it before that caused you to have to swipe back twice rather than once to get back to our main list. Using popScene rather than swapScene just pulls the alter value scene off the top of the stack. Next you will need to rename the scene folder and phtml file to be /altervalue/altervalue-scene.phtml.
Now since we changed the swapScene to a pushScene we have introduced another small bug. When you are on the altervalue scene and hit the button to save the value it does just that. It saves the value in the proper storage mechanism and then pulls the altervalue scene off the top of the stack. The scene you see now is the cookie-scene showing the list of values stored in the cookie. However the bug is caused because you never update the cookie-scene to tell it that the model data has been changed. We need to add some code to our assistant’s activate method so that when the scene is activated we update the view with the data from our model. Please make your cookie-assistant.js file look like the following.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
function CookieAssistant() { this.listModel = {} this.model = null; } CookieAssistant.prototype.setup = function() { this.model = new WBModel(‘cookie’); this.listModel = { items: this.model.getListContents() }; this.controller.setupWidget(“cookieListWgt”, { itemTemplate: “cookie/cookieRowTemplate”, listTemplate: “cookie/cookieListTemplate”, swipeToDelete: false, renderLimit: 40, reorderable: false }, this.listModel ); this.cookieListHandler = this.loadAlterValueScene.bindAsEventListener(this); this.controller.listen(this.controller.get(“cookieListWgt”), Mojo.Event.listTap, this.cookieListHandler); }; CookieAssistant.prototype.loadAlterValueScene = function(event) { Mojo.Controller.stageController.pushScene( ‘altervalue’, ‘cookie’, event.index, this.listModel.items); }; CookieAssistant.prototype.activate = function(event) { this.listModel.items = this.model.getListContents(); this.controller.modelChanged(this.listModel); }; CookieAssistant.prototype.deactivate = function(event) { }; CookieAssistant.prototype.cleanup = function(event) { Mojo.Event.stopListening(this.controller.get(“cookieListWgt”), Mojo.Event.listTap, this.cookieListHandler); }; |
You can see that we have changed this quite a bit since Part 2 of this series. First in our setup method we are instanciating our WBCookie object and storing it in the this.cookie property. We are then calling the getListContents method on our WBCookie object and using that as the items for our listModel. The next thing we did was rename the loadAlterCookieScene method to loadAlterValueScene so that we conform to the changes we made earlier. The rest of the code has remained the same. Now that we have our alterations out of the way you should be able to run this in the emulator and work your way through the cookie list as you could in the end of Part 2. You may be asking why the heck we did all of these alterations to our code when we have not yet introduced any new functionality. Well the reason is because after looking at the code planning out this part of the series I thought we could make it much cleaner by using this method. Now all we have to do in order to add a new storage mechanism is create the object code and alter our WBModel.js object to allow for that storageType.
With these changes out of the way we are free to move on and add more storageTypes which we will cover in the next part of this series. Part 4 will show you how to use the depot as a storage mechanism.