Background
I have been using Backbone.js for some time and one aspect of it that impresses me is how it allows me to simplify, abstract and reuse DOM elements as 'views'. I have tried to read through some of the annotated source and am familiar with JQuery, but have little knowledge of how the DOM works on a deeper level.
Question
How does Backbone.JS tie DOM elements to views without assigning an id, class or other attribute to them?
i.e.
<ul>
<li>Item one</li>
<li>Item two</li>
<li>Item three</li>
</ul>
I love that Backbone does this and would like to know how it does it!
In javascript, a variable can hold a reference (i.e a programmatic thing that "refers to") to some element of the DOM, which is just a Javascript object. Backbone makes sure that for a view, at least that element exists. For example, in jQuery, when you refer to the third item in your list:
var item3 = $('ul li').eq(2);
(It's a zero-offset list, the first item is at index 0, the third at index 2), you can now change the text from "Item three" to "Item three point one four one five nine" with ordinary jQuery DOM manipulators:
item3.text("Item three point one four one five nine");
Even though that list item doesn't have any particular class or ID attributes.
A backbone view's el field contains a constant reference to the parent element in which that view renders all of its stuff. Backbone uses the jQuery delegate event manager to attach a generic event handler to that one constant reference. Whenever an event happens within that DOM element or any of its children, the delegate catches the event, along with a reference to the specific DOM object within the parent el that created the event, and backbone uses some fairly standard jQuery magic to map that to an event handler in the view.
It is, indeed, very cool stuff.
I should add that the "constant"-ness of the el reference is, er, arguable. If you're attaching view logic to an existing HTML element, you do assign to the el once, in the view's initialize(). Javascript doesn't enforce any notion of constantness, but you should only directly assign to el (ie this.el = something()) if you're sure you know what you're doing.
Related
Using and html element's addEventListener has several advantages over using inline events, like onclick.
However, to store the element including its inline event is straight forward, for example, by embedding it in a parent element and storing the parent's innerHTML.
Is it possible to do something similar when using event listeners?
Edit:
I realized that my question is not sufficiently explained. So here some additions.
By "store" I mean a way to get the information holding the element and the event listener.
The analogue with inline events is easy: just embed in a parent element and save the parent's innerHTML (string) somewhere, for example in a database, and recreate the element later by loading the string and applying it to the innerHTML of some element.
But how would one do the analogue with elements when using event listeners? One cannot just use the innerHTML since then the events are not stored.
I hope this clarifies my question a bit.
Edit 2
With the help of comments I have made some unsuccessful attempts.
It is possible to get store the information of an element using createDocumentFragment() or element.cloneNode(true).
However, the first method does not work for external storage since, if I understood correctly, will contain only a pointer. Here is an example:
https://jsfiddle.net/hcpfv5Lu/
The second method does not work either. I am not fully sure why, but if I JSON.stringify the clone it "vanishes". Here is an example:
https://jsfiddle.net/3af001tq/
You could use a document fragment to store the DOM node in a JavaScript variable which can then be appended to a DOM element when required.
https://developer.mozilla.org/en/docs/Web/API/Document/createDocumentFragment
Yes.
You can use something like.
<ul>
<li id="list">Some data</li>
<li>Dumy</li>
</ul>
then in your javascript file,
document.getElementById("list").addEventListener("click", function(){
var htmlMarkUp = this.parentNode.innerHTML;
});
This would store the html content of ul in var htmlMarkUp.
I remember when I was playing with Flash around 10 years ago. I could create a clip, than duplicate it, and whatever I would do with clip 1 would auto-instantly change duplicated item - so in essence these where two windows of the same thing.
I wonder if same is possible with dom elements. I would like to clone element, and whatever happens to original should happen to clone too. In whatever I mean, click events and classes mostly.
This is html:
<li id="67" data-word-id="2" data-order-id="1" class="ui-state-default">
<article>
Some content...
<div class="invisible active" data-invisi-status"1"="" title="Do you want to keep this item private and invisible to anyone except for you?">Make word invisible</div>
</article>
</li>
I want to keep invisible icon active class and status synced between copied elements.
I achieve copying from 1 list to the other by:
$(this).removeClass("ui-sortable-helper").css({"height":"auto"}).clone().appendTo($list).show("slow");
How I would keep this synced?
Using classes affects all DOM elements of that class. So I guess you could think of a flash clip as a DOM element with a specific class.
$('.my-clip').removeClass('ui-sortable-helper');
Would remove the class ui-sortable-helper from all elements with the class .my-clip
However, there are certain methods which only work on one element, and it's generally the first in the selection.
If you bind an event handler to something inside .my-clip, say a link for instance, and then refer to it using the this variable, you're only updating the element that triggered the event.
For example:
$('.my-clip').on('click', 'a', function(e){
$(this).addClass('link-clicked');
});
This would only add the class link-clicked to the link that triggered the event, however, if you did:
$('.my-clip').on('click', 'a', function(e){
$('.my-clip').find('a').addClass('link-clicked');
});
It would add the class link-clicked to all a elements within all instances of .my-clip
I would like to know what is more appropriate to do if I really need a good performance for my app. I'm programming cross-platform apps via PhoneGap and the way I code is very crucial.
Which is more appropriate:
document.getElementbyID('id').addEventListener()
or
var id = document.getElementbyID('id');
id.addEventListener();
and how can I use the keyword "delete" to improve the performance of my app?
According to this test i just made on jsperf.com, document.getElementbyID('id').addEventListener() seems to be the fastest way. - in Chrome on Mac OS X.
Try it on the desired browsers, and edit the test to add/remove features such as the delete you were talking about.
The difference between the two will be marginal. To improve performance you should minimize the number of event handlers you add to the dom and remove those you don't require again. Delete doesn't make sense in the context you posted. It should be used to free up items in (associative) arrays or to remove objects you created. You do neither of those in your example.
For lists in which each item is clickable you should just attach one event handler to the list container and not to individual elements. You can then use the target property of the event object passed into the handler to find the actual listitem that was tapped.
edit: an example on how to use one event handler for multiple list items
The li.id is used to identify the actual item that was clicked. If the 'li' have children you might have to walk up the target DOM tree until you find the correct item.
<ul id="list">
<li id="item_1">First Item</li>
<li id="item_2">Second Item</li>
<li id="item_3">Third Item</li>
<li id="item_4">Fourth Item</li>
<li id="item_5">Fifth Item</li>
<li id="item_6">Sixth Item</li>
<li id="item_7">Seventh Item</li>
</ul>
<script>
window.onload(function() {
document.getElementById("list").addEventListener("click",
function(event) { alert("" + event.target.id + " was clicked"); });
});
</script>
JavaScript is interpreted at runtime, so ultimately it depends on whether or not the compiler optimizes away that variable allocation or not. Either way, the difference will be extremely small. Build a few tests and see.
Focus on readability (and finding a model where you are not worried about micro-optimization).
and how can i use the keyword "delete" to improve the performance of
my app?
Since you are discussing performance-intensive applications, I assume you are interested in proper garbage collection in JavaScript. See How does garbage collection work in JavaScript?
It is better to use
var id = document.getElementbyID('id');
id.addEventListener();
If you require the DOM element more than once else it takes same time for both ways.
I'm displaying a tabbed interface with the help of jQuery. When you click a tab, a ajax call will replace all html from a $(".content") element with new html, using something like
$(".content").html(response);
When I do this, are all jquery events and functions that are attached to elements inside the .content div removed? Is it ok to fire these events and functions again after I replace the HTML ? If I click the tabs 324523452354 times, will it duplicate jQuery data every time?
Yes. They will be removed. You can use the live event to attach to elements that dont exist yet.
$(".myElementClass").live("click", function (e) {
e.preventDefault();
//do stuff
});
In this case, this function will always be called on myElement no matter when it is injected into the DOM.
All HTML inside of your selector is replaced with the parameter you pass in, implying it is completely removed from the DOM. Meaning if you have:
<div id="mine">
<ul>
<li>One thing</li>
</ul>
</div>
And I do a call as such:
$('div#mine').html("hey");
My HTML will then be:
<div id="mine">
hey
</div>
As you can see the is completely removed and all its bound events mean nothing. If you use the jQuery.live() binding instead however, then elements that don't yet exist can have events associated with them. Meaning if you add some elements to the DOM then they events will still work, without you have to rebind if you add more, or replace them.
**.live** events are binded at the document level , read the following document which is really useful
http://www.bennadel.com/blog/1751-jQuery-Live-Method-And-Event-Bubbling.htm
I'm trying to implement a simple horizontal navigation menu that just shows a single div for each link. It is kinda like a dropdown menu but instead of a mouseover triggering a dropdown, an onclick event will trigger the showing of a div. I want to make sure I am taking the right approach before going too much further, any help is appreciated. This is what I have so far:
<ul id="settings_nav">
<li>
<a>Theme</a>
<div id="settings_block"><%= render :partial => 'email_password' %></div>
</li>
<li>
Lists
<div id="settings_block"><%= render :partial => 'lists' %></div>
</li>
</ul>
window.onload = function(){
settingsMenuInit('settings_nav')
}
function settingsMenuInit(settings_nav){
$(settings_nav).childElements().each(
function(node){
node.onclick= function(){ this.next.show() };
})
}
Something like that, but I am unsure how to get the div that is currently shown and hide it. I could iterate through all the childElements and hide each div and then show the one that is being clicked, but maybe there's a better way?
Some notes FW(T)W:
With Prototype and similar libraries, you don't want to hook up event handlers by assigning functions to the element's onclick and similar properties; that style has several disadvantages (not least that there can only be one handler for the event on the element). Instead, use Prototype's observe function:
someElement.observe('click', functionRefHere);
// or
Element.observe(someElementOrID, 'click', functionRefHere);
This also lets Prototype work around some IE memory loss bugs for you.
You might look at is Prototype's dom:loaded event, which happens sooner than window.onload (which won't happen until all of your images and other external resources have loaded, which can be a second or two after the page is displayed):
document.observe('dom:loaded', initFunctionRefHere);
You can use event delegation and just watch your settings_nav element, rather than each child node individually.
$(settings_nav).observe('click', handleNavClick);
function handleNavClick(event) {
var elm = event.findElement("some CSS selector here");
if (elm) {
event.stop();
// Handle it
}
}
As you can see, Event#findElement accepts a CSS selector. It starts with the actual element that was clicked and tries to match that with the selector; if it matches, it returns the element, otherwise it goes to the parent to see if it matches; etc. So with your HTML you might look for a li (event.findElement('li')) or the link (event.findElement('a')).
But if you want to watch each one individually, they can share a function (as they do in your example):
$(settings_nav).childElements().invoke('observe', 'click', handleNavClick);
function handleNavClick(event) {
// Prototype makes `this` reference the element being observed, so
// `this` will be the `li` element in this example.
}
Whether you watch each element individually or use event delegation depends on what you're doing (and personal preference). Whenever anything is likely to change (adding and removing navigation li elements, for instance) or when there are lots of things to watch, look to event delegation -- it's much easier simpler to deal with changing sets of elements using event delegation and just watching the parent. When dealing with a stable structure of just a few things (as in your example), it may be simpler to just watch the elements individually.
Once inside your handler, you can use Element#down to find child elements (so from the li, you might use li.down('div') to find the div), or Element#next to get to the next sibling element (e.g., going from the link to the div). Either way, once you have a reference to the div, you can use Element#show and Element#hide (or Element#toggle).
I recommend using named functions instead of anonymous ones (see my example above). Named functions help your tools (debuggers, browsers showing errors, etc.) help you. Just be sure not to declare a named function and use it as an expression (e.g., don't immediately assign it to something):
// Don't do this because of browser implementation bugs:
someElement.observe('click', function elementClickHandler(event) {
// ...
});
// Do this instead:
someElement.observe('click', elementClickHandler);
function elementClickHandler(event) {
// ...
}
...because although you should be able to do that according to the spec, in reality various bugs in various browsers make it not work reliably (article).