Avoid double getElementId's within else if - javascript

I have a long list of else if statements.
Each one does a document.getElementById to check for existence of some element.
In one of of the else if statements to the bottom i need to not only do getElementById but I need to check also if that element has a certain attribute. This made me do getElementById twice, which i was hoping to avoid.
This is my code:
if (doc.getElementById('blah')) {
} else if (doc.getElementById('blah2')) {
} else if (doc.getElementById('js_blob') && doc.getElementById('js_blob').hasAttribute('action')) {
//here
} else if (doc.getElementById('blah3')) {
} else if (doc.getElementById('blah4')) {
} else {
console.warn('none of them');
}
Notice the line: } else if (doc.getElementById('js_blob') && doc.getElementById('js_blob').hasAttribute('action')) {
I had tried something like this and it didnt work: } else if (var myBlobL = doc.getElementById('js_blob') && myBlobL.hasAttribute('action')) { this would give syntax error
anyway to avoid doing double getElementById in this else if statement?
Thanks

Use a temporary variable:
var tmp;
// ...
} else if ((tmp = doc.getElementById('js_blob')) && tmp.hasAttribute('action')) {

You can save the result in a variable, in this case you only once will call getElementById
var js_blob = doc.getElementById('js_blob')
if (js_blob && js_blob.hasAttribute('action')) {
}
I see that you have logic with many "else if", I think you can replace it on something like this
function fn() {
var ids = ['elem1', 'elem2', 'elem3'],
len = ids.length,
i, el;
for (i = 0; i < len; i++) {
el = document.getElementById(ids[i]);
if (el) {
break;
}
}
if (!el) {
return;
}
// work with el which has action
// or add logic for specific ID
if (el.hasAttribute('action')) {
el.innerHTML = 'action';
} else if (el.getAttribute('id') === 'elem2') {
el.innerHTML = 'ELEM2';
}
}
fn();
Need decrease count appeals to the DOM, in this case you just once will appeal to each element, and further will work only with copy.

Related

Unexpected results from a function

I have the following two blocks of code that I am trying to debug.
function getSectionId(target){
let element = target;
if(element.hasAttribute('id')){
console.log(element.id);
return element.id;
}
else {
getSectionId(element.parentElement);
}
};
function coverageLimitHandler(event) {
const target = event.target;
if (target.getAttribute('data-status') !== 'set') {
let itemBlock = addLineItem();
let sectionId = getSectionId(target);
let attribute = '';
console.log(sectionId);
}
}
The event fires and the functions run, but the above gives the unexpected following results
//first-coverage-section (this one is expected.)
//undefined (this is expected to be the same, but is not.)
And I cannot for the life of me figure out why this is happening.
the problem is that your recursive call is not returning anything.
when you do:
getSectionId(element.parentElement);
it will call the function and maybe some day, the if above
if(element.hasAttribute('id')){
console.log(element.id);
return element.id;
}
will return something, but that won't be returned to the previous calls therefore your main call wont have anything to return, so to solve this you need to do this:
function getSectionId(target){
let element = target;
if(element.hasAttribute('id')){
console.log(element.id);
return element.id;
}
else {
// add this return and your function will work correctly.
return getSectionId(element.parentElement);
}
};
basically you have something like this:
function recursiveNotWorking(n) {
if (n === 5) {
return "something"
} else {
// you dont return anything, then something never bubbles up
recursiveNotWorking(n + 1);
}
}
function recursiveWorking(n) {
if (n === 5) {
return "something"
} else {
// we return something
return recursiveWorking(n + 1);
}
}
console.log("NW: ", recursiveNotWorking(1));
console.log("Working: ", recursiveWorking(1));
You need to return the result of the recursive call:
const getSectionId = target => {
if (target.hasAttribute('id') {
return target.id;
}
// You should also check that parentElement exist
// Otherwise you might reach the root node and then parentElement could become null
return getSectionId(target.parentElement);
};
Alos, this can be re-written as well as one liner:
const getSectionId = t => t.id || getSectionId(t.parentElement)
You don't have return in the first function and you don't check on undefined. Also you don't need to use the element variable. It's useless.
Maybe this will work:
function getSectionId(target){
if (typeof target === 'undefined') return null;
if(target.hasAttribute('id')){
console.log(target.id);
return target.id;
}
return getSectionId(target.parentElement);
}

Javascript Click Event Pushing Multiple Duplicates to Array

In recreating the Simon game, I am trying to push a click event to an array and then immediately test that Array in a nested function. On the first pass it seems to work.
However, on the third run the array does not seem to clear.
The screen shot below also shows that each input is printed multiple times to the console.
Full code pen here - https://codepen.io/jhc1982/pen/NwQZRw?editors=1010
Quick example:
function userMoves() {
var userInput = [];
document.getElementById("red").addEventListener("click", function(){
userInput.push("red");
testington();
});
$(".red").mousedown(function(event){
redAudio.play();
$(".red").css("background-color", "red");
});
$(".red").mouseup(function(){
$(".red").css("background-color", "#990000");
});
function testington(){
if (userInput.length == pattern.length) {
for (var i = 0; i < userInput.length; i++) {
if (userInput[i] !== pattern[i]) {
alert("Game Over");
} else if (i === userInput.length -1 && userInput[i] === pattern[i]) {
userInput = emptyArr;
simonMoves();
console.log("user input is ",userInput);
} else {
continue;
}
}
}
}
}
I am sure it is something really obvious but have been stuck for hours.
I think the problem may be that you are assigning the click events every time the userMoves execute. That means every time the function is called the event is added to the elements, so after two calls to userMoves() when you click on red the event is executed twice, after three calls it is executed three times, etc.
The code that adds the event listener should be out of the userMoves function. The testington function should also be out of userMoves, which would get much simpler:
function userMoves() {
$("#score-text").text(level);
userInput = [];
}
Here's a Pen with working code: https://codepen.io/anon/pen/ppzqyY
You need to add the break; keyword to after alert("Game over");
function testington(){
if (userInput.length == pattern.length) {
for (var i = 0; i < userInput.length; i++) {
if (userInput[i] !== pattern[i]) {
alert("Game Over");
break; // Break the loop
} else if (i === userInput.length -1 && userInput[i] === pattern[i]) {
userInput = emptyArr;
simonMoves();
console.log("user input is ",userInput);
} else {
continue;
}
}
}
}

How to accomplish this without using eval

Sorry for the title but I don't know how to explain it.
The function takes an URI, eg: /foo/bar/1293. The object will, in case it exists, be stored in an object looking like {foo: { bar: { 1293: 'content...' }}}. The function iterates through the directories in the URI and checks that the path isn't undefined and meanwhile builds up a string with the code that later on gets called using eval(). The string containing the code will look something like delete memory["foo"]["bar"]["1293"]
Is there any other way I can accomplish this? Maybe store the saved content in something other than
an ordinary object?
remove : function(uri) {
if(uri == '/') {
this.flush();
return true;
}
else {
var parts = trimSlashes(uri).split('/'),
memRef = memory,
found = true,
evalCode = 'delete memory';
parts.forEach(function(dir, i) {
if( memRef[dir] !== undefined ) {
memRef = memRef[dir];
evalCode += '["'+dir+'"]';
}
else {
found = false;
return false;
}
if(i == (parts.length - 1)) {
try {
eval( evalCode );
} catch(e) {
console.log(e);
found = false;
}
}
});
return found;
}
}
No need for eval here. Just drill down like you are and delete the property at the end:
parts.forEach(function(dir, i) {
if( memRef[dir] !== undefined ) {
if(i == (parts.length - 1)) {
// delete it on the last iteration
delete memRef[dir];
} else {
// drill down
memRef = memRef[dir];
}
} else {
found = false;
return false;
}
});
You just need a helper function which takes a Array and a object and does:
function delete_helper(obj, path) {
for(var i = 0, l=path.length-1; i<l; i++) {
obj = obj[path[i]];
}
delete obj[path.length-1];
}
and instead of building up a code string, append the names to a Array and then call this instead of the eval. This code assumes that the checks to whether the path exists have already been done as they would be in that usage.

JQuery distinct descendants (filter out all parents in result set)

I needed a method to filter out all elements that are parents of other elements in the result set. I tried to write a plugin:
jQuery.fn.distinctDescendants = function() {
var nodes = [];
var result = this;
jQuery(result).each(function() {
var node = jQuery(this).get(0);
if(jQuery(node).find(result).length == 0) {
nodes.push(node);
}
});
return nodes;
};
When i run the following command on this example page:
jQuery('body, textarea').distinctDescendants();
I get the (wrong) result:
[body.contact-page, textarea, textarea]
This is wrong because body is the parent of at least one other element in the result (both textareas). Therefore the expected result would be:
[textarea, textarea]
What is wrong here?
Why aren't you using jQuery('body > input') instead?
You can use the following (verbose) code to achieve what you want; it should work as drop-in replacement of your plugin code.
jQuery.fn.distinctDescendants = function() {
var nodes = [];
var parents = [];
// First, copy over all matched elements to nodes.
jQuery(this).each(function(index, Element) {
nodes.push(Element);
});
// Then, for each of these nodes, check if it is parent to some element.
for (var i=0; i<nodes.length; i++) {
var node_to_check = nodes[i];
jQuery(this).each(function(index, Element) {
// Skip self comparisons.
if (Element == node_to_check) {
return;
}
// Use .tagName to allow .find() to work properly.
if((jQuery(node_to_check).find(Element.tagName).length > 0)) {
if (parents.indexOf(node_to_check) < 0) {
parents.push(node_to_check);
}
}
});
}
// Finally, construct the result.
var result = [];
for (var i=0; i<nodes.length; i++) {
var node_to_check = nodes[i];
if (parents.indexOf(node_to_check) < 0) {
result.push(node_to_check);
}
}
return result;
};
Your method seems OK but your example is perhaps wrong. You said -
jQuery('body, input').distinctDescendants();
I get the (wrong) result:
[body.contact-page, textarea, textarea]
How come you are getting textarea if that is not there in the selector?
Also be careful using this method. Remember -
jQuery('div, input').distinctDescendants(); means some input are inside the div under consideration and some are outside. Though the result is not unpredictable but it is apparently difficult to guess. So most of the time try use selector having class name or id.
Do let us know your feedback ... I feel the function is ok.
I think this is what you are expecting for
jQuery('body, input').filter(function(){if($(this).children().length >0) return false; else return true; })
or may be rather
jQuery('body, input, textarea').filter(function(){if($(this).children().length >0) return false; else return true; })
and this will return only the text areas(as you want in the example)
jQuery('body, textarea').filter(function(){if($(this).children().length >0) return false; else return true; })
UPDATE
so you want something like this
var elems = 'textarea';
jQuery('body, '+ elems )
.filter(function(){
if($(this).find(elems ).length >0)
return false;
else return true;
})
which returns
[textarea, textarea]

Why childNodes[i] in function return undefined but alert an object?

Well, i'm writting my own getElementByClassName and this is my problem :
function getElementByClassName(elemento,clase){
var i = 0;
if(elemento.hasChildNodes()){
while(elemento.childNodes[i]){
if(elemento.childNodes[i].nodeType != 3){
if(elemento.childNodes[i].className == clase){
return elemento.childNodes[i]; // <---- This is my problem, change to alert
}
else {
getElementByClassName(elemento.childNodes[i],clase);
}
}
i++
}
}
}
var div = getElementByClassName(document.body,"foo");
alert(div);
It alerts undefined, but if i put ( in function) alert this alerts [objectHTMLDivElement] and undefined, so why this returns undefined if this recognize that's a [objectHTMLDivElement] with alert?
To answer your question, the reason why your implementation is not working is because you're recursively calling your function in the else clause and doing nothing with the return value. That's why your code is finding the object, but it's never getting returned.
This is a slightly reworked version of yours, but there are also other limitations to your approach, one being elements with multiple classes will not be found (i.e. <div class="foo bar"> will not be returned). Unless you're just doing this as a learning exercise, I suggest going with an existing implementation, like the link in Yoni's answer.
function getElementByClassName(elemento, clase)
{
var i = 0;
if (elemento.hasChildNodes())
{
while (elemento.childNodes[i])
{
if (elemento.childNodes[i].nodeType != 3)
{
if (elemento.childNodes[i].className == clase)
return elemento.childNodes[i];
else
{
var result = getElementByClassName(elemento.childNodes[i], clase);
if (result != null)
return result;
}
}
i++;
}
}
return null;
}

Categories

Resources