I want to change 'hello' to 'hey' programmatically, the solution should work with any number of nested elements (I just use 2 levels to keep it simple).
var data = {level1: {level2 : 'hello' }};
I have access to the 'data' variable, the path ('level1/level2') and the new value ('hey').
I tried to do:
var parents = 'level1/level2'.split('/');
var target = data;
for(var i=0; i<parents.length; i++){
target = data[parents[i]];
}
target = 'hey';
The idea was to travel to the root
target = data
then 1 level deep
target = data['level1']
...keep going
target = data['level1']['level2'] //data['level1'] === target
and modify the contents
target = 'hey'
But it looks like a lose the reference to the original object (data) when I do (target = target['level2']).
I guess I can build a string with the path and then evaluate it:
eval("data['level1']['level2']='hey');
Is there a better solution that dosen't involve eval()?
There are two issues. First is that you keep using data inside the loop, which means you're trying to access the top level keys instead of the inner keys. Change target = data[parents[i]]; to
target = target[parents[i]];
The second is that when you change the variable target, you're not changing the data variable but target instead. If you drop out of the loop one iteration earlier you can update the object which is stored as a reference:
for(var i=0; i<parents.length-1; i++){
target = target[parents[i]];
}
target[parents[i]] = 'hey';
Demo: http://jsfiddle.net/Lherp/
Try something like this:
var data = {level1: {level2 : 'hello' }};
var parents = 'level1/level2'.split('/');
var target = data;
for(var i=0; i < parents.length - 1; i++){
target = target[parents[i]];
}
target[parents[i]] = 'hey';
Or am I missing something?
edit: I was missing something (sorry, should have tested it first..)
Related
I've been having a hard time with cross browser compatibility and scrapping the dom.
I've added data analytics tracking to ecommerce transactions in order to grab the product and transaction amount for each purchase.
Initially I was using document.querySelectorAll('#someId')[0].textContent to get the product name and that was working fine for every browser except internet explorer.
It took some time to figure out that it was the .textContent part that was causing ie problems.
Yesterday I changed .textContent to .innerText. From looking inside analytics it seems that the issue has been resolved for ie but now Firefox is failing.
I was hoping to find a solution without writing an if statement to check for the functionality of .textContent or .innerText.
Is there a cross browser solution .getTheText?
If not what would be the best way around this? Is there a simple solution? (I ask given my knowledge and experience with scripting, which is limited)
** added following comments **
If this is my code block:
// build products object
var prods = [];
var brand = document.querySelectorAll('.txtStayRoomLocation');
var name = document.querySelectorAll('.txtStayRoomDescription');
var price = document.querySelectorAll('.txtStayRoomSplashPriceAmount');
for(var i = 0; i < brand.length; i++) {
//set granular vars
var prd = {};
//add to prd object
prd.brand = brand[i].innerText;
prd.name = name[i].innerText;
prd.price = price[i].innerText;
prd.quantity = window.session_context_vars.BookingContext.Booking.ReservationLineItems[i].ReservationCharges.length/2;;
//add to prods array
prods.push(prd);
}
Then if I understand the syntax from the comments and the question linked to in the comment, is this what I should do:
// build products object
var prods = [];
var brand = document.querySelectorAll('.txtStayRoomLocation');
var name = document.querySelectorAll('.txtStayRoomDescription');
var price = document.querySelectorAll('.txtStayRoomSplashPriceAmount');
for(var i = 0; i < brand.length; i++) {
//set granular vars
var prd = {};
//add to prd object
prd.brand = brand[i].textContent || brand[i].innerText;
prd.name = name[i].textContent || name[i].innerText;
prd.price = price[i].textContent || price[i].innerText;
prd.quantity = window.session_context_vars.BookingContext.Booking.ReservationLineItems[i].ReservationCharges.length/2;;
//add to prods array
prods.push(prd);
}
So using or with a double bar || assigns the first non null value?
Re: your edit, not quite. The way to access methods or properties on an object (eg a DOM element) is to use dot notation if you have the name itself, or square brackets in case of variables/expressions (also works with strings, as in obj["propName"], which is equivalent to obj.propName). You can also just test the property against one element and use that from there on:
// build products object
var prods = [];
var brand = document.querySelectorAll('.txtStayRoomLocation');
var name = document.querySelectorAll('.txtStayRoomDescription');
var price = document.querySelectorAll('.txtStayRoomSplashPriceAmount');
for(var i = 0; i < brand.length; i++) {
//set granular vars
var prd = {};
//add to prd object
var txtProp = ("innerText" in brand[i]) ? "innerText" : "textContent"; //added string quotes as per comments
prd.brand = brand[i][txtProp];
prd.name = name[i][txtProp];
prd.price = price[i][txtProp];
prd.quantity = window.session_context_vars.BookingContext.Booking.ReservationLineItems[i].ReservationCharges.length/2;;
//add to prods array
prods.push(prd);
}
Regarding the line:
var txtProp = (innerText in brand[i]) ? innerText : textContent;
The in keyword checks an object to access the property (syntax: var property in object). As for the question notation (I made an error earlier, using ||, the correct thing to use was a :),
var myVar = (prop in object) ? object[prop] : false;
As an expression, it basically evaluates the stuff before the ?, and if it's true, returns the expression before the :, else the one after. So the above is the same as / a shorthand for:
if(prop in object){
var myVar = object[prop];
}
else{
var myVar = false;
}
Since you are checking between two properties only and wanting to assign one or the other, the shortest way would indeed be:
var txtProp = brand[i].innerText || brand[i].textContent;
It would basically test the first property, and if it were false or undefined, it would use the second one. The only reason I (pedantically) avoid using this is because the first test of a || b would fail even if a existed but just had a value of 0, or an empty string (""), or was set to null.
Firstly, I'm trying to create multiple DOM nodes and cache them as a variable for use in a function. What I want to do is create a function that sets-up the elements by classname. Then call that function as variable for use later.
Secondly, I'm not sure what the correct syntax is when manipulating inserted nodes via classname, when you want to select all classes with that name.
i.e for (var i = 0; i < insertedNodes.length; i++) {
To clarify what exactly I'm asking, my questions are this:
How to insert nodes as variables for use later on in function.
How to call those each of those variables.
How to call both of those variables together.
Hopefully my code will help explain what I'm trying to understand a little further:
var div1 = document.querySelector('.div1');
var div2 = document.querySelector('.div2');
var node1 = {};
var node2 = {};
var bothNodes = {};
function nodes() {
function insertNodes() {
node1 = div1.appendChild(nodeBase);
node2 = div2.appendChild(nodeBase);
bothNodes = [node1, node2];
}
function nodeBase() {
var node = document.createElement('div');
node.className = 'newNode';
}
function dosomething(node1, node2) {
//
}
function dosomethingElse(bothNodes) {
//
}
}
new nodes();
You don't return node here..
function nodeBase() {
var node = document.createElement('div');
node.className = 'newNode';
// ADD THIS LINE
return node;
}
And, as pointed out by Felix in the comment:
function insertNodes() {
node1 = div1.appendChild(nodeBase()); // Fixed
node2 = div2.appendChild(nodeBase()); // Fixed
bothNodes = [node1, node2];
}
This might get drowned with down votes, but pointing you to another direction.
How to insert nodes as variables for use later on in function.
you don't actually have to store those elements. You can always query them later.
How to call each of those variables.
You can select the first matching element with a particular class as follows:
var div1 = document.getElementsByClassName('.div1')[0];
OR
var div1 = document.querySelector('.div1');
How to call both of those variables together.
You can select all elements with a particular class, iterate over the collection and apply your logic as follows:
var nodes = document.getElementsByClassName('.div1');
OR
var nodes = document.querySelectorAll('.div1');
for(var i=0;i<nodes.length;i++){
// your loic
}
https://stackoverflow.com/a/1234337/1690081
shows that array.length = 0;will empty array but in my code it doesn't
here's an sample:
window.onload = draw;
window.onload = getFiles;
var namelist = [];
function draw(){
// assing our canvas to element to a variable
var canvas = document.getElementById("canvas1");
// create html5 context object to enable draw methods
var ctx = canvas.getContext('2d');
var x = 10; // picture start cordinate
var y = 10; // -------||---------
var buffer = 10; // space between pictures
for (i=0; i<namelist.length; i++){
console.log(namelist[i])
var image = document.createElement('img');
image.src = namelist[i];
canvas.appendChild(image);
ctx.drawImage(image,x,y,50,50);
x+=50+buffer;
}
}
function getFiles(){
namelist.length = 0;// empty name list
var picturesFiles = document.getElementById('pictures')
picturesFiles.addEventListener('change', function(event){
var files = picturesFiles.files;
for (i=0; i< files.length; i++){
namelist.push(files[i].name);
console.log(namelist)
}
draw();
}, false);
}
after i call getFiles() second time. It doesn't remove the previous list, just appends to it. any idea why?
You should empty the array in the event handler, not getFiles which is only called once per pageload. It is actually doing nothing because the array is already empty when the page loads.
picturesFiles.addEventListener('change', function(event){
namelist.length = 0; //empty it here
var files = picturesFiles.files;
for (i=0; i< files.length; i++){
namelist.push(files[i].name);
console.log(namelist)
}
draw();
}, false);
Another problem is that you cannot just set .src to the name of a file. That would make the request to your server for the file.
To really fix this, just push the file objects to the namelist:
namelist.push(files[i]);
Then as you process them in draw, create localized BLOB urls to show them:
var file = namelist[i];
var url = (window.URL || window.webkitURL).createObjectURL( file );
image.src = url;
It looks like you're using namelist as a global variable. This would be easier (and would avoid needing to empty it at all) if you passed the new array out of the function as a return value.
ie:
function getFiles() {
var newNameList = [];
..... //push entries here.
return newNameList;
}
... and then populate namelist from the return value where you call it:
namelist = getFiles();
However, to answer the question that was actually asked:
Instead of setting the length to zero, you can also reset an array simply by setting it to a new array:
namelist = [];
You haven't shown us how you're 'pushing' entries to the list, but I suspect that the end result is that namelist is being generated as a generic object rather than an array object. If this is the case, then setting .length=0 will simply add a property to the object named length with a value of 0. The length property in the way you're using it only applies to Array objects.
Hope that helps.
If you are using non-numeric indexes then the array will not clear.
"...whenever the length property is changed, every property whose name is an array index whose value is not smaller than the new length is automatically deleted"
Test:
var arr = [];
arr['this'] = 'that';
arr.length = 0;
console.log(arr);
//output ['this':'that']
var arr = [];
arr[0] = 'that';
arr.length = 0;
console.log(arr);
//output []
There is nothing wrong with how you empty the array, so there has to be something else that is wrong with your code.
This works fine, the array doesn't contain the previous items the second time:
var namelist = [];
function draw() {
alert(namelist.join(', '));
}
function getFiles() {
namelist.length = 0; // empty name list
namelist.push('asdf');
namelist.push('qwerty');
namelist.push('123');
draw();
}
getFiles();
getFiles();
Demo: http://jsfiddle.net/Guffa/76RuX/
Edit:
Seeing your actual code, the problem comes from the use of a callback method to populate the array. Every time that you call the function, you will add another event handler, so after you have called the function the seccond time, it will call two event handlers that will each add all the items to the array.
Only add the event handler once.
I need a way to add an object into another object. Normally this is quite simple with just
obj[property] = {'name': bob, 'height': tall}
however the object in question is nested so the following would be required:
obj[prop1][prop2] = {'name': bob, 'height': tall}
The clincher though, is that the nesting is variable. That is that I don't know how deeply each new object will be nested before runtime.
Basically I will be generating a string that represents an object path like
"object.secondObj.thirdObj.fourthObj"
and then I need to set data inside the fourth object, but I can't use the bracket [] method because I don't know how many brackets are required beforehand. Is there a way to do this?
I am using jQuery as well, if that's necessary.
Sure, you can either use recursion, or simple iteration. I like recursion better. The following examples are meant to be proof-of-concept, and probably shouldn't be used in production.
var setDeepValue = function(obj, path, value) {
if (path.indexOf('.') === -1) {
obj[path] = value;
return;
}
var dotIndex = path.indexOf('.');
obj = obj[path.substr(0, dotIndex)];
return setDeepValue(obj, path.substr(dotIndex + 1), value);
};
But recursion isn't necessary, because in JavaScript you can just change references.
var objPath = 'secondObj.thirdobj.fourthObj';
var valueToAdd = 'woot';
var topLevelObj = {};
var attributes = objPath.split('.');
var curObj = topLevelObj;
for (var i = 0; i < attributes.length; i++) {
var attr = attributes[i];
if (typeof curObj[attr] === 'undefined') {
curObj[attr] = {};
}
curObj = curObj[attr];
if (i === (attributes.length - 1)) {
// We're at the end - set the value!
curObj['awesomeAttribute'] = valueToAdd;
}
}
Instead of generating a string...
var o="object";
//code
o+=".secondObj";
//code
o+=".thirdObj";
//code
o+=".fourthObj";
...you could do
var o=object;
//code
o=o.secondObj;
//code
o=o.thirdObj;
//code
o=o.fourthObj;
Then you can add data like this:
o.myprop='myvalue';
And object will be updated with the changes.
See it here: http://jsfiddle.net/rFuyG/
I've a function that takes an object as a parameter, and uses the structure of the object to create nested DOM nodes, but I receive the following error:
http://new.app/:75NOT_FOUND_ERR: DOM Exception 8: An attempt was made to reference a Node in a context where it does not exist.
What I would like my function to do, is, when supplied with a suitable object as a parameter, example:
var nodes = {
tweet: {
children: {
screen_name: {
tag: "h2"
},
text: {
tag: "p"
}
},
tag: "article"
}
};
It would create the following DOM nodes:
<article>
<h2></h2>
<p></p>
</article>
Here is my attempt so far:
function create(obj) {
for(i in obj){
var tmp = document.createElement(obj[i].tag);
if(obj[i].children) {
tmp.appendChild(create(obj[i].children)); /* error */
};
document.getElementById("tweets").appendChild(tmp);
};
};
I'm already struggling!
Ideally I'd like to eventually add more child key's to each object, not just tag, but also id, innerHTML, class etc.
Any hel would be much appreciated, though please: I'm sure a framework or library could do this for me in just a few lines of code, or something similar, but I'd prefer not to use one for this particular project.
If you could briefly explain your answers too it'd really help me learn how this all works, and where I went wrong!
Thank you!
NB: I've changed and marked the line in my function that the error message is talking about.
I changed it from:
mp.appendChild(obj[i].children);
to:
mp.appendChild(create(obj[i].children));
This is because I want any nested keys in the children object to also be created, so screen_name had a children key, they too would be created. Sorry, I hope you can understand this!
I'm looking at http://jsperf.com/create-nested-dom-structure for some pointers, this may help you too!
Your "create" function is going to have to be written recursively.
To create a node from your data (in general), you need to:
Find the "tag" property and create a new element
Give the element the "id" value of the element (taken from the data)
For each element in "children", make a node and append it
Thus:
function create(elementDescription) {
var nodes = [];
for (var n in elementDescription) {
if (!elementDescription.hasOwnProperty(n)) continue;
var elem = elementDescription[n];
var node = document.createElement(elem.tag);
node.id = n; // optional step
var cnodes = create(elem.children);
for (var c = 0; c < cnodes.length; ++c)
node.appendChild(cnodes[c]);
nodes.push(node);
}
return nodes;
}
That will return an array of document elements created from the original "specification" object. Thus from your example, you'd call:
var createdNodes = create(nodes);
and "createdNodes" would be an array of one element, an <article> tag with id "tweets". That element would have two children, an <h2> tag with id "screen_name" and a <p> tag with id "text". (Now that I think of it, you might want to skip the "id" assignment unless the node description has an explicit "id" entry, or something.)
Thus if you have a <div> in your page called "tweets" (to use your example, though if so you'd definitely want to cut out the "id" setting part of my function), you'd add the results like this:
var createdNodes = create(nodes), tweets = document.getElementById('tweets');
for (var eindex = 0; eindex < createdNodes.length; ++eindex)
tweets.appendChild(createdNodes[eindex]);
I added a function appendList that accepts a list of elements, and the container to append to. I removed the append to "tweets" part out of the create function to more effectively separate your code.
function create(obj) {
var els = [];
for(i in obj){
var tmp = document.createElement(obj[i].tag);
var children;
if(children = obj[i].children) {
var childEls = create(children);
appendList(childEls, tmp);
}
els.push(tmp);
};
return els;
};
function appendList(list, container){
for(var i = 0, el; el = list[i]; i++){
container.appendChild(el);
}
};
// gets an array of root elements populated with children
var els = create(nodes);
// appends the array to "tweets"
appendList(els, document.getElementById("tweets"));
Building on the previous answer:
I think you still need to create the element you're trying to append:
tmp.appendChild(children[prop].tag);
should be
tmp.appendChild(document.createElement(children[prop].tag));
function create(obj) {
for(i in obj){
var tmp = document.createElement(obj[i].tag);
var children;
if(children = obj[i].children) {
for(var prop in children)
tmp.appendChild(document.createElement(children[prop].tag));
}
document.getElementById("tweets").appendChild(tmp);
};
};