I have a question regarding the local variables for my jQuery plugin. I am pretty sure if I declare them outside the main jQuery function register, then every time the plugin is called it will redefine the variables.
Example:
(function($){
jQuery.fn.test = function(options){
if ( options ) {
$.extend( settings, options );
}
this.each(function(i,item){
// do whatever i am going to do
});
};
var settings = {
value1: "hello",
value2: "word"
};
})(jQuery);
Say that $(object).test({value1:'newterm'}); is called multiple times.. Am I correct in thinking that every time that method is called, that it will override the settings with the most recently declared settings?
If i want multiple instances of my plugin, do I need to declare everything within the scope of the main jQuery.fn.test = function(){//here} method?
Yes, that is correct, because $.extend will modify settings which is in the closure scope exposed when the jQuery initialization function sets up .test on the global object jQuery. That closure scope is the same everytime you execute .test; therefore, all objects will retain changes.
It depends on the order you pass objects to $.extend. The first (target) object passed will be modified, in your case the settings object. If you want to keep the defaults:
$.extend(options, settings);
Or to get a brand new object:
var newoptions = $.extend({}, options, settings);
Related
I encountered with snippet of code in the jQuery menu library. I'm new to jQuery so can somebody explain what $(this) in cssmenu variable means (its a kind of context?).
$.fn.menumaker = function(options) {
var cssmenu = $(this),
settings = $.extend({
title: "Menu",
format: "dropdown",
sticky: false
}, options);
}
That code defines a jQuery plugin, by adding a function to the jQuery's object prototype. In the context of a plugin's initializer function, $(this) is totally unecessary: this in the context of a plugin function already refers to a jQuery object.
Not only that, but the way the plugin is written doesn't follow jQuery principles where a plugin might be called on a jQuery object wrapping over multiple DOM elements.
The plugin's definition also doesn't take in account the fact that jQuery.noConflict() would be used.
A form that would make more sense is:
//Using an IIFE and passing jQuery allow us to use $ safely
(function($) {
$.fn.menumaker = function(options) {
//Apply defaults
options = $.extend({
title: "Menu",
format: "dropdown",
sticky: false
}, options);
//Initialize the plugin for every DOM element in the jQuery oject
//and return the jQuery object to avoid breaking chaining
return this.each(function() {
//In here, this points to a DOM element, that's why we wrap it
//in a DOM object
initPlugin($(this), options);
});
};
function initPlugin($el, options) {
//The plugin would be applied here to $el
}
})(jQuery);
Let's look at the whole thing step by step:
$.fn.menumaker = function(options) {
}
This defines a jQuery plugin called menumaker, taking an options object. It is run as $('mySelector').menumaker({}).
var cssmenu = $(this), settings = $.extend({}, options);
Now, this is going to be the jQuery object $('mySelector') from the above example. Wrapping it in another jQuery constructor ($(this)) is rather redundant.
But in any case, it's saved as a variable named cssmenu. A settings variable is also created, applying the passed in objects to the default settings.
So basically, yes, it's the context. It's also pointless.
Basically $.fn.menumaker means its allows you to extend jquery with your own function, which in turn will be available to all jquery objects.
So the this, is the Jquery equivalent of the DOM object with which you have used plugin function.
I understand I can extend jQuery in two forms:
1)
jQuery.fn.extend({
variation1: function(){
...
}
});
I use this like $(selector).variation1();
2)
and custom function
jQuery.variation2 = {
something: function(){
var execute = function(){
...
}
}
}
I use this like $.variation2.something()
My questions are:
In the first case, I was unable to call functions without a selector; this code caused errors $.variation1(); Is this correct? meaning, is there a way to call these functions without an element selector?
In the second case, how could I initialize variation2 with options? The reason I ask this is because in MooTools, when creating a class, we can initialize the class like:
var jsClass = new Class({
Implements: [Options],
options: {
},
initialize: function(options){
this.setOptions(options);
}
});
we call it, and initialize with custom options like
new jsClass({option1: this, option2: that})
So in the MooTools example the class instance executes options by default. Going back to the question, is there a way to do similar in jQuery?
Thanks in advance
The two variations are distinct as you have noticed. jQuery.fn provides the set of functions that are applied to a jQuery QuerySet object (the result of applying a jQuery selector). jQuery.* could simply be thought of as global static objects, including (sometimes) functions that are accessible to anyone who has access to the jQuery object.
To initialize variation 2, I would do something like:
jQuery.variation2 = (function() {
var a = ...;
var b = ...; // other initialization
return {
something: function() {
...
}, ...
};
})();
You could use closures in similar ways to provide initialized code.
I've sort if fell into this organization of javascript and was wondering if I'm missing the point somewhere here, or if there's a more elegant way of doing this.
Basically I'm wrapping everything in a function (object) and then setting up methods on that object, then instantiating an instance of the wrapper object and passing in any options and dependencies.
I have a hunch there's a way to automatically run .init() and a few other tweaks that could be made. Am I doing it right?
function AppModuleCore(){
var AppModuleCore = this; //keep internals sane
// Various global vars, objects
AppModuleCore.defaultOptions = {};
AppModuleCore.init = function(opts) {
// todo: that thing where you extend an options object a la juery
AppModuleCore.bindEvents();
};
AppModuleCore.bindEvents = function() {
// bind events here, send to functions within AppModuleCore.<FUNCTIONNAME>();
// Example:
$("a#clicker").unbind("click");
$("a#clicker").click(function(event){
AppModuleCore.handleClickerClick(event);
});
};
AppModuleCore.handleClickerClick = function(event){
alert("clicker was clicked");
};
}
// --------------------------------------------------------------------
// instantiate AppModuleCore object and initialize with opts,
// dependency injection
// --------------------------------------------------------------------
$(document).ready(function(){
AppModuleCore = new AppModuleCore;
var options = {};
AppModuleCore.init(options);
});
OK, some points
Having your code wrapped in a constructor only really makes sense if
You're going to instantiate more than one
You have "public" methods on the object that you are going to call
Your code doesn't exhibit these characteristics. I say this because your jQuery selectors a#clicker are hard coded so I'm assuming that you wouldn't want to bind the same events to them more than once?
You'd be better off using a function (perhaps your init) or an object literal to limit your scope..
function init( options ) {
var defaultsOptions = {};
var privateVar = 'only in this scope';
//extend your default options with options here
//using jquery
options = $.extend( defaultOptions, options );
// this function is completely private to this scope
function privatefunction() {
//do stuff
}
function handleClickerClick( event ){
alert("clicker was clicked");
}
// you don't need to wrap your handler in an anonymous function unless
// you're doing some work to the event before forwarding:- just give a
// reference to your handler
// the handler has access to other members of this scope, we're in a closure
$(options.selector).click( handleClickerClick );
//etc
}
init( {selector: 'a#clicker'} );
On a stylistic note: when you alias this with the same name as the constructor and then add methods to the alias, it looks at first glance like you are adding static methods to the constructor. This may be confusing to someone who looks at your code later and doesn't notice the alias.
function C() {
// a static method i.e a property of the constructor, C not objects created with it
// it is a bit wierd that it is defined in the constructor but not unheard of
C.staticMethod = function(){};
//quite plainly a method of objects of this type, easy to understand
this.method = function(){};
}
jQuery plugins use a pattern like this to hide private functions of a plugin:
(function ($) {
var a_private_function = function (opts) {
opts.onStart();
}
$.fn.name_of_plugin = function (options) {
a_private_function(opts);
}
})(jQuery);
jQuery then makes those fn functions available like this:
some_callback = function() {};
jQuery('selector').name_of_plugin( { onStart: some_callback } );
Now I'd like to override a_private_function. Is there any way I can access it without patching the actual plugin code?
I thought maybe I could access the execution context of the private function by using caller but that did not work:
some_callback = function() {
console.log(some_callback.caller.a_private_function); // -> undefined
};
jQuery('selector').name_of_plugin( { onStart: some_callback } );
As I learned in this answer, the only way to access the private members of a jQuery plugin are to modify the plugin source itself.
What you have there is a classical example of a closured function.
a_private_function is a function which is only visible within the scope from the "outer" anonymous function. Because of closure, the anonymous function assigned to name_of_plugin has access to the outer context and therefore a_private_function.
This is a good thing since you can protect and hide some of functions and variables.
Short story, there is absolutly zero chance to access a closured variable from the outside.
When using the JQUERY UI widget factory, the functions (which are prefixed with _) are not private, but instead (simulated) protected (prototype) functions.
This means you can access them as long as you extend the existing prototype. For example:
$.extend( $.ui.accordion.prototype, {
open: function( index ) {
//now you can access any protected function
var toOpen = self._findActive( index );
toOpen.next().show();
},
_completed: function ( cancel ) {
//You can even overwrite an existing function
}
});
The function you have demonstrated in your first example is, however, private - and therefore as the other answers suggest you cannot access these from the outside.
However, if you want to access protected variables inside a JQuery UI widget then this is possible (as above).
Thought this might be useful.
I created a jQuery plugin that was working great until I started testing it with more than one object on a page. The problem is that the options object passed to each new plugin object is not always the same one associated with that specific object when I access it from inside a function in the plugin. I have a feeling that I am missing something very simple, so the code might make things more clear.
Method used to create the plugin
$.fn.myPlugin = function(options) {
return this.each(function() {
var opts = $.extend({}, $.myPlugin.defaults, options);
new $.myPlugin($(this), opts);
});
}
Function that accesses the options object
$.myPlugin = function($textbox, options) {
function doSomething($textbox) {
alert(options.someProperty);
}
}
No matter what options.someProperty was when I created the plugin. The call to doSomething inside the plugin will always return the someProperty of the first options object in the first plugin object I created for example:
$textbox.focus(function() { doSomething($textbox); } );
This will always return the same someProperty even if different objects with different options objects are focused.
I hope I have made this clear enough. Let me know if you need anymore details.
You're giving the plugin a reference to the same options object for all of the elements.
Try the following:
return this.each(function() {
new $.myPlugin($(this), $.extend({ }, options));
});
This will copy the members of the options object to a new object each time. If your options object has other objects nested within it, you might need a deep copy, like this: $.extend(true, { }, options)
EDIT: You need to extend the options object inside the each method. Otherwise, you'll still have the same object for all of the elements.
Try changing
function doSomething($textbox) {
to
var doSomething = function($textbox) {
to ensure that you are creating separate doSomething methods that use separate closures. (the first version will create a single function the first time it's called, and reuse that function and its closure)