Background
So, I came across an article Essential jQuery Plugin Patterns a few months ago. I was trying to write a simple jQuery plugin and I found this article very useful. It contains a lot of information, most of which is beyond my expertise of JavaScript or jQuery. The main thing of interest was the first plugin template, dubbed jQuery Lightweight Plugin Boilerplate, which I referenced in another question a few months ago.
Recently, I started using CoffeeScript and looked up for a CoffeeScript jQuery Plugin Template, which I found here.
Both of these implement a pretty much same concept. Some of the important things different here are:
- The private objects that are not there in the Lightweight Boilerplate
- The namespacing block that is different in the CoffeeScript Template. (I have absolutely no idea what he did with
methods[method].apply this, Array::slice.call(arguments, 1)
though I somewhat understand the namespacing block of Lightweight Boilerplate. - In Lightweight Boilerplate chainability and iteration over objects is taken care of in the namespacing block, whereas in CoffeeScript Template
method.init()
needs toreturn $(this)
as well as do$(this).each()
.
Research
So I went ahead and spliced both of these. I wrote my own template. Here it goes:
(($, window, document) ->
# ---------------------------------------------------------------------------
# Conventionally private variables
# ---------------------------------------------------------------------------
_PluginName = "FooBar"
_Defaults =
property: 'value' # etc. etc.
# ---------------------------------------------------------------------------
# Private methods
# ---------------------------------------------------------------------------
_Debug = (msg) ->
window.console.log(msg)
return
# ---------------------------------------------------------------------------
# Plugin Constructor
# ---------------------------------------------------------------------------
Plugin = (element, options) ->
@element = element
@options = $.extend true, {}, _Defaults, options
# TODO: Call methods to do stuff
return
# ---------------------------------------------------------------------------
# Plugin Methods
# ---------------------------------------------------------------------------
Plugin.prototype.init = () ->
# TODO: Plugin initializiation logic
return
Plugin.prototype.destroy = () ->
# TODO: Cleanup, unbind and eject
return
# ---------------------------------------------------------------------------
# Actual plugin body
# ---------------------------------------------------------------------------
$.fn[_PluginName] = (options) ->
return @.each () ->
($.data @, 'plugin_' + _PluginName
new Plugin @, options
) unless $.data @, 'plugin_' + _PluginName
return
) jQuery, window, document
I'm not asking for a comparison between both plugins. And my plugin is not even close to perfect. I found out from answer to another question that using the ($, window, document)
closure is an overkill. There may be other faults and you are absolutely welcome to point them out and discuss them.
Nonetheless, I have some very specific questions.
Questions
How safe are these conventionally 'private' objects/methods. What if someone intentionally wanted to call
Plugin()
instead of using the plugin the right way? Could_Defaults
be accessed in any scope other than this closure?What exactly is the scope of these 'private' objects? Do they float around in jQuery namespace willy-nilly? Could they interfere with anything? How does using these 'private' variables help make the code better?
I don't completely understand
prototype
. Areinit()
anddestroy()
properties of thePlugin()
itself? Why doinit()
anddestroy()
refer to theoptions
property ofPlugin()
asthis.options
as ifinit()
anddestroy()
were defined inside the context ofPlugin()
itself... And what if I have an object defined asPlugin.prototype.methods
? What isthis
for the functions inside themethods
object?
NOTE: If it is difficult for anyone to follow links to view code, please let me know. I'll edit this question to include the code. I didn't include because then the question would become too long.
When you do something like this:
the JavaScript version looks like this:
In particular,
Pancakes
is local to the anonymous self executing function and so it cannot be accessed from outside the function. Also note that the CoffeeScript compiler will wrap each.coffee
file in a self-executing function to avoid scope creep, i.e. this:ends up as (more or less):
so even if you don't manually add some sort of scope wrapper, CoffeeScript will add one for you. Of course, if someone is compiling the CoffeeScript themselves, they can suppress the outer function wrapper by running
coffee --bare
to compile the CoffeeScript. OTOH, everyone has a right to a foot-gun and they're welcome to shoot off their own feet if so desired.That should answer 1 and 2.
As far as your prototype questions go, you should be writing:
instead of referencing
prototype
directly, they're the same thing but::
is more CoffeeScripty.Back to the question at hand. When you say this:
that's the same as saying:
so assigning something to
prototype
is just adding a method to the "class". That means that@
(AKAthis
) will (usually) be the object:Of course, the value of
@
inside any CoffeeScript function depends (just like in JavaScript) on how the function is called and whether or not the function has been bound to an object.You can verify this with a simple example:
That will get you two copies of the same random number in your console as expected.
Hopefully that takes care of 3.