How to read additional data within handlebars block expression template? - javascript

My html document will have div container which gets populated with handlebars template name 'relatedVideosTemplate' on execution.
<div id="relVideos"></div>
Below anonymous function will make ajax request and pass the data to handlebar template which loops over all items in data object to construct related videos UI.
(function(options) {
var defualt = {
defaultThumb: "/url/to/default/thumbnail/",
apiURL: '/path/to/api/url'
};
options = options || {};
options = $.extend(true, defaults, options);
// handlebars template for the related video carousel
var relatedVideosTemplate : "<h3>Related Videos</h3>" +
"<ul class=\"related-videos\">" +
"{{#foreach items}}<li class=\"video {{#if $last}} last{{/if}}\">" +
"<a href=\"/video/?videoid={{id}}\" class=\"image\">" +
"<img alt=\"{{name}}\" {{#if videoStillURL}}src=\"{{videoStillURL}}\"{{else}}src=\"{{defaultVideoThumb}}\"{{/if}} width=\"90\" height=\"75\" data-id=\"{{id}}\" onerror=\"this.src='{{defaultVideoThumb}}';\" />" +
"<span class=\"video-time\">{{length}}</span></a>" +
"<div class=\"info\"><h5>{{name}}</h5>" +
"<span class=\"published-date\">{{publishedDate}}</span>" +
"{{#if playsTotal}}<span class=\"video-views\">{{playsTotal}} views</span>{{/if}}" +
"</div></li>{{/foreach}}" +
"</ul>",
template, relVideosHTML;
$.ajax({
url: options.apiURL,
success: function(data) {
template = Handlebars.compile(relatedVideosTemplate);
relVideosHTML = template(data);
$("#relVideos").html( relVideosHTML );
}
});
});
Its very likely the video still image (thumbnail) for some videos will return 404 and in that case I use onerror event on image tag to replace it with default thumbnail.
Is there a way to pass this default thumbnal value as object to handlebars block expression template ?
I don't want to amend the data object to have 'defaultThumb' property for all items. Like below...
for ( i = 0, max = data.items.length; i < max; i++) {
// Adding new property to each item in data object
data.items[i].defaultThumb = options.defaultThumb;
};
It seems to me amending data property in above loop is non efficient as the defaultThumb is same across all videos which return 404 for still(thumb) image.

Why not set defaultVideoThumb once at the top level and then reference it as {{../defaultVideoThumb}} inside the loop? Something like this in the template:
<img
{{#if videoStillURL}}src="{{videoStillURL}}"{{else}}src="{{../defaultVideoThumb}}"{{/if}}
width="90" height="75"
onerror="this.src='{{../defaultVideoThumb}}'
>
and then in the JavaScript:
data.defaultVideoThumb = '...';
var html = template(data);
Demo: http://jsfiddle.net/ambiguous/9ecx3/

Related

Opening a div in a {{/if}} and closing it later with meteor/blaze

I'm trying to make something that is not possible in HTML/Blaze only.
By that I mean that I'm trying to open a div in a {{#if}} without closing it in this specific if. Impossible to do it the following way :
{{#each getData}}
{{#if equals this.level first}}
<div><p>content</p>
{{/if}}
{{#if equals this.level last}}
</div>
{{/if}}}
{{/each}}
So I found something that could solve this problem. Using triple curly braces and generate the HTML part in JS.
{{#each getData}}
{{{getContent this}}}
{{/each}}
getContent() return my HTML. It looks like this :
getContent(obj) {
if (obj.level == first)
return "<div><p>content</p>";
if (obj.level == last)
return "<p>content></div>";
else
return "<p>content</p>";
}
It works, it render the HTML, but there is one problem. The div I'm opening seems to close itself.
This is what's rendered :
<div><p>Content</p></div>
<p>content</p>
<p>content</p>
instead of :
<div><p>content</p>
<p>content</p>
<p>content</p>
</div
I'm really tired, I apologize if I'm not clear enough or if the solution is obvious.
EDIT: What I have now, and it's working fine, is the following :
HTML
{{#with getDataFromButton}}
{{formatData this}}
{{/with}}
JS
formatData(data) {
var res = "";
for (var i = 0; i < data.length; i++) {
if (data[i]["level"] == 0) {
if (res.length > 0)
res += "</div>";
res += "<div class=\"whiteBox\"><p>" + data[i]["value"] + "</p>";
}
else if ('last' in data[i]) {
res += "<p>" + data[i]["value"] + "</p></div>";
}
else {
res += "<p>" + data[i]["value"] + "</p>";
}
}
return res;
},
Thanks to all of you for the explanations ! And happy new year (it's not too late).
This is done because of browser try to fix HTML structure on any change. If you using Blaze, then HTML is rendered on the client side and in any evaluation of your helper code is injected to DOM. Then browser gets this HTML and tries to fix it.
Best solution is use simple pattern
<div>
{{#each getData}}
<p>content</p>
{{/each}}
</div>
If you want to apply the logic that you presented you have to prepare full correct HTML element in JS. You can to it as follow.
In template type: {{ myFullCorrectNodeElement }}
In JS type:
helpers: {
myFullCorrectNodeElement() {
let html = "";
const data = Template.instance().getData;
for(let i=0; i<data.length; i++) {
// your logic
if(i===0) {
html += `<div><p>content</p>`
// there you can access to variables by syntax ${}
// insde of ``, you can read about template strings from ES6
}
// ... and rest of your logic
}
return html;
}
}

Alter the template from the `createViewModel` function within a custom component loader

In knockoutjs I have a custom component loader in which I do some logic. Basically I want to alter the template from the createViewModel function. I know there's the componentInfo.templateNodes but I don't know what to do with it.
The reason I want to alter it in the createViewModel function is because the createViewModel function is called every time a component is shown.
Ahwell, code says way more than words so check it out yourself down here.
ko.components.loaders.unshift({
getConfig: function (name, callback) {
var component; // The component gets loaded somewhere. (Sadly I can't alter the template here because it is only called once.)
callback({
viewModel: {
createViewModel: function (params, componentInfo) {
// Load these parameters somewhere
var args;
var location;
// I'd love to add these two items before and after my template.
var before = "<div data-bind=\"with: " + location + "\">";
var after = "</div>";
// Construct a viewModel with the data provided.
return app.core.helpers.construct(component.viewModel, location, args);
}
},
template: component.template
});
},
loadTemplate: function (name, template, callback) {
// Get the location again.
var location;
// I just want to load the template while applying the correct binding scope from the createViewModel.
var templateString = "<!-- ko stopBinding: true -->\n<!-- ko with: " + location + " -->\n" + template + "\n<!-- /ko -->\n<!-- /ko -->";
// Just let it load.
ko.components.defaultLoader.loadTemplate(name, templateString, callback);
}
});
I managed to create a working solution (although in its infancy). As I still don't know how to add template code to the componentInfo I discovered it is possible to edit the things available in the componentInfo. (See the solution below)
ko.components.loaders.unshift({
getConfig: function (name, callback) {
var component;
callback({
viewModel: {
createViewModel: function (params, componentInfo) {
// Load these parameters somewhere
var args;
var location;
/*
* The first one is the element we're binding on. The next sibling is the element we probably want to alter.
*/
if (componentInfo.element.nextSibling.nodeType == 8 && componentInfo.element.nextSibling.nodeValue.indexOf("[injectNamespace]") > -1) {
componentInfo.element.nextSibling.nodeValue = "ko with: models." + name.replace('/', '.');
}
return app.core.helpers.construct(component.viewModel, location, args);
}
},
template: component.template
});
},
loadTemplate: function (name, template, callback) {
var templateString = "<!-- ko with: [injectNamespace] -->\n" + template + "\n<!-- /ko -->";
ko.components.defaultLoader.loadTemplate(name, templateString, callback);
}
});

Bbind a onclick function with object as parameter on anchor tag created in a loop

I am getting some JSON data and I am looping through it and for each data I am creating a Anchor tag and onClick of it I am trying to call a function with object as parameter.
Something like this:
$.getJSON('/ui_dashboard/rest/getAlertDefVsAlertValues',function(data) {
console.log(data)
$("#checkAllAlertsTbody").empty();
var mailServerTbody="";
var objData = data.getAlertDefVsAlertValues;
var objLen = objData.length;
for(var i=0;i<objLen;i++){
//one object for all the key in the json
var alertWholeObject = {
alertFunctionId : objData[i].alertFunctionId,
alertMessage : objData[i].alertMessage,
definitionName:objData[i].definitionName,
emailMessage : objData[i].emailMessage,
emailSubject:objData[i].emailSubject,
emailTemplate : objData[i].emailTemplate,
hubId:objData[i].hubId,
id : objData[i].id,
isDefinitionEnabled:objData[i].isDefinitionEnabled,
mandatoryParam : objData[i].mandatoryParam,
scanInterval:objData[i].scanInterval,
smsMessage : objData[i].smsMessage,
smsTemplateId:objData[i].smsTemplateId,
tenantId : objData[i].tenantId,
timestamp:objData[i].timestamp,
list: objData[i].list,
isArray : function(what){
return Object.prototype.toString.call(what) === '[object Array]';
}
};
console.log(alertWholeObject.alertMessage)
var idWithoutSpace = alertWholeObject.id.replace(/ /g, '');
//for overview table
mailServerTbody = '<tr><td>'+(i+1)+'</td><td>'+alertWholeObject.definitionName+'</td><td>'+alertWholeObject.timestamp+'</td><td>'+
'<div class="controls center">'+
'<a class="btn btn-info btn-mini" id="edit_'+idWithoutSpace+'" onclick="editAlertRules(' + alertWholeObject + ');"><i class="icon-edit icon-white"> </i></a>'+
'<a class="btn btn-danger btn-mini" id="delete_'+idWithoutSpace+'" ><i class="icon-trash icon-white"> </i></a>'+
'</div></td></tr>';
$("#checkAllAlertsTbody").append(mailServerTbody);
}
});
}
but the onclick function is not working; in console its showing only [object object].
How do I send this object created in the loop to click of each anchor tag?
Please help.
Edited:
Sorry, I jumped the gun on this. Here is the update:
Do not pass the object as a parameter. Instead pass an index. Declare an Object array with an outer scope (or global scope).
var obj = [];
Inside your for loop instantiate an object in this array at the current index:
for (i=0; ....) {
obj[i] = new Object;
...
}
Now, when you are specifying an inline onclick, just pass the index:
mailServerTbody = "... <a ... onclick='editAlertRules(" + i + ");'" ...</a>.."
Inside your function editAlertRules use the object in outer scope with index as the argument:
function editAlertRules(index) {
console.log(obj[index]);
...
}
Check this fiddle to get the idea: http://jsfiddle.net/n8hSJ/1/
Hope that helps.
Use jQuery to bind to the click event. The function scope will make the right alertWholeObject available to the click callback. Add this code after you appended the HTML to your document.
$('#edit_' + idWithoutSpace).click(function () {
editAlertRules(alertWholeObject);
});

How do i get a value from Kendo UI template

I have this template:
var template = kendo.template("<div class='relatedItemRow'>"
+ "<span id='relatedItemName'>Related Item #: Number #</span>"
+ "<div class='relatedItemRowInfo'><span >#: Name #</span>"
+ "<a data-relatedItemID='#: Value #' class='removeRelatedItem'>"
+ "<img src= '#: Img #'/</a></div><br class='clear'/></div>");
var data = {
Name: "" + checkbox.getAttribute('flatName'),
Number: $('#relatedItemsList').children().length + 1,
Img: '/Content/images/x_remove.png',
Value: checkbox.value
};
var result = template(data);
$("#relatedItemsList").append(result);
I am able to access the data-relatedItemID by using:
$('#relatedItemsList').children().eq(i).children().last().attr('data-relatedItemID')
But how do I get to the Number field in data? I want to change that one dynamically.
I have tried:
$('#relatedItemsList').children().eq(i).children().attr('Number') == 5
but it does not work. Any idea how to do that?
I know that there is an answer for this question even accepted but I'd like to suggest a different approach that I think it is much simpler and more Kendo UI oriented and is using Kendo UI ObservableObject. This allows you to update an HTML that is bound to the ObservableObject without having to crawl the HTML.
This approach is as follow:
Wrap your data definition with a Kendo Observable Array initialization.
Redefine your template and start using using data-binding.
Append this new template to your HTML.
Bind data to the HTML.
Now you can get current value using data.get("Number") or set a new value using data.set("Number", 5) and the HTML get magically updated.
The code is:
Template definition
<script type="text/kendo-template" id="template">
<div class='relatedItemRow'>
<span id='relatedItemName'>Related Item <span data-bind="text : Number"></span></span>
<div class='relatedItemRowInfo'>
<span data-bind="html : Name"></span>
<a class='removeRelatedItem' data-bind="attr: { data-relatedItemID : Value }">
<img data-bind="attr : { src : Img }"/>
</a>
</div>
<br class='clear'/>
</div>
</script>
data definition as:
var data = new kendo.data.ObservableObject({
Name: "" + checkbox.getAttribute('flatName'),
Number: $('#relatedItemsList').children().length + 1,
Img: '/Content/images/x_remove.png',
Value: checkbox.value
});
and the initialization of the HTML is:
$("#relatedItemsList").append($("#template").html());
Getting current value of Number is:
var old = data.get("Number");
Setting is:
data.set("Number", 5);
Running example in JSFiddle here : http://jsfiddle.net/OnaBai/76GWW/
The variable "result" and thus the data you're trying to change aren't a Kendo templates, they're just html created by the template, and the number is just text in the html. To modify the number without rebuilding the whole string, you need to change the template so you can select the number by itself with jQuery by putting it in it's own element, and adding something to identify it.
var template = kendo.template("<div class='relatedItemRow'><span id='relatedItemName'>Related Item <span class='relatedNumberValue'>#: Number #</span></span><div class='relatedItemRowInfo'><span >#: Name #</span><a data-relatedItemID='#: Value #' class='removeRelatedItem'><img src= '#: Img #'/</a></div><br class='clear'/></div>");
//other code the same
And once you can select it, then you can change it like this.
$('#relatedItemsList').children().eq(i).find('.relatedNumberValue').text(5);
And retrieve it like this.
var foo = $('#relatedItemsList').children().eq(i).find('.relatedNumberValue').text();

CanJS/JavascriptMVC: How to store a view template in a variable

Is it possible to define a view template in a javascript variable, instead of a script tag or a file?
Something like this:
var template = "< h1 ><%= title %> < / h1 >";
var rendered = can.view.render(template, data);
Ok, after a lot of research, because it isn't written in documentation, I found how to do it.
The trick is that you have to register your template with an id first.
If you are using a script tag or url to find a template, this step is done automatically by canJS.
So, if you want to render a template, stored in a variable, you have to do something like this:
var template = "< h1 ><%= title %> < / h1 >";
can.view.ejs('my-view-id', template);​​​​​​​
var rendered = can.view.render('my-view-id', data);
Now you have the document fragment in rendered.
Take a look to jquery utils string format plugin. There is an example that shows rendering html from template that was defined as as string.
Creating template:
$.tpl('tweet', [
'<div id="tweet-{id:s}" class="tweet">',
'<div class="tweet-body"><b>#{from:s}</b>: {body:s}',
'(<a href="{href:s}" class="tweet-date">',
'<abbr title="{timestamp:s}">{timesince:s}</abbr>',
'</a>)',
'</div>',
'</div>'
]);
Rendering:
$.getJSON('/tweets/username/', function(resp, s){
$.each(resp.tweets, function(idx, tweet) {
$.tpl('tweet', {
id: tweet.id,
body: tweet.body,
from: tweet.screen_name,
timestamp: tweet.pub_time,
timesince: $.timeago(tweet.pub_time)
}).appendTo('#tweet-list');
});
});

Categories

Resources