Trying to add event handler to dynamic content using jQuery - javascript

I'm trying to add an onclick event handler to a list of checkboxes. Instead of alerting the checkbox ID that I would expect it to (the one I'm clicking on), it's always alerting checkbox number 3 regardless of which on I click. Why is this and how can I get this to behave as desired. When I click on checkbox 2, I want it to alert "2"... etc.
function component (componentId, displayType, parentId) {
this.componentId = componentId;
this.parentId = parentId;
this.displayType = displayType;
}
var components = [];
components.push(new component(0, null, null);
components.push(new component(1, null, null);
components.push(new component(2, null, null);
components.push(new component(3, null, null);
var arrayLength = components.length;
for (var i = 0; i < arrayLength; i++) {
var component = components[i];
jQuery('#questionnaireComponentId'+component.componentId).find('input:checkbox').click(
function(){
selectParentCheckbox(component.componentId);
}
);
}
function selectParentCheckbox(componentId){
alert(componentId);
}

This is happening because of the Javascript closures.
You should pass the parameter each iteration, and not only after finishing the iteration.
for (var i = 0; i < arrayLength; i++) {
var component = components[i];
jQuery('#questionnaireComponentId' + component.componentId)
.find('input:checkbox')
.click(selectParentCheckbox.bind(null, component.componentId));
}
The bind function allows you to name what the function the click event will be calling and what parameters should be passed to it.
Nothing to do with the question, but only for your information: I think you're misunderstanding the power of jQuery and its event delegation, since you could use a much better aproach at all.

Related

JavaScript addEventListener with Event and Binding a variable

I'm busy trying to dynamically assign functions to certain buttons and I've run into a strange problem that I'm absolutely stumped with.
I have the following simple HTML for demonstration purposes
<div id="butts">
<button>BUTT 01</button>
<button>BUTT 02</button>
</div>
Now I am assigning functions to these buttons using JavaScript with the following loop (including the event)
var butts = $("#butts").find("button");
for(var cnt = 0; cnt < butts.length; cnt ++) {
// get button description just for testing
var buttonDesc = $(butts[cnt]).text();
// EVENT
butts[cnt].addEventListener (
"click", function(event) {
funcEvent(event)
}
);
}
Calling a very simple test function to verify that it is working
function funcEvent(event) {
console.log("funcEvent");
console.log(event);
}
This is working fine but I also need to pass a variable to the function which I would normally do as follows
var butts = $("#butts").find("button");
for(var cnt = 0; cnt < butts.length; cnt ++) {
// get button description just for testing
var buttonDesc = $(butts[cnt]).text();
// BIND
butts[cnt].addEventListener (
"click", funcBind.bind(this, buttonDesc)
);
}
Another very simple test function
function funcBind(buttonDesc) {
console.log("funcBind");
console.log(buttonDesc);
}
Separately they both work just fine but I am struggling to pass the event argument in the bind function
I am trying to combine the two so that I can call a single function that can receive both the event and the argument
UPDATE
This seems to be a possible fix although I do not understand how to be honest
With the same loop
var butts = $("#butts").find("button");
for(var cnt = 0; cnt < butts.length; cnt ++) {
// get button description just for testing
// using var did not work (always last element of array)
// var buttonDesc = $(butts[cnt]).text();
let buttonDesc = $(butts[cnt]).text();
// EVENT
butts[cnt].addEventListener (
"click", function(event) {
funcEventBind(event, buttonDesc);
}
);
}
Calling a very simple test function to verify that it is working
function funcBindEvent(event, buttonDesc) {
console.log("funcEvent");
console.log(event);
console.log(buttonDesc);
}
You need to create a closure so that the even handler callback contain the context. You can do that by using forEach like this
butts.forEach(function(bt) {
var buttonDesc = $(bt).text();
// BIND
bt.addEventListener (
"click", function(event){
funcBind(event, buttonDesc)
}
);
})

Trigger event for each object

I want website to change class of an element if it is filled. So, when user blurs out of an input field the program checks if it has any value and if yes adds a class. The problem is to pass this behaviour to each element in class' collection.
var input = document.getElementsByClassName('input');
contentCheck = function(i){
if(input[i].value>0) input[i].classList.add('filled');
else input[i].classList.remove('filled');
};
for(var i=0; i<input.length; i++) {
input[i].addEventListener('blur',contentCheck(i));
}
This works once after reloading the page (if there's any content in cache), but contentCheck() should trigger each time you leave the focus.
You've half-applied the "closures in loops" solution to that code, but you don't need the closures in loops solution, just use this within contentCheck and assign it as the handler (rather than calling it and using its return value as the handler):
var input = document.getElementsByClassName('input');
var contentCheck = function(){ // <== No `i` argument (and note the `var`)
// Use `this` here
if(this.value>0) this.classList.add('filled');
else this.classList.remove('filled');
};
for(var i=0; i<input.length; i++) {
input[i].addEventListener('blur',contentCheck);
// No () here -------------------------------------^
}
Side note: classList has a handy toggle function that takes an optional flag:
var contentCheck = function(){
this.classList.toggle('filled', this.value > 0);
};
If you needed the "closures in loops" solution (again, you don't, but just for completeness), you'd have contentCheck return a function that did the check:
var input = document.getElementsByClassName('input');
var makeContentCheckHandler = function(i){
return function() {
if(input[i].value>0) input[i].classList.add('filled');
else input[i].classList.remove('filled');
};
};
for(var i=0; i<input.length; i++) {
input[i].addEventListener('blur', makeContentCheckHandler(i));
}
Note I changed the name for clarity about what it does.
Try to use anonymous function
input[i].addEventListener('blur',function(e){
console.log(e);
});
Example: https://jsfiddle.net/42etb4st/4/

Select tag onchange not working on mobile

I'm setting an onchange event on select tags like this:
Dictionary.prototype.setSelectEvent = function setSelectEvent() {
var selects = document.querySelectorAll('.dictionary-page__select');
if (selects) {
for (var i = 0; i < selects.length; i++) {
selects[i].onchange = function() {
this.onChange();
}.bind(this);
}
}
};
This works fine on the desktop. The event is properly set and fired with no errors. On mobile, the event is not being fired at all. I tried with onblur as well, but that did nothing. Using vanilla javascript, how can I set an event on a select tag and have it work on mobile?
Solution:
Third party library was overriding desktop events, but not mobile ones. Had to pass the select object in the event.
Dictionary.prototype.setSelectEvent = function setSelectEvent() {
var selects = document.querySelectorAll('.dictionary-page__select');
var that = this;
if (selects) {
for (var i = 0; i < selects.length; i++) {
selects[i].onchange = function() {
that.onChange(this);
};
}
}
};
Dictionary.prototype.setSelectEvent = function setSelectEvent() {
var selects = document.querySelectorAll('.dictionary-page__select');
for (var i = 0; i < selects.length; i++) {
selects[i].onchange = this.onChange.bind(this);
}
};
The preceding code should do what you're looking for, integrates over the NodeList it until selects.length. Also you'll note you can directly bind to this, we don't need to define a new onChange function each time that calls an onChange method on the Dictionary.
Edit: removed the conversion to an array.

Get the specific div id from an onclick event (Pure JS no Jquery)

When I try the code referenced in SO #1, I get the console logging a blank string:
installChoices() {
var choices = this.game.page.options;
for (var i = 0; i < choices.length; i++) {
var choice = choices[i];
var choiceDiv = document.createElement("choice" + i);
choiceDiv.innerText = choice[0];
choiceDiv.onclick = function() {
console.log(this.id);
}
this.choicesContainer.appendChild(choiceDiv);
}
}
I want to bind to my class function clicked
installChoices() {
var choices = this.game.page.options;
for (var i = 0; i < choices.length; i++) {
var choice = choices[i];
var choiceDiv = document.createElement("choice" + i);
choiceDiv.innerText = choice[0];
choiceDiv.onclick = this.clicked;
this.choicesContainer.appendChild(choiceDiv);
}
}
clicked(e) {
console.log(e.parentNode); //this is undefined
console.log(e.srcElement);
}
But that shows undefined. When I log srcElement, I get the full element
<choice0>path 1</choice0>
I want to get just the div id when I click, so I can parse that and do logic.
I'd recommend the following approach, as it is the standard:
//assign the event
choiceDiv.addEventListener('click', clicked)
//define the listener
function clicked(event) {
console.log(event.currentTarget)
}
update:
I'm tempted to offer a fix to your code, because I don't think you're achieving what are you trying to actually do:
function installChoices() {
var choices = this.game.page.options;
for (var i = 0; i < choices.length; i++) {
var choice = choices[i];
var choiceDiv = document.createElement("div");
choiceDiv.id = "choice" + i;
choiceDiv.innerText = choice[0];
choiceDiv.addEventListener("click", clicked);
this.choicesContainer.appendChild(choiceDiv);
}
}
function clicked(ev) {
console.log(ev.currentTarget.id); //this will log "choice0"
}
Your "clicked" function are receiving an Event rather than a HTML Element. This is the default behavior when an onClick event triggered.
The click event have a srcElement property, indicating the source element the click event occurred upon. This event object have no parentNode property.
Use e.srcElement.parentNode instead.
BTW, in the SO #1 example, it assign "showIt(this)" to onClick, so browser pass "this", the target element rather than the event object, to the onClick function.

java script last iteration set the value for all iterations

var myElements = document.getElementsByName('bb1');
for (var i = 0; i < myElements.length; i++) {
var curValue = myElements[i].getAttribute('innerId')
myElements[i].addEventListener('mouseover', function () {
alert('Hello i am : ' + curValue);
}, false);
}
when mouse over, every element, instead of showing a different value for curValue, a constant value (the last iteration value) is displayed.
what am i doing wrong here?
There is no different scope inside blocks like for in JavaScript, so when your mouseover event is triggered, it will alert the current variable value which was set in the last iteration.
You can just use this inside your callback function to get the attribute of the object which the event was triggered.
var myElements = document.getElementsByName('bb1');
for (var i = 0; i < myElements.length; i++) {
myElements[i].addEventListener('mouseover', function () {
alert('Hello i am : ' + this.getAttribute('innerId'));
}, false);
}
The general issue here is the closure in Javascript. This happens when using variable (in this case curValue) not defined within the callback function.
I recommend reading answers about JS closures here.

Categories

Resources