I am having problems with Javascript event handler functions created dynamically.
In my HTML I have the following:
<input id="nvctrlfont1" type=number min=18 max=144 step=1 value=36 maxlength="3" size="8">
<input id="nvctrlfont2" class="color" value="000000" style="background-color:black;width:70px;">
<input id="nvctrlfont4" class="color" value="000000" style="background-color:black;width:70px;">
.....
The class color is just a javascript color picker.
The element ID's have 1,2,4 ... etc as Boolean values. That is to say a JS function receives a Boolean that is used to hide or display the HTML input elements. For elements that are hidden no events are to be assigned, for those that are not hidden, events are to be assigned.
The Events functions would reside in the nvFontFuncs.
The problem I cannot seem to solve:
When the function assignment takes place to the nvEL.onchange it appears to replace it each time with the same function.
For example, given the Boolean nvFontBool comes in with say "6" then what should happen is the Input Elements nvcontrolfont2 and nvcontrolfont4 should be enabled and have the functions in nvFontFuncs nvctrlfont2 and 4 respective be assigned to the onchange events accordingly.
What happens however is nvctrlfont4 function ends up getting assigned to BOTH elements.
In other words, no matter which element that has had its onchange event set, when clicked it calls the LAST one assigned. In my example, nvctrlfont4() gets called no matter if I change INPUT nvctrlfont2 or nvctrlfont4 elements.
If I for example change the nvFontBool parameter to "7" thus enabling all three input elements. Then no matter which one is changed, it calls nvctrlfont4() (which happens to be the last one assigned in the loop).
I tried using addEvent and setAttributes as well, same results. Its as the onchange assignment of the function is static when bound. So which ever is the most "recent" one bound ends up replacing the prior bindings across all elements respective.
Help?
Heres the JS.
var nvFontFuncs = {
nvctrlfont1: function () { alert('Function 1'); },
nvctrlfont2: function () { alert('Function 2'); },
nvctrlfont4: function () { alert('Function 4'); }
};
Here is the Function
function nvSetFontCtrls(nvFontBool)
{
var nvEL;
var nvName="nvctrlfont";
var nvTemp;
nvFg=1;
while(nvFontBool != 0) {
//alert("FontBool:"+nvFontBool);
nvTemp=nvName+nvFg.toString();
// nvTemp="func"+nvFg.toString();
nvEL=document.getElementById(nvTemp);
if(nvFontBool & nvFg) {
nvEL.style.display="block";
nvEL.onchange=function() { nvFontFuncs[nvTemp](); }
}
else {
nvEL.style.display="none";
}
nvFontBool=nvFontBool&(~nvFg); //alert("nvfontbool:"+dechex(~nvFg));
nvFg = nvFg << 1;
}
}
You assign the following function:
function() { nvFontFuncs[nvTemp](); }
nvTemp is a variable in the local scope of nvSetFontCtrls. So when the handler is called, then, of course, nvTemp has the last value that it was set to.
You could do somthing like this:
nvEL.onchange=(function(nvTemp) {
return function() { nvFontFuncs[nvTemp](); }
}(nvTemp));
The problem is, that nvTemp is set to the last value of the loop, so once the handler function was called, nvTemp will have 'nvctrlfont4'.
If you use nvEL.onchange=nvFontFuncs[nvTemp] assignment, the problem will be solved
Related
I have this code, it's about disabling and enabling a button of a form according to its check box input. It's getting executed on different pages, you can see the last two lines where I loop through the forms on the page, all works fine when there is one from in the page, however, on pages where there is more than one form, it doesn't matter which check box I check (second or third for instance) it always affects the first button.
you can notice there are two "console.log" statements, the first one returns the correct input element (so when there are two forms in the pages, you can see in the console the elements reference the correct inputs on the DOM), however, the second one always shows an element in the console referring to the first input in the DOM.
my guess is that there is something wrong with the way I add the event listener, any suggestions for making that work?
class Steps {
constructor(el) {
this.el = el;
this.bindDOM();
this.bindStepEvents();
}
bindDOM() {
this.checkboxSelectorStep = this.el.querySelector('.radio-selector-steps-enable');
this.submit = this.el.querySelector('button[type=submit]');
}
bindStepEvents() {
console.log(this.checkboxSelectorStep); // returns the correct element
this.checkboxSelectorStep.addEventListener('click', (ev) => {
console.log(this.checkboxSelectorStep); // always returns the first element
if (ev.target.checked) {
this.submit.removeAttribute('disabled');
} else {
this.submit.setAttribute('disabled', '');
}
});
}
}
const forms = Array.from(document.querySelectorAll('.radio-selector-steps-form'));
forms.map(form => new Steps(form));
<form method="get" class="radio-selector-steps-form">
<input type="checkbox"
name="checkbox-selector-step"
id="checkbox-selector-step"
class="radio-selector-steps-enable">
<label for="checkbox-selector-step"><span>some text</span></label>
<button type="submit" disabled="">button text</button>
</form>
<form method="get" class="radio-selector-steps-form">
<input type="checkbox"
name="checkbox-selector-step"
id="checkbox-selector-step"
class="radio-selector-steps-enable">
<label for="checkbox-selector-step"><span>some text</span></label>
<button type="submit" disabled="">button text</button>
</form>
The problem was that both of the inputs had the same id, and the labels are referencing that id, so hitting the input itself works fine but whenever the label is being clicked the first listener gets called.
(due to styling, clicking the input was not possible for my case so I would always end up with the wrong behavior until I tried to create a SO snippet for it and caught the issue)
Hoping this might help someone.
You need to use querySelectorAll instead of querySelector
bindDOM() {
this.checkboxSelectorStep = this.el.querySelectorAll(STEPS_ENABLE_CHKBOX);
this.submit = this.el.querySelector(STEPS_FORM_SUBMIT);
}
bindStepEvents() {
console.log(this.checkboxSelectorStep);
this.checkboxSelectorStep.forEach(function(elem) {
elem.addEventListener('click', (ev) => {
console.log(this.checkboxSelectorStep);
if (ev.target.checked) {
this.submit.removeAttribute('disabled');
this.update(JSON.parse(this.steps[0].primaryInput.value))
} else {
this.submit.setAttribute('disabled', '');
}
});
});
I found this code (on HTML5), and there is nothing wrong with it, it work just fine, but i dont get the whole process it make(the code create a virtual keyboard that writes on a textfield the letters you click)
the part i dont get it:
var i,c;
function init(){
i=document.getElementById('keyboard').getElementsByTagName('input');
for(c=0;c<i.length;c++) {
if(i[c].type==='button') {
i[c].addEventListener('onclick',makeClickHandler(c));
}
}
My mains doubt are the i[c]
Thanks in advance
var i,c; // global var declaration usually not clever
function init(){ // he creates a js function
var keyboardelement=document.getElementById('keyboard');
var inputfield=keyboardelement.getElementsByTagName('input');
for( var c=0;c<inputfield.length;c++) {
if(inputfield[c].type==='button') { // he runs over the dom tree
// under keyboard to find his buttons...
// now he adds on each of the buttons a OWN click handler which
//calls the
// routine "makeClickHandler" -wrong wording should be known as
// ClickHandler or maybee not s below
inputfield[c].addEventListener('onclick',makeClickHandler(c));
}
}
that is what he made and what i call not that clever cause now he has to implement or make for each button own routine to handle this. Maybee that is why he chose the wording makeclickhanler cause h creates them dynamically.
This here do it all including the evnet handler a "nice" display and a bit action. It will display the text you click together.
<html><body>
<form name="keyboard">
//could also be dynamically created but...
<div id="out"><div>
<input type="button" value="a" class="kbdbutton">
<input type="button" value="b" class="kbdbutton">
<input type="button" value="c" class="kbdbutton">
<input type="button" value="d" class="kbdbutton">
</form>
usually i would add also a ID attribute but
<script>
// get all buttons in one call
var kbd_btn=documentGetElementsByClassName("kbdbutton")
for (var i=0; I< kbd_btn.length;i++){
kbd_btn[i].id = kbd_btn[i].value;
}
// NOW every key has its own *corresponding* id free of charge
// which also makes the button interesting for a global event handler
// act like a man and use ONE handler
document.addEventListener("click", function(event) {}
var id=event.target.id;
if (id){ // not all click events have a id
console.log(id);
display=document.getElementById("out")
switch(id) {
case "a":
alert("a clicked")
case default:
display.innerHTML=display.innerHTML+id;
}
}
)
</script>
</body></html>
if i would a just few lines more and use the evil eval function i would have a nice complete pocket calculator...
I am working on the project to create one variant of calculator. Basically, I am storing all the user clicks (which may be number and operator like +,-) in an array and handing the array to the another function when user clicks "=". I am using javascript for this. The below is my code:
var arr=[]; //array of every input from the user interface stored
var i=0; //number of input clicks from the user
var opPos=[]; //position of the operator as given by the user
var operAnd=[];//operands as given by the user
var disp='';
function clearval() {
// This function clears all the values stored in related array when C or CE is pressed
document.getElementById("op").value=0;
arr=[];
i=0;
opPos=[];
operAnd=[];
disp='';
}
//This Function get the value and supplies the array to calculating function
function getval(inp){
if(inp!="=")
{
arr[i]=inp;
disp=disp+inp;//for display in the screen of the output screen
document.getElementById("op").value=disp;
if (typeof inp!="number"){
opPos.push(i);
operAnd.push(inp);
}
i++;
}
else
{
var newInput=assembler(arr,opPos,operAnd);
clearval();
getval(parseFloat(newInput,10));
}
}
//<!------This Function calculates based on array, operator position and operands------!>
function assembler(num_array,opPos,operAnd){
var num='';
var numCollect=[];
var posCount=0;
for(var j=0;j<num_array.length;j++){
if (j==opPos[posCount]) {
numCollect.push(parseFloat(num,10));
num='';
j++;
posCount++;
}
else if (j>posCount) {
}
num=num+num_array[j];
}
num=parseFloat(num,10);
numCollect.push(num);
//document.getElementById("op").value= numCollect;
var newInput=calculator(numCollect,operAnd);
return newInput;
}
function calculator(target_num,operAnd) {
// Not the nice solution but straightforward nonetheless
var result='';
for (var l=0;l<operAnd.length;l++) {
result=result+target_num[l]+operAnd[l];
}
result=result+target_num[l];
document.getElementById("op").value=result + '=' + eval(result) ;
return eval(result);
}
I have html which has buttons like this:
<button class="num" onclick="getval(0)">0</button>
<button class="num" onclick="getval(1)">1</button>
<button class="num" onclick="getval(2)" >2</button>
<button class="num" onclick="getval(3)" >3</button>
......................... and so on
For the basic math, this code works fine and is not a problem. However, here is my problem from where I have hard time on thinking how to implement this. Say for example, I have a following button like this.
<button class='extra' onclick="Regression()"> Find Regression </button>
Now, I will have regression function which will ask user to input the regression type (1-linear, 2-quadratic and so on...this is just an example).
function regression(){
clearval();
document.getElementById("op").value=" Enter the degree of regression:";
which is basically asking user to enter the number and click '=' to enter into the program.
Now you can see my dilemma. Anything user inputs will be firstly processed by getval() which will pass the array to another function when user clicks '=', which is not what I will want in this case. To be clear, for this kind of case which I will have many such as std. dev or some kind of functions like this, I want the keypad to behave as normal keypad and pass the value to another function without doing normal calculator stuff as it was supposed to do.
Am I thinking this straight? Any suggestions?
I think you could solve this by adding another function called passval(), which will contain much of the same logic as getval() in terms of parsing the input into a float, etc., but which doesn't ever push values onto your operand stack or call the assembler function. It simply returns the button pushed as a nice float value to the .extra function that called it.
Then, as part of the logic in your .extra functions like Regression(), you would initially swap the onclick function for all of your buttons from getval() to passval(). When the regression or other special function is complete, swap the buttons back to their default behavior.
Well, this is what I would do(if I undestood right):
Change you button layout to this:
<input type="button" class="num" value="0" /> Removing the onclick event
Always better to use input type="button" than <button>.
Create a function to bind events to the buttons:
function bindButtonEvent(func) {
var buttons; // Here some way to get the button in a collection from the DOM tree
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = function() { func(buttons[i]); }
}
}
With this you will got a function to change the click event of all your buttons. This will make your engine more flexible.
You will have to change your getval function a little to get the value by itself:
function getval(el){
var inp = el.value;
if(inp!="=")
So on the calculator load, you set the click function:
bindButtonEvent(getval())
When you want to call a custom behaviour function, you call the binding again:
function regression() {
bindButtonEvent(function(el) {
value = el.value;
// Do things
// When done, take bindings back.
bindButtonEvent(getval());
});
clearval();
document.getElementById("op").value=" Enter the degree of regression:";
}
NOTE: That is an ideia. I didn't tested the code. If you're interested on this and have errors on implementation, let me know, and we'll going fixing them.
I have a two set of input checkbox in one page. and I have added a "checked" function to each set and each has different functionality.
I will set an example,
<div id="set1">
for(var i=0;i<n;i++)
<input type="checkbox" class=filter[i] onclick="clickCheck(filter[i])">array values
}
</div>
<div id="set2">
(for var j=0;j<n;j++)
<input type="checkbox" name="facets" value=array[j]>array values
}
</div>
Ive used jquery functions like
$("#set1 :checkbox").click(checkFacetSelectionCount);
checkFacetSelectionCount()
{
$('#set1 :input[type=checkbox]:checked').each(function() {
alert("Checked");
}
and
clickCheck(s)
{
if ($("#set2").is(':checked'))
{
alert(s);
}
else
{
alert("Nothing Checked");
}
, These two functions get activated on click on checkbox, so what happens is that whenever I click on any of the set, both set of functions will get activated. How can I prevent this? How can I differently call these two functions?
I think that
first one should
$('#set1 :input[type=checkbox]:checked')
{function body;}
and another should
$('#set2 :input[type=checkbox]:checked')
^ 2 instead of 1
{function body;}
This works, with a simple button calling the addDamage function:
var damage=0;
function addDamage()
{
damage+=10;
document.getElementById("p1s").innerHTML=damage;
}
This doesn't:
var damage=0;
function addDamage(who)
{
who+=10;
document.getElementById("p1s").innerHTML=who;
}
With this button:
<button type="button" onclick="addDamage(damage)">Add</button>
It's probably obvious. I'm really new. Thanks!
You are adding 10 to who within the function. Via the parameter passed on invocation, who takes the value of damage which is a global variable.
the function uses the updated value of who to set the innerHTML of an element. all that works. Then the function exits. who goes out of scope. The updated value of who is now forgotten.
When you click the button again, it uses the value of damage, which is still its original value, 0 (zero). who gets that value again, then gets 10+, which is 10, and so on.
To update a global variable, return it from the function, and set it in the handler.
var damage=0;
function addDamage(d)
{
d+=10;
document.getElementById("p1s").innerHTML=d;
return d;
}
and
<button type="button" onclick="damage=addDamage(damage);">Add</button>
Cheeso has identified the basic problem, which is that JavaScript parameters are passed by value. To get the behavior you want, you can wrap your counter in an object:
var player1 = { damage: 0 };
function addDamage(who) {
who.damage+=10;
document.getElementById("p1s").innerHTML=who.damage;
}
Then your button would do this:
<button type="button" onclick="addDamage(player1)">Add</button>
Presumably you would have other properties for player1 that you could put in the object as well.
To make the addDamage more flexible, you could also pass a second parameter to tell where you want to display the results:
function addDamage(who, outputId) {
who.damage+=10;
document.getElementById(outputId).innerHTML=who.damage;
}
Then button looks like:
<button type="button" onclick="addDamage(player1, 'p1s')">Add</button>
var who=0; // want to use who not damage
function addDamage(who)
{
who+=10;
document.getElementById("p1s").innerHTML=who;
}
// also change me from damage to who
<button type="button" onclick="addDamage(who)">Add</button>
the nice alternative solution would be this
<button id="addDamage"> Add </button>
<div id="showDamage"> </div>
// add handler to button
document.getElementById("addDamage").addEventListener("click", (function() {
// damage is not stored in global space.
var damage = 0,
// div is only gotten once.
div = document.getElementById("showDamage");
// show functionality is in its own function so its easily changed
function show() {
div.textContent = damage;
}
// call show to show the initial damage
show();
// return event handler
return function() {
// change damage
damage+=10;
// show the new damage
show();
};
})());