I have two functions that display and hide elements on a page. I'm trying to make the code more efficient so if elements get added to the page I don't have to update every line. This is how the code is now:
function myOpenFunction() {
var elem1 = document.getElementById("div1");
if (elem1) { document.getElementById("div1").style.display = "block";}
var elem2 = document.getElementById("div2");
if (elem2) { document.getElementById("div2").style.display = "block";}
var elem3 = document.getElementById("div3");
if (elem3) { document.getElementById("div3").style.display = "table";}
}
function myCloseFunction() {
var elem1 = document.getElementById("div1");
if (elem1) { document.getElementById("div1").style.display = "none";}
var elem2 = document.getElementById("div2");
if (elem2) { document.getElementById("div2").style.display = "none";}
var elem3 = document.getElementById("div3");
if (elem3) { document.getElementById("div3").style.display = "none";}
}
I tried putting all of those variables into an array outside of the function so that I don't have to repeat the variables if I add new elements. However, it doesn't work.
var myArray = [
document.getElementById("div1"),
document.getElementById("div2"),
document.getElementById("div3")
];
function myOpenFunction() {
if (myArray[0]) { myArray[0].style.display = "block";}
if (myArray[1]) { myArray[1].style.display = "block";}
if (myArray[2]) { myArray[2].style.display = "table";}
}
function myCloseFunction() {
if (myArray[0]) { myArray[0].style.display = "none";}
if (myArray[1]) { myArray[1].style.display = "none";}
if (myArray[2]) { myArray[2].style.display = "none";}
}
Is there anyway to do this?
Your myOpenFunction and myCloseFunctions are checking whether the elements exist at the time the function is called, whereas your myArray is putting the elements into the array when the script runs - the logic is not the same. You might use an array of ids, and then when the open or close function is called, iterate over the array to call getElementById to extract each element, and if it exists, set its style:
const ids = ['div1', 'div2', 'div3'];
function myOpenFunction() {
ids.forEach((id, i) => {
const element = document.getElementById(id);
if (element) {
element.style.display =
i === 2
? 'block'
: 'table';
}
});
}
What you can do is use a counter and concatenate with your class names to push to your array. You just need to have all your divs with IDs of div1, div2, etc:
var myElements = document.getElementsByClassName("selectedDivs");
var myArray = [];
for (var i = 1; I <= myElements.length; i++) {
myArray.push("div" + i);
}
Then use this in your function:
if (document.getElementById(myArray[I]) {
This will fix your problem.
It would be better to give all items the same class and loop other them. If you need different display states for some elements, you could specify them inside an html data attribute.
const myElements = document.querySelectorAll('.open-close');
function myOpenFunction() {
myElements.forEach(elem => {
elem.style.display = elem.dataset.display || 'block';
});
}
function myCloseFunction() {
myElements.forEach(elem => {
elem.style.display = 'none';
});
}
<div class="open-close">...</div>
<div class="open-close">...</div>
<div class="open-close" data-display="table">...</div>
EDIT: Because I am not shure about your JavaScript experience, here is a maybe more simple version with more classic methods and syntax:
var myElements = document.getElementsByClassName('open-close');
function myOpenFunction() {
for (var i = 0; i < myElements.length; i++) {
var elem = myElements[i];
var displayAttr = elem.getAttribute('data-display');
if (displayAttr !== null) {
elem.style.display = displayAttr;
} else {
elem.style.display = 'block';
}
}
}
function myCloseFunction() {
for (var i = 0; i < myElements.length; i++) {
myElements[i].style.display = 'none';
}
}
Related
I'am building a page with a few procede and back button ,i need to add a multiple event listener on all button with same class, how i can achive?
I tried with querySelectorAll all, and called the methods inside handleNextStepButton () in a forEach but what would be the best method?
class Wizard {
constructor(obj) {
this.wizard = obj;
this.panels = new Panels(this.wizard);
this.steps = new Steps(this.wizard);
this.stepsQuantity = this.steps.getStepsQuantity();
this.currentStep = this.steps.currentStep;
this.concludeControlMoveStepMethod = this.steps.handleConcludeStep.bind(this.steps);
this.wizardConclusionMethod = this.handleWizardConclusion.bind(this);
}
updateButtonsStatus() {
if (this.currentStep === 0) this.previousControl.classList.add("disabled");
else this.previousControl.classList.remove("disabled");
}
updtadeCurrentStep(movement) {
this.currentStep += movement;
this.steps.setCurrentStep(this.currentStep);
this.panels.setCurrentStep(this.currentStep);
this.handleNextStepButton();
this.updateButtonsStatus();
// this.currentStep.classList.add("current");
}
handleNextStepButton() {
if (this.currentStep === this.stepsQuantity - 1) {
this.nextControl.innerHTML = "Conclude!";
this.nextControl.removeEventListener("click", this.nextControlMoveStepMethod);
this.nextControl.addEventListener("click", this.concludeControlMoveStepMethod);
this.nextControl.addEventListener("click", this.wizardConclusionMethod);
} else {
this.nextControl.innerHTML = "Next";
this.nextControl.addEventListener("click", this.nextControlMoveStepMethod);
this.nextControl.removeEventListener("click", this.concludeControlMoveStepMethod);
this.nextControl.removeEventListener("click", this.wizardConclusionMethod);
}
}
addControls(previousControl, nextControl) {
this.previousControl = previousControl;
this.nextControl = nextControl;
this.previousControlMoveStepMethod = this.moveStep.bind(this, -1);
this.nextControlMoveStepMethod = this.moveStep.bind(this, 1);
previousControl.addEventListener("click", this.previousControlMoveStepMethod);
nextControl.addEventListener("click", this.nextControlMoveStepMethod);
this.updateButtonsStatus();
}
}
let wizardElement = document.getElementById("wizard");
let wizard = new Wizard(wizardElement);
let buttonNext = document.querySelector(".next");
let buttonPrevious = document.querySelector(".previous");
wizard.addControls(buttonPrevious, buttonNext);
Just have in mind that you'll get a list of elements. So you just need to iterate throught the list of elements and append your listerner:
let buttons = document.querySelectorAll(".next");
for(let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener("click", yourFunction);
}
The following is javascript code, the purpose is simulating jquery nextAll() function. Now the problem is that when I use function as argument, but it cannot see aLists variable in _nextAll() function.
function _nextAll(func) {
var aLists = document.getElementsByTagName("*");
var i = 0;
var temp = [];
while (i < aLists.length) {
if (func) { //if aLists[i].id = "one" replace func, this work
var j = i;
while (j < aLists.length) {
temp.push(aLists[j + 1]);
j++;
}
}
i++;
}
return temp;
}
var temp = _nextAll(function(){
if (aLists[i].id = "one"){ //aLists[i] cannot be seen in anonymous function
return true;
}
});
for (i = 0; i < temp.length; i++) {
temp[i].style.color = "orange";
}
You should pass the selector to _nextAll, have the function select the first element (or every element) matching the selector, then iterate over each element that comes after it:
function _nextAll(sel) {
let el = document.querySelector(sel);
const elements = [];
while (el = el.nextElementSibling) {
elements.push(el);
}
return elements;
}
for (const el of _nextAll('#one')) {
el.style.color = "orange";
}
<div>a</div>
<div>b</div>
<div id="one">c</div>
<div>d</div>
<div>e</div>
you have options to make the two functions see the same variable
1- you can define the variables outside the two functions and you can access the same from the two function
2-pass all the variable you want to share to the function parameters
I'm working on a simon game and is doing a sequence of 3 at level 2 instead of doing just 2 at level 2. I've looked all over. and I've trying output to console, but I guess I've been staring at this for too long. If someone can find the bug, please share. thanks for the help.
here's the pen
https://codepen.io/zentech/pen/XaYygR
//variables
userSeq = [];
simonSeq = [];
const NUM_OF_LEVELS = 5;
var id, color, level = 0;
var strict = false;
var error = false;
var boardSound = [
"http://www.soundjay.com/button/sounds/button-4.mp3", //green
"http://www.soundjay.com/button/sounds/button-09.mp3", //red
"http://www.soundjay.com/button/sounds/button-10.mp3", //yellow
"http://www.soundjay.com/button/sounds/button-7.mp3" //blue
];
//1- start board sequence
$(document).ready(function() {
$(".start").click(function() {
strict = false;
error = false;
level++;
simonSeq = userSeq = [];
simonSequence();
})
//user pad listener
$(".pad").click(function() {
id = $(this).attr("id");
color = $(this).attr("class").split(" ")[1];
userSequence();
});
//strict mode listener
$(".strict").click(function() {
level = 0;
level++;
simonSeq = userSeq = [];
strict = true;
simonSequence();
})
})
//user sequence
function userSequence() {
userSeq.push(id);
console.log(id+" "+color);
addClassSound(id, color);
//check user sequence
if(!checkUserSeq()) {
//if playing strict mode reset everything lol
if(strict) {
console.log("strict");
simonSeq = [];
level = 1;
}
displayError();
userSeq = [];
error = true;
console.log("start simon error")
simonSequence();
}
//checking end of sequence
else if(userSeq.length == simonSeq.length && userSeq.length < NUM_OF_LEVELS) {
level++;
userSeq = [];
error = false;
console.log("start simon")
simonSequence();
}
//checking for winners
if(userSeq.length == NUM_OF_LEVELS) {
displayWinner();
resetGame();
}
}
/* simon sequence */
function simonSequence() {
console.log("level "+level);
$(".display").text(level);
if(!error) {
getRandomNum();
}
var i = 0;
var myInterval = setInterval(function() {
id = simonSeq[i];
color = $("#"+id).attr("class");
color = color.split(" ")[1];
console.log(id+" "+color);
addClassSound(id, color);
i++;
if(i == simonSeq.length) {
clearInterval(myInterval);
}
}, 1000);
}
//generate random number
function getRandomNum() {
var random = Math.floor(Math.random() * 4);
simonSeq.push(random);
}
/* add temporary class and sound */
function addClassSound(id, color) {
$("#"+id).addClass(color+"-active");
playSound(id)
setTimeout(function(){
$("#"+id).removeClass(color+"-active");
}, 500);
}
/* checking user seq against simon's */
function checkUserSeq() {
for(var i = 0; i < userSeq.length; i++) {
if(userSeq[i] != simonSeq[i]) {
return false;
}
}
return true;
}
/* display error */
function displayError() {
console.log("error");
var counter = 0;
var myError = setInterval(function() {
$(".display").text("Err");
counter++;
if(counter == 3) {
$(".display").text(level);
clearInterval(myError);
userSeq = [];
counter = 0;
}
}, 500);
}
//display winner
function displayWinner() {
var count = 0;
var winInterval = setInterval(function() {
count++;
$(".display").text("Win");
if(count == 5) {
clearInterval(winInterval);
$(".display").text("00");
count = 0;
}
}, 500);
}
/* play board sound */
function playSound(id) {
var sound = new Audio(boardSound[id]);
sound.play();
}
/* reset game */
function resetGame() {
userSeq = [];
simonSeq = [];
level = 0;
strict = false;
$(".display").text("00");
}
PROBLEM
You have a reference vs copy problem in your initialization code.
$(document).ready(function() {
$(".start").click(function() {
strict = false;
error = false;
level++;
simonSeq = userSeq = []; //PROBLEM !!!!
simonSequence();
})
Arrays are passed by reference, not value.
simonSeq = userSeq = [];
/* Any changes to 'userSeq' will affect 'simonSeq'.
'simonSeq' is referencing 'userSeq' */
SOLUTION
Change all instances of
simonSeq = userSeq = [];
To
simonSeq = [];
userSeq = [];
EXPLINATION
Values in JavaScript can be referred to in 2 ways; by reference and by value.
When you refer to something by value, you are copying it.
var numA = 5;
var numB = numA; //COPY numA over to numB
numA = 12; // Changes to numA will not affect numB because it was copied
console.log(numA); // 12
console.log(numB); // 5
When you refer to something by reference, your are referring/referencing it, not copying it. Any changes made to the original will affect everything that is referencing it.
var original = [1,2,3];
var ref = original; //Any changes made to 'original' will affect 'ref'
original.push('APPLES');
console.log(original); // [1,2,3,'APPLES']
console.log(ref); // [1,2,3,'APPLES']
In the above code ref does not actually contain any values. ref contains the memory location of original.
ref is referencing original.
Arrays and Objects are always passed/refereed to by reference.
Everything else is passed/refereed to by value (they are copied).
I'm working on a form that will have a "Validate" button. The purpose of this button is to check and make sure all the fields are completed (this is what the project demands). Below is the code that checks to see if the field is null and then changes the border color and displays a text box.
if (form1.Main.sfRequestor.requestNameFirst.rawValue == null){
form1.Main.sfRequest.txtValidate.presence = "visible";
form1.Main.sfRequestor.requestNameFirst.border.edge.color.value = "255,0,0"
} else {
form1.Main.sfRequest.txtValidate.presence = "hidden";
form1.Main.sfRequestor.requestNameFirst.border.edge.color.value = "255,255,255"
};
if (form1.Main.sfRequestor.requestNameLast.rawValue == null){
form1.Main.sfRequest.txtValidate.presence = "visible";
form1.Main.sfRequestor.requestNameLast.border.edge.color.value = "255,0,0"
} else {
form1.Main.sfRequest.txtValidate.presence = "hidden";
form1.Main.sfRequestor.requestNameLast.border.edge.color.value = "255,255,255"
};
There are 20+ fields in several subforms that need to be checked. I'm trying to consolidate the code but am at a loss on how to do so. Can variables handle field names in Javascript?
You can make this into a loop pretty easily, and could write it as an IIFE to keep your namespace clean
(function (arr) {
var txtValidate = form1.Main.sfRequest.txtValidate,
i, e;
for (i = 0; i < arr.length; ++i) {
e = form1.Main.sfRequestor[arr[i]]; // cache me
if (e.rawValue == null){
txtValidate.presence = "visible";
e.border.edge.color.value = "255,0,0"
} else {
txtValidate.presence = "hidden";
e.border.edge.color.value = "255,255,255"
}
}
}(['requestNameFirst', 'requestNameLast']));
However, it looks like txtValidate.presence only ever gets set to whatever the last item's conditions were, are you sure you don't want to use a flag and set this last instead? e.g.
(function (arr) {
var txtValidateState = 'hidden',
i, e;
for (i = 0; i < arr.length; ++i) {
e = form1.Main.sfRequestor[arr[i]];
if (e.rawValue == null){
txtValidateState = "visible"; // any null makes txtValidate visible
e.border.edge.color.value = "255,0,0"
} else {
e.border.edge.color.value = "255,255,255"
}
}
form1.Main.sfRequest.txtValidate.presence = txtValidateState; // set last
}(['requestNameFirst', 'requestNameLast']));
Update for generic forms, assuming sfRequestor and sfRequest
(function (form, arr) {
var txtValidateState = 'hidden',
i, e;
for (i = 0; i < arr.length; ++i) {
e = form.sfRequestor[arr[i]];
if (e.rawValue == null){
txtValidateState = "visible";
e.border.edge.color.value = "255,0,0";
} else {
e.border.edge.color.value = "255,255,255";
}
}
form.sfRequest.txtValidate.presence = txtValidateState;
}(form1.Main, ['requestNameFirst', 'requestNameLast']));
Update Assuming sfRequest is a constant but sfRequestor could be something different
(function () { // moved IIFE to protect namespace
function validate(form, subform, arr) { // now named, new param subform
var txtValidateState = 'hidden',
i, e;
for (i = 0; i < arr.length; ++i) {
e = form[subform][arr[i]]; // select from subform
if (e.rawValue == null){
txtValidateState = "visible";
e.border.edge.color.value = "255,0,0";
} else {
e.border.edge.color.value = "255,255,255";
}
}
form.sfRequest.txtValidate.presence = txtValidateState; // assuming stays same
}
validate(form1.Main, 'sfRequestor', ['requestNameFirst', 'requestNameLast']);
validate(form1.Main, 'sfClientInfo', ['firstname']);
// if you have many here you can re-write as a loop again
}());
You could easily make this into a simple function:
function validateField(element) {
if (element.rawValue == null) {
form1.Main.sfRequest.txtValidate.presence = "visible";
element.border.edge.color.calue = "255,0,0";
}
else {
form1.Main.sfRequest.txtValidate.presence = "hidden";
element.border.edge.color.calue = "255,255,255";
}
}
And then just call it like so:
validateField(form1.Main.sfRequestor.requestNameFirst);
validateField(form1.Main.sfRequestor.requestNameLast);
To simplify even further, put all 20 elements in an array and loop
var elements = [form1.Main.sfRequestor.requestNameFirst, form1.Main.sfRequestor.requestNameLast, ...];
elements.forEach(function(element) {
validateField(element);
});
I have this javascript snippet:
var selectName["id1","id2","id3"];
setOnClickSelect = function (prefix, selectName) {
for(var i=0; i<selectName.length; i++) {
var selId = selectName[i];
alert(selId);
$(selId).onchange = function() {
$(selId).value = $(selId).options[$(selId).selectedIndex].text;
}
}
}
But when I change value to my id1 element, the alert wrote me always "id3".
Can I fix it?
EDIT:
I've changed my snippet with these statements:
setOnChangeSelect = function (prefix, selectName) {
for(var i=0; i<selectName.length; i++) {
var selId = selectName[i];
$(selId).onchange = (function (thisId) {
return function() {
$(selId).value = $(thisId).options[$(thisId).selectedIndex].text;
}
})(selId);
}
}
But selId is always the last element.
This is caused by the behavior of javaScript Closure, selId has been set to the selectName[2] at the end of the loop and that's why you get 'id3' back.
An fix is as following, the key is wrap the callback function inside another function to create another closure.
var selectName = ["id1","id2","id3"];
var setOnClickSelect = function (prefix, selectName) {
for(var i = 0; i < selectName.length; i++) {
var selId = selectName[i];
$(selId).onchange = (function (thisId) {
return function() {
$(thisId).value = $(thisId).options[$(thisId).selectedIndex].text;
}
})(selId);
}
};
Ps: there is synyax error for var selectName["id1","id2","id3"], you should use var selectName = ["id1","id2","id3"];