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
}
}
Related
I am a begginer programmer and I am working on my first small project. I decided to make a notes app. It is basically completed, except that I wanted to have a button to delete all completed task and I am having problems with it. Basically, it works on localStorage, where I store the task name as a key, wit ha value of either 0(incomplete) or 1(completed). In this last function, I want to delete all the tasks with localStorage value of 1. This is my code. Once I run it, I am having problems with deleting all of the li elements. some get deleted, some not. In the console, I see two errors of tableChild not being declared. It seems weird.
document.querySelector(".btn-delete-completed").addEventListener("click", function () {
let children = listOfTasks.children; /*takes an ul element (listOfTasks) and selects its child elements*/
let arrayLenght = children.length; /*variable to see the lenght of the created arrray*/
let tableChild = 0; /*creates a variable that will be redeclared in the loop dynamically*/
for (let i = 0; i < arrayLenght; i++) { /*for loop that takes a stative arrayLenght instead of dynamic one(since we will be deleting elements)*/
tableChild = children[i]; /*redeclares a tableChild variable to a single li with the text that is a key to localStorage*/
if (localStorage.getItem(tableChild.textContent) == 1) {/*Checks for the value in localStorage, getItem() uses the key that is text of the selected element*/
tableChild.remove();/*removes the element from html*/
localStorage.removeItem(tableChild.textContent); /*removes element from localStorage*/
}
}
});
Here it is. I hope the documentation is good enough. As I said, I am just a begginer and I am posting here first time. If you have any questions, I will gladly answer them in the comments! Once again sorry if I messed something up in this post.
Thanks in advance!
When you delete elements from an array, its length changes. This is not accounted for in your code.
I find it easiest to iterate backwards in that case:
for (let i = children.length - 1; i >= 0; i--){
//...
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);
}
});
This has been eating me away. Check CodePen here. I have a function that adds elements to a ruler. Call expose.init(lengthOfPeriod) with an integer to set the length of each period in the ruler. Call expose.addAction(time, team) with an integer and a string == 'HOME' || 'AWAY' to place an action in the ruler.
When the screen is resized, I want something to happen to the elements in the ruler that touch each other (basically collapse into a group).
I have a function 'detectOverlap' that takes 2 params and determines if they are touching or not. I also have a function in the resize event handler that populates an array 'allTouching' with all the elements in the ruler that are touching each other at each resize.
if (actionArray.length > 1) { //don't run if there's only one element in the page
actionArray.reduce(function (prev, next) {
if (detectOverlap(prev, next)) {
if (allTouching.indexOf(prev, next) === -1) allTouching.push(prev, next);
}
return next;
});
If actions are touching each other, I need them to collapse into groups. In order to do this, I need to create an array for each group of actions touching each other. However, I haven't been able to make this happen so far.
Here's some pseudo code:
for (i = 0; i < allTouching.length; i++) {
if (detectOverlap(allTouching[0], alltouching) {
touchingGroup[i] = new Array(allTouching[0], all other elements touched by 0);
do not include any element more than once in touchingGroup[i];
do not include any similar arrays (same elements) in allGroups;
allGroups.push(touchingGroup[i]);
}
}
In short, this would need to loop for all the elements in the allTouching array, and create a new touchingGroup[n] for each new group of actions that touch each other.
This sounds simple in my head, and I'm sure there must be a way to do it without code getting overly complex, but I haven't found it yet.
Any feedback would be appreciated.
It seems your question is only about the grouping, so I will ignore the visualisation aspect and assume that the function detectOverlap is correct.
Then you could create the groups in one for loop. In this snippet I have added simplistic sample data and a mock detectOverlap function that will return true when its two arguments are the same (just for the purpose of the snippet):
// Simplistic mock data and function, just to make the snippet work
var actionArray = [1, 1, 3, 3, 3, 8, 9];
function detectOverlap(a, b) { return a === b; }
// Actual code:
var allGroups = [];
var start = 0;
for (var i = 1; i <= actionArray.length; i++) {
if (i >= actionArray.length || !detectOverlap(actionArray[i-1], actionArray[i])) {
if (i - start > 1) // Remove this condition if you want singletons as well
allGroups.push(actionArray.slice(start, i));
start = i;
}
}
console.log(JSON.stringify(allGroups));
Explanation
The variable start is used as an index in the array, from where the most recently found group should start. That group is not yet actually created, since we do not know where it ends, and so I will call this the "undecided" group. start is initialised at 0 to indicate that the first group will start there.
The loop iterates over the array, but starting at 1. In each iteration it decides whether the "undecided" group (started at start) is complete. The group is considered complete when there is no overlap between the previous and the current element of the array. In that case the previous element is the last element of the "undecided" group. The elements for that group are copied from the array with slice. Note that the second argument of slice is the index of the first element that should not be part of the group. Now that group is stored, and start is put at the current index, where the next (and only) "undecided" group should start.
But as long as the two elements do overlap, no new group should be created (that is why the condition has a !). Instead start remains unchanged, and so this "undecided", "unclosed" group is getting bigger in size.
There is an if just before that slice, which prevents the creation of groups that only contain one element. If you remove that if, then also single elements will be isolated in their own "singleton" groups.
The loop will go up to and including arrayAction.length: this is unusual, since that makes the last i an invalid index. But it is useful, since in that case we still want to finish up the last group that is still "ongoing". So in that case i >= arrayAction.length will be true, and so the detectOverlap function will not be called (because the if condition is already known to be true). The if block will be entered and the final group will be created.
This question is related to one I asked previously at Using Mouseenter / MouseLeave to Change Div in JavaScript.
As answered, I had an index closure problem. I had a new function use an existing index, and the answer provided made perfect sense - except for one part.
After this:
for (var i = 0; i < radialDivList.length; i++) {
if (!radialDivList[i]) continue; //skip null, undefined and non-existent elements
if (!radialDivList.hasOwnProperty(i)) continue; //skip inherited properties
smallID[i] = radialDivList[i].id; //assign the list of four divs to the smallID array;
largeID[i] = smallID[i] + 'Full'; // add "Full" to each smallID element to make a largeID element
}
I had
$('#' + smallID[i]).mouseenter(function () { //works for all four radial menu divs when mouse enters
alert(largeID[i]); // undefined
alert(largeID); // gives expected output of full array
I now understand why the largeID index is undefined, but I don't understand why the mouseenter function works for all four radial menu items. My understanding of index closure makes me think that the value of [i] for the smallID[i].mouseenter function will be "3", given the result of the previous for loop. If so, why do all four menu sections trigger a mouseenter event properly?
Thanks for any help you are willing to provide!
#programmerGuy - care to further your explanation from the previous question?
This seems to be related to a browser caching issue. After a reboot, this code no longer triggers a mouseenter event.
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--) {