Update a template with Mustache js - javascript

I`m using mustache js to render a template with data from API and works nice, but i need to update (re-render) same template after a while. In my case I have a list in template like this:
template.html
<div id="template">
{{#list}}
<span>{{firstName}} {{lastName}} - {{phone}}</span>
{{/list}}
</div>
index.js
$(document).ready(function(){
$.ajax(
//some ajax here
).done(function(response){
loadTemplate(response);
});
});
function loadTemplate(data){
var template = $("#template").html();
Mustache.parse(template);
var render = Mustache.to_html(template, data);
$("#template").empty().html(render);
};
But the user can add more elements on this list, and after that I need to update the mustache template. I tried call the Ajax (that response with new value add on the list) then call loadTemplate function again but does not work, the list does not change (update) with new values.

The first time you render the template, the original mustache template gets lost.
ONLY the rendered text exists in same location. So the second time you try to re-render the template there is no template to render simply text that is NOT a template anymore so the text is simply outputed again.
The solution is to store your original template in another location (eg inside an element with id=#originalTemplate).
Then do following:
function loadTemplate(data){
var template = $("#originalTemplate").html(); // NOTE we use original template which does not get overriden
Mustache.parse(template);
var render = Mustache.to_html(template, data);
$("#template").empty().html(render);
};

Related

Assign value to a locals variable in keystonejs

In keystonejs (a node cms), I am using handlebars as templating engine in keystonejs. In handlebar file, I am using #each to iterate through an array.
{{#each data.positions}}
<h1 onclick = "callFunction()"> {{title}} </h1>
{{/each}}
Here I want to call a function within route, How can I do it?
Other way that is coming in my mind to to initialize locals variable. Like I have a variable locals.selectedPositionIndex in route file and I want to assign #index value of specific h1 element to this variable when any h1 element is clicked.
You could include a script at the bottom of your Handlebars template and include the function there:
<script>
document.querySelector('h1').addEventListener('click', function() {
// Code here
})
</script>
Alternatively, there is a question here with a useful answer:
Passing a function into a Handlebars template
I have amended the answer from the above question to work with KeystoneJS:
In your view you can assign your function to locals:
locals.func = function () {
// Code here
};
Then you will need to create a Handlebars helper in /templates/views/helpers/index.js:
_helpers.stringifyFunc = function(fn) {
return new hbs.SafeString("(" + fn.toString().replace(/\"/g,"'") + ")()");
};
You can then use this in your Handlebars template:
<h1 onclick="{{ stringifyFunc func }}">{{ title }}</h1>
I hope this helps.

Rendering more than just model attributes in Parse JS API (backbone.js) view templates

So I'm trying to use a join table to display a list of data in my Parse app. The javascript API is similar enough to backbone.js that I'm assuming anyone who knows that could help me. I can't show my actual source code but I think I simple twitter-like "user follows user" scenario can answer my question. So assume I have a join table called "follows" that simply contains its own objectId, the id of each user in the relationship, and some meta-data about the relationship (needing metadata is why I'm using a join table, instead of Parse.Relation). I want to have a view that finds all of the users the current user follows and renders an instance of another view for each case. From what I have so far, that would looks something like this.
In the intialize of the top level view (let's call it AllFollowsView), I would have something like this.
var currentUser = Parse.User.current();
var followsQuery = new Parse.Query(Follows);
followsQuery.equalTo("userId", currentUser.id);
followsQuery.find({
success: function(followsResult){
for (var i = 0; i < followsResult.length; i++){
var view = new OneFollowView({model:followsResult[i]});
this.$("#followed-list").append(view.render().el);
}//for loop
},
error: function(error){
console.log("error finding plans query");
}
});
OneFollowsView is just a view that renders an showing data about the relationship and listens for changes on that particular relationship (mainly change or delete in my case). I understand that by passing in the corresponding model with
var view = new OneFollowView({model:followsResult[i]});
I can print out attributes of that model in the OneFollowsView template like this
<li>You are following a user with the id of <%= _.escape(followedUserId) %></li>
My problem is that this only gives me access to the information stored in the "follows" object. How would I pass in the corresponding user models (or any other models that I can query for the id of) into the template so I can access them in the html in the same way. I would like to be able to run queries in one of the views and then access those models in the html. I know I can add attributes to the object before declaring a new instance of the lower level class with that object as the model, but that doesn't help me because I don't want to save it with new attributes attached.
EDIT: My render function for the top level function is empty at the moment. It's initilize function contains this line to render the template. I guess this should probably be in the render function and then I would call render from initialize.
this.$el.html(_.template($("#all-follows-template").html()));
Here's the render for the lower (individual li) view
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
return this;
this.delegateEvents();
}
From my understanding this just renders the template to el while parsing the model to JSON and then returns to allow chained calls.
The problem here lies in you render method. When you call this.template in your render method. That method, this.template is a template function returned by calling the _.template function. When you call your this.template method, the properties of the object you pass in will be available as instance variables in your template.
In your case you're passing in the JSON of the object. So, the properties of the model become names of variables available in your template. If you want to expose additional variables to the template you have a couple options: 1) Add to the jsonified model's attributes. 2) Send in the model as a top level variable and any additional variables you may want.
// option 1
render: function() {
var templateArgs = _.extend(this.model.toJSON(), { additionalVar: 'new var' });
var content = this.template(templateArgs);
$(this.el).html(content);
this.delegateEvents();
return this;
}
// option 2
render: function() {
var templateArgs = {
followResult: this.model.toJSON(),
additionalVar: 'new var'
};
var content = this.template(templateArgs);
$(this.el).html(content);
return this;
this.delegateEvents();
}
Either option is reasonable. I would probably go with option 2. Which allows you in the template to say something like:
<li> <%= followResult.someProperty %> <%= additionalVar %> </li>
Hope that helps. :)

How to register event on element in handlebars template, using data passed to that template

In my ecommerce web app, a handlebars template for the cart is populated by the cart data model, obtained through an API.
this is how the view is created
Cart.prototype.createView = function() {
// render cart handlebars template
var html = this.template(this.model);
// save rendered template as view
this.view = $(html);
// insert into DOM
$('.cart').html('');
this.view.clone(true).appendTo($('.cart'));
};
This is a part of the handlebars template
<ul class="items">
{{#each contents}}
<li>
<span class="remove"><i class="fa fa-times"></i></span>
<span class="price">{{this.pricing.rounded.with_tax}}</span>
<span class="name">{{this.title}}</span>
<span class="quantity">{{this.quantity}}</span>
</li>
{{/each}}
</ul>
...
Now how can I register an onClick event (e.g. with jQuery) to the .remove span, and use the data from the handlebars #each loop? In particular: the product ID, so I can do the "remove product from cart" button.
I know I could add the product ID to the DOM like so:
<span data-product-id="{{this.id}}" class="remove">...</span>
and then use it in the controller
Cart.prototype.initController = function() {
var self = this;
var container = $('.cart');
// register click event on remove button
container.on('vclick', '.remove', function() {
// get product ID
var productID = $(this).attr('data-product-id');
// remove product from cart
self.removeProduct(productID);
});
};
But I don't want to do it like this. I don't want to put the productID into the DOM. So how can I properly register that event to the node created in a handlebars loop, while using the data that is passed to the template? I basically need to link the data to the view.
I don't want to use messy solutions like loop through the data and DOM list arrays and link them via their array index. I'm sure there is a proper MVC style solution that I'm not seeing.
here is a function one can use to tie a string template to an object, without putting any info from the object into the template.
var refData=[];
function ref(v){
if(typeof v!=="object") return refData[v];
var id=refData.indexOf(v);
if(id==-1) id=refData.length;
refData[id]=v;
return id;
};
if you pass it an object, you get back an index number. if you pass it an index number, you get back an object. You can reset the cache by setting refData.length=0; To use this in a template, you need to use a helper, which handlerbars supports. you put the index from passing an object into the template with that helper, and in your click events, you can wrap the call around index to get an object in-place. for example:
<input id={{ref}} onclick=alert(ref(this.id).someProperty) />
this is completely separate from handlebars or any model/template package, so it's portable, small, and easy to use. Sure, built-in 2-way data binding is more powerful, but not everything has it yet, and this is certainly better than mixing view creation metaphors.

Handlebars: More than one model inside template

I am starting with Handlebars, and was wondering:
Is there the possibility to pass more than one model to the view?
I am passing my model with $(template(model)) to the view:
var source = $('#template').html();
var template = Handlebars.compile(source);
var model = this.model.toJSON();
$(template(model)).appendTo(this.$parent);
So I can pass one variable with stored JSON data to the view. But what if I want to have two different variables/models in one template?
Is this possible? This would be much easier than generating another template and loading into the other.
A compiled Handlebars template just wants an object as its argument, you can build that object however you want. If you want two models, just add an extra level of indirection:
var html = template({
model: this.model.toJSON(),
other: this.other_model.toJSON()
});
and then inside your template you can say things like:
{{model.attribute}}
{{other.other_attribute}}
and the like.
As an aside, a Backbone view adding HTML to anything other than this.$el (i.e. this.$parent) is a bit dodgy. Events are bound to this.$el so events won't work without help. You'll probably an easier time if you turn that around a bit so that the parent places your view's $el somewhere so that your view can be self contained.

populating Moustache.js template

I am currently working with a QuickBase database trying to make a custom report using moustache.js, When I was looking into moustach.js I noticed you needed to have your data in JSON format so I used (http://pastie.org/9364908#16) to get my data into JSON which then comes out like (http://pastie.org/9364674#18)
My question is when I make the template for this, does it need to be all contained in the page that the call to get the json data is? I have looked at examples of mustache templates, but all I can find is it being used with data supplied right then made in JSON format, but my data I need is being supplied when I make the call so how would I go about setting that up.?
When working with mustache js templates (or js templates in general), you do not need to have the template be in the same location as the data being returned from the ajax call. However, you will need to have the data stored in a var that is within scope of the render function of the template. I am assuming you wish to ajax in a set of data, parse it to JSON, then at some point render that JSON data to the DOM. So to answer your questions:
1) Does the template need to be contained in the page that the JSON data is?
Not necessarily, but you will need to be able to access both parts from one locations (ie, you would ajax in both the template and the data, then put it together. Alternatively, you could store the template in the DOM as a script [recommended way unless you are using AMD], and then render the template when your ajax call is complete).
The main thing to note is you will need to have a reference to both your template and your data in the same scope.
2) Setting up the template / data ?
This can be done by using the ajax success callback: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest
So on success, you can have the function call render on your template, or just set a locally scoped variable with the data and handle the template rendering after.
3) render the template normally
var template = $('#template').html(), //script tage with id="template" from your dom
data = jsonData, //saved somewhere in a local scoped var .. maybe from an ajax call
rendered = Mustache.render(template, data),
target = $("#target"); //where the template renders to
$('#target').html(rendered);
4) render the template via $.ajax
$.ajax("/api/data", function(data){
var jsonData = $.parseJSON(data),
template = $('#template').html(), //script tage with id="template" from your dom
rendered = Mustache.render(template, jsonData),
target = $("#target"); //where the template renders to
});

Categories