I'm making my own jQuery plugin (I am still learning!). The plugin needs to return values once clicked on a certain element.
It looks along the lines of this:
$.fn.testPlugin = function(options) {
var defaultOptions = {
values: {
value1: "",
value2: ""
},
onDivNameClick: function() {}
};
options = $.extend(true, defaultOptions, options);
//Do some initializing here
divName.click(function () {
//Do some other plugin stuff on click here
// onClick User Function
if ($.isFunction(options.onDivNameClick)) {
options.onDivNameClick.call(options.values);
}
})
//Other code here.
return this;
}
I initialize the plugin and pass commands that I would like to be executed however the plugin does not return the values I need through values instead it is 'undefined'.
Debugging shows me that options.values does have data inside the plugin so I'm not entirely sure this is the correct way to do it.
$("#elName").testPlugin({
onDivNameClick: function (values) { //values returns 'undefined'
model.value1 = values.value1;
model.value2 = values.value2;
},
option1: "",
//other options
});
onDivNameClick should pass options.values into the function in options but it currently does not. What am I doing wrong?
The javascript prototype.call() method needs one more parameter referring to this.
if ($.isFunction(options.onDivNameClick)) {
options.onDivNameClick.call(this, options.values);
}
Because of this - no actual values were passed through the function.
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
So, i'm using the jplist plugin to filter a list of flats and i'd need at some point to reset one (or more) specific filters. Point is, there's no api we can use to control the plugin externally.
Here's the code used for the reset button to reset all filters to default:
(function() {
var d = function(b) {
b.$control.on("click", function() {
b.observer.trigger(b.observer.events.unknownStatusesChanged, [!0])
})
},
e = function(b) {
d(b);
return jQuery.extend(this, b)
};
jQuery.fn.jplist.ui.controls.Reset = function(b) {
return new e(b)
};
jQuery.fn.jplist.controlTypes.reset = {
className: "Reset",
options: {}
}
})();
I have to admit that i don't fully get how it works but i've done a quick test with this:
jQuery('#some-id-button').on('click', function() {
b.observer.trigger(b.observer.events.unknownStatusesChanged, [!0])
});
And it does reset all filters from a specific element.
Question is, how could i pass some arguments (id, classes) to target specific filters and not all of them?
I want to create jQuery plugin with config (for example plugin myplugin).
Than call $(elem).myplugin(config); After that I want to call methods from this plugin like $(elem).myplugin().method() with already stored config.
My offer is something like that:
(function($) {
$.fn.myplugin = function(options) {
var $this = $(this);
var getOptions = function() {
return $this.data('myplugin');
};
var initOptions = function(opt) {
$this.data('myplugin', opt);
};
var setOption = function(key, value) {
$this.data('myplugin')[key] = value;
}
var updateBorderWidth = function() {
$this.css('border-width',
getOptions().borderWidth * getOptions().coeficient);
};
var init = function(opt) {
initOptions(opt);
updateBorderWidth();
}
function changeBorder(width) {
setOption('borderWidth', width)
updateBorderWidth();
}
if(options) {
init(options);
}
return {
changeBorder : changeBorder
};
}
})(jQuery);
And usage:
$(function() {
var item1 = $('#test1').myplugin({ coeficient: 1, borderWidth: 1 });
var item1 = $('#test2').myplugin({ coeficient: 2, borderWidth: 1 });
$('#btn').click(updateBorder);
});
function updateBorder() {
$('#test1').myplugin().changeBorder($('#inpt').val());
$('#test2').myplugin().changeBorder($('#inpt').val());
}
Example: http://jsfiddle.net/inser/zQumX/4/
My question: is it a good practice to do that?
May be it's incorrect approach. Can you offer better solution?
Edit:
After searching for threads on jQuery plugin template I found these Boilerplate templates (updated) which are more versatile and extensive designs than what I've offered below. Ultimately what you choose all depends on what your needs are. The Boilerplate templates cover more use cases than my offering, but each has its own benefits and caveats depending on the requirements.
Typically jQuery plugins either return a jQuery object when a value is passed to them as in:
.wrap(html) // returns a jQuery object
or they return a value when no parameter is passed in
.width() // returns a value
.height() // also returns a value
To read your example calling convention:
$('#test1').myplugin().changeBorder($('#inpt').val());
it would appear, to any developer who uses jQuery, as though two separate plugins are being utilized in tandem, first .myplugin() which one would assume will return a jQuery object with some default DOM maniplulation performed on #test1, then followed by .changeBorder($('#inpt').val()) which may also return a jQuery object but in the case of your example the whole line is not assigned to a variable so any return value is not used - again it looks like a DOM manipulation. But your design does not follow the standard calling convention that I've described, so there may be some confusion to anyone looking at your code as to what it actually does if they are not familiar with your plugin.
I have, in the past, considered a similar problem and use case to the one you are describing and I like the idea of having a convenient convention for calling separate functions associated with a plugin. The choice is totally up to you - it is your plugin and you will need to decide based on who will be using it, but the way that I have settled on is to simply pass the name of the function and it's parameters either as a separate .myplugin(name, parameters) or in an object as .myplugin(object).
I typically do it like so:
(function($) {
$.fn.myplugin = function(fn, o) { // both fn and o are [optional]
return this.each(function(){ // each() allows you to keep internal data separate for each DOM object that's being manipulated in case the jQuery object (from the original selector that generated this jQuery) is being referenced for later use
var $this = $(this); // in case $this is referenced in the short cuts
// short cut methods
if(fn==="method1") {
if ($this.data("method1")) // if not initialized method invocation fails
$this.data("method1")() // the () invokes the method passing user options
} else if(fn==="method2") {
if ($this.data("method2"))
$this.data("method2")()
} else if(fn==="method3") {
if ($this.data("method3"))
$this.data("method3")(o) // passing the user options to the method
} else if(fn==="destroy") {
if ($this.data("destroy"))
$this.data("destroy")()
}
// continue with initial configuration
var _data1,
_data2,
_default = { // contains all default parameters for any functions that may be called
param1: "value #1",
param2: "value #2",
},
_options = {
param1: (o===undefined) ? _default.param1 : (o.param1===undefined) ? _default.param1 : o.param1,
param2: (o===undefined) ? _default.param2 : (o.param2===undefined) ? _default.param2 : o.param2,
}
method1 = function(){
// do something that requires no parameters
return;
},
method2 = function(){
// do some other thing that requires no parameters
return;
},
method3 = function(){
// does something with param1
// _options can be reset from the user options parameter - (o) - from within any of these methods as is done above
return;
},
initialize = function(){
// may or may not use data1, data2, param1 and param2
$this
.data("method1", method1)
.data("method2", method2)
.data("method3", method3)
.data("destroy", destroy);
},
destroy = function(){
// be sure to unbind any events that were bound in initialize(), then:
$this
.removeData("method1", method1)
.removeData("method2", method2)
.removeData("method3", method3)
.removeData("destroy", destroy);
}
initialize();
}) // end of each()
} // end of function
})(jQuery);
And the usage:
var $test = $('#test').myplugin(false, {param1: 'first value', param2: 'second value'}); // initializes the object
$test.myplugin('method3', {param1: 'some new value', param2: 'second new value'}); // change some values (method invocation with params)
or you could just say:
$('#test').myplugin(); // assume defaults and initialize the selector
Passing parameters to javascript via data attributes is a great pattern, as it effectively decouples the Javascript code and the server-side code. It also does not have a negative effect on the testability of the Javascript code, which is a side-effect of a lot of other approaches to the problem.
I'd go as far as to say it is the best way for server-side code to communicate with client-side code in a web application.
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
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.)