How can I provide a public function in a jQuery plugin? - javascript

I have written a small jQuery plugin with the following structure:
(function($) {
// Private vars
// Default settings
$.PLUGINNAME = {
id: 'PLUGINNAME',
version: '1.0',
defaults: {
min: 0,
max: 10
}
};
// extend jQuery
$.fn.extend({
PLUGINNAME: function(_settings) {
init = function() {
}
prepare = function() {
}
...
return this.each(function() {
_this = this;
init();
});
}
});
})(jQuery);
I am calling this as follows:
$("#update-panel").PLUGINNAME({
min: 1,
max: 20
});
I am trying to provide an additional public method where some data inside the function can be updated after the above function call to the plugin and am not sure how to go about doing this. What I am looking for is something like this:
_PluginInstance = $("#update-panel").PLUGINNAME({
min: 1,
max: 20
});
...
...
_PluginInstance.setMin(2); //should change the minimum to 2
setMin will probably use some of the plugin's internal variables so I am not understanding how to do this. I know I am not returning an instance of the plugin to do the above but can someone please tell me how to go about doing this by keeping the same plugin structure?

Just make the function a property of this within the PLUGINNAME object:
(function($) {
$.PLUGINNAME = {
// Default settings
};
// extend jQuery
$.fn.extend({
PLUGINNAME: function(_settings) {
// private methods
var init = function() {};
var prepare = function() {};
// public methods
this.setMin = function ( val ) {
_settings.min = val;
};
this.getMin = function () {
return _settings.min;
};
return this.each(function() {
_this = this;
init();
});
}
});
})(jQuery);
Then you could do:
_PluginInstance = $("#update-panel").PLUGINNAME({
min: 1,
max: 20
});
_PluginInstance.getMin(); // 1
_PluginInstance.setMin(2);
_PluginInstance.getMin(); // 2
EDIT: Oh god I can't believe I forgot the var keywords, why didn't y'all tell me my fly was down?

You could use the jQuery-UI method calling style:
$("#update-panel").PLUGINNAME('method', arguments...);
And then in your plugin:
PLUGINNAME: function(_settings, args) {
if(!$.isPlainObject(_settings)) {
// _settings (should) be the method name so
// do what needs to be done to execute the method.
return;
}
// Proceed as before.
You might want to use the arguments pseudo-array instead of the extra args parameter. You can store extra things in $(this).data('PLUGINNAME') inside your PLUGINNAME function if you need to attach internal data to your individual objects.

Not a jQuery guy myself, and mu is too short's answer seems to be the right one. But I'd imagine you could also do like it says on jQuery's own docs and use the .data() method to store the min/max values.
In other words, make your code look for existing data on the elements, so the first time you call $('#update_panel').plugin({min:1, max:20}), it won't find any existing data, so it'll place those min/max values in an object and "save" them using .data().
Then, when you later call $('#update_panel').plugin({min:2}) your code finds the existing data, and updates the values.
Just an idea

You can define a variable just like you've defined a plugin.
$.fn.PLUGINNAME.someVariable = 4;
or if you prefer, just declare an empty variable outside the plugin and then add to it from inside the plugin
var someVariable;
(function($) {
... the plugin code
})(jQuery);

Related

Referencing a parent object in callback functions with jQuery

I've a page that is generated dynamically, and that includes certain number (user-dynamically-defined) of advanced scatter plot charts. I intend to create a JavaScript object which defines the scatter plot itself, i.e. which takes some parameters, some data, and some container ID, and which will create the various elements needed to obtain the visualisation: canvas elements, toolbar, etc.. To do so, I started with the following (simplified) class:
(function () {
if (!this.namespace) { this.namespace = {};}
this._instances = { index: 0 };
this.namespace.ScatterPlot = function (containerId, file, options) {
_instances.index ++;
this.id = this.containerId+"-"+_instances.index ;
this.containerId = containerId ;
_instances [this.id] = this;
// ... Do stuffs with file and options ...
// Initialize elements once the DOM is ready
$(this.updateDOM);
}
namespace.ScatterPlot.prototype = {
updateDOM: function() {
$("<canvas>")
.click(clickCallback)
.appendTo("#"+this.containerId);
//(...)
},
clickCallback: function() {
alert("Some click: "+this.id);
}
}
})();
Each object can be created with:
var v1 = new namespace.ScatterPlot("container1", "foo", "foo");
var v2 = new namespace.ScatterPlot("container2", "foo", "foo");
There are two problems here: (1) in updateDOM, 'this' does not make reference to my initial ScatterPlot object, which means that this example will never work, and (2) similarly, the clickCallback will not be able reference the scatterplot with 'this' either.
I'm new to javascript, and I'm still struggeling to understand the logic of OO programming in javascript, so the question is: I'm I taking the wrong direction here ? After some digging, I could roughly achieve what I wanted by passing this to updateDOM:
$(this.updateDOM(this)); // This blows my eyes but does the trick, at least partially
updateDOM: function(that) {
$("<canvas>")
.click(that.clickCallback)
.appendTo("#"+that.containerId);
//(...)
},
clickCallback: function() {
// Not working either... Should pass 'that' to the function too
alert("Some click: "+this.id);
}
But I don't feel this patters to be very elegant... And the problem is not fixed either regarding the click callback.
Thoughts ?
Have a look at MDN's introduction to the this keyword.
The standard ways of dealing with that issue are using a that variable - not as an argument, but in a separate function:
var that = this;
$(function() {
that.updateDOM();
});
// or
$(this.getClickCallback());
...
namespace.ScatterPlot.prototype.getClickCallback = function() {
var that = this;
return function clickCallback(e) {
alert("Some click: "+that.id);
};
};
Alternatively, you can always use .bind() (or $.proxy for older browsers) which do quite what the second example does in a more generic way:
$(this.clickCallback.bind(this));

Adding a function to one jQuery/DOM element

I am authoring a plugin which instantiates a map. The map would then provide a function to move to another place on the earth.
The script makes the map just fine. However I can't "tack" the function on the element, to be used by another plugin in a callback.
Here's the approach I tried; in plugin:
(function($){
$.fn.mapDo(options){
map = new BlahMap(this.get(0));
this.moveTheMap = function(place){
map.moveItToThat(place);
}; // nope.
}
})(jQuery);
Then, in view:
$(map).mapDo();
$(otherElement).otherControl({
callback: function(place){
$(map).moveTheMap(place); // moveTheMap is not there on $(map)!
}
};
The Question
How do I add a function to the map jQuery or DOM element, if possible? If not, how can I provide that kind of functionality?
More importantly, am I going the right way here by separating the things that way? I'm a bit of a neophyte to Javascript, how are these tasks usually done while still keeping the components apart?
While that's the stab I took at it, more generally, I struggled with the concept of outputting things from a jQuery plugin while maintaining chainability. In this case, what I am trying to do is to output a callback from the plugin that will work on the called element later in the execution.
Plugins normally only add one method to the jQuery prototype, and the method calls to the plugin's instances are done with strings.
(function($) {
$.fn.mapDo = function(options) {
var args = [].slice.call(arguments, 1); //Get all the arguments starting from 2nd argument as an array
return this.each(function() {
var $this = $(this),
instance = $this.data("map-instance");
if (!instance) {
$this.data("map-instance", (instance = new BlahMap(this, options)));
}
if (typeof options == "string") {
instance[options].apply(instance, args);
}
});
};
})(jQuery);
$(elem).mapDo( "moveTheMap", place ); //This would also instantiate the plugin if it wasn't instantiated
Here's jsfiddle showing it in action:
http://jsfiddle.net/X8YA8/1/
You could store the map with .data method.
(function($){
$.fn.mapDo = funciont(options) {
this.data('map', new BlahMap(this.get(0)));
return this;
};
$.fn.moveTheMap = function(place) {
var map = this.data('map');
if (map) {
map.moveItToThat(place);
}
return this;
};
})(jQuery);

Troubles Writing A Proper jQuery Plugin

I'm in the process of rewriting a jQuery plugin to be used in an RSS reader I'm building during an internship. This plugin uses Google's Feed API to pull a JSON-formatted RSS feed and return it to the developer, allowing them fine-tuned control over how that feed is displayed on the webpage. I have been following the official jQuery Plugin Authoring page as a reference.
On the reference page, code examples say that you need to add your plugin to jQuery's prototype: $.fn. Here's what I've done:
(function($) {
"use strict";
$.fn.rssObj = function(newUrl) {
var RSSFeed = function(newUrl) {
/*
* An object to encapsulate a Google Feed API request.
*/
this.feedUrl = newUrl;
};
RSSFeed.prototype.load = function() {
var feed = new google.feeds.Feed(this.feedUrl);
feed.load(function(result) {
console.log(result);
});
};
return new RSSFeed(newUrl);
};
})(jQuery);
When I attempt to use this plugin by executing $.rssObj("http://rss.test.com"), my browser gives me this error:
$.rssObj() is not a function
What am I doing wrong?
You add to $.fn if you want your function to be available on jQuery instances (e.g., the objects you get back from $("your selector here") and such). If you want your function available from the $ object directly, you add it directly to it.
Here's an example showing each:
// Creating the plugin
(function($) {
// This will be on *instances*
$.fn.green = function() {
// `this` is the jQuery instance we were called on
return this.css("color", "green");
};
// This will be on the $/jQuery object itself
$.blue = function(selector) {
// You don't use `this` here (you could if you want,
// it will be === $/jQuery, but there's no reason to)
$(selector).css("color", "blue");
return this;
};
})(jQuery);
// Usage
jQuery(function($) {
// Make all divs green with a border
$("div").green().css("border", "1px solid green");
// Make all paragraphs blue
$.blue("p");
});
<div>I'm a div</div>
<p>I'm a paragraph</p>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
See where I've done exactly what the author was wanting to do here! I simply used this template I've been using for years:
(function($) {
if (!$.myExample) { // check your plugin namespace does not already exist
$.extend({ // this will allow you to add your plugin to the jQuery lib
myExample: function(elm, command, args) {
// keep in mind, right here you might want to do a class or data check to determine which direction this call is going
// for example, upon init the plugin on an element you may add the plugin name as a class,
// this way, when it's recalled, you can see it alrady has that class and might be calling a command,
// thus make an if statemnt to push the process through
return elm.each(function(index){
// do work to each element as its passed through
// be sure to use something like
// return elm.each(function(e) { dor work });
// as your final statement in order to maintain "chainability"
});
}
});
$.fn.extend({ // this gives the chainability functionality seen with $ funcs like: $("#eleID").css("color", "red") <--returns original element object
myExample: function(command) {
return $.myExample($(this), command, Array.prototype.slice.call(arguments, 1));
}
});
$.myExample.props = { // Here you can establish specific properties to your plugin, prehaps even make them "Over-writable"
key1: "value",
key2: "value"
};
$.myExample.methods = { // Here you can establish specific methods/functions for your plguin to carry out and maintain your namespace as well
key1: function(param) {
/* do work */
},
key2: function(param) {
/* do work */
}
};
// This next part is not seen in many plugins but useful depending on what you're creating
$.myExample.init = function(param) { // If you have an initialize method to apply, namespace it in here and calll on initializing your plugin
var key = "value",
key2 = {
subKey: "value"
};
/*
/ run any number of initializing functions here
/ I prefer to make my param a value that can be a
/ string with a possible object
/ the string for holding a base configuration
/ the object for any change in properties or base values for that config
*/
};
$.myExample.defaults = { // establish base properties here that can be over-written via .props, but their values should never truly change
key1: "value",
key2: {
prop1: {
subKey1: "value",
subKey2: "value"
},
prop2: {
subKey1: "value"
}
},
key3: function(param) {
}
};
}
})(jQuery);

How to manually execute a function inside a jQuery plugin?

I have a plugin that looks like this:
(function($) {
$.fn.plugin_name = function(options) {
var $this = $(this);
var defaults = {
// some defaults
};
options = $.extend({}, defaults, options);
var work = {
action_1: function() {
// do something
},
action_2: function(output) {
alert('hello world');
}
}
that.submit(function(e) {
e.preventDefault();
work.action_1();
});
return $this;
}
})(jQuery);
It's being used like any traditional jquery plugin, by being attached to a page element like so:
$('#search-form').plugin_name({
// overide options
});
My question is, how can I execute the work.action_2() function that's deeply nested inside the plugin? I would like to call it manually from the javascript console in firebug.
My question is, how can I execute the work.action_2() function that's deeply nested inside the plugin?
You can't, it's outside of your scope!
you can't reach private function variables, just like you can't reach my functions...
Create it in a separate utility function if its needed independently, better if its in your own global object.
So you can call it like so: $.myGlobalObj.action_2()

Creating methods on the fly

Hi I'm trying to author a jQuery plugin and I need to have methods accessible to elements after they are initialized as that kind of object, e.g.:
$('.list').list({some options}); //This initializes .list as a list
//now I want it to have certain methods like:
$('.list').find('List item'); //does some logic that I need
I tried with
$.fn.list = function (options) {
return this.each(function() {
// some code here
this.find = function(test) {
//function logic
}
}
}
and several other different attempts, I just can't figure out how to do it.
EDIT:
I'll try to explain this better.
I'm trying to turn a table into a list, basically like a list on a computer with column headers and sortable items and everything inbetween. You initiate the table with a command like
$(this).list({
data: [{id: 1, name:'My First List Item', date:'2010/06/26'}, {id:2, name:'Second', date:'2010/05/20'}]
});
.list will make the <tbody> sortable and do a few other initial tasks, then add the following methods to the element:
.findItem(condition) will allow you to find a certain item by a condition (like findItem('name == "Second"')
.list(condition) will list all items that match a given condition
.sort(key) will sort all items by a given key
etc.
What's the best way to go about doing this?
If you want these methods to be available on any jQuery object, you will have to add each one of them to jQuery's prototype. The reason is every time you call $(".list") a fresh new object is created, and any methods you attached to a previous such object will get lost.
Assign each method to jQuery's prototype as:
jQuery.fn.extend({
list: function() { .. },
findItem: function() { .. },
sort: function() { .. }
});
The list method here is special as it can be invoked on two occasions. First, when initializing the list, and second when finding particular items by a condition. You would have to differentiate between these two cases somehow - either by argument type, or some other parameter.
You can also use the data API to throw an exception if these methods are called for an object that has not been initialized with the list plugin. When ('xyz').list({ .. }) is first called, store some state variable in the data cache for that object. When any of the other methods - "list", "findItem", or "sort" are later invoked, check if the object contains that state variable in its data cache.
A better approach would be to namespace your plugin so that list() will return the extended object. The three extended methods can be called on its return value. The interface would be like:
$('selector').list({ ... });
$('selector').list().findOne(..);
$('selector').list().findAll(..);
$('selector').list().sort();
Or save a reference to the returned object the first time, and call methods on it directly.
var myList = $('selector').list({ ... });
myList.findOne(..);
myList.findAll(..);
myList.sort();
I found this solution here:
http://www.virgentech.com/blog/2009/10/building-object-oriented-jquery-plugin.html
This seems to do exactly what I need.
(function($) {
var TaskList = function(element, options)
{
var $elem = $(element);
var options = $.extend({
tasks: [],
folders: []
}, options || {});
this.changed = false;
this.selected = {};
$elem.sortable({
revert: true,
opacity: 0.5
});
this.findTask = function(test, look) {
var results = [];
for (var i = 0,l = options.tasks.length; i < l; i++)
{
var t = options['tasks'][i];
if (eval(test))
{
results.push(options.tasks[i]);
}
}
return results;
}
var debug = function(msg) {
if (window.console) {
console.log(msg);
}
}
}
$.fn.taskList = function(options)
{
return this.each(function() {
var element = $(this);
if (element.data('taskList')) { return; }
var taskList = new TaskList(this, options);
element.data('taskList', taskList);
});
}
})(jQuery);
Then I have
$('.task-list-table').taskList({
tasks: eval('(<?php echo mysql_real_escape_string(json_encode($tasks)); ?>)'),
folders: eval('(<?php echo mysql_real_escape_string(json_encode($folders)); ?>)')
});
var taskList = $('.task-list-table').data('taskList');
and I can use taskList.findTask(condition);
And since the constructor has $elem I can also edit the jQuery instance for methods like list(condition) etc. This works perfectly.
this.each isn't needed. This should do:
$.fn.list = function (options) {
this.find = function(test) {
//function logic
};
return this;
};
Note that you'd be overwriting jQuery's native find method, and doing so isn't recommended.
Also, for what it's worth, I don't think this is a good idea. jQuery instances are assumed to only have methods inherited from jQuery's prototype object, and as such I feel what you want to do would not be consistent with the generally accepted jQuery-plugin behaviour -- i.e. return the this object (the jQuery instance) unchanged.

Categories

Resources