Simple Example : Setting attributes by iterating - javascript

$(function(){
var els = [];
var m = $("#container");
m.attr({"style" : "width:50%;"});
$(".grid").each(function(e){
els.push(this);
});
var n = els[3];
n.attr({"style" : "width:50%;"}) //error
});
Hello, I am fairly new to DOM manipulating. I am wondering why the JQuery above returns [Object][Object] for var m, but returns [Object][HTMLDivElement] for var n.
Because of this behavior I cannot use statements such as n.attr(args).
TypeError: n.attr is not a function
Ultimately, I would like to store each .grid element in an array, and iterate over them, setting attributes as I go.
To be more exact, I have a 6x3 grid of div elements, and each time the page loads, they are given a random animation-duration because they are animated to fade in and out of view.
Why can I not use n.attr()?

That's because this inside the .each() loop is the actual DOM element, not the jQuery object. If you want the items in your array to be jQuery objects you need to wrap them in one yourself:
els.push($(this));
Alternatively, you can just wrap the DOM element at the time you are accessing it:
var n = $(els[3]);
From jQuery .each():
More importantly, the callback is fired in the context of the current
DOM element, so the keyword this refers to the element.
and
To access a jQuery object instead of the regular DOM element, use
$( this )

A DOM element is not a jQuery object. n is an array of DOM elements, not jQuery objects which have a DOM element as a property. You can achieve the same result using DOM methods without jQuery
onload = function() {
var m = document.getElementById("container");
m.style.width = "50%";
var els = document.querySelectorAll(".grid");
var n = els[3];
n.style.width = "50%";
}

Related

Can't get next() after parent() in jquery

I'm trying to get the next element ('ul') after I traverse up and get a parent.
But I'm getting an error. "object doesn't support property or method next"
var p = $(e.target).parents()[1];
var c = p.next('ul');
Write
var c = $(p).next('ul');
re-blessing your element into a jQuery object. Btw do you really need to access the dom node instead of keeping it wrapped in the jquery object ? You can access the grandparent using parent().parent().
You probably need to just use .parent() if you're looking for the immediate parent. But if you really want the grandparent then use .eq(1) instead of [0], as this gives you a plain DOM element, without the benefit of jQuery methods.
//looking for the immediate parent?
var p = $(e.target).parent(); //or .parents().first()
var c = p.next('ul');
//looking for the grand parent?
var p = $(e.target).parents().eq(1);
var c = p.next('ul');

jQuery - text() method?

Being fairly new to jquery and javascript I can't seem to understand how the .text() method really works. I read through the jQuery documentation but still can't figure it out.
for (var i=0; i < arrayLength; i++){
var currentElement = $(".artist")[i];
var currentArtist = currentElement.text;
console.log(currentElement);
console.log(currentArtist);
}
currentArtist returns "undefined" in the console. It works fine on the $(".artist") alone, but not when I use the [i] or anything additional for that matter. What am I missing here? How else could I grab a text value inside a selector?
By using the [] operator on jQuery object you're accessing the raw element node that was found by jQuery. This raw element doesn't have the jQuery methods anymore, nor a text property.
If you want to get single element from jQuery object and keep the jQuery wrapper, use eq method.
var artistElement = $(".artist").eq(i);
artistElement.text(); // gets the text content of the element
The code you've posted is also not very optimized. For instance, with every loop iteration you're searching the document over and over again for elements with class artist. Better to cache that search result in a variable before performing the loop. And if the loop iterates over all .artist elements, you can use jQuery's each method.
$(".artist").each(function () {
var artist = $(this); // this poits to the raw element thus wrapping into jQuery object
console.log(artist.text());
});
var currentArtist = currentElement.text;
Should be:
var currentArtist = currentElement.text();
You should use a each():
$(".artist").each(function(i,val){
var currentArtist = $(val).text();
console.log(val);
console.log(currentArtist);
});
$(".artist") produce a jQuery object that could be like this:
[div, div, div, div, prevObject: jQuery.fn.jQuery.init, context: document, selector: ".artist"...]
So the result of $(".artist")[i] is a HTMLElement and do not have a text method, that's why you're getting undefined
Also text() is a function and may be followed with ()
But if you want to keep the for loop you can do
for (var i=0; i < arrayLength; i++){
var currentElement = $(".artist")[i];
var currentArtist = $(currentElement).text();
console.log(currentElement);
console.log(currentArtist);
}
.text() shows the text of an html element or set of html elements that would be visible to the user.

jQuery throws an error that element.find() is not a function

I have written a small JS to iterate through a set of matched elements and perform some task on each of them.
Here is the code:
var eachProduct = $(".item");
eachProduct.each(function(index, element){
var eachProductContent = element.find(".product-meta").clone();
});
When I console log element it outputs properly and the exact objects. Why should jquery throw this error?
because element is a dom element not a jQuery object
var eachProductContent = $(element).find(".product-meta").clone();
Inside the each() handler you will get the dom element reference as the second parameter, not a jQuery object reference. So if you want to access any jQuery methods on the element then you need to get the elements jQuery wrapper object.
You are calling .find() on a plain JS object, But that function belongs to Jquery object
var eachProductContent = $(element).find(".product-meta").clone();
You can convert it to a jquery object by wrapping it inside $(). And in order to avoid this kind of discrepancies you can simply use $(this) reference instead of using other.
Use $(this) for current Element
var eachProductContent = $(this).find(".product-meta").clone();
you should change "element" to "this":
var eachProduct = $(".item");
eachProduct.each(function(index, element){
var eachProductContent = $(this).find(".product-meta").clone();
});

map nodes to data

I'd like to create a correlation between data and DOM nodes. I tried to directly create a Object, with the nodes as properties, but it looks like only the string representations of the nodes are used.
To make the problem more concrete, let's say I have the below document, and I want to associate the first div with the number 3, and the second div with the list ['x', 'y', 'z'], how would you do this?
<html> <body>
<div/>
<div/>
( 100 more divs )
</body> </html>
I see that jQuery has a .data() method just for doing this. Is that the only way? This seems like such a fundamental operation that I had expected to do it with plain-old javascript.
The intent is to register an onclick event with these nodes, and have the data on hand.
window.onload = function() {
var index2data = { 0:3, 1:['x', 'y', 'z'] };
var divs = document.getElementsByTagName("div");
for(var i = 0; i < divs.length; i++){
setData( divs[i], index2data[i] )
divs[i].onclick = onClick;
}
}
function onClick() {
var data = getData( this );
// do a bunch more stuff
}
You can just set an attribute with the JSON encoded data against it. In the examples I've provided I'm adding the data using the boot up JavaScript, but there's no reason this shouldn't already be on in the markup delivered by your server.
Then the getData method just reads the attribute and JSON parses it:
function getData(el) {
return JSON.parse(el.getAttribute('data-stuff'));
}
Note that I've just called the data attribute 'data-stuff' - as data-* attribute a valid HTML5 - obviously name it more appropriately.
Working example without jQuery: http://jsbin.com/opugax/edit
Working example with jQuery: http://jsbin.com/opugax/2/edit
Note that if you don't want to use jQuery, and do want to support IE7 and below - you'll need to include json2.js in your page: https://github.com/douglascrockford/JSON-js
In order of keeping the actual data separate from the DOM you can try to set div IDs for all elements by script - in doing so you should test if any div already has an id and use that instead in order of not breaking things...
var obj={};
function setDivIdsAndData(dataToSet)
{
var divs = document.getElementsByTagName("div");
for(var i = 0; i < divs.length; i++)
{
var divId="d"+i;
if (divs[i].getAttribute("id")==null)
divs[i].setAttribute("id",divId);
else
divId=divs[i].getAttribute("id");
obj[divId]=dataToSet[i];
}
};
And now applied to your example case:
window.onload = function()
{
var index2data = { 0:3, 1:['x', 'y', 'z'] };
setDivIdsAndData(index2data);
};
function onClick()
{
var data = obj[this.getAttribute("id")];
// do a bunch more stuff
};
Technically, this is simple, but I'm not sure if it's the best way to accomplish your overall goal.
The DOM is extensible.
All that is needed to add data to an element is to assign it to the DOM object, either when the element is created or afterward. Here's an afterward example using the elements ID to reference it:
var divObj = document.getElementById('link1');
divObj.data = ['1', '2', '3'];
The data is not an HTML attribute of the element, but a node extension of the DOM which can hold any type of data including functions/methods.
Accessing and working with this data is as simple as assigning it above:
var divObj = document.getElementById('link1');
doSomething(divObj.data);
Your goal was to access it with an event handler, which depends on your method of assigning events. I use the more modern obj.addEventListener, and obj.attachEvent (MSIE). Getting the target object that was clicked is a bit different from the simple obj.onclick() assignment, but accessing the data is the same.
A collection of div nodes is an ordered list- source code order.
Use an array to hold your data and assign the same indexed div that data.
var dom= /*[parent]*/.getElementsByTagName('div'),
data= // reference the data array.
L= Math.min(data.length,dom.length),
next;
while(L){
next= data[--L];
if(next!= undefined) dom[L].setAttribute('data-association', next);

Most efficient way to create and nest divs with appendChild using *plain* javascript (no libraries)

Is there a more efficient way to write the following appendChild / nesting code?
var sasDom, sasDomHider;
var d = document;
var docBody = d.getElementsByTagName("body")[0];
var newNode = d.createElement('span');
var secondNode = d.createElement('span');
// Hider dom
newNode.setAttribute("id", "sasHider");
docBody.appendChild(newNode);
sasDomHider = d.getElementById("sasHider");
// Copyier dom
secondNode.setAttribute("id", "sasText");
sasDomHider.appendChild(secondNode);
sasDom = d.getElementById("sasText");
Ok, question has changed. Blah. Here's the new answer:
You might gain a little bit in the way of execution efficiency by building the branch before appending it to the DOM tree (browser won't try to recalc anything while building). And a bit in the way of maintenance efficiency by reducing the number of superfluous variables:
var d = document;
var docBody = d.getElementsByTagName("body")[0];
// Copyier dom
var sasDom = d.createElement('span');
sasDom.setAttribute("id", "sasText");
// Hider dom
var sasDomHider = d.createElement('span');
sasDomHider.setAttribute("id", "sasHider");
sasDomHider.appendChild(sasDom); // append child to parent
docBody.appendChild(sasDomHider); // ...and parent to DOM body element
Original answer:
You're trying to insert the same element twice, in the same spot...
var newNode = d.createElement('span');
...That's the only place you're creating an element in this code. So there's only one element created. And you insert it after the last child element in the body here:
docBody.appendChild(newNode);
So far, so good. But then, you modify an attribute, and try to insert the same node again, after the last child of sasDomHider... which is itself! Naturally, you cannot make a node its own child.
Really, you want to just create a new element and work with that:
newNode = d.createElement('span');
newNode.setAttribute("id", "sasText");
sasDomHider.appendChild(newNode);
// the next line is unnecessary; we already have an element reference in newNode
// sasDom = d.getElementById("sasText");
// ... so just use that:
sasDom = newNode;
You don't need to search again for the nodes:
var d = document;
var docBody = d.body;
var sasDomHider = d.createElement('span');
var sasDom = d.createElement('span');
// Hider dom
sasDomHider.setAttribute("id", "sasHider");
docBody.appendChild(sasDomHider);
// Copyier dom
sasDom.setAttribute("id", "sasText");
sasDomHider.appendChild(sasDom);
It's because newNode references an instance of a HtmlElement which you are attempting to insert into two different places within the DOM. You'll need to create a new element each time (or use cloneNode, but there are cross browser discrepancies with how that works).
Something like this should work
var sasDom,
d = document,
docBody = d.getElementsByTagName("body")[0],
sasDomHider = d.createElement('span');
// Hider dom
sasDomHider.setAttribute("id", "sasHider");
docBody.appendChild(sasDomHider);
// Copyier dom
sasDom = sasDomHider.cloneNode(true);
sasDom.setAttribute("id", "sasText");
sasDomHider.appendChild(sasDom);
// job done. sasDomHider and sasDom still reference the
// created elements.
There are a few ways to make this more efficient (in terms of performance and code size/readability), most of which have been covered already:
// Hider dom
var sasDomHider = document.createElement('span');
sasDomHider.id = "sasHider";
// Copier dom
var sasDom = document.createElement('span');
sasDom.id = "sasText";
sasDomHider.appendChild(sasDom);
document.body.appendChild(sasDomHider);
Obtains body using document.body
Uses only one variable each for the nodes you've created
Removes the getElementById lines, since they get you references to the same elements you had already
Uses the id property of the elements rather than setAttribute, which is an unnecessary function call and more verbose
Creates the whole branch being added to the document before adding it, thus avoiding unnecessary repaint/reflow
Removes d as an alias for document: there's no need to keep another reference to the document hanging around
Removes the docBody variable, since you're only using it once
Generally one set of the body's innerHTML with the desired HTML be the most efficient method.
e.g.
document.body.innerHTML = document.body.innerHTML + '<span id="foo"></span><span id="bar"></span>';
In your example, the objects referenced by newNode, sasDomHider, and sasDom are all the same, all pointing at a single DOM element. You're trying to put it in two places at once. You need to clone it, or simply make a new <span> for the second instance. Merely changing the id attribute is not enough (you're changing the id of the one already in the document).

Categories

Resources