Let’s say I have following simple plugin:
(function ( $ ) {
$.fn.greenify = function() {
this.css( "color", "green" );
return this;
};
}( jQuery ));
I don’t want to make any changes to the plugin itself, but I would like to wrap with another function and extend its namespace as follow:
(function($) {
$.fn.myFunction = function () {
(function ($) {
$.fn.greenify = function () {
this.css("color", "green");
return this;
};
}(jQuery));
};
})(jQuery);
So I can call the plugin function like this:
$(selector). myFunction().greenify();
Basically I want to disable calling ‘greenify’ function directly.
Is it possible?
It's not clear from the question, but I'm assuming the simple plugin "greenify" is a third-party or other "forced to use" plugin that you, for whatever reason, can't change. Let's also assume that it's actually quite a complicated plugin and simplified for the sake of the question.
This means
you can't change it
you can't duplicate the entire plugin inside your wrapper
The usual method for overwriting something is to take a copy, then make the new version do what you want, possibly calling the old version, eg:
var oldfoo = foo;
foo = function() {
alert("foo called");
oldfoo(); // or oldfoo.apply(this) to be clearer
}
The same principle can be applied here, but instead make 'foo' (in the example above) null - to get rid of it, eg:
var oldfoo = foo;
newfoo = function() {
alert("foo called");
oldfoo(); // or oldfoo.apply(this) to be clearer
}
foo = null;
The complication is with jquery and wanting to keep the method chaining, which can be achieved by storing 'this' and applying it as desired.
Here's the full code with explanation comments:
// The original plugin to be wrapped
(function ( $ ) {
$.fn.greenify = function() {
// changed to background-color for more impact (demo purposes)
this.css( "background-color", "lightgreen" );
return this;
};
}( jQuery ));
(function($) {
// allow this to be referred to later
// inside another loop where 'this' is something else
var me = this;
// take a copy of the original
// this stays inside the scope of (function($)) so can't be seen outside
me.original_greeny = $.fn.greenify;
// provide a wrapper
$.fn.myFunction = function () {
// the jquery elements for applying later
var $this = $(this)
// exported function
return {
greenify: function() {
// Call the original function with the jquery elements
// and return them for chaining
return me.original_greeny.apply($this)
}
};
};
})(jQuery);
// Now remove the original completely
(function ( $ ) {
$.fn.greenify = null;
}(jQuery));
// As desired, also demonstrating chaining still works
$("#a").myFunction().greenify().css("font-style", "italic")
// Confirm that the original has been removed
// gives error $(...).greenify is not a function
try {
$("#a").greenify()
} catch(e) {
$("#a").text("error on $().greenify: " + e)
}
and a jsfiddle
If you want to create your own context, one way is to return an object with a set of functions:
(function($) {
$.fn.myFunction = function () {
// Cache jQuery object
var $this = this;
// Return interface that acts on jQuery object
return {
greenify: function () {
$this.css("color", "green");
return $this;
}
};
};
})(jQuery);
Then you could call it using:
$(selector).myFunction().greenify();
Fiddle
Edit: As a warning though, when you do this you are leaving the jQuery chaining context after calling .myFunction, which can be very confusing in code.
You definitely don't want to take the current shown approach, because every time myFunction is called, it will also assign greenify. Simply take your myFunction plugin, assign a flag to the jQuery object that was constructed, and then check for that flag in greenify.
(function($) {
$.fn.myFunction = function () {
this.greenMarker = true;
return this;
};
$.fn.greenify = function () {
if(!this.greenMarker) return this;
this.css("color", "green");
return this;
};
})(jQuery);
$('.f').greenify();
$('.g').myFunction().greenify();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="f">No Green</div>
<div class="g">Green Me</div>
I think you're talking about method chaining which is the beauty of jQuery methods. Define your new method as follows, but leave greenify() unchanged:
(function($) {
$.fn.myFunction = function () {
return this.each(function() {
//Do stuff
});
};
})(jQuery);
(function ( $ ) {
$.fn.greenify = function() {
this.css( "color", "green" );
return this;
};
}( jQuery ));
(function($) {
$.fn.myFunction = function () {
return this.each(function() {
$(this).css({border:'1px solid black',textAlign:'center'});
});
};
})(jQuery);
$('.box').myFunction().greenify();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="box">COLOR CHANGE</div>
Pretty sure so long as your myFunction returns this, that you should be able to chain whatever you want to it.
(function ( $ ) {
$.fn.greenify = function() {
this.css( "color", "green" );
return this;
};
}( jQuery ));
(function($) {
$.fn.myFunction = function () {
// Do some stuff
return this;
};
})(jQuery);
$(selector).myFunction().greenify();
EDIT: If the whole point is to "disable calling greenify directly", why would you extend jQuery in the first place? 'greenify could be nested and triggered by passing an argument of some kind. Something like:
(function($) {
$.fn.myFunction = function (options) {
// Do some stuff
if (options.greenify) {
this.css("color", "green");
}
return this;
};
})(jQuery);
$(selector).myFunction({greenify: true});
... or you could just define greenify as a function (instead of a plugin) and call it. But the point of defining plugins is so they can then be called globally.
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"
})
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.
(function( $ ){
$.fn.foo = function(params) {
params = $.extend( {
on: false
}, params);
this.each(function(){
if(params.on) {
function alertDate(){
alert('FOO BAR!!!');
}
}
});
};
})( jQuery );
How can i access the "alertDate()" function from out of the script?
if i use:
$('#test').foo()
will give-me access for the function, ok, everything fine, i will gain access to function "alertDate()" internally at this.each(function(){}).
I want to access the function "alertDate()" externally by something like:
$('#text').foo({on: 'true'}).each().alertDate();
How can i do this?
Thanks in advance and sorry for the poor english
Here's how I would handle it, so that your alertDate() function can be used both internally and externally:
$.fn.foo = function(params) {
params = $.extend( {
on: false
}, params);
this.alertDate = function(){
alert("FOO BAR!!!");
}
this.each(function(){
if(params.on) {
this.alertDate();
}
});
return this;
};
//Externally the alertDate function could be called as:
$("test").foo().alertDate();
You can do something like this, but this is not how plugins normally work.
$.fn.foo = function() {
function bar() {
alert("bar");
}
this.bar = bar;
return this;
};
$("a").foo().bar();