Derby: Issue with array-like bindings in the view and workaround

2nd of July, 2013 04:47 PM

It's been a busy year and thus, I've not had time to post anything here unfortunately. That has not meant however that I have not been paying close attention to Derby - rather the other way around! At B&B Web we have started using Derby for various projects, and lately we've been struggling with one particular issue; bindings with [].

The issue

The issue is best highlighted with an example. Consider this view (e.g. views/app/index.html - can also be found as gist here):

<body:><ul>{#each _page.unplannedOrder as :row}<li style="{#if _page.selected[:row.id]}background: red;{/}">{:row.id}</li>{/}</ul><ul>{#each _page.unplannedOrder as :row}<li style="{#if multiplePartPath(_page.selected, :row.id)}background: red;{/}">{:row.id}</li>{/}</ul>

And this application code (e.g. lib/app/index.js - can also found as gist here):

var app = require('derby').createApp(module)
    ;

app.get('/*', function(page, model, params) {
  model.set('_page.unplannedOrder', [{"id":"1"}, {"id":"2"}, {"id":"3"}]);
  page.render('');
});

app.enter('/*', function (model) {
  model.remove('_page.unplannedOrder', 0);
  model.set('_page.selected.3', true); // Triggers red backround on wrong li on the top ul (li above - aka #2)
});


app.view.fn({
    /**
     * Alternative to the buggy binding-implementation of myPath[myKey]
     * E.g. where:
     *   myKey = 1
     *   myKey2 = 0
     * {multiplePartPath(myPath, myKey, "property", mySecondKey)}
     * will result in a dynamic version of:
     *   {myPath.1.property.1}
     * @param  {Object}   basePath    The base path already converted to an object (e.g. the path without quotes)
     * @param  {String/Key} leaf     A leaf as a string, either directly or indirectly through a variable (which thus will be converted to a string through Derby)
     * @return {Value}    The returning value
     */
    multiplePartPath: function (basePath) {
        var argumentsLength = arguments.length
            ;

        for(var i = 1; i < argumentsLength && typeof basePath !== 'undefined'; i++) {
          basePath = basePath[arguments[i]]
        }

        return basePath;
    }
});
What will happen when this render is that both lists (uls) of items (lis) should have item #3 (the last li in each list) get a red background. If you run this however, you will realize that only the second ul behave as expected. The issue lies on line #2 in the template - the if-binding on _page.selected[:row.id] will not work as expected but rather get some kind of index miscalculation (I assume - most likely the bindings' indices aren't updated) and update the wrong item (li).
The work around

A simple workaround is to not use these paths, but instead use a view helper to achieve the same effect as _page.selected[:row.id]. I created a quite generic view helper which can be used to avoid this issue. The example (line #3 in the template) as well as the comments highlights how it can be used to replace _page.selected[:row.id].

To summarize

I  hope this can help anyone who stumbles upon the same issue. I have created an issue on GitHub (here) to make sure the issue is known both to the derby team as well as people in general. I was not able to locate where the issue lie and thus not able to create a test (my testing skills are not that great either to be honest...). Anyhow, this should be enough as a "test" in order to fix it.

The Weblog

Carl-Johan Blomqvist