How to prevent more then one object's init call? - javascript

Our plugin function is something like: var pluginName = function(selector, settings){}. So user call it like var myPlugin = new pluginName('#id', {settings}). But how to detect that user called it again, for example, with new options: myPlugin = new pluginName('#id', {settings_2})?
We need to detect this to prevent double events attaching: something like destroy created object/remove events and call init again.
So problem is we dont have access to myPlugin variable to check it.

Just put the plugin around it's own scope:
var pluginName = (function(){
var plugin_created = false;
var plugin = function(selector, settings){
if(plugin_created) return false;
plugin_created = true;
//rest of the plugin class
}
return plugin;
})();
So when you create it for the first time it will set it's local scope variable to true and then any other creations will fail.

Related

How to use Callback functions in Class with JavaScript/jQuery

I have a class I am using for creating CRUD Objects for my site.
It stores the form and table paths for adding, listing, editing and deleting the data, as well as reloading your view with ajax after each edit.
Here is my class definitions:
class CRUDObj{
constructor(containerSel, links) {
this.links = links;
this.containerSel = containerSel;
this.formCallBack = function(){};
}
setActive(obj_id){
$.post(this.links.editURL+'?'+obj_id, {status:"active"}, this.reload);
}
reload(returnData){
this.formCallBack(returnData);
this.formCallBack = function(){};
if($(this.containerSel).length > 0){
ajaxLoad(this.links.listURL, $(this.containerSel));
}
}
}
A basic instance of initializing it:
var contactObj = new CRUDObj('#contacts', {
editURL: '/contact.edit.php',
listURL: '/contacts.list.php',
});
contactObj.formCallBack = function(){
console.log('Custom Reload Callback');
};
The problem appeared when I tried to add the callback, so that I could run a custom function during the refresh.
Running contactObj.setActive(); works properly, and my refresh function is called after the form submits, but when it hits my callback I get:
Uncaught TypeError: this.formCallBack is not a function
Calling it manually contactObj.refresh(); works smoothly.
How can I pass this callback function through better?
The problem is that you're passing method as function, so you loose this context. this will be window object or undefined (if using strict mode):
You need this:
var self = this;
lightboxForm(this.links.editURL+'?'+obj_id, function(x) { self.reload(x) });
or using ES6
lightboxForm(this.links.editURL+'?'+obj_id, x => this.reload(x));
or using bind to return function with given context:
lightboxForm(this.links.editURL+'?'+obj_id, this.reload.bind(this));

Transforming a jQuery plugin to support multiple instances

Here is a fictional version of my jQuery plugin, but the structure is exactly the same:
(function ($)
{
var initialized = false;
var element;
var counter = 0;
$.fn.myPlugin= function(action)
{
if (action === "increase")
{
increase(arguments[1]);
}
else if (!initialized)
{
settings = $.extend({
...
}, action);
initialized = true;
element = $(this);
return this;
}
else
{
console.error("Unknown function call.");
return;
}
};
var increase = function(amount)
{
counter += amount;
element.text(counter);
};
}(jQuery));
With this code I am able to initialize my plugin like this:
$("#element").myPlugin(options);
And I can call the method increase like this:
$("#element").myPlugin("increase", 5);
However, I am not able to initialize my plugin on multiple elements on one page, because of the variables initilized, element and counter.
How do I modify this code in such a way that I can use it multiple times on one page without changing the way you can initialize and call methods?
I do this exact same thing myself, and it's very simple once you know how.
Take this example of a simple plugin...
$.fn.myPlugin = function() {
// plugin global vars go here
// do plugin stuff here
}
To modify it to work on multiple instances, you just have to parse this when you call it...
$.fn.myPlugin = function() {
$(this).each(function() {
// plugin global vars go here
// do plugin stuff here
});
}
That way it will work when you assign the plugin to either a single instance, or a collection of elements.
Then, to call methods on individual elements, you just need to specify the correct one...
$("#element1").doMethod(1, 2, 3);
$("#element2").doMethod(4, 5, 6);

jQuery Plugin/Widget Only Modifying Last Instantiated Variables

I'm fairly new to jQuery plugins so I think this is a relatively simple issue.
I've created a plugin to control an HTML calendar. I want to be able to have multiple calendars on the same page. Each calendar keeps track of its own data via the plugin.
When I have a single calendar on the page, it works great. However, as soon as more are added there are problems. The settings object (which stores all the calendar info) only gets updated for the last instantiated calendar. So when I click something to modify the first calendar, the settings object for the first calendar is not changed at all; only the second calendar's settings object is updated.
I think it's either an issue in how my plugin runs against the incoming jQuery objects or the way I'm using the data() method.
In my code below, when either of the 'change settings' listeners are fired, the settings.listView value is supposed to be changed. Then, when the showSettings() method is called it's supposed to use the updated value.
Here's a stripped down version of my plugin:
(function($){
var methods = {
_init: function($el, options, config){
var settings = $.extend({}, $.fn.pluginName.defaults, options, config);
$el.data("settings", settings);
settings = $el.data("settings");
this.$el = $el;
var instance = this;
// change settings
$el.on("click", ".popup.settings li.calendar", function(){
// do stuff...
//settings.listView = false; // doesn't work
instance.$el.data("settings").listView = false; // doesn't work either
});
$el.on("click", ".popup.settings li.list", function(){
// do stuff...
//settings.listView = true; // doesn't work
instance.$el.data("settings").listView = true; // doesn't work either
});
},
showSettings: function (el){
var settings = this.$el.data("settings"); // doesn't
var props = {
classes: "settings",
isList: settings.listView
};
// do more stuff...
},
}
$.fn.pluginName = function(options){
return this.each(function() {
var config = {
listView: false
}
methods._init($(this), options, config);
});
};
$.fn.pluginName.defaults = {
fullMobileQuery: "48em",
verticalScrollOffset: 30
};
}(jQuery));

jQuery plugin instance variables

I'm beginning with jQuery plugins, apologies for the newbie question. My objective is to have a single plugin instantiated twice, where each instance has its own variables values. However, they seem to share the namespace.
For example, given the following plugin:
(function ( $ ) {
var x = false;
$.fn.test = function() {
alert(x);
if ( !x )
x = true;
return this;
};
}( jQuery ));
that is invoked from the following divs:
$( "div1" ).test();
$( "div2" ).test();
The alert displays first false, then true, when the objective is to have to sets of variables where the alert would display false twice.
is this possible?
There is some confusion in your question. Your plugin is a simple function. You don't "instantiate" a function by calling it. So you don't "instantiate" your plugin either.
You can instantiate things in your function, and persist them somewhere.
Since the outer scope runs only once, in your original solution you only get one instance of variable x.
If you create it inside the function, a new instance gets created every time you call it.
I assume you want to create an instance for every element you call this plugin on. A good solution would be to attach your data to the DOM element you initiate your plugin on, like:
(function ( $ ) {
$.fn.test = function() {
var vars = this.data('testPlugin');
if (!vars) {
vars = {
x: false,
something: 'else'
};
this.data('testPlugin', vars);
}
alert(vars.x);
vars.x = !vars.x;
return this;
};
}( jQuery ));
Try this fiddle.
You should put
var x = false;
inside $.fn.test function, otherwise the x variable is the same for all test() functions, and set to true after first call.
You can read more here about javascript variable scoping.
Actually, this is much easier than the previous answers. The context of this in your plugin is the jQuery object for the DOM element you're receiving based on the selector you provided. To gain uniqueness, simply iterate over each element, like so:
(function($) {
$.fn.test = function() {
return this.each(function() {
var x = false;
alert(x);
if (!x) {
x = true;
}
});
}
}(jQuery));
$("div1").test(); //false
$("div2").test(); // false
Here's a JSFiddle to confirm: http://jsfiddle.net/Z6j7f/

Using "this" within $.ready within prototype

How do I pass in a variable proxy of the "this" context of the Class instance? For example, this.saySomething() isn't doing what I'd like it to.
Do you have any other recommendations on OOJS code organization?
// Truveo Video Player Class
function Player(){
// Player Defaults
this.opts = {
volume: 0 // Initial volume settings
};
}
// Initialize player setup / methods
Player.prototype.init = function(configs){
// Overwrite defaults with custom configs
this.opts = $.extend(this.opts, configs);
$(document).ready(function(){
this.saySomething();
});
$(window).load(function(){
// do something else
});
}
Player.prototype.saySomething = function(){
alert(this.opts.volume);
}
// Create instance for channel surf
var configs = {volume:10};
var cSurf = new Player();
cSurf.init(configs);
Save a copy of this before entering the function:
var me = this;
$(document).ready(function(){
me.saySomething();
});
In addition to the correct answer from #Box9, one possibility would be to set the value of this for the entire .ready() call.
You can do this with the jQuery.proxy()[docs] method.
$(document).ready( $.proxy(function(){
this.saySomething();
}, this) );
Now in the function that's sent to .ready() it will have your Player object as the value of this.

Categories