For loop doesent work while trying to add elements - javascript

Ok so i have a sidenav menu and it has 3 levels and the second layer has openers but the third layer doesent so i have to add openers like this ▼ via javascript but there are too many of them so i had to use a for loop too give each of the second layers an opener,i already created an object named opener2 like this
var opener2=document.createElement("span");
opener2.classList.add("opener");
aand then here is the for loop that doesent work
for(var q=0; q < $('.menu-block').length; q++){
if($('.menu-block')[q].childElementCount >=2){$('.menu-block')[q].before(opener2)};};
so let me explain menu-block is the part that contains both the opener and an item of the second layer,like there are 5 of them and each are different from each other,if there are 2 elements inside the second layer items menu block,that means this for loop shouldnt add an opener.Soo what should i do?
Edit:The problem is solved thx for your help guys

for(var q=0; q < $('.menu-block').length; q++) {
if($('.menu-block')[q].childElementCount >=2){
let opener2=document.createElement("span");
opener2.classList.add("opener");
$('.menu-block')[q].before(opener2)};
};
Declare the object declaration inside the for loop or else the same element will get appended again and again

The problem is that you have created only one span element, so this will not magically create new ones. If you have a second call to .before, it will actually move the span from its previous insertion point to the current.
It is not so efficient that you repeatedly reselect the elements with the ".menu-block" selector. Just do that once, and use .each to iterate over that collection.
Also, you are mixing jQuery code with jQuery-less code, which is a pity. If you use jQuery, you should not have to use things like document.createElement.
So here is how you could do it:
$('.menu-block').each(function () {
if (this.childElementCount >= 2) {
$("<span>").addClass("opener").insertBefore(this);
}
});

Related

Index Variable Rendering as Literal and Not as Value When Looping Event Listener Adds

Fellow Stackers.
I am attempting the following:
for(let i = 0; i <= promoObj.length - 1; i++) {
document.getElementById(promoObj[i]["note"]).addEventListener("click",
function(){
onPromoView(promoObj[i])
}, false);
}
promoObj is an object containing several arrays. i is the index of each array. When I run the loop shown above, onPromoView(promoObj[i] is rendered literally as onPromoView(promoObj[i] and not with the looped index number such as onPromoView(promoObj[0].
How do I get the event listener to render with the actual index number? I've tried many ways and did much searching but nothing is working. Hopefully, someone here has the answer.
BTW, I prefer the solution in vanilla JS.
Thanks.
Rudy

Trying to make sense of "this" in my javascript code (one thing works, the other doesn't)

I've been trying to learn javascript by refactoring some Jquery examples in a book into javascript. In the following code I add a click listener to a tab and make it change to active when the user clicks on the tab.
var tabs = document.querySelectorAll(".tabs a span");
var content = document.querySelectorAll("main .content li");
for (var tabNumber = 0; tabNumber <= 2; tabNumber++) {
tabs[tabNumber].addEventListener("click", function (event) {
for (var i = 0; i < tabs.length; i++) {
tabs[i].classList.remove("active");
}
tabs[tabNumber].classList.add("active");
for (var i = 0; i < content.length; i++) {
content[i].innerHTML = "";
}
event.preventDefault();
});
}
This returns an undefined error when I run it. However, I tried replacing tabs[tabNumber].classList.add("active") with this.classList.add("active") and it worked.
Why doesn't the previous code work? As far as I can see they are referring to the same thing, and tabs[tabNumber] should work since at that point in the code it is tabs[0].
If use this, I think it's better and a more polished solution. If you still want to use tabNumber, it's probably evaluating to 3 in every click callback, because it's the number after the last iteration, and you don't have a tabs[3] position.
So, you just have to make a closure of the tabNumber variable.
I guess other answers told you why tabs[tabNumber] does not work (because it comes from the score of the for loop and so, is always equal to the greater value of tabNumber).
That's why I would recommend using a .forEach loop. Careful though because it doesn't work on arrays of DOM nodes produced by document.querySelectorAll(), but you can use:
// ES6
Array.from(document.querySelectorAll('...'))
// ES5
[].slice.call(document.querySelectorAll('...'))
Anyway, I made a simplified working demo of your code.
Note that I save the currently active tab in a variable, to prevent another for loop. You could also do:
document.querySelector('.active').classList.remove('active')
But I like to reduce the amount of DOM reading.
Good luck for your apprentissage, re-writing some jQuery code into Vanilla JS seems like a good method, and you might acquire a far better comprehension of JavaScript.

How can I remove blank lines in an array generated that show in a dropdown

I have a dropdown that is generated using javascript and html. I have some code which I will post below that loops through this list and should potentially remove any blank lines found but is not working. "$maxfield1rows" has a value of 7, what I am saying is that if the value is a blank (=='') then remove it. I used removeChild but this doesn't seem to work, I also tried splice, I think filter can work but am not sure. I tried the disabled=true but that just makes them disabled and unselectable. Can someone please help?
for(index=1; index<$maxfield1rows; index++) {
if(document.pickDivision.field1.options[index].value=='') {
document.pickDivision.field1.removeChild(document.pickDivision.field1.options[index]);
}
}
Updated below, I'm using $maxfield1rows since that is the amount of maximum number of rows the the loop goes through, also by change I meant that there is an onchange event that gets triggered when the user selects a different option in the dropdown menu, so depending on the option selected the output for field1 changes, sometimes there are 5 values that show and sometimes just 1:
for(index=$maxfield1rows-1; index>=0; index--) {
alert(document.pickDivision.field1.options[index]);
if(document.pickDivision.field1.options[index].value==''){
document.pickDivision.field1.removeChild(document.pickDivision.field1.options[index]);
document.pickDivision.field1.options[index].disabled=true;
}
else{
document.pickDivision.field1.options[index].disabled=false;
}
}
To expand upon my comment, I think the issue is that you are looping forwards through the options array. I am surprised that no error is being thrown when you try to do this. You should loop backwards through the collection to keep from skipping an item.
JS Fiddle demonstrating the error. In the example, items 1 and 2 are blank and item 3 is not.
These built in collections are changed each time your add/remove an item. Using the example in the fiddle, my array changes from [1,2,3] to [2,3] to error no item at index 2.
Looping backwards, my collection takes this change: [1,2,3] to [1,3] to [3].
Here is the code with comments explaining what each part of the for loop is used for and why. You can use a while loop if you prefer too.
//options.length - 1 because arrays are 0 based
//i >= 0 because you don't want to use a negative index on an array
//i-- to loop backwards
for(var i = document.pickDivision.field1.options.length - 1; i >= 0; i--) {
//Is this an empty item
if(document.pickDivision.field1.options[i].value == '') {
//Remove the empty item
document.pickDivision.field1.removeChild(document.pickDivision.field1.options[i]);
}
}
UPDATE
With the newly updated code you posted, you are attempting to access an option element after it has been removed. From the looks of the line, you don't need it anymore as you are already removing the element. If you do still want to disable the element before removing it, move that line above the other line (see comment in code).
for(index=$maxfield1rows-1; index>=0; index--) {
alert(document.pickDivision.field1.options[index]);
if(document.pickDivision.field1.options[index].value==''){
document.pickDivision.field1.removeChild(document.pickDivision.field1.options[index]);
//This line is causing the issue; move it above the previous line or remove it
document.pickDivision.field1.options[index].disabled=true;
}
else{
document.pickDivision.field1.options[index].disabled=false;
}
}
UPDATE 2
Per the question in the comments, when you change the number of options to 6, your code breaks. This is because you are using the hard coded PHP value $maxfield1rows. Since you already know the name of the form and the name of the field in the form, I would recommend you use the length of the options collection in your for loop rather than this variable. This will make sure that no matter how many option elements there are (1, 10, 1000), you will always loop through the entire collection.
for(var i = document.pickDivision.field1.options.length - 1; i >= 0; i--) {

What is the fastest way to empty a range of elements from the DOM

My layout (simplified) is the following
<div class="row">
<div class="row_section">
<div class="row_section">
...
</div>
<div class="row">
<div class="row_section">
...
</div>
...
and I want to figure out the fastest way to empty all the divs that have class="row" in a certain interval (say from 0 to 59)
So far I've tried a lot of options:
$j('div.row').slice(nr_i, nr_s).empty();
surprisingly takes longer than
for(i = nr_i; i < nr_s; i++) {
$j('div.row:eq('+i+')').empty();
}
and in an example where there are .row_section divs only in the selected interval,
$j('div.row_section').remove();
is the fastest BUT it's still not fast enough. It takes twice as much to empty a section of 60 rows than it is to create a new one, almost 3 times as much actually.
Considering creating the rows involves more claculations than just adding a bunch of sections (the sections also have other stuff in them and there's a lot of sections per row) I would think creating them would take longer.
What's the fastest way of doing the original task, and why is it taking so much more to empty (with the fastest solution) than to create new ones?
I would also like to add that the row elements can go into the thousands yet there are never more than 60-80 non-empty rows, and row_sections go to the hundreds per row. Yes, I know it's impractical to do this in a web app but alas, it's what has to be done.
var elements = document.getElementsByClassName("row");
for(i=0; i<elements.length; i++)
{
elements[i].innerHTML = '';
}
According to the jQuery code, the slice method perform a native slice then call the jQuery.merge method. And the merge method does a loop on each element in the current jQuery collection.
So in your second example, you make just one loop. In your first example, you make a slice, then a loop for the merge, then a second loop to apply the empty method to all element in your collection.
I think the fastest solution is to use the navigator querySelectorAll method (jQuery use this method when you use a standard CSS selector). Try to construct a big CSS selector like div.row:eq(1), div.row:eq(2), div.row:eq(3) ... and use it to do this : $(selector).empty();
Why don't you try it:
function removeRow(fromV, toV){
$('div.row').each(function(i){
if(i >= fromV && <= toV){
$(this).remove();
}
});
}
Another try:
function removeRow(fromV, toV){
var i = fromV;
while(i <= toV){
$('div.row').eq(i).remove();
i++;
}
}
Another option, which doesn't incur extra query selector parses:
var divs = $j('div.row');
for(var i = nr_i; i < nr_s; i++) {
divs.eq(i).empty();
}

Algorithm design for a js loop structure

I got an array [1,2,3,4]
I need first to run over [2] that was chosen, set his styles, and than after run over the other children and reset their styles.
At the moment, my algorithm is as following:
for item in array
if item == chosenEl
set chosen classes
for item in array
if item != chosenEl
reset chosen classes for the rest of the children
Or coffe:
for item in #array
if item.element is element
item.selected = true
item.element.classList.add #selectedClass
item.element.focus()
for item in #array
if item.element isnt element
item.selected = false
item.element.classList.remove #selectedClass
I need to design the function like that, and not as a simple forElse loop, due to some restrictions in me framework.
How can I improve this code?
Thank you.
Explanation- The reason I need this design, is due to duplicated calls for 2 different functions, that conflict one the other.
I'm developing an app for LG TV. The LG TV has its own libraries. Now in my function I set the styles of chosen elements. Byt when I focus to the chosen element, I activate the LG TV onFocus listener, which in his turn control the selected styles.
So when I loop the second or third child, I set again the selected styles, of cleared elements. TLDR but thats how the loops conflicting each other. One interrupting the work of other.
The code was not written by me. I entered an existing project, and I havent written this function, so i cannot just remove the Focus() function.
Instead of looping twice, consider below code
for item in array
{
if item == chosenEl
{
set chosen classes
continue; <--use this to return to next iteration without executing next statements
}
reset chosen classes for the rest of the children
}
It will be O(n) instead of O(n) + O(n)
Edit:
I am sorry, I didn't understand your explanation. If all you need is a function which you can repeatedly call then here it is, call resetStyles multiple times by passing selected value. Please note since you haven't provided exact data types, I assumed them as integers.
<script>
var resetStyles = function(selecteditem)
{
var arr = [1,2,3,4];
for(var i=0; i < arr.length; i++)
{
if(arr[i] == selecteditem)
{
//set class here
continue;
}
//remove class here
}
};
resetStyles(2);//call this by changing the values
</script>
Why are you looping twice? Far easier to just do:
for( var i=0, l=array.length; i<l; i++) {
if( item == chosenEl) {
// set styles
}
else {
// clear styles
}
}

Categories

Resources