I've been working on some javascript for a project and decided it should be a jQuery plugin. I've written some before but this needs to be more robust and destroyable. To that end I have followed a few tutorials but they all fall short when describing how to destroy the plugin.
So how do I destroy the plugin? I can't seem to access $('.js-target).fullscreen('destroy')
doesn't seem to work. Nor does $(.js-target).data('fullscreen').destroy()
which returns TypeError: Cannot read property 'destroy' of undefined
in the console.
I have written it in coffeescript. The generated javascript is posted below.
(($, window) ->
'use strict'
# Create the defaults once
pluginName = 'fullscreen'
defaults =
reference: window
offset: 0
debug: true
# The actual plugin constructor
Plugin = ( element, options ) ->
this.element = element
this.options = $.extend {}, defaults, options
this._defaults = defaults
this._name = pluginName
this.init()
Plugin.prototype.init = ->
this.bind()
this.setHeight()
Plugin.prototype.bind = ->
# Maintain the scope
self = this
# Trigger on resize
$(window).on 'resize orientationchange', ->
self.setHeight()
# When scrolling on a touchscreen
# prevent further resizes due to address bar shrinking
$(window).on 'touchstart', ->
self.unbind()
Plugin.prototype.getHeight = ->
this.log 'Get height from: ', this.options.reference
$( this.options.reference ).height()
Plugin.prototype.setHeight = ->
if this.options.offset == parseInt( this.options.offset )
offset = this.options.offset
else
offset = 0
$(this.element).css
'min-height' : this.getHeight() - offset
Plugin.prototype.unbind = ->
this.log 'Unbind the resize, touchstart and orientationchange event handlers'
$(window).off 'resize touchstart orientationchange'
Plugin.prototype.destroy = ->
this.unbind()
log 'Remove any heights set on', this.element
$(this.element).attr('style','')
Plugin.prototype.log = ( msg, object ) ->
if this.options.debug
if !object
object = ''
console.log( pluginName + ': ' + msg, object )
# A really lightweight plugin wrapper around the constructor,
# preventing multiple instantiations
$.fn[pluginName] = ( options ) ->
return this.each ->
if !$.data(this, 'plugin_' + pluginName)
$.data(this, 'plugin_' + pluginName)
new Plugin(this, options)
return $.fn[pluginName]
) jQuery, window
This is the generated javascript. Could it be the anonymous function that coffeescript wraps around the function?
(function(){
(function($, window) {
'use strict';
var Plugin, defaults, pluginName;
pluginName = 'fullscreen';
defaults = {
reference: window,
offset: 0,
debug: true
};
Plugin = function(element, options) {
this.element = element;
this.options = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = pluginName;
return this.init();
};
Plugin.prototype.init = function() {
this.bind();
return this.setHeight();
};
Plugin.prototype.bind = function() {
var self;
self = this;
$(window).on('resize orientationchange', function() {
return self.setHeight();
});
return $(window).on('touchstart', function() {
return self.unbind();
});
};
Plugin.prototype.getHeight = function() {
this.log('Get height from: ', this.options.reference);
return $(this.options.reference).height();
};
Plugin.prototype.setHeight = function() {
var offset;
if (this.options.offset === parseInt(this.options.offset)) {
offset = this.options.offset;
} else {
offset = 0;
}
return $(this.element).css({
'min-height': this.getHeight() - offset
});
};
Plugin.prototype.unbind = function() {
this.log('Unbind the resize, touchstart and orientationchange event handlers');
return $(window).off('resize touchstart orientationchange');
};
Plugin.prototype.destroy = function() {
this.unbind();
log('Remove any heights set on', this.element);
return $(this.element).attr('style', '');
};
Plugin.prototype.log = function(msg, object) {
if (this.options.debug) {
if (!object) {
object = '';
}
return console.log(pluginName + ': ' + msg, object);
}
};
$.fn[pluginName] = function(options) {
return this.each(function() {
if (!$.data(this, 'plugin_' + pluginName)) {
$.data(this, 'plugin_' + pluginName);
return new Plugin(this, options);
}
});
};
return $.fn[pluginName];
})(jQuery, window);
}).call(this);
Any help would be appreciated.
You have some strange things going on here so I'll start at the top.
That CoffeeScript looks like you transliterated an existing jQuery plugin from JavaScript to CoffeeScript. You should write CoffeeScript in CoffeeScript:
class Plugin
constructor: (@element, options) ->
@options = $.extend { }, defaults, options
#...
@init()
init: ->
@bind()
@setHeight() # The `return` is implicit here
bind: ->
# Use `=>` instead of an explicit `self = this` trick.
$(window).on 'resize orientationchange', => @setHeight()
$(window).on 'touchstart', => @unbind()
#...
Now for the actual plugin definition:
$.fn[pluginName] = ( options ) ->
return this.each ->
if !$.data(this, 'plugin_' + pluginName)
$.data(this, 'plugin_' + pluginName)
new Plugin(this, options)
The $.data
call inside the if
doesn't do anything useful, you want the $.data(obj, key, value)
form of $.data
if your intent is to attach the Plugin
instance to the DOM node. And again, you don't need the return
s and @
is more common than this
in CoffeeScript:
$.fn[pluginName] = (options) ->
@each ->
if !$.data(@, "plugin_#{pluginName}")
$.data(@, "plugin_#{pluginName}", new Plugin(@, options))
I also switched to string interpolation rather than +
for the $.data
key, that's usually easier to read.
Now you should be able to say:
$('.js-target').data('plugin_fullscreen').destroy()
Note that the data key is 'plugin_fullscreen'
rather than 'fullscreen'
. This is a bit nasty of course, you probably don't want to force everyone to look at private details.
If you want to do jQuery-UI style things like:
$('.js-target').fullscreen('destroy')
then all you need to do is update the plugin function to know that 'destroy'
is supposed to be a method call rather than an options
object. Something simple like this should get you started:
$.fn[pluginName] = (args...) ->
@each ->
plugin = $.data(@, dataKey)
if typeof args[0] == 'string'
plugin?[args[0]]?()
else if !plugin
$.data(@, dataKey, new Plugin(@, args[0]))
So if you say $(x).fullscreen('string')
then it assumes that you're trying to call a method on the internal Plugin
instance, all the existential operators (?
) just deal with missing values (plugin not attached, unknown method, ...); in real life you might want to whitelist the methods that you're allowed to call this way. And if you say $(x).fullscreen(opt: 1)
then it assumes that you're trying to attach the plugin to something using {opt: 1}
as the options. Again, a real life version of this would probably be more complicated.
Quick'n'dirty demo: http://jsfiddle.net/ambiguous/568SU/1/
You might want to look at the jQuery-UI widget factory if you're doing a lot of this sort of thing, the factory takes care of a lot of the unpleasant details for you.