I have a really annoying scope issue for my JQuery Widget. Essentially I need to access the widget instance (this) inside of my map/object options.
Is it possible to do this? Any advice how I can achieve this?
$.widget( "my.myWidget", {
// Below 'this.defCallback' will be undefined
// How can I store 'this' (the widget instance) in a variable??
options: {
callback: this.defCallback // allow user to overwrite/provide custom callback
},
....
defCallback: function() {
console.log('defCallback');
}
});
If I had a nested function I know I can easily solve this but I have a nested object/map which makes things difficult.
function foo {
var _this = this;
...
var bar = function() {
// easily access this
_this.defCallback();
...
}
}
Usage:
$('<div></div>')
.myWidget(); // use defCallback
$('<div></div>')
.myWidget({
callback: function() {
...
}
}); // use custom callback
Edit: How the callback function is 'bound' and called:
_create: function() {
this.element.click( this.options.callback );
}
.click(value.callback(_this)
In javascript you could dynamically change the context of a function with the apply() and with call() methods.
On es5 you could use bind().
So your code:
_create: function() {
this.element.click( this.options.callback );
}
Became with apply():
_create: function() {
var el = this.element;
var callback = this.options.callback;
el.click(function() {
callback.apply(el);
// If you have parameters:
// callback.apply(el, arguments || array);
});
}
With call():
_create: function() {
var el = this.element;
var callback = this.options.callback;
el.click(function() {
callback.call(el);
// If you have parameters:
// callback.call(el, arg0, arg1, ...);
});
}
With bind():
_create: function() {
this.element.click(this.options.callback.bind(this));
}
UPDATE
As your issue is to have the this reference binded inside the object definition you need to change your code.
The quick way is is to emend it like this (from your fiddle):
var mw = {
defCallback: function () {
alert("abc");
},
_create: function () {
//this.populateOptions();
alert("Is undefined: " + this.options.isUndefined); // outputs 'true'
this.element.click(this.options.callback.bind(this));
},
populateOptions: function() {
if (this.options.callback === undefined)
this.options.callback = this.defCallback;
}
};
So you first define your object with the parent attributes and functions.
mw.options = {
//accessObjectParent: this.instantiator,
isUndefined: (mw.defCallback === undefined), // this refers to the map/object
// Can I access the maps 'parent'/instantiator?
// this.instantiator.defCallback ???
callback: mw.defCallback
};
Than you attach the options object and you could refer on the parent object instead of using this.
$.widget( "my.myWidget", mw );
And now you pass the object on your widget declaration.
Related
I'm working on a jQuery plugin that does not have a selector. When initializing it, I instanciate an object that has functions. In these functions, I need to use closures. In these closures, I would like to call my initial object functions.
To make it more clear, here is a simplified version of the code.
HTML
<script src="/path/to/jquery/2.1.1/jquery.min.js"></script>
<script src="/path/to/my/script/myeditor.js"></script>
<div class="editable">Div1</div>
<div class="editable">Div2</div>
<script>
$.myeditor({
option1: 'a',
option2: 'b'
});
</script>
myeditor.js
function ($) {
var MyEditor = function (options)
{
this.$options = $.extend(true, {}, $.myeditor.defaults, options);
this.init();
};
$.myeditor = function (options = {})
{
return new MyEditor(options);
};
$.flyeditor.defaults = {
option1: '1',
option2: '2'
};
MyEditor.prototype.notify = function(message = '')
{
console.log(message);
};
MyEditor.prototype.init = function()
{
// Do stuff
$('.editables').each(function() {
$this = $(this);
// Here comes the error
notify($this.html());
});
};
}(jQuery);
The problem is that notify(this.html()); raises an error ReferenceError: notify is not defined
How can I reach this notify method?
You can assign this to a separate local variable in a closure. You need to do that because this will no longer point to your MyEditor object inside the each, it will point to each of the .editables
Also, you probably meant to call this.notify(), since the function is attached to the prototype of MyEditor
MyEditor.prototype.init = function()
{
// Do stuff
var that = this; // <-- now we can reach this inside function.
$('.editables').each(function() {
$this = $(this);
// Here comes the error
// You can't use notify, that function is not defined
// You can't use this.notify because this points to something else (the node)
// inside the function in each
that.notify($this.html());
});
};
MyEditor.prototype.init = function()
{
// Do stuff
var self = this;
$('.editables').each(function() {
$this = $(this);
// Here comes the error
self.notify($this.html());
});
};
I've been looking at the plugin boiler plate for jQuery plugins, I find it ok but there is one major flaw in the design or maybe just something I can't figure out.
When I author plugins at the moment, it is easy for me to define publicly exposed methods and private methods that only the plugin has access to.
When I tried to do something similar in the boiler plate I was thwarted.
;(function ( $, window, document, undefined ) {
// Create the defaults once
var
pluginName = "defaultPluginName",
defaults = {
propertyName: "value"
};
// The actual plugin constructor
function Plugin ( element, options ) {
this.element = element;
this.settings = $.extend( {}, defaults, options );
this.defaults = defaults;
this.name = pluginName;
this.init();
}
Plugin.prototype.init = function() {
console.log('init')
console.log(this)
this.yourOtherFunction();
}
Plugin.prototype.yourOtherFunction = function () {
console.log('yourOtherFunction')
console.log(this)
this.yourOtherFunction2();
}
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction().bind(this)
}
var privateFunction = function() {
console.log('private')
console.log(this)
}
// A really lightweight plugin wrapper around the constructor,
// preventing against multiple instantiations
$.fn[ pluginName ] = function ( options ) {
return this.each(function() {
if ( !$.data( this, "plugin_" + pluginName ) ) {
$.data( this, "plugin_" + pluginName, new Plugin( this, options ) );
}
});
};
})( jQuery, window, document );
$(document).defaultPluginName()
Anyway you can see the function 'privateFunction' it's scope is to the window object, but what I want to be able to do is scope it to the Plugin instance, or basically 'this' from the prototype methods.
What I don't want to do, is pass the scope into each private function as a function argument!
So how can I bind the scope?
Console output
init
Plugin { element=document, settings={...}, defaults={...}, more...}
yourOtherFunction
Plugin { element=document, settings={...}, defaults={...}, more...}
private
Window index.html <-- I want Plugin, not window
You are calling privateFunction and then binding this as scope for its result. So use (as said by #Khanh_TO):
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction.apply(this,arguments);
}
Instead of:
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction().bind(this)
}
More details:
bind returns a copy of the function on which is called (the result of privateFunction in your case) after applying the scope you've passed in (this in your case). What bind does is something like:
Function.prototype.bind = function(scope) {
var _function = this;
var _args = [];
for (var i = 0, len = arguments.length-1; i < len; i++){ _args[i] = arguments[i+1]; }
return function() {
// returns the same function on which is called (not the same Function object, but
// another with same properties) with 'this' equal to the first parameter and
// the remaining specified parameters as parameters of the function returned
return _function.apply(scope, _args);
}
}
eg. myFunction.bind(newScope, param1, param2, ...) -> returns an anonymous function which in turns returns the function myFunction(param1, param2,....) with set this = newScope.
So, as a proof of concept, also this code would have worked:
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction.bind(this)();
}
but you should use the first one since the last one does the same thing with extra passages.
Replace:
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction().bind(this)
}
With
Plugin.prototype.yourOtherFunction2 = function () {
privateFunction.apply(this,arguments);
}
If I define prototype like this
jQuery.fn.myFunc = function() {
console.log(this);
}
and call it like this:
$('div').myFunc();
this inside of myFunc will refer to jQuery object selection.
Now if I don't want to pollute .fn with multiple attached functions, can I do something like
jQuery.fn.myPlugin = {
myFunc1: function() {
},
myFunc2: function() {
}
}
If I call it $('div').myPlugin.myFunc1(); - how do I get reference to selected objects inside of myFunc1? Or is there a different, better approach?
Nope. can't do it that way. Instead, define it as a function, then add properties to that function.
jQuery.fn.myPlugin = function() {
console.log(this);
};
jQuery.fn.myPlugin.myFunc1 = function() {
};
jQuery.fn.myPlugin.myFunc2 = function() {
};
note that myFunc1 and myFunc2 still wont' have access to the selected element, therefore it's relatively useless to define them this way other than the fact that they can be easily overridden by other developers (which is a good thing)
The normal way of having additional methods within your plugin is to have your plugin method accept a parameter that can eitehr be an object to init the plugin, or a string to execute a target method on the element. for example, $("somediv").myPlugin("myFunc1")
The jQuery plugin tutorial suggests this:
(function( $ ) {
$.fn.popup = function( action ) {
if ( action === "open") {
// Open popup code.
}
if ( action === "close" ) {
// Close popup code.
}
};
}( jQuery ));
I suppose this would be another alternative:
(function($) {
$.fn.myPlugin = function (action) {
var functions = {
open: function () {
console.log('open: ', this);
},
close: function () {
console.log('close:', this);
}
}
if (action && functions[action]) {
functions[action].call(this)
} else {
console.log('no function', this);
}
return this;
};
}(jQuery));
$('#theDiv')
.myPlugin()
.myPlugin('open')
.myPlugin('close');
http://jsfiddle.net/faJAk/
work if you create a object before.
Like this:
<script>
jQuery.fn.myPlugin = {};
jQuery.fn.myPlugin = {
myFunc1: function() {
console.log(this);
},
myFunc2: function() {
alert(this);
}
};
$(document).ready(function(){
$('div').myPlugin.myFunc1();
$('div').myPlugin.myFunc2();
});
</script>
Another possible approach is to use defineProperty:
(function($) {
var myPlugin = {
foo: function() {
console.log(this)
}
}
Object.defineProperty($.fn, "myPlugin", {
get : function() { return $.extend({}, this, myPlugin) }
});
})(jQuery);
Now $(...).myPlugin.foo should resolve this correctly:
$(function() {
$("body").myPlugin.foo(); // logs "body"
})
var JavascriptHelper = Backbone.Model.extend("JavascriptHelper",
{}, // never initialized as an instance
{
myFn: function() {
$('.selector').live('click', function() {
this.anotherFn(); // FAIL!
});
},
anotherFn: function() {
alert('This is never called from myFn()');
}
}
);
The usual _.bindAll(this, ...) approach won't work here because I am never initializing this model as an instance. Any ideas? Thanks.
You could do it by hand:
myFn: function() {
$('.selector').live('click', function() {
JavascriptHelper.anotherFn();
});
}
Or, if anotherFn doesn't care what this is when it is called (or if it wants this to be what live uses):
myFn: function() {
$('.selector').live('click', JavascriptHelper.anotherFn);
}
As an aside, live has been deprecated in favor of on. Also, if you're not instantiating your JavascriptHelper, then why is it a Backbone.Model at all? Why not use a simple object literal:
var JavascriptHelper = {
myFn: function() {
//...
},
anotherFn: function() {
//...
}
};
And what are you expecting this construct:
var JavascriptHelper = Backbone.Model.extend(string, {}, {...})
to leave you in JavascriptHelper? Extending a string is strange but passing three arguments to Backbone.Model.extend is pointless, it only cares about two arguments. If you want static properties then you should be passing them as the second argument:
var JavascriptHelper = Backbone.Model.extend({}, { myFn: ... });
I have the following (simplified) code:
var Foo = (function () {
var data = {},
settings = {
// default settings here
};
function bar(callback) { // bar is an asynchronous function
var result = null;
// fiddle around until you get a result
if (callback) {
callback(result);
}
}
return {
init: function (options, callback) {
var kallback = callback;
$.extend(settings, options);
bar(function () {
if (kallback) {
kallback(WHAT_GOES_HERE);
}
});
},
debug: function () {
return {
settings: settings,
data: data
};
},
set: function (k, v) {
settings[k] = v;
},
get: function (k) {
return settings[k];
}
};
}());
The code above is in a js file, then in the footer of the page in question:
<script type="text/javascript">
Foo.init({ option1: "value", option2: "value" }, function (obj) {
console.log("The object was ", obj);
});
</script>
Basically, here is what I want to be able to do:
Create an object (with a set of optional params, but not important for this question)
During the creation phase of the object, have it call an asynchronous function
When the asynchronous function is done, I should be able to trigger a callback, and the argument for the callback should be the intialized object
I thought that this would work for WHAT_GOES_HERE above, but turns out, at least when I've tested it, that this is the DOM Window object.
First of all, am I constructing this object correctly? Or is there a better way to create it?
Secondly, assuming I am doing this right, what should go in the WHAT_GOES_HERE so that when console.log("The object was ", foo); runs, the value for obj is the created Foo object?
Yes, in an anonymous function called this way, this will refer to the window object. To refer to the this reference from the init method you have to store the reference in another variable:
var kallback = callback, self = this;
$.extend(settings, options);
bar(function () {
if (kallback) {
kallback(self);
}
});
Your way of constructing the object works, as long as you only want to have one single foo object.