Is it possible to render dust.js templates synchronously?
Asked Answered
A

3

19

I am trying to write an adapter for a client-side HTML/JS templating system to use dust.js under the hood. Unfortunately the API expects render operations to occur synchronously: the rendered output should be returned from the render() call. Dust.js is asynchronous and passes render output to a callback function. Is there any way to work around this, either in the Dust APIs or through some crazy Javascript hack?

Ability answered 24/3, 2012 at 3:17 Comment(2)
Good question! I want to know the same thing because I want to use dust.js in a CouchDB "show" function ( server-side ).Nativeborn
Unfortunately it looks like the API offered by consolidate.js uses a callback function in the same way dust.js does, so I don't think it will help here :/Ability
I
16

DustJS is only going to execute things asynchronously when the resources it needs to render (templates, partials) haven't already all been loaded.

If all the dependencies of a template are loaded before you execute that template then it'll execute synchronously (as far as I can tell anyhow). So you can do something like:

var result;
dust.render("tpl", data, function(err, res) {
   result = res;
});
console.log(result); // result will actually already be filled out if dustjs didn't
// have to go look for resources somewhere.

Here is a fuller example below: (and here is a jsfiddle link so you can run it: http://jsfiddle.net/uzTrv/1/)

<script type="text/javascript" src="dust.js"></script>
<script>
    var tpl = dust.compile("Omg {#people} {.} {/people} are here! {>partial/}", "tpl");
    var partial = dust.compile("I'm a partial but I've already been included so things still run {how}", "partial");
    dust.loadSource(tpl);
    dust.loadSource(partial);

    var data = {
        people: ["jim", "jane", "jack", "julie"],
        how: "synchronously!"
    };

    var result;
    dust.render("tpl", data, function(err, res) { 
        result = res;
    });
    console.log(result);
</script>

There could be cases (besides the one I mentioned) where I'm wrong... I don't know everything about dustjs.

Imperfect answered 2/5, 2012 at 4:25 Comment(2)
Verified. This works, although you've got to be really careful not to do anything that would make the template decide to go async.Spurrier
One warning: the dust.onload (which can be used to lazy load templates/partials) is not the only thing that can be asynchronous. Any custom helpers or functions in your JSON data can also be asynchronous by calling chunk.map and then making an ajax call, setTimeout, etc. Therefore, this is definitely not a full proof solution.Thrombo
H
2

I too wanted to have a function that accepted a context and returned the dust rendered text. Here is the solution I came up with:

// This function sets up dust template, and returns a new function "dusterFn()"
// dusterFn() can be passed a Context, and will return the rendered text.
// @param {String} text: The template text.
// @param {String} [name]: The name of the template to register with dust. If none is provided, a random number is used.
// @param {Function} [onError]: A function that is called if an error occurs during rendering.
function getDusterFn(text, name, onError) {

    var dusterFn = null;
    name = name || Math.floor(Math.random() * 99999).toString();
    onError = onError || function (error) { };

    try {

        var compiled = dust.compile(text, name)
        dust.loadSource(compiled);

        dusterFn = function (context) {
            var dustOutput = '';
            dust.render(name, context, function (error, out) {
                if (error) onError(error);
                dustOutput = out;
            });
            return dustOutput;
        };

    } catch (e) {
        // invalid template syntax 
        e += "\n\nPlease check your template syntax.";
        throw (e);
    }

    return dusterFn;

}

Usage

var greetingTemplate = getDusterFn('Hello {name}, You are {age} years old!');
greetingTemplate({name: 'Jane', age: 24});
Hazardous answered 30/11, 2012 at 21:16 Comment(0)
F
0

Matt's solution gave me some pointers on how to write a little wrapper that hides the "ugliness" of his solution (by "ugliness" I mean declaring variable outside of callback, assigning value inside callback and returning outside callback).

It not only wraps the hack into a little function but also binds the template´s name. I find this incredible helpful as I find myself using the same render function over and over again, but I do not want to specifiy the template´s name every time.

function templates(template) {
  return function templatesWrapper(data) {
    var result;
    dust.render(template, data, function onRender(err, data) {
      if (err) {
        throw err;
      }
      result = data;
    });
    return result;
  }
}

This is how to use it:

var renderHello = templates('hello.html');
renderHello({ username: 'Stackoverflow' });
// => <h1>Hello, Stackoverflow</h1>
Ferromanganese answered 22/5, 2015 at 6:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.