I'm struggling with how best to combine javascript Classes and jQuery plugins. This question isn't very specific, what I'm hoping for is pointers to more resources.
Basically, I want to store state data and private methods in a class, and then extend each jQuery object which I call my plugin on to have those private methods and properties. Such that inside the plugin I can call methods directly off the jQuery object.
I read jQuery plugin design pattern (common practice?) for dealing with private functions, specifically David's answer, however this initializes a new Class each time, and thus can't be used to save the state of the object.
I also found http://fuelyourcoding.com/jquery-plugin-design-patterns-part-i/, which recommends creating a class and then storing it in .data().
I think ideally what I want to end up with is code that looks like
(function( $ ){
var methods = {
init : function( options ) { // Initialize each object with a state and private methods },
show : function( ) {
// testFoo() is a private method that checks the element's state
if(this.testFoo()){
// Relying on jQuery's html() method
this.html() = this.fooTemplate();
}
}
};
// Boiler plate plugin from http://docs.jquery.com/Plugins/Authoring
$.fn.myPlugin = function( method ) {
// Method calling logic
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 );
Finally, it doesn't seem like I can bake the private methods into the plugin directly because methods like "testFoo()" will return a boolean, and therefore aren't chainable.
Thoughts? Am I approaching this the right way? Is there another design pattern I should be using? Perhaps not using jQuery plugin architecture at all?
Here's a proposed solution. It combines few different approaches (John Resig's inheritance model and Alex Saxton's plugin inheritance model).
Define your inheritable plugin:
(function ($) {
My.Plugin = Class.extend({
/*
* Initialization (constructor)
*/
init: function (element, meta) {
var $meta = $.extend({ name: "pluginName" }, meta);
// Call the base constructor
this._super(element, $meta);
// TODO: Add custom initialization code like the following:
// this._testButton = $('.testButton', element).get(0);
},
/*
* Public methods
*/
show: function() {
alert('This is a public method');
},
/*
* Private methods
*/
// DEMO: Overriding the base _paint method:
_paint: function () {
// "this._super()" is available in all overridden methods
// and refers to the base method.
this._super();
alert('TODO: implement myPlugin._paint!');
}
});
// Declare this class as a jQuery plugin
$.plugin('my_plugin', My.Plugin);
})(jQuery);
Define Base class
(function () {
var initializing = false, fnTest = /xyz/.test(function () { xyz; }) ? /\b_super\b/ : /.*/;
// The base Class implementation (does nothing)
this.Class = function () { };
// Create a new Class that inherits from this class
Class.extend = function (prop) {
var _super = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] =
typeof prop[name] == "function"
&& typeof _super[name] == "function"
&& fnTest.test(prop[name])
? (function (name, fn) {
return function () {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name])
: prop[name];
}
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if (!initializing && this.init)
this.init.apply(this, arguments);
}
// Populate our constructed prototype object
Class.prototype = prototype;
// Enforce the constructor to be what we expect
Class.constructor = Class;
// And make this class extendable
Class.extend = arguments.callee;
return Class;
};
})();
Plugin Creation
(function ($) {
// The "inheritance plugin" model
// [http://alexsexton.com/?p=51][1]
$.plugin = function (name, object) {
$.fn[name] = function (options) {
var instance = $.data(this, name, new object(this, options));
return instance;
};
};
})(jQuery);
Calling your plugin from javascript:
$('#someElem').my_plugin({options: {}, data: {} /* you can modify your plugin code to accept anything */}).show();
Note:
Private methods here are marked as _methodName. It's pure convention. If you really want to hide them, you can use module pattern (google for it or here's one for starters: http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth)
Is this what you're looking for?
Related
According to this StackOverflow answer What does jQuery.fn mean?, the fn property in jQuery.fn.jquery is an alias to the prototype property. I assume that this would be the same in these two methods whose full code is below
$.fn.map = function() and $.fn.tweets = function()
My question then, is, if, for example, $.fn.tweets uses the prototype to create a tweets method, would this code with $('tweets').tweets be calling it...
var $tweets = $('#tweets').tweets({
query: buildQuery(approxLocation),
template: '#tweet-template'
});
and, if so, how might it trigger that method. For example, does the mere creation of the variable on file loading trigger that function, which has other methods inside of it, namely query? Thanks for your help
Full code of methods
$.fn.map = function(method) {
console.trace();
console.log(method);
if (method == 'getInstance') {
console.log("fn.map");
return this.data('map');
}
return this.each(function() {
var $this = $(this);
var map = $this.data('map');
if (map && MyMap.prototype[method]) {
map[method] (Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
var options = method;
$this.data('map', new MyMap( this, options ));
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.map' );
}
});
}
$.fn.tweets = 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.tweets' );
}
}
variables that call those methods?
var $tweets = $('#tweets').tweets({
query: buildQuery(approxLocation),
template: '#tweet-template'
});
var $map = $('#map').map({
initialLocation: approxLocation,
radius: 1000,
locationChanged: function(location) {
$tweets.tweets('setQuery', buildQuery(location));
}
});
Firstly, prototypes are just objects. In this case, yes:
jQuery.prototype === jQuery.fn
So saying jQuery.fn.map = function() {} is like saying jQuery.prototype.map = function() {}
When you instantiate a new jquery object with $(selector | dom node | ...) you are returning a jQuery object which automatically inherits all the prototype methods, including map, tweet, etc. Research Javascript's prototypal inheritence model and how object prototypes work in regard to new
$ is just a reference to jQuery which returns a specially modified new object. $ is a function which returns a new object reference. Here's a simplified example (but you really should research more about prototypal inheritence, it has been answered many times repeatedly):
var A = function() {
};
A.prototype.doThing = function() {
};
var newObj = new A();
newObj.doThing // this new object has this method because it's on A's prototype
so newObj.doThing is just like $(selector).tweet
Also feel free to read the source of jQuery and trace what happens when a new object is created. You can see near the top exactly what is happening under the comment // Define a local copy of jQuery
I'm working on a jQuery plugin that allows you to log any javascript class or object.
The idea is to override each function inside the object, or prototype of a function.
(function($)
{
"use strict";
$.log = function(object, logger)
{
if (!$.isFunction(logger))
{
logger = function(name, args)
{
console.log(name + "(" + $.makeArray(args).join(", ") + ")");
};
}
var s = $.isFunction(object) ? object.prototype : object;
for (name in s)
{
var fn = s[name];
if ($.isFunction(fn))
{
s[name] = (function(name, fn)
{
return function()
{
logger(name, arguments);
return fn.apply(this, arguments);
};
})(name, fn);
}
}
};
})(jQuery);
This seems to work for logging individual plugins. For example $.log($.ui.tabs); logs all the function calls inside the tabs prototype.
But when I want to log all of jQuery $.log($); it's giving me some reference error.
I can't figure out why I'm getting this error. I'm under the impression it has something to do with either this or the arguments being passed, but I'm not sure.
Edit: Now I think about It some more it might also be caused because the overridden function always returns.
I created a fiddle to demo the problem: http://jsfiddle.net/Sj6xN/4/
EDIT:
This is the code i ended up with, so far working perfectly:
(function($)
{
"use strict";
var Logger = function(options)
{
this.options = $.extend(this.defaults, options);
};
Logger.prototype = {
defaults:
{
inherited: false,
deep: false,
logWriter: function(name, args)
{
console.log("CALL: " + name + "(" + $.makeArray(args).join(", ") + ")");
}
},
augment: function(object)
{
var self = this;
// Make sure this object is not already augmented
if (object.__isAugmented__)
{
return;
}
// Set 'isAugmented' to prevent recursion
object.__isAugmented__ = true;
// Loop through the object
for (var name in object)
{
var originalFunction = object[name];
// If it's a function and the function is not inherited or 'inherited' is enabled augment it
if ($.isFunction(originalFunction) && (object.hasOwnProperty(name) || self.options.inherited))
{
// Wrap in self executing function so references to 'name' and 'orginalFunction' are maintained
object[name] = (function(name, originalFunction)
{
// If the function has a prototype and 'deep' is enabled augment that as well
if (self.options.deep && originalFunction.prototype)
{
self.augment(originalFunction.prototype);
}
var augmentedFunction = function()
{
// Execute log writer
self.options.logWriter(name, arguments);
// Call original function
return originalFunction.apply(this, arguments);
};
// Inherit prototype of original function
augmentedFunction.prototype = originalFunction.prototype;
// Return the augmented function
return augmentedFunction;
})(name, originalFunction);
}
// If it's a plain object and 'deep' is enabled augment that as well
else if (self.options.deep && $.isPlainObject(originalFunction))
{
self.augment(originalFunction);
}
}
}
};
$.log = function(object, options)
{
var logger = new Logger(options);
// If the object is a function use it's prototype, otherwise assume a plain object
object = $.isFunction(object) ? object.prototype : object;
// Augment
logger.augment(object);
};
})(jQuery);
Can be used like this:
$.log(<object or function> [,
{
inherited: <bool>,
deep: <bool>,
logWriter: <function(name, args)>
}]);
Well look closely to the error.
Uncaught ReferenceError: name is not defined
Means you haven't defined name and since you are in strict mode, you can't use a variable without defining it(normally if you do it, it'll be a global variable, but not in strict mode). So if you write a var name before it you won't get this error anymore.
Though there is another error for not having tab method. The other error says tabs is not a method of the object which is because when you wrap the function, you didn't inherit the prototype, so when the function is called with new, it doesn't have prototype functions(tabs is one of them).
Here's the fixed code : http://jsfiddle.net/Sj6xN/8/
I'm using Resig's Simple JavaScript Inheritance to create my classes. The only thing I don't like about it so far is that when I log an object created with this library to the console, it's name is simply "Class". My question is whether there is a way to modify his code so that I get the actual class name in the console instead. Here's an example from Chrome's console:
I would really like that name "Class" to be the actual name of the class I've created, in the way it would if you did the following:
I believe I know the reason why this happen's with Resig's library: the actual constructor function is simply named "Class". Here's the code for his library:
(function(){
var initializing = false,
// Determine if functions can be serialized
fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// Create a new Class that inherits from this class
Object.subClass = function(prop) {
var _super = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
var proto = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
proto[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn){
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) :
prop[name];
}
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if ( !initializing && this.init )
this.init.apply(this, arguments);
}
// Populate our constructed prototype object
Class.prototype = proto;
// Enforce the constructor to be what we expect
Class.constructor = Class;
// And make this class extendable
Class.subClass = arguments.callee;
return Class;
};
})();
You'll find the Class() function about 2/3 of the way down. Does anyone know how to modify this code so that you get the actual name of the class in the console?
Toss in a change to Person.prototype.constructor when you're creating Person:
var Person = (function() {
var myConstructor = Class.extend({
init: function(isDancing){
this.dancing = isDancing;
},
dance: function(){
return this.dancing;
}
});
myConstructor.prototype.constructor = function Person(){};
return myConstructor;
}());
I don't think you could do it from within the Simple JavaScript Inheritance. You'd need the Person.prototype.constructor to be a named function, and I don't think you can name a function without eval... and you have too much rep for me to explain why you shouldn't do that ;)
No promises this doesn't screw something up elsewhere though :P
Consider the following base code:
(function($) {
$.fn.myPlugin = function(settings) {
return this.each(function() {
//whatever
});
};
});
The plugin returns a jQuery object. The question is how am I supposed to write a plugin that returns a custom object so that I can do something like this:
var api = $('div.myelement').myPlugin();
api.onMyEventName(function(e, whateverParam) {
//whatever
});
It'd be highly appreciated if you could write some lines of code that describes me how to do that, how to call the onMyEventName function on a custom api object...
Thanks.
(function($) {
function MyApi($this, settings) {
this.$this = $this;
this.settings = settings;
};
MyApi.prototype.output = function (val) {
// use this.$this to access the original jQuery object
return this.settings[val];
};
$.fn.myPlugin = function(settings) {
return new MyApi(this, settings);
};
});
Note that we've passed this from $.fn.myPlugin() into the MyApi constructor; this allows you to access the jQuery object that myPlugin() was originally called on within MyApi methods.
You can also do the same using the object literal syntax:
(function($) {
$.fn.myPlugin = function(settings) {
return {
settings: settings,
output: function (val) {
// use this.$this to access the original jQuery object
return this.settings[val];
},
$this: this
};
};
});
Then;
var something = $('#something').myPlugin({
val: 'Lemon'
});
alert(something.output('val')); // Lemon
... again, we've captured the value of this (the jQuery object) into a property $this on the newly constructed objected, to gain access to the original jQuery object.
There is a great article by Hector Virgen detailing a possible solution (also used in bootstrap) to adress this question.
The key moment is basically storing your API object in the data section of the node:
$.fn.myplugin = function() {
return $.each(function() {
...
myplugin = new MyPlugin(this)
...
$(this).data('myplugin', myplugin);
}
}
After this, users can easily access the object by:
$(this).data("myplugin")
A possibly useful extension to this solution could be defining a mypluginapi method as a shorthand, for accessing your API object:
$.fn.mypluginapi = function() {
return $(this).myplugin().data('myplugin')
}
I have this code:
var myWidget = $('#myWidget');
and calls like this elsewhere:
myWidget.hide();
myWidget.slideToggle();
These work of course because jQuery adds these methods.
Now, let's say I'm doing some refactoring to make myWidget a proper object with its own custom methods and state:
var myWidget = (function() {
// private stuff
var actualJQueryObject = $('#myWidget');
return {
publicMethod: function() {...},
// MAGIC!
}
})()
but I want to have all the calls that expect a jQuery object, which are all around my code, to still work even though myWidget is no longer a jQuery object, because myWidget knows how to delegate these calls to actualJQueryObject.
Is this possible?
You could also extend your jQuery object, with another object that has your custom methods:
var myWidget = function() {
// private stuff
var actualJQueryObject = $('#myWidget');
var extensionMethods = {
publicMethod: function() { alert('public method!'); }
}
return $.extend(actualJQueryObject, extensionMethods);
}();
Just be careful with the name of your extension methods, to not clash with any other jQuery defined function.
You can try the above snippet here.
One option is using the original jquery object as a prototype.
function wrap(jqObject) {
function MyNewType() {
this.changeFontSize = function(a) {
this.css({fontSize : this.size});
};
}
MyNewType.prototype = jqObject;
return new MyNewType;
}
var obj = wrap($('#someitem'));
obj.size = 50; // obj.size
obj.changeFontSize(); // obj.changeFontSize
obj.hide(); // $.hide
obj.fadeIn("slow"); // $.fadeIn
I've written a plugin that might help you. It's basically a plugin for writing plugins. This dev group post explains it and has some code samples:
http://groups.google.com/group/jquery-dev/browse_thread/thread/664cb89b43ccb92c/72cf730045d4333a?hl=en&q=structure+plugin+authoring#72cf730045d4333a
And the source is here:
http://code.google.com/p/jquery-plugin-dev/source/browse/trunk/jquery.plugin.js
EDIT:
I created a function that has similar functionality to that plugin:
jQuerify = function(fn) {
function plugin() {
var instantiate = false;
// check to see if it has any prototyped methods (we only need one iteration to do this)
for (var i in construct.prototype) {
instantiate = true;
break;
}
// if there are prototyped methods, return an instance (since an instance's return value won't vary)
// otherwise just call it using apply so the return value can vary
return instantiate
? new construct(this, arguments)
: construct(this, arguments);
}
function construct(parent, args) {
// 'this' will not mimic jQuery unless given the length property
this.length = 0;
this.selector = parent.selector;
this.context = parent.context;
// mimic 'this' in jQuery, but for the plugin namespace
Array.prototype.push.apply(this, $.makeArray(parent));
// return the constructors return value
// should be 'this' if you want to chain the new namespace
return fn.apply(this, arguments);
}
// copy all static properties and methods
for (var i in fn) {
plugin[i] = fn[i];
}
// allow .fn and copy all instance properties and methods; the last part is for IE
plugin.fn = construct.prototype = plugin.prototype = fn.prototype;
return plugin;
}
This allows you to add custom objects to jQuery as a plugin while using 'this' to refer to the selected objects and also allows you to have an unlimited depth to your namespace:
function test1() {
return this;
}
test1.prototype.getHtml1 = function() {
return $(this).html();
}
function test2() {
return this;
}
test2.prototype.getHtml2 = function() {
return $(this).html();
}
function test3() {
return this;
}
test3.prototype.getHtml3 = function() {
return $(this).html();
}
jQuery.fn.test1 = jQuerify(test1);
jQuery.fn.test1.fn.test2 = jQuerify(test2);
jQuery.fn.test1.fn.test2.fn.test3 = jQuerify(test3);
jQuery(function($) {
alert($('body').test1().getHtml1());
alert($('body').test1().test2().getHtml2());
alert($('body').test1().test2().test3().getHtml3());
});