Using a function while keeping "this" - javascript

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.

Related

jQuery: use var shortcut to assign data and how to chain function to run on load and click

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);

Why does the context of "this" change in this example?

I'm embarrassed to admit how many hours I spent trying to figure this problem out. It turned out that the order of the two lines under the "problem area" comment change the context of "this" when it's used in the prototype addSong function.
var PlaylistView = function(config){
this.config = config || {};
this.$addSongForm = this.config.addSongForm || $('#addSongForm');
this.$song = this.config.song || $('#song');
// problem area
this.addSong = $.proxy(this.addSong, this);
this.listenAddSong();
};
PlaylistView.prototype.listenAddSong = function(){
this.$addSongForm.on('submit', this.addSong);
};
PlaylistView.prototype.addSong = function(event){
//Here is where I'm getting different context for this
var songName = this.$song.val();
//do some stuff...
return false;
};
return PlaylistView;
When the two lines are in the order shown I get the behavior I want: "this.$song" contains a jquery selector that I had set when initializing the PlaylistView object. However, when I had the order reversed, looking at the inspector in Firefox showed that "this" referred to the actual form in the DOM.
Why is that?
The reason is because this.addSong !== $.proxy(this.addSong, this). When you run $.proxy and then listenAddSong the bound function is used and this is your Playlist object. When you reverse the order then the unbound function is passed to the listener in listenAddSong. You replace the unbound function with the bound function in this line:
this.addSong = $.proxy(this.addSong, this);
So, depending on which function this.addSong points to when listenAddSong runs, you either get the correct behavior, or the incorrect behavior.

Why can't I set mass event handlers?

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.

Variable is undefined/out of scope

This is a scope issue, correct?
EDIT: http://jsfiddle.net/RPwLK/7/
On line 38 of the following function, where displayBossMessage() is called by itself, the variable "boss" seems to be out of scope. It should contain the name (string) of the current boss, and when the function is called, it should put it back in the "boss" argument.
But boss seems to always be undefined. I've tried creating a variable right before the jQuery listener is created to see if it would be within scope, but it seems as if it isn't.
Or maybe I'm just having a slow day. ;p
function displayBossMessage(boss,message,options,timer){
boss = bosses[boss];
//clear any possible lingering text/buttons
$(boss.messagebox).text('');
$(boss.optionsbox).text('');
displayMessage_CurrentBoss = boss;
//if no options provided, set a default "continue" button
if(options == ''){
options = {
'default' : {
'text' : 'Continue',
'func' : function(){}
}
}
}
$('#container div').hide();
$(boss.div).fadeIn(1500);
writeMessage(message,$(boss.messagebox),0);
setTimeout(function(){
$(boss.optionsbox).fadeIn(1000);
},3000);
//"listen" for a choice
var i = 0;
for(option in options){
$(boss.optionsbox).html($(boss.optionsbox).html() + '<button name="'+ i +'">'+ options[option].text +'</button> ');
$(document).on('click', (boss.div)+' button[name="'+i+'"]', function(){
options[option].func();
//close message screen or show defined response
if(typeof options[option].response != 'undefined'){
displayBossMessage(boss,options[option].response,'',true);
}else{
$(boss.div).hide();
$('#container div').fadeIn(1500);
}
});
}
if(timer){
//if they are afk/don't click after a minute, do it for them
setTimeout(function(){
$(boss.div+' button[name="0"]').click();
},60000);
}
}
Hope I'm not being completely oblivious and missing something so simple.
*Edit: Bosses variable (is global) *
(updated jsfiddle revision link to #11 which includes both solutions)
Looks like this could be a working fiddle: http://jsfiddle.net/RPwLK/11/
A minor problem: you have an extra ' on line 30 with the (second) alert call - the string literal was not closed correctly (or rather another was being opened). After that I was able to investigate and come up with the following conclusion (2 problems)...
The first problem was with the variable override here:
function displayBossMessage(boss,message,options,timer){
boss = bosses[boss]; // this line, boss was a string, now it will be an object
And the later usage in the same function here:
if(typeof options[option].response != 'undefined'){
displayBossMessage(boss,options[option].response,'',true); // first arg is now an object
The solution is to create a reference to the original boss when it was a string like:
function displayBossMessage(boss,message,options,timer){
var origBoss = boss; // let's remember what it was in its original string form
boss = bosses[boss];
And use it like so:
if(typeof options[option].response != 'undefined'){
displayBossMessage(origBoss,options[option].response,'',true); // now we're dealing with a string ref
The second problem is the reference to option within the for loop. It was always referencing the last value since the $(document).on('click'... is always delayed (asynchronous). There are a number of ways to solve this. I chose to use bind and pass in an argument with a reference to the value of option for each specific iteration.
Notice that in the original option is in the async function but not in a closure (for is not a closure):
for(option in options){
//...
$(document).on('click', (boss.div)+' button[name="'+i+'"]', function(){
options[option].func(); // here option is always the last item in options
So introduce an argument conveniently called option in the callback function for the click handler:
$(document).on('click', (boss.div)+' button[name="'+i+'"]', function(option){ // here
options[option].func(); // and now option is whatever the argument value is
And don't forget to pass it in the function declaration via bind:
$(document).on('click', (boss.div)+' button[name="'+i+'"]', function(option){
options[option].func();
// ...
}.bind(this,option)); // here we're passing whatever option is for the specific iteration as the first argument of the callback function
Note, this is just the scope, and each subsequent parameter for bind (see MDN) after the first corresponds to the arguments in the function definition.

Assign a variable inside an $.ajax() function

I would like to assign a variable inside an $.ajax() function so that it's still available the next time the function is called...
Is this possible?
var xxx = 1; isn't doing the job.
Thanks!
I think you should do something like that and avoid DOM manipulation for that...
(function($){
var YourApp = {
var your_vault : "",
binders : function(){
$('#button').on('click',YourApp.ajaxRequest);
},
ajaxRequest : function() {
// Do something with YourApp.your_vault
$.ajax({
url: 'http://someurl.com',
succes: function(){
// Modifying your value:
YourApp.your_vault = 'A wonderfull new value';
}
});
}
};
YourApp.binders();
})(jQuery);
Since I don't have real code from your app I can't give more exact code but I think you'll get the idea.
The key idea is the scope. Whenever you have your var in a higher scope you'll be able to modify it in a lower scope, without using the var keyword.
The variable must be stored in a static place e.g. hidden input or in a data-attribute.
$('#hiddenId').val(1);
// Or
$('#someFooId').data('last-ajax-value', 1);
Next time you call the function, use this to get the previous value:
var lastValue = $('#hiddenId').val();
// Or
var lastValue = $('#someFooId').data('last-ajax-value');
data docs:
Description: Store arbitrary data associated with the matched elements
If your variable is defined at a global level, then it can be used at many places.
You need to learn about variable scoping, and this same applies to javascript, in whichever function its run.

Categories