jQuery Plugin/Widget Only Modifying Last Instantiated Variables - javascript

I'm fairly new to jQuery plugins so I think this is a relatively simple issue.
I've created a plugin to control an HTML calendar. I want to be able to have multiple calendars on the same page. Each calendar keeps track of its own data via the plugin.
When I have a single calendar on the page, it works great. However, as soon as more are added there are problems. The settings object (which stores all the calendar info) only gets updated for the last instantiated calendar. So when I click something to modify the first calendar, the settings object for the first calendar is not changed at all; only the second calendar's settings object is updated.
I think it's either an issue in how my plugin runs against the incoming jQuery objects or the way I'm using the data() method.
In my code below, when either of the 'change settings' listeners are fired, the settings.listView value is supposed to be changed. Then, when the showSettings() method is called it's supposed to use the updated value.
Here's a stripped down version of my plugin:
(function($){
var methods = {
_init: function($el, options, config){
var settings = $.extend({}, $.fn.pluginName.defaults, options, config);
$el.data("settings", settings);
settings = $el.data("settings");
this.$el = $el;
var instance = this;
// change settings
$el.on("click", ".popup.settings li.calendar", function(){
// do stuff...
//settings.listView = false; // doesn't work
instance.$el.data("settings").listView = false; // doesn't work either
});
$el.on("click", ".popup.settings li.list", function(){
// do stuff...
//settings.listView = true; // doesn't work
instance.$el.data("settings").listView = true; // doesn't work either
});
},
showSettings: function (el){
var settings = this.$el.data("settings"); // doesn't
var props = {
classes: "settings",
isList: settings.listView
};
// do more stuff...
},
}
$.fn.pluginName = function(options){
return this.each(function() {
var config = {
listView: false
}
methods._init($(this), options, config);
});
};
$.fn.pluginName.defaults = {
fullMobileQuery: "48em",
verticalScrollOffset: 30
};
}(jQuery));

Related

Transforming a jQuery plugin to support multiple instances

Here is a fictional version of my jQuery plugin, but the structure is exactly the same:
(function ($)
{
var initialized = false;
var element;
var counter = 0;
$.fn.myPlugin= function(action)
{
if (action === "increase")
{
increase(arguments[1]);
}
else if (!initialized)
{
settings = $.extend({
...
}, action);
initialized = true;
element = $(this);
return this;
}
else
{
console.error("Unknown function call.");
return;
}
};
var increase = function(amount)
{
counter += amount;
element.text(counter);
};
}(jQuery));
With this code I am able to initialize my plugin like this:
$("#element").myPlugin(options);
And I can call the method increase like this:
$("#element").myPlugin("increase", 5);
However, I am not able to initialize my plugin on multiple elements on one page, because of the variables initilized, element and counter.
How do I modify this code in such a way that I can use it multiple times on one page without changing the way you can initialize and call methods?
I do this exact same thing myself, and it's very simple once you know how.
Take this example of a simple plugin...
$.fn.myPlugin = function() {
// plugin global vars go here
// do plugin stuff here
}
To modify it to work on multiple instances, you just have to parse this when you call it...
$.fn.myPlugin = function() {
$(this).each(function() {
// plugin global vars go here
// do plugin stuff here
});
}
That way it will work when you assign the plugin to either a single instance, or a collection of elements.
Then, to call methods on individual elements, you just need to specify the correct one...
$("#element1").doMethod(1, 2, 3);
$("#element2").doMethod(4, 5, 6);

Access plugin instance using the id of the DOM Element

The concept is to have 2 plugins one for form and another for button. I want to bind all forms in my page to JQuery plugin that will handle some jobs let say that this is my plugin
$.fn.PluginForm = function (Options) {
var o = jQuery.extend({
SomeOption: 1
}, Options);
var Validate = function(){
if(o.SomeOption == 1) return true;
else return false;
};
$(this).on('submit', function(e) {
e.preventDefault();
//some code here
});
};
The form actually doesn’t have button in my case the post is triggered from another control. This is because of the structure of the application I want to build. The button plugin is:
$.fn.PluginButton = function (Options) {
var o = jQuery.extend({
Actions: [],
FormID: ''
}, Options);
$(this).click(function(){
var Form = $('#' + o.FormID);
if(Form.length > 0 && Form.PluginForm.Validate()) {
Form.submit();
//do something
}
else{
//do something else
}
});
};
What I want to succeed is to invoke the validation function on the Form element but I don’t want to invoke another instance of the PluginForm. Something like $('#' + o.FormID).PluginForm.Validate()
All this must be as plugin because there will be a lot of forms in the same page and a lot of buttons. Also there will be a lot of buttons that can invoke submit on the same form but with different options. That’s why I want to invoke one time the instance of the form. Also the controls that will be validated will be passed as parameter in the options of the PluginForm. Something like this $('#' + o.FormID).PluginForm({ Action: ‘Validate’ }) is not an option because will lose the initial parameters of the PluginForm.
You can save the plugin instance in the .data() structure on the element, and then call it back. Most of plugins use it that way.
/*!
* jQuery lightweight plugin boilerplate
* Original author: #ajpiano
* Further changes, comments: #addyosmani
* Licensed under the MIT license
*/
// the semi-colon before the function invocation is a safety
// net against concatenated scripts and/or other plugins
// that are not closed properly.
;(function ( $, window, document, undefined ) {
// undefined is used here as the undefined global
// variable in ECMAScript 3 and is mutable (i.e. it can
// be changed by someone else). undefined isn't really
// being passed in so we can ensure that its value is
// truly undefined. In ES5, undefined can no longer be
// modified.
// window and document are passed through as local
// variables rather than as globals, because this (slightly)
// quickens the resolution process and can be more
// efficiently minified (especially when both are
// regularly referenced in your plugin).
// Create the defaults once
var pluginName = "defaultPluginName",
defaults = {
propertyName: "value"
};
// The actual plugin constructor
function Plugin( element, options ) {
this.element = element;
// jQuery has an extend method that merges the
// contents of two or more objects, storing the
// result in the first object. The first object
// is generally empty because we don't want to alter
// the default options for future instances of the plugin
this.options = $.extend( {}, defaults, options) ;
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype = {
init: function() {
// Place initialization logic here
// You already have access to the DOM element and
// the options via the instance, e.g. this.element
// and this.options
// you can add more functions like the one below and
// call them like so: this.yourOtherFunction(this.element, this.options).
},
yourOtherFunction: function(el, options) {
// some logic
}
};
// A really lightweight plugin wrapper around the constructor,
// preventing against multiple instantiations
$.fn[pluginName] = function ( options ) {
return this.each(function () {
if (!$.data(this, "plugin_" + pluginName)) {
$.data(this, "plugin_" + pluginName,
new Plugin( this, options ));
}
});
};
})( jQuery, window, document );
taken from: https://github.com/jquery-boilerplate/jquery-patterns/blob/master/patterns/jquery.basic.plugin-boilerplate.js
also there are more jquery plugin design patterns that may fit more for your plugin at http://jqueryboilerplate.com/.

Adding and overriding functions in existing plugin

Below is my my plugin:
(function($) {
$.fn.myPlugin = function(options) {
var opt = $.extend({}, $.fn.myPlugin.defaults, options);
this.foo()
{
alert('test');
}
}
$.fn.myPlugin.defaults = {
};
});
Now I want to extend it without touching the original plugin i.e I want the full feature of existing plugin + the new features which I want. Below are the new things I need:
First:
Name of the new plugin "myPlugin2"
Second:
The "foo" function of the existing plugin should be overridden in the new plugin with this:
function foo() {
alert('test2');
}
Third:
I need to add one more method to my new plugin say function foo2(){} .
Can you help me in achieving this?
You need to define your default name and foo events in your defaults declaration:
$.fn.myPlugin.defaults = {
name: 'test',
onFoo: function() {
alert(this.name);
},
onFoo2: function() {
// your default behaviour for foo2
}
};
Then, when someone calls your plugin, they can override the defaults, in this case name:
$("#myControl").myPlugin({
name: 'test2'
});
Note that they don't need to override onFoo, because it will display an alert with test2. Anyway, if they need to override it to do something different, then they should:
$("#myControl").myPlugin({
name: 'test2',
onFoo: function() {
alert('onFoo overrired');
},
onFoo2: function() {
alert('onFoo2 overrired');
}
});
In your plugin, you invoke the foo methods as
(function($) {
$.fn.myPlugin = function(options) {
var opt = $.extend({}, $.fn.myPlugin.defaults, options);
// if onFoo is defined then call it
if (opt.onFoo) {
opt.onFoo();
}
// if onFoo2 is defined then call it
if (opt.onFoo2) {
opt.onFoo2();
}
}
$.fn.myPlugin.defaults = {
name: 'test',
onFoo: function() {
alert(this.name);
},
onFoo2: function() {
// your default behaviour for foo2
}
};
});
You should use this technique for public methods/properties that you want to expose to the users of your plugin.
I didn't tested but should work
Edit
You need to check if the event is set before calling it:
// if onFoo is defined (not null) then call it
if (opt.onFoo) {
opt.onFoo();
}
You are setting already an event for onFoo and onFoo2, but the user of your plugin might choose to disable it:
$("#myControl").myPlugin({
onFoo: null
});
In this case, although you have defined an onFoo event, the user of your plugin decided to ignore it, by setting it to null. So, even though you have defined an event, you never know what others will do with it, therefore it's better to be on the safe side and check for nullity.
Once again, you need to be careful with what you expose to the end user, because setting/unsetting events should not break the basic functionality of your plugin
If this is any decently coded plugin, you shouldn't be able to alter it's methods. It should of made anything which isn't meant to be invoked an internal function i.e.:
$.fn.oldPlugin = function() {
var foo = function() {
alert('old code');
};
};
There is no way to invoke foo or overwrite it.
Should you not need to change any of the methods/functions then you can use $.extend($.fn.pluginName, {/*your methods/properties*/};
What it all really comes down to is:
How the plugin you want to extend is coded
If you want to overwrite or just extend on it's functionality

jQuery: Why would trigger not fire from a JS object?

I've been implementing a form of a publisher/subscriber design pattern in jQuery. I'm basically building classes in Javascript utilizing CoffeeScript that serve as components on my page. i.e. Navigation, DataList, etc.
Instead of having DOM elements fire events, I have instances of these classes that use trigger on themselves to send custom events. These instances can then listen to each other and can update the DOM elements they own accordingly based on the changes in each others behavior!
I know this works as I have one of my components dispatching a custom event properly. However, I've ran into a snag. I've created another component and for the life of me I cannot figure out why it's event is not being fired.
This is the implementation of my class:
window.List = (function() {
List = function(element, settings) {
var _a, _b, _c;
this.list = $(element);
this.settings = jQuery.extend(List.DEFAULTS, settings);
this.links = this.list.find(this.settings.link_selector);
this.links.selectable();
_b = [SelectableEvent.COMPLETED, SelectableEvent.UNDONE, SelectableEvent.SELECTED, SelectableEvent.DESELECTED];
for (_a = 0, _c = _b.length; _a < _c; _a++) {
(function() {
var event_type = _b[_a];
return this.links.bind(event_type, __bind(function(event, selectable_event) {
return this.dispatch(selectable_event);
}, this));
}).call(this);
}
return this;
};
List.DEFAULTS = {
link_selector: "a",
completed_selector: ".completed"
};
List.prototype.change = function(mode, previous_mode) {
if (mode !== this.mode) {
this.mode = mode;
if (previous_mode) {
this.list.removeClass(previous_mode);
}
return this.list.addClass(this.mode);
}
};
List.prototype.length = function() {
return this.links.length;
};
List.prototype.remaining = function() {
return this.length() - this.list.find(this.settings.completed_selector).length;
};
List.prototype.dispatch = function(selectable_event) {
$(this).trigger(selectable_event.type, selectable_event);
return alert(selectable_event.type);
};
return List;
}).call(this);
Pay attention to:
List.prototype.dispatch = function(selectable_event) {
$(this).trigger(selectable_event.type, selectable_event);
return alert(selectable_event.type);
};
This code is triggered properly and returns the expected event type via an alert. But before the alert it is expected to trigger a custom event on itself. This is where I'm encountering my problem.
$(document).ready(function() {
var list_change_handler, todo_list;
todo_list = new List("ul.tasks");
list_change_handler = function(event, selectable_event) {
return alert("Hurray!");
};
$(todo_list).bind(SelectableEvent.COMPLETED, list_change_handler);
$(todo_list).bind(SelectableEvent.UNDONE, list_change_handler);
$(todo_list).bind(SelectableEvent.SELECTED, list_change_handler);
$(todo_list).bind(SelectableEvent.DESELECTED, list_change_handler);
}
You see here the alert "Hurray" is what I want to fire but unfortunately I am having no luck here. Ironically I've done the exact same thing with another class implemented the same way dispatching a custom event and the listener is receiving it just fine. Any ideas on why this wouldn't work?
Update:
Per discussing in the comments, it looks like Logging "this" in console returns the JS Object representing the class. But logging "$(this)" returns an empty jQuery object, thus trigger would never be fired. Any thoughts on why $(this) is coming up empty when "this" is accurately returning the instance of the class?
I found out that jQuery could not index my object because the class implemented it's own version of a jQuery method. In this case, length(). Renaming the length() method to total() resolved the problem completely and any instance of the class can successfully trigger events.

jQuery Plugin Authoring - Set different options for different elements

I have created a jQuery plugin that works great with the exception of being able to call the plugin on different objects and each object retaining the options it was given. The problem is that if I call the plugin on one object, say:
$('#myDiv1').myPlugin({
option1: 'some text',
option2: true,
option3: 'another option value'
});
then call the plugin again on another object, say:
$('#myDiv2').myPlugin({
option1: 'different text',
option2: false,
option3: 'value for myDiv2'
});
Then if I go back and try to do something with #myDiv1 that needs its original options to still be intact, ie:
$('#myDiv1').myPlugin.update();
it won't have it's original options, but they will be overridden by the options for #myDiv2. What's the proper way to do this so that each object will retain the original options given to it? (And here's some example code of what I'm doing in the plugin)
(function($) {
$.fn.myPlugin = function(options) {
// build main options before element iteration
var opts = $.extend({}, $.fn.myPlugin.defaults, options);
_option1 = opts.option1;
_option2 = opts.option2;
_option3 = opts.option3;
// iterate all matched elements
return this.each(function() {
callPluginFunctions( this, opts );
});
};
....code continued....
I realize this is some kind of scope creep or something. So, how do I get my options to stay attached and remain in the scope of the original object (ie #myDiv1) that they were given to.
EDIT: In doing some research I see that you can store data to an object using jQuery's .data function, and the docs say jQuery UI uses it extensively. Would the proper thing to do here be store the options on the object using .data, then when referenced later use the options stored in .data ???
First, you will generally want to handle the command within your extension method. Second, you should be attaching configurations to each item...
(function($){
var defaultOptions = { /* default settings here */ };
//called on the native object directly, wrap with $(obj) if needed.
function initializeObject(obj, options) {
//assign the options to the object instance.
$(obj).data('myPlugin-options', $.extend(defaultOptions, options) );
//do other initialization tasks on the individual item here...
}
function updateObject(obj) {
// use $(obj).data('myPlugin-options');
}
function setOption(obj, key, value) {
var d = $(obj).data('myPlugin-options');
d[key] = value;
$(obj).data('myPlugin-options', d);
}
$.fn.myPlugin = function(command, option, val) {
if (typeof command == "object") {
//initialization
return this.each(function(){
initializeObject(this, command);
});
}
if (typeof command == "string") {
// method or argument query
switch (command.toLowerCase()) {
case 'option':
//get value, return the first item's value
if (typeof val == undefined) return this.eq(0).data('myPlugin-options')[option];
//set option value
return this.each(function() {
setOption(this, option, val);
});
case 'update':
return this.each(function() {
updateObject(this);
});
//other commands here.
}
}
}
})(jQuery)
With the above example, you have a generic template for a jQuery extension, It's usually good form to have the following convention for use..
Initialization:
$(...).myPlugin({ initialization-options-here });
Command:
$(...).myPlugin('command-name'); //where command can be update, etc.
Get Option:
var myValue = $(...).myPlugin('option', 'option-name');
Set Option:
$(...).myPlugin('option', 'option-name', newValue);
Updated to use .data off of each individual obj.
I've been having the same problem, but only functions passed were being overwritten. Straight up properties were persisting.
Anyway, to further explain what ~reinierpost meant, changing your code to this should work. You'll have to pass 'opts' everywhere, but I think that's necessary to take the options out of the plugin-wide namespace.
$.fn.myPlugin = function(options) {
// iterate all matched elements
return this.each(function() {
// build main options before element iteration
var opts = $.extend({}, $.fn.myPlugin.defaults, options);
callPluginFunctions( this, opts );
});
};
Edit: Here's code from my plugin
this.each(function() {
var thisConfig = config;
thisConfig = $.extend(thisConfig,settings);
$.attach($(this),thisConfig);
});
...
$.attach = function($target,config){
So I have no plugin-wide "config" - just inside each function.
Your options are plugin-wide. Move them inside the each().
(Why are you asking this question? You pretty much spell out the solution yourself.)

Categories