When I was first starting to program TapNote I did a lot of reading through Palm’s documentation and other WebOS developer help sites, but I never was able to find any good information on how to set up a data model.
Turns out that there aren’t any official recommendations. Palm mentions the app/models
folder mainly just to get you thinking about using an MVC design for your app, I suspect.
While I designed the architecture of my app, however, I thought a fair amount about how to break down the logic, and I ended up coming up with some guidelines that may help other developers with their app design. Of course, this is probably pretty obvious stuff for people who design with MVC regularly, but I would have found it useful when I was starting out with WebOS programming (particularly because it was complicated by WebOS’s persistently asynchronous nature).
Knowledge and responsibilities
The controller (or assistant, in WebOS parlance) knows these things:
- The interface for the model
- The expected Javascript object schema for the data
- Details about the view elements (views being the HTML files for the scene)
The controller’s responsibilities are:
- Querying the model for new data when needed
- Updating view items with data once the model sends it over
The model (which should be either a Prototype class or a unique object stored in your app/models
directory) knows these things:
- Exactly what storage system is being used for the data
- How to interact with the storage system (SQL queries or whatever else)
- The Javascript object schema for the data
The model’s responsibilities are:
- Gathering data when it is requested
- Transforming the data from its raw format into the proper Javascript object
- Returning the data using the provided callback
A decent metaphor is that of two coworkers in neighboring cubicles. The controller asks for something from the model, and tells it where to send the results. The model does what it needs to do to gather those results, then sends them where the controller requested. Neither can see into the other’s cubicle to know what exactly what their coworker is doing, but neither one needs to. Either model or controller can change what they’re doing with the data (how it’s stored or how it’s displayed), but this won’t affect their relationship to one another.
If you ever have something in the controller that depends on knowing what type of storage system is used for the data, you are doing it wrong. Similarly, if you ever have anything in the model that references or needs to know about specifically what view items are being populated with data, you need to rethink your architecture.
Basic layout
Unlike assistants which are automatically loaded by Mojo when their scene is pushed, you will need to add your model class to your app’s sources.json
file by hand:
[
{"source": "app/models/myDataModel.js"},
{
"scenes": "Myscene",
"source": "app/assistants/myscene-assistant.js"
},
]
What this does is expose the contents of myDataModel.js
globally to your app (so be careful with your globally scoped variable names!).
Typical round trips
Based on TapNote’s design (in which I tried to follow the above guidelines), this is a typical function layout for the controller:
- Request function: this is typically triggered by an event in the view, and all it does is request something from the model (passing along any relevant info from the event, along with the callback function reference)
- Callback function: this accepts data from the model, and does whatever needs to be done with it in the view.
A typical controller would thus look like this:
var MysceneAssistant = Class.create({
/* Standard function for Prototype classes */
initialize: function() {
this.model = new MyDataModel();
// Always save static bound references to member functions
this.bound = {
populateData = this.populateData.bind(this)
};
},
/* ... Setting up events etc. happens here ... */
/* This function is triggered by an event */
refreshData: function(event) {
// Any necessary logic prior to requesting data
this.model.refreshData(this.bound.populateData);
},
populateData: function(dataObject) {
// Handle populating the view with the data object here
}
});
My model ended up being very similar, since it too had an asynchronous call (although if you were accessing data in a depot or cookie you might not need two functions):
- Request function: receives requests from the controller, builds the necessary SQL (or whatever) and executes it, binding the callback into the success function of the datastore
- Return function: processes the successful results from the query, and sends the results of processing to the callback
Using the previous example code for the controller, here’s how our model class would look:
var MyDataModel = Class.create({
/* ... Set up the model here ... */
refreshData: function(callback) {
// Set up the SQL, etc. here
// Query the database here binding in our unique callback
this.db.query(query, {
onSuccess: this.returnData.bind(this, callback)
});
},
/* Because of the on-the-fly bind, the callback is the first arg */
returnData: function(callback, results) {
// Transform results into Javascript object; simple example:
var resultObject = results[0];
// Send the object to the controller
callback(resultObject);
}
});
Unlike the controller, in the model we have to do the binding on the fly because otherwise we can’t pass the callback through. If you can guarantee that there will only ever be one controller requesting data at any given time, you could instead use a this.bound
object like the controller and save the callback to a class variable (for instance, this.callback
).
Brief side note: you may notice the unexplained this.db.query()
call above. When I was writing TapNote, one of the first things I did was write a generic Database class to abstract away from the heinous syntax of the HTML 5 database connection. This is a generically useful class that I’ve released for free.
Implementing in your own app
The above examples certainly are not the only way to do things (I actually deviated from that design a bit for some data responses when TapNote became a multi-stage app partway through development), and you will likely find yourself changing things up based on the needs of your own app. However, if you start with this design as a basis and keep in mind the roles and responsibilities of model and controller, then you will hopefully create an app whose code is easy to debug and expand on down the road.