Using "this" inside named function on a jQuery plugin - javascript

I'm trying to write a simple jQuery plugin and I need some code to run on both load and window resize so I wrote a function inside the plugin.
jQuery(document).ready(function($) {
(function( $ ) {
$.fn.responsiveNav = function() {
function enable_responsive_nav() {
if( this.hasClass('class') ) {
//do stuff
}
}
$(window).resize(function(e) {
enable_responsive_nav();
});
enable_responsive_nav();
return this;
};
}( jQuery ));
$('nav').responsiveNav();
The problem is 'this' doesn't seem to be recognized inside the function. I tried passing it as a function argument:
enable_responsive_nav( this )
...but then I get an error on the console saying hasClass() 'is not a function'.
I guess I could do it without the function out and then bind the window resize event outside the plugin, but I'm trying to keep that to a single call and I'm sure that what I'm missing is very simple.

One common solution is to create a local variable called that or self in the scope where this has the expected value, and then to refer to the local variable in the scope of the inner function:
(function( $ ) {
$.fn.responsiveNav = function() {
var self = this; // local variable
function enable_responsive_nav() {
if( self.hasClass('class') ) { // self is in scope
//do stuff
}
}
$(window).resize(function(e) {
enable_responsive_nav();
});
enable_responsive_nav();
return this;
};
}( jQuery ));
$('nav').responsiveNav();

I tried passing it as a function argument:
enable_responsive_nav( this )
Let's follow the chain through:
jQuery will call your event callback with this referencing the DOM element (not jQuery object) that the event was hooked on. So you can do this:
enable_responsive_nav( $(this) );
...with
if( arg.hasClass('class') ) {
//do stuff
}
Or
enable_responsive_nav.call(this);
with
if($(this).hasClass('class') ) {
//do stuff
}

Related

Override jQuery functions simply by extending?

This is related to, but not a duplicate of, another SO Q&A Override jQuery functions.
It is clear from the answer to the above question that the pattern to override a jQuery function is:
(function($){
// store original reference to the method
var _old = $.fn.method;
$.fn.method = function(arg1,arg2){
if ( ... condition ... ) {
return ....
} else { // do the default
return _old.apply(this,arguments);
}
};
})(jQuery);
But why!?
I've been able to override a jQuery function simply by defining a function of the same name as the function to be overridden, within $.extend or $.fn.extend.
Consider this:
// random example showing jquery function overriding
$.fn.extend({
hide: function() {
$(this).css({"color":"red"});
}
});
$("#test").hide(); // this will actually paint the #test element red!
jsFiddle
I'd like to understand why _old.apply(this,arguments) would be the preferred way to override a jQuery function, as listed here and here.
From glancing at references provided at original post, summary of pattern could be to keep both "old" and "new" methods available ?
Edit, updated
Sorry, I don't get this. As far as I see, the reference to the overridden method is saved in a local variable in a closure is
unquestionably lost outside the closure. Can you explain how the "old"
method is still available? –SNag
I'd like to understand why _old.apply(this,arguments) would be the
preferred way to override a jQuery function, as listed here and
here.
Utilizing pattern at 1st link , above , if interpret pieces correctly, appear arguments test within if statement of jquery method within "self-executing anonymous function" determine return value of "old" or "new" (newly included; override) jquery method ?
i.e.g., try
html
<div>abc</div>
js
// See http://www.paulirish.com/2010/duck-punching-with-jquery/ , at
// `First we start off with a self-executing anonymous function,
// that makes a happy closure while remapping jQuery to $:`
// `closure` start
(function ($) {
// jquery `.css()`
var _oldcss = $.fn.css;
// jquery `.hide()`
var _oldhide = $.fn.hide;
// "new" `.css()`
$.fn.css = function (prop, value) {
// "new" `.css()` `test`
if (/^background-?color$/i.test(prop)
&& value.toLowerCase() === 'burnt sienna') {
return _oldcss.call(this, prop, '#EA7E5D');
} else {
return _oldcss.apply(this, arguments);
}
};
// "new" `.hide()`
$.fn.hide = function (prop, value) {
// "new" `.hide()` `test`
if (/color/i.test(prop) && /[a-f]|[0-9]/i.test(value)) {
return $.fn.css.call(this, prop, value);
} else {
return _oldhide.apply(this, arguments);
}
};
})(jQuery);
// `closure` stop
// and using it...
// "new" `.css()`
jQuery(document.body).css('backgroundColor', 'burnt sienna');
// "old" `.css()`
$("div").css("color", "yellow");
// "old" `.hide()`
$("div").hide(7500, function () {
// "old" `.css()`
$(document.body)
.css({
"transition": "background 2s",
"background": "#edd452"
})
.find($("div")).show(2500)
// "new" `.hide()`
.hide("color", "red")
});
jsfiddle http://jsfiddle.net/guest271314/5bEe4/
(function($){
// store original reference to the method
// stored locally
var _old = $.fn.method;
$.fn.method = function(arg1,arg2){
if ( ... condition ... ) {
return ....
} else { // do the default
// call apply method, in order to pass the this context.
return _old.apply(this,arguments);
}
};
})(jQuery);
Here in the above code, we are calling an anonymous function, in which we are declaring a local variable _old. When this anonymous function execute, it save the _old method reference and form a closure.
Now, when we call the new method, i.e,
$.fn.method = function(arg1,arg2){
if ( ... condition ... ) {
return ....
} else { // do the default
return _old.apply(this,arguments);
}
};
we also have an access to _old method, since its scope exists in the current context. And then, we can use it inside the new method.
Here we are calling _old method with the help of apply, because we want to have the same this context for that as well.
With this approach, we can easily override the jQuery method by preserving its original functionality.

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.

How to call function in another js file or trigger a custom event that passes a parameter as data object in Jquery?

I'm using reuqireJS and am struggling to call a function, which is in a js file I'm requiring. My main app.js "controller" requires (plugin)app.js, which handles all plugin configuration and plugin related functions.
This is from app.js
define([], function(){
var start = function() {
require(['jquery', 'overrides', 'jqm', 'multiview', 'respond'],function() {
// globals
var
// PROBLEM attempt at an external plugin function object
dataTablesExt = {},
...;
// call for (plugin)app.js
enhanceDataTables =
function( page, from ) {
var datatable = page.find('.table-wrapper table');
if ( datatable.length > 0 && datatable.jqmData('bound') != true ) {
datatable.not(':jqmData(bound="true")')
.each( function() {
var that = $(this),
tblstyle = that.jqmData("table-style");
that.jqmData('bound', true);
require(['services/datatables/app'], function (App) {
// this calls (plugin)app.js
App.render({style: tblstyle, table: that });
});
});
}
};
// PROBLEM - try to call function "Hello" inside datatables.app
anotherFunc=
function( page, from ) {
dataTablesExt.sayHello("john");
};
I guess my problem is how to set up the global variable dataTablesExt, so I can "fill" it with functions to be called globally. Here is what I'm trying inside (plugin)app.js:
define(['services/datatables/app', 'services/datatables/datatables.min'], function( app, datatables ) {
function render(parameters) {
...
// the function I want to call
function helloName( name ){
alert( name );
};
// I'm trying to add this function to the global "dataTablesExt"
dataTablesExt.sayHello = helloName;
}
But... doesn't work. I'm always getting:
dataTablesExt.sayHello is not a function
Question:
Can someone point me to what I'm doing wrong? If this is not possible, what would be an alternative.
I was thinking to trigger a custom event, but I would have to set up an object to pass along with the event, which I have no clue how to do.
Thanks for help!
Got it. I need to attach dataTablesExt to a global variable I'm using and not declare it as a variable. So like this:
// globals
var
...;
$.dataTablesExt = {};
Then I can assign functions to it and call them.

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.

JQuery plugins: Function using $(this) inside the plugin's options

Hello everyone.
I am trying to develop a Jquery plugin following the steps I found in http://docs.jquery.com/Plugins/Authoring and I seem to have problems reaching the caller object (the “this” variable) inside the options passed to the plugin. It is a plugin that I just want to use to make a button have a “blink” effect.
I would like to be able to pass the functions to execute in “show/hide” (or link blink-on, blink-off, if you prefer) as an option for the plugin. Let's say the user wants to achieve the “blinking” effect by hiding/showing the whole button every 1000 milliseconds. Then I would like the options to be something like:
$("#bttnOk").myBlinker ({
blinkHide: function(){$(this).hide();},
blinkShow: function(){ $(this).show();},
interval:1000
});
// … //
// And to make it actually blink:
$("#bttnOk").myBlinker ("blink");
Or let's say that the user wants to move the button up and down applying an inline css sytle every 200ms. Then the options would something like:
$("#bttnOk").myBlinker ({
blinkHide: function(){$(this).css(“margin-top: 10px”);},
blinkShow: function(){ $(this).css(“margin-top: 0px”);},
interval:200
});
The problem is that I seem to lose the reference to “$(this)” when I am inside the options. When the plugin reaches the blinkHide/blinkShow functions, “this” is the whole DOM window, not the button $(“#bttnOk”) my “myBlinker” plugin is attached to.
This is the first Jquery plugin I'm trying to write so I'm not even sure if there's a way to achieve what I'm trying to do.
My plugin code follows the following structure:
(function($){
var defaultOptions = {
interval: 500
}
var methods = {
init : function( options ) {
return this.each(function(){
this.options = {}
$.extend(this.options, defaultOptions, options);
var $this = $(this);
var data = $this.data('myBlinker');
// If the plugin hasn't been initialized yet
if ( ! data ) {
$this.data('myBlinker', {
on : true
});
}
});
},
destroy : function( ) { // Some code here},
blink: function ( ){
console.log("Blinking!. This: " + this);
var current = 0;
var button=this.get(0);
setInterval(function() {
if (current == 0){
button.options["blinkShow"].call(this);
current=1;
} else {
button.options["blinkHide"].call(this);
current=0;
}
}, button.options["interval"]);
}
};
$.fn. myBlinker = function( method ) {
// Method calling logic
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.myBlinker ' );
return null;
}
};
})(jQuery);
Any idea, correction, link or tip will be appreciated.
Thank you.
Within the setInterval function, this is the global object, not the current element DOMElement like in the blink function.
A solution to that is to save a reference of this and use this saved reference in the setInterval:
blink: function ( ){
// save a reference of 'this'
var that = this;
setInterval(function() {
// use the saved reference instead of 'this'
button.options["blinkShow"].call(that);
}, button.options["interval"]);
}
DEMO

Categories

Resources