jsrender (jsviews) bind handlers - javascript

I've try use jsrender/jsviews first time. It looks awesome, but I'm not find clear documentation or example how to dynamically bind event handlers for generated content.
For example pure jQuery old approach was:
Code from bean class to render collection of objects:
container = $('#tabs-my');
this.load( // Obtain array of objects
$.proxy(function(list){
container.html('');
list.forEach(
$.proxy(function(it, i){
container.append(this.renderItem(it));
}
,this)
);
}
,this)
);
And in object itself render method:
,renderItem: function(/*Specialist*/ it){
var container = $('<div class="specialist-item" />')
container.append(
$('<span class="x">Remove</span>').click($.proxy(function(){
this.removeSpecialistFromList(it.id)
}
,this))
);
container.append(
$('<span class="x">Edit</span>').click($.proxy(function(){
this.renderSaveForm(container)
}
,this))
);
container.append('<p><b>' + it.name + '</b> <i>' + it.phone + '</i></p>' +
'<p>' + it.skill + '</p>' +
( it.city ? '<p>' + it.city.name + '</p>' : ''));
return container;
}
Note I bind handler via closure content for current object without use any external identificators in tag itself.
Then I try use templating to separate content from visialisation.
My template:
<div class="specialists-list">
Items in list: {{:specialists.length}}
{^{for specialists}}
<div class="specialist-item">
<span class="x">Remove</span><span class="x">Edit</span>
<p><b class="name">{{:name}}</b> <i class="phone">{{:phone}}</i></p>
<p class="skill">{{:skill}}</p>
<p class="city">{{:city.name}}</p>
</div>
{{/for}}
</div>
And rendered like:
var template = $.templates({
specialistsTmpl: tmpl
});
$.templates.specialistsTmpl.link(container, {
specialists: list
});
I realize it could be done using common handlers in attributes something like:
<span class="x" data-id="{{:id}}">Edit</span>
And then try obtain that object again from external. But it is workaround and is not desired.
Is there way to bind handlers in template or via helpers, custom tags?

There are many samples on http://www.jsviews.com which include event binding, and handlers such as for removing or inserting data items. Did you look at the examples here: http://www.jsviews.com/#samples/editable - you will find four different approaches to the same scenario.
For example:
Template
{^{for languages}}
<input data-link="name" />
<img class="removeLanguage" .../>
{{/for}}
Code:
$.link.movieTmpl("#movieList", app)
.on("click", ".removeLanguage", function() {
var view = $.view(this);
$.observable(view.parent.data).remove(view.index);
return false;
});
Note the use of var view $.view(this); - you pass in the element (this) that is clicked on, and $.view(clickedElement) returns you the view, from which you can get view.index (the item index - in the case of iteration over an array), view.data (the current data item - in you case that would be the specialist item), view.parent.data (in your case, the specialists array) etc.
Of course since view.data is the current data item, if your data item is in effect a view model, with methods, you can call a method: view.data.someMethod(...).
But as an alternative to using the jQuery on() for binding handlers, you can use declarative binding directly in the template like this:
{^{for specialists}}
<div class="specialist-item">
<span class="x" data-link="{on removeMe}">Remove</span> ...
...
</div>
{{/for}}
where I assume your specialist has a removeMe() method. The "{on ...}" binding binds by default to the click event, and you can bind to methods on your data, or to helpers, etc.
Take a look at this example: http://jsfiddle.net/BorisMoore/cGZZP/ - which uses {on ...} for binding to helpers for modifying a two-dimensional array.
I hope to create some new samples using {on ...} binding before too long.
BTW I don't recommend using the onAfterCreate for doing event binding. Either of the above approaches are better, and will ensure correctly disposal of event bindings.

I'm not 100% sure I completely understand but I believe I do. You want to use a different approach. Look at .on for jQuery. I've switched to using it most all the time. The call back function doesn't need to change. It is really pretty nice.
In my case, I was creating thousands of event handlers and it was killing my performance. So I switched to using .on and it solved my problem.
This doesn't exactly answer your question... but I think its a better solution.

Related

show all the values with .html [duplicate]

Lets say I have an empty div:
<div id='myDiv'></div>
Is this:
$('#myDiv').html("<div id='mySecondDiv'></div>");
The same as:
var mySecondDiv=$("<div id='mySecondDiv'></div>");
$('#myDiv').append(mySecondDiv);
Whenever you pass a string of HTML to any of jQuery's methods, this is what happens:
A temporary element is created, let's call it x. x's innerHTML is set to the string of HTML that you've passed. Then jQuery will transfer each of the produced nodes (that is, x's childNodes) over to a newly created document fragment, which it will then cache for next time. It will then return the fragment's childNodes as a fresh DOM collection.
Note that it's actually a lot more complicated than that, as jQuery does a bunch of cross-browser checks and various other optimisations. E.g. if you pass just <div></div> to jQuery(), jQuery will take a shortcut and simply do document.createElement('div').
EDIT: To see the sheer quantity of checks that jQuery performs, have a look here, here and here.
innerHTML is generally the faster approach, although don't let that govern what you do all the time. jQuery's approach isn't quite as simple as element.innerHTML = ... -- as I mentioned, there are a bunch of checks and optimisations occurring.
The correct technique depends heavily on the situation. If you want to create a large number of identical elements, then the last thing you want to do is create a massive loop, creating a new jQuery object on every iteration. E.g. the quickest way to create 100 divs with jQuery:
jQuery(Array(101).join('<div></div>'));
There are also issues of readability and maintenance to take into account.
This:
$('<div id="' + someID + '" class="foobar">' + content + '</div>');
... is a lot harder to maintain than this:
$('<div/>', {
id: someID,
className: 'foobar',
html: content
});
They are not the same. The first one replaces the HTML without creating another jQuery object first. The second creates an additional jQuery wrapper for the second div, then appends it to the first.
One jQuery Wrapper (per example):
$("#myDiv").html('<div id="mySecondDiv"></div>');
$("#myDiv").append('<div id="mySecondDiv"></div>');
Two jQuery Wrappers (per example):
var mySecondDiv=$('<div id="mySecondDiv"></div>');
$('#myDiv').html(mySecondDiv);
var mySecondDiv=$('<div id="mySecondDiv"></div>');
$('#myDiv').append(mySecondDiv);
You have a few different use cases going on. If you want to replace the content, .html is a great call since its the equivalent of innerHTML = "...". However, if you just want to append content, the extra $() wrapper set is unneeded.
Only use two wrappers if you need to manipulate the added div later on. Even in that case, you still might only need to use one:
var mySecondDiv = $("<div id='mySecondDiv'></div>").appendTo("#myDiv");
// other code here
mySecondDiv.hide();
if by .add you mean .append, then the result is the same if #myDiv is empty.
is the performance the same? dont know.
.html(x) ends up doing the same thing as .empty().append(x)
Well, .html() uses .innerHTML which is faster than DOM creation.
.html() will replace everything.
.append() will just append at the end.
You can get the second method to achieve the same effect by:
var mySecondDiv = $('<div></div>');
$(mySecondDiv).find('div').attr('id', 'mySecondDiv');
$('#myDiv').append(mySecondDiv);
Luca mentioned that html() just inserts hte HTML which results in faster performance.
In some occassions though, you would opt for the second option, consider:
// Clumsy string concat, error prone
$('#myDiv').html("<div style='width:'" + myWidth + "'px'>Lorem ipsum</div>");
// Isn't this a lot cleaner? (though longer)
var newDiv = $('<div></div>');
$(newDiv).find('div').css('width', myWidth);
$('#myDiv').append(newDiv);
Other than the given answers, in the case that you have something like this:
<div id="test">
<input type="file" name="file0" onchange="changed()">
</div>
<script type="text/javascript">
var isAllowed = true;
function changed()
{
if (isAllowed)
{
var tmpHTML = $('#test').html();
tmpHTML += "<input type=\"file\" name=\"file1\" onchange=\"changed()\">";
$('#test').html(tmpHTML);
isAllowed = false;
}
}
</script>
meaning that you want to automatically add one more file upload if any files were uploaded, the mentioned code will not work, because after the file is uploaded, the first file-upload element will be recreated and therefore the uploaded file will be wiped from it. You should use .append() instead:
function changed()
{
if (isAllowed)
{
var tmpHTML = "<input type=\"file\" name=\"file1\" onchange=\"changed()\">";
$('#test').append(tmpHTML);
isAllowed = false;
}
}
This has happened to me . Jquery version : 3.3.
If you are looping through a list of objects, and want to add each object as a child of some parent dom element, then .html and .append will behave very different. .html will end up adding only the last object to the parent element, whereas .append will add all the list objects as children of the parent element.

passing values to a function does not work when inside for loop

I'm mapping currencies from a json file and i render the mapped currencies to a component. I have a .php file like this
<div class="currency-switch-container" id="currency_container">
<span style="font-size:12px;font-weight:bold">All currencies</span>
<div id="currency-map" style="margin-top:15px"></div>
</div>
I refer the div in the above component in my js file as follows
let currencyMap = jQuery("#currency-map");
And when my jQuery document is ready i'm doing the following
jQuery(document).ready(function($) {
$.getJSON('wp-content/themes/mundana/currency/currency.json', function(data) {
for(let c in data){
currencyMap.append(`<span onclick="onCurrencyClick(${data[c].abbreviation})"
class="currency-item">
<span>
${data[c].symbol}
</span>
<span>
${data[c].currency}
</span>
</span>`)
}
});
}
and my function is like this
function onCurrencyClick(val){
console.log("val",val);
setCookie("booking_currency", val, 14);
}
Here the function does not work. But if i do not pass anything to the function it seems to work as i can see the log in the terminal.
Hi your expression ${data[c].abbreviation} will put the value into function string without string quotes i.e. the resultant would be onCurrencyClick(abbreviation) while it should be onCurrencyClick('abbreviation').
please use onclick="onCurrencyClick('${data[c].abbreviation}')" instead.
Instead of using the inline onclick, use event delegation. This means that you have a single event listener that handles all the events from the children and grandchildren. The modification is a very minor one seeing the example here below.
A reason for doing this is that you keep your JavaScript inside your JS file. Like now, you encounter a JS error and have to look for it in your HTML. That can get very confusing. Also however inline onclick listeners are valid, they are outdated and should be avoided unless there is absolutely no other way. Compare it with using !important in CSS, same goes for that.
function onCurrencyClick(event){
var val = $(this).val();
setCookie("booking_currency", val, 14);
}
currencyMap.on('click', '.currency-item', onCurrencyClick);
This example takes the val that you try to insert from the value attribute from the clicked .current-item. <span> elements don't have such an attribute, but a <button> does and is a much more suitable element for it expects to be interacted with. It is generally a good practice to use clickable elements for purposes such as clicking.
In the example below you see the button being used and the abbreviation value being output in the value attribute of the <button> element and can be read from the onCurrencyClick function.
jQuery(document).ready(function($) {
$.getJSON('wp-content/themes/mundana/currency/currency.json', function(data) {
for(let c in data){
currencyMap.append(`
<button value="${data[c].abbreviation}" class="currency-item">
<span>
${data[c].symbol}
</span>
<span>
${data[c].currency}
</span>
</button>
`)
}
});
onclick will not work for a dynamically added div tag
Yo should follow jQuery on event
Refer: jQuery on
Stackoverflow Refer: Dynamic HTML Elements

how to use html script template correctly

I have to generate some li element automatically and the way I was doing it it through a function that return text inside a loop, something like this:
function getLi(data) {
return '<li>' + data + '</li>';
}
then I found a better way to do it by writing html inside a div:
<div style="display:none;" id="Template">
<li id="value"></li>
</div>
and then I would change the id and value get the html and reset the element to original state:
var element = $("#value");
element.html(data);
element.attr('id', getNewId());
var htmlText = $("#Template").html();
element.html('');
element.attr('id', 'value');
return htmlText;
then I was reading on script template
and I figured this could be a better way of doing it,
However apply the previous code didn't work as the inner elements didn't exist according to this article
so how can I apply this?
EDIT:
I put inside a ul tag, I use this method to get the items dynamically
EDIT2:
<li>
<a href="#" >
<span>
<span>
some text
</span>
</span>
</li>
this isn't necessarily what I have but something along the way
Edit3:
my ul does not exist orgialy it's generated dynamically
I insist this is not a duplicate I want to know how to use a template with some dynamic variables
You could do the following way. It's clean, reusable and readable.
//A function that would return an item object
function buildItem(content, id) {
return $("<li/>", {
id: id,
html: content
});
}
In your loop, you could do the following. Do not append each LI inside the loop as DOM manipulation is costly. Hence, generate each item and stack up an object like below.
var $items = $();
// loop begin
var contents = ['<span><span>', data, '</span></span>'].join('');
var $item = buildItem(contents, getNewId());
$items.add($item);
// loop end
Just outside the loop. append those generated LIs to the desired UL, like below.
$("ul").append($items);
This is what I'd do and I am sure there are many better ways. Hope that helps.
One option is to upgrade to a modern JavaScript framework like AngularJS and then you could do it in one line using ng-repeat.
This would serve your purpose and make you more money as a developer.
If you're going to repeat this, use a templating system. Like {{ mustache }} or Handlebars.js.
If not, you can do this.
<ul>
<li class="hidden"></li>
</ul>
And in Javascript
$('ul .hidden').clone().removeClass('hidden').appendTo('ul');
And CSS, of course
.hidden { display:none }
Try this...
function getLi(data,ID) {
return $('<li id = "'+ ID + '">' + data + '</li>');
}
It returns javascript objest of Li..and you append it where ever you need.
what you need is using jquery templates, in the bellow link you can use good one which I'm using.
you create your template and prepare you JASON object of data.
after that every thing will be ready in one function call, more details in this link.
jquery.tmpl
hope this helps you and any one come to here in future..

AngularJS on top of server generated content

I'm looking for a way to integrate something like ng-repeat with static content. That is, to send static divs and to have them bound to JS array (or rather, to have an array constructed from content and then bound to it).
I realize that I could send static content, then remove and regenerate the dynamic bits. I'd like not to write the same divs twice though.
The goal is not only to cater for search engines and people without js, but to strike a healthy balance between static websites and single page applications.
I'm not sure this is exactly what you meant, but it was interesting enough to try.
Basically what this directive does is create an item for each of its children by collecting the properties that were bound with ng-bind. And after it's done that it leaves just the first child as a template for ng-repeat.
Directive:
var app = angular.module('myApp', []);
app.directive('unrepeat', function($parse) {
return {
compile : function (element, attrs) {
/* get name of array and item from unrepeat-attribute */
var arrays = $parse(attrs.unrepeat)();
angular.forEach(arrays, function(v,i){
this[i] = [];
/* get items from divs */
angular.forEach(element.children(), function(el){
var item = {}
/* find the bound properties, and put text values on item */
$(el).find('[ng-bind^="'+v+'."]').each(function(){
var prop = $(this).attr('ng-bind').split('.');
/* ignoring for the moment complex properties like item.prop.subprop */
item[prop[1]] = $(this).text();
});
this[i].push(item);
});
});
/* remove all children except first */
$(element).children(':gt(0)').remove()
/* add array to scope in postLink, when we have a scope to add it to*/
return function postLink(scope) {
angular.forEach(arrays, function(v,i){
scope[i] = this[i];
});
}
}
};
});
Usage example:
<div ng-app="myApp" >
<div unrepeat="{list:'item'}" >
<div ng-repeat="item in list">
<span ng-bind="item.name">foo</span>
<span ng-bind="item.value">bar</span>
</div>
<div ng-repeat="item in list">
<span ng-bind="item.name">spam</span>
<span ng-bind="item.value">eggs</span>
</div>
<div ng-repeat="item in list">
<span ng-bind="item.name">cookies</span>
<span ng-bind="item.value">milk</span>
</div>
</div>
<button ng-click="list.push({name:'piep', value:'bla'})">Add</button>
</div>
Presumable those repeated divs are created in a loop by PHP or some other backend application, hence why I put ng-repeat in all of them.
http://jsfiddle.net/LvjyZ/
(Note that there is some superfluous use of $(), because I didn't load jQuery and Angular in the right order, and the .find on angular's jqLite lacks some features.)
You really have only one choice for this:
Render differently for search engines on the server, using something like the approach described here
The problem is you would need to basically rewrite all the directives to support loading their data from DOM, and then loading their templates somehow without having them show up in the DOM as well.
As an alternative, you could investigate using React instead of Angular, which (at least according to their website) could be used to render things directly on the web server without using a heavy setup like phantomjs.

JavaScript: pass arguments to onclick handler

I need to pass extra arguments to onclick handler. I can't decide which way is "better":
EDIT:
Context: I have a table that shows roster of an event. Each row has a 'delete' button. What is a better way to pass recordId to the delete-handler?
$('a.button').click(function() {
var recordId = $(this).metadata().recordId;
console.log(recordId);
});
...
<tr>...<a class='{recordId:1} button'>delete</a></tr>
<tr>...<a class='{recordId:2} button'>delete</a></tr>
or
function delete(recordId) {
console.log(recordId);
}
...
<tr>....<a class='button' onclick='deleteRecord(1)'>Delete</a></tr>
<tr>....<a class='button' onclick='deleteRecord(2)'>Delete</a></tr>
What are the pros and cons for each option?
NOTE: I use a.button as a custom, CSS-styled button, it does not behave as a link.
EDIT:
I would appreciate alternative solutions as well, if you can argument the advantages of offered alternatives.
Store the record id as an attribute of element itself, but instead of using the metadata plugin which stores it in a weird format, I would recommend you use HTML5's data attributes that is also backwards compatible.
A row would look like:
<tr> .. <a data-id="1">delete</a> .. </tr>
In the handler, retrieve the attribute value and act on it
function deleteRecord() {
var rowId = $(this).attr('data-id');
...
}
It is comparable to using the metadata plugin, but it does not overload the class attribute. No extra plugins are needed for this. It uses a single handler just as the metadata plugin does which is performant for large datasets.
The inline onclick handlers are bad for the same reasons. A new handler is created per row. It cuts down on flexibility and is generally a bad practice.
I would just go with your second approach - it's the simplest and there is nothing wrong with it.
$('a.button').click(function() {
var classes = $(this).attr('class').split(' ');
var option;
for( var i in classes )
{
if( classes[i].indexOf( 'option' ) != -1 )
{
option = classes[i].substr( 6 );
break;
}
}
console.log( option );
});
...
<a class='option-yes button'>Yes</a>
<a class='option-no button'>No</a>
Edit:
It sounds like you're assigning data to these 'buttons' dynamically. You'd be better off assigning the data to the button using jQuery's .data() method and then getting the data from there. I have updated my example code.
If each type of button performs a different action then use a different handler for each type of button:
$('a.button').click(function (e) {
// Stuff with $(this).data().recordId
});
$('a.button.no').click(function (e) {
//Stuff
});
...
<a class="button yes">Yes</a>
<a class="button no">No</a>

Categories

Resources