Z-Scale controller part VIII: modelling our data

We laid the foundation for our app in the previous article. The next step is now to create a data model to make it run smoothly.

High level logic of the application

The application lets the use create “locomotives”, where we track a few informations such as picture and description, as well as a runtime and maintenance log. We also manage “layouts”: a layout contains a layout diagram, and a list of controllers and accessories.

In a standard session, the user will select a locomotive and a layout, connect to the controller, and then drive the train & accessories. With that high level view in mind, we can define what screens are required to support this:

Application screens

Below is a diagram of all the screens that make up the application:

Train controller app screens

The home screen is the screen where you control the trains and accessories. There are a few utility screens such as diags or settings (I did not show the “About” screen on the diagram, as it is fully static).

Then both “loco” and “layout” screens follow a similar hierarchy: first a “list” screen, leading to a “details” or “edit” screen for editing or adding new locos/layouts.

Data models

At this stage, the basic types of data that will have to be persisted in the database and that will be managed by the app are becoming clear:

– “Locomotives”
– “Layouts”
– “Settings”, as discussed in article XXX

A bit less obvious, a few additional data types also make sense – this corresponds to data that needs to be persisted and is linked to locomotives or layouts:

– Accessories: layouts contain lists of accessories
– Controllers: layouts contain controllers – only one in the first version of the app
– Maintenance logs

All those model definitions on the Backbone side are stored in the “models” directory of the web app.

All models in our application look a bit similar, as they are pretty much standard Backbone models. I borrowed heavily from Christophe Coenraets’ “Wine Cellar” application when I started this app, so you will notice familiar patterns if you read that tutorial too.

How models are implemented

I will use the “Layouts” model as an example.

window.Layout = Backbone.Model.extend({

    urlRoot: "/layouts",
    idAttribute: "_id",

...

    defaults: {
        name: "Layout name",
        description: "Enter your description here",
        picture: null,
        controllers: [],
        accessories: []
    }
});

As you can see in that code, we simply define the path on the server when that model’s data can be queried using RESTful calls – in this particular case, “/layouts”. We also define the name of the “ID” field. In MongoDB, this is “_id”.

Last, we define a set of default values, this is especially useful when creating new model instances. Note that it is absolutely not necessary to define default values, and that this set of defaults is not a schema. Nevertheless, I found that it is good to have one place in the code when I could keep track of the way data is organised, and this was the natural location.

You will see in the code that the model is also in charge of validation. I have not put a lot of emphasis on this in this prototype app so I won’t comment on it either.

With each model where there can be several instances, we also define a collection, as shown here:

window.LayoutCollection = Backbone.Collection.extend({

    model: Layout,
    url: "/layouts"

});

… and that’s about it! This matches REST calls on the backend directly, and we can now create models in our Backbone application, that can be very easily synchronised with the backend database.

Application views

The last step is to define the views that correspond to each application screen. In very simple “Hello World” style applications, we usually have a “one screen / one view” match, but in our case, the application is a bit too complex for this, especially for the home screen: that screen shows layouts, controllers, accessories, locomotives, and so on, so we need to build a “composite” view there, that is, a mashup of “Run mode” views of the various models.

The app views can be summarised as shown on the diagram below:

Train controller app views

In Backbone, views contain a lot of logic beyond the simple task or displaying information. That task is actually handled in the “render()” method. In that respect, they are closer to “controllers” in MVC terminology, and the actual MVC “view” is closer to the template that is used by the View to output HTML content.

Simple views and templates

For someone just starting on Backbone, it can be very confusing to implement views, because Backbone does not enforce any particular way of managing how a view should generate its HTML output. Backbone is, by design, very lightweight, which is great once you get going, but tricky at first. In particular, Backbone is built on top of another lower level library called Underscore.js, and assumes that the reader is familiar with its functionality. This is especially true when it comes to templates.

So what are templates? As the name implies, those are bits of HTML code that contain tags that are resolved either as javascript variables or run as javascript code – a bit like JSP if you are familiar with that world. This functionality is provided by a small utility part of Underscore.js in the Utility functions. Note that Backbone explicitly mentions other templating libraries which provide more sophisticated features, but taking into account the time I had to do all this, and the quantity of stuff I had to learn along the way, I decided to stick with that was built in.

Christophe Coenraets described an elegant way to decouple templates from the javascript code, in order to maintain a bit of order in the source code, and I reused it with great success. You can refer to his article which describes his template loader better than I could.

This means that for our application, we load all templates at the end of the “main.js” application initialisation, and store all templates in the “tpl” directory in the code:

utils.loadTemplate(['HomeView', 'HeaderView', 'AboutView', 'LocoView', 'LocoListItemView', 'LayoutListItemView', 'LayoutView',
                    'ControllerDetailsView', 'SettingsView', 'LayoutRunView', 'LocoRunView', 'ControllerRunView', 'AccessoryDetailsView',
                    'AccessoryItemView', 'DiagnosticsView', 'AccessoryItemDiagView'
                   ], function() {
    app = new AppRouter();
    Backbone.history.start();
});

Conclusion

We now have a good data model for our application, that syncs with the backend with no effort, and a good idea of the screens and views that we will require. The next article will focus on some of the interesting parts of the implementation that are worth mentioning, you can read all about it here.

Leave a Reply

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