How can I emulate the Jquery UI API? - javascript

I've written basic jQuery plugins before, but I'm struggling to get my head around something more complex. I'm looking to emulate the API of jQuery UI, which works like this:
$('#mydiv').sortable({name: 'value'}); // constructor, options
$('#mydiv').sortable("serialize"); // call a method, with existing options
$('#mydiv').sortable('option', 'axis', 'x'); // get an existing option
I've tried the following:
(function($){
$.fn.myPlugin = function(cmd){
var config = {
default: 'defaultVal'
};
if(typeof cmd === 'object'){
$.extend(config, cmd);
}
function _foo(){
console.log(config.default);
}
if(cmd==='foo'){
return _foo();
}
this.each(function(){
// do default stuff
});
}
})(jQuery);
$('#myElement').myPlugin({default: 'newVal'});
$('#myElement').myPlugin('foo');
What I would like to see here is 'newval' being logged, but I'm seeing 'defaultVal' instead; the plugin is being called and started from scratch every time I call .myPlugin() on the element.
I've also tried using _foo.call(this) and some other variants. No joy.
In a way, I understand why this is happening, but I know that it must be possible to do it the same way as jQuery UI. I just can't see how!
(I appreciate that jQuery UI uses the widget factory to handle all of this, but I don't want to make that a requirement for the plugin.)

Perhaps what you want is this...
(function($){
var config = {
default: 'defaultVal'
};
$.fn.myPlugin = function(cmd){
if(typeof cmd === 'object'){
$.extend(config, cmd);
}
function _foo(){
console.log(config.default);
}
if(cmd==='foo'){
return _foo();
}
this.each(function(){
// do default stuff
});
}
})(jQuery);
$('#myElement').myPlugin({default: 'newVal'});
$('#myElement').myPlugin('foo');
Move the config variable outside the myPlugin function. This change will cause config to be initialized only once: when your plugin function is created.

You're declaring config during the function call rather than as a closure used by it. Try this:
(function($){
var config = {
default: 'defaultVal'
};
$.fn.myPlugin = function(cmd){
if(typeof cmd === 'object'){
$.extend(config, cmd);
}
function _foo(){
console.log(config.default);
}
if(cmd==='foo'){
return _foo();
}
this.each(function(){
// do default stuff
});
}
})(jQuery);
$('#myElement').myPlugin({default: 'newVal'});
$('#myElement').myPlugin('foo');
In addition, you could look into the jQuery data API for caching data, especially if you aren't going to have just one instance per page.

Related

Custom JQuery Plugin Method error

I've been working on writing a custom jquery plugin for one of my web applications but I've been running into a strange error, I think it's due to my unfamiliarity with object-oriented programming.
The bug that I've been running into comes when I try to run the $(".list-group").updateList('template', 'some template') twice, the first time it works just fine, but the second time I run the same command, I get an object is not a function error. Here's the plugin code:
(function($){
defaultOptions = {
defaultId: 'selective_update_',
listSelector: 'li'
};
function UpdateList(item, options) {
this.options = $.extend(defaultOptions, options);
this.item = $(item);
this.init();
console.log(this.options);
}
UpdateList.prototype = {
init: function() {
console.log('initiation');
},
template: function(template) {
// this line is where the errors come
this.template = template;
},
update: function(newArray) {
//update code is here
// I can run this multiple times in a row without it breaking
}
}
// jQuery plugin interface
$.fn.updateList = 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('UpdateList');
if(!instance) {
// create plugin instance and save it in data
item.data('UpdateList', new UpdateList(this, opt));
} else {
// if instance already created call method
if(typeof opt === 'string') {
instance[opt](args);
}
}
});
}
}(jQuery));
One thing I did notice when I went to access this.template - It was in an array so I had to call this.template[0] to get the string...I don't know why it's doing that, but I suspect it has to do with the error I'm getting. Maybe it can assign the string the first time, but not the next? Any help would be appreciated!
Thanks :)
this.template = template
Is in fact your problem, as you are overwriting the function that is set on the instance. You end up overwriting it to your args array as you pass that as your argument to the initial template function. It basically will do this:
this.template = ["some template"];
Thus the next time instance[opt](args) runs it will try to execute that array as if it were a function and hence get the not a function error.
JSFiddle

Calling a function in a jquery plugin

I am trying to get the object of a jquery plug in I'm making use of. I want to eventually be able to add features to the plugin to suit my needs. I am currently having a problem with calling one the functions I defined in the plug in. Here is the skeleton of the code I have:
;(function($) {
$.fn.myPlugin = function(o) {
.//Implementations here
.
.
var Ex = function(e){
//implementations here
};
};
})(jQuery);
The reason I want this function inside is because I want access to some of the variables defined. I would like be able to call function Ex from my html file but whatever I tried so far hasn't worked. An example of what I have tried is:
$.fn.myPlugin.Ex("x");
No return statement anywhere. I'm not great at javascript or jquery but I'm trying to learn. Any help in explaining what I'm doing wrong is appreciated.
Your plugin design pattern is wrong.
To achieve what you want, you can use this common one :
;(function($){
var methods = {
init: function() {
//stuff
},
function1: function() {
//stuff
},
function2: function(opt) {
//stuff
}
};
$.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);
}
};
})(jQuery);
With this structure :
$.fn.myPlugin(); will call init()
$.fn.myPlugin("function1"); will call function1()
$.fn.myPlugin("function2",option_exemple); will call function2(opt)
Disclaimer : I use this very often, but it's not mine. Can't remember where I found it*.
edit : http://docs.jquery.com/Plugins/Authoring , thanks to Beetroot-Beetroot for the reminder !

Adding a function to one jQuery/DOM element

I am authoring a plugin which instantiates a map. The map would then provide a function to move to another place on the earth.
The script makes the map just fine. However I can't "tack" the function on the element, to be used by another plugin in a callback.
Here's the approach I tried; in plugin:
(function($){
$.fn.mapDo(options){
map = new BlahMap(this.get(0));
this.moveTheMap = function(place){
map.moveItToThat(place);
}; // nope.
}
})(jQuery);
Then, in view:
$(map).mapDo();
$(otherElement).otherControl({
callback: function(place){
$(map).moveTheMap(place); // moveTheMap is not there on $(map)!
}
};
The Question
How do I add a function to the map jQuery or DOM element, if possible? If not, how can I provide that kind of functionality?
More importantly, am I going the right way here by separating the things that way? I'm a bit of a neophyte to Javascript, how are these tasks usually done while still keeping the components apart?
While that's the stab I took at it, more generally, I struggled with the concept of outputting things from a jQuery plugin while maintaining chainability. In this case, what I am trying to do is to output a callback from the plugin that will work on the called element later in the execution.
Plugins normally only add one method to the jQuery prototype, and the method calls to the plugin's instances are done with strings.
(function($) {
$.fn.mapDo = function(options) {
var args = [].slice.call(arguments, 1); //Get all the arguments starting from 2nd argument as an array
return this.each(function() {
var $this = $(this),
instance = $this.data("map-instance");
if (!instance) {
$this.data("map-instance", (instance = new BlahMap(this, options)));
}
if (typeof options == "string") {
instance[options].apply(instance, args);
}
});
};
})(jQuery);
$(elem).mapDo( "moveTheMap", place ); //This would also instantiate the plugin if it wasn't instantiated
Here's jsfiddle showing it in action:
http://jsfiddle.net/X8YA8/1/
You could store the map with .data method.
(function($){
$.fn.mapDo = funciont(options) {
this.data('map', new BlahMap(this.get(0)));
return this;
};
$.fn.moveTheMap = function(place) {
var map = this.data('map');
if (map) {
map.moveItToThat(place);
}
return this;
};
})(jQuery);

How to manually execute a function inside a jQuery plugin?

I have a plugin that looks like this:
(function($) {
$.fn.plugin_name = function(options) {
var $this = $(this);
var defaults = {
// some defaults
};
options = $.extend({}, defaults, options);
var work = {
action_1: function() {
// do something
},
action_2: function(output) {
alert('hello world');
}
}
that.submit(function(e) {
e.preventDefault();
work.action_1();
});
return $this;
}
})(jQuery);
It's being used like any traditional jquery plugin, by being attached to a page element like so:
$('#search-form').plugin_name({
// overide options
});
My question is, how can I execute the work.action_2() function that's deeply nested inside the plugin? I would like to call it manually from the javascript console in firebug.
My question is, how can I execute the work.action_2() function that's deeply nested inside the plugin?
You can't, it's outside of your scope!
you can't reach private function variables, just like you can't reach my functions...
Create it in a separate utility function if its needed independently, better if its in your own global object.
So you can call it like so: $.myGlobalObj.action_2()

jQuery expose plugin functions

One of our old developers has built a jQuery plugin like so:
jQuery.fn.limelight = function(options) {
/*Skipped code here*/
jQuery(".spotlight-btn.back a").click( function (e) {
if(lastSelectedCastIndex - 1 >= 0) {
removeFromSpotlight();
lastSelectedCastIndex--;
e.preventDefault();
$.address.value(lastSelectedCastIndex);
ca$t.scroll(jQuery.jcarousel.intval(lastSelectedCastIndex), true);
switchTo(lastSelectedCastIndex);
}
return false;
});
function switchTo(i)
{
ca$t.scroll(jQuery.jcarousel.intval(i), true);
$.address.title($("#title_text").text());
putInSpotlight();
}
};
I've not done any jQuery plugin programming, but would like to expose the switchTo function so it can be called anywhere. How would I be able to do this?
This is probably overkill for your purposes, but it doesn't seem like your developer really understood or grasped the purpose of jQuery plugins.
You want a plugin to be somewhat generic where it can accept a selector and apply events, styles, dynamic html, whatever to the item(s) found in the selector. It looks like he wrote a "plugin" for a single purpose... maybe just to maintain some sort of organization.
Most plugins follow a form similar to this:
; (function ($) {
$.fn.limelight = function (method) {
var methods = {
//Initialize the plugin
init: function (options) {
return this.each(function () {
//Refactor to conform to plugin style
// $(this).click( function (e) {
// if(lastSelectedCastIndex - 1 >= 0) {
// removeFromSpotlight();
// lastSelectedCastIndex--;
// e.preventDefault();
// $.address.value(lastSelectedCastIndex);
// ca$t.scroll(jQuery.jcarousel.intval(lastSelectedCastIndex), true);
// switchTo(lastSelectedCastIndex);
// }
// return false;
// });
});
},
switchTo: function (i) {
//Refactor to conform to plugin style
// ca$t.scroll(jQuery.jcarousel.intval(i), true);
// $.address.title($("#title_text").text());
// putInSpotlight();
}
};
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.limelight');
}
};
})(jQuery);
//Following this pattern you'd be able to call your plugin like this.
$(".spotlight-btn.back a").limelight();
$(".spotlight-btn.back a").limelight("switchTo", 0);
Here's the official documentation on the subject: http://docs.jquery.com/Plugins/Authoring
Paste the switchTo() function within your <script></script> tags to make it a generally-available function.
You can use Jquery UI Widget Factory to create stateful plugins, which allows you to expose public methods (still avoiding global window scope) to be used even after the plugin has been instantiated
https://learn.jquery.com/plugins/stateful-plugins-with-widget-factory/

Categories

Resources