2

So I got a large JSON with many items nested into topics and I use mustache.js to make a button from each topic. Now I want to bind a reference to the sub-object into each button so that its onClick() will not have to perform a heavy lookup and without pasting huge amounts of data into the button's event.

Can this be done with mustache or with another templating language?

The JSON:

myJson={
   "topics": [
   {
       "name":"topic 1",
       "id":1,
       "items":[
           "name":"a great item",
           "data": {...}
       ]
   },
   ...
}

The template:

{{#topics}}
     <button onClick="_.bind(browseToTopic,{{this}})">{{name}}</button>
{{/topics}} 
MrIsaacs
  • 69
  • 5
akiva
  • 2,408
  • 3
  • 27
  • 38

2 Answers2

1

I had a similar issue last week when trying to bind events to a list of "things". My philosophical struggles lead me to this answer concerning the logic-less nature of Mustache (and Handlebars): https://stackoverflow.com/a/4946409/365252

It's important to note that Mustache is - by design - logic-less. What this is trying to accomplish is to aggravate motivate developers into separating logic from presentation, i.e anything that makes your presentation markup "dance" or link to "things" which is not expressible with markup alone should stay the hell away from your templates.

But back to our problem... the true Eureka moment came to me upon the realisation that my problem wasn't "How do I bind this with markup".

My true problem was: "There's clearly logic involved between the loop and the items of the loop. Why am I using Mustache to iterate over them in a logic-less environment?"

So after beating around the bush for a couple minutes, you might be wondering what my solution was:

This is my fictional template:

<h1>Argh these pesky topics!</h1>
{{{#topics}}} <!-- Note the 3 mustaches to avoid html escaping -->

And this is my row template:

<button>Shiver me timber, I'm rusty ol' {{name}}</button>

And then all the logic with ye olde Javascript:

/* Important: Assuming you're using jQuery, if not you'll have to convert the render output from a string to a DOM Node first. jQuery is NOT necessary for this to work - I'm just trying to avoid innerHTML hacks */
var topics = [/* whatever topics are,*/],
    topicsDOM = document.createDocumentFragment(),
    $ = jQuery,
    list,
    i, t, row;

for (i = 0, t = topics.length; i < t; i++) {
    row = $(Mustache.render(yourRowTemplate, topics[i]));

    row.on('click', function () {
        /* We know exactly what row this is via topics[i] so you can do anything you'd like here */
    };

    topicsDOM.appendChild(row);
}

/* Now... this bit I'm not very proud of and I'm hoping someone can point out a better solution */
list = $(Mustache.render(yourListTemplate, {
           topics: '<ins class="no-collision-please">'}));

/* We're doing this because we want to preserve the decorated DOM Nodes,
   Mustache expects plain markup as a string (our <ins>) */
list.find('ins.no-collision-please').replaceWith(topicsDOM);

/* Note that the <ins> never leaves our little sandbox nor does
   it cause reflow/layouting since we're working inside a documentFragment (list) */

return list;

And finally after swallowing all this Kool-Aid, the obvious aftertaste: I have to do THAT to bind an event, I'd rather just put a <script> tag inside my template!?

If you have asked yourself this question, maybe logic-less templating isn't for you.

Disclaimer: I have been using Handlebars/Mustache for less than a month and I still find myself asking the question above for certain use cases. It's not a Silver Bullet.

Community
  • 1
  • 1
Denis 'Alpheus' Cahuk
  • 3,882
  • 2
  • 20
  • 31
  • thanks a lot! that's a great solution but it's a js monkey-patch on top of mustache/handlebars. there must be a straight forward way to do that althought I admit this does the trick – akiva Sep 27 '14 at 23:05
  • I too believe that there's probably a one-liner solution available for all this but I've got a feeling that it does not involve Mustache/Handlebars. – Denis 'Alpheus' Cahuk Sep 27 '14 at 23:10
  • I'm ready to try any other templating lanuage that would make my life easier. I'll dig into jquery template's reference and let you know – akiva Sep 27 '14 at 23:13
1

The way I found (in handlebars, but probably appliable also to Mustache) to do this is the following:

  • create a helper that returns this object in JSON (stringified)
  • create a function that parses the JSON and returns the object
  • call that function from within the template.

The code:

Handlebars.registerHelper('getThis', function() {
    return JSON.stringify(this);
});

browseToTopic = function(obj){
    console.log(JSON.parse(obj));
    // do other stuff..
}

HTML/template:

{{#topics}}
  <button onClick="_.bind(browseToTopic,{{getThis}})">{{name}}</button>
{{/topics}}

See an example (different names but same operation) in action here: http://jsfiddle.net/awhb772m/

Update: added an example more similar to your initial code: http://jsfiddle.net/fbL7xjet/

Please take into account that:

1) your object is malformed. The following part:

 "items":[
       "name":"a great item",
       "data": {...}
   ]

should be:

 "items":[
     {
       "name":"a great item",
       "data": {...}
     }
   ]

2) The code I wrote is just a proof of concept, and you'd probably want to improve the getThis helper to escape the stringified JSON;

3) the this object you'll find inside the browseToTopic function is not a 1:1 copy of the original context, but it has been converted to JSON and then again to object. This means that the destination object will lack all the non primitive object (e.g. functions). Consider if this could be enough for your needs.

Lorenzo Marcon
  • 7,756
  • 5
  • 34
  • 59
  • Whatever `{{getThis}}` returns should probably be escaped to fit into attributes (like quotations and ampersands), Mustache/Handlebars will only escape code for use as markup. Further assuming that it only contains encodable data (no functions, references, other non-primitive types) – Denis 'Alpheus' Cahuk Sep 27 '14 at 22:39
  • Thanks for pointing that out, it should be surely escaped. Also, you're right, it won't really return `this`, but a copy of it without non-primitive types or functions; still I think this could be enough for the OP's needs, while keeping the code lean and feasible. – Lorenzo Marcon Sep 27 '14 at 22:48
  • thanks for the reply but I'm uncomfortable with having onclick="_.bind(func,{as-long-as-hell-data-in-here}. I would strongly prefer some referencing instead (as I wrote in the initial question) – akiva Sep 27 '14 at 23:07
  • right, I agree with you, but you started using `onclick`. As someone pointed out in the comments to the main question, it's better to avoid it. To use some referencing I guess you could re-design your initial data. – Lorenzo Marcon Sep 28 '14 at 19:15