My First MontageJS Application

Posted on May 24, 2013 | Comments Off

logo-montage I saw MontageJS presented at 360|MIN in Las Vegas last year by Tom Ortega. I have been meaning to give it a try and finally got around to it.

What is MontageJS?

In short it is an “HTML5 framework for building modern Web Apps.”

To start explaining what MontageJS is in more detail some history is needed. Lets first take a look at who is behind MontageJS. Here are the current members on GitHub. If you drill down into their backgrounds you see a team that has worked for Apple, Motorola Mobility, and Google. Even though the framework has only been open source since last year there is a lot of application development encompassed in this team.

From a technical standpoint MontageJS is a MVC framework that keeps separation at the forefront. It provides various features to allow the developer to accomplish great separation and component composition. For example, 2-way binding, component templates, object serialization with DOM mapping, event management, and component draw cycle management. Another technical point is that MontageJS fully supports CommonJS Modules 1.1.1, Packages 1.0, and a subset of Package Mappings proposal C.

It is also important to point out that Montage also has some tools to help with development and bundling for production. These are call minit and mop.

A list of technical features does not really help explain what a framework does for a developer. Let’s dive into some examples to get a better feel for what MontageJS is. In the getting started guide on montagejs.org it shows you how to install minit to create an app and some components. I think it is important to explain what the basic MontageJS component is and how it is structured before using a tool to set it up for you.

Employee Directory MontageJS Example Files for this post

This example was a port of a backbone.js + bootstrap application called Employee Directory written by Christophe Coenraets

You can see the application running here: http://renaun.com/html5/EmployeeDirectory/

Employee Directory Sample Image

Source code on GitHub here: https://github.com/renaun/montagejs-examples/tree/master/EmployeeDirectory
The GitHub readme page has the instructions to install the source code and install the needed MontageJS bits.

Explaining MontageJS

A montage component is usually made up of three files located in a folder by itself, using MyComponent as the sample: my-component.css, my-component.html, my-component.js. You can have a component that doesn’t have a template and css which means it would not have the .html or .css file but just the .js file. These components are put into a folder with a “.reel” suffix. You typically (minit does this for you) put them in a my-component.reel/ folder. You could conceivable name things as you want but the above is the MontageJS default way. Lets first look at the .html page of a component. The idea of this page is to use normal HTML to create your template and maybe montage components into that normal HTML DOM. Here is the full source code of an example .html part and a image showing the mapping:

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <link rel="stylesheet" type="text/css" href="employee-list-item-view.css">
    <script type="text/montage-serialization">
    {
        "owner": {
            "properties": {
                "element": {"#": "employee-list-item-view"},
                "itemLinkElement": {"#": "itemLink"}
            }
        },
        "firstName": {
            "prototype": "montage/ui/dynamic-text.reel",
            "properties": {
                "element": {"#": "firstName"}
            },
            "bindings": {
                "value": {"<-": "@owner.data.firstName" }
           }
       },
       "lastName": {
           "prototype": "montage/ui/dynamic-text.reel",
           "properties": {
               "element": {"#": "lastName"}
           },
           "bindings": {
               "value": {"<-": "@owner.data.lastName" }
           }
       },
       "title": {
           "prototype": "montage/ui/dynamic-text.reel",
           "properties": {
               "element": {"#": "title"}
           },
           "bindings": {
               "value": {"<-": "@owner.data.title" }
           }
       },
       "employeeImage": {
           "prototype": "montage/ui/native/image.reel",
           "properties": {
               "element": {"#": "employeeImage"}
           },
           "bindings": {
               "src": {"<-": "@owner.employeeImageSrc" }
           }
       }
   }
   </script>

</head>
<body>
    <li data-montage-id="employee-list-item-view" class="EmployeeListItemView">
        <a id="itemLink" href="javascript:void(0);">
            <img data-montage-id="employeeImage" width="50" height="50" style="float:left;margin-right: 10px;"/>
            <p class="list-item"><span data-montage-id="firstName"></span> <span data-montage-id="lastName"></span><br /><span data-montage-id="title"></span></p>
        </a>
    </li>
</body>
</html>

Montage Template DOM Mapping

MontageJS uses a JSON object serialization tree defined in the head of your html template using a script tag with a attribute of type=”text/montage-serialization”. The object serialization and custom data attribute “data-montage-id” do not invalidate the .html, which means the file can be designed as a valid html page by designers/html developers without any specialized Montage knowledge or tools. But in terms of MontageJS this process of mapping an object tree into the DOM elements keeps the separation between the view and your controller/model. This can become a bit verbose as even simple labels are considered a component and need a mapping. There is a trade off with being verbose but then having fine control of each component. When you create more complex or deeper component composition it does make it nice to have strong separation. So thats what the .html file of a component is.

Now lets talk about the .js part of a MontageJS component. I was a bit confused about the relationship between the .html’s Montage object serialization tree and the .js file when I started building out my first MontageJS application. I finally boiled it down to the .html file does the mapping of DOM elements to Montage compatible component ids, not component properties per se. It is in the .js part of the component that you setup your class properties, methods, and logic. The .js file drives all the actual data of a component but isn’t supposed to know much about the DOM tree. Even if you want to bypass any Montage component logic and do normal JS/HTML manipulation you should map a DOM element to a Montage component object in the .html object serialization tree. In the object serialization JSON tree the “#” identifier refers to DOM elements, and the “@” identifier refers to template objects (or the other components defined in the object serialization). This makes the Montage object serialization a real graph of pointers to Montage objects or DOM elements. Now the confusing part is that you can, and probably will, do a bunch of logic that you would think should go in to the .js part of the component through binding and listeners directly into the .html’s object serialization JSON.

Take this snippet from the the employee-list-item.view.html file:

"firstName": {
    "prototype": "montage/ui/dynamic-text.reel",
    "properties": {
        "element": {"#": "firstName"}
    },
    "bindings": {
        "value": {"<-": "@owner.data.firstName" }
    }
}

The “firstName”: field defines the id that the Montage component will work with. The prototype defines the class that this object will be created as, in this case it is a Montage component called “dynamic-text”. The properties -> elements maps this dynamic text component to the DOM HTML element that has the data-montage-id attribute with the same name. The bindings section allows you to use the Montage binding syntax (some cool new functional reactive bindings stuff is coming in the edge branch of Montage) to properties of the your component. In this case most Montage components have a property value, but if this was not a dynamic text component but your own custom component the “value” property could be specific to that component. The @owner.data.firstName is saying that the owner template object’s data.firstName value should be bound to the firstName.value component property. The owner is defined in each montage component to refer to the component instance that will load the template. The element property of owner component maps to the DOM element of the template, and is the reference to any properties defined in the .js part of the component. In this case @owner.data means there needs to be a data property in the .js component part. Lets take a look at that part.

Here is the employee-list-item-view.js file:

/**
    @module "ui/employee-list-itemview.reel"
    @requires montage
    @requires montage/ui/component
*/

var Montage = require("montage").Montage,
    Component = require("montage/ui/component").Component;

/**
    Description TODO
    @class module:"ui/employee-list-item-view.reel".EmployeeListItemView
    @extends module:montage/ui/component.Component
*/

exports.EmployeeListItemView = Montage.create(Component, /** @lends module:"ui/employee-list-item-view.reel".EmployeeListItemView# */ {
    templateDidLoad: {
        value: function(event) {
            var scope = this;
            this.itemLinkElement.addEventListener("click",function(event){
                scope.dispatchEventNamed("selectedEmployeeEvent", true, true, {employee: scope.data});
           }, false);
        }
    },
   
    employeeImageSrc: {
        value: ""  
    },
    _data: {
        value: null
    },

    data: {
        set: function(val){
            this._data = val;
            if (val)
                this.employeeImageSrc = "pics/" + this._data.firstName + "_" + this._data.lastName + "50.jpg";
        },
        get: function(){
            return this._data;
        }
    }

   
});

You see there is a data: property as part of the JSON object tree passed into the constructor of the Montage.create method to create the EmployeeListItemView class. In this property there is a setter and getter (not needed) but it shows that this is also a place to set values on the template objects (instead of using binding).

Now that we are in the .js part of the component lets talk about some of the Montage component lifecycle methods. There is a whole section on the Montage component life cycle and also you can look at the API docs of the components. Some ones you come across will come across early on are templateDidLoad, prepareForDraw(), and draw(). You can read up on them more in the link above. The other prescribed Montage method convention you’ll want to understand before getting to far is with events, you can read about it here. A basic explanation is you setup the listener in the .html’s object serialization. Here is some a code snippet:

"openSearchBtn": {
    "prototype": "montage/ui/native/button.reel",
    "properties": {
        "element": {"#": "openSearchBtn"},
        "identifier": "openSearchButton",
        "navid": "openSearch"
    },
    "listeners": [
        {
            "type": "action",
            "listener": {"@": "owner"}
        }
    ]
}

And in the .js component part you would define a method with the name handleOpenSearchButtonAction. Montage takes the name of the component capitalizes it and adds “handle” to the beginning. Another example of the prescribed method MontageJS convention is captureOpenSearchButtonAction would relate to the capture phase of the event. The code snippet above also highlights the “identifier” property, this property sets the MontageJS component id for this component. By default it uses the “openSearchBtn” object property, unless the object has an “identifier” property.

The .css part of the component is pretty straightforward. You put any css that is relative to the component itself in that file.

Hopefully that gives you an idea of what a montage component is. The minit command creates the scaffolding for new components.

Observations During the Porting Process

In the porting process of my example it was easy to use minit and create the same components in the backbone.js example: home, contact, employee-view (I combined summary into this component), and employee-list-item. I was able to copy over the html templates from the original app and make minor changes from the <%= someVar %> syntax to using data-montage-id. Now there was still the step of adding the template components for each data-montage-id, which did feel a bit verbose, but it wasn’t hard at all.

I was able to include the bootstrap css files into the main app index.html file and all the html then looked the same.

MontageJS doesn’t have any deep linking implemented and I could have tried to write one in JS but decided not to mess with that for this port. In that regard I had to manage page changes and state differently but it just means that part wasn’t a port but new code to change the screens and show/hide Montage components. In this case I used events to call into a function and set the switchValue of a Montage substitution component in the main component template. The rest of my time was just trying to figure out different MontageJS components, event management, and binding.

This was a little hard because of the lack of good documentation. There is a lot of example code, a getting started guide, etc… but for what seems like simple questions I had to head to the irc to get answers. That being said the MontageJS team hangs out on irc and answers question pretty fast. Talking with the MontageJS guys they know that the documentation is lacking and are actively working on trying to fix that issue.

Final Thoughts

MontageJS is a pretty verbose framework, with such verbose and prescribed separation patterns there are both pros and cons to such an approach. It does allow devs to know what is where as its all prescribed into a .reel folder. And if you use binding correctly it is nice to build components that are reusable and self encapsulated. Although in my example I didn’t do anything complex I did see the framework built on top of a great component lifecycle means it setup with performance in mind (yes even browsers have rendering performance issues).

It will be interesting to see what else comes out of the MontageJS group as they work on updating the framework with things like functional reactive binding and new out of the box components.