I am trying to list out all the elements inside an array and as you can see Company has three levels, but I've only written the script to print the output until two levels. How do I access the third level? What should be the array that I should be using inside the third for loop?
What you're looking for is recursion.
Here is a fixed version of your fiddle: http://jsfiddle.net/jEmf9/
function generateEntity(obj) {
var html = [];
var name = obj.entity;
html.push('<li>');
html.push(name);
html.push('</li>');
var arrayName = name.replace(/\s/gi, '_');
if (obj[arrayName] == undefined) {
return html.join('');
}
var entity = obj[arrayName];
for (var i = 0; i < entity.length; i++) {
html.push('<ul>');
html.push(generateEntity(entity[i]));
html.push('</ul>');
}
return html.join('');
}
In your case you do not need a special technique for accessing the third level. You need to write a recursive tree walking function so that you can render a tree of any depth.
I've done a quick patch of your code here: http://jsfiddle.net/rtoal/xcEa9/6/
Once you get things working as you like, you can work on forming your html. Your repeated string concatenation using += is known to be extremely inefficient, but that is outside the scope of this question. :)
Related
Today while working with some JS I had a bug in my code. I was able to resolve it but I don't really understand why the change I made works. All I can guess is that it comes down to either closure or variable scope.
I was trying to build up a nested hash of arrays like so:
var maxNumberOfPairs = 2;
var container = {};
var pairsHash = {};
$.each(["nurse", "doctor", "janitor", "chef", "surgeon"], function(index, role) {
for(var i = 0; i < maxNumberOfPairs; i++){
var pairIdSubString = "attribute_" + i + "_" + role;
pairsHash["attribute_" + i] = [pairIdSubString + "_night", pairIdSubString + "_day"];
}
container [role] = pairsHash;
});
If you run this you get a nice nested output inside container but when you look at each array in the hash you get a weird behaviour with the string produced.
Each one has the last role in each string like so:
"attribute_0_surgeon_night"
If you log out the variable pairIdSubString it correctly has the role in the string, but as soon as this is added to pairHash it just uses the last element in the $.each array.
I was able to fix it by moving pairsHash inside the $.each but outside the for loop.
Can anyone explain to my why the output was different after moving it inside the each?
Thanks
It actually has to do with reference vs value. When its outside the each you are operating on the same object over and over so every time you set it to the container you are just setting a reference to the same object that is constantly changing. So every reference in container after the loop is the last state of the pairsHash because they all point to the same object.
When you put the pairsHash in the each it is reinitialized every time so they all point to different memory addresses. Not the same one since a new one is created every loop.
To further clarify all objects are just references to a memory address In JavaScript so in order to get new one you need to initialize or to pass by value to a function clone it.
First of all, I'm aware there are many questions about closures in JavaScript, especially when it comes to loops. I've read through many of them, but I just can't seem to figure out how to fix my own particular problem. My main experience lies with C#, C++ and some ASM and it is taking some getting used to JavaScript.
I'm trying to populate a 3-dimensional array with new instances of a class (called Tile) in some for loops. All I want to do is pass along a reference to some other class (called Group) that gets instantiated in the first loop (and also added to another array). As you might have guessed, after the loops are done, every instance of the Tile class has a reference to the same Group object, namely the last one to be created.
Apparently instead of passing a reference to the Group object, a reference to some variable local to the function is passed along, which is updated in every iteration of the loop. My assumption is that solving this problem has something to do with closures as this appears to be the case with many similar problems I've come across while looking for a solution.
I've posted some trimmed down code that exposes the core of the problem on jsFiddle:
//GW2 namespace
(function( GW2, $, undefined ) {
//GW2Tile class
GW2.Tile = function(globalSettings, kineticGroup)
{
//Private vars
var tilegroup = kineticGroup;
// console.log(tilegroup.grrr); //Shows the correct value
var settings = globalSettings;
this.Test = function(){
console.log(tilegroup.grrr);
}
this.Test2 = function(group){
console.log(group.grrr);
}
} //Class
}( window.GW2 = window.GW2 || {}, jQuery ));
var zoomGroups = [];
var tiles = [];
var settings = {};
InitArrays();
tiles[0,0,0].Test(); //What I want to work, should give 0
tiles[0,0,0].Test2(zoomGroups[0]); //How I'd work around the issue
function InitArrays(){
var i, j, k, zoomMultiplier, tile;
for(i = 0; i <= 2; i++){
zoomGroups[i] = {};
zoomGroups[i].grrr = i;
tiles[i] = [];
zoomMultiplier = Math.pow(2, i);
for(j = 0; j < zoomMultiplier; j++){
tiles[i,j] = [];
for(k = 0; k < zoomMultiplier; k++){
tile = new GW2.Tile(settings, zoomGroups[i]);
tiles[i,j,k] = tile;
}
}
}
}
Up till now when working with JavaScript, I've generally fiddled with the code a bit to make it work, but I'm tired of using work-arounds that look messy as I know there should actually be some fairly simple solution. I'm just not fond of asking for help, but this is really doing my head in. Any help is very much appreciated.
Multidimensional arrays
The problem
The first issue with your code above is how you are attempting to create multidimensional arrays.
The syntax you are using is:
tiles[0,0,0]
However, the way JavaScript will interpret this is:
tiles[0]
Accessing a multidim array
If you wish to access a multidim array you have to use:
tiles[0][0][0]
And to create a multidim array you would need to do the following:
tiles = [];
tiles[0] = [];
tiles[0][0] = [];
tiles[0][0][0] = 'value';
or:
tiles = [[['value']]];
With respect to your code
In your code you should be using:
tiles[i][j][k] = tile;
But you should also make sure that each sub array actually exists before setting it's value, otherwise you'll get undefined or illegal offset errors.
You can do this by way of:
(typeof tiles[i] === 'undefined') && (tiles[i] = []);
(typeof tiles[i][j] === 'undefined') && (tiles[i][j] = []);
tiles[i][j][k] = tile;
Obviously the above can be optimised depending on how you are traversing your loops i.e. it would be best to make sure the tiles[i] level exists as an array before stepping in to the the [j] loop, and then not worry about checking it's existence again whilst stepping j.
Other options
Depending on what your dataset is, or at least what you hope to do with the tiles array it can be worth considering using an object instead:
/// set up
tiles = {};
/// assignment
tiles[i+','+j+','+k] = 'value';
However this method is likely to be slower, although I've been proved wrong a number of times by my assumptions and differing JavaScript interpreters. This would probably be were jsPerf would be your friend.
Optimisation
One benefit of using the tiles[i][j][k] approach is that it gives you the chance to optimise your references. For example, if you were about to process a number of actions at one level of your multidimensional array, you should do this:
/// set up
var ij = tiles[i][j];
/// use in loops or elsewhere
ij[k] = 'value'
This is only of benefit if you were to access the same level more than once however.
var brands = document.getElementsByName("brand");
for(var brand in brands){
$("input[name='brand']").eq(brand).click(function(){
alert("hello22");
loadDataFN(1);
});
}
This code is not executing in ie6,
Any help would be appreciated.
The problem is likely that you are trying to use a for-in construct to iterate over a numeric array. This often won't give expected results. Use an incremental for loop instead:
var brands = document.getElementsByName("brand");
// Use an incremental for loop to iterate an array
for(var i=0; i<brands.length; i++){
$("input[name='brand']").eq(brands[i]).click(function(){
alert("hello22");
loadDataFN(1);
});
}
However,
after seeing the first part of your code, the loop appears unnecessary. You should only need the following, since you are assigning the same function to all brand inputs.
// These will return the same list of elements (as long as you don't have non-input elements named brand)
// though the jQuery version will return them as jQuery objects
// rather than plain DOM nodes
var brands = document.getElementsByName("brand");
$("input[name='brand']");
Therefore, the getElementsByName() and loop are not necessary.
$("input[name='brand']").click(function() {
alert("hello22");
loadDataFN(1);
});
for-in loops are used for iterating over the properties of an object, not over the elements of an array.
Why don't you write the code without jQuery if this doesn't work?
Something like this:
function getInputByName(name) {
var i, j = document.getElementsByTagName('input').length;
for(i=0;i<j;++i) { // You can also use getAttribute, but maybe it won't work in IE6
if(document.getElementsByTagName('input')[i].name === name) {
return document.getElementsByTagName('input')[i];
}
}
return null;
}
I don't know jQuery, but maybe you can do something like this:
$(getInputByName('brand')).eq(brand).click(function(){
alert("hello22");
loadDataFN(1);
});
Javascript for Mozilla:
while (result)
{
alert(result.childNodes[0].nodeValue);
//txt=txt+result.childNodes[0].nodeValue+"<br/>";
result=nodes.iterateNext();
}
This is intended to return a series of strings from an xml file. When I use the alert line, it alerts as expected with the proper strings in a series. The "txt" variable on the commented line is supposed to go to an innerHTML further down in the function. But Firebug keeps telling me that the result.childNodes[0] is undefined. Why would it be defined in the alert but not the next line?
I hope that's enough code to determine the problem...if not I will post more.
Thanks for help
[edit]
this is the definition of result:
var x=xmlDoc.responseXML;
var nodes=x.evaluate(path, x, null, XPathResult.ANY_TYPE, null);
var result=nodes.iterateNext();
I am retrieving XML
[edit]
okay I put the iterator in a for loop like this:
for (var i=0; i<2; i++)
{
var res=(xmlNodes.childNodes[0].nodeValue);
txt=txt+res+"<br/>";
xmlNodes=xmlQuery.iterateNext();
}
because I had a theory that once the iteration was null the loop would fail. So that's what happened--it worked until I set the loop one instance higher than the amount of nodes available. now I am trying to figure out how to get the "length" of the node-set. How do I do that?
I don't see any problems in the code you have shown so far. If childNodes[0] is undefined, then it has to be a text node or an empty node, and you should see an exception when trying to access a property such as nodeValue of childNodes[0] which is undefined. The exception will show up on alert or concatenation, or any other type of access.
This is the answer to your updated question.
now I am trying to figure out how to get the "length" of the node-set. How do I do that?
You can have the following types of node-sets returned from the evaluate function:
Iterators
Snapshots
First Nodes
I'll skip "first nodes" as that doesn't apply in this situation.
Iterators
With iterators, you only get an iterateNext() method for traversing nodes sequentially. Iterators refer to live nodes, meaning if the nodes in the document were to change while you are traversing the resultset, the iterator will become invalid.
Here's an example with using an iterator to go over each resulting node:
var results = doc.evaluate("expr", doc, null, ORDERED_SNAPSHOT, null);
var node;
while(node = results.iterateNext()) {
console.log(node);
}
If you want to use iterators, and find the number of matching results, a wrapper function that iterates through all nodes and returns them in an array might be useful.
function evaluateXPath(document, expression) {
var ANY_TYPE = XPathResult.ANY_TYPE;
var nodes = document.evaluate(expression, document, null, ANY_TYPE, null);
var results = [], node;
while(node = nodes.iterateNext()) {
results.push(node);
}
return results;
}
Get nodes as an array and loop the array:
var results = evaluateXPath(doc, "expr");
for(var i = 0; i < results.length; i++) {
console.log(results[i]);
}
Snapshots
Snapshots provide a static result of the nodes at the time of querying. Changes to the document will not affect this snapshot. Useful interfaces here will be the snapshotLength property, and snapshotItem(index) function.
Here's an example using a snapshot result:
var ORDERED_SNAPSHOT = XPathResult.ORDERED_NODE_SNAPSHOT_TYPE;
var results = doc.evaluate("expr", doc, null, ORDERED_SNAPSHOT, null);
for(var i = 0; i < results.snapshotLength; i++) {
var node = results.snapshotItem(i);
console.log(node);
}
See a working example.
It seems you are developing this for Firefox. Did you consider using E4X for this purpose? It provides a really easy interface for dealing with XML documents - for creating, manipulating, and querying.
now I am trying to figure out how to
get the "length" of the node-set. How
do I do that?
In XPath this is achieved using the standard XPath count() function.
count(someExpression)
evaluates to the number of nodes selected by someExpression.
Is there a way for javascript to detect all assigned variables? For example, if one js file creates a bunch of vars (globally scoped), can a subsequent file get all the vars without knowing what they're named and which might exist?
Thanks in advance :)
EDIT, Question Part 2:
How do I get the values of these variables? Here is what I have attempted:
This is what I ended up with, as per comment suggestions:
for (var name in this) {
variables[name] = name;
variables[name]=this[name]
}
Flanagan's "JavaScript - The Definitive Guide" gives the following on page 653:
var variables = ""
for (var name in this)
variables += name + "\n";
For Firefox, you can see the DOM tab -- easy, though not an answer to your question.
The for in loop provided in Kinopiko's answer will work, but not in IE. More is explained in the article linked below.
For IE, use the RuntimeObject.
if(this.RuntimeObject){
void function() {
var ro = RuntimeObject(),
results = [],
prop;
for(prop in ro) {
results.push(prop);
}
alert("leaked:\n" + results.join("\n"));
}();
}
See also:
Detecting Global Pollution with the JScript RuntimeObject (DHTML Kitchen article)
RuntimeObject (MSDN docs)
There is the this variable. This is an object or an array, and you can simply put:
for(i in this) { //do something }
Unfortunately, it will return everything under the this object.
This will output all the variables into the console without needing to read the variable yourself.
var variables = ""
for (var name in this)
variables += name + "\n";
console.log(variables)
/*
This could work too... but it's such a big unecessary code for something you could do in one line
var split = variables.split("\n");
for (var i in split)
console.log(split[i])
*/
If you want to assign values from one object to another, there are a couple of ways to do this:
//Way 1st
const variables= { ...this };
// or (I don't know what's the difference ;) )
// Don't forget const variables= {};
Object.assign(variables, this);
// Yes. It's very easy. You just "copy" entries from this to variables. I want to note that you are not copying a link to this, namely ENTRY.
Or
// Way 2nd. If u need to do smth. with entries.
const variables= [];
for (const name of Object.keys(this)) {
/*
Doing smth........
*/
variables[name] = this[name];
}
I want to note that this is not a way to collect all declared variables into an object (I am looking for this method myself). They are simply ways of copying the contents of one object into another.