I'm following the tutorial here: http://docs.jquery.com/Plugins/Authoring
I just wanted to create a simple plugin for a project we are working on that would add +/- signs next to an input box that could be used to increment the value of the box.
So I'm reading the tutorial and doing the section that talks about having multiple methods and everything is going fine, except for one small hitch.
So here is the code:
(function( $ ) {
var methods = {
init: function(options) {
if (options) {
$.extend(settings, options);
}
this.css('float', 'left');
this.after('<div class="increment-buttonset"><div class="increment-button" style="float: left;">+</div><div class="decrement-button">-</div></div><br style="clear: both" />');
$('.increment-button').click(function() {
$.increment('change');
});
return this;
},
change: function() {
// Increment Decrement code would go here obviously
return this;
}
};
$.fn.increment = function(method) {
var settings = {
'step': 1
};
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 );
The issue is the $.increment('change');
I want to bind the +/- buttons on click to call the increment('change'). I get errors when I do that.
Uncaught TypeError: Object function (a,b){return new c.fn.init(a,b)} has no method 'increment'
I've tried it without the $. but that just tells me increment isn't defined yet. Am I messing some syntax up here or just going about this completely wrong?
The solution is pretty simple, you were calling the method the wrong way. It should be
$('.increment-button').click(function() {
$(this).increment('change');
});
That is, because function added to $.fn.functionName can only be called on a jQuery elements like $(selector).functionName.
Related
I have several pages which I wish to allow the the user to inline edit many fields and update the server DB. To implement this, my intent is to create a jQuery plugin which I can do the typical passing of the configuration options and uses ajax to save the results.
(function($){
var methods = {
init : function (options) {return this.each(function () {/* ... */});},
method1 : function () {return this.each(function () {/* ... */});},
method2 : function () {return this.each(function () {/* ... */});}
};
$.fn.myEditPlugin= function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); //Line 10
} else if (typeof method === 'object' || ! method) {
return methods.init.apply(this, arguments); //Line 12
} else {
$.error('Method ' + method + ' does not exist on jQuery.myEditPlugin');
}
};
}(jQuery)
);
For each individual page, there are several options which are common to all (i.e. the url endpoint, the record's primary key, etc) and I would rather not duplicate each when applying the plugin.
Originally, I was just going to define a function on each page which takes some input and applies the common options to each.
function wrapEdit(e,options) {
options.url='/page1/etc';
options.pk=document.getElementById('pk').value;
return $(e).myEditPlugin(options);
}
wrapEdit('.someclass',{foo:123});
It doesn't seem all that professional to me, so in my obsessive quest, thought I would make a class which I could pass the common options to and it would apply the plugin.
class WrapEdit(options)
{
constructor(options) {
this.options = options;
}
this.applyIndividualOptions=function(e, options) {
return $(e).myEditPlugin(Object.assign({}, this->options, options));
}
}
var wrapEdit=new WrapEdit({url: '/page1/etc', pk: document.getElementById('pk').value});
wrapEdit.applyIndividualOptions('.someclass',{foo:123});
Better, but not very jQueryish as I will be passing the select element instead of directly applying the plugin to elements typical of jQuery.
Is it possible to create an instance of a jQuery plugin which keeps previously defined data? Maybe something like the following:
$.myEditPlugin({url: '/page1/etc', pk: document.getElementById('pk').value});
$('.someclass').myEditPlugin({foo:123}); //Will also pass previously defined url and pk to myEditPlugin
Or maybe best to create a custom jQuery plugin per page which just adds the extra options and initiates the real plugin...
$.fn.myEditPluginInstance = function(options) {
return this.myEditPlugin(Object.assign({url: '/page1/etc', pk: document.getElementById('pk').value}, options));
};
Creating a function to be called against a jquery collection
The basic idea is to define a new property (function) in jQuery.fn, before any call to your plugin is made (In other words, any code related to the application is executed). You can use an "Immediately Invoked Function Expressions" (a.k.a. IIFEs) to fence your plugin API in. Then you have to loop over the collection and execute any code your plugin needs to apply on the collection items.
Basic skeleton:
(function ($) {
// Enclosed scope (IIFE)
// You can define private API/variables in here
// …
// Once your plugin API is ready, you have to apply the magic to each item
// in the collection in some ways. You must add a property to jQuery.fn object.
$.fn.myAwesomePlugin = function(Opt) {
var defaultConfig = {option1: 'someValue' /*, …*/};
// Eval supplied Opt object (Validate, reject, etc.)
// If all goes well, eventually merge the object with defaults.
$.extend(defaultConfig, Opt);
// Apply the magic against each item in the jQuery collection
// (Your plugin may not need to use "each" function though)
// Return the jQuery collection anyway to keep chaining possible.
// Once again, this is not required, your plugin may return something else depending on the options passed earlier for instance.
return this.each(function(el, idx) {
// Your plugin magic applied to collection items…
});
}
})(jQuery);
You should be able to call your plugin $('someSelector').myAwesomePlugin(); right after the declaration.
Simple implementation example:
(function ($) {
let required = {url: null, pk: null}
// Function to be executed upon first call to the plugin
, populateCommons = () => {
let ep = $('#someNode').data('endpoint')
, pk = document.querySelector('#pk')
;
// Basic tests to alert in case the page
// doesn't comply with the plugin requirements
if( typeof ep !== 'string' || !/^\/[a-z]+/.test(ep) || !pk) {
throw ` "myEditPlugin" init phase error:
Detected endpoint: '${ep}'
Is PK value found: ${!!pk}
`;
}
[required.url, required.pk] = [ep, +pk.value];
};
$.fn.myEditPlugin = function(Opt) {
let allOpts;
// First call will trigger the retrival of common data
// that should be available as static data somewhere every page source.
!required.url && populateCommons();
allOpts = $.extend({}, Opt, required);
return this.each(function(el, idx) {
// Your logic here, request
console.log("Payload is", allOpts);
});
}
})(jQuery);
function debounce(fn, time) {
debounce.timer && (clearTimeout(debounce.timer));
debounce.timer = setTimeout(() => (fn(), debounce.timer = null), time);
}
$('[type="text"]').keydown(function(e){
debounce(() => this.value && $(this).myEditPlugin({foo:this.value, bar: 'Contextual value'}), 2000);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="pk" type="hidden" value="5">
<div id="someNode" data-endpoint="/api/endpoint">
Editing the below input will trigger the plug-in code
</div>
<input type="text" title="Edit me"/>
Related documentation here
I am new to jQuery and just learning new stuff. I was just reading through Chris Coyer's article and came across the following code :
$.fn.faq = function(options) {
return this.each(function(i, el) {
var base = el,
$base = $(el);
console.log(options);
base.init = function() {
// Do initialization stuff
$base
.find("dd")
.hide()
.end()
.find("dt")
.click(function() {
var ans = $(this).next();
if (ans.is(":visible")) {
base.closeQ(ans);
} else {
base.openQ(ans);
}
})
};
base.openQ = function(ans) {
// Open panel
ans.show();
// Do callback
options.qOpen.call();
};
base.closeQ = function(ans) {
// Open panel
ans.hide();
// Do callback
options.qClose.call();
};
base.init();
});
};
$("dl").faq({
qOpen: myQuestionOpenCallback,
qClose: myQuestionCloseCallback
});
function myQuestionOpenCallback() {
alert("answer opened!");
}
function myQuestionCloseCallback() {
alert("answer closed!");
}
Now I didn't quite understand this part of the code:
return this.each(function(i, el) {
The second line in the code, what exactly is i and el? I don't see anywhere these parameters being passed into the each function.
I asked a senior colleague of mine and got the following answer:
Many plugins start that way. Since most plugins are chainable, they
have to return this. And they also have to loop through the elements
from the selector,
return this.each(function(i, el) {
does them both. A loop, then the return.
but I still didn't quite understand.
The JS Fiddle can be found here.
Inside a jQuery plugin, this refers to the jQuery object representing what you called the plugin on. For example, in the case of this faq plugin, if I call $('#someDiv').faq({ ... });, this will be the same as $('#someDiv') inside the plugin function.
So because it is a jQuery object representing a selection of DOM nodes, you can call the jQuery method .each() on it, which takes a function that gets given two parameters when it is called for each DOM node in the selection:
The index (0, 1, 2 and so on)
The DOM node itself
.each() also returns the thing it was called on, so you end up returning the $('#someDiv') object from the plugin. That's great, because then we can call some other jQuery method on it straight afterwards ("chaining"). e.g. $('#someDiv').faq({ ... }).hide(); to hide it immediately.
https://api.jquery.com/jquery.each/
i : index of the element.
el : the DOM element (not a jQuery object).
I am trying to add a functionality to a web page that uses a jquery library which doesn't seem to have any documentation. (unknown origin) my problem is mainly due to the lack of understanding on jquery plugin model and/or inner workings of javascript.
1. the plugin is initiated as follows
jQuery('div.carousel').scrollGallery({
mask: 'div.mask',
slider: 'div.slideset',
slides: 'div.slide', ............ });
2. the plugin is defined in jquery as follows
;(function($){
function ScrollGallery(options) {
this.options = $.extend({
mask: 'div.mask', ...... }, options);
this.init();
3. in the Object.prototype declaration i see the following function numSlide defined.
ScrollGallery.prototype = {
....................
numSlide: function(c) {
if(this.currentStep != c) {
this.currentStep = c;
this.switchSlide();
}
},
.......... };
Question.
How do i reference numSlide(int) function externally?.
I tried the following methods and it did not work.
myx = jQuery('div.carousel').scrollGallery({ // var myx was added in the global scope
myx.numSlide(1); //error undefined is not a function
i tried adding return this; at the end of myx = jQuery('div.carousel').scrollGallery({ but it still returns the jQuery object.
i also tried
jQuery.scrollGallery().numSlide(2); //error undefined is not a function
jQuery.scrollGallery.numSlide(2); //same error
Do i need to add LIGHT BULB
// jquery plugin
$.fn.scrollGallery = function(opt){
return this.each(function(){
$(this).data('ScrollGallery', new ScrollGallery($.extend(opt,{holder:this})));
});
};
}(jQuery));
ANSWER (I think)
it looks like the ScrollGalary object is stored in a data for the selector. So i believe i can do the following jQuery('selector').data('ScrollGallery').numSlide(2);
I decided to post this anyway in-case if anyone in the future had a similar gullible situation.
One way of doing this will be to initiate ScrollGallery object first and then use it.
var test = new ScrollGallery();
test.numSlide();
if you want to extend jQuery and use the function you can assign it as follows
$.fn.scrollGallery = new ScrollGallery();
and use it
$("window").scrollGallery.numSlide();
I want to create a jQuery plugin which can be attached to a text box, and after the user enters a certain key combination, a callback function can be called, with a variable that is set based on the entered key combo. I'm coming from a Ruby background, and I'm not sure if this is even possible in Javascript/jQuery. Here's an example:
$('textbox').attach_my_plugin(function(){|key_combo_var|
// do something with key_combo_var...
});
How would I achieve this? Plan B is to stick key_combo_var into the .data() of the element. Would there be a better way than this?
This is totally possible. Although you don't give much details (what certain action ?).
A good start is this jQuery plugin boilerplate
The site provides a way to start creating your own plugin. The thing is pretty well documented so if you can read javascript/jquery code, it should not be too difficult.
If you provide a bit more details on what you'd like to do, I can help you further implementing it but right now it's a bit too blurry.
As example
I have created using the boilerplate an example of a plugin that should do what you're looking after. At least this will give you a good start.
It basically will execute the callback when you press ctrl-shift-a.
You can test it live on jsfiddle.
;(function ( $, window, document, undefined ) {
var pluginName = 'callbackOnKey',
defaults = {
// define a default empty callback as default
callback: function() {}
};
function Plugin( element, options ) {
this.element = element;
this.options = $.extend( {}, defaults, options) ;
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype.init = function () {
var $this = $(this.element),
keydownHandler = function(e) {
// in here, 'this' is the plugin instance thanks to $.proxy
// so i can access the options property (this.options.callback)
// if key combination is CTRL-SHIFT-a
if (e.ctrlKey && e.shiftKey && e.which === 65 && this.options.callback) {
// execute the callback
this.options.callback.apply(this);
}
};
// bind the handler on keydown
// i use $.proxy to change the context the handler will be executed
// with (what will be 'this' in the handler). by default it would
// have been the input element, now it will be the plugin instance
$this.bind('keydown', $.proxy(keydownHandler, this));
};
$.fn[pluginName] = function ( options ) {
return this.each(function () {
if (!$.data(this, 'plugin_' + pluginName)) {
$.data(this, 'plugin_' + pluginName, new Plugin( this, options ));
}
});
}
})(jQuery, window, document);
// use the plugin and pass a callback function
$('#myinput').callbackOnKey({
callback: function() { alert("It's working :o)"); }
});
I would like to create a custom version of the sortable widget. I have been searching for documentation, but could not find something really accurate. The best information I found was : http://jqueryui.pbworks.com/Widget-factory.
I tried :
$.widget("ui.customsortable", $.extend($.ui.sortable, {
_init: function() {
$.widget.prototype._init.apply(this, arguments);
}
}));
But $.widget.prototype._init is not the function I want to call I guess since it is the $.widget prototype.
Then, I tried something I read here and there :
var _init = $.ui.sortable.prototype._init;
$.widget("ui.customsortable", $.extend($.ui.sortable, {
_init: function() {
_init.apply(this, arguments);
},
}));
But :
I can't believe I have to store all methods I want to override like this, it is so ugly.
It throws an error ("this.refresh is not a function"), which means the refresh method does not exist. Does that mean I would have to recreate all methods I want to override ? What's the point of extending in that case ?
Am I missing something here ?
Thanks for your help !
These are kinda strange answers.
There is an optional second parameter - basewidget to inherit from. It's easy. No need to work with prototype and so on.
$.widget( "ui.customsortable", $.ui.sortable, {
_init: function() {
this.element.data('sortable', this.element.data('customsortable'));
// or whatever you want
}
} );
The second parameter is $.ui.sortable. I think it's all you need.
After several tries, I finally found out how to do this easily :
$.widget("ui.customsortable", $.extend({}, $.ui.sortable.prototype, {
_init: function(){
this.element.data('sortable', this.element.data('customsortable'));
return $.ui.sortable.prototype._init.apply(this, arguments);
}
// Override other methods here.
}));
$.ui.customsortable.defaults = $.extend({}, $.ui.sortable.defaults);
The key is to copy data from your custom widget to the original one.
Don't forget to use $.ui.sortable.prototype.[overriden method].apply(this, arguments); in each overriden method.
Holly crap !
Regarding the selected solution above:
$.widget("ui.customsortable", $.extend(true, {}, $.ui.sortable.prototype, {
If you are extending one objects options into another, the [deep] flag of true will give you the desired results.
I'm using this in order to predefine start, stop and update functions:
$.widget('ui.custom_sortable_or_any_other_name', $.ui.sortable, {
_init: function() {
this.element.children().css('position', 'relative'); //for example
},
options : {
start: function (event, ui) {
ui.item.addClass('noclick'); //ui.item get's the item, that's my point
},
stop: function (event, ui) {
},
update: function (event, ui) {
$.ajax(); //some ajax you might want to do
}
}
});
I don't know just what you're after, when you say "extend a widget". In my case I wanted to change how the widget rendered itself, and fiddling with the CSS classes didn't satisfy. It was not a case of extending the behavior of a widget, but rather modifying the behavior of a widget.
So I over-rode the render method. The widget in question was the jQueryUI autocomplete, and the over-ride looked like this:
function monkeyPatchAutocomplete() {
// don't really need this, but in case I did, I could store it and chain
var oldFn = $.ui.autocomplete.prototype._renderItem;
$.ui.autocomplete.prototype._renderItem = function( ul, item) {
// whatever
};
}
I just called that in $(document).ready().
related:
- Can I replace or modify a function on a jQuery UI widget? How?
- jQueryUI: how can I custom-format the Autocomplete plug-in results?
I used this one time:
$.ui.buttonset.prototype.value = function() {
return this.element.find('#' + this.element.find('label[aria-pressed="true"]').attr('for')).val();
}