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);
Related
I am adding table rows dynamically to a table using Javascript.
Is there a reason why items.length doesn't increase when I add a row?
I am also trying to sum the numbers contained in each row. But this doesn't work either. The dynamically added rows are completely ignored for some reason.
I am coming from jQuery where things like these used to work.
I am probably missing something really fundamental here. Thanks for any pointers.
document.addEventListener("DOMContentLoaded", function() {
var form = document.querySelector("#form");
var items = form.querySelectorAll(".item");
form.addEventListener("click", function(event) {
if (event.target.className == ".add_item") {
addFields(event);
}
});
function addFields(event) {
var item = document.createElement("template");
item.innerHTML = fields.trim();
items[items.length - 1].insertAdjacentElement("afterend", item.content.firstChild);
console.log(items.length);
event.preventDefault();
}
})
querySelector and querySelectorAll returns NodeList where as getElementsByClassName returns HTMLCollection.
The difference is, NodeList is a static copy but HTMLCollection is a live copy. So if element is modified, like in your case, a new row is added, HTMLCollection will work but NodeList will not.
So changing
var items = form.querySelectorAll(".item")
to
var items = form.getElementsByClassName("item")
might solve the problem.
Pointers
You cannot have a selector in getElementsByClassName. It expects a className, you cannot use composite selector like #form .item.
Reference:
Difference between HTMLCollection, NodeLists, and arrays of objects
You only query the items once here:
var items = form.querySelectorAll(".item");
form.addEventListener(
//you call addItem here
)
function addItem(){
//you access items.length here
}
You need to keep re-query-ing the selectors every time you add an item.
var items = form.querySelectorAll(".item"); //i got queried for the 1st time
function addFields(event) {
items = form.querySelectorAll(".item"); //get items again
//...
}
Or just don't query outside addFields() all in all. Put var items = ... in the function. There's no need to put it outside.
Read up #Rajesh's answer why this is so.
I need to pass some html code as a parameter, however, before I pass it, I need to change some src attribute values.
I cannot use lastIndexOf or any of those to modify the html value since I don't know which value the src's will have.
What I'm trying to do then, is to create an object containing the html, and then alter that object only. I don't want to alter the actual webpage.
is this possible??
What I did first was this:
$('[myImages]').each(function() {
var urlImg = "../tmpFiles/fileName" + counter;
$(this).attr('src', urlImg);
counter++;
});
So finally, I had the desired code like this:
myformData = { theChartCode: $('#TheDivContainingTheHTML').html() }
However, this actually changes the image sources on the webpage, which I don't want to.
Then I thought I could create a JQuery object with the html so I could alter that object only like this:
var $jQueryObject = $($.parseHTML($('#TheDivContainingTheHTML').html()));
But now, I can't figure out how to iterate within that object in order to change the src attribute's values of the desired images.
Any help will be really appreciated ;)
There are several ways to do It. First would be creating a clone of target element and use the same on the Fly. You can do like below:
var Elem = $('#TheDivContainingTheHTML').clone();
now do whatever you want like iterate, alter,insert,remove.
var allImages =$(Elem).children("img");
Thanks Much!
Depending on when you want to change the object, solution will be different. Let's pretend you want to change it after you click another element in the page. Your code will look like that :
var clonedHTML;
$('#clickable-element').click(function() {
var $originalHTML = $(this).find('.html-block');
var $cloneHTML = $originalHTML.clone();
$cloneHTML.find('.my-image').attr('src', 'newSrcValue');
clonedHTML = $cloneHTML.clone();
return false; //Prevents click to be propagated
});
//Now you can use `clonedHTML`
The key point here is the clone method : http://api.jquery.com/clone/.
You can clone the elements:
var outerHTML = $collection.clone().attr('src', function(index) {
return "../tmpFiles/fileName" + index;
}).wrapAll('<div/>').parent().html();
You can also use the map method:
var arr = $collection.map(function(i) {
return $(this).clone().attr('src', '...').prop('outerHTML');
}).get();
I have all my elements, on a project, that are being transferred with data attributes so the javascript can know what to do.
But I have one problem, one part of the application must get all the created variables to compare time, and I have they saved like (data-product-created, data-category-created, data-link-created, etc...). It would be a huge headache if I had to put them manually on the jQuery selector...
Do jQuery has some method to search custom data attributes existence?
Like: element[data-(.*)-created]
You could create a low level Javascript method to loop through elements and pull their created values:
var getCreateds = function($els) {
var returnSet = [];
$els.each(function(i, el){
var elDict = {'el': el};
$.each(el.attributes, function(i, attr){
var m = attr.name.match(/data\-(.*?)\-created/);
if (m) elDict[m[1]] = attr.value;
});
returnSet.push(elDict);
});
return returnSet;
};
See demo
Based on mVChr answer (THANK YOU), i've rewritten the code on a simple, better and clean code:
jQuery('div').filter(function(){
for( var i in this.attributes ){
if(typeof this.attributes[i] !== 'object') continue;
if(this.attributes[i].name.match(/data\-(.*?)\-created/)) return true;
}
return false;
})
Changes:
+ Verifying attribute type... some attributes are browser internal functions or callbacks.
~ Using filter handler from jQuery, its better and cleaner way.
~ Using for instead of $.each, faster indeed.
I have a form that I want to only submit post data for value which have changed.
So the way I have been doing this is like this:
function submit_form(){
var hd = [];
// hd is a big array that is defined here
// hd ['some id_number'] = 'some value'
// main function
for (var id_number in hd ){
var x=document.getElementById(id_number).selectedIndex;
var y=document.getElementById(id_number).options;
selector_text = y[x].text;
if (hd[id_number] == selector_text){
$(id_number).remove();
}
}
document.forms["my_form"].submit()
}
So the goal is that if the selector equals what is in the array, then don't POST the data.
To do this I have been doing the remove function. Everything up to the remove function works as expected. However when I look at the post data I still get the selected value for the id_numbers that mach the value in hd.
Is there a better way to remove to prevent it from going to the POST data? The id.parent.removeChild(id) method didn't work either.
The jQuery id selector should begin with a #, but yours appears not to:
$('#' + id_number).remove();
Your for-in loop should be a regular incremental for loop, which is the proper way to iterate an array in JavaScript. for-in loops are typically used for iterating object properties rather than array elements.
for (var i=0; i<hd.length; i++) {
// Access hd[i] in the loop
var x=document.getElementById(hd[i]).selectedIndex;
var y=document.getElementById(hd[i]).options;
selector_text = y[x].text;
if (hd[i] == selector_text){
$('#' + hd[i]).remove();
}
}
Since you aren't really using jQuery here except for that line, instead the plain JS version is:
var removeMe = document.getElementById(hd[i]);
removeMe.parentNode.removeChild(removeMe);
A simple question I'm sure, but I can't figure it out.
I have some JSON returned from the server
while ($Row = mysql_fetch_array($params))
{
$jsondata[]= array('adc'=>$Row["adc"],
'adSNU'=>$Row["adSNU"],
'adname'=>$Row["adname"],
'adcl'=>$Row["adcl"],
'adt'=>$Row["adt"]);
};
echo json_encode(array("Ships" => $jsondata));
...which I use on the client side in an ajax call. It should be noted that the JSON is parsed into a globally declared object so to be available later, and that I've assumed that you know that I formated the ajax call properly...
if (ajaxRequest.readyState==4 && ajaxRequest.status==200 || ajaxRequest.status==0)
{
WShipsObject = JSON.parse(ajaxRequest.responseText);
var eeWShips = document.getElementById("eeWShipsContainer");
for (i=0;i<WShipsObject.Ships.length;i++)
{
newElement = WShipsObject.Ships;
newWShip = document.createElement("div");
newWShip.id = newElement[i].adSNU;
newWShip.class = newElement[i].adc;
eeWShips.appendChild(newWShip);
} // end for
}// If
You can see for example here that I've created HTML DIV elements inside a parent div with each new div having an id and a class. You will note also that I haven't used all the data returned in the object...
I use JQuery to handle the click on the object, and here is my problem, what I want to use is the id from the element to return another value, say for example adt value from the JSON at the same index. The trouble is that at the click event I no longer know the index because it is way after the element was created. ie I'm no longer in the forloop.
So how do I do this?
Here's what I tried, but I think I'm up the wrong tree... the .inArray() returns minus 1 in both test cases. Remember the object is globally available...
$(".wShip").click(function(){
var test1 = $.inArray(this.id, newElement.test);
var test2 = $.inArray(this.id, WShipsObject);
//alert(test1+"\n"+test2+"\n"+this.id);
});
For one you can simply use the ID attribute of the DIV to store a unique string, in your case it could be the index.
We do similar things in Google Closure / Javascript and if you wire up the event in the loop that you are creating the DIV in you can pass in a reference to the "current" object.
The later is the better / cleaner solution.
$(".wShip").click(function(){
var id = $(this).id;
var result;
WShipsObject.Ships.each(function(data) {
if(data.adSNU == id) {
result = data;
}
});
console.log(result);
}
I could not find a way of finding the index as asked, but I created a variation on the answer by Devraj.
In the solution I created a custom attribute called key into which I stored the index.
newWShip.key = i;
Later when I need the index back again I can use this.key inside the JQuery .click()method:
var key = this.key;
var adt = WShipsObject.Ships[key].adt;
You could argue that in fact I could store all the data into custom attributes, but I would argue that that would be unnecessary duplication of memory.