Creating reuseable variables within a .on() or any object literal - javascript

Sometimes I run into situations where I'm having to create the same variables, and retrieve the exact same type of information over & over again while inside of a object literal, such as an .on() Out of sheer curiosity, and the fact that there has to be a better way, here I am.
NOTE I am not talking about jQuery .data() or any sort of normal window. global variable. I am talking one that is maintained within the closure of the object literal.
Some of these variables change in real-time of course, hence why I always had them inside of each method within .on()
Case in point:
$(document).on({
focusin: function () {
var placeHolder = $(this).attr('data-title'),
status = $(this).attr('data-status');
// etc etc
},
focusout: function () {
var placeHolder = $(this).attr('data-title'),
status = $(this).attr('data-status');
// etc etc
},
mouseenter: function () {
// same variables
},
mouseleave: function () { }
}, '.selector');
Is there a way to just have the variables stored somewhere, and retrieve on each event? They need to be dynamic
$(document).on({
// Basially:
// var placeHolder; etc
// each event gets these values
focusin: function () {
// now here I can simply use them
if (placeHolder === 'blahblah') {}
// etc
}
}, '.selector');

You can use event data to pass some static variables to the event, as well as to make a method-wise trick to pass the "dynamic" ones:
$(document).on({
focusin: function(e) {
var attrs = e.data.getAttributes($(this)),
var1 = e.data.var1;
var placeHolder = attrs.title,
status = attrs.status;
// ...
},
focusout: function(e) {
var attrs = e.data.getAttributes($(this)),
var1 = e.data.var1;
var placeHolder = attrs.title,
status = attrs.status;
// ...
},
// ...
}, ".selector", {
// static variables
var1: "some static value",
// dynamic "variables"
getAttributes: function($this) {
return {
title: $this.data("title"),
status: $this.data("status")
};
}
});
DEMO: http://jsfiddle.net/LHPLJ/

There are a few ways:
Write your own function that will return the JSON above; you can loop through properties to keep from duplicating work.
Write a function that returns those variables (eg: as JSON) so you need only call one function each time.
Write a function to set those variables as global properties and refer to them as needed.

Why not simply add a helper function to extract it?
var getData = function(elm) {
return {
placeHolder : $(elm).attr('data-title'),
status : $(elm).attr('data-status');
};
};
$(document).on({
focusin: function () {
var data = getData (this);
// do stuff with data.status etc.
},
//repeat...

If you're always targeting the same element (i.e. if there's a single element with class selector), and if the values of those variables won't change between the different times the events are triggered, you can store them on the same scope where the handlers are defined:
var placeHolder = $('.selector').attr('data-title'),
status = $('.selector').attr('data-status');
$(document).on({
focusin: function () {
// etc etc
},
focusout: function () {
// etc etc
},
mouseenter: function () {
// same variables
},
mouseleave: function () { }
}, '.selector');
Functions declared on the same scope as those variables will have access to them.

I would recommend wrapping everything in a function scope so that the variables are not global. If these attributes never change, you could do something like:
(function(sel){
var placeHolder = $(sel).attr('data-title'),
status = $(sel).attr('data-status');
$(document).on({
focusin: function () {
// etc etc
},
focusout: function () {
// etc etc
},
mouseenter: function () {
// same variables
},
mouseleave: function () { }
}, sel);
})('.selector');
Otherwise, you could do this (in modern browsers, IE9+)
(function(sel){
var obj = {};
Object.defineProperty(obj, 'placeHolder', {
get:function(){return $(sel).attr('data-title');}
});
Object.defineProperty(obj, 'status', {
get:function(){return $(sel).attr('data-status');}
});
$(document).on({
focusin: function () {
console.log(obj.placeHolder, obj.status);
//etc etc
},
focusout: function () {
// etc etc
},
mouseenter: function () {
// same variables
},
mouseleave: function () { }
}, sel);
})('.selector');

You're doing it close to correct, but the best way to do it is this:
$(document).on({
focusin: function () {
var el = $(this), //cache the jQ object
placeHolder = el.data('title'),
status = el.data('status');
// etc etc
}
}, '.selector');
Data was created for this purpose, don't worry about trying to create re-useable items. If you're delegating the event using object, then it's probably because the elements aren't always on the page in which case you need to get the variables within each individual event.
Finally, don't try to optimize when you don't need to.

You can save them in the window object and make them global.
window.yourVariable = "whatever";
This does what you want but is for sure not the most clean way. If you can, you can consider saving the desired data to the object itself via $(element).data("key", "value")

Related

jQuery.each() - passing extra argument to the callback parameter

This is a fragment of a plugin that I'm using on my site:
$.fn.extend({
limiter: function(limit, elem) {
$(this).on("keyup focus", function() {
setCount(this, elem);
});
function setCount(src, elem) {
...
}
}
});
The setCount() function works well only if the elem property in not an array - only single values are accepted.
I'd like to change it so that I can pass arrays (multiple selection in my case) to the setCount() function.
I thought that a solution would be to use jQuery.each() iterator like so:
$.fn.extend({
limiter: function(limit, elem) {
$(this).on("keyup focus", function() {
$.each(elem, setCount)
});
function setCount(src, elem) {
...
}
}
});
The problem is that I also have to pass the this object which points to the object that received "keyup focus" as is the case in the first snippet.
How do I pass this object to the callback property of $.each() in the given scenario?
Try binding the context, using bind (warning not IE8 compatible):
$.each(elem, setCount.bind(this))
This makes the this-context inside each setCount-call, pointing to this from the event handler.
You can keep a reference to the "first" this and use it in setCount:
$.fn.extend({
limiter: function(limit, elem) {
var that = this;
$(this).on("keyup focus", function() {
$.each(elem, setCount)
});
function setCount(src, elem) {
// use 'that' here
...
}
}
});

Passing $(this) to jQuery .hover() functions

I wrote a simple tooltip functionality, to be seen here.
The thing is, in both handler functions set in .hover() I need access to $(this) and other 2 variables based on it. In order do achieve that, I declare the same 3 variables in both handlers:
$('a').hover(
function () {
var $this = $(this);
var link_offset = $this.offset();
var link_tooltip = $this.data('tooltip');
// Rest of the code
},
function () {
var $this = $(this);
var link_offset = $this.offset();
var link_tooltip = $this.data('tooltip');
// Rest of the code
}
);
DRY principle should be respected, so my question is: Is there other and smarter/less dirty way of passing the same variables set to both functions within .hover()?
Obvioulsy, the variables can't be global (and globals are evil anyway).
Any ideas how to achieve this with jQuery or pure JS?
Call one named function inside the anonymous callbacks:
$('a').hover(function() {
hoverFunc($(this), true)
}, function() {
hoverFunc($(this), false)
});
function hoverFunc($this, is_hovered) {
var link_offset = $this.offset();
var link_tooltip = $this.data('tooltip');
if (is_hovered) {
console.log('ok')
// do something
} else {
console.log('out')
// do something else
};
}
http://jsfiddle.net/mblase75/8njk2m32/

Accessing javascript native "this" inside of jQuery

How can i access to this.today inside of the Moved function? It will be called via jQuery so the this keyword will be overwritten by jQuery to jQuery object or a DOM element.
Here is something similar to what I have:
(function(Map) {
Map.Timeline = {
today: null,
Init: function () {
jQuery("#timeline").mousemove(Map.Timeline.Moved); // or this.Moved
},
Moved: function (event) {
console.log(this); // jQuery Object or DOM element
console.log(this.today); // fails
console.log(Map.Timeline.today); // works fine
},
// more code here ...
Use jQuery.proxy() to use a custom context in a callback call
jQuery('#timeline').mousemove(jQuery.proxy(Map.Timeline.Moved, this));
You can store this before it is overwritten:
(function(Map) {
var myvar = $(this);
Map.Timeline = {
today: null,
Init: function () {
jQuery("#timeline").mousemove(Map.Timeline.Moved); // or this.Moved
},
Moved: function (event) {
console.log(myvar); // jQuery Object or DOM element
console.log(myvar.today); // fails
console.log(Map.Timeline.today); // works fine
},
// more code here ...

What's the easiest way i can pass an element as a first argument to event handlers in JavaScript?

I know that having the value of this being changed to the element receiving the event in event handling functions is pretty useful. However, I'd like to make my functions always be called in my application context, and not in an element context. This way, I can use them as event handlers and in other ways such as in setTimeout calls.
So, code like this:
window.app = (function () {
var that = {
millerTime: function () {},
changeEl: function (el) {
el = el || this;
// rest of code...
that.millerTime();
}
};
return that;
}());
could just be like this:
window.app = (function () {
return {
millerTime: function () {},
changeEl: function (el) {
// rest of code...
this.millerTime();
}
};
}());
The first way just looks confusing to me. Is there a good easy way to pass the element receiving the event as the first argument (preferably a jQuery-wrapped element) to my event handling function and call within the context of app? Let's say I bind a bunch of event handlers using jQuery. I don't want to have to include anonymous functions all the time:
$('body').on('click', function (event) {
app.changeEl.call(app, $(this), event); // would be nice to get event too
});
I need a single function that will take care of this all for me. At this point I feel like there's no getting around passing an anonymous function, but I just want to see if someone might have a solution.
My attempt at it:
function overrideContext (event, fn) {
if (!(this instanceof HTMLElement) ||
typeof event === 'undefined'
) {
return overrideContext;
}
// at this point we know jQuery called this function // ??
var el = $(this);
fn.call(app, el, event);
}
$('body').on('click', overrideContext(undefined, app.changeEl));
Using Function.prototype.bind (which I am new to), I still can't get the element:
window.app = (function () {
return {
millerTime: function () {},
changeEl: function (el) {
// rest of code...
console.log(this); // app
this.millerTime();
}
};
}());
function overrideContext (evt, fn) {
var el = $(this); // $(Window)
console.log(arguments); // [undefined, app.changeEl, p.Event]
fn.call(app, el, event);
}
$('body').on('click', overrideContext.bind(null, undefined, app.changeEl));
Using $('body').on('click', overrideContext.bind(app.changeEl)); instead, this points to my app.changeEl function and my arguments length is 1 and contains only p.Event. I still can't get the element in either instance.
Defining a function like this should give you what you want:
function wrap(func) {
// Return the function which is passed to `on()`, which does the hard work.
return function () {
// This gets called when the event is fired. Call the handler
// specified, with it's context set to `window.app`, and pass
// the jQuery element (`$(this)`) as it's first parameter.
func.call(window.app, $(this) /*, other parameters (e?)*/);
}
}
You'd then use it like so;
$('body').on('click', wrap(app.changeEl));
For more info, see Function.call()
Additionally, I'd like to recommend against this approach. Well versed JavaScript programmers expect the context to change in timeouts and event handlers. Taking this fundamental away from them is like me dropping you in the Sahara with no compass.

prototype this selector

If I'm using the following function :
clusters.prototype.shop_iqns_selected_class = function() {
if(this.viewport_width < 980) {
$(this.iqns_class).each(function() {
$(this.iqn).on('click', function() {
if($(this).hasClass('selected')) {
$(this).removeClass('selected');
} else {
$(this).addClass('selected');
}
});
});
}
}
To add a property to the clusters function, I know that using this.viewport_width I'm referring to the parent function where I have this.viewport_width defined, but when I'm using the jQuery selector $(this), am I referring to the parent of the $.on() function ?
In JavaScript, this is defined entirely by how a function is called. jQuery's each function calls the iterator function you give it in a way that sets this to each element value, so within that iterator function, this no longer refers to what it referred to in the rest of that code.
This is easily fixed with a variable in the closure's context:
clusters.prototype.shop_iqns_selected_class = function() {
var self = this; // <=== The variable
if(this.viewport_width < 980) {
$(this.iqns_class).each(function() {
// Do this *once*, you don't want to call $() repeatedly
var $elm = $(this);
// v---- using `self` to refer to the instance
$(self.iqn).on('click', function() {
// v---- using $elm
if($elm.hasClass('selected')) {
$elm.removeClass('selected');
} else {
$elm.addClass('selected');
}
});
});
}
}
There I've continued to use this to refer to each DOM element, but you could accept the arguments to the iterator function so there's no ambiguity:
clusters.prototype.shop_iqns_selected_class = function() {
var self = this; // <=== The variable
if(this.viewport_width < 980) {
// Accepting the args -----------v -----v
$(this.iqns_class).each(function(index, elm) {
// Do this *once*, you don't want to call $() repeatedly
var $elm = $(elm);
// v---- using `self` to refer to the instance
$(self.iqn).on('click', function() {
// v---- using $elm
if($elm.hasClass('selected')) {
$elm.removeClass('selected');
} else {
$elm.addClass('selected');
}
});
});
}
}
More reading (posts in my blog about this in JavaScript):
Mythical methods
You must remember this
Don't use this all throughout the code. Methods like $.each give you another reference:
$(".foo").each(function(index, element){
/* 'element' is better to use than 'this'
from here on out. Don't overwrite it. */
});
Additionally, $.on provides the same via the event object:
$(".foo").on("click", function(event) {
/* 'event.target' is better to use than
'this' from here on out. */
});
When your nesting runs deep, there's far too much ambiguity to use this. Of course another method you'll find in active use is to create an alias of that, which is equal to this, directly inside a callback:
$(".foo").on("click", function(){
var that = this;
/* You can now refer to `that` as you nest,
but be sure not to write over that var. */
});
I prefer using the values provided by jQuery in the arguments, or the event object.

Categories

Resources