2
votes

I'm trying to pick a JS template engine for an upcoming project and one of my favorites seems to be dust.js.

I like the idea that it's asynchronous, i.e. I just stage some template for rendering and when it's ready the callback inserts the rendered HTML into the DOM.

I'm not sure however how to work around the problem of asynchronous rendering within synchronous callbacks. For example - I often use DataTables plugin which provides some callbacks allowing me to modify DOM nodes before they are actually inserted.

To illustrate the problem - let's assume I have a following snippet (taken and modified from DataTables website):

$(document).ready( function() {
  $('#example').dataTable( {
    "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
      // modify the node before it's actually inserted into the document
      $('td:eq(4)', nRow).html( '<b>' + aData[4] + '</b>' );
    }
  } );
} );

Now - I would like to get rid of '<b>' + aData[4] + '</b>' and use a template rendered with some context instead (it's a trivial example but shows the issue).

If I understand correctly I can't force dust.js to render the template synchronously, so it might happen that an unprocessed node will be inserted into the document (and users will see it unprocessed) and later on when dust.js actually renders the template that node would be modifed.

This obviously wouldn't look good from the user standpoint.

So is this really the case (that dust.js can't be forced to be synchronous) and if yes - how to cope with that? Should I use synchronous engine, like handlebars or mustache or maybe I'm not seeing something obvious here?

Any help or insight or advice would be very welcome. Thanks! :)

EDIT:

This question is not about how to render a template, but about how to make sure it's rendered before the fnRowCallback finishes. Thanks @robertklep for pointing this out with your (deleted) answer.

1
Deleted because I realised it wasn't an answer to your problem :) Anyway, you can't force an asynchronous function to be ready before fnRowCallback finishes, so if that's really an issue I don't think Dust.js is usable. - robertklep
Yes I noticed - thanks for trying to help though ! :) And yes - that's what I was worried about - there are some libraries out there which expect you to return (or do) something from a synchronous callback and dust.js might not play with them too well :( - kgr

1 Answers

-2
votes

Edit: To be clear, I think this is a bad practice, and you typically should not do things like this. I included the code below merely as an example of how one COULD do something like this if technical or architectural limitations forced it upon you. However, it should only ever be used as a last resort.

I've done some nasty things in the past that involve using a boolean and a loop to "capture" asynchronous calls, but I wouldn't really recommend it as a particularly good practice. Nevertheless, if you want it, here it is.

// name is the template identifier, template is the template contents,
// data is the "context" of the template (its model), callback (optional)
// is the method you want the rendered template passed to, cbContext
// (optional) is the context in which you want the callback to execute
asyncToSync = function (name, template, data, callback, cbContext) {
    // Tracks if it's time to return or not
    var finished = false;
    // Gives us an exit condition in case of a problem
    var maxLoops = 10000;
    // Create a variable to store the return value
    var outVal = 'Template rendering did not finish';
    // And the callback function to pass to dust
    var dustCb = function (err, html) {
        finished = true;
        outVal = html;
    }
    var i = 0;
    // We create this as a function so that increment goes to the bottom
    // of the execution queue, giving dustCb a chance to execute before
    // the counter hits max.
    var incrementCounter = function () {
        i += 1;
    };
    // Compile, load, and render the template
    var compiled = dust.compile(template, name);
    dust.loadSource(compiled);
    // Pass our callBack so the flag gets tripped and outVal gets set
    dust.render(name, data, dustCb);
    // count up to maxLoops
    while(!finished && (i < maxLoops)) {
        incrementCounter();
    }
    // If a callback is defined, use it
    if (callback) {
        // If a callback context is defined, use it
        return (cbContext) ? callback.call(cbContext, outVal) : callback(outVal);
    }
    // otherwise just return the rendered HTML
    return outVal;
}