Im trying to learn javascript by tracing through some code at the moment, I was wondering if someone could explain what is happening in this snippet of code (this snippet is just part of a function hence no closing brace):
document.addEventListener("keydown",function(e){
for(i=0;i < keys.length; i++) {
if(e.keyCode == keys[i]){
var tri = document.getElementById("foo").childNodes[i];
if(i==0){
var tri = document.getElementById("foo").childNodes[1];
}
if(i==1){
var tri = document.getElementById("foo").childNodes[3];
}
if(i > 1) {
var tri = document.getElementById("foo").childNodes[(i*2)+1];
}
The part im confused about most in this is the childNodes[] and the if(i) statements?
// Bind an event handler to keydown on the entire document
document.addEventListener("keydown",function(e){
// Everything in here happens on keydown
// keys must be an array declared somewhere earlier in the code
// This loops through that array
for(i=0;i < keys.length; i++) {
// If the current key we are looking at in the array
// is the key that was pressed
if(e.keyCode == keys[i]){
// Get the (i+1)th childnode of foo
var tri = document.getElementById("foo").childNodes[i];
// If i = 0 get the second element (not the first)
if(i==0){
var tri = document.getElementById("foo").childNodes[1];
}
// If i == 1 get the fourth element (not the second)
if(i==1){
var tri = document.getElementById("foo").childNodes[3];
}
// Otherwise get the (i*2+2)th element.
if(i > 1) {
var tri = document.getElementById("foo").childNodes[(i*2)+1];
}
// Here we are still in an if-statement, in a loop, in a function,
// so there is probably more code here, at least some closing }'s
Note that var tri = document.getElementById("foo").childNodes[i]; is a useless line, because i cannot be negative, one of the next three if statements will always succeed and tri will be overwritten.
Also note that when i = 0, (i*2)+1 = 1 and when i = 1, (i*2)+1 = 3, so those other two if statements are useless as well because the third covers all cases and doesn't need to even be in an if clause. The above code is 100% equivalent to:
document.addEventListener("keydown",function(e){
for(i=0;i < keys.length; i++) {
if(e.keyCode == keys[i]){
var tri = document.getElementById("foo").childNodes[(i*2)+1];
...
Since i is the variable used to iterate through the array called keys, and the node selected depends on i. keys must be an array with an unusual purpose. It is an array of keyCodes, where the position of the keyCode in the array determines which node should be selected and stored in tri when that key is pressed.
Childnodes is the collection (array, effectively) of descendents of a DOM element.
E.g. consider some markup:
<div id='div1'>
<p id='p1'>
<span id='s1'>Span one</span>
<span id='s2'>Span two</span>
<span id='s3'>Span three</span>
</p>
</div>
in this scenario, document.getElementById('p1').childnodes[0] will return span with id='s1', childnodes[1] will return the span with id='s2' and so on.
http://www.w3schools.com/Dom/prop_element_childnodes.asp
More detail: https://developer.mozilla.org/En/DOM/Node.childNodes
People will complain about a link to w3schools.com, but imho it is adequate for quick intros to concepts.
It gets different DOM elements based on key being pressed.
Related
This was given to me as an interview question -- didn't get the job, but I still want to figure it out.
The objective is to write two querySelectorAll functions: one called qsa1 which works for selectors consisting of a single tag name (e.g. div or span) and another called qsa2 which accepts arbitrarily nested tag selectors (such as p span or ol li code).
I got the first one easily enough, but the second one is a bit trickier.
I suspect that, in order to handle a variable number of selectors, the proper solution might be recursive, but I figured I'd try to get something working that is iterative first. Here's what I've got so far:
qsa2 = function(node, selector) {
var selectors = selector.split(" ");
var matches;
var children;
var child;
var parents = node.getElementsByTagName(selectors[0]);
if (parents.length > 0) {
for (var i = 0; i < parents.length; i++) {
children = parents[i].getElementsByTagName(selectors[1]);
if (children.length > 0) {
for (var i = 0; i < parents.length; i++) {
child = children[i];
matches.push(child); // somehow store our result here
}
}
}
}
return matches;
}
The first problem with my code, aside from the fact that it doesn't work, is that it only handles two selectors (but it should be able to clear the first, second, and fourth cases).
The second problem is that I'm having trouble returning the correct result. I know that, just as in qsa1, I should be returning the same result as I'd get by calling the getElementsByTagName() function which "returns a live NodeList of elements with the given tag name". Creating an array and pushing or appending the Nodes to it isn't cutting it.
How do I compose the proper return result?
(For context, the full body of code can be found here)
Here's how I'd do it
function qsa2(selector) {
var next = document;
selector.split(/\s+/g).forEach(function(sel) {
var arr = [];
(Array.isArray(next) ? next : [next]).forEach(function(el) {
arr = arr.concat( [].slice.call(el.getElementsByTagName(sel) ));
});
next = arr;
});
return next;
}
Assume we always start with the document as context, then split the selector on spaces, like you're already doing, and iterate over the tagnames.
On each iteration, just overwrite the outer next variable, and run the loop again.
I've used an array and concat to store the results in the loop.
This is somewhat similar to the code in the question, but it should be noted that you never create an array, in fact the matches variable is undefined, and can't be pushed to.
You have syntax errors here:
if (parents.length > 0) {
for (var i = 0; i < parents.length; i++) {
children = parents[i].getElementsByTagName(selectors[1]);
if (children.length > 0) {
for (var i = 0; i < parents.length; i++) { // <-----------------------
Instead of going over the length of the children, you go over the length of the parent.
As well as the fact that you are reusing iteration variable names! This means the i that's mapped to the length of the parent is overwritten in the child loop!
On a side note, a for loop won't iterate over the elements if it's empty anyway, so your checks are redundant.
It should be the following:
for (var i = 0; i < parents.length; i++) {
children = parents[i].getElementsByTagName(selectors[1]);
for (var k = 0; k < children.length; i++) {
Instead of using an iterative solution, I would suggest using a recursive solution like the following:
var matches = [];
function recursivelySelectChildren(selectors, nodes){
if (selectors.length != 0){
for (var i = 0; i < nodes.length; i++){
recursivelySelectChildren(nodes[i].getElementsByTagName(selectors[0]), selectors.slice(1))
}
} else {
matches.push(nodes);
}
}
function qsa(selector, node){
node = node || document;
recursivelySelectChildren(selector.split(" "), [node]);
return matches;
}
I'm fairly new to HTML and Javascript. I want to know how to create an infinite loop from, let's say, myArray, list, or whatever and then display result one at a time. Can you please give me an example, hints, or anything with detailed explanation of how it works? I just want to understand on how things work. Thanks!
A very basic loop is a while loop:
while (condition) {
//code block to be executed
}
Typically you would use it like so:
var i = 0;
while (i < 10) {
//code block to be executed
i++;
//This block of code will continue until i >= 10
//adding 1 to the value of I each iteration
}
Easiest way to do a endless loop:
while (true) {
code block to be executed
}
//true will always be true so this will continue until it
//hits a return; statement, the end of time, or the software
//or hardware gives up
A common mistake that end up in an endless loop:
var i = 0;
while (i < 10) {
code block to be executed
//In this example i is never being increased so
//i will always be less than 10
}
A very practical way to do a while loop correctly:
var array = ['a','b','c'];
var i = 0;
while (i < array.length) {
alert(array[i]);
i++;
}
//This will alert a, alert b, then alert c
Another way to do the above is using a for loop:
var array = ['a','b','c'];
for (var i = 0; i < array.length; i++) {
alert(array[i];
}
//for loops are a good practice because you are less
//likely to leave out steps like defining the iterator,
//or increasing the iterator
OP
I'm trying to create something using HTML/Javascript that everytime I press a button called next item (I created one using <form></form>) it would display an item, or an image. The trick is I don't know how to keep displaying the item after the last position. After the last position it should go back to the first position. For example, for the array that you provide in your example you have [a, b, c], after I displayed c and press the button again I want to display a again and so forth. Can you give me hints or any other valuable info on how to do this?
Answer
JSFiddle Example:
http://jsfiddle.net/d2809p6z/
HTML
<button id="button">click me</div>
JS
var array = ['a','b','c'];
var index = 0;
document.getElementById('button').onclick=function(){
if (index >= array.length) {
index = 0;
}
alert(array[index]);
index++;
};
i am newbie learner and i am learning basic javaScript from codecademy.I stuck at "Search Text for Your Name" tutorial 5/7.
here is my question:
your loop should stop when it hits the value of the first iterator (say, i)
plus the length of your myName variable.
here is some informations from to tutorial:
Your second "for" loop
Okay! Last loopy step: add another for loop, this time inside the body of your if statement (between the if's {}s).
This loop will make sure each character of your name gets pushed to the final array. The if statement says: "If we find the first letter of the name, start the second for loop!" This loop says: "I'm going to add characters to the array until I hit the length of the user's name." So if your name is 11 letters long, your loop should add 11 characters to hits if it ever sees the first letter of myName in text.
For your second for loop, keep the following in mind:
First, you'll want to set your second loop's iterator to start at the first one, so it picks up where that one left off. If your first loop starts with
> for(var i = 0; // rest of loop setup
your second should be something like
> for(var j = i; // rest of loop setup Second
think hard about when your loop should stop.
Finally, in the body of your loop, have your program use the .push() method of hits. Just like strings and arrays have a .length method, arrays have a .push() method that adds the thing between parentheses to the end of the array. For example,
newArray = [];
newArray.push('hello');
newArray[0]; // equals 'hello'
and here is my code:
multistr:true
var text = "Hey, how are you \
doing? My name is Emily.";
var myName = "Emily";
var hits = [];
for (var i = 0; i > text.length; i++)
{
if (text[i] === 'E')
{
for(var j = i; j > text.length; j++){
};
};
};
ps: i don't want to pass this tutorial without understand it. please help me. teach me.
for (var i = 0; i > text.length; i++) should be
for (var i = 0; i < text.length; i++)
otherwise it won't ever meet the criteria to even start the loop.
Welcome on board! You confused > with <. Your loops won't run because for the first check when i = 0 it certainly does not hold that 0 > text.length, because text.length is at least 0 (there are no strings shorter than the empty string).
You should make a habit of manually going through your loops for the first two steps and then check what happens just before the loop ends.
Here is what I got for my code:
for ( i = 0; i < text.length; i++)
{
if ( text[i] === "E")
{
for( var j = i; j < (myName.length + i ); j++)
{
hits.push(text[j]);
}
}
};
It looks like you were missing the " + i " part in your second for loop. That seems to make sure that the first loop will be included. I tried it without the "+ i" and it does not work.
I tried continuing directly from the second for loop using a "+ j" and that only crashes the browser.
I've ditched jquery about 9(ish) months ago and needed a selector engine (without all the hassle and don't mind ie<7 support) so i made a simplified version of document.querySelectorAll by creating this function:
// "qsa" stands for: "querySelectorAll"
window.qsa = function (el) {
var result = document.querySelectorAll(el)[0];
return result;
};
This works perfectly fine for 95% of the time but I've had this problem for a while now and i have researched mdn, w3c, SO and not to forget Google :) but have not yet found the answer as to why I only get the first element with the requested class.
And I know that only the first element being returned is caused by the "[0]" at the end, but the function won't work if I remove it so I've tried to make a for loop with an index variable that increases in value depending on the length of elements with that class like this:
window.qsa = function (el) {
var result, el = document.querySelectorAll(el);
for(var i = 0; i < el.length; ++i) {
result = el[i];
}
return result;
};
Again that did not work so I tried a while loop like this:
window.qsa = function (el) {
var result, i = 0, el = document.querySelectorAll(el);
while(i < el.length) {
i++;
}
result = el[i];
return result;
};
By now I'm starting to wonder if anything works? and I'm getting very frustrated with document.querySelectorAll...
But my stubborn inner-self keeps going and I keep on failing (tiering cycle) so I know that now is REALLY the time to ask these questions :
Why is it only returning the first element with that class and not all of them?
Why does my for loop fail?
Why does my while loop fail?
And thank you because any / all help is much appreciated.
Why is it only returning the first element with that class and not all of them?
Because you explicitly get the first element off the results and return that.
Why does my for loop fail?
Because you overwrite result with a new value each time you go around the end of loop. Then you return the last thing you get.
Why does my while loop fail?
The same reason.
If you want all the elements, then you just get the result of running the function:
return document.querySelectorAll(el)
That will give you a NodeList object containing all the elements.
Now that does what you say you want, I'm going to speculate about what your real problem is (i.e. why you think it doesn't work).
You haven't shown us what you do with the result of running that function, but my guess is that you are trying to treat it like an element.
It isn't an element. It is a NodeList, which is like an array.
If you wanted to, for instance, change the background colour of an element you could do this:
element.style.backgroundColor = "red";
If you want to change the background colour of every element in a NodeList, then you have to change the background colour of each one in turn: with a loop.
for (var i = 0; i < node_list.length; i++) {
var element = node_list[i];
element.style.backgroundColor = "red";
}
You are returning a single element. You can return the array. If you want to be able to act on all elements at once, jQuery style, you can pass a callback into your function;
window.qsa = function(query, callback) {
var els = document.querySelectorAll(query);
if (typeof callback == 'function') {
for (var i = 0; i < els.length; ++i) {
callback.call(els[i], els[i], i);
}
}
return els;
};
qsa('button.change-all', function(btn) {
// You can reference the element using the first parameter
btn.addEventListener('click', function(){
qsa('p', function(p, index){
// Or you can reference the element using `this`
this.innerHTML = 'Changed ' + index;
});
});
});
qsa('button.change-second', function(btn) {
btn.addEventListener('click', function(){
var second = qsa('p')[1];
second.innerHTML = 'Changed just the second one';
});
});
<p>One</p>
<p>Two</p>
<p>Three</p>
<button class='change-all'>Change Paragraphs</button>
<button class='change-second'>Change Second Paragraph</button>
Then you can call either use the callback
qsa('P', function(){
this.innerHTML = 'test';
});
Or you can use the array that is returned
var pList = qsa('p');
var p1 = pList[0];
This loop
for(var i = 0; i < el.length; ++i) {
result = el[i];
}
overwrites your result variable every time. That's why you always get only one element.
You can use the result outside though, and iterate through it. Kinda like
var result = window.qsa(el)
for(var i = 0; i < result.length; ++i) {
var workOn = result[i];
// Do something with workOn
}
I have a JSON response like this:
var errorLog = "[[\"comp\",\"Please add company name!\"],
[\"zip\",\"Please add zip code!\"],
...
Which I'm deserializing like this:
var log = jQuery.parseJSON(errorLog);
Now I can access elements like this:
log[1][1] > "Please add company name"
Question:
If I have the first value comp, is there a way to directly get the 2nd value by doing:
log[comp][1]
without looping through the whole array.
Thanks for help!
No. Unless the 'value' of the first array (maybe I should say, the first dimension, or the first row), is also it's key. That is, unless it is something like this:
log = {
'comp': 'Please add a company name'
.
.
.
}
Now, log['comp'] or log.comp is legal.
There are two was to do this, but neither avoids a loop. The first is to loop through the array each time you access the items:
var val = '';
for (var i = 0; i < errorLog.length; i++) {
if (errorLog[i][0] === "comp") {
val = errorLog[i][1];
break;
}
}
The other would be to work your array into an object and access it with object notation.
var errors = {};
for (var i = 0; i < errorLog.length; i++) {
errors[errorLog[i][0]] = errorLog[i][1];
}
You could then access the relevant value with errors.comp.
If you're only looking once, the first option is probably better. If you may look more than once, it's probably best to use the second system since (a) you only need to do the loop once, which is more efficient, (b) you don't repeat yourself with the looping code, (c) it's immediately obvious what you're trying to do.
No matter what you are going to loop through the array somehow even it is obscured for you a bit by tools like jQuery.
You could create an object from the array as has been suggested like this:
var objLookup = function(arr, search) {
var o = {}, i, l, first, second;
for (i=0, l=arr.length; i<l; i++) {
first = arr[i][0]; // These variables are for convenience and readability.
second = arr[i][1]; // The function could be rewritten without them.
o[first] = second;
}
return o[search];
}
But the faster solution would be to just loop through the array and return the value as soon as it is found:
var indexLookup = function(arr, search){
var index = -1, i, l;
for (i = 0, l = arr.length; i<l; i++) {
if (arr[i][0] === search) return arr[i][1];
}
return undefined;
}
You could then just use these functions like this in your code so that you don't have to have the looping in the middle of all your code:
var log = [
["comp","Please add company name!"],
["zip","Please add zip code!"]
];
objLookup(log, "zip"); // Please add zip code!
indexLookup(log, "comp"); // Please add company name!
Here is a jsfiddle that shows these in use.
Have you looked at jQuery's grep or inArray method?
See this discussion
Are there any jquery features to query multi-dimensional arrays in a similar fashion to the DOM?