So these two work fine:
$(document).on("click", "#_something", function(){
$('#vid').attr('src',video_config["something"].video);
});
$(document).on("click", "#_anotherthing", function(){
$('#vid').attr('src',video_config["anotherthing"].video);
});
However, something and nothing are properties of an object I made, so I attempted to do this:
for (var key in video_list){
$(document).on("click", "#_"+key, function(){
$('#vid').attr('src',video_list[key].video);
});
}
Which sort of messed it up, and set all the src values to the last video_list[key].video value I have. To rephrase, this assigned all of the src properties the same value.
How do I do this correctly without manually writing each of the event handlers?
This is because your Handler function captures the key variable which is scoped to the parent function. When your Handler executes, key has the last value.
The fix is to capture the current value at each iteration by using yet another function scope. Like this:
for (var k in video_list) {
function(key) {
// create your event handler here using key
}(k);
}
This is explained in this question that is basically the same as this one:
javascript closure in a for loop
In ES6 browsers, let being block scoped you can use it as a shortcut:
for (let k in video_list) {
let key = k;
// same code as your question goes here, using key.
}
Here's a simple way using one event handler, a class and a data attribute:
$(document).on("click", ".video", function(){
var key = $(this).data("key"); // in the element add data-key="xyz"
$('#vid').attr('src',video_list[key].video);
});
The quick and dirty hack:
for (var key in video_list){
(function(key){// create a new context, so not all handlers point the the same key
$(document).on("click", "#_"+key, function(){
$('#vidSrc').attr('src',video_list[key].video);
});
})(key);
}
The correct way:
$(document).on("click", ".some-new-class-you-just-defined", function() {
$(this).attr('src', video_list[$(this).attr('id').slice(1)].video);
});
EDIT: Add substring to the id. It's better to have some sort of lookup mechanism, rather than storing this in id's, as #jods suggested.
Related
I have some buttons, with same name anOldFile and I want to get theirs id, or innerHTML if I clicked. I think this is a good start, but I don't know how to continue, if this is good:
document.getElementsByName('anOldFile').addEventListener('click', changeOffCanvasContent(this));
function changeOffCanvasContent(e) {
//???
}
As getElementsByName() returns a nodelist, you need to iterate and bind event handler to each element and just pass the function reference.
document.getElementsByName('anOldFile').forEach(function (elem) {
elem.addEventListener('click', changeOffCanvasContent);
});
It will set the context of this to element which invoked the event
function changeOffCanvasContent(e) {
console.log(this.id);
console.log(e.target.id);
}
you have several problems there. Fist of all getElementsByName returns array of node elements, so you have to addEventListener to each one of them. Secondly you are registering eventListener function incorrectly. You should not call a handler function in addEventListener, but just pass a handler name.
Thridly in the function definition you can get hold of element by referencing target element as follows:
Whole code snippet that does the job should look as follows:
var elements = document.getElementsByName('anOldFile');
function changeOffCanvasContent(e) {
console.log(e.target.innerHTML);
console.log(e.target.id);
}
for(var i = 0; i< elements.length-1; i++){
elements[i].addEventListener('click', changeOffCanvasContent);
}
I've got a two-part question.
One: can I use the last variable I set to update the value on the line after if (!last) {? i.e., something like last = size;?
var j$ = jQuery.noConflict();
function updateCount() {
var self = j$(this),
last = self.data('last'),
size = self.val().length,
span = j$('.currentCount');
if (!last) {
self.data('last', size);
} else if (last != size) {
span.text(size);
self.data('last', size);
}
}
j$('textarea[id$=textBody]').on('propertychange change click keyup input paste', updateCount);
Secondly, can I chain my .on('propertychange ... line to have updateCount run as soon as the script is loaded?
Question 1:
No, you can not use simply the assignment to the variable because there is no data-binding in jQuery. So updating last variable will never update the data-last of the jQuery object.
Question 2:
This is what I am used to do:
j$('textarea[id$=textBody]').on('propertychange change ...', updateCount).change();
Where change() automatically triggers the function.
For Q1: No you can't. If you want data binding to work, you can try AngularJS where 2 way data binding between UI and model is possible.
Q2: My solution would be something like this using immediate function
$('textarea[id$=textBody]').on('propertychange change click keyup input paste', (function(){
updateCount();
return updateCount;
}()));
The last variable has a copy of the value in self.data('last'), it is not a pointer, so you would have to do something like this:
last = size;
self.data('last', last);
For the second question, you can trigger the event or just call the function:
j$('textarea[id$=textBody]').trigger('propertychange');
// or
j$('textarea[id$=textBody]').each(updateCount);
I have a bit of HTML generated by PHP in the format of:
<div class=zoomButton>
<input type=hidden name=zoomURL value=*different per instance*>
</div>
I am trying to attach a listener (imageZoom(event, url)) to each of the class "zoomButton" elements, but call it with different arguments for each instance.
i.e.
var zoomButtonArray = document.getElementsByClassName('zoomButton');
for (i=0; i<zoomButtonArray.length; i++)
{
var zoomURL = zoomButtonArray[i].children[0].value;
zoomButtonArray[i].addEventListener("mousedown", function(){imageZoom(event,zoomURL);});
}
however it seems that zoomURL is always the value of the very last element. How can I change my code/approach so that the argument passed to the listener is the correct one, and not the last one in the "zoomButtonArray" array?
Thanks
You need to wrap the event listener in a closure:
function makeEventListenerForZoomURL(zoomURL) {
return function(event) {
imageZoom(event, zoomURL);
}
}
var zoomButtonArray = document.getElementsByClassName('zoomButton');
for (i=0; i<zoomButtonArray.length; i++)
{
zoomButtonArray[i].addEventListener(
"mousedown",
makeEventListenerForZoomURL(zoomButtonArray[i].children[0].value)
);
}
This can also be simplified using the ECMAScript5 forEach:
var zoomButtonArray = document.getElementsByClassName('zoomButton');
zoomButtonArray = Array.prototype.slice.call(zoomButtonArray, 0);
zoomButtonArray.forEach(function(node) {
node.addEventListener("mousedown", function(event) {
imageZoom(event node.children[0].value);
});
});
The reason is that each time the for loop executes a new function is created, this new scope references the variable i but i changes each time the loop iterates. So by the time the event listener runs it looks at the value of i only to find that it is the last value when the for loop ended. By using a closure described above the scope created is unique to each iteration of the loop so that when the event listener finally executes the value of the wrapped variable (zoomURL or node in the examples above) will not have changed.
Here is a good article explaining closures in for loops: http://trephine.org/t/index.php?title=JavaScript_loop_closures
I think you are missing quotes around attributes. I just added quotes and the tested at jsFiddle (Fiddle link in comments) and it's working see to console in developer tool. it is iterating through each element as desired. Console screen shot
I currently have a function set up like this:
$('.color').each(function(){
$(this).val('stuff');
});
As you can see, for each .color function, I'm setting the val of the selected .color item to "stuff".
However, I only want to set the value of $this when the mouse has clicked up. For example:
$('.color').each(function(){
$(window).mouseup(function(){
$(this).val('stuff');
});
});
However, this will change this to window instead of keeping the value at .color (or so I think unless I'm mistaken.)
How can I still use $(this) while keeping the same value as though it didn't have another function above it?
I think you misunderstood with registering events for elements with a common class. You don't need to iterate that, just use the following snippet.
Try,
$('.color').mouseup(function(){
$(this).val('stuff');
});
I think you may looking for something like this,
$(window).mouseup(function(e){
if($(e.target).is('.color')){
$(e.target).val('stuff');
}
});
And finally, if you want to go in a wronger way, you can just correct your code by using cache variables, meaning cache the current object before event binding.
var cacheWinow= $(window);
$('.color').each(function(){
var cacheThis = $(this);
cacheWinow.mouseup(function(){
cacheThis.val('stuff');
});
};
Then you would need a reference to the object you're talking about like this
$('.color').each(function(){
var $this = $(this);
$(window).mouseup(function(){
$this.val('stuff');
});
});
Not advising on the use of the window part, but changing the whole thing does not answer your question.
You should use mouseup on the actual element not window
Another thing you should be doing is wrapping window only once like this
var $window = $(window);
$('.color').each(function(){
var $this = $(this);
$window.mouseup(function(){
$this.val('stuff');
});
});
If you don't you will be calling the jQuery constructor every single time you mouseup and that is not necessary
Try this one:
$(window).mouseup(function(){
$(".color").val('stuff');
});
You should cache .color. Like this:
$('.color').each(function () {
var color = $(this);
$(window).mouseup(function () {
color.val('stuff');
});
});
In regards to preserving this, or keeping the scope, you can also use proxy() or bind() etc. Example using proxy():
»Fiddle«
Anonymous function:
$('.color').each(function(){
$(window).mouseup(
$.proxy(function(){ $(this).val('stuff') }, this)
// |
// +--- this is "this" of each
)
});
Named function:
function other(str) {
$(this).val(str);
// |
// +---- "this" (in this case) is "this" of each.
}
$('.other').each(function(){
$(window).mouseup(
$.proxy(other, this, 'other stuff')
// | |
// | +--- Arguments
// +---------- this is "this" of each
)
});
As to if this is a good solution for this case is another discussion where Rajaprabhu Aravindasam has some good points.
I have a map of messages
say:
var Mapping = {
"notnow": 2,
"expensive": 3,
"not_worth_it": 4
}
i have a bunch of html elements (lets say divs with the same name)
so
<div id="notnow"></div>
,etc
now i want to attach a click handler to each of them,
i run a loop as shown below
function setThemUp(){
for(var item in Mapping)
{
$("#" + item).bind('click', function () {
Apply(Mapping[item]); });
}
}
But for some reason all of them seem to get bound to "not_worth_it":4. Not to their respective values.
I'm using Jquery 1.5.
Can someone please explain why this might be happening?
My guess is that instead of the Mapping[item] being resolved to their values, it's being passed as a reference or something, that's why since the value of item eventually points to "not worth it" all of them call the function with that value itself. Any way in which i could overcome them.
Hard coding each of them as
$("#notnow").bind('click', function () {
Apply(Mapping["notnow"]); });
$("#expensive").bind('click', function () {
Apply(Mapping["expensive"]); });
$("#not_worth_it").bind('click', function () {
Apply(Mapping["not_worth_it"]); });
does work, but i would prefer an elegant solution with a loop.
Answer
i went with the closure solution
function setThemUp(){
for(var item in Mapping)
{
$("#" + item).bind('click', (function () {
return function(temp) {
Apply(Mapping[temp]); };
})(item));
}
}
Reasons being , this was more of a why the loop didn't work rather than about optimization of the jquery , since this was afterall a representative example, not my actual code, and this was an elegant solution to that problem.
This is a classic case of scope issues: you're referencing the variable item in each of your bound handlers. The variable item changes, though -> it's being assigned all properties of the Mapping object literal, the last one being not_worth_it.
Creating a closure might help, to preserve the state of item for each callback:
for(var item in Mapping)
{
$("#" + item).bind('click', (function(currentItem)
{//IIFE, pass item as argument---------/
return function ()
{//return function, \/ access to closure scope
Apply(Mapping[currentItem]);
};
}(item)););
}
But this seems to be somewhat overkill, why not simply delegate the event, and use Mapping[$(this).attr('id')]?
I would suggest moving to this form:
Add the class mapped to your mapping divs.
HTML
<div id="notnow" class="mapped"></div>
JS
function setThemUp(){
$('.mapped').bind('click', function () {
Apply(Mapping[this.id]);
});
}
The problem is you need to write:
for (var item in Mapping)
rather than foreach.