Adding bulk items to DOM while preserving jQuery event handling? - javascript

I'm adding some bulk <li> elements to a <ul>.
Previously, I was doing something like:
var playlistItemListUl = $('#PlaylistItemList ul');
for(var i = 0; i < 1000; i++ ){
var listItem = $('<li/>', {
'data-itemid': item.get('id'),
contextmenu: function (event) {
console.log('contextmenu', event);
},
click: function(event){
console.log('click', event);
}
});
listItem.appendTo(playlistItemListUl);
}
The listItem's click and contextmenu events successfully write to the console.log in this example. However, it is painfully slow and I wanted to reduce the lag induced from the bulk-add. So, I rewrote the code to incur just one DOM modification:
var playlistItemListUl = $('#PlaylistItemList ul');
var listItemsOuterHtml = [];
for(var i = 0; i < 1000; i++ ){
var listItem = $('<li/>', {
'data-itemid': item.get('id'),
contextmenu: function (event) {
console.log('contextmenu', event);
},
click: function(event){
console.log('click', event);
}
});
listItemsOuterHtml.push(listItem.prop('outerHTML'));
}
playlistItemListUl.append(listItemsOuterHtml.join(''));
This reduces the number of DOM manipulations required, but all of my nicely bound jQuery events are discarded.
Is there an effective middle-ground between these two styles which would allow me to create nodes using jQuery, instead of hand-crafting the HTML, but avoid unnecessary DOM insertions?

Why not add those click events to playlistItemListUl and let event delegation take care of the event? Like this :
playlistItemListUl.append(listItemsOuterHtml.join('')).on("click", "li", function(event) {
console.log('click', event);
});
Here the click event would be bound to the <ul/> and will get cascaded to <li/> when they are clicked on. And on is the replacement for live, bind and delegate. And, if you're binding multiple events,
playlistItemListUl.append(listItemsOuterHtml.join('')).on({
"click": function(event) {
console.log('click', event);
},
"contextmenu" : function (event) {
console.log('contextmenu', event);
}
}, "li");
And if you want it to work in this current setup, you must send out elements in listItemsOuterHtml, not its outerHTML alone.

You're pushing the outerHTML to an array, you should be storing DOM elements, not strings of HTML.
var playlistItemListUl = $('#PlaylistItemList ul'),
listItems = $([]);
for(var i = 0; i < 1000; i++ ){
var listItem = $('<li/>', {
'data-itemid': item.get('id'),
contextmenu : function (event) {
console.log('contextmenu', event);
},
click: function(event){
console.log('click', event);
}
});
listItems = listItems.add(listItem);
}
playlistItemListUl.append(listItems);

Related

Is there any way to make the onClick global or DRY?

$("input").on("keypress",function(e){
if(e.which===13){
$("ul").last().append("<li>"+$(this).val()+"</li>");
}
$("li").on("click",function(){
$(this).toggleClass("striked");
});
$("li").on("mouseenter",function(){
$(this).css("color","green");
});
});
$("li").on("click",function(){
$(this).toggleClass("striked");
});
$("li").on("mouseenter",function(){
$(this).css("color","green");
});
$("#slide").on("click",function(){
$("input").slideToggle();
});
Here, I have used the onClick event on<li> to apply the striked class two times just to make it work for both dynamic and non-dynamic elements on the page. But the code is replicated and seems long. Is there any way to shorten so that I can write it once and it gets activated for both types of elements?
Use event delegation instead, on the ul, so you only have to set up listeners once, rather than setting up multiple listeners for every element on load and on each .append. Also, save the ul and the input jQuery-wrapped elements in a variable once rather than selecting them and wrapping them with jQuery each time they're used:
const $ul = $("ul");
const $input = $("input");
$input.on("keypress", function(e) {
if (e.which === 13) {
$ul.last().append("<li>" + $(this).val() + "</li>");
}
});
$ul.on("click", 'li', function() {
$(this).toggleClass("striked");
});
$ul.on("mouseenter", 'li', function() {
$(this).css("color", "green");
});
$("#slide").on("click", function() {
$input.slideToggle();
});
A rather generic approach would be to capture the click event and check if it is from ul
document.body.onclick = function(e){
e = e || event;
var from = findParent('ul',e.target || e.srcElement);
if (from){
/* it's a link, actions here */
}
}
//find first parent with tagName [tagname]
function findParent(tagname,el){
while (el){
if ((el.nodeName || el.tagName).toLowerCase()===tagname.toLowerCase()){
return el;
}
el = el.parentNode;
}
return null;
}
now you can change the tagName passed to the findParent function and do accordingly
Read Here
You can try using the jquery all selector $('*'). For more information on this see
https://api.jquery.com/all-selector/.
Or you can add a specific class to every element you want to have an onClick action.

Passing this to a method?

p.num = 100;
$('body').on('click', '.del', this.delete.bind(this));
p.delete = function(e) {
console.log(this.num); //100
//how can I get the .del element?
}
I'm trying to get the element that produced the click, but I also need access to the num property.
How can I access both types of 'this' inside my delete method?
The callback for an event receives an Event object that you can use to retrieve the element on which the event was called.
function(e) {
var element = $(e.target); //This is the element that the event was called on.
}
Disclaimer : This is the my exact answer (adapted with the current code) taken from here : Pass additional arguments to event handler?
Yet, the question doesn't seem to be an exact duplicate (but i may be wrong).
As said in the documentation of .on, you can pass datas to your event.
.on( events [, selector ] [, data ], handler )
data
Type: Anything
Data to be passed to the handler in event.data when an event is triggered.
So your event could look like that :
p.num = 100;
$('body').on('click', '.del', {object : this}, this.delete);
p.delete = function(e) {
var myObj = e.data.object;
// [myObj] will be your plugin
// [this] will be the clicked element
// [e] will be you event
}
if you're using jquery, you can combine those functions all into one like below:
note: num is an attribute so you have to use .attr().
$(document).ready(function() {
$('body').on('click', '.del', function() {
var num = $(this).attr('num');
alert('click function and num = ' + num);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Delete
or if you really want to keep them separate functions....:
$(document).ready(function() {
$('.del').on('click', function() {
deleteThis($(this));
});
});
function deleteThis(element){
var num = element.attr('num');
alert('click function and num = ' + num);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Delete
also, if youre using separate functions for the click and the delete, pass the element from click to delete: pass element - callDeleteFunction($(this)), and retrieve element - myDeleteFunction(element aka $(this))
I'm not sure what you're asking about but maybe this is what you want:
var p = {};
p.num = 100;
$('body').on('click', '.del', p.delete); // don't bind to this
p.delete = function(e) {
console.log(p.num); // here you still can call p
// this is del DOM element
}

Need help in using .on in jquery so that it can work with dynamic data also

I have a function "single_double_click" and I am invoking the same via $('#packagelisttable tr').single_double_click(fn), which works fine with static data.
However it is not responding when I am deploying the same application to work with dynamic data.
I tried using .on also as mentioned in several posts but then also no success.Please find the same below:
$(#packagelisttable ).on('single_double_click', 'tr', fn)
$(document).on('single_double_click', 'packagelisttable tr', fn)
I need to click on a row of table (#packagelisttable) and need to check whether it was a single or double click.
Please find the code which I am using:
jQuery.fn.single_double_click = function (single_click_callback, double_click_callback, timeout) {
return this.each(function () {
var clicks = 0,
self = this;
jQuery(this).click(function (event) {
clicks++;
if (clicks == 1) {
setTimeout(function () {
if (clicks == 1) {
single_click_callback.call(self, event);
} else {
double_click_callback.call(self, event);
}
clicks = 0;
}, timeout || 300);
}
});
});
}
//$(#packagelisttable ).on('single_double_click', 'tr', function(){
//$(document).on('single_double_click', 'packagelisttable tr', function(){
// $('#packagelisttable tr').single_double_click(function () {
alert("Try double-clicking me!")
},
function () {
alert("Double click detected")
});
The delegated event version of on is used for events, but single_double_click is not an event. It is a function.
It is not possible to connect a jQuery plugin/function to a dynamically loaded elements that way.
You either need to connect any new elements to your plugin after load, or change the plugin to use classes (e.g. class="singleordouble") and use a delegated click event handler, or you can add a selector as an additional parameter and attach to a non-changing ancestor element (as Cerlin Boss demonstrates).
e.g.
jQuery(document).on('click', '.singleordouble', function (event) {
But if you do that, using a plugin becomes pointless.
It is more flexible to generate your own custom click events, using the settimeout trick you already have.
Here is a full example using custom events: http://jsfiddle.net/TrueBlueAussie/wjf829ap/2/
Run this code once anywhere:
// Listen for any clicks on the desired
$(document).on('click', '.singleordouble', function (e) {
var $this = $(this);
var clicks = $this.data("clicks") || 0;
// increment click counter (from data stored against this element)
$(this).data("clicks", ++clicks);
// if we are on the first click, wait for a possible second click
if (clicks == 1) {
setTimeout(function () {
var clicks = $this.data("clicks");
if (clicks == 1) {
$this.trigger("customsingleclick");
} else {
$this.trigger("customdoubleclick");
}
$this.data("clicks", 0);
}, 300);
}
});
It will generate custom events (called customsingleclick and `customdoubleclick in this example, but call them whatever you want).
You can then simply listen for these custom events:
$(document).on('customsingleclick', function(e){
console.log("single click on " + e.target.id);
});
$(document).on('customdoubleclick', function(e){
console.log("double click on " + e.target.id);
});
Or using delegated event handlers: http://jsfiddle.net/TrueBlueAussie/wjf829ap/3/
$(document).on('customsingleclick', '.singleordouble', function(){
console.log("single click on " + this.id);
});
$(document).on('customdoubleclick', '.singleordouble', function(){
console.log("double click on " + this.id);
});
how about this
I have made some small changes to your code. Not sure if this will work for you.
I have added one more parameter which takes a selector. Please comment if you have any doubt.
jQuery.fn.single_double_click = function (selector, single_click_callback, double_click_callback, timeout) {
return this.each(function () {
var clicks = 0,
self = this;
jQuery(this).on('click', selector, function (event) {
clicks++;
if (clicks == 1) {
setTimeout(function () {
if (clicks == 1) {
single_click_callback.call(self, event);
} else {
double_click_callback.call(self, event);
}
clicks = 0;
}, timeout || 300);
}
});
});
}
Usage :
$('#headmnu').single_double_click('li',
function () {
; // not doing anything here
}, function () {
alert('twice')
});
Here li is the child of the first jquery selector($('#headmnu')) which is a ul
This will work with dynamically added elements also.
UPDATE
Just to clarify $('#headmnu') is a parent element of all lis.
I have used event delegation here to achieve this. Please refer the documentation for more info
I checked your code and if you have pasted, then you should also check
$(#packagelisttable ).on('single_double_click', 'tr', fn) // shold be
$('#packagelisttable').on('single_double_click', 'tr', fn)
$(document).on('single_double_click', 'packagelisttable tr', fn) // should be
$(document).on('single_double_click', '#packagelisttable tr', fn)

Implementing jQuery's "live" binder with native Javascript

I am trying to figure out how to bind an event to dynamically created elements. I need the event to persist on the element even after it is destroyed and regenerated.
Obviously with jQuery's live function its easy, but what would they look like implemented with native Javascript?
Here's a simple example:
function live(eventType, elementId, cb) {
document.addEventListener(eventType, function (event) {
if (event.target.id === elementId) {
cb.call(event.target, event);
}
});
}
live("click", "test", function (event) {
alert(this.id);
});
The basic idea is that you want to attach an event handler to the document and let the event bubble up the DOM. Then, check the event.target property to see if it matches the desired criteria (in this case, just that the id of the element).
Edit:
#shabunc discovered a pretty big problem with my solution-- events on child elements won't be detected correctly. One way to fix this is to look at ancestor elements to see if any have the specified id:
function live (eventType, elementId, cb) {
document.addEventListener(eventType, function (event) {
var el = event.target
, found;
while (el && !(found = el.id === elementId)) {
el = el.parentElement;
}
if (found) {
cb.call(el, event);
}
});
}
In addition to Andrew's post and Binyamin's comment, maybe this is an option:
With this you can use 'nav .item a' as the selector.
Based on Andrew's code.
function live (eventType, elementQuerySelector, cb) {
document.addEventListener(eventType, function (event) {
var qs = document.querySelectorAll(elementQuerySelector);
if (qs) {
var el = event.target, index = -1;
while (el && ((index = Array.prototype.indexOf.call(qs, el)) === -1)) {
el = el.parentElement;
}
if (index > -1) {
cb.call(el, event);
}
}
});
}
live('click', 'nav .aap a', function(event) { console.log(event); alert('clicked'); });
The other solutions are a little overcomplicated...
document.addEventListener('click', e => {
if (e.target.closest('.element')) {
// .element has been clicked
}
}
There is a polyfill in case you need to support Internet Explorer or old browsers.
An alternative to binding an event to dynamically to a specific element could be a global event listener. So, each time you update the DOM with another new element event on that element will also the "catches". An example:
var mybuttonlist = document.getElementById('mybuttonlist');
mybuttonlist.addEventListener('click', e=>{
if(e.target.nodeName == 'BUTTON'){
switch(e.target.name){
case 'createnewbutton':
mybuttonlist.innerHTML += '<li><button name="createnewbutton">Create new button</button></li>';
break;
}
}
}, false);
ul {
list-style: none;
padding: 0;
margin: 0;
}
<ul id="mybuttonlist">
<li><button name="createnewbutton">Create new button</button></li>
</ul>
In this example I have an event listener on the <ul> for click events. So, an event happens for all child elements. From the simple event handler I created, you can see that it is easy to add more logic, more buttons (with different or repeating names), anchors etc.
Going all in, you could add the eventlistener to document instead of the list element, catching all click events on the page and then handle the click events in the event handler.

jQuery events don't work when html is injected

I have something like this:
function SetTableBehavior() {
$(".displayData tr").hover(function(e) {
$(this).children().addClass('displayDataMouseOver');
}, function () {
$(this).children().removeClass('displayDataMouseOver');
});
$(".displayData tr td").click(function(e) {
var rowsSel = $(".displayData .displayDataRowSelected");
for (var i = 0; i < rowsSel.length; i++) {
var rowSel = rowsSel[i];
$(rowSel).children().removeClass("displayDataRowSelected");
}
$(this).parent().addClass('displayDataRowSelected');
var p = $(this).parent();
p.children().addClass('displayDataRowSelected');
});
}
When the body of the table is injected neither hover or click work.
If i use
$(".displayData tr td").live('click',function(e) {
the click event works but
$(".displayData tr").live('hover',function(e) {
doesn't work
What is the solution so that hover works.
Thanks.
It seems to work like this:
function SetTableBehavior() {
$(".displayData tr").live('mouseenter', function (e) {
$(this).children().addClass('displayDataMouseOver');
}).live('mouseleave', function(e) {
$(this).children().removeClass('displayDataMouseOver');
});
$(".displayData tr td").live('click',function(e) {
var rowsSel = $(".displayData .displayDataRowSelected");
for (var i = 0; i < rowsSel.length; i++) {
var rowSel = rowsSel[i];
$(rowSel).children().removeClass("displayDataRowSelected");
}
$(this).parent().addClass('displayDataRowSelected');
var p = $(this).parent();
p.children().addClass('displayDataRowSelected');
});
}
$(".hoverme").live("mouseover mouseout", function(event) {
if ( event.type == "mouseover" ) {
// do something on mouseover
} else {
// do something on mouseout
}
});
From here: http://api.jquery.com/live/
There is no event called "hover" so you can't use it with live or bind. It is just a "short-cut" that jQuery implemented for us.
You cannot use hover with live. You'll have to split it up in 2 separate event listeners: one for mouseenter, and another one for mouseleave.
Additionally, in your situation, you don't need live. Use delegate, which is much better for performance:
$(".displayData").delegate('tr', 'mouseeneter',function(e) {
$(this).children().addClass('displayDataMouseOver');
})
.delegate('tr', 'mouseleave',function(e) {
$(this).children().removeClass('displayDataMouseOver');
});
hover(a, b) is a shortcut for mouseenter(a).mouseleave(b) (which themselves, are shortcuts for bind('mouseenter', a).bind('mouseleave', b)), so try:
$(".displayData tr").live('mouseenter', function(e) {
// mouseenter handler
}).live('mouseleave', function (e) {
// mouseleave handler
});
For more info, see the hover() and live() docs.

Categories