This is the prompt:
In the event listener DOMContentLoaded set up two addEventListener methods for each of the second input elements (using the array of inputElements[] and the corresponding index) and passing the first and the second
value (use inputElements[].value) and the appropriate span element (using spanElements[]). Since you need to pass arguments, use the following construct:
inputElements[index].addEventListener('blur',function(){ fCompareInput(arguments); })
This is the Function:
function fCompareinput(value1,value2,display)
This is what I have:
inputElements[2].addEventListener('blur', function() {
fCompareInput(inputElements[2].value);
});
inputElements[2].addEventListener('blur', function() {
fCompareInput(spanElements[2].value);
});
inputElements[4].addEventListener('blur', function() {
fCompareinput(inputElements[4].value);
});
inputElements[4].addEventListener('blur', function() {
fCompareinput(spanElements[4].value);
});
I don't think I'm passing the arguments correctly.
It's simply easier to go through your inputElements collection using a loop, and then bind the event listener. Also:
You can place multiple function calls in the blur callback
Use this in the callback to refer to the DOM node that has triggered the event
Let's say you only want the element with the index of 2 and 4 to have such events bound, then we can do this:
// Define which indexes you want to bind eventistener to
var indexes = [2,4];
// Loop through the entire collection
for (let i = 0; i < inputElements.length; i++) {
if (indexes.indexOf(i) !== -1) {
inputElements[i].addEventListener('blur', function() {
fCompareinput(this.value);
fCompareinput(spanElements[i].value);
});
}
}
Supplementary:
If you want to retrieve all even numbered element (by index) in the collection, use:
if (i % 2 === 0)
If you want to retrieve all even numbered elements and excluding the first element, use:
if (i % 2 === 0 && i !== 0)
For example:
// Loop through the entire collection
for (let i = 0; i < inputElements.length; i++) {
if (i % 2 === 0) {
inputElements[i].addEventListener('blur', function() {
fCompareinput(this.value);
fCompareinput(spanElements[i].value);
});
}
}
Since you have tagged your question with jQuery, there is also a jQuery-based solution: much simpler, but of course incurs an overhead of having to load a library.
var indexes = [2,4];
$(inputElements).each(function(i) {
if (indexes.indexOf(i) !== -1) {
$(this).on('blur', function() {
fCompareinput(this.value);
fCompareinput($(spanElements).get(i).val());
});
}
});
...or, if you want to select even numbered indexes only:
$(inputElements).each(function(i) {
if (i % 2 === 0) {
$(this).on('blur', function() {
fCompareinput(this.value);
fCompareinput($(spanElements).get(i).val());
});
}
});
Related
I'm trying to create a dynamic function using a for-loop in javascript, which will fire rollovers. I am using JS vs. CSS as the amount of images which will be rollovers is growing, and I figure a function is easier to maintain than x number of selectors.
This is creating a on method similar to jQuery.
var on = function(event, elem, callback, capture) {
if (typeof elem === 'function') {
capture = callback;
callback = elem;
elem = window;
}
capture = capture ? true : false;
elem = typeof elem === 'string' ? document.querySelector(elem) : elem;
if (!elem) return;
elem.addEventListener(event, callback, capture);
};
These are my rollOver and rollOut functions:
function rollOver(elem) {
document.getElementById(elem).src = `/images/home-page/desktop/EYES_ON_YOU_desktop_HP_HOVER_${elem.slice(length-1)}.jpg?$staticlink$`
}
function rollOut(elem) {
document.getElementById(elem).src = `/images/home-page/desktop/EYES_ON_YOU_desktop_HP_NO_HOVER_${elem.slice(length-1)}.jpg?$staticlink$`
}
And this is where my for-loop lives:
document.addEventListener("DOMContentLoaded", function(event) {
var rollOverCollectionA = document.getElementById('roll-over-collection-a').querySelectorAll('img');
rollOverCollectionA = Array.prototype.slice.apply(rollOverCollectionA);
for (var i = 0; i < rollOverCollectionA.length; i++) {
on('mouseover', rollOverCollectionA[i].id, function(){
console.log( rollOverCollectionA[i].id)
rollOver(rollOverCollectionA[i].id);
});
on('mouseout', rollOverCollectionA[i].id, function(){
rollOut(rollOverCollectionA[i].id);
});
}
});
The main problems I saw were:
elem.slice(length - 1); should be elem.slice(elem.length - 1) otherwise you're subtracting 1 from undefined
elem.slice should be replaced with elem.substr(elem.lastIndexOf('-') + 1) otherwise any images over 9 will start back at 0 because you would only get the last character of the id.
When you pass a string as elem to on, it uses document.querySelector, but you pass the id without the hash symbol (#). You don't need this anyway as you already have a reference to the image element, you can just pass that.
I also tidied it up and modernized it a little bit.
The glaring problem I neglected to mention was the use of var and the for(;;) loop. Thanks to #tranktor53 for pointing that out. I always instinctively replace for(;;) loops with for...in or for...of loops where I see them, and var with let or const, I didn't even notice that it was part of the problem.
function on({ type, element = window, callback, capture = false }) {
if (typeof element === 'string') element = document.querySelector(element);
if (!element) return;
element.addEventListener(type, callback, capture);
};
function rollOver({ element, id }) {
element.src = `https://via.placeholder.com/200x100?text=${ id }+hover`;
}
function rollOut({ element, id }) {
element.src = `https://via.placeholder.com/200x100?text=${ id }+no+hover`;
}
document.addEventListener("DOMContentLoaded", _ => {
const elements = document.querySelectorAll('#roll-over-collection-a img');
for(let element of elements) {
const id = element.id.substr(element.id.lastIndexOf('-') + 1);
on({ type: 'mouseover', element, callback: _ => rollOver({ element, id }) });
on({ type: 'mouseout' , element, callback: _ => rollOut({ element, id }) });
}
});
<div id="roll-over-collection-a">
<img id="roll-over-1" src="https://via.placeholder.com/200x100?text=1+no+hover">
<img id="roll-over-2" src="https://via.placeholder.com/200x100?text=2+no+hover">
<img id="roll-over-3" src="https://via.placeholder.com/200x100?text=3+no+hover">
<img id="roll-over-4" src="https://via.placeholder.com/200x100?text=4+no+hover">
<img id="roll-over-5" src="https://via.placeholder.com/200x100?text=5+no+hover">
<img id="roll-over-6" src="https://via.placeholder.com/200x100?text=6+no+hover">
</div>
The main problem is using var in the for loop and assuming that the value of i seen in the event handler will match that of i when th call back function was created. This is incorrect, since i will have the value it reached when the for loop completed, at a later time when the handler gets executed.
In current browsers the solution is to use let instead of var and hence starting the loop as
for (let i = 0; i < rollOverCollectionA.length; i++) ...
For discussion and older solutions see JavaScript closure inside loops – simple practical example
In regards the image source string calculation
/images/home-page/desktop/EYES_ON_YOU_desktop_HP_HOVER_${elem.slice(length-1)}.jpg?$staticlink$
please review what is needed - if you need to copy the entire value of elem as a string, don't include the call to slice. If a part of the string is required, the posted code may be correct. Slicing from a start index of elem.length-1 will copy the last letter of the element id (of course) and may also be correct.
Update: The need to capture the value of i during loop iteration can be eliminated in at least two ways:
In the mouseover and mouseout event handlers replace rollOverCollectionA[i]with this. There is no need for reverse lookup of an HTML collection based on a captured index value to determine the element an event handler is attached to.
Use event delegation with a single listener attached to on the images' DOM container. Using the same on function, and elem.id.slice(length-1) as a possible image source suffix, similar to:
document.addEventListener("DOMContentLoaded", function(event) {
var rollOverCollectionA = document.getElementById('roll-over-collection-a');
on('mouseover', rollOverCollectionA, function( event){
var elem = event.target;
if( elem && elem.id && elem.tagName === 'IMG') {
elem.src = `/images/home-page/desktop/EYES_ON_YOU_desktop_HP_HOVER_${elem.id.slice(length-1)}.jpg?$staticlink$`;
}
});
on('mouseout', rollOverCollectlonA, function(event) {
var elem = event.target;
if( elem && elem.id && elem.tagName === 'IMG') {
elem.src = `/images/home-page/desktop/EYES_ON_YOU_desktop_HP_NO_HOVER_${elem.id.slice(length-1)}.jpg?$staticlink$`;
}
});
});
I have made a simple JavaScript library with a on function.
The library works fine but I want to get my on function to support multiple events and add events to all the elements.
What is the most efficient way I can do this?
Here is the code for my on function:
Q.fn.on = function(type, fn){
this[0]["on" + type] = fn;
};
You were only adding events to the first element because you were using this[0].
this[0] is the first item in the this object I.E the first element.
What you want to do is loop through ALL the elements in the this object and add an event listener to each element.
To support multiple events you want to split your "type" string (now called events) into an array, loop through that array and attach each element with the each event in that array.
This should work for what you are trying to achieve.
Q.fn.on = function(events, callback) {
// split multiple events into an array
// or push a single event into a new array
events = events.match(" ") ? events.split(" ") : [events];
// loop through the events
for(var e = 0; e < events.length; e++) {
// replace the "on" in the event, we don't need it.
ev = events[e].replace(/^on/, "");
// loop through your elements, you want to add an event listener to all of them
for(var i = 0; i < this.length; i++) {
// add your event listener to the element
this[i].addEventListener(ev, callback, false);
}
}
};
Just in case you need a off function that behaves in the same way:
Q.fn.off = function(events, callback) {
// split multiple events into an array
// or push a single event into a new array
events = events.match(" ") ? events.split(" ") : [events];
// loop through the events
for(var e = 0; e < events.length; e++) {
// replace the "on" in the event, we don't need it.
ev = events[e].replace(/^on/, "");
// loop through your elements, you want to remove the event listener from all of them
for(var i = 0; i < this.length; i++) {
// remove your event listener from the element
this[i].removeEventListener(ev, callback, false);
}
}
};
For more information on adding and removing events in JavaScript visit MDN
Try this:
$(".el").on({
click: function() {
// Do something
},
mouseenter: function() {
// Do something
},
mouseleave: function() {
// Do something
}
});
Using vanilla JS I would like to know if would be possible to see the property onclick on a HTML object (div)
for (var name in element) {
if(name == "onclick"){
// do smt
}
}
Instead of enumerating properties of element, you can immediately retrieve the onclick property of element with:
var clickHandler = element.onclick;
Events nowadays are bound with addEventListener (and attachEvent in old IE), which allow for multiple handlers per event type. Setting the onevent property only allows for one handler, can be overwritten, and normally isn't the way to bind handlers in a web page.
Unfortunately, you are not able to retrieve any listeners bound with addEventListener (and attachEvent), without writing a wrapper function that tracks them...for example:
var events = [];
function addEvent(element, eventName, callback) {
element.addEventListener(eventName, callback, false);
var found = false;
for (var i = 0; i < events.length; i++) {
if (events[i].el === element) {
found = true;
events[i].list.push(callback);
break;
}
}
if (!found) {
events.push({
el: element,
list: [callback]
});
}
}
function viewEvents(element) {
for (var i = 0; i < events.length; i++) {
if (events[i].el === element) {
return events[i].list;
}
}
return null;
}
And you'd use it like:
var div = document.getElementById("some_id");
addEvent(div, "click", function () {
console.log("whatever");
});
console.log(viewEvents(div));
(of course, you'd need a wrapper for removeEventListener that removes handlers from events too)
I am collecting all images elements from the document var listen = document.getElementsByTagName('img'); which results in an array. Now I have to specify listen[0], listen[1], listen[2], listen[3] etc to listen for events on a element from this array. The question is is there any way to just do something like listen[any item from this array], or a function. Just to don't have to specify every element to listen on manually.
Example
Now I have to do something like this, for every array item:
listen[3].click = function() {};
Depending on what it is exactly what you want and what the DOM looks like you may want to use an event listener higher up the DOM tree somewhere instead of create a lot of listeners:
document.addEventListener('click', function(e) {
if (e.target.nodeName !== 'IMG') {
return;
}
alert('img clicked');
});
Demo: http://jsfiddle.net/APSMT/1/
Note that I have attached the event listener to document, but if you can make it more specific you should
With a reasonable new browser you can use Array.forEach:
[].forEach.call(document.getElementsByTagName('img'), function (img) {
el.addEventListener('click', callback);
});
Or "old skool" (and maybe even better readable):
var imgs = document.getElementsByTagName('img');
for (var i = 0, len = imgs.length; i < len; ++i) {
imgs[i].addEventListener('click', callback);
}
Reading the HTMLCollection.length only once can give a speed-up. It will never be slower.
You should not create a Function object for every iteration:
// Don't
for (...) {
img[i].onclick = function () { ... }
}
// Do
function onclick () {
// `this` will be img[i]
}
for (...) {
img[i].onclick = onclick;
}
I have a list which contains links . I am using this code to access them:
function initAll() {
var allLinks = document.getElementById("nav").getElementsByTagName("a");
for (var i=0; i< allLinks.length; i++) {
allLinks[i].onmouseover = showPreview;
allLinks[i].onmouseout = function() {
document.getElementById("previewWin").style.visibility = "hidden";
allLinks[i].onclick=mainProcess;
}
}
}
function mainProcess(evt){
alert(this.value);
false;
}
This is not the exact code, what I am trying to do is that I need to identify link is clicked and perform some function on the basis of link clicked. I don't know where code needs to be modified... Page is giving error on the allLinks[i].onclick=mainProcess(this); line.
Now the problem is that I don't know how I should handle all the three events?
1) You're setting the onclick property of each of the links to be the value returned by mainProcess() - which always returns false. So, in effect, you're writing allLinks[i].onclick = false;
2) When you define an event handler directly, the argument that gets passed to it when the event fires, is the event object - not the element it was fired on.
To figure out the element, you can either look in the event object, or (since the handler has been added to the element itself) simply use this, as that will refer to the link element
for (var i = 0; i < allLinks.length; i++) {
allLinks[i].onclick = mainProcess;
}
function mainProcess(event) {
{
alert(this.value);
return false;
}
You do need to pass this to mainProcess(link). As stated in http://www.quirksmode.org/js/events_tradmod.html "No parentheses!" and "this" chapters. Check it out, there's an example there too. Should be everything you need.
Try changing to this:
for (var i = 0; i < allLinks.length; i++) {
allLinks[i].onclick = mainProcess;
}
function mainProcess(event) {
{
alert(this.value);
return false;
}