Yielding a variable in a jQuery plugin / Javascript - javascript

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)"); }
});

Related

Create an instance of a jQuery plugin

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

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/.

How do I access declared functions the done() scope of a jQuery plugin that makes JSON requests like this?

I'm trying to keep a jquery plugin I'm working on configurable, and someone what maintainable, by keeping the various functions I use short and relatively easy to test.
To this I'm using some jQuery plugin code, based around the jQuery boilerplate, Addy Osmani's Lightweight Start, to have a plugin where I can pass in overrides, and compose existing functions from a series of small ones.
However, I'm having some trouble working out how to access functions I've declared from within a deferred done() callback, without declaring all the function code inside the done() function call again.
Is there a recommended pattern for making these functions available when using a prototype based approach like outlined in the boilerplate?
(function($, window, document, undefined) {
var pluginName = 'myModule';
function myModule(element, options) {
this.element = element;
// allow override of defaults
this.options = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = pluginName;
// calling the init() function defined below
this.init();
}
myModule.prototype = {
init: function() {
// add listeners for clicks on the element, and trigger some
// behaviour defined in fetchScore()
$(this.element).click(function() {
that.fetchScore();
return false;
});
},
handySuccessFunction: function() {
// some handy DOM manipulation stuff,
// kept out the main fetchScore function,
// ideally to make it more testable and readable
},
handyFailingFunction: function() {
// same again for failing case
},
fetchScore: function(authToken) {
$.getJSON(this.options.endpoint, {
apiKey: this.options.apiKey,
otherParam: this.options.otherParam,
token: authToken
})
.done(function(json) {
// I want to call the handySuccessFunction() here,
// but I have no access to myModule
})
.fail(function(jqxhr, textStatus, error) {
// Likewise I want to call the handyFailingFunction() here
});
}
}
// A really lightweight plugin wrapper around the constructor,
// preventing against multiple instantiations.
// We store a reference to the
$.fn[pluginName] = function(options) {
return this.each(function() {
if (!$.data(this, "plugin_" + pluginName)) {
$.data(this, "plugin_" + pluginName,
new pluginName(this, options));
}
});
}
})(jQuery, window, document);
Here's my expected usage:
jQuery(document).ready(function($) {
// console.log('clicking the popup');
$('#elementToAttachTo').myModule();
// clicking on this to trigger the fetchScore
// behaviour in myModule
$('#elementToAttachTo').click();
})
You should use "bind" to callback function in "done", set it context ("this") to myModule instance, where this function was declarated.
There are several ways.
You can use navtive Function.prototype.bind() method, which works in modern browsers
You can use jQuery $.proxy function.
So
myModule.prototype.fetchScore = function(authToken) {
$.getJSON(this.options.endpoint, {
apiKey: this.options.apiKey,
otherParam: this.options.otherParam,
token: authToken
})
.done(function(json) {
this.handySuccessFunction();
}.bind(this))
.fail($.proxy(function(json) {
this.handyFailingFunction();
}, this))
;
};

jQuery plug-in data access -- performance hit?

I'm writing a new jQuery plugin. For the guide, I am using their recommendation:
(function( $ ){
var methods = {
init : function( options ) {
return this.each(function(){
var $this = $(this),
data = $this.data('tooltip'),
tooltip = $('<div />', {
text : $this.attr('title')
});
// If the plugin hasn't been initialized yet
if ( ! data ) {
data = {
element : this,
target : $this,
tooltip : tooltip
};
$(this).data('tooltip', data);
}
methods.update.apply(data.element, 'Test');
},
update : function( content ) {
var $this = $(this),
data = $this.data('tooltip');
// check or change something important in the data.
private.test.apply( data.element );
return data.element;
}
};
var private = {
test: function() {
var $this = $(this),
data = $this.data('tooltip');
// again, do some operation with data
}
};
$.fn.tooltip = function( method ) {
// Method calling logic
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 );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
};
})( jQuery );
Its a little different from their version to make it shorter but also to show my differences. Basically, in the init, I am instantiating and creating data object that gets stored in the element. Part of the data object is the element itself:
element : this,
Then, after all of the initialization is done, I call a public method from the init (lets say I do it for functionality reuse purpose). To make the call, I use .apply() and provide the proper context (my element), which would match the context when the function is called externally:
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
This is fine and understandable. However, what I am unsure about is the performance of acquiring the data of the plugin from within a private or a public method. To me, it seems that at the top of every public and private method I have to execute the following lines in order to get the data:
var $this = $(this),
data = $this.data('tooltip');
Of course, I wouldn't execute them when I have no need for whatever is stored in data. However, my plugin performs quite a bit of animations and state tracking and almost all of the functions require access to the data. As such, it seems like accessing .data() in almost every private and public call is a pretty big performance hit.
My question is whether anyone uses this plug-in structure (I'm hoping that yes since jQuery recommends it) and has found a different way of referencing the data without hitting .data() in every function call.

Jquery plugin using methods needs to call itself

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.

Categories