sábado, 10 de outubro de 2015

In the way to a scalable application with Marionette

One of lessons learned while building my first javascript mobile application was the need to use a framework on top of Backbone. Marionette is the natural choice given it’s popularity and active development.

I bought, and read, David Sulc’s introductory book for Marionette. It’s a great resource to get in touch with the framework but one of the topics that falls short is the structure, based in modules, a deprecated feature and discarded even by it’s creator. There’s a follow up book that explains how to use RequireJS with it.

After writing a working example i felt the next step was build a full application, even if small. It should use a sane architecture to serve as base for future larger applications.

The search for reference applications

I remembered that the Marionette homepage has links to some example applications so i looked into them for possible starting points. Edit was discarded because of CoffeScript, Streamus seems tied to Chrome extension. Both Gistbook and Marionette Wires impressed me by its modular, organized architecture. In the down side they use specialized components (router) and Babel/ES6 and i don’t want, at my current stage, to learn so many new technologies/tools at same time.

As a last resort, i evaluated TodoMVC. A quick look at the code showed that is not what i was looking for. It uses global variables (although well organized into a namespace) and lacks a few common iterations like page transitions.

Kicking off

Since none of the example applications could be used as starting point, i setup the project from scratch. After the usual installation of packages and configuration of module loader (Webpack), i got stuck at the application bootstrap/start.

In the Application section of the Marionette documentation, the Application Regions feature is marked as deprecated and suggest to use a root LayoutView, something like:

var RootView = Marionette.LayoutView.extend({
  el: 'body'
});

//followed by

app.rootView = new RootView();

I did something similar…

var RootView = Marionette.LayoutView.extend({
  el: 'body',
  regions: {
    'header': '#app-header',
    'content': '#app-content'
  }
});

var Application = Marionette.Application.extend({
  initialize: function () {
    this.rootView = new RootView({});
  },
  onStart: function() {
    console.log('app start');
  }

});

//followed by

var app = new Application();
app.start();
app.rootView.showChildView('content',new MyView());

… and got an error message:

Error: An “el” #app-content must exist in DOM

After a while i figured that i have to put the script at the end of html file or wrap the initialization code in $.ready handler. It took some time to show the first view, but is rewarding anyway.

Decouple roads

To write a scalable application is necessary a mechanism to navigate through it. While a basic requirement is also a hard one to implement in a decoupled, flexible and uniform way. Below a some patterns i found in the above examples:

  • Execute a method or trigger an event in the app instance. The example of A Gentle Introduction book does this. But in a not uniform way.
    Sometimes triggers an event
contactsListView.on("childview:contact:show", function(childView, args){
   ContactManager.trigger("contact:show", args.model.get("id"));
});

…sometimes updates the application regions directly.

contactsListView.on("childview:contact:edit", function(childView, args){
   var model = args.model;
   var view = new ContactManager.ContactsApp.Edit.Contact({
     model: model
   });
   //...

  ContactManager.regions.dialog.show(view);
});

This pattern has the drawback of tying to the application instance and interface.

  • Use hyperlinks and let the router do the actual work. This approach is used in Marionette Wires example:
  //a  book route is defined
  routes: {
    'books/:id' : 'show'
  },

  show() {
    return new ShowRoute({
      layout: this.layout
    });
  }
  
  //in the view a hyperlink is created for each book 
  export default ItemView.extend({
    template: template,
    tagName: 'a',
    attributes() {
      return {
        href  : `#books/${this.model.get('id')}`
      };
    }
  });

This pattern has some criticism that may not be the case in the specific app since it uses an customized route to take care of state. Anyway it requires a pretty opinionated architecture which is no-go for me at least for now.

  • Uses a pub/sub mechanism. Gistbook uses Radio to request a view to be show. Pretty simple and straightforward:
  //configure the handler 
  var RootView = LayoutView.extend({
  initialize() {
    var containerRegion = this.getRegion('container');
    Radio.comply('rootView', 'showIn:container', containerRegion.show, containerRegion);
  }
});
 //later do the request
  export default Route.extend({
   show() {
    var gistView = new GistView({
      model: new Gist(),
      ownGistbook: false,
      homePage: true
    });
    Radio.command('rootView', 'showIn:container', gistView);
 }
});

Gistbook also uses an specialized router to take care of state. In fact i could not get the full picture of how it works. Anyway, using pub/sub pattern seems a good idea even if used without a state router.

With base structure done, is time to some coding. Let’s see results.

Nenhum comentário:

Postar um comentário