Properly get "this" in jQuery plugin member - javascript

I have a JavaScript module that I would like to create a jQuery plugin interface to.
The module itself is like this:
var Foo = (function () {
"use strict";
var self = {};
self.add = function (selector, otherParam)
{
// Does things unto selector.
// Precisely what it does doesn't matter.
};
return self;
}());
and is used, with success, like this:
Foo.add(a);
Now, I would like to create a plugin that interfaces to this module,
so I can use it somewhat like this:
$.fn.foo = Foo;
$.fn.foo.add = function (param) {
var selector = this;
Foo.add(selector, param);
}
$(elem).foo.add(a);
The problem I'm facing is that I can't get "this" working in .add().
The best way I managed to do it was to not have Foo be self-initializing,
and use a syntax like:
$.fn.foo = Foo;
$(elem).foo().add(a);
It works, but I find it less aesthatically pleasing and less "clean".
Is there a way to do this? Am I on the wrong approach altogether?
Thankful for any input, and I apologize in advance if this has already been answered or is unfit in any other way.
I did search for answers, but I'm not well-versed in plugin authoring nor an expert on jQuery itself.
TL;DR: I have a module like Foo above, and would like to access it's members like a jQuery plugin.

Here is a simplified version of the pattern I normally use (error checking and extra features removed).
It uses a single class function and a plugin bridge extension method to allow attachment to multiple elements. Methods are called by using a string option value:
var Foo = (function () {
"use strict";
// Constructor
function Foo($element, options){
this.$element = $element;
this.options = options
this.fooVal = 0;
}
// Create method (called from bridge)
Foo.prototype.onCreate = function(){
this.fooVal = ~~this.$element.text()
};
// Add the specified val to the elements current value
Foo.prototype.add = function (val) {
this.fooVal += val;
// Update the element text with the new value
this.$element.text(this.fooVal);
};
return Foo;
})();
// Create a bridge to each element that needs a Foo
$.fn.foo = function (options, args) {
this.each(function () {
var $element = $(this);
// Try to get existing foo instance
var foo = $element.data("Foo");
// If the argument is a string, assume we call that function by name
if (typeof options == "string") {
foo[options](args);
}
else if (!foo) {
// No instance. Create a new Foo and store the instance on the element
foo = new Foo($element, options);
$element.data("Foo", foo);
// Record the connected element on the Foo instance
foo.$element = $element;
// Call the initial create method
foo.onCreate();
}
});
}
// testing
console.clear();
$('#test').foo();
$('#button2').click(function () {
$('#test').foo("add", 2);
});
$('#button10').click(function () {
$('#test').foo("add", 10);
});
For your example Foo takes the initial value from the element text and subsequent "add" calls modify that value.
JSFiddle: http://jsfiddle.net/TrueBlueAussie/o2u7egfy/3/
Notes:
~~ is just a fast short-cut for parseInt()
You could supply an initial value via the options parameter (ignored in first example). See following:
e.g.
// Create method (called from bridge)
Foo.prototype.onCreate = function(){
this.fooVal = this.options.value || ~~this.$element.text()
// Set initial value
this.$element.text(this.fooVal);
};
and start with
$('#test').foo({value: 999});
JSFiddle: http://jsfiddle.net/TrueBlueAussie/o2u7egfy/4/

Related

Create parameter based jquery plugin

I have a long jquery function and I want to separate it as an plugin.
Normally, most jquery plugin has a selector element as below.
;( function( $, window, document, undefined )
{
'use strict';
$.fn.myPlugin = function( options )
{
// codes goes here
}
})( jQuery, window, document );
and the usage is something like this.
$('.element').myPlugin();
But in my case, the javascript function doesn't really need a selector element, it only expect a value(parameter).
e.g
function coolMethods(Str){
return Str + doSomething;
}
So, how can I use this as a plugin?
I would expect the usage is like this
myPlugin.coolMethods(Str) // and this should return a value.
If you don't need any jquery functionality (the $(selector). part) and you want to call it like
myPlugin.CoolMethod("abc")
then you need a namespace rather than a plugin.
There's a number of ways to generate namespaces and scripts to help this, but essentially a namespace is an object that contains functions, the basic setup would be:
var myPlugin = {};
myPlugin.CoolMethod = function(param1) {
return param1 + param1;
};
// to use this:
console.log(myPlugin.CoolMethod("abc"))
Once you start using namespaces, you can then have private methods/variables within that namespace if needed and can easily add other methods / sub-namespaces.
Another common way to setup a namespace is something like:
var myPlugin = myPlugin || {};
(function(myplugin, $) {
// can also use $ within the namespace declaration
myplugin.CoolMethod = function(param1) {
return param1 + param1;
};
})(myPlugin, jQuery);
// to use this:
console.log(myPlugin.CoolMethod("abc"))
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
There is no problem! you can create your plugin in this case and use it on each element without any relation with it!
generally, is not necessary that your function uses JQuery elems.
example:
$.fn.NewFunc /*Your plugin function*/ = function(param1){
console.log("Oh! It's ok!!");
console.log(arguments);
};
Usage:
$.fn.NewFunc("Hello!");
or
$("").NewFunc("I Am An Argument!");
or even:
$("AnySelector").NewFunc("param1", "param2");
If you dont need selected elems by selector, you can use other way similar these (this is not a plugin):
$.NewFunc = function(p1, p2){/*do somethings*/};
and use it similar this:
$.NewFunc("param1", "param2");
Here is my version
(function ($, window, document, undefined) {
'use strict';
var PLUGIN_NAME = 'myPlugin';
function plugin(options) {
var opts = $.extend(true, {}, $[PLUGIN_NAME].defaults, options);
_print(opts.name);
}
var _print = function (name) {
console.log(name);
};
$[PLUGIN_NAME] = function (options) {
new plugin(options);
};
$[PLUGIN_NAME].defaults = {
name: 'John'
};
}(jQuery, window, document));
Usage:
$.myPlugin({
name: 'Suzy'
});
Or
$.myPlugin();
Working example

Override JavaScript (window) Function

I'd like to override a function which is being made by a javascript plugin. I know how to override regular window functions, but this is different. I'm not sure how to name it, but the structure of that function is like:
window.something.function
I have no idea how to override that. I have tried the following:
var originalFunction = window.something.function;
window.something.function = function(parameter) {
alert('called');
return originalFunction(parameter);
}
But it's not working.
Does someone know a solution?
Edit:
As I have been told my question is unclear, I have edited it again using the actual names of the plug-in.
The plugin is meant to be used as:
var myColor = new jscolor(target, options)
When this is being used, there is a function "inside" the object "jscolor" which is being called when setting the value of target element. I want to override that function to add an extra functionality without changing the original js file.
Code:
if (!window.jscolor) { window.jscolor = (function () {
var jsc = {
.....
jscolor : function (targetElement, options) {
....
//Function I want to change:
this.exportColor = function (flags) {
if (!(flags & jsc.leaveValue) && this.valueElement) {
var value = this.toString();
if (this.uppercase) { value = value.toUpperCase(); }
if (this.hash) { value = '#' + value; }
if (jsc.isElementType(this.valueElement, 'input')) {
this.valueElement.value = value;
} else {
this.valueElement.innerHTML = value;
}
}
}
}
};
My attempts so far:
var origJsColor = jscolor.exportColor;
jscolor.exportColor = function(flags) {
console.log('called');
return origJsColor(flags);
}
and the window attempt above.
The jscolor code you've shown creates an object with its own copy of exportColor (one is created for each object). So to replace it, you have to replace it on each instance as the instance is created.
You can do that as a one-off in much the way you showed, just working with the instance rather than the plugin function, and using Function#call to call it with the right this:
// Get the instance
var c = new jscolor(target, options)
// Update it
var origExportColor = c.exportColor;
c.exportColor = function(flags) {
console.log('called');
return origExportColor.call(c, flags); // Note the changes on this line
};
Or instead of
return origExportColor.call(c, flags);
you might use
return origExportColor.apply(c, arguments);
...if there's any chance of the function being called with anything other than exactly one argument. (arguments is a magic pseudo-array containing the arguments used to call the function.)
If you want to do that for all instance you might create, you can put a facade in front of jscolor to do that to each instance:
var realJscolor = jscolor;
jscolor = function() {
// Call the real function, passing along all the arguments we
// get automatically (`arguments` is a magic pseudo-array)
var retVal = realJscolor.apply(this, arguments);
// If it returned a non-`null` object, we want to use that instead
// of `this`; if not, we keep using `this`
if (!retVal || typeof retVal !== "object") {
retVal = this;
}
// Slip in our version of exportColor
var origExportColor = retVal.exportColor;
retVal.exportColor = function(flags) {
console.log('called');
// (Maybe use `apply` here instead)
return origExportColor.call(retVal, flags);
};
// Return the result, in case the real function overrode `this`
return retVal;
};
jscolor.prototype = realJscolor.prototype;
Then just use jscolor normally:
var c = new jscolor(target, options);
The reason for the retVal thing is that although normally a new expression's result is a refernece to the new object created by new, a constructor function can return a non-null object reference and, if it does, the new expression's result is that object reference instead. That's why we check the return value of realJscolor.
Of course, that means that all uses of jscolor on the page that use the global will now use your updated function instead. If you don't want that, just use your own name and don't override jscolor:
var myColor = function() {
var retVal = jscolor.apply(this, arguments);
// ...and so on...
return retVal;
};
myColor.prototype = jscolor.prototype;
Usage:
var c = new myColor(target, options);
Function
function a() {alert(this)} // will print `window` obejct
is defined in the window scope. That is, it is a method of the window. Your more difficult situation comes from the fact that this is different from window if you define function as a method in another object.
var a = {method: function() {alert(this)}}
you call a.method() but see that the same window again. You need to bind your function to the parent object to make it compete method.

How to redefine private method in javascript?

I create component from Trumbowyg plugin to vue.js library. I need add two way binding in this beautiful wysiwyg editor.
How to rewrite buildEditor() method?
This method is private. how to do it correctly?
<script>
jQuery.trumbowyg = {
// options object
};
(function (navigator, window, document, $, undefined) {
$.fn.trumbowyg = function (options, params) {
// ... code ...
$(this).data('trumbowyg', new Trumbowyg(this, options));
// ... code ...
};
var Trumbowyg = function (editorElem, o) {
var t = this;
// ... code ...
t.init();
};
Trumbowyg.prototype = {
init: function () {
var t = this;
t.buildEditor();
// ... code ...
},
buildEditor: function () {
// i need rewrite this method
}
// code for otner method
};
})(navigator, window, document, jQuery);
// -------------------------------------
// other file. I want init this plugin
// here do need to rewrite buildEditor() method? What best way to do this?
$('.selector').trumbowyg();
If this plugin doesn't return 'Trumbowyg' variable it's not possible. I recommend fork this plugin and create your own version with your settings.
The best way to do it would be to fork the plugin yourself, as Slava answered. But technically you're able to modify that function.
Whenever you construct a class, that instance has its own constructor property. This is equal to the class function.
So, if you can get access to an instance of Trumbowyg, you're able to use its class:
$foo.trumbowyg(...)
var trumbowyg = $foo.data('trumbowyg')
var TrumbowygClass = trumbowyg.constructor
Now we can modify its prototype:
TrumbowygClass.prototype.buildEditor = function() {
// ...
}
You might want to make $foo be a temporary or unused element. That's because it'll have called the old buildEditor (as soon as you ran $foo.trumbowyg()), not your own modified version.
After you've set the prototype function you could run it on the element you actually want to use trumbowyg on (e.g. $('#target'))
As an example:
(function() {
window.makeInstance = function() {
return new HiddenClass()
}
var HiddenClass = function() {
this.setGreeting()
this.showGreeting()
}
HiddenClass.prototype.setGreeting = function() {
this.greeting = 'Hello, world!'
}
HiddenClass.prototype.showGreeting = function() {
console.log(this.greeting)
}
})()
var myTempInstance = makeInstance()
// Should log 'Hello, world!'
var HiddenClass = myTempInstance.constructor
console.log(HiddenClass) // Should be the HiddenClass function
// Now we overwrite our function..
HiddenClass.prototype.setGreeting = function() {
this.greeting = 'Redefined!'
}
var myUsedInstance = makeInstance()
// Should log 'Redefined!', since we redefined setGreeting
// And later we can use `myUsedInstance`.
// In this code myTempInstance is like $foo, and myUsedInstance
// is like $('#target').

"this" is a global object after using call()

I have stored the names of the methods in a list.
var list = ['fn1', 'fn2', 'fn3', 'fn4'];
I select the method using some criteria dynamically. The methods are part of a larger class that are attached using 'prototype
MyObj.prototype.selectfn = function(criteria) {
var fn = list[sel];
this[fn].call(this, arg1);
}
MyObj.prototype.fn1 = function(args) { // do something }
MyObj.prototype.fn2 = function(args) { // do something}
And so on. The problem is inside the selected "fn" function, the this variable appears as a global object even though I used call() I read the mozilla docs, but I'm not able to understand why this is so, can someone help out please?
It it helps, my environment is node.js 0.10.12.
Edit : It's a little hard to provide the correct sample code because my code involves callbacks in a lot of places, but I'll try to elucidate.
Assume two files User.js and Helper.js.
User.js
var m, h;
var Helper = require('./Helper');
function init() {
// to simplify, assume the `this` here refers to `User`
h = new Helper(this);
}
function doSomething() {
// pass some criteria string
h.selectfn(criteria);
}
Helper.js
var Helper = module.exports = function(user) {
this.user = user;
}
Helper.prototype.selectfn = function(criteria) {
// based on some criteria string, choose the function name from "list" array
// here, the string "sel" holds the selected function name
var fn = list[sel];
this[fn].call(this.user, arg1);
// if I print to console, `this.user` is correct over here, but inside the function it shows as undefined
}
Helper.prototype.fn1 = function(args) {
// Here, I talk to databases, so I have callbacks. Say, on a callback, the user property is to be updated. This is why I want to use `call()` so that the `this` refers to `User` and can be updated.
// For example, if we want to update the "last-seen" date.
this.lastseen = new Date();
}
Hope the little example made it clearer.
the first parameter of call() is your function context "this"
as example:
var someObject = {
withAFunction: function(text) { alert('hello ' + text); }
};
var testFunction = function(text) {
this.withAFunction(text);
};
testFunction.call(someObject, 'world');
I tried to review your code:
var list = ['fn1', 'fn2', 'fn3', 'fn4'];
//MyObj is copy of Object?
var MyObj = Object;
//or a preudoclass:
//var MyObj = function(){};
//inside this function you use sel and arg1 that seems undefined and use like arguments criteria that seems useless
MyObj.prototype.selectfn = function(sel,arg1) {
var fn = list[sel];
this[fn].call(this, arg1); // this is MyObj istance.
MyObj.prototype.fn1 = function(args) { console.log("fn1", args);/* do something */ }
MyObj.prototype.fn2 = function(args) { console.log("fn2",args); /* do something */ }
xxx = new MyObj();
xxx.selectfn(1,"ciao");
//call xxx.fn1("ciao");
see the console for response.

jQuery call plugin method from inside callback function

I am using a boilerplate plugin design which looks like this,
;(function ( $, window, document, undefined ) {
var pluginName = "test",
defaults = {};
function test( element, options ) {
this.init();
}
test.prototype = {
init: function() {}
}
$.fn.test = function(opt) {
// slice arguments to leave only arguments after function name
var args = Array.prototype.slice.call(arguments, 1);
return this.each(function() {
var item = $(this), instance = item.data('test');
if(!instance) {
// create plugin instance and save it in data
item.data('test', new test(this, opt));
} else {
// if instance already created call method
if(typeof opt === 'string') {
instance[opt].apply(instance, args);
}
}
});
};
})( jQuery, window, document );
Now say i have two <div> with same class container.
And now i would call my test plugin on these divs like so,
$(".container").test({
onSomething: function(){
}
});
Now when function onSomething is called from inside my plugin how can i call that plugin public methods referring to the instance onSomething function was called from?
For example something happened with the first container div and onSomething function was called for only first container div.
To make it a bit more clear I have tried to pass this instance to the onSomething function, that way i expose all plugin data and then i can do something like,
onSomething(instance){
instance.someMethod();
instance.init();
//or anything i want
}
To me me it looks quite wrong so there must be a better way... or not?
Well im not sure if it is the best idea, but you could pass the current object as a parameter. Let's say onSomething : function(obj) { }
So whenever "onSomething" is called by the plugin, you can call it like this: "onSomething(this)" and then refer to the object asobject`
Lets give a specific example.
var plugin = function (opts) {
this.onSomething = opts.onSomething;
this.staticProperty = 'HELLO WORLD';
this.init = function() {
//Whatever and lets pretend you want your callback right here.
this.onSomething(this);
}
}
var test = new Plugin({onSomething: function(object) { alert(object.staticProperty) });
test.init(); // Alerts HELLO WORLD
Hope this helps, tell me if its not clear enough.
Oh wait, thats what you did.

Categories

Resources