I'm am trying to create a jQuery plugin that will add new namespace functions to the context object(s), while maintaining full chain-ability. I'm not sure if it's possible, but here's an example of what I have so far:
(function ($) {
var loadScreen = $('<div />').text('sup lol');
$.fn.someplugin = function (args) {
var args = args || {},
$this = this;
$this.append(loadScreen);
return {
'caption' : function (text) {
loadScreen.text(text);
return $this;
}
};
}
})(jQuery);
This works fine if I do $(document.body).someplugin().caption('hey how\'s it going?').css('background-color', '#000');
However I also need the ability to do $(document.body).someplugin().css('background-color', '#000').caption('hey how\'s it going?');
Since .someplugin() returns it's own object, rather than a jQuery object, it does not work as expected. I also need to be able to later on access .caption() by $(document.body). So for example if a variable is not set for the initial $(document.body).someplugin(). This means that somehow how .caption() is going to be set through $.fn.caption = function () ... just for the document.body object. This is the part which I'm not quite sure is possible. If not, then I guess I'll have to settle for requiring that a variable to be set, to maintain plugin functions chain-ability.
Here's an example code of what I expect:
$(document.body).someplugin().css('background-color', '#000');
$('.non-initialized').caption(); // Error, jQuery doesn't know what caption is
$(document.body).caption('done loading...');
Here's what I'm willing to settle for if that is not possible, or just very inefficient:
var $body = $(document.body).someplugin().css('background-color', '#000');
$('.non-initialized').caption(); // Error, jQuery doesn't know what caption is
$body.caption('done loading...');
The be jquery-chainable, a jQuery method MUST return a jQuery object or an object that supports all jQuery methods. You simply have to decide whether you want your plugin to be chainable for other jQuery methods or whether you want it to return your own data. You can't have both. Pick one.
In your code examples, you could just define more than one plugin method, .someplugin() and .caption(). jQuery does not have a means of implementing a jQuery plugin method that applies to one specific DOM object only. But, there is no harm in making the method available on all jQuery objects and you can only use it for the ones that it makes sense for.
I think you could use this:
(function ($) {
var loadScreen = $('<div />').text('sup lol');
$.fn.someplugin = function (args) {
var args = args || {},
$this = this;
$this.append(loadScreen);
return(this);
}
$.fn.caption = function (text) {
loadScreen.text(text);
return this;
}
})(jQuery);
$(document.body).someplugin().css('background-color', '#000');
$('.non-initialized').caption('whatever');
$(document.body).caption('done loading...');
If there's supposed to be some connection between the two .caption() calls, please explain that further because I don't follow that from your question.
Related
I've had a look at this thread: How to create a jQuery plugin with methods?, and while there are plenty of solutions regarding multi-function plugins, I'm interested if it's possible to use an object so that you don't have ugly constructions like $("selector").plugin().dostuff(), and instead can use it more like this: $("selector").plugin.dostuff(), ie 'plugin' is an object not a function.
I can write the plugin like this:
$.fn.plugin = {
dostuff: function() {},
domorestuff: function(){}
};
But then the inner functions won't have access to the JQuery object that's calling it in the first place, as this returns the plugin object. Is there any way to structure a plugin based around an object that would work with JQuery?
No, and for the reason you gave: You lose this, and this is hugely important when writing a plugin. To get this, your plugin must be a function.
You basically have three ways of implementing methods:
Using string arguments, e.g.:
$("selector").plugin("dostuff");
...where you dispatch to your handler methods within the plugin function. This is by far the most popular way. It's the way used by jQuery UI, Bootstrap, and others. Full example of this below.
Using the "ugly construction" as you put it, where your plugin is a function and it returns an object (a new object each time, to preserve this) with functions you can call.
Adding several plugin methods to jQuery rather than just one, using a prefix of some kind, so it ends up being $("selector").pluginDoThis(); and $("selector").pluginDoThat(); and $("selector").pluginDoTheOther(); The key is to use a prefix to avoid name conflicts. I've only seen this done once, a long time ago, and I haven't seen that plugin in a while. Not really a popular choice.
I just recently posted this other answer showing the full pattern for #1 (in their case, the methods were callThis and destroy), e.g.:
// Create the plugin
(function ($) {
var methods = {
init: function(options) {
// Determine options
var opts = $.extend({
opacity: 0.5
}, options);
// Remember them
this.data("pluginname", opts);
// Initial stuff
this.css("opacity", opts.opacity);
},
callThis: function(opts) {
// Use 'opts' if relevant
this.css("display","none");
},
destroy: function(opts) {
this.removeData("pluginame");
this.css("display", "").css("opacity", "");
}
};
jQuery.fn.pluginname = function (options) {
var method, args;
// Method?
if (typeof options === "string") {
// Yes, grab the name
method = options;
// And arguments (we copy the arguments, then
// replace the first with our options)
args = Array.prototype.slice.call(arguments, 0);
// Get our options from setup call
args[0] = this.data("pluginname");
if (!args[0]) {
// There was no setup call, do setup with defaults
methods.init.call(this);
args[0] = this.data("pluginname");
}
}
else {
// Not a method call, use init
method = "init";
args = [options];
}
// Do the call
methods[method].apply(this, args);
};
})(jQuery);
// Example usage
function doInit() {
$("#target").pluginname();
setTimeout(doCallThis, 700);
}
function doCallThis() {
$("#target").pluginname("callThis");
setTimeout(doDestroy, 700);
}
function doDestroy() {
$("#target").pluginname("destroy");
setTimeout(doInit, 700);
}
setTimeout(doInit, 700);
<div id="target">This is the target</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I need to incorporate a little canvas app I built into a huge mess of a Sharepoint site where I do not have any control over anything, everything. The app will go into a "WebPart" wrapper div. To keep things nice and tidy in my JS I was thinking I could a cloned version of jQuery that always uses my wrapper object as context so that I can be sure I'll never select and alter anything outside of my wrapper like:
var ctxed$ = ctxjQuery('#wrapper-el'); //this is what I don't get
(function($){
$('p').addClass('awesome'); //should only affect elements inside #wrapper-el
})(ctxed$)
Anyhow, things I tried did not work out. I tried using $.proxy which did not work as I expected (as context is a different thing here...).
This is the point where I am stuck at the moment:
function getContextifiedjQuery(ctx) {
var fn = function () {
return $.call(arguments[0], ctx);
}
$.each($, function (k, v) {
fn[k] = v;
});
return fn;
}
var wrapper$ = getContextifiedjQuery('#wrapper');
wrapper$('p').css('color', 'red');
wrapper$.each([1, 2, 3], function () {
alert(this)
});
which works as a simple test case, yet when I try to use it in my project I get circular references and things go awry. What is it that I am missing here?
What you are doing with $("p") is basically
ctxjQuery('#wrapper-el')('p')
If you want to work off the context it would be
(function($){
$.find('p').addClass('awesome'); //should only affect elements inside #wrapper-el
})(ctxed$)
If you want to write the wrapper, it would look something like this, but it will only support find(). It will override everything else.
function ctxjQuery(context) {
return function (selector) {
return $(context).find(selector);
}
}
var ctxed$ = ctxjQuery('#wrapper-el'); //this is what I don't get
(function ($) {
$('p').addClass('awesome'); //should only affect elements inside #wrapper-el
})(ctxed$)
JSFiddle
and thinking about it again, you can do something like this:
function ctxjQuery(context) {
var fnc = function(selector){ return $(context).find(selector); };
var $Fnc = $.extend( fnc, $);
return $Fnc;
}
var ctxed$ = ctxjQuery('#wrapper-el'); //this is what I don't get
(function ($) {
console.log($.each);
$('p').addClass('awesome'); //should only affect elements inside #wrapper-el
})(ctxed$)
JSFiddle
It will break a lot of stuff!!! Example $("<p/>") will not create an element since it is overloaded with find(). Basically you would need to recreate the jQuery core function to get all the functionality!
Try this
var ctxed$ = function (selector)
{
return $(selector, '#wrapper-el');
}
(function($){
$('p').addClass('awesome'); //should only affect elements inside #wrapper-el
})(ctxed$);
I try to move some common application specific actions to jQuery plug-in by:
$.fn.extpoint = function() {...}
But I don't want to declare several extension points:
$.fn.extpoint1 = function() {...}
$.fn.extpoint2 = function() {...}
...
Instead I would like to use syntax sugar like:
$("#id").extpoint.func1().extpoint.func2()
With definition:
$.fn.extpoint = {}
$.fn.extpoint.func1 = function() {
this.val();
this.data("ip");
...
return this;
}
and call:
$("#id").extpoint.func1(...)
this point to $.fn.extpoint (dictionary with func1, func2, ... elements) instead of original jQuery object, when func1 evaluated.
Is it possible to make jQuery plug-in extendible?
PS. It is possible to pass function name as first argument to $.fn.extpoint and implement $.fn.extpoint('extend', func) call to extend (save to internal dictionary association between names and implementations) extension point. In that case use-cases look like:
$("#id").extpoint('func1', ...).extpoint('func2', ...)
but I look for way to make in more syntactic sugar...
The task I ask is hard to implement.
Official docs say:
Under no circumstance should a single plugin ever claim more than one namespace in the jQuery.fn object:
(function( $ ){
$.fn.tooltip = function( options ) {
// THIS
};
$.fn.tooltipShow = function( ) {
// IS
};
$.fn.tooltipHide = function( ) {
// BAD
};
})( jQuery );
This is a discouraged because it clutters up the $.fn namespace. To remedy this, you should collect all of your plugin's methods in an object literal and call them by passing the string name of the method to the plugin.
Another approach is maintain link to this as in http://code.google.com/p/jquery-plugin-dev/source/browse/trunk/jquery.plugin.js
So your calls looks like:
$.fn.addPlugin('test2', {
__construct : function(alertText) { alert(alertText); },
alertAttr : function(attr) { alert($(this).attr(attr)); return this; },
alertText : function() { alert($(this).text()); return this; }
});
$('#test2').bind('click', function() {
var btn = $(this);
btn.test2('constructing...').alertAttr('id').alertText().jQuery.text('clicked!');
setTimeout(function() {
btn.text('test2');
}, 1000);
});
Some related links:
http://milan.adamovsky.com/2010/02/how-to-write-advanced-jquery-plugins.html
http://milan.adamovsky.com/2010/09/jquery-plugin-pattern-20.html
http://ludw.se/blog/articles/19/patching-milans-jquery-plugin-pattern-for-jquery-16
http://code.google.com/p/jquery-plugin-dev/source/browse/trunk/jquery.plugin.js
Old style plug-in extention:
http://docs.jquery.com/Plugins/Authoring
jQuery Plugin Authoring and Namespacing
http://coding.smashingmagazine.com/2011/10/11/essential-jquery-plugin-patterns/
http://mahtonu.wordpress.com/2010/09/20/jquery-plugin-authoring-step-by-step/
http://www.capricasoftware.co.uk/corp/template.php
Here is an overview of creating a plugin. I believe what you are asking about is called "chaining". It is what makes jQuery so easy to use, and it's good that you want to make sure that you are implementing it correctly.
The key thing to remember while developing your plugin in regards to chaining is to always return this; from your methods. That is what will allow you to keep the chain going.
Is it inadvisable to add methods to a JQuery element?
eg:
var b = $("#uniqueID");
b.someMethod = function(){};
Update
Just to clarify, I am working on a JS-driven app that is binding JSON data to local JS objects that encapsulate the business logic for manipulating the actual underlying DOM elements. The objects currently store a reference to their associated HTML element/s. I was thinking that I could, in effect, merge a specific instance of a jquery element with it's logic by taking that reference add adding the methods required.
Well, there's nothing inherently wrong with it. It is, however, pretty pointless. For example:
$('body').someMethod = function(){};
console.log($('body').someMethod); // undefined
You are attaching the new function only to that selection, not to all selections of that element.
What you should do instead is to add a new function to jQuery.fn, which is a shortcut for jQuery.prototype:
jQuery.fn.someMethod = function() {
if (this[0].nodeName == 'body') {
// do your function
}
return this; // preserve chaining
};
The problem is that your function would be quite transient. A further requery and it will be gone. You can extend the jQuery object itself by $.fn.someMethod = function() {} and this method will be available for all queries.
$.fn.someMethod = function() {}
var b = $("body");
b.someMethod();
Or you can create a jQuery plugin. You can define a plugin this way:
$.fn.someMethod = function(options) {
# ...
});
Call it using $('body').someMethod();
According to the developer documentation jquery plugins are supposed to have only one namespace for all functions they make available. Which is straight forward as long as you only expose a single function per context (static/element).
(function($){
var
state_a = 0,
$.myplugin = function(in_options) {
// static
return this;
}
$.fn.myplugin = function(in_options) {
// element
return this;
}
})(jQuery);
This makes calls like this possible:
$("elem").myplugin(options);
jQuery.myplugin(options);
What's the best approach if you have more than one function and need to share state? I would like to call into my plugin like this:
$("elem").myplugin.start(options);
$("elem").myplugin.stop();
jQuery.myplugin.start(options);
jQuery.myplugin.stop();
I've used arguments.callee before:
(function ($) {
$.fn.pagination = function (options) {
var defaults = {
};
options = $.extend(defaults, options);
var object = $(this);
arguments.callee.updatePaging = function () {
//do stuff
};
});
Then, on your page:
var pagination = $("#pagination");
pagination.pagination();
pagination.pagination.updatePaging();
jQuery plugins tend to use a string parameter for different functions:
$(...).myplugin('start', options);
$(...).myplugin('stop');
I can't think of an easy way to use the syntax you would like to use, since having an extra property inbetween will make this point to something else than the jQuery object with the selected elements. Personally I find using a string parameter to completely change what the function does somewhat ugly too. $(...).myplugin().function() would be doable, but likely not as efficiently as just using that string parameter.
This is explained well in the docs: http://docs.jquery.com/Plugins/Authoring#Namespacing