Set attribute on multiple selectors - javascript

I'm trying the following, which doesn't work, but am wondering if there is something similar?
Basically, I'm trying to set the same attribute on multiple elements:
document.querySelectorAll("#id1, #id2, #id3").setAttribute('onclick','return false;');
I'm using vanilla Javascript, no library.

querySelectorAll() will return an array-like node list that you need to loop through:
var elems = document.querySelectorAll("#id1, #id2, #id3");
for(var i = 0; i < elems.length; i ++){
elems[i].setAttribute('onclick', 'return false');
}
JSFiddle

There are several approaches for this, the most basic one is to iterate the set and add click handlers (In your case, you want an addEventHandler not an attribute!)
var elements = document.querySelectorAll("#id1, #id2, #id3");
[].forEach.call(elements, function addClickHandler(el) {
el.addEventListener('click', function() { return false; });
};
Perhaps a better approach if you have many elements is to set one event handler on the document, and see if it matches a query selector:
var elements = [].slice.call(document.querySelectorAll("#id1, #id2, #id3")); //Turn into an array
document.addEventHandler('click', function(e) {
if (elements.indexOf(e.target) !== -1) { //Element in list was clicked!
return false;
}
};

Related

how to remove jquery filter from function

I have a function that I want to remove jQuery from:
var hunt = new RegExp(action.search);
$('a').filter(function() {
return hunt.test(this.href);
}).click(doStuff);
I am trying to rewrite this function using vanilla javascript. this is what I have so far but it is not working
var hunt = new RegExp(action.search);
var aSelector = document.getElementsByTagName('a');
var arr = Array.prototype.filter.call(aSelector, function(el){
return hunt.test(el.href)
});
for(var i=0; i<arr.length; i++){
var elem = arr[i].getAttribute('href');
elem.onclick = function(){
doStuff();
};
}
I am guessing that the jQuery selector behavior is slightly different than document.getElementsByTagName but I really have no idea what is going on. Any help would be greatly appreciated. Thanks
better use querySelectorAll() instead of getElementsByTagName(), besides that, I think the code is self-explaining. Ask if something is unclear
//a very basic utility to fetch Nodes by css-selectors and return them as an Array
//- shorthand for document.querySelectorAll()
//- document.querySelectorAll() returns a NodeList wich lacks the Array-methods
//- second argument to provide a Node as the context of the query
function $$(selector, context){
return Array.from((typeof context === "object" && context || document).querySelectorAll(selector));
}
//now we can use the regular Array-methods
$$('a').filter(function(node){
return hunt.test(node.href);
}).forEach(function(node){
node.addEventListener("click", doStuff);
})
or this way:
$$('a').forEach(function(node){
if(hunt.test(node.href)){
node.addEventListener("click", doStuff);
}
});

Cleaning up memory leaks

I am using jQuery to clone elements, then I save a reference to an element within that clone. And much later remove the clone. Here is a basic example:
HTML
<div> <span></span> </div>
Script
var i, $clone, $span,
$saved = $('span'),
$orig = $('div');
for (i = 0; i < 100; i++) {
$clone = $orig.clone().appendTo('body');
$span = $clone.find('span');
$saved = $saved.add($span);
$clone.remove();
}
console.log( 'leaking = ', $saved.length);
The console log outputs a length of 101.
I need to clean up the $saved jQuery object and remove references to elements no longer attached to the DOM. So I wrote this basic function to clean it all up.
var cleanUpLeaks = function ($el) {
var el, remove,
index = $el.length - 1;
while (index >= 0) {
el = $el[index];
remove = true;
while (el) {
el = el.parentNode;
if (el && el.nodeName === 'HTML') {
remove = false;
break;
}
}
if (remove) {
$el.splice(index, 1);
}
index--;
}
return $el;
};
console.log( 'cleaned up = ', cleanUpLeaks( $saved ).length );
This time the console outputs 1.
So now my questions are:
How could I have prevented the memory leak in the first place?
And if that isn't possible, should I be using .splice() in the cleanUpLeaks function to remove the reference? Or would it be better to set that reference to null as is recommended? Because when I do set it to null, $saved remains at a length of 101.
Demo: http://jsfiddle.net/Mottie/6q2hjazg/
To elaborate, I save a reference to the span in $saved. There are other functions that use this value for styling and such. This is a very basic example; and no, I do not immediately remove the clone after appending it to the body, it was done here to show how the memory leak is occurring.
The better solution here is to stop saving dynamic DOM elements in a persistent jQuery variable. If your page is regularly removing content from the DOM, then saving these in a persistent jQuery object just sets you up for having to deal with memory leaks, rather than changing the design to a design that does not have to save references to DOM elements at all.
If instead, you just tag interesting elements with a particular class name that is not used elsewhere in the document, you can generate the desired list of elements at any time with a simple jQuery selector query and you will have no issues at all with leaks because you aren't ever retaining DOM references in persistent variables.
One possible solution is that you take a leaf out of AngularJS's book and monkey-patch jQuery to fire an event when an element is removed. Then you can add a handler for that event and restore the state of $saved to what it was before you added the $span.
First, monkey patch jQuery (taken from AngularJS source):
// All nodes removed from the DOM via various jQuery APIs like .remove()
// are passed through jQuery.cleanData. Monkey-patch this method to fire
// the $destroy event on all removed nodes.
var originalCleanData = jQuery.cleanData;
var skipDestroyOnNextJQueryCleanData;
jQuery.cleanData = function (elems) {
var events;
if (!skipDestroyOnNextJQueryCleanData) {
for (var i = 0, elem;
(elem = elems[i]) != null; i++) {
events = jQuery._data(elem, "events");
if (events && events.$destroy) {
jQuery(elem).triggerHandler('$destroy');
}
}
} else {
skipDestroyOnNextJQueryCleanData = false;
}
originalCleanData(elems);
};
Next, add in your $destroy event handler and restore the captured original state of $saved.
var i, $clone, $span,
$saved = $('span'),
$orig = $('div');
for (i = 0; i < 100; i++) {
(function ($originalSaved) {
$clone = $orig.clone().appendTo('body');
$span = $clone.find('span');
$clone.on('$destroy', function () {
$saved = $originalSaved;
$originalSaved = null;
});
$saved = $saved.add($span);
$clone.remove();
})($saved);
}
console.log('original length = ', $saved.length); // => 1
Here is a jsFiddle with this working. In my testing in Chrome, this doesn't introduce additional leaks.

jquery bind dblclick event to multiple elements

I am attempting to bind a dblclick event to divs in a nodeList which I am iterating through.
Here is the code:
var elems = document.getElementsByClassName("click");
currentLocation = elems[0].id;
for (var i=0; i<elems.length; i++){
$(elems[i]).dblclick(function() {
if((elems[i].id) != currentLocation){
badAnswer = true;
alert(badAnswer);
}
});
}
currentLocation is a global variable set to the first element id of the node list. badAnswer is also a global boolean set to false. If a element is double clicked that matches an element other than the currentLocation global, badAnswer is set to true.
Currently I receive a undefined error, which I tried to remedy by creating a local variable inside of the event handler. This didn't seem to work either and badAnswer is always true on double click as the elementID is always equal to the ID value of the last element.
Is there a better way to do this?
Yes, there is a much better way:
var currentLocation = $(".click")[0].id;
$(".click").on("dblclick", function() {
if (this.id != currentLocation) {
badAnswer = true;
}
});
$('.click').dblClick(function() {
if(this.id != currentLocation) {
...
}
});
You're already using jQuery so you might as well use the selectors...much easier!

Manipulating a jquery collection

I have a JQuery collection created with:
var children = $('span[data-Parent="7"]');
Now I would like to manipulate the css class of the items in that collection. I've tried:
for (var i = 0; i < children.length; i++) {
var $child = children[i];
if ($child.hasClass("red")) {
$child.addClass("green");
}
But get errors stating that the object doesn't have a method hasClass. What is the proper way to get the elements of the collection to be document elements so they can be manipulated?
You must wrap the DOM element as a jQuery object. And iterating over elements using jQuery is usually done with the provided each function :
$('span[data-Parent="7"]').each(function(){
if ($(this).hasClass('red')) {
$(this).addClass('green');
}
});
Note that the whole code can be reduced to
$('span[data-Parent="7"].red').addClass('green');
I'd say use $.each:
$.each(children, function(i, child) {
if ($(child).hasClass("red")) {
$(child).addClass("green");
}
});
Just cycle through each of the children and run the check in the .each loop.
var children = $('span[data-Parent="7"]');
children.each(function(index)
{
var self = $(this);
if (self.hasClass("red"))
{
self.addClass("green");
}
});
Change your code to:
var $child = $(children[i]);
//the rest of the code

Transfer HTML elements from one list to another with JavaScript

I have a problem with the following JavaScript function. I have two UL and I need that when the user clicks on a LI, this element transfers to the other UL.
I've managed to move them onClick from on list to the other, the problem appears when I try to move again a LI that was previously in the other UL, when that happens it just doesn't work...
function testList() {
usersA = document.getElementById("users-a");
usersB = document.getElementById("users-b");
for (var i=0; i < usersA.getElementsByTagName("li").length; i++) {
usersA.getElementsByTagName("li")[i].onclick = function() {
transfer = this.cloneNode(true);
usersB.appendChild(transfer);
usersA.removeChild(this);
return false;
}
}
for (var i=0; i < usersB.getElementsByTagName("li").length; i++) {
usersB.getElementsByTagName("li")[i].onclick = function() {
transfer = this.cloneNode(true);
usersA.appendChild(transfer);
usersB.removeChild(this);
return false;
}
}
}
I know that my logic sucks but it's all I could come up with. Any ideas why it works the first time I transfer a LI but when I try to move back to its original UL it doesn't work?
You're not "moving" elements, you're creating a copy and deleting the original. Although this seems like a "move" from the user's point of view the new elements that you create do not have click handlers assigned. From MDN: "Cloning a node copies all of its attributes and their values but does not copy event listeners."
According to MDN, .appendChild() will remove the child from its current parent so you shouldn't need the two-step clone/remove that you are currently doing. I haven't tested it, but perhaps using just .appendChild() it will keep the handlers? If so you need to remove that handler and assign a new one to allow for which list it now belongs to.
Or, rewrite your handlers so that they check which list is the current parent (have a look at .parentNode) and move to the other list as appropriate.
Bearing in mind that click events "bubble up" starting from the target/source element and up through the parent heirarchy you're probably better off setting your click handlers on the parent <ul> elements and then testing which <li> was clicked. That way you don't have to worry about setting new click handlers on new child <li> elements.
function testList() {
var usersA = document.getElementById("users-a"),
usersB = document.getElementById("users-b");
usersA.onclick = function(e) {
// allow for the IE and non-IE way of handling the event object
if (!e) e = window.event;
var el = e.target || e.srcElement;
if (el.tagName === "li") {
usersB.appendChild(el);
}
}
usersB.onclick = function(e) {
// allow for the IE and non-IE way of handling the event object
if (!e) e = window.event;
var el = e.target || e.srcElement;
if (el.tagName === "li") {
usersA.appendChild(el);
}
}
}
Also, if you're using .getElementsByTagName() call it once in the loop initialisation and assign the result to a variable, then use the variable - don't keep calling the function again and again to test the length, or to access individual elements inside your loop:
for (var i=0, lis = usersA.getElementsByTagName("li"); i < lis.length; i++) {
lis[i].onclick = function() {
// etc
}
}
The problem is that even after you've moved an element from list A to list B, it still keeps its old onclick handler, which still says "remove me from list A and add me to list B". You need to change its onclick handler to say "remove me from list B and add me to list A". Here's one way to fix that:
var usersA = document.getElementById("users-a");
var usersB = document.getElementById("users-b");
var onclickA;
var onclickB = function() {
usersA.appendChild(this);
this.onclick = onclickA;
return false;
};
onclickA = function() {
usersB.appendChild(this);
this.onclick = onclickB;
return false;
};
for (var i=0; i < usersA.getElementsByTagName("li").length; i++)
usersA.getElementsByTagName("li")[i].onclick = onclickA;
for (var i=0; i < usersB.getElementsByTagName("li").length; i++)
usersB.getElementsByTagName("li")[i].onclick = onclickB;
(I also got rid of the cloneNode stuff, for exactly the reasons that nnnnnn gives.)

Categories

Resources