I' trying to establish user options for checking all and none checkboxes. I use this code:
function selectToggle(toggle, form) {
var myForm = document.forms['cbx'];
for( var i=0; i < myForm.length; i++ ) {
if(toggle) {
myForm.elements[i].checked = "checked";
}
else {
myForm.elements[i].checked = "";
}
}
}
document.getElementById("all").addEventListener("click", selectToggle, true);
document.getElementById("none").addEventListener("click", selectToggle, false);
Here works only the option check all - check none doesn't work.
But, if i use instead of event listeners inline javascript like All | None both options work.
How can i force document.getElementById("none").addEventListener("click", selectToggle, false); to work?
That is because the third parameter in addEventListener is actually the useCapture option. You are not passing true or false into the arugment for the function selectToggle. To do that, you will need to call it using an anonymous function:
document.getElementById("all").addEventListener("click", () => selectToggle(true)));
document.getElementById("none").addEventListener("click", () => selectToggle(false)));
I have left out the second argument because it doesn't seem like you're using it in your method at all. However, if the second arugment is to be used as a form identifier, then you can do this:
document.getElementById("all").addEventListener("click", () => selectToggle(true, 'cbx')));
document.getElementById("none").addEventListener("click", () => selectToggle(false, 'cbx')));
Explanation of why your code went wrong: The reason why in your original code the check all function works, is because when you use selectToggle as the callback, like this:
document.getElementById('all').addEventListener('click', selectToggle);
...the first argument is actually the event object. Since it is an object that is passed to the first argument called toggle, toggle will always be truthy, hence you will always end up executing the logic inside the if (toggle) {...} block.
Related
Here is a link to my JS fiddle: https://jsfiddle.net/apasric4/v1qkmgyu/1/
function inputCheck(input) {
if (input.name==="email") {
console.log("email")
return isValidEmail
} else if (input.name==="password") {
return isValidPassword
console.log("pass")
} else if (input.name==="userName") {
return isValidUserName
console.log("user")
}
}
function isValidEmail (email) {
return /^[^#]+[#][^#.]+\.[a-z]+$/.test(email)
}
function isValidPassword(pass) {
return /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/.test(pass)
}
function isValidUserName(user) {
return /^[a-zA-Z0-9]+([_ -]?[a-zA-Z0-9])*$/.test(user)
}
function validation(e) {
e.preventDefault()
inputs.forEach(input=> createListener(inputCheck(input)))
}
function createListener(validator) {
return (e)=> {
const inputValue=e.target.value;
const valid=validator(inputValue)
console.log(valid)
}
}
I'm trying to create form validation using closures. I am trying to make my code as efficient as possible.
I want to loop over each input element (without selecting each individually), and apply an event listener to each one. The inputCheck function would return a validator function depending on the name attribute of each input, and the createListener function takes the value returned by inputCheck, which would be a specific type of validator, and then for testing purposes, console.log true or false.
So far, the only if branch that works in the inputCheck function is the first one associated with name attribute email. The other if branches won't work if I type values into other input elements and submit the form.
Can anyone tell me where I'm going wrong and how to improve my code?
I'm new to closures so I understand that this issue might seem relatively simple to most of you.
I can observe two things:
First, just like #VLAZ pointed out, two console.log in inputCheck are actually not executed since they are placed after return.
Second, createListener and validation are not quite right. createListener returns a function with one argument. validation forEach doesn't log anything because createListener returns a function, no function execution here.
There is another problem with the argument e of createListener. It seems like you treat it as an event, but based on your implementation, there is only one event, that is form submit event. So, I'd suggest to modify these two functions a little bit:
function validation(e) {
e.preventDefault()
inputs.forEach(input=> createListener(inputCheck(input))(input))
}
function createListener(validator) {
return (e)=> {
const inputValue=e.value;
const valid=validator(inputValue)
console.log(valid)
}
}
Then, the console prints out true or false based on the input value of each input field.
Please check whether the output is your intension or not https://jsfiddle.net/jqgbefhw/
I'm dynamically creating a table row full of checkboxes using JavaScript in the function populateParameterTr(parameters). In this function, I set an event handler such that when the checkbox is made active, a hidden input field is created in the toggleHidden function.
What I'm having trouble with is trying to call toggleHidden to create these hidden input elements after the checkbox is created, ie not in the event handler. Reason being: I want these hidden input fields when the checkboxes are loaded for the first time. From my understanding calling toggleHidden() results in this pointing to the global scope.
I've had a look at bind,call and apply to see if this will help like below:
toggleHidden(display, parameterContainer).bind(checkbox);
Can someone advise me if this can be done. I'm looking at this example as an exercise to get my head around using these methods.
Code:
var populateParameterTr = function(parameters) {
var checkboxes = $('<td></td>');
for (var i in parameters) {
var display = parameters[i].display;
var checkbox = $('<input>').attr({
//...
});
var parameterContainer = $('<div></div>').addClass('parameter').append(checkbox);
checkboxes.append(parameterContainer);
toggleHidden(display, parameterContainer).bind(checkbox);
checkbox.on('change', toggleHidden(display, parameterContainer));
}
}
var toggleHidden = function(display, tableDivide) {
return function() {
if (this.checked) {
var hidden = $('<input>').attr({
//......
});
hidden.appendTo(tableDivide);
} else {
$(this).siblings("input[type='hidden']").remove();
}
};
};
.call and .apply directly executes the function with first param is thisArg, the difference between them is .call accepts parme like .call(thisArg, param1, param2....) , but .apply accepts like .apply(thisArg, [param1, param2....]).
.bind returns a new Function, when it executes, it use the first param when you call .bind as thisArg, and if you gives other param, it'll put it directly to the origin function for you, e.g:
var func = function(param1, param2) {
console.log(param1, param2);
};
var bindFunc = func.bind(null, 1);
bindFunc(2); // log 1, 2 now
Take above knowledge to examine the code you post:
toggleHidden(display, parameterContainer).bind(checkbox);
It Seems you want to call it, and the this is reference to that checkbox, so you can write:
toggleHidden.call(checkbox, display, parameterContainer);
// or
toggleHidden.apply(checkbox, [display, parameterContainer]);
Then this part:
checkbox.on('change', toggleHidden(display, parameterContainer));
Here you want to call toggleHidden when checkbox change, as you're listen to checkbox's change event, and the input param should be pre-defined, you can write
checkbox.on('change', toggleHidden.bind(checkbox, display, parameterContainer));
You can get more info from js call() & apply() vs bind()?, mdn-function-bind, mdn-function-call.
I've been trying to understand why whenever value of the array I click, it always add the class "foo".
Example: I clicked on London (cities[1], right?) and it added the class foo.
var cities = [
document.getElementById('Paris'),
document.getElementById('London'),
document.getElementById('Berlin')
];
for (var i = 0; i < cities.length; i++) {
cities[i].onclick = test;
function test(){
if(cities[i] === cities[0]) {
el.classList.add("foo");
}
}
}
EDIT: my original answer was incorrect, this updated one is right. addEventListener returns nothing. Instead, you should use some kind of wrapper to add and remove your listeners, again so that you don't waste resources on listeners that you aren't using:
function on (element, eventName, callback) {
element.addEventListener(eventName, callback);
return function unregister () {
element.removeEventListener(callback);
}
}
function test (event) {
if (event.currentTarget===cities[0]) {
event.target.classList.add('foo');
}
}
var listenerRemovers = cities.map(function (city) {
return on(city, 'click', test);
});
Now you can remove any of these listeners by calling the corresponding function in your listenerRemovers array:
listenerRemovers.forEach(function (unRegisterFunc) { unRegisterFunc(); });
ORIGINAL WRONG ANSWER:
For what it's worth, you're probably better off using .map in a case like this, since best practice is to keep a reference to the event listeners so you can cancel them if needed.
function test (event) {
if (event.currentTarget===cities[0]) {
event.target.classList.add('foo');
}
}
var listenerHandlers = cities.map(function (city) {
return city.addEventListener('click', test);
});
This is happening because you are setting the event functions inside a loop. Each function is using the same value of i.
Try to use this instead of trying to cities[i] inside the function.
function test(){
if(this === cities[0]) {
el.classList.add("foo");
}
}
The easiest approach to achieve this functionality is to use jQuery, here is the idea:
In html tags, give those cities a common class, e.g. class="city"
$('.city').click(function(){$('.city').addClass('foo')});
jQuery saves you more time and coding efforts.
The problem is you are trying to assign a function to a DOM attribute. You are not registering a listener but modifying the DOM. If you wish to do it this way, you must assign the onclick as cities[i].onclick = 'test()'
Also, you should move the function test outside of the for loop to look like the following. The problem is the function test is being declared many times, each with a different 'i' value.
for (var i = 0; i < cities.length; i++) {
cities[i].onclick = 'test(this)';
}
function test(el){
if(cities[i] === cities[0]) {
el.classList.add("foo");
}
}
I have the following code which is designed to create an onchange event handler for all elements with class name 'State'. The only problem is that I want the element to be passed into the 'StateChange' function. How can I update this JS to pass 'this' into the StateChange function? Thanks!
var c = document.getElementsByClassName('State');
for (i = 0; i < c.length; i++) c[i].onchange = createEventHandler( StateChange, c[i] );
Edit: I forgot to provide the createEventHandler function. Sorry about that... Here it is:
function createEventHandler(fn, input) {
return function () {
fn(input);
};
}
Also, some clarification. The purpose of the function is to obviate the need to put the onchange event next to each element with class name = 'State'. The result should be the same as if I were to write:
<select id="MyState1" class="State" onchange="StateChange(this)">
Update:
Re your updated question: You've said that the end result you want is as though you'd done this:
<select id="MyState1" class="State" onchange="StateChange(this)">
Your quoted createEventHandler function does exactly that.
Original Answer(s):
I'm not entirely sure I know exactly what you're trying to do. I can read the question at least two ways:
Inside the StateChange function call, you want this to refer to the element that changed.
Inside the StateChange function call, you want this to be the same as this where you're setting up your event handler.
Option 1: You want this = element within StateChange
You don't actually have to pass the element instance into createEventHandler, because when the event occurs, this will refer to the element because of the way you're hooking it up. But if you prefer to set it explicitly, your createEventHandler function could look like this:
function createEventHandler(handler, element) {
return function(event) {
return handler.call(element, event || window.event);
};
}
What that does is return a function that, when the event is triggered, will call the function you pass in (StateChange) with this set to the element you pass in.. This uses the JavaScript call feature of function objects, which allows you to define what this will be during the function call. You just pass it in as the first argument to call (subsequent arguments are passed on to the function being called).
If you want to rely on the fact that the way you're setting up the handler, this will already be set to the element instance, you can do away with the element argument:
function createEventHandler(handler) {
return function(event) {
return handler.call(this, event || window.event);
};
}
That just passes along the this value set up for the event handler by the browser.
Option 2: You want this = this as of where you're setting up the handler
It's the same principle as the above, just with a different argument. In this case, you'll need to pass this in:
var c = document.getElementsByClassName('State');
for (i = 0; i < c.length; i++) c[i].onchange = createEventHandler( StateChange, this, c[i] );
...and then createEventHandler looks like this:
function createEventHandler(handler, thisArg, element) {
return function(event) {
return handler.call(thisArg, event || window.event, element);
};
}
(Note I've passed in the element as a second argument to StateChange.)
More reading:
Mythical methods
You must remember this
One way is:
var c = document.getElementsByClassName('State');
for (i = 0; i < c.length; i++)
c[i].onchange = createEventHandler(function(){
StateChange(c[i]);
});
I'm creating a button dynamically using JavaScript and at the same time assigning attributes such as 'ID', 'type' etc and also 'onclick' in order to trigger a function.
All works fine apart from the assignment of the 'onclick'. When clicked, the button is not triggering the function as it is supposed to. the function I'm trying to run is 'navigate(-1)' as seen below.
Where am I going wrong?
Here's my code:
function loadNavigation() {
var backButton;
backButton = document.createElement('input');
backButton.ID = 'backButton';
backButton.type = 'button';
backButton.value='Back';
backButton.onclick = 'navigate(-1)';
document.body.appendChild(backButton);
}
As the other said you should assign a function.
Just wanted to point out that in this case you want to pass a value so you need to assign an anonymous function (or a named function defined inline) like
button.onclick = function() {otherfunction(parameter)};
If the function you want to assign does NOT require a parameter you can use it directly
button.onclick = otherfunction;
Note that there is no parenthesis in this case
button.onclick = otherfunction(); // this doesn't work
won't work as it will call otherfunction as soon as it is parsed
you are assigning text to the onclick, try assigning a function.
backButton.onclick = function(){navigate(-1);};
You have to assign a function, not a string.
backButton.onclick = function wastefulDuplicationOfBackButton () {
navigate(-1);
}
Use a function instead of a string. For example,
backButton.onclick = function () { navigate(-1); };
You should assign a function, not a string:
//...
backButton.onclick = function () {
navigate(-1);
};
//...
backButton.onclick = function() { navigate(-1); }
In case this question is passed as a dupe, here is how to do it in current browsers
ES6
backButton.addEventListener("click",() => history.back());
Older but newer than onclick
backButton.addEventListener("click",function() { history.back() });