jQuery Plugin callback doesn't work properly - javascript

I replicate the problem in simple plugin in jsfiddle: https://jsfiddle.net/5atn3010/1/
The idea is:
I have 2 selectors with a range of years (from 2005 to 2020)
When I select an option in the first selector, the selected value (for example 2010) is set in the second selector as the minimum value.
Finally the second selector is redrawn and only shows the new values (form 2010 to 2020)
This works, but with a big mistake. Not only changes the second select values, but the minimum value for the first selector also changes.
Why does it happen? How can I solve it?
;(function ( $, window, document, undefined ) {
"use strict";
var pluginName = "testing";
function Plugin( element, options ) {
this.element = element;
this.$element = $(element);
this.name = pluginName;
this.opts = $.extend({}, $.fn[pluginName].defaults, options);
this.$elements = {
year:null,
}
this.init(element,options);
}
Plugin.prototype = {
init: function () {
var me=this;
me.$elements.year=$("<select />").attr("name","year");
if (me.opts.css!=null) {
me.$elements.year.addClass(me.opts.css);
}
me.$elements.year.on("change",function() {
me.opts.onChange.call(me,me.$elements.year.val());
me._draw.call(me); //redraw me only for show error
});
me.$element.append(me.$elements.year);
me._draw();
},
_draw: function() {
var me=this;
var date_start=me.opts.date.start;
var date_end=me.opts.date.end;
me.$elements.year.find("option").remove();
for (var i=date_start;i<=date_end;i++) {
var option=$("<option/>").attr("value",i).text(i);
me.$elements.year.append(option);
}
},
setMin: function(min) {
this.opts.date.start=min;
this._draw();
}
}
$.fn[pluginName] = function(options) {
var param=arguments[1];
return this.each(function() {
if (!$.data(this, 'plugin_' + pluginName)) {
$.data(this, 'plugin_' + pluginName, new Plugin(this, options));
}
else if ($.isFunction(Plugin.prototype[options])) {
$.data(this, 'plugin_' + pluginName)[options](param);
} else {
$.error('Method ' + options + ' is not available');
}
});
};
$.fn[pluginName].defaults = {
date: {
start:2005,
end:2020
},
onSelect:function() {}
};
})( jQuery, window, document );
$().ready(function(){
$("#span1").testing({
onChange:function(min) {
console.log(min);
$("#span2").testing("setMin",min);
}
});
$("#span2").testing();
});

there are two issues with your code. First of all - you're not extending your options object recursively. To be short - you have two different opts objects that holds a reference to the same date object. I added some logging so you can understand what I'm talking about. You need to deep copy your options object.
Please, read carefully jQuery.extends page http://api.jquery.com/jquery.extend/
Secondly, you assign this.opts.date.start to a <option> value. It works okay for now but it won't work as expected if you will try to set a minimum date to selected date + N. It will concatenate N as a string instead of adding. I added some logs for this case as well.
http://jsfiddle.net/5atn3010/7/

Related

Extend jQuery Plugin with additional function / override function

I need to extend a jQuery Plugin (https://github.com/idiot/unslider) in order to add additional behavior with another public method.
(function(){
// Store a reference to the original remove method.
var originalMethod = $.fn.unslider;
// Define overriding method.
$.fn.unslider = function(){
// Execute the original method.
originalMethod.apply( this, arguments );
console.log( "Override method" );
function test() {
console.log("test called");
}
this.each(function() {
// Operations for each DOM element
console.log("each dom element?");
}).data('unslider', {
// Make test accessible from data instance
test: test
});
return this;
}
})(jQuery);
I already managed to make the public method accessible when calling
var slider = $('#slider');
slider.data('unslider').test();
However, I want to keep the old behavior of unslider anyways, but extend the Plugin with another function. Does anyone have an idea?
I created a fiddle, so you can check whats happening:
My new function gets called, but the old ones are gone:
http://jsfiddle.net/b2os4s7e/1/
If you look at the source of unslider, you can see it stores the Unslider instance inside the data:
// Enable multiple-slider support
return this.each(function(index) {
// Cache a copy of $(this), so it
var me = $(this),
key = 'unslider' + (len > 1 ? '-' + ++index : ''),
instance = (new Unslider).init(me, o);
// Invoke an Unslider instance
me.data(key, instance).data('key', key);
});
In your code you're overwriting this object with your own object. However, the slider expects there to be an Unslider instance. So what you want to do is get this instance and then extend it with your own functions:
var key = $(this).data('key');
var obj = $(this).data(key);
obj.test = function() { console.log('Working!'); };
See http://jsfiddle.net/b2os4s7e/2/
Just define:
$fn.unslider2 = function() { ... }
With any name and behaviour you like.
For extend JQuery should use .fn.extend
(function($){
$.fn.extend({
helloworld: function(message){
return this.each(function(){
$(this).click(function(){
alert(message);
});
});
}
});
})(jQuery)
the object .fn.extend is used for extend funcionality of jQuery
Thanks for your answers! I did it this way:
(function($){
var originalMethod = $.fn.unslider;
$.fn.extend({
unslider: function(o) {
var len = this.length;
var applyMethod = originalMethod.apply( this, arguments );
var key = applyMethod.data('key');
var instance = applyMethod.data(key);
// Cache a copy of $(this), so it
var me = $(this);
if (instance) {
instance.movenext = function (callback) {
return instance.stop().to(instance.i + 1, callback);
};
instance.moveprev = function (callback) {
return instance.stop().to(instance.i - 1, callback);
};
}
return applyMethod.data(key, instance);
}
});
})(jQuery)
The key was to address the data attribute as sroes suggested.
Moreover i needed to apply the original method, since i need the old methods.

jQuery plugin - update settings after initialization

I have a jQuery plugin, and I want to be able to change options on the fly, like this example: $('.element').pwstabs('options','effect',scale) or something simular to it. I tried adding update: function, tried adding Plugin.prototype.update, but still cant figure out how to do that :)
Here's the structure of the plugin:
;(function ($, window, document, undefined) {
var pluginName = "pwstabs",
defaults = {
effect: 'scaleout',
defaultTab: 1,
containerWidth: '100%',
tabsPosition: 'horizontal',
horizontalPosition: 'top',
verticalPosition: 'left',
responsive: false,
theme: '',
rtl: false,
controlls: false,
next: '',
prev: '',
first: '',
last: '',
auto: false,
play: '',
pause: ''
};
function Plugin(element, options) {
this.element = $(element);
this.$elem = $(this.element);
this.settings = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype = {
init: function(){
// Here's the code for the plugin
}
};
$.fn[pluginName] = function ( options ) {
return this.each(function () {
new Plugin( this, options );
});
};
})(jQuery, window, document);
So now I use the plugin like:
$('.element').pwstabs({
effect: 'scalein',
defaultTab: 2
});
And when I click a button, i want to change effect to lets say scaleout. With code like:
$('.button').click(function(){
$('.element').pwstabs('options','effect','scalein');
});
So how do I implement this in the plugin?
Currently the only supported invocation pattern in that plugin is to send in an object literal containing the settings to overwrite the defaults. E.g.:
$('.element').pwstabs({
effect: 'scalein',
defaultTab: 2
});
That invocation pattern is defined in the following method:
$.fn[pluginName] = function ( options ) {
return this.each(function () {
new Plugin( this, options );
});
};
As you see, a dictionary of options is sent as the only parameter to the constructor function Plugin() to build the plugin and initialize it.
To support the invocation pattern you need, you would have to modify this method to support both invocation patterns (initialization with an object literal, but also invoking any method with more params, like your options setting method).
Here is an improved function that will handle both invocation patterns. In addition it will also store the instance of a plugin on an element, so you can access the existing settings etc. on subsequent invocations (e.g. settings changes) on the same element.
$.fn[pluginName] = function (options) {
// get the arguments
var args = $.makeArray(arguments),
after = args.slice(1);
return this.each(function () {
// check if there is an existing instance related to element
var instance = $.data(this, pluginName);
if (instance) {
if (instance[options]) {
instance[options].apply(instance, after);
} else {
$.error('Method ' + options + ' does not exist on Plugin');
}
} else {
// create the plugin
var plugin = new Plugin(this, options);
// Store the plugin instance on the element
$.data(this, pluginName, plugin);
return plugin;
}
});
}
This would allow you to invoke the plugin as requested:
$('.element').pwstabs('options','effect','slidedown');
However, this implies you have an 'options' method in the Plugin prototype, so make sure to add one:
Plugin.prototype = {
options: function (option, val) {
this.settings[option] = val;
},
// Constructing Tabs Plugin
init: function () {
// omitted code for brevity
}
}
As you see the options settings just sets the new option on the existing instance. Very simple and efficient. The new setting will be picked up by the click method handler and voila!
Here is a jsFiddle with example code in case you have trouble implementing what i was describing so far:
http://jsfiddle.net/7whs3u1n/6/
Update: I have much improved my answer to get rid of unneeded stuff, include more details and a full implementation that works (check the fiddle above) ;) i hope that this answers your question!
Adding statefulness to your plugin wasn't hard, but when you have spare time also check the alternative mechanism for writing stateful jQuery stateful plugins called jQuery widget factory:
http://learn.jquery.com/plugins/stateful-plugins-with-widget-factory/
In the future you can consider rewriting your plugin to use the widget factory. It would certainly make your code simpler ;)
Try this pattern
(function ($) {
var defaults = {
"text": "abcdefg",
}
, options = $.extend({}, defaults, options);
$.fn.plugin = function (options) {
var options = (function (opts, def) {
var _opts = {};
if (typeof opts[0] !== "object") {
_opts[opts[0]] = opts[1];
};
return opts.length === 0
? def
: typeof opts[0] === "object"
? opts[0] : _opts
}([].slice.call(arguments), defaults));
return $(this).text(options.text)
}
}(jQuery));
$(".results:eq(0)").plugin(); // return `defaults`
$(".results:eq(1)").plugin({"text":"gfedcba"}); // return `options`
$(".results:eq(2)").plugin("text", 123); // return `arguments` as `options`
(function ($) {
var defaults = {
"text": "abcdefg",
}
, options = $.extend({}, defaults, options);
$.fn.plugin = function (options) {
var options = (function (opts, def) {
var _opts = {};
if (typeof opts[0] !== "object") {
_opts[opts[0]] = opts[1];
};
return opts.length === 0
? def
: typeof opts[0] === "object"
? opts[0] : _opts
}([].slice.call(arguments), defaults));
return $(this).text(options.text)
}
}(jQuery));
$(".results:eq(0)").plugin(); // return `defaults`
$(".results:eq(1)").plugin({"text":"gfedcba"}); // return `options`
$(".results:eq(2)").plugin("text", 123); // return `arguments` as `options`
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="results"></div><br />
<div class="results"></div><br />
<div class="results"></div>

Modify jQuery plugin function externally

I'm working with iThemes Exchange for WordPress and I'm trying to modify the javascript file that controls certain aspects of the iThemes Exchange shopping cart checkout process.
The problem is I can't modify the plugin itself because any changes will be overwritten when the client updates their plugins. So I'm trying to modify the javascript plugin's updateStates() function so that the country label is automatically set to "United States".
I've tried modifying the plugin using jQuery.fn.itCountryStatesSync which is where the plugin is, but I must be doing something wrong. If it helps I'll include the plugin's javascript file.
/**
* jQuery Country States Sync Plugin by iThemes
* Plugin framework via <http://coding.smashingmagazine.com/2011/10/11/essential-jquery-plugin-patterns/>
*/
;(function ( $, window, document, undefined ) {
// Create the defaults once
var pluginName = 'itCountryStatesSync',
defaults = {
stateWrapper : '.it-exchange-state',
stateFieldID : '#it-exchange-address-state',
action : 'ite-country-states-update',
templatePart : '',
clearTextValue : false,
ajaxUrl : itExchangeAjaxCountryStatesAjaxURL,
adminPrefix : false,
autoCompleteState : false,
autoFocusState : false
};
// The actual plugin constructor
function Plugin( countrySelectElement, options ) {
// Set element
this.element = countrySelectElement;
// 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) ;
// Country Field ID
this.options.countryFieldID = '#' + $(this.element).attr('id');
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
$(this.element).on('change', this.options, this.updateStates );
};
Plugin.prototype.updateStates = function( event ) {
var iteCountryStatesSyncPostData = {};
iteCountryStatesSyncPostData.ite_base_country_ajax = $(event.data.countryFieldID).val();
iteCountryStatesSyncPostData.ite_base_state_ajax = $(event.data.stateFieldID).val();
iteCountryStatesSyncPostData.ite_state_name_ajax = $(event.data.stateFieldID).attr('name');
iteCountryStatesSyncPostData.ite_action_ajax = event.data.action;
iteCountryStatesSyncPostData.ite_template_part_ajax = event.data.templatePart;
iteCountryStatesSyncPostData.ite_admin_prefix_ajax = event.data.adminPrefix;
iteCountryStatesSyncPostData.ite_clearTextValue = event.data.clearTextValue;
$.post(event.data.ajaxUrl, iteCountryStatesSyncPostData, function(response) {
if (response) {
var parentForm = $(event.data.countryFieldID).closest('form');
$(event.data.stateWrapper, parentForm).html(response);
if (event.data.autoCompleteState)
$(event.data.stateFieldID).filter('select').selectToAutocomplete();
if (event.data.autoFocusState) {
if ( $(event.data.stateWrapper).children('.ui-autocomplete-input').length !== 0 ) {
$(event.data.stateWrapper).children('.ui-autocomplete-input').focus();
} else {
$(event.data.stateFieldID).focus();
}
}
}
});
}
// 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 );

Writing jquery plugin and it is not executing

I am trying to write a simple jQuery plugin for my needs, using a variant of the first one in this style guide.
;(function($) {
var plugin_name = 'my_plugin',
defaults = {};
function Plugin ( element, options ) {
this.element = element;
this.options = $.extend( {}, defaults, options );
this._defaults = defaults;
this._name = plugin_name;
this.init();
}
Plugin.prototype = {
init: function () {
// Plugin code - attempt to debug
alert('hi');
}
}
$.fn[plugin_name] = function ( options ) {
return this.each(function () {
if (!$.data(this, 'plugin_' + plugin_name)) {
$.data(this, 'plugin_' + plugin_name, new Plugin( this, options ));
}
})
}
})( jQuery );
However, it doesn't seem to be executed when I call it. Fiddle here: http://jsfiddle.net/DCRnU/
$(document).ready(function() {
$.fn.my_plugin();
});
What am I missing out on?
You're not calling the function properly.
If you had a div element in your HTML, you could call your function like this:
$(document).ready(function() {
$("div").my_plugin();
});
Example fiddle
That's because this line: return this.each
It's expect to get some iterable object.
But there is nothing to loop over it.
if you add something like this:
var array = [1];
$(array).my_plugin();
it'll be fine.

Customer JQuery Plugin

I am learning JQuery. I have a need to create a custom control. This control is going to basically render some HTML. Sometimes, I want to just get the HTML. My hope is to use the following syntax:
// Put generated html inside of "myElement". "myElement" is a div element.
$("#myElement").myPlugin({ value: 10 });
// Retrieve the html that myPlugin would place of a div element.
// Basically, I want the javascript equivalent of a C# static function here.
// But I think the following approach is wrong:
var html = myPlugin().getHtml(10);
alert(html);
In an attempt to accomplish this, I'm using the following:
(function ($) {
$.fn.myPlugin = function (element, options) {
var defaults = { theValue: 0 }
var plugin = this;
plugin.settings = {};
var $element = $(element),
element = element;
plugin.init = function () {
plugin.settings = $.extend({}, defaults, options);
var html = createHtml(defaults.theValue);
$(element).html(html);
}
plugin.getHtml = function (v) {
return createHtml(v);
}
var createHtml(v) {
return "<span>" + v + "</span>";
}
})(jQuery);
I have two problems: 1) I can see that the HTML is being generated, but it does not appear to be added to the DOM. 2) I can't statically call the function. How can I make my function statically visible?
Thank you!
There are a few tweaks you need to make:
(function($) {
$.fn.myPlugin = function(options) {
var defaults = {
theValue: 0
},
settings = $.extend({}, defaults, options);
this.html($.myPlugin.getHtml(settings.theValue));
};
$.myPlugin = {
getHtml: function(value) {
return "<span>" + value + "</span>";
}
};
})(jQuery);
Usage:
$("#foo").myPlugin({ theValue: 10 });
var html = $.myPlugin.getHtml(10);
First off, you were never calling the init method, so nothing was going to work. It isn't going to be called automatically. Perhaps you were thinking of the jQueryUI widget factory?
Additionally, element doesn't get passed to the plugin, this refers the the element the plugin was called on. You only get passed options.
Finally, to create a static jQuery method, just attach it right to $.
Example: http://jsfiddle.net/xF2S6/

Categories