segunda-feira, 28 de dezembro de 2015

Pain points of development with MarionetteJS

After sometime developing a fully functional application with MarionetteJS, i already have a good understanding of its inner working and what is good and what leaves to be desired.

The good is the small leaning curve, easily integrating with other web resources (Bootstrap, jQuery plugins etc). But using MarionetteJS is not hassle free.

Dynamic update of DOM

I use Handlebars as the template engine and i’m quite comfortably with it. Together with a Webpack loader, loading and using the template is straight and simple as could be. The problem is, after the first render, when the model data changes. I tried some options (with actual examples from the project i’m working):

Configure an event in the view to catch when data change and update using jQuery:

modelEvents: {
    'change:latestPrice': 'onLatestPriceChange'
  },
  onLatestPriceChange: function () {
    var price = this.model.get('latestPrice');
    var variation = this.model.get('latestVariation');
    this.$el.find('.stock-price').html(price ? 'R/pre> + price.toFixed(2) : '--');
    this.$el.find('.stock-variation').toggleClass('negative-value', variation < 0).html(variation ? (variation * 100).toFixed(2) + '%' : '--');
  }

This solution is far from perfect: duplicates the formatting done in template, becomes tedious, error prone and time consuming as the number of elements to update grows.

Re-render the whole view

This is the easiest, keeping the view logic in same place and the setup is minimal, but it comes with it’s own issues, with potential effects in performance and smoothness. Things are worse in LayoutView, where all regions must be also re-rendered.

Use a databinding library like Stickit

This solves the duplication of view logic and optimally updates the DOM. In the other side needs some configuration that, in my experience, is quite tedious to do. In one of my views, with the template below, i concluded that was a good call to use Stickit. But given the extra work to convert from Handlebars, i gave up and ended just rendering everything (including the regions)

<div class="leading-stock-price js-stock-price">{{formatCurrency latestPrice}}
    <span class="stock-variation js-stock-variation {{#if (isNegative latestVariation)}}negative-value{{/if}}">{{formatFloat latestVariation}}%</span>
</div>

<div class="leading-owned-stock">
    {{stockQuantity}} ações = {{formatCurrency stockValue}}
    <span class="stock-variation {{#if (isNegative valueVariation)}}negative-value{{/if}}">{{percentage valueVariation}} ({{formatCurrency valueDifference}})</span>
</div>

<div class="list card">
    <div class="item item-divider item-icon-right">
        Operações de compra
        <i class="icon ion-plus"></i>
    </div>
    <div class="js-buys">

    </div>

    <div class="item item-divider row">
        <div class="col-25 col-offset-50">{{formatCurrency meanPrice}}</div>
        <div class="col-25">{{formatCurrency buyValue}}</div>
    </div>
</div>

Boilerplate for simple lists

The need to define a CollectionView subclass, and ChildView is overkill for simple lists or tables. Below is the views and template for a really simple list:

var BuyItemView = Marionette.ItemView.extend({
  template: require('../templates/buyitem.hbs'),
  className: 'item item-icon-right',
  serializeData: function () {
    return _.clone(this.model.attributes)
  }
});

var BuysView = Marionette.CollectionView.extend({
  childView: BuyItemView
});

//template
<h3>{{dateToLocale date}}: {{quantity}} ações a {{formatCurrency price}}</h3>
<p>Total: {{formatCurrency totalPrice}}</p>
<i class="icon ion-chevron-right"></i>

Aside from the boilerplate, the templates directory can become crowded fast (something that can be mitigated with a better directory layout)

Switching code editor between view <-> template <-> css

For more that you try to make the view independent of the template, often is needed to change both to accomplish some UI changes. The fact that the attributes of root element are defined in the view class does not helps. Definitively hurts productivity.

Where to go from here?

I’m a desktop developer with some very large projects (hundreds of forms/views) over my shoulders. Currently i’m in the phase of experimentation, using a hobby project as field, with no time pressure. I plan to add a web client to the main projects, eventually replacing the desktop client.

It’s clear that, as i’m doing today, is not doable to do the serious work with JS/HTML/MarionetteJS. Just feel / is little productive. But it does not mean i gave up. There’s some ideas i have in mind to boost my experience:

Use another databinding library like RivetsJS

When i first looked at RivetsJS, i think: no, never, its markup is weird, i want clean standard html. Also, there are some patterns, like rendering collection in a template loop or defining the callback in the template that is/was regarded as bad practice. With the rise of Angular, React, RiotJS and VueJS seems that those patterns are no more weird.

With such library, the dynamic DOM update and the boilerplate for simple lists could be tackled. At first look, does not suffers from the time consuming configuration step required by StickIt. In fact, it would mostly replace the Handlebars templating. The down side is the lack of project updates, since the main author pointed little interest to continue its development.

See what smart DOM updates libraries can do

This would improve the DOM update since re-render would be much less cost, but initiatives like handlebars-idom are in its infancy.

Search/develop ways to handle related code together

It’s pretty interesting the ability to have JS, template and CSS in the same file as Vue does. It uses a Webpack loader (or Browserify plugin) to precompile into regular code. Searched for a generic loader in the same line without success. An alternative is to see if is doable to adapt vue-loader.
Another try is to look for a WebStorm extension that does something similar at IDE level.</div>

Nenhum comentário:

Postar um comentário