Accessing public functions from inside a jQuery plugin - javascript

It is the first time I write a jQuery plugin without a tutorial. Now (September 28 2014), the jQuery site doesn't work (I don't know why), so I cannot find any resource there.
Below is part of my plugin that reports errors:
$(function($){
$.fn.dialog = function(command, options) {
var opts = $.extend( {}, $.fn.dialog.defaults, options );
//code
$.fn.dialog.handleCancel = function() {
};
$.fn.dialog.handleAccept = function() {
};
return this;
};
$.fn.dialog.defaults = {
// some other props
onCancel: $.fn.dialog.handleCancel(),
onAccept: $.fn.dialog.handleAccept()
};
// code
}(jQuery));
When I call the plugin ($("#dialog1").dialog(/*..*/)), the browser console, shows the following:
Uncaught TypeError: undefined is not a function
The error is on the line with onCancel: $.fn.dialog.handleCancel().
How can I access these methods, and where should them be? (I also want them to have access to $(this) <- for the dialog itself)

Your handleCancel and handleAccept functions are not initialized until you call the $.fn.dialog function. Therefore, they are undefined when you set the dialogs defaults.
Insert this code prior to $.fn.dialog.defaults:
$.fn.dialog();

Try rearranging blocks within the piece , adding a filter , to prevent both handleCancel and handleAccept being called by default; e.g.,
(function($){
$.fn.dialog = function(command, options) {
var $el = this;
// access , pass arguments to methods
$.fn.dialog.handleCancel = function(c) {
$el.html(c + "led")
};
$.fn.dialog.handleAccept = function(a) {
$el.html(a + "ed")
};
// call `handleCancel` or `handleAccept` ,
// based on `command`
$.fn.dialog.defaults = {
// some other props
onCancel: command === "cancel"
? $.fn.dialog.handleCancel(command)
: null,
onAccept: command === "accept"
? $.fn.dialog.handleAccept(command)
: null
};
var opts = $.extend( {}, $.fn.dialog.defaults, options );
//code
return this;
};
// code
}(jQuery));
$("button").on("click", function(e) {
$("#result").dialog(e.target.id)
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<button id="accept">accept</button><button id="cancel">cancel</button><br />
Result: <div id="result"></div>

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>

How to call a function's methods from within another function

In the form object below, from within the "check" function, how do I call the "show" and "hide" methods of the notification function?
(function (namespace, $, undefined) {
var form = {
check : function(){
form.notification.show(); // Generates an error
},
notification : function(){
this.show = function(){
...
};
this.hide = function(){
...
};
}
};
}(window.namespace = window.namespace || {}, jQuery));
With form.notification.show() I get the following error:
Uncaught TypeError: Cannot read property 'show' of undefined
Try to define notification outside form and then refer to it:
var notification : { // no function here
show : function(){...}, // avoid "this"
hide : function(){...}
};
var form = {
check : function(){
notification.show(); // <-- Use here
},
notification : notification // and here
};
(I omitted the jQuery protection code for clarity).
The next problem is that you this.show = will assign the function to whatever this is when the function notification() is executed. this isn't notification!
You've enclosed it, so you need to return it and that will expose it for you, if you whip the following in chrome console, you'll see you have access to the form object
(function (namespace, $, undefined) {
var form = {
check : function(){
form.notification.show(); // Generates an error
},
notification : function(){
this.show = function(){
};
this.hide = function(){
};
}
};
return{form:form};}(window.namespace = window.namespace || {}, jQuery));
All i've done to your code is added
return{form:form};
After the form object. Hope this helps
EDIT
If you want to only expose certain parts of the form, for example only notifications, you could modify the return like so:
return{form.notification: notification}

jquery fn.extend() returning values and call values in object

I need some help in understanding something, that propably is easy for serious jquery and javascript programmers.
Lets say I have a code like this:
jQuery.fn.extend({
myNameSpace: {
myPlugIn: function (o) {
var o = { variable : o.variable || false };
var myfunction = function(v) {
o.variable = v;
};
return {
myfunction : myfunction
};
}
});
and now I am able to call that with:
x = new $.myNameSpace.myPlugIn({variable : 99}) ;
and then I call my function myfunction like this
x.myfunction(20);
I can understand that, now the question: how can I get the value of variable inside my plug in.
I tried something like alert(x.o[variable]); etc. but I just cant get it - It must be easy...
What I try to accomplish is a value I could call if something inside the plugin is finished, or calculated.
You can not get the variables inside with your current code, unless you change it to:
var myfunction = function(v) {
o.variable = v;
return v; //or o.variable
};
//...
x.myfunction(20) //20;
Added
It seems like you are trying to make a plugin for jQuery. To create a plugin, you do not use $.extend. $.extend is only used to preset default settings. [1] Normally this is how you set up a plugin:
(function($) {
var methods = {
getVar: function(){
return $.extend({
data: data,
}, methods);
},
setVar: function(d){
data = d;
return methods;
}
},
data = {};
$.fn.myPlugin = function(options) {
//do stuff
data = $.extend( data , options );
return methods;
};
})(jQuery);
http://jsfiddle.net/DerekL/wv5QH/1/

Why is Drupal not recognizing my javascript function?

Why is the following not working:
(function($){
Drupal.my_module = {};
Drupal.behaviors.my_module = {
attach: function(context, settings) {
$('#some-div').Drupal.my_module.doStuff();
}
};
Drupal.my_module.doStuff = function(){
this.append('Hello World');
}
})(jQuery);
I get this error: TypeError: $(...).Drupal is undefined
If I use another architecture like passing the selector as an argument for the function it works:
(function($){
Drupal.my_module = {};
Drupal.behaviors.my_module = {
attach: function(context, settings) {
Drupal.my_module.doStuff($('#some-div'));
}
};
Drupal.my_module.doStuff = function(elem){
elem.append('Hello World');
}
})(jQuery);
It also works if I declare the function in the jQuery.fn object:
$.fn.doStuff = function(){...do something...}; // It works
Then call it like:
$('#my-div').doStuff(); // It works
But I want to put it under Drupal.my_module, something more like the first case.
Any clues?
Thanks!
$('#some-div').Drupal.my_module.doStuff();
Drupal is not a valid jQuery method or property, hence the undefined error.

Categories