Well, I could not resist this catchy title, could I? Perhaps I could. In any case I have this idea and implementation where one jQuery plugin is sort-of-a “socket” (not a network socket, but a socket in the wall) into which you can plug-in your “plugs” which will do standard jQuery related stuff.
Where “standard jQuery related stuff” is either changing the state of the DOM behind, or either using the information from the DOM to do something with it.
Example of the former is much loved “animation” stuff one can do with jQuery. Example of the latter, could be collecting some information from the DOM entities selected with the jQuery and doing something with that information. Sending it to some server or just displaying it would qualify as “doing something with the information”.
As it turns out it is easier to use jQuery for changing the state of the dom, then it is to use information that it can provide from the DOM, of the current page. This is because the latter involves your code which has nothing to do with the stuff that jQuery does. Strictly speaking this is not “difficult” , it is just not “naturally flowing” as jQuery chains of commands do.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// while we are chaning the state of the dom // that jQuery instance is referencing // all is nicely flowing // including having the plugins in the chain, // which changes // the state of the elements referenced var $JQ = $("some selector") .show(500).hide(500).show("slow") .some_animation_plugin() .some_font_plugin() ; // ad infinitum. But. // getting the stuff from the elements // through jQuery , breaks the chain var name = $JQ.find(":last"). data("name","Jeresig") .data("name") ; // have to stop here var guts = $JQ.html() ; // and here var msg = $JQ.text() ; // and here // |
Perhaps this looks a bit trivial, and you are perhaps wondering what am I going on about here, and why are you reading this post? You are demanding it, and now I have to find an convincing use-case , to er… convince you. Ok, here we go. Consider writing a code that will “blanket” any user selected number of elements on the page, by placing one div “over” it. This div has to be just big enough to cover all the elements selected. We can make it transparent and/or write some text on it etc … Convincing enough for You? Good. So we have this clever algorithm which given set of overlapping squares, returns one square position and dimensions that “blankets” them all. Obviously we can imagine an plugin that does all of this. Walks through the current “stack” of selected elements, collects positions and dimensions of each , etc … And that is the point: plugin is required.
Each time you want to take something “out” form the jQuery object it is best to write a plugin that will take that “thing out” and then return it, or do something with it, and then return the jQuery instance (doing: return this;) so that Your beloved chaining is not broken.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// a general plugin structure for taking "things out" // without breaking a chain jQuery.fn.extend ( { use_and_proceed : function ( description_of_a_information_to_take_out, callback ) { // somehow obtain the information var the_information = somehow_obtain_the_information (description_of_a_information_to_take_out) ; // use the information obtained callback(the_information); return this; /* so that chain can proceed */ } } ) ; // usage $("whatever").use_and_proceed( "length", alert ).hide("slow").css("top", 123).hide(500) ; $("whatever").use_and_proceed( ":last[id*='something']", alert ).hide("slow").css("top", 123).hide(500) ; $("whatever").use_and_proceed ( ":last[~width]", collect_widths ).hide("slow" ).css("top", 123).hide(500) ; // Nice. Our application logic is satisfied and jQuery // chains are nice and clean.... ? // Oops ?! Can we write generic // somehow_obtain_the_information () // , inside the use_and_proceed() // plugin that will serve all the above ? // And everything else that can be asked for ? //.... |
If we could do the above we could have one (very) nice framework that would allow us to re use the plugin above for virtually “anything”. The actual solution will be in the callback given to the plugin. For example we could try solve the “blanketing” ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
function blanketing ( position, dimensions ) {} $("whatever").use_and_proceed ( "blanketing position and dimensions", blanketing ) ; // Hm .. what information do we get out and how ? // use_and_proceed has to be generic ... // Maybe we can change it and give "this" to callbacks ? // for even more usefulness we can optionally // select a subset form the current stack // referenced by jQuery object // jQuery.fn.extend ( { use_and_proceed : function ( subset_selector, callback ) { if ( subset_selector ) { // find and use the subset callback( this.find( subset_selector ) ) ; } else { // or use the current jQuery instance callback(this); } return this; /* so that chain can proceed */ } } ) ; // this will work for every callback // that understands jQuery object // Hurah ?! We have generic "mother of all plugins" ! // Just give it different callbacks // for different tasks ... function blanketing ( jq_object ) {} $("whatever").use_and_proceed ( blanketing ) ; //.... |
Lovely … But. Will this work ? For starters, call to the callback()
above may block the plugin or throw an exception. Fine. We can solve that.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// // Appache 2.0 (c) 2010-2018 // by dbj@dbj.org jQuery.fn.extend ( { use : function ( callback, subset_selector, error_handler ) { var THAT = null ; error_handler || ( error_handler = (window.console || window.alert) ) ; if ( subset_selector ) { // find and use the subset THAT = this.find( subset_selector ) ; } else { // or use the current jQuery instance THAT = this ; } var tid = setTimeout( function () { try { callback.apply(THAT) ; } catch(x) { error_handler(x); } } , 1 ) ; return this; /* so that chain can proceed */ } } ) ; //.... |
Renamed it to “use”. This will execute the callback on a different call-path (not thread), and will also handle the exception thrown , if any. error_handler
is either user defined or console or alert.
1 2 3 4 5 6 7 |
// function c1 ( ) { alert( this.html() ); } // use c1 on first element from the current stack $("#toolbar").use(c1,":first") ; // use c1 on the current stack $("#toolbar").use(c1) ; //.... |
[nextpage title=”So far so good”]
Perhaps. What is this good for? Ok, for starters what we have here is an “meta thing”. “Meta concept”, overarching abstraction of a familly of abstractions. An “zigot” of meta-programming. In this case “plugin-of-plugins”. I shall call this “Meta Plugin”. Ok, but why is this a “good thing”?
For example , jQuery plugins are (badly?) missing name-spaces. There is no inbuilt classification of plugins, nor is there a valid way to do so. Which in the presence of hundreds of plugins is a problem. In the presence of thousands of plugins it will be (is already?) a nightmare. And to change jQuery plugin mechanism itself, is already a nightmare because of legacy plugins.
But, consider if everybody will be using “Meta Plugin” as an template? Simple but ubiquitous measure, which will give an single level hierarchy of namespacing for templates. Simply use this pattern, but in implementing it give it “your name”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function c1 ( ) { alert( this.html() ); } // Imagine that each company and individual // is using "Meta Plugin" pattern // to namespace their plugins. $("#toolbar") // use c1 he msft namespace of plugins .msft(c1,":first") // use c1 in the goog namespace of plugins .goog(c1,":nth(2)") // use c1 in the bbc namespace of plugins .bbc(c1,":last") // use c1 in the malsup namespace of plugins .malsup(c1,":last") ; // ... |
All the “Meta Plugin” implementations above are identical, but all are named differently. This guarantees (to some degree) re-usability of callbacks between different name-spaces. Like the callback c1() in the code above. This approach can be easily standardized by jQuery.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
// MetaPlugin : Appache 2.0 // (c) 2010-2018 by dbj@dbj.org jQuery.fn.extend ( { MetaPlugin : function ( callback, subset_selector, error_handler ) { /* implementation same as 'use()' plugin above */ } } ); // Above is to be official and inside jQuery itself ... // inside e.g. Microsoft closure // msft meta-plugin jQuery.fn.extend ( { msft : function ( callback, subset_selector, error_handler ) { return this.MetaPlugin(callback, subset_selector, error_handler ); } } ); ... // inside Google closure // goog meta-plugin jQuery.fn.extend ( { goog : function ( callback, subset_selector, error_handler ) { return this.MetaPlugin( callback, subset_selector, error_handler ); } } ); ... // inside BBC closure // bbc meta-plugin jQuery.fn.extend ( { bbc : function ( callback, subset_selector, error_handler ) { return this.MetaPlugin( callback, subset_selector, error_handler ); } } ); // and so on ... |
Name-spacing without name-spacing. Again if ubiquitously used this simple mechanism will do a lot of good for jQuery. It is indeed “yet another level of indirection” which makes code slower, but it seems it will be well worth it.
Now. There was/is a lot of discussion on how to develop re-usable architecture for jQuery patterns, how to “sneak in” namespaces, etc. All of this has proven that jQuery plugin namespaces can not be done properly without changing jQuery itself. And jQuery rightly team has not shown inclination to do so.
This “plugin meta architecture” is very unobtrusive and is not stopping anyone indulging in the above kind of plugin mental activity. The only requirement is to move all of that activity and solutions onto the callback object used here. So, one can fire away and do her favourite “ninja” scripting, but all in and around a callback object.
[nextpage title=”Conclusion”]
There is an small(ish) set of “official” jQuery plugins which are maintained by jQuery team. Consequently there is de-facto standard on what is a “good plugin”. And consequently to that there is a small list of “official” requirements for such a plugin. Each and every point in the list of “official” requirements for a good plugin one can be solve easily with the properly developed callback object, to be used by MetaPlugin.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// // development of the callback object is important // (and what is not?) var my_callback = function ( options ) { var meta_data, private_function = function () {} , callback = function () { meta_data = use_meta_data_plugin_or_not ( this ) ; var some_option = options.something ; // does not matter what is returned // MetaPlugin does the rest } ; callback.secondary = function () { meta_data ; options.something; /* or whatever */ } ; callback.default = { s1 : 1, s2: 2} ; // default public settings return callback ; } |
This is one of the several possible valid callback object designs. Now let us see how is the callback object above solving the plugin requirements bellow.
- Claim only a single name in the jQuery namespace
- Solved, by using MetaPlugin
- Accept an options argument to control plugin behavior
- Using the callback as above this is easy (too).
$("#toolbar").bbc( my_callback({ o1 : 1, o2: 2},":last") ;
- Using the callback as above this is easy (too).
- Provide public access to default plugin settings
- As made possible with the my_callback above. One just has to learn to use the callback object.
// made here or somewhere else
var callback = my_callback({ o1:1 })
// just javascript
var s1 = callback.default.s1
//...
- As made possible with the my_callback above. One just has to learn to use the callback object.
- Provide public access to secondary functions (as applicable)
// just javascript
callback.secondary() ;
//...
- Keep private functions private
- As my_callback does…
- Support the Metadata Plugin
- As my_callback does, also
I thought it is better to have the solution for the “official” list of jQuery plugin requirements, here than in the comments bellow ;)
Of course one does not have to have all this “scafolding” to use MetaPlugin. Good old simple function will do, most of the time.
–DBJ