jQuery Plugin structure: Accessing JavaScript class inside plugin definition? - javascript

I have a question regarding the structure of a jQuery plugin that I found.
For better understanding, here is a simplified example of the plugins structure:
// Regular constructor function
function MyPlugin() {
this.myValue = "My Value";
}
// Methods on the prototype
MyPlugin.prototype.showValue = function() {
alert($.myplug.getValue());
}
MyPlugin.prototype.getValue = function() {
return this.myValue;
}
// jQuery plugin
$.fn.myplug = function() {
// Why is is possible to access $.myplug here although it's not created yet?
return this.each(function() {
$(this).html($.myplug.getValue());
});
};
// Create new MyPlug instance
$.myplug = new MyPlugin();
// Calling the jQuery plugin on a DOM element
$('div').myplug();
For the most part, I get what is happening. The actual plugin logic seems to be written as a normal JavaScript "class".
This is followed by a jQuery plugin definition – I think, actually, some new method is added to jQuery's prototype. This is where things get tricky to me:
How is is possible to access the class instance inside the plugin, although the class is instantiated after the plugin definition? Is there a mechanism at work similar to variable hoisting?
In case you want to try something, here is a Fiddle of the example: http://jsfiddle.net/kq8ykkga/

$(this).html($.myplug.getValue()); isn't evaluated until you call $('selector').myplug(), executing the function body.

Related

JQuery plugins and prototype, exposing methods

I have this really old unmaintained plugin which in a nutshell is structured like this:
The plugin itself work fine, but I need to access some of the prototype methods.
var $el = $.find('myEl');
$el.searchlight(foo, bar);
$el.clearResults() //this throws an exception
When I call a prototype method I get an exception that the method doesn't exist. Did I get something completely wrong with prototype or am I just using it the wrong way.(I can hack the library if that's required since it's unmaintained for 7 years now).
An additional question would be how I can make the initializer return the element itself, would I just have to add 'return this.each' as last statement in $.fn.searchlight?
To solve this you could store the instance of the plugin within a data attribute on the element. You can then call methods on that function as required, something like this:
// in plugin:
$.fn.searchLight = function(url, options) {
return this.each(function() {
$(this).data('searchlight', new SearchLight(this, url, options));
});
};
// in the calling code, instantiate
var $el = $('.searchLightElement').searchLight(foo, bar);
// then use the methods of the plugin
$el.data('searchlight').clearResults();
Working example

How to write and/or call a jQuery plug-in?

I'm trying to follow this article to write a simple jQuery plugin: http://brolik.com/blog/how-to-create-a-jquery-plugin/
I seem to always get the following error in my console:
HTML
<div id="prod-part">TODO write content</div>
Javascript
(function($){
$.blogPost = function(el, options) {
var base = this;
base.$el = $(el);
base.el = el;
base.$el.data('blogPost', base);
base.init = function(){
console.log("hello");
};
};
})(jQuery);
$(function () {
$('#prod-part').blogPost();
});
Here is a simple jsfiddle which still creates the issue. I'm not sure If I am calling on the plug-in incorrectly or if the plugin is coded incorrectly. I've tried jQuery versions 1.7.2 and 1.11.0 and still come out with the same results. Any suggestions would be greatly appreciated.
http://jsfiddle.net/45oLp31m/1/
Background:
The jQuery function (jQuery() or $()) acts as a factory to return a new instance of jQuery collection when you pass in a selector. So $('#foo') returns an instance of jQuery collection. In order to call methods off of that instance, like $('#foo').somePlugin(), those methods have to be defined on the instance. The primary way we get methods onto instances is to add them to the constructor's prototype.
Solution
So the solution to your specific error is that jQuery plugins are defined on the jQuery collection constructor's prototype. This prototype is aliased at jQuery.fn (and jQuery is aliased as $, so $.fn is also ok). Adding methods to the prototype is as simple as $.fn.somePlugin = function () {}.
Thus your plugin needs to be defined like:
$.fn.blogPost = function(el, options) {
More
As I said, this is for the specific error you quoted. I assume at this point that you haven't made it much further in your tutorial, so I won't go into the other issues in your code (like the fact that your plugin does not return the collection for chaining).
Instead of
$.blogPost = function(el, options) {...}
Try
$.fn.blogPost = function(el, options) {...}
Functions in the $.fn namespace are available as methods on jQuery collections, which is what you want.
$(selector).blogPost();
Such methods a generally known as "plugins".
In other (rarer) circumstances, you might want to extend the jQuery namespace itself, in which case your $.foo = function() {...} would be applicable. Such functions are known as "static methods".

How does the skeleton of jQuery work?

I've been programming in JavaScript for a couple of years, but I've never understood how different techniques work in JavaScript, only that it works..
Now, after learning properly how prototypes and constructors work, I took a look at jQuery to learn something before I set out to make my own (not publicly accessible) plugin for my own website.
The problem is just that I don't understand how it works. Here is a almost working sample:
http://jsfiddle.net/3zWvR/1/
(function() {
test = function(selector) {
return new test.prototype.init(selector);
}
test.prototype = {
init: function(selector) {
alert("init ran");
if (!arguments[0]) {
return this;
}
}
}
// As I understand the jQuery code, the next line should really be
// test.prototype = {
test.prototype.init.prototype = {
send: function() {
alert("send ran");
}
}
window.ob = test;
})()
ob().send();​
I've commented a line in there that shows what I think really should be there if I do it like jQuery. But I'm not able to replicate it so that you could do ob.method() either...
How is the jQuery "framework" or skeleton built and how does it work?
Well, your question is very interesting, and it's something that has been on my head since I started to look at the source code of jQuery. Your code should work as you wanted to if you add this:
test.prototype.init.prototype = test.prototype;
test.send = test.prototype.send = function(){
alert("send ran");
};
instead of this:
test.prototype.init.prototype = {
send: function() {
alert("send ran");
}
};
You are probably saying "I already know that, you are just adding the method send() to the test object and to the prototype of itself", but doing it this way, you are doing exactly what you want: make that ob, ob() and all the variables created with it, like var somevar = ob() have the method send().
If you take a look at the jQuery source code, they use the method extend() to extend the jQuery object. Looking at some of the .extend() calls, you will see that if one method/property is added only to the jQuery.fn object (that is a shorthand to the prototype), the jQuery object doesn't have that method/property. You can see this if you type in a console jQuery.off, it will return undefined, but the method off exists in the jQuery.fn object (type jQuery.fn.off in a console and you will see it).
If you think in jQuery, when you write a plugin, you start by doing jQuery.fn.plugin =, so you add your plugin to the objects created by jQuery, but your plugin is not accessible directly from jQuery.
Think of jQuery as a constructor that also has a lot of utilities. The object returned when you do $(selector) have all the methods in jQuery.fn but not all of the jQuery object, only the ones that they have added into the jQuery.fn.

Correct usage of $.fn.myFunction()

(function($) {
$.fn.myFunction = function(config) {
var defaults = {
setting1: 'myDiv',
setting2: ''
};
var config = $.extend(defaults, config);
//code here
};
})(jQuery)
$(document).ready(function() {
$.fn.myFunction({
setting1: 'myDiv',
setting2: ''
});
});
This is how I've been using jQuery plugins, but I recently learned it should be used like:
$('#myDiv').myFunction({
setting1: 'myDiv',
setting2: ''
});
1) I take it this allows the usage of $(this) for $('#myDiv')?
2) Is $(document).ready(function() required?
3) Is there anything detrimental about the way I have been using this jQuery function?
4) If $('#myDiv').myFunction() is the proper usage, how would you call the function if it is simply a function to run at document ready - see my usage here: https://stackoverflow.com/a/12316349/1455709. Is this simply an incorrect usage of the function?
Calling $.fn.myFunction() and calling $('#myDiv').myFunction() are two different things. There is no right or wrong - it depends upon what you're doing.
Calling $.fn.myFunction()
This is essentially a static function and you can use it like that if you want. In the jQuery world, .fn is like .prototype so $.fn.myFunction() is like calling jQuery.prototype.myFunction(). It is allowed, but it's a static function call that is not associated with a specific jQuery object. If you just want a static function call, then you can do this, but this is not normally how the jQuery prototype is used and I would not generally recommend it. If you just want a static function and want to use the jQuery namespace, you can just do this:
$.myFunction = function(args) {/* your code here */};
and then call it like:
$.myFunction();
As there is no need to use the prototype at all.
Calling $('#myDiv').myFunction()
The prototype (e.g. .fn in the jQuery world is used when you want to add methods to actual jQuery objects.
When you do this $('#myDiv').myFunction() is a member function of a live jQuery object. You can refer to this inside the myFunction() implementation and it will be a jQuery object that, in this case holds the DOM object that corresponds to id="myDiv". If you return the jQuery object from your method, you can also use chaining.
Which to Use?
If your code operates on a jQuery object, then it should be a method on a live jQuery object and it should access this to get at the jQuery object instance data and you should declare it as $.fn.myFunction = function() {}; and call it as$('#myDiv').myFunction()`.
If you code does not operate on jQuery object and is just a utility function that you call and it doesn't always operate on a jQuery object, then, you should declare it as $.myFunction = function() {}; and you should call it as $.myFunction().
Yes, as well as proper chaining if you return this from inside your function.
Yes, if your code is run before the dom is loaded.
It's not really jQuery, it doesn't work how people would expect it to. Somebody could call it as $('#myDiv').myFunction() and it wouldn't perform as expect.
If you want a function that you can just run any time, don't add it to the jQuery prototype ($.fn). Instead you could add it onto $ like the other jQuery functions that don't require selectors, $.trim for example. Then you could call it like this: $.myFunction(options);

jQuery plugin patterns: something more object-oriented?

I'm working on a jQuery plugin, following the pattern detailed in the Authoring guide. Basically:
(function($) {
// Private
var doSomething = function($element, settings) { ... }
var doSomethingElse = function($element, settings) { ... }
// Public
var methods = {
init: function(options) { ... },
show: function() { ... },
hide: function() { ... },
update: function(content) { ... }
};
$.fn.myPlugin = function(method) {
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.myPlugin');
}
};
})(jQuery);
Here's my dislike: I have to pass the same "instance" variables to all of the private functions. I'm still working on becoming a JS pro — so pardon my incorrect term usage — but if I were doing this same thing in Ruby or PHP, I'd create a class with all of these public and private members and methods, and each instance of the class would correspond to an $element. Then I could do something like this (in JS):
var firstElement = new MyPlugin($element, settings);
firstElement.doSomething();
Rather than passing $element and settings to doSomething(), it already has access to those via this.$element and this.settings.
Get where I'm going with this? I'm looking for a more object-oriented approach, I guess. Now, I totally understand that JS doesn't have classes like Ruby or PHP. But between constructor functions, the module pattern, and regular object notation (like methods above), I'm not sure which is the best option for a jQuery plugin.
Can someone help point me in the right direction? Maybe some examples of existing jQuery plugins that do this well? Thanks!
The jQuery UI Widget Factory might be a good solution. It's useful for creating any kind of stateful jQuery plugins and can be used entirely separate from the rest of the jQuery UI suit.
Some useful links:
http://bililite.com/blog/understanding-jquery-ui-widgets-a-tutorial/
http://wiki.jqueryui.com/w/page/12138135/Widget-factory
http://ajpiano.com/widgetfactory/ (presentation)
If you want a more bare bone solution I'd go with either a regular Constructor + prototype setup to do things "properly" or use the Revealing Module Pattern to create a function that takes the element and any options as arguments and returns the public methods.
An example using the Revealing Module Pattern:
function myPlugin (element, options) {
var privateVar;
function privateFunc () {}
function publicMethod () {}
return {
publicMethodName: publicMethod
};
}
This pattern is a bit more tidy than a traditional prototypal set up, but does not take advantage of the prototype chain.
Edit: To clarify, when using any of these patterns you are supposed to create a new instance for each element/use.
It isn't necessarily a good idea to store any kind of stateful information in the plugin itself since it would be shared by all instances. One option is to store that data elsewhere, outside of the plugin.
The Plugins/Authoring page has a Data section which describes how to store information for use by your plugin on a per-element basis using the data() function.
Using data helps you keep track of variables and state across method
calls from your plugin. Namespacing your data into one object literal
makes it easy to access all of your plugin's properties from one
central location, as well as reducing the data namespace which allows
for easy removal if need be.
The example provided on the page uses the plugin pattern described in your post, but allows "instance" variables to be stored with the element they're associated with.
One key thing to remember when doing this is:
Always namespace your methods, events and data.
Edit:
It should be noted too, that in your example some of your functions expect $element as a parameter, but this isn't necessary since this will refer to the right thing when those functions are called through the plugin (because apply() is being called and setting the context to the correct this).

Categories

Resources