lazy binding click event on elements not on dom - javascript

There are few elements on our DOM which are loaded as a response to a webservice.
I want to bind click event on them.
I do not have any control on webservice call, as it is done by a framework I am not intending to modify.
$(document).on('click', 'element', function)
This would have helped me but the jquery version that is being used is older.
Is there an alternative or native javascript solution to this?

One of the hot methodologies in the JavaScript world is event delegation, and for good reason. Event delegation allows you to avoid adding event listeners to specific nodes; instead, the event listener is added to one parent. Refer this: http://davidwalsh.name/event-delegate
document.getElementById("parent-list").addEventListener("click", function(e) {
// e.target is the clicked element!
// If it was a list item
if(e.target && e.target.nodeName == "LI") {
// List item found! Output the ID!
console.log("List item ", e.target.id.replace("post-"), " was clicked!");
}
});

Related

Class selector does not work after append (JQuery)

$(".quantidade").on("change blur", function() {
if ($(this).val().length) {
validateField("quantidade");
} else {
invalidateField("quantidade");
showGeneralMessage("É necessário preencher a quantidade!", "danger");
}
});
It works just fine, until a new item is added with append:
$(".btn-add-produto").click(function() {
...
$("#listaProdutosPedido").append(content);
...
});
This "content" includes input with the same class. After this html piece is added, the class selector does not apply to it. I guess it happens because when the document is ready, that piece of HTML does not exist. How do I "reload" the javascript / document so that the selector works even with future references? I've tried to check with setInterval(), but it does not work as well...
You need to use .on() delegated event syntax. In event delegation, you attach the event to a parent element. Internally, jQuery relies on the events ability to bubble up the DOM tree. When an event of the specified type with the e.target property matching your delegated selector is found, your event is fired.
To accomodate this change, modify your code like so:
$(document).on("change blur", ".quantidade", function() {
if ($(this).val().length) {
validateField("quantidade");
} else {
invalidateField("quantidade");
showGeneralMessage("É necessário preencher a quantidade!", "danger");
}
});
Please note, delegating at this high of a level is extremely inefficient. document should be changed out for the closest parent element that contains the newly appended content. In this case, I used it to demonstrate how the process occurs because there was not HTML DOM tree output specified in your question. When implementing this into your system, I encourage you to update the document selector accordingly.

Clone div with all nested elements along with events in pure javascript

I am trying to clone a div which has input fields.
But the eventListeners are not being fired.
Even though I am doing a deep clone in the following way -
var rows = document.querySelectorAll('.row');
var dupNode = rows[0].cloneNode(true);
sheet.appendChild(dupNode);
Here is the Demo
Each input has a click event and the cloned inputs are not registering with the click event. What am I missing ?
The simplest (and the most effective) thing to do is to bind single event listener on the #sheet container and benefit from event bubbling. In this case you can append as many new elements as you wish and will never need to bind events to them:
document.querySelector('#sheet').addEventListener('click', function(e) {
var target = e.target;
if (target.tagName == 'INPUT') {
alert("clicked");
}
}, false);
A little tricky part is that you need to check on what element event occurred and execute your code on those only you are interested in.
Demo: http://jsbin.com/mejegaqasu/1/edit?html,js,output
Event handlers are not copied during cloning. You will need to delegate the event handlers if you want them to work everywhere in the DOM after cloning.
Event delegation http://davidwalsh.name/event-delegate
cloneNode documentation https://developer.mozilla.org/en-US/docs/Web/API/Node.cloneNode

Use onclick function after ajax call

So I have built one page that all the data gets pulled into. Works great in IE9+ and chrome but need it to work in IE8 and anything that is pulled in through ajax wont work. Code would be something like this.
ajax
$.ajax({
url: "includes/myfile.php",
type: "GET",
data: ({'data' : data,}),
success: function(data){
$("section").append(data);
}
});
JS
function myfunction(data){
//do something
}
HTML
< button onclick='myfunction(data)' > click here < /button>
Note: I know $(something).on(stuff,stuff) will work but that would involve rebuilding all my functions. so trying to avoid that if I can.
It is because jQuery /JavaScript is unaware of the items that are loaded in myfile.php. The easiest solution is to use on() to delegate the events there, so those events will bubble up the DOM tree.
http://api.jquery.com/on/
For instance -
$(document).on('click', 'element', function() {
Now, when element is clicked the click event bubbles up to document where it will be acted on. The element that the event bubbles up to must exist at the time your jQuery code originally runs so jQuery is 'aware' of the element and can intercept bubbled events to that element. That makes document a fairly safe bet, but you can narrow it down if you desire.
If you don't want to go the easier route you can write event delegation in vanilla JavaScript:
// get the parent element, add a click listener...
document.getElementById("element").addEventListener("click", function(e) {
// example using a list item
if(e.target && e.target.nodeName == "li") {
console.log("list item ", e.target.id.replace("post-"), " was clicked!");
}
});

Closure event delegation - event listener on DOM parent that covers children/descendants of a given class

In jQuery, you can do the following:
$('#j_unoffered').on('click', '.icon_del', function () {...
This puts one handler on the element j_unoffered that fires if any descendant element with class icon_del is clicked. It applies, furthermore, to any subsequently created icon_del element.
I can get this working fine in Closure where the click is on the element itself.
goog.events.listen(
goog.dom.getElement('j_unoffered'),
goog.events.EventType.CLICK,
function(e) {...
How can I specify a parent event target in Closure that works for its children/descendants in the same way as the jQuery example?
I'm assuming I need to use setParentEventTarget somehow, but I'm not sure how to implement it for DOM events. Most of the documentation I've found pertains to custom dispatch events.
-- UPDATE --
I'm wondering if there is anything wrong with this rather simple solution:
goog.events.listen(
goog.dom.getElement('j_unoffered'),
goog.events.EventType.CLICK,
function(e) {
if (e.target.className.indexOf('icon_del') !== -1) {...
It still leaves this bound to the parent, but e.target allows a work-around. The fifth argument in listen (opt_handler) allows you to bind this to something else, so I guess that's an avenue, too.
I don't know about such possibility too, so I suggest other piece of code:
var addHandler = function(containerSelector, eventType, nestSelector, handler) {
var parent = goog.isString(containerSelector) ?
document.querySelector(containerSelector) :
containerSelector;
return goog.events.listen(
parent,
eventType,
function(e) {
var children = parent.querySelectorAll(nestSelector);
var needChild = goog.array.find(children, function(child) {
return goog.dom.contains(child, e.target);
});
if (needChild)
handler.call(needChild, e);
});
});
Usage:
addHandler('#elem', goog.events.EventType.CLICK, '.sub-class', function(e) {
console.log(e.target);
});
Update:
If you will use this e.target.className.indexOf('icon_del') there will be possibility to miss the right events. Consider a container div with id = container, it has couple of divs with class innerContainer, and each of them contains couple of divs with class = finalDiv. And consider you will add event handler with your code above, which will check e.target for innerContainer class. The problem is when user will click on finalDiv your handler will be called, but the event target will be finalDiv, which is not innerContainer, but contained by it. Your code will miss it, but it shouldn't. My code checks if e.target has nested class or contained by it, so you will not miss such events.
opt_handler can't really help you either, because there might be many nested elements you want to hanlde (which of them will you pass here? maybe all, but not that helpful, you can get them in event handler whenever you want), moreover they can be added dynamically after, so when you add handler you could not know about them.
In conclusion, I think doing such a job in an event handler is justified and most efficient.
What you are referring to is called event delegation
It seems that this is not possible (out of the box) with Google Closure Library; So my recommandation is to use jQuery or another similar event handling library that offers this functionality. If this is not possible or if you wanna do it by hand here's one possible approach (NOTE: this is not for production use)
var delegateClick = function(containerId, childrenClass, handler){
goog.events.listen(goog.dom.getElement(containerId), goog.events.EventType.CLICK, function(event){
var target = event.target;
//console.log(event);
while(target){
if ( target.className && target.className.split(" ").indexOf(childrenClass)!== -1) {
break;
}
target = target.parentNode;
}
if(target){
//handle event if still have target
handler.call(target, event);
}
});
}
//then use it, try this here: http://closure-library.googlecode.com/git/closure/goog/demos/index.html
//..select the nav context
delegateClick( 'demo-list' ,'goog-tree-icon', function(event){console.log(event);})
Here's a more in depth analysis of event delegation
Again, you should use a proven library for this, here are some options: jQuery, Zepto, Bean

Can I improve jQuery performance by combining all events of same type into one handler?

I have a jQueryMobile site/app where performance is an issue. I am reading a lot about how it's smart not to bind too many separate handlers. So if you have a page with a lot of buttons , the advice is to bind like this...
$(document).on('click',function(){
if (this = $('#button1')){
//Handler for button one
} else if (this = $('#button2') {
//etc
} else //etc
})
...rather than like this...
$('#button1').click(function(){
//Handler for button one
})
$('#button2').click(function(){
//Handler for button two
})
//etc
So I am wondering to what extent this is truly useful.
What if I made one listener for all 'pageshow' events in my document? (a jQueryMobile website is a single document containing many pages, and many of those pages need a bit of dolling up around showtime).
What if I made one click listener for all forty-odd buttons in the document, and used the handler to distinguish which button we're dealing with and what needs to be done in response?
Your first option is faster to install and initialize, but slower to run when an event happens.
If you had hundreds of buttons with all nearly identical event handling, it might make sense to only install one event handler for all of them rather than run the code to install hundreds of event handlers. But, you would still pay a small performance penalty when a particular event happens.
If you want to maximally optimize for run-time performance at the time of an event, it is still faster to install an event handler on the specific object you want to monitor. That's because the event handlers are kept in a separate list just for that object and the code doesn't have to sort through a long list of event handlers to find which one is appropriate for that specific object and event.
Also, your if statement in the first option will never work for several reasons. It would need to be something like this:
$(document).on('click',function(e) {
if (e.target.id == 'button1') {
//Handler for button one
} else if (e.target.id == 'button2') {
//etc
}
})
And, anytime you use delegated event handling, it's better to pick a common parent for the buttons that is much closer to the actual objects than the document object (helps runtime performance).
$("#buttonContainer").on('click',function(e) {
if (e.target.id == 'button1') {
//Handler for button one
} else if (e.target.id == 'button2') {
//etc
}
})
If you wanted just button clicks with delegated event handling, you could use some of the delgation features built into .on() like this
$("#buttonContainer").on('click', 'button', function(e) {
if (this.id == 'button1') {
//Handler for button one
} else if (this.id == 'button2') {
//etc
}
})
The advice is, to use event delegation. You don't actually need to bind that "master event handler" to the document or document.body, you can (and should) bind it to the closest possible node that all buttons share.
$('#ClosestSharedParent').on('click', 'button', function( event ) {
// "this" references the button that was actually clicked
switch( event.id ) {
case 'button1':
alert('button1 clicked');
break;
case 'button2':
alert('button2 clicked');
break;
// and so on
}
});
The second argument from .on() accepts a selector, where you can specify which elements you want to delegate events for. In older version of jQuery, this was accomplished by the .delegate() method.
Ref.: .one(), .delegate()

Categories