jQuery: plugin inter-communication - javascript

Here is the problem:
I have a very complex plugin that does lots of different initialization and binding when it is executed.
I want to be able to run the same plugin multiple times on the same element, giving it different options. After it runs once on the element, certain initialization does not need to be done again on subsequent executions on that element.
Currently the plugin code is inside of a closure and it doesnt know anything about other times the same plugin has run on the element.
Is there a pattern that people follow when they want inter-communication?
I am thinking of something like this:
$.plugin = {
globalRefs = [];
}
$.fn.plugin = function() {
var that = {};
$.fn.plugin.id ++; //each execution gets its unique id
var privateFn = function() { ... };
that.privateFn = privateFn; //expose all useful inner functions to that.
$.plugin.globalRefs[$.fn.plugin.id] = that; //make that global
}
$.fn.plugin.id = 0;

You talk about "other plugins", but it's not clear what you mean by that; what other plugins? What do they need to "know" about each other?
If you just want to maintain state, why not just use the jQuery data() mechanism to store whatever you need right on the target DOM elements? That would let your plugin find out about previous invocations, and it would also allow these mysterious "other plugins" to use that stored data too.
// ...
$(theElement).data('pluginName', { 'fabulous': 'data' });
The data that you store with this mechanism can be anything you like:
$(theElement).data('pluginName', {
'aNumber': 23.5,
'anArray': ['hello', 'world'],
'aFunction': function(arg) {
alert("wow a function! Here is the argument: " + arg);
}
'anObject': {
'more': 'stuff'
}
});

Related

Pure Javascript plugin development

I need to develop a pure javascript plugin wich can be accessed like jquery typed plugins ( $('.pluginWrapper').pluginInit();
However i need to to use pure javascript and i was thinking maybe about 2 supported formats:
document.getElementById('pluginWrapper').pluginInit();
pluginInit(document.getElementById('pluginWrapper'));
I know that you have to do an IIFE to wrap it and call it via object methods but i do not know how i can bind that to an element.
I am mentioning that i am a begginer so please can someone please explain something about this. Cheers!
You would be safer developing a plugin interface that simply exposes plugins as functions which take an element as an argument.
function MyPlugin(element) {
// do stuff with element
element.setAttribute('data-plugin-id', 'pluginName');
}
The other approach involves extending Element.prototype. Which could potentially be a dangerous action in production software.
However, it is still possible.
Element.prototype.pluginInit = function() {
// the element is accessible as `this`
this.setAttribute('data-plugin-id', 'pluginName');
}
A function is easy for everyone to understand. Plugin writers don't have to understand any interfaces for creating and registering plugins, they just need to know that they should write functions that take elements as arguments.
There's a great talk from Rich Hickey (the creator of Clojure) called Simplicity Matters in which he stresses that the worst thing you can do is add additional complexity when simple solutions will do.
In this case, you don't need anything more complex than a function which takes an element as an argument.
If it is completely essential that you have control of the function, you could write a simple interface for registering and initiating plugins.
function Plugin(element) {
if(element === null) {
throw new TypeError("Element must not be null!");
}
// get all the plugin names from the store
var pluginNames = Object.keys(Plugin.store);
// make sure `this` is set to the element for each plugin
var availablePlugins = pluginNames.reduce(function(plugins, name) {
plugins[name] = Plugin.store[name].bind(element);
return plugins;
}, {});
// return an object containing all plugins
return availablePlugins;
}
// we'll store the plugins in this object
Plugin.store = {};
// we can register new plugins with this method
Plugin.register = function(name, pluginFn) {
Plugin.store[name] = pluginFn;
};
Which you could use like this.
Plugin.register('myPlugin', function() {
this.setAttribute('data-plugin-id', 'myPlugin');
});
Plugin(document.getElementById('pluginWrapper')).myPlugin();
If you want the plugin function to take a selector, the same way as jQuery, then you can use document.querySelectorAll inside your definition for Plugin.
function Plugin(selector) {
var element = document.querySelectorAll(selector);
if(element === null) {
throw new TypeError("Element must not be null!");
}
// get all the plugin names from the store
var pluginNames = Object.keys(Plugin.store);
// make sure `this` is set to the element for each plugin
var availablePlugins = pluginNames.reduce(function(plugins, name) {
plugins[name] = Plugin.store[name].bind(element);
return plugins;
}, {});
// return an object containing all plugins
return availablePlugins;
}
Then you would use it like this instead.
Plugin.register('myPlugin', function() {
this.setAttribute('data-plugin-id', 'myPlugin');
});
Plugin('#pluginWrapper').myPlugin();

encapsulating javascript inside a namespace

I'm looking to encapsulate my javascript inside a namespace like this:
MySpace = {
SomeGlobal : 1,
A: function () { ... },
B: function () { ....; MySpace.A(); .... },
C: function () { MySpace.SomeGlobal = 2;.... }
}
Now imagine that instead of a few lines of code, I have about 12K lines of javascript with hundreds of functions and about 60 globals. I already know how to convert my code into a namespace but I'm wondering if there's a quicker way of doing it than going down 12K lines of code and adding MySpace. all over the place.
Please let me know if there's a faster way of doing this.
Thanks for your suggestions.
I like to wrap up the namespace like so. The flexibility is huge, and we can even separate different modules of the MySpace namespace in separate wrappers if we wanted too. You will still have to add some sort of _self. reference infront of everything, but at least this way you can change the entire name of the namespace very quickly if need be.
You can see how with this method you can even call _self.anotherFunc() from the 1st module, and you'll get to the second one.
(function (MySpace, $, undefined) {
var _self = MySpace; // create a self-reference
_self.test = function () {
alert('we got here!');
_self.anotherFunc(); // testing to see if we can get the 2nd module
};
_self = MySpace; // reassign everything just incase
}(window.MySpace = window.MySpace || {}, jQuery));
$(function () {
MySpace.test(); // call module 1
MySpace.callOtherModule(); // call module 2
});
// Here we will create a seperate Module to the MySpace namespace
(function (MySpace, $, undefined) {
var _self = MySpace; // create a self-reference
_self.callOtherModule = function () {
alert('we called the 2nd module!');
};
_self.anotherFunc = function () {
alert('We got to anotherFunc from the first module, even by using _self.anotherFunc()!');
};
_self = MySpace; // reassign everything just incase
}(window.MySpace = window.MySpace || {}, jQuery));​
jsFiddle DEMO
Wrap a function body around your existing code to use as scope, hiding everything from global - this will allow you to do internal calls without pasting Namespace. prefix everywhere, neatly hide things you don't want everyone else to see, and will require minimal changes as well.
After that, decide what functions you want to "export" for everyone and assign them to properties of object you want to use as "namespace".

jQuery plugin namespace for specific objects

I'm am trying to create a jQuery plugin that will add new namespace functions to the context object(s), while maintaining full chain-ability. I'm not sure if it's possible, but here's an example of what I have so far:
(function ($) {
var loadScreen = $('<div />').text('sup lol');
$.fn.someplugin = function (args) {
var args = args || {},
$this = this;
$this.append(loadScreen);
return {
'caption' : function (text) {
loadScreen.text(text);
return $this;
}
};
}
})(jQuery);
This works fine if I do $(document.body).someplugin().caption('hey how\'s it going?').css('background-color', '#000');
However I also need the ability to do $(document.body).someplugin().css('background-color', '#000').caption('hey how\'s it going?');
Since .someplugin() returns it's own object, rather than a jQuery object, it does not work as expected. I also need to be able to later on access .caption() by $(document.body). So for example if a variable is not set for the initial $(document.body).someplugin(). This means that somehow how .caption() is going to be set through $.fn.caption = function () ... just for the document.body object. This is the part which I'm not quite sure is possible. If not, then I guess I'll have to settle for requiring that a variable to be set, to maintain plugin functions chain-ability.
Here's an example code of what I expect:
$(document.body).someplugin().css('background-color', '#000');
$('.non-initialized').caption(); // Error, jQuery doesn't know what caption is
$(document.body).caption('done loading...');
Here's what I'm willing to settle for if that is not possible, or just very inefficient:
var $body = $(document.body).someplugin().css('background-color', '#000');
$('.non-initialized').caption(); // Error, jQuery doesn't know what caption is
$body.caption('done loading...');
The be jquery-chainable, a jQuery method MUST return a jQuery object or an object that supports all jQuery methods. You simply have to decide whether you want your plugin to be chainable for other jQuery methods or whether you want it to return your own data. You can't have both. Pick one.
In your code examples, you could just define more than one plugin method, .someplugin() and .caption(). jQuery does not have a means of implementing a jQuery plugin method that applies to one specific DOM object only. But, there is no harm in making the method available on all jQuery objects and you can only use it for the ones that it makes sense for.
I think you could use this:
(function ($) {
var loadScreen = $('<div />').text('sup lol');
$.fn.someplugin = function (args) {
var args = args || {},
$this = this;
$this.append(loadScreen);
return(this);
}
$.fn.caption = function (text) {
loadScreen.text(text);
return this;
}
})(jQuery);
$(document.body).someplugin().css('background-color', '#000');
$('.non-initialized').caption('whatever');
$(document.body).caption('done loading...');
If there's supposed to be some connection between the two .caption() calls, please explain that further because I don't follow that from your question.

Using jQuery, how do I add elements to a jQuery object that's yet to be created?

Perhaps I am doing this wrong and suggestions on how to improve my code are appreciated. My situation is this: I have a toolbar with different elements that are populated by a callback. I do not want to use the show() or hide() commands, I prefer to use detach, but I think there should be a nice way to deal with it. Here's my code:
entryView = function _entryView() {
var menuButton = $('<div/>').addClass('menuButton');
toolBar();
$.getJSON('ajax', function(o) {
var $enum2 = ss.IEnumerator.getEnumerator(dto.TransmittalDates);
while ($enum2.moveNext()) {
var dateTime = $enum2.get_current();
$('.menu').append($('<div/>').addClass('menuitem').text(dateTime.toString()));
}
});
}
toolBar = function _toolBar() {
var flyoutMenu = $('<div/>').addClass('menu');
$('.menuButton').click(function(o) {
$('.menubutton').append(flyoutMenu);
});
I did a quick cut and paste and renamed the variables to make them make sense. As you can see on the entry I build the toolbar and the very last thing I do is the ajax call. The menu, however, is not created until the "click" event, so appending is not possible.
I realize that having global variables is bad, so I'm trying to avoid that, but I think the best situation would have the ajax call populate a Menu variable and when the DOM is created, to pull from that same Menu item. How do I pull this off? Is there a better way to do it?
Edit: Fubbed a bit on the toolbar function, I think I have it should be correct now.
I'm confused by some parts of your code:
What's the entryView function and when is it called?
Why does the toolBar function exist as opposed to being inline? From where else is it called?
Why are you creating functions like that? Creating a variable without var is bad practice, always makes global variables, and will be forbidden in ES5 strict mode. You should create functions like this:
var someFunction = function(arg1, arg2) { … };
or like this:
function someFunction(arg1, arg2) { … }
Why are you giving each function a second name (e.g. _toolBar)? The "private" name will only be in scope inside the function.
The menu doesn't have to be in global scope, just in a scope that's common to both functions.
I would refactor it like this (knowing very little about the design of your application), inlining toolBar:
function entryView() {
var menuButton = $('<div/>' {'class': 'menuButton'}), menu = $('<div/>', {'class': 'menu'});
$.getJSON('ajax', function(o) {
var $enum2 = ss.IEnumerator.getEnumerator(dto.TransmittalDates);
while ($enum2.moveNext()) {
var dateTime = $enum2.get_current();
$('.menu').append($('<div/>' {'class': 'menuItem'}).text(dateTime.toString()));
}
});
menuButton.click(function(o) {
menuButton.append(menu);
});
}

Is it possible to destroy loaded JavaScript, including function & local variable?

I know. It is possible to dynamically load JavaScript and style sheet file into header of document. In the other hand, it is possible to remove script and style sheet tag from header of document. However, loaded JavaScript is still live in memory.
Is it possible to destroy loaded JavaScript from web browser memory? I think. It should be something like the following pseudo code.
// Scan all variables in loaded JavaScript file.
var loadedVariable = getLoadedVariable(JavaScriptFile);
for(var variable in loadedVariable)
{
variable = null;
}
// Do same thing with function.
Is it possible to create some JavaScript for doing like this?
Thanks,
PS. Now, you can use xLazyLoader and jQuery for dynamic loading content.
If the loaded script is assigned to a window property, for instance with the module pattern like so:
window.NiftyThing = (function() {
function doSomething() { ... }
return {
doSomething: doSomething
};
})();
or
window.NiftyThing = {
doSomething: function() { ... }
};
or
NiftyThing = {
doSomething: function() { ... }
};
Then you can delete the property that references it:
delete window.NiftyThing;
...which removes at least that one main reference to it; if there are other references to it, it may not get cleaned up.
If the var keyword has been used:
var NiftyThing = {
doSomething: function() { ... }
};
...then it's not a property and you can't use delete, so setting to undefined or null will break the reference:
NiftyThing = undefined;
You can hedge your bets:
NiftyThing = undefined;
try { delete NiftyThing; } catch (e) { }
In all cases, it's up to the JavaScript implementation to determine that there are no outstanding external references to the loaded script and clean up, but at least you're giving it the opportunity.
If, as Guffa says, the loaded script doesn't use the module pattern, then you need to apply these rules to all of its symbols. Which is yet another reason why the module pattern is a Good Thing(tm). ;-)
It might be possible to remove a Javascript file that has been loaded, but that doesn't undo what the code has done, i.e. the functions that was in the code are still defined.
You can remove a function definition by simply replacing it with something else:
myFunction = null;
This doesn't remove the identifier, but it's not a function any more.

Categories

Resources