jQuery Plugin Stored Values - javascript

I'm working on a jquery plug in and I'm running into a problem saving properties for later use. In the example below, the console output is 18, 50, 50 when I'm looking for 18, 50, 18. I understand why this is happening, but I can't figure out a good way to save properties for use in multiple different methods. I have a feeling I'm missing something very obvious, but I'm just not seeing it.
<html>
<body>
<h1>Hello</h1>
<h2>World</h2>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>
<script type="text/javascript">
(function ($) {
var commonOperations, methods, properties;
commonOperations = function () {
console.log(properties.height);
};
methods = {
init : function (overrides) {
var defaults;
defaults = { height: 18 };
properties = $.extend(defaults, overrides);
commonOperations();
},
foo : function () {
commonOperations();
}
};
$.fn.myPlugin = function (method) {
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 for jQuery.myPlugin');
}
};
}(jQuery));
$(document).ready(function () {
$("h1").myPlugin();
$("h2").myPlugin({ height: 50 });
$("h1").myPlugin("foo");
});
</script>
</body>
</html>

It depends on the nature of your plugin, but it's likely that using .data() to store the properties on a per-element basis would make sense.
init: function(overrides) {
return this.each(function() {
var defaults = { whatever: "foo" };
$(this).data('properties', $.extend(defaults, overrides));
});
}
Then the other methods always pull the "properties" object from the element:
foo : function () {
return this.each(function() {
commonOperations.call(this, $(this).data('properties'));
});
}

Related

Invoke function from jquery plugin boiler plate code

I have found nice jQuery plugin and it uses boiler plate template. Everything works fine, but I can't invoke internal function to get selected items.
The construction of this plugin is below:
(function ($, window, document) {
'use strict';
// constructor
var SearchableOptionList = function ($element, options) {
this.$originalElement = $element;
this.options = options;
this.metadata = this.$originalElement.data('sol-options');
};
// plugin prototype
SearchableOptionList.prototype = {
DATA_KEY: 'sol-element',
// default option values
defaults: {
...
},
// initialize the plugin
init: function () {
this.config = $.extend(true, {}, this.defaults, this.options, this.metadata);
...
return this;
},
//some functions
...
selectAll: function () {
...
},
deselectAll: function () {
...
},
getSelection: function () {
return this.$selection.find('input:checked');
}
};
// jquery plugin boiler plate code
SearchableOptionList.defaults = SearchableOptionList.prototype.defaults;
window.SearchableOptionList = SearchableOptionList;
$.fn.searchableOptionList = function (options) {
var result = [];
this.each(function () {
var $this = $(this),
$alreadyInitializedSol = $this.data(SearchableOptionList.prototype.DATA_KEY);
if ($alreadyInitializedSol) {
result.push($alreadyInitializedSol);
} else {
var newSol = new SearchableOptionList($this, options);
result.push(newSol);
setTimeout(function() {
newSol.init();
}, 0);
}
});
if (result.length === 1) {
return result[0];
}
return result;
};
}(jQuery, window, document));
Full code you can find on GitHub.
I try to invoke getSelection function like below:
// initialize sol
var s = $('#my-select').searchableOptionList({
maxHeight: '150px',
showSelectAll: true
});
s.selectAll();
I get an error:
TypeError: this.config is undefined
Is it possible to invoke functions using this template of boiler plate?
You can play around on jsfiddle
I believe line 1031 is the culprit
setTimeout(function() {
newSol.init();
}, 0);
Since the init is deferred the code is not ready when you call it right away. The esiest fix is to defer your call too, but there is no gurantee that it will be initialized.
setTimeout(function(){s.selectAll()},1000);
The better solution is to use the plugin's events to hook into when it has been initialized.
$('#my-select').searchableOptionList({
maxHeight: '150px',
events: {
onInitialized: function() {
this.selectAll();
}
}
});
fiddle

JavaScript functions is throwing Uncaught TypeError

I have a web page. In my web page I'm referencing some JavaScript I've written in a file called "spacer.js". spacer.js is more complicated, but the general setup is like this:
function spacer() {
// do stuff
console.log(spacer.options);
}
spacer.initialize = function(options) {
spacer.options = options;
};
Then, in my web page, I have:
<script type="text/javascript" src="./spacer.js"></script>
<script type="text/javascript">
spacer.initialize({ id:1 });
window.onresize = spacer();
</script>
When I load my web page, I get an error that says:
Uncaught TypeError: spacer.initialize is not a function.
I don't understand. What am I doing wrong.
function spacer() {
// do stuff
console.log(spacer.options);
}
spacer.initialize = function(options) { // needed an assignment operator and function keyword
spacer.options = options;
}
To create a method you need to use the function keyword.
To the browser, this
spacer.initialize(options) {
spacer.options = options;
}
is interpreted like this
spacer.initialize(options); // method evocation
{spacer.options = options;}; // anonymous object
Change the way it is defined
//from
spacer.initialize(options) {
spacer.options = options;
}
//to
spacer.initialize = function(options) {
spacer.options = options;
}
In your JS remove the () from window.resize call
spacer.initialize({ id:1 });
window.onresize = spacer;
As an aside it looks like you're trying to do one of two things and sitting uncomfortably in the middle. I'd personally go for 1) in this instance, but probably best to stick with one or the other.
1) Creating an object with methods
var spacer = {};
spacer.initialize = function (options) {
this.options = options;
}
spacer.getOptions = function () {
return this.options;
}
spacer.initialize({ name: 'spacer' });
spacer.getOptions(); // { name: spacer });
DEMO
2) Using a constructor function to build an space object instance:
function Spacer() {}
Spacer.prototype.initialize = function (options) {
this.options = options;
return this;
}
Spacer.prototype.getOptions = function () {
return this.options;
}
var spacer = new Spacer().initialize({ name: 'spacer' });
spacer.getOptions(); // { name: 'spacer' }
DEMO

jQuery plugin - update settings after initialization

I have a jQuery plugin, and I want to be able to change options on the fly, like this example: $('.element').pwstabs('options','effect',scale) or something simular to it. I tried adding update: function, tried adding Plugin.prototype.update, but still cant figure out how to do that :)
Here's the structure of the plugin:
;(function ($, window, document, undefined) {
var pluginName = "pwstabs",
defaults = {
effect: 'scaleout',
defaultTab: 1,
containerWidth: '100%',
tabsPosition: 'horizontal',
horizontalPosition: 'top',
verticalPosition: 'left',
responsive: false,
theme: '',
rtl: false,
controlls: false,
next: '',
prev: '',
first: '',
last: '',
auto: false,
play: '',
pause: ''
};
function Plugin(element, options) {
this.element = $(element);
this.$elem = $(this.element);
this.settings = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype = {
init: function(){
// Here's the code for the plugin
}
};
$.fn[pluginName] = function ( options ) {
return this.each(function () {
new Plugin( this, options );
});
};
})(jQuery, window, document);
So now I use the plugin like:
$('.element').pwstabs({
effect: 'scalein',
defaultTab: 2
});
And when I click a button, i want to change effect to lets say scaleout. With code like:
$('.button').click(function(){
$('.element').pwstabs('options','effect','scalein');
});
So how do I implement this in the plugin?
Currently the only supported invocation pattern in that plugin is to send in an object literal containing the settings to overwrite the defaults. E.g.:
$('.element').pwstabs({
effect: 'scalein',
defaultTab: 2
});
That invocation pattern is defined in the following method:
$.fn[pluginName] = function ( options ) {
return this.each(function () {
new Plugin( this, options );
});
};
As you see, a dictionary of options is sent as the only parameter to the constructor function Plugin() to build the plugin and initialize it.
To support the invocation pattern you need, you would have to modify this method to support both invocation patterns (initialization with an object literal, but also invoking any method with more params, like your options setting method).
Here is an improved function that will handle both invocation patterns. In addition it will also store the instance of a plugin on an element, so you can access the existing settings etc. on subsequent invocations (e.g. settings changes) on the same element.
$.fn[pluginName] = function (options) {
// get the arguments
var args = $.makeArray(arguments),
after = args.slice(1);
return this.each(function () {
// check if there is an existing instance related to element
var instance = $.data(this, pluginName);
if (instance) {
if (instance[options]) {
instance[options].apply(instance, after);
} else {
$.error('Method ' + options + ' does not exist on Plugin');
}
} else {
// create the plugin
var plugin = new Plugin(this, options);
// Store the plugin instance on the element
$.data(this, pluginName, plugin);
return plugin;
}
});
}
This would allow you to invoke the plugin as requested:
$('.element').pwstabs('options','effect','slidedown');
However, this implies you have an 'options' method in the Plugin prototype, so make sure to add one:
Plugin.prototype = {
options: function (option, val) {
this.settings[option] = val;
},
// Constructing Tabs Plugin
init: function () {
// omitted code for brevity
}
}
As you see the options settings just sets the new option on the existing instance. Very simple and efficient. The new setting will be picked up by the click method handler and voila!
Here is a jsFiddle with example code in case you have trouble implementing what i was describing so far:
http://jsfiddle.net/7whs3u1n/6/
Update: I have much improved my answer to get rid of unneeded stuff, include more details and a full implementation that works (check the fiddle above) ;) i hope that this answers your question!
Adding statefulness to your plugin wasn't hard, but when you have spare time also check the alternative mechanism for writing stateful jQuery stateful plugins called jQuery widget factory:
http://learn.jquery.com/plugins/stateful-plugins-with-widget-factory/
In the future you can consider rewriting your plugin to use the widget factory. It would certainly make your code simpler ;)
Try this pattern
(function ($) {
var defaults = {
"text": "abcdefg",
}
, options = $.extend({}, defaults, options);
$.fn.plugin = function (options) {
var options = (function (opts, def) {
var _opts = {};
if (typeof opts[0] !== "object") {
_opts[opts[0]] = opts[1];
};
return opts.length === 0
? def
: typeof opts[0] === "object"
? opts[0] : _opts
}([].slice.call(arguments), defaults));
return $(this).text(options.text)
}
}(jQuery));
$(".results:eq(0)").plugin(); // return `defaults`
$(".results:eq(1)").plugin({"text":"gfedcba"}); // return `options`
$(".results:eq(2)").plugin("text", 123); // return `arguments` as `options`
(function ($) {
var defaults = {
"text": "abcdefg",
}
, options = $.extend({}, defaults, options);
$.fn.plugin = function (options) {
var options = (function (opts, def) {
var _opts = {};
if (typeof opts[0] !== "object") {
_opts[opts[0]] = opts[1];
};
return opts.length === 0
? def
: typeof opts[0] === "object"
? opts[0] : _opts
}([].slice.call(arguments), defaults));
return $(this).text(options.text)
}
}(jQuery));
$(".results:eq(0)").plugin(); // return `defaults`
$(".results:eq(1)").plugin({"text":"gfedcba"}); // return `options`
$(".results:eq(2)").plugin("text", 123); // return `arguments` as `options`
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="results"></div><br />
<div class="results"></div><br />
<div class="results"></div>

How to call elements inside user defined methods of an jQuery plugin

I have a jQuery Plugin which accept multiple elements and some methods to be called like:
(function($){
methods = {
init : function( options, callbacks) {
$.fn.myPlugin.settings = $.extend({
'userDefinedMethod': function() {}
}, options);
return this.each(function(){
$.fn.myPlugin.settings.userDefinedMethod();
}
}
}
$.fn.myPlugin = function(method) {
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 exists on jQuery.myPlugin' );
}
}
})(jQuery);
An simple example which will make you understand what I want to achieve:
$(document).ready(function(){
$('#myElement1, #myElement2, #myElement3').myPlugin({
userDefinedMethod: function() {
// I want here to use the elements in selector
$(this).css('color', 'black');
}
});
});
I know that $(this) in the example above will represent the jQuery Plugin Object but I want somehow to use each element in the provided selector.
$(document).ready(function () {
$('#myElement1, #myElement2, #myElement3').myPlugin({
userDefinedMethod: function () {
// I want here to use the elements in selector
$(this).css('color', 'red');
}
});
});
(function ($) {
methods = {
init: function (options, callbacks) {
//don't set the settings to shared object
this.settings = $.extend({
userDefinedMethod: $.noop
}, options);
return this.each($.proxy(function (idx, el) {
//use Function.call() to set a custom execution context
this.settings.userDefinedMethod.call(el);
}, this))
}
}
$.fn.myPlugin = function (method) {
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 exists on jQuery.myPlugin');
}
}
})(jQuery);
Demo: Fiddle
In methods.init function this will be the jQuery object obtained by quering the selector. So, if you want to send this to userDefinedMethod just use apply or call when you call that function:
...
var methods = {
init : function( options, callbacks) {
$.fn.myPlugin.settings = $.extend({
'userDefinedMethod': function() {}
}, options);
$.fn.myPlugin.settings.userDefinedMethod.call(this);
// or if you want to send the arguments
// $.fn.myPlugin.settings.userDefinedMethod.apply(this, arguments);
return this;
}
}
...
Also, don't forget that you didn't use var for declaring methods. methods will become a magic global variable...
I also corrected the missing ) that was generating a syntax error.
JSFIDDLE

Persisting values in the plugin object for a jQuery plugin

I'm trying to write a simple jQuery plugin that follows similar structure to the one below. The problem I'm having is that when I initialize the plugin the value plugin.myValue is set to 1 however when I try to access that value in the talk function it's undefined. Can anyone help me refine this plugin structure so when I set values on the plugin object they can be accessed in different methods.
Please keep in mind that the plugin below is not my actual use case it is just a simple example to illustrate the problem I'm having.
My actual use case would be very long because it is a jQuery carousel that I'm writing. If it will help I can provide that code however it's much longer and the below example follows the same flow. I would initialize this plugin with the following code:
$('#Div1').talk({ message: "test message" });
$('#Div1').talk('talk');
(function ($) {
$.fn.talk = function(method) {
var settings = {};
var $el = $(this);
var plugin = this;
var methods = {
init: function(options){
settings = $.extend(true, settings, this.talk.defaults, options);
return this.each(function() {
plugin.myValue = 1;
});
},
talk: function(){
helpers.talk(settings.message);
}
}
var helpers = {
talk: function(message){
if (plugin.myValue == 1)
{
alert(message);
}
}
}
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 in talk plugin!');
}
}
$.fn.talk.defaults = {
message: "default message"
}
})(jQuery);
When you define a plugin, this refers already to the jquery object (not the dom element), so I think your plugin var should go en each element, like this:
(function ($) {
$.fn.talk = function(method) {
var settings = {};
var $el = this; //Not need of $() here
var methods = {
init: function(options){
settings = $.extend(true, settings, this.talk.defaults, options);
return this.each(function(index, item) {
item.myValue = 1; //check this
});
},
talk: function(){
helpers.talk.call(this, settings.message); //talk needs scope
}
}
var helpers = {
talk: function(message){
var $elems = this;
if ($elems[0] && $elems[0].myValue == 1) //Something like this
{
alert(message);
}
}
}
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 in talk plugin!');
}
}
$.fn.talk.defaults = {
message: "default message"
}
})(jQuery);
I hope you get my idea. Don't forget that your selector might match to more than one element. You were stroring the data to the jquery object, and don't forget that it's a different one each time you call $("#yourDiv"), so your data was lost.
Note: It'd be cleaner to do $(item).data('myValue', 1); instead of item.myValue = 1; (and its proper retrieval), but it's a matter of choice
EDIT Option 2. This may look more similar to your code, but will work only when your selector only matched a single element
(function ($) {
$.fn.talk = function(method) {
var settings = {};
var $el = this; //Not need of $() here
var methods = {
init: function(options){
settings = $.extend(true, settings, this.talk.defaults, options);
$el.data("myValue", 1);
},
talk: function(){
helpers.talk(settings.message);
}
}
var helpers = {
talk: function(message){
if ($el.data("myValue") == 1)
{
alert(message);
}
}
}
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 in talk plugin!');
}
}
$.fn.talk.defaults = {
message: "default message"
}
})(jQuery);
Hope this helps. Cheers

Categories