I'm interested on how is realized the JavaScript pattern used, for example, in jQuery UI dialog:
$.dialog('mydialod').dialog('close');
ie I can't get how to reference back a constructor function after I created it in a jQuery compliant fashion.
EDIT
Just to clarify: what is really obscure to me is how I can have somewhere
$('#mydlg').dialog();
and then somewhere else
$('#mydlg').dialog("somecommand");
that even in absolutely different places seems to point back to the original instance.
I think, it is somehow related with this (jquery.ui.widgets.js ),
// create selector for plugin
$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
return !!$.data( elem, fullName );
};
but really I'm too green in javascript / jquery to get what's happening.
I'm not sure how jQuery UI does it (you'd have to look at the source), but here's a way to do this https://gist.github.com/elclanrs/5668357
The advantage of using this approach is that you keep all your methods private instead of in the prototype by using a closure; a module pattern in this case.
Edit: Aight, got it. This is how I got it to work. This I'm calling the "Advanced jQuery Boilerplate". I added the methods to the prototype, I don't think it really makes a difference to keep them outside, and makes it easier to call methods within methods with this.method():
(function($) {
var _pluginName = 'myplugin'
, _defaults = {
};
function Plugin(el, options) {
this.opts = $.extend({}, _defaults, options);
this.el = el;
this._init();
}
Plugin.prototype = {
_init: function() {
return this;
},
method: function(str) {
console.log(str);
return this;
}
};
Plugin.prototype[_pluginName] = function(method) {
if (!method) return this._init();
try { return this[method].apply(this, [].slice.call(arguments, 1)); }
catch(e) {} finally { return this; }
};
$.fn[_pluginName] = function() {
var args = arguments;
return this.each(function() {
var instance = $.data(this, 'plugin_'+ _pluginName);
if (typeof args[0] == 'object') {
return $.data(this, 'plugin_'+ _pluginName, new Plugin(this, args[0]));
}
return instance[_pluginName].apply(instance, args);
});
};
}(jQuery));
Now I have two divs:
<div></div>
<div id="mydiv"></div>
And I can use the plugin like:
$('div').dialog({ n: 69 }); // initialize both divs
console.log($('#mydiv').dialog('method', 'hello world'));
//=^ prints "hello world" and returns instance
console.log($('#mydiv').data('plugin_dialog').opts.n); //=> 69
It's basically storing the instance of the plugin in data to be able to restore the options since this info is attached to the element. It's similar to how jQuery Boilerplate works.
This is called 'chaining pattern'.
Basic idea is that object methods return constructed instance, look at simplified example:
function Dialog (){
this.open = function(){
console.log('open dialog');
return this;
}
this.close = function(){
console.log('close dialog');
return this;
}
}
var d = new Dialog();
d.open().close();
Note 'return this' statement in every method.
Related
This one is something that is fairly easy to do in PHP and I find my self in a situation where it would come in handy, but I do not believe the PHP trick will work.
Basically I want to use a variable passed from a function within an object to then reinitialize that object using the child (defined by the variable).
var View = function(){
var fn = this;
fn.load = function(name){
return new name();
}
}
var view = View.load('titleView');
This is a very early work on it, so forgive the fact that it looks so strange (still need to tinker more with the concept). But overall it should roughly show the concept.
Is there a way to basically recreate the current functions instance with a new function? To do this in the aspect I am thinking of I will need to use a variable rather then pass the new object. Is this possible? I am sure in some form it has to be. Any ideas/pointers? Google has been failing me since I am not sure of the right keywords for this.
EDIT:
should also show the idea behind the "titleView" class
var titleView = function(){}
titleView.prototype = new View;
I think the easiest way to do this is via some kind of factory that can produce the types of views you are wanting. Something like this:
var View = (function() {
var registry = {};
return {
register: function(type, fn) {
if (typeof registry[type] === 'undefined') {
registry[type] = fn;
return true;
}
return false;
},
load: function(type) {
return new registry[type]();
}
};
})();
var titleView = function() {
this.name = 'titleView';
};
var subTitleView = function() {
this.name = 'subTitleView';
}
View.register('titleView', titleView);
View.register('subTitleView', subTitleView);
var view = View.load('titleView');
console.log("Created view type: " + view.name);
view = View.load('subTitleView');
console.log("Created view type: " + view.name);
This would give the following (allowing you to recreate view variable on the fly):
// Created view type: titleView
// Created view type: subTitleView
If you try to do it the way your example is going, you'll have to use subclasses like so:
function Base() {
this.name = "Base";
this.load = function(fn) {
fn.apply(this);
}
}
function Other() {
Base.apply(this, arguments);
this.name = "Other";
}
var view = new Base();
console.log(view);
view.load(Other);
console.log(view);
// => Base { name: "Base", load: function }
// => Base { name: "Other", load: function }
However, with this method, after calling view.load(Other), your view will still retain whatever properties/methods it had prior to calling load (which may not be what you want).
think this is what ur asking
var View = function(){
this.load = function(name){
return name;
}
}
var myView = new View;
var v = myView.load('titleView');
alert(v);
My favorite design pattern to create a jQuery plugin is shown below.
Is there any reason to create a namespace for methods in the plugin? Specifically, my use of var privateMethods shown below?
(function($){
var privateMethods={};
privateMethods.method1=function(){alert('privateMethods1');};
privateMethods.method2=function(){alert('privateMethods2');};
privateMethods.method3=function(){alert('privateMethods3');};
var privateMethod1=function(){alert('privateMethods1');};
var privateMethod2=function(){alert('privateMethods2');};
var privateMethod3=function(){alert('privateMethods3');};
function privateFunction1(){
//Consider using this approach if there is significant script
alert('privateFunction1');
}
var defaults = { //private, right?
foo: 'bar'
};
var methods = {
init : function (options) {
var settings = $.extend({}, defaults, options);
return this.each(function () {
//do whatever
});
},
destroy : function () {
return this.each(function () {});
},
otherPublicMethod : function() {
return $(this).each(function(){
//do whatever
})
}
};
$.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)
);
Concerning the visibility of your methods outside your plugins scope there is no difference between
privateMethods.method
privateMethod1 = function(){}
function privateFunction(){}
On the other hand namespacing does not have any serious drawbacks apart from the slightly bigger size your jQuery plugin will have (easily mitigated by mini- and uglifying your code). The possible advantages are the possibilities to:
Introduce a logical structure to your code by breaking your methods into namespaced groups
Use well-known Javascript patterns around Namespaces, making your code readable and maintainable e.g.: http://addyosmani.com/blog/essential-js-namespacing/
Encapsulation. Gets relevant for plugins with massive code base
In conclusion: Overall I'd opt to use namespaces anytime.
I have been trying to work out why my public methods do not appear to exist for my custom jQuery plugin object.
I have created a simplified version of the jQuery plugin with a private variable and two public methods to access/modify the private variable. I have looked online but the results are not that clear, that or my searching skills are terrible.
It keeps saying 'TypeError: myObject.getMyValue is not a function', anyone got a clue as to what I am doing wrong, or suggestions for a better approach to this?
The basic object class is below, but a proper code example can be found on the jsFiddle link.
(function ($) {
var MyClass = function (element) {
var myValue = 'Hello World';
this.getMyValue = function() {
return myValue;
};
this.setMyValue = function(value) {
myValue = value;
};
return this;
};
$.fn.myPlugin = function() {
return this.each(function(key, value){
if ($(this).data('myclass')) {
return $(this).data('myclass');
}
var instance = new MyClass(this);
$(this).data('myclass', instance);
return instance;
});
};
})(jQuery);
var myObject = $('#test').myPlugin();
alert(myObject.getMyValue());
myObject.setMyValue('Goodbye World');
alert(myObject.getMyValue());
http://jsfiddle.net/hZExb/4/
Because you're returning the result of this.each() which would be this. Create a variable outside of this.each() and return that after your this.each has completed.
jsFiddle
$.fn.myPlugin = function() {
var instance;
this.each(function(key, value){
if ($(this).data('myclass')) {
return $(this).data('myclass');
}
instance = new MyClass(this);
$(this).data('myclass', instance);
});
return instance;
};
If you wanted to return an array of MyClass's if your jQuery object was a collection you could do it like this:
jsFiddle
$.fn.myPlugin = function() {
var instances = [];
this.each(function(key, value){
if ($(this).data('myclass')) {
return $(this).data('myclass');
}
var instance = new MyClass(this);
$(this).data('myclass', instance);
instances.push(instance);
});
if (instances.length == 1)
return instances[0];
return instances;
};
I've spent the last couple days researching a way to have private or protected properties in MooTools classes. Various articles (ie, Sean McArthur's Getting Private Variables in a MooTools Class) provide an approach for deprecated versions of MooTools, but I haven't been able to track down a working method for MooTools 1.3+.
Today, after playing with code for hours, I think I have have created a suitable solution. I say "think," because I'm really not that experienced as a programmer. I was hoping the community here could check out my code and tell my if it's actually a valid solution, or a hackjob emulation.
var TestObj = (function() {
var _privateStaticFunction = function() { }
return new Class({
/* closure */
_privates: (function() {
return function(key, val) {
if (typeof(this._data) == 'undefined') this._data = {};
/* if no key specified, return */
if (typeof(key) == 'undefined') return;
/* if no value specified, return _data[key] */
else if (typeof(val) == 'undefined') {
if (typeof(this._data[key]) != 'undefined') return this._data[key];
else return;
}
/* if second argument, set _data[key] = value */
else this._data[key] = val;
}
/* tell mootools to hide function */
})().protect(),
initialize: function() {},
get: function(val) { return this._privates(val); },
set: function(key,val) { this._privates(key,val); }
})
})();
obj1 = new TestObj();
obj2 = new TestObj();
obj1.set('theseShoes','rule');
obj2.set('theseShoes','suck');
obj1.get('theseShoes') // rule
obj2.get('theseShoes') // suck
obj1._privates('theseShoes') // Error: The method "_privates" cannot be called
obj1._privates._data // undefined
obj1._privates.$constructor._data // undefined
I really appreciate any tips! Thanks, everyone!
EDIT: Well, this is embarrassing. I forgot to check out the obvious, obj1._data. I didn't think the this would reference the instance object! So, I suck. Still, any ideas would be awesome!
heh. in your case, a simpler pattern would do the trick.
consider a var behind a closure - extremely hard to puncture. it is available through the getter and setter.
downside: data values cannot be in the instance or they can be accessed directly.
var testObj = (function() {
var data = {__proto__:null}; // 100% private
return new Class({
get: function(key) {
return data[this.uid][key] || null;
},
set: function(key, value) {
data[this.uid][key] = value;
},
remove: function(key) {
delete data[this.uid][key];
},
otherMethod: function() {
alert(this.get("foo"));
},
initialize: function() {
this.uid = String.uniqueID();
data[this.uid] = {};
}
});
})(); // why exec it?
var foo = new testObj();
var bar = new testObj();
foo.set("bar", "banana");
console.log(foo.get("bar")); // banana!
console.log(bar.get("bar")); // undefined.
bar.set("bar", "apple");
console.info(foo.get("bar"), bar.get("bar")); // banana apple
In action: http://jsfiddle.net/dimitar/dCqR7/1/
I am struggling to find a way to puncture this pattern at all - which is sometimes achievable through prototyping like this.
in fact, i played with it some and here's the fixed pattern w/o the namespacing:
http://jsfiddle.net/dimitar/dCqR7/2/
var testObj = (function() {
var data = {__proto__:null}; // 100% private
return new Class({
get: function(key) {
return data[key] || null;
},
set: function(key, value) {
data[key] = value;
},
remove: function(key) {
delete data[key];
},
otherMethod: function() {
alert(this.get("foo"));
}
});
});
var foo = new new testObj();
var bar = new new testObj();
foo.set("bar", "banana");
console.log(foo.get("bar")); // banana!
console.log(bar.get("bar")); // undefined.
bar.set("bar", "apple");
console.info(foo.get("bar"), bar.get("bar")); // banana apple
edit why that is...
my reliance on mootools means my understanding of native js prototypes leaves something to be desired as it abstracts you having to deal with this directly but..
in pattern one, you define AND run the function, which creates the prototype and sets data - a singular instance. you then create new functions with that 'live' prototype where data is already set.
in pattern two, a brand new prototype is created and referenced for each instance, independent of each other. your function returns a new Function with prototype Class... so really new Class({}); hence new new <var>() will create and instantiate the class.
to understand this better, perhaps you can write it like this first - a common enough pattern for creating and instantiating a class that is not being reused - which will make more sense:
new (new Class({
initialize: function() {
alert("hi");
}
}))();
which in turn can be written like this (if saved into a variable):
var foo = new Class({
initialize: function() {
alert("hi");
}
});
new foo();
I hope it makes sense, I am not the best person at explaining...
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());
});