Getting the Most Out of jQuery Widgets

51
Richard Lindsey @Velveeta http://conqueringtheclient.com/ PLATFORM FEE ARCHITECT | THE ADVISORY BOARD COMPANY jQuery Widgets GETTING THE MOST OUT OF

description

Richard Lindsey's presentation from the 2013 jQuery Conference in Austin, Tx.

Transcript of Getting the Most Out of jQuery Widgets

Page 1: Getting the Most Out of jQuery Widgets

Richard Lindsey @Velveeta http://conqueringtheclient.com/PLATFORM FEE ARCHITECT | THE ADVISORY BOARD COMPANY

jQuery Widgets

GETTING THE MOST OUT OF

Page 2: Getting the Most Out of jQuery Widgets

Let’s say we’re making Widgets…

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Page 3: Getting the Most Out of jQuery Widgets

What’s a Widget?

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Page 4: Getting the Most Out of jQuery Widgets

ELEMENTS / COMPOUNDS /CELLS / ORGANISMS

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Thinksmall.Thinkmodular.

Page 5: Getting the Most Out of jQuery Widgets

Communicate through

events.KEEP COMPONENTS DECOUPLED / MAKE THEM SUBSCRIBE AND RESPOND

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Page 6: Getting the Most Out of jQuery Widgets

Communicate through

events.KEEP COMPONENTS DECOUPLED / MAKE THEM SUBSCRIBE AND RESPOND

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Page 7: Getting the Most Out of jQuery Widgets

Observe and

mediate.BUNDLE SMALLER MODULES / PROVIDE PUBLIC API / DIRECT REFERENCES SHOULD ONLY GO DOWNWARDS / EACH LAYER CONSUMES LOWER-LEVEL EVENTS & PUBLISHES UPWARDS

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Page 8: Getting the Most Out of jQuery Widgets

Observe and

mediate.BUNDLE SMALLER MODULES / PROVIDE PUBLIC API / DIRECT REFERENCES SHOULD ONLY GO DOWNWARDS / EACH LAYER CONSUMES LOWER-LEVEL EVENTS & PUBLISHES UPWARDS

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Page 9: Getting the Most Out of jQuery Widgets

Observe and

mediate.BUNDLE SMALLER MODULES / PROVIDE PUBLIC API / DIRECT REFERENCES SHOULD ONLY GO DOWNWARDS / EACH LAYER CONSUMES LOWER-LEVEL EVENTS & PUBLISHES UPWARDS

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Page 10: Getting the Most Out of jQuery Widgets

Observe and

mediate.BUNDLE SMALLER MODULES / PROVIDE PUBLIC API / DIRECT REFERENCES SHOULD ONLY GO DOWNWARDS / EACH LAYER CONSUMES LOWER-LEVEL EVENTS & PUBLISHES UPWARDS

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Page 11: Getting the Most Out of jQuery Widgets

$.widget(‘abc.autocomplete’, {_create: function () {

this._widgets = {dataloader: {loader:{}},optionlist: {results:{}},input: {search:{}}

};this._createWidgets();this._routeTraffic();

},_routeTraffic: function () {

this._on(this.element, { autocompletesuccess: this._showOptionList });this._on(this._widgets.search, { inputkeydown: _.debounce(this._updateDataloaderSearchParam, 100) });this._on(this._widgets.results, { optionlistselected: this._updateInput });

},_updateDataloaderSearchParam: function (e, search) {

var deferred = this._widgets.loader .dataloader(‘updateParams’, ‘search’, search) .dataloader(‘fetch’);

this._trigger(‘fetch’, deferred);},_showOptionList: function () {

this._widgets.results.optionlist(‘show);this._trigger(‘showresults’);

},_updateInput: function (e, value) {

this._widgets.search.input(‘setValue’, value);this._trigger(‘change’, value);

},setData: function (data) {

var deferred = this._widgets.loader .dataloader(‘setData’, data) .dataloader(‘fetch’);

this._trigger(‘fetch’, deferred);},setValue: function (value) {

this._updateInput(null, value);}

});

$(function () {$(‘abc-autocomplete’).autocomplete();

});

Page 12: Getting the Most Out of jQuery Widgets

$.widget(‘abc.autocomplete’, {_create: function () {

this._widgets = {dataloader: {loader:{}},optionlist: {ddl:{}},input: {search:{}}

};this._createWidgets();this._routeTraffic();

},_routeTraffic: function () {

this._on(this.element, { autocompletesuccess: this._showOptionList });this._on(this._widgets.search, { inputkeydown: _.debounce(this._updateDataloaderSearchParam, 100) });this._on(this._widgets.results, { optionlistselected: this._updateInput });

},_updateDataloaderSearchParam: function (e, search) {

var deferred = this._widgets.loader .dataloader(‘updateParams’, ‘search’, search) .dataloader(‘fetch’);

this._trigger(‘fetch’, deferred);},_showOptionList: function () {

this._widgets.results.optionlist(‘show);this._trigger(‘showresults’);

},_updateInput: function (e, value) {

this._widgets.search.input(‘setValue’, value);this._trigger(‘change’, value);

},setData: function (data) {

var deferred = this._widgets.loader .dataloader(‘setData’, data) .dataloader(‘fetch’);

this._trigger(‘fetch’, deferred);},setValue: function (value) {

this._updateInput(null, value);}

});

$(function () {$(‘abc-autocomplete’).autocomplete();

});

Page 13: Getting the Most Out of jQuery Widgets

$.widget(‘abc.autocomplete’, {_create: function () {

this._widgets = {dataloader: {loader:{}},optionlist: {ddl:{}},input: {search:{}}

};this._createWidgets();this._routeTraffic();

},_routeTraffic: function () {

this._on(this.element, { autocompletesuccess: this._showOptionList });this._on(this._widgets.search, { inputkeydown: _.debounce(this._updateDataloaderSearchParam, 100) });this._on(this._widgets.results, { optionlistselected: this._updateInput });

},_updateDataloaderSearchParam: function (e, search) {

var deferred = this._widgets.loader .dataloader(‘updateParams’, ‘search’, search) .dataloader(‘fetch’);

this._trigger(‘fetch’, deferred);},_showOptionList: function () {

this._widgets.results.optionlist(‘show);this._trigger(‘showresults’);

},_updateInput: function (e, value) {

this._widgets.search.input(‘setValue’, value);this._trigger(‘change’, value);

},setData: function (data) {

var deferred = this._widgets.loader .dataloader(‘setData’, data) .dataloader(‘fetch’);

this._trigger(‘fetch’, deferred);},setValue: function (value) {

this._updateInput(null, value);}

});

$(function () {$(‘abc-autocomplete’).autocomplete();

});

Page 14: Getting the Most Out of jQuery Widgets

$.widget(‘abc.autocomplete’, {_create: function () {

this._widgets = {dataloader: {loader:{}},optionlist: {ddl:{}},input: {search:{}}

};this._createWidgets();this._routeTraffic();

},_routeTraffic: function () {

this._widgets.loader.on(‘dataloadersuccess’, this._updateDropdownlist);this._widgets.ddl.on(‘dropdownlistselected’, this._updateInput);this._widgets.search.on(‘inputkeydown’, _.debounce(this._updateDataloaderSearchParam, 300));

},_updateDataloaderSearchParam: function (e, search) {

var deferred = this._widgets.loader .dataloader(‘updateParams’, ‘search’, search) .dataloader(‘fetch’);

this._trigger(‘fetch’, deferred);},_showOptionList: function () {

this._widgets.results.optionlist(‘show);this._trigger(‘showresults’);

},_updateInput: function (e, value) {

this._widgets.search.input(‘setValue’, value);this._trigger(‘change’, value);

},setData: function (data) {

var deferred = this._widgets.loader .dataloader(‘setData’, data) .dataloader(‘fetch’);

this._trigger(‘fetch’, deferred);},setValue: function (value) {

this._updateInput(null, value);}

});

$(function () {$(‘abc-autocomplete’).autocomplete();

});

Page 15: Getting the Most Out of jQuery Widgets

$.widget(‘abc.autocomplete’, {_create: function () {

this._widgets = {dataloader: {loader:{}},optionlist: {ddl:{}},input: {search:{}}

};this._createWidgets();this._routeTraffic();

},_routeTraffic: function () {

this._widgets.loader.on(‘dataloadersuccess’, this._updateDropdownlist);this._widgets.ddl.on(‘dropdownlistselected’, this._updateInput);this._widgets.search.on(‘inputkeydown’, _.debounce(this._updateDataloaderSearchParam, 300));

},_updateDataloaderSearchParam: function (e, search) {

var deferred = this._widgets.loader .dataloader(‘updateParams’, ‘search’, search) .dataloader(‘fetch’);

this._trigger(‘fetch’, deferred);},_showOptionList: function () {

this._widgets.results.optionlist(‘show);this._trigger(‘showresults’);

},_updateInput: function (e, value) {

this._widgets.search.input(‘setValue’, value);this._trigger(‘change’, value);

},setData: function (data) {

var deferred = this._widgets.loader .dataloader(‘setData’, data) .dataloader(‘fetch’);

this._trigger(‘fetch’, deferred);},setValue: function (value) {

this._updateInput(null, value);}

});

$(function () {$(‘abc-autocomplete’).autocomplete();

});

Page 16: Getting the Most Out of jQuery Widgets

Richard Lindsey @Velveeta http://conqueringtheclient.com/

BAD IDEAAHEAD

Page 17: Getting the Most Out of jQuery Widgets

Decorate ALL thefunction

s!

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Page 18: Getting the Most Out of jQuery Widgets

Richard Lindsey @Velveeta http://conqueringtheclient.com/

MODIFY THE FACTORY FUNCTION IF YOU NEED TO

Decorate ALL thefunction

s!

Page 19: Getting the Most Out of jQuery Widgets

var widgetFactory = $.widget;

$.widget = function (name, base, prototype) {var targetPrototype = prototype || base;

$.each(targetPrototype, function (key, callback) {if (typeof callback === ‘function’) {

targetPrototype[key] = function () {if (someConditionPasses) {fireSomeFunction();}

var result = callback.apply(this, arguments);

if (someOtherConditionPasses) {fireSomeOtherFunction();}

return result;};

}});return widgetFactory.apply(this, arguments);

};

// The widget factory function itself has some function members itself,// like $.widget.bridge and $.widget.extend. Don’t forget to copy those// items over from the original factory to our new implementation!$.each(widgetFactory, function (key, value) {

$.widget[key] = value;});

Page 20: Getting the Most Out of jQuery Widgets

var widgetFactory = $.widget;

$.widget = function (name, base, prototype) {var targetPrototype = prototype || base;

$.each(targetPrototype, function (key, callback) {if (typeof callback === ‘function’) {

targetPrototype[key] = function () {if (someConditionPasses) {fireSomeFunction();}

var result = callback.apply(this, arguments);

if (someOtherConditionPasses) {fireSomeOtherFunction();}

return result;};

}});return widgetFactory.apply(this, arguments);

};

// The widget factory function itself has some function members itself,// like $.widget.bridge and $.widget.extend. Don’t forget to copy those// items over from the original factory to our new implementation!$.each(widgetFactory, function (key, value) {

$.widget[key] = value;});

Page 21: Getting the Most Out of jQuery Widgets

var widgetFactory = $.widget;

$.widget = function (name, base, prototype) {var targetPrototype = prototype || base;

$.each(targetPrototype, function (key, callback) {if (typeof callback === ‘function’) {

targetPrototype[key] = function () {if (someConditionPasses) {fireSomeFunction();}

var result = callback.apply(this, arguments);

if (someOtherConditionPasses) {fireSomeOtherFunction();}

return result;};

}});return widgetFactory.apply(this, arguments);

};

// The widget factory function itself has some function members itself,// like $.widget.bridge and $.widget.extend. Don’t forget to copy those// items over from the original factory to our new implementation!$.each(widgetFactory, function (key, value) {

$.widget[key] = value;});

Page 22: Getting the Most Out of jQuery Widgets

var widgetFactory = $.widget;

$.widget = function (name, base, prototype) {var targetPrototype = prototype || base;

$.each(targetPrototype, function (key, callback) {if (typeof callback === ‘function’) {

targetPrototype[key] = function () {if (someConditionPasses) {fireSomeFunction();}

var result = callback.apply(this, arguments);

if (someOtherConditionPasses) {fireSomeOtherFunction();}

return result;};

}});return widgetFactory.apply(this, arguments);

};

// The widget factory function itself has some function members itself,// like $.widget.bridge and $.widget.extend. Don’t forget to copy those// items over from the original factory to our new implementation!$.each(widgetFactory, function (key, value) {

$.widget[key] = value;});

Page 23: Getting the Most Out of jQuery Widgets

var widgetFactory = $.widget;

$.widget = function (name, base, prototype) {var targetPrototype = prototype || base;

$.each(targetPrototype, function (key, callback) {if (typeof callback === ‘function’) {

targetPrototype[key] = function () {if (someConditionPasses) {fireSomeFunction();}

var result = callback.apply(this, arguments);

if (someOtherConditionPasses) {fireSomeOtherFunction();}

return result;};

}});return widgetFactory.apply(this, arguments);

};

// The widget factory function itself has some function members itself,// like $.widget.bridge and $.widget.extend. Don’t forget to copy those// items over from the original factory to our new implementation!$.each(widgetFactory, function (key, value) {

$.widget[key] = value;});

Page 24: Getting the Most Out of jQuery Widgets

Richard Lindsey @Velveeta http://conqueringtheclient.com/

ALWAYS TRY TO USE PUBLIC API FOR FORWARD COMPATIBILITY

Decorate ALL thefunction

s!

Page 25: Getting the Most Out of jQuery Widgets

Richard Lindsey @Velveeta http://conqueringtheclient.com/

WHO CARES ABOUT INTERNAL IMPLEMENTATIONS?

Feel free to

mix it up.

Page 26: Getting the Most Out of jQuery Widgets

Richard Lindsey @Velveeta http://conqueringtheclient.com/

OVERRIDE FUNCTIONALITY IN ONE OF TWO WAYS:

Feel free to

mix it up.

$.widget Factory

Widget Options

• Overrides prototype, affects all instances

• Maintains pointer to overridden function via _super and _superApply

• Overrides instance-level functionality only

• Provides easy access to consumers to override functionality

Page 27: Getting the Most Out of jQuery Widgets

$.widget(‘abc.dataloader’, {options: {url: null,success: function (results) {this.element.html(JSON.stringify(results));},// etc},fetch: function () {this.element.addClass(‘loading’);return this._load().done($.proxy(function (results) {this.options.success.call(this, results);}, this).always($.proxy(function () {this.element.removeClass(‘loading’);}, this));},_load: function () {return $.ajax(this.options);}

});$.widget(‘abc.dataloader’, abc.dataloader, {

_load: function () {var deferred = $.Deferred();this.element.data(‘backboneCollection’).fetch({reset: true,success: function (collection) {deferred.resolve(collection.toJSON());},error: function (collection, response) {deferred.reject(response);}});return deferred.promise();}

});

var myTemplate = Handlebars.compile($(‘#myTemplate’).html());$(‘#myDiv’).dataloader({

success: function (results) {this.element.html(myTemplate(results));}

});

Page 28: Getting the Most Out of jQuery Widgets

$.widget(‘abc.dataloader’, {options: {url: null,success: function (results) {this.element.html(JSON.stringify(results));},// etc},fetch: function () {this.element.addClass(‘loading’);return this._load().done($.proxy(function (results) {this.options.success.call(this, results);}, this).always($.proxy(function () {this.element.removeClass(‘loading’);}, this));},_load: function () {return $.ajax(this.options);}

});$.widget(‘abc.dataloader’, abc.dataloader, {

_load: function () {var deferred = $.Deferred();this.element.data(‘backboneCollection’).fetch({reset: true,success: function (collection) {deferred.resolve(collection.toJSON());},error: function (collection, response) {deferred.reject(response);}});return deferred.promise();}

});

var myTemplate = Handlebars.compile($(‘#myTemplate’).html());$(‘#myDiv’).dataloader({

success: function (results) {this.element.html(myTemplate(results));}

});

Page 29: Getting the Most Out of jQuery Widgets

$.widget(‘abc.dataloader’, {options: {

url: null,success: function (results) {this.element.html(JSON.stringify(results));},// etc

},fetch: function () {

this.element.addClass(‘loading’);return this._load().done($.proxy(function (results) {this.options.success.call(this, results);}, this).always($.proxy(function () {this.element.removeClass(‘loading’);}, this));

},_load: function () {

return $.ajax(this.options);}

});$.widget(‘abc.dataloader’, abc.dataloader, {

_load: function () {var deferred = $.Deferred();this.element.data(‘backboneCollection’).fetch({reset: true,success: function (collection) {deferred.resolve(collection.toJSON());},error: function (collection, response) {deferred.reject(response);}});return deferred.promise();

}});

var myTemplate = Handlebars.compile($(‘#myTemplate’).html());$(‘#myDiv’).dataloader({

success: function (results) {this.element.html(myTemplate(results));

}});

Page 30: Getting the Most Out of jQuery Widgets

$.widget(‘abc.dataloader’, {options: {url: null,success: function (results) {this.element.html(JSON.stringify(results));},// etc},fetch: function () {this.element.addClass(‘loading’);return this._load().done($.proxy(function (results) {this.options.success.call(this, results);}, this).always($.proxy(function () {this.element.removeClass(‘loading’);}, this));},_load: function () {return $.ajax(this.options);}

});$.widget(‘abc.dataloader’, abc.dataloader, {

_load: function () {var deferred = $.Deferred();this.element.data(‘backboneCollection’).fetch({reset: true,success: function (collection) {deferred.resolve(collection.toJSON());},error: function (collection, response) {deferred.reject(response);}});return deferred.promise();}

});

var myTemplate = Handlebars.compile($(‘#myTemplate’).html());$(‘#myDiv’).dataloader({

success: function (results) {this.element.html(myTemplate(results));}

});

Page 31: Getting the Most Out of jQuery Widgets

Make it

testable!

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Page 32: Getting the Most Out of jQuery Widgets

Make it

testable!DOES IT PERFORM A LOGICAL OPERATION OR CALCULATION? / IS IT PART OF THE WIDGET’S PUBLIC-FACING API?

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Page 33: Getting the Most Out of jQuery Widgets

Make it

testable!DOES IT PERFORM A LOGICAL OPERATION OR CALCULATION? / IS IT PART OF THE WIDGET’S PUBLIC-FACING API?

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Page 34: Getting the Most Out of jQuery Widgets

Richard Lindsey @Velveeta http://conqueringtheclient.com/

PUBLIC FUNCTIONS SHOULD HAVE UNIT TESTS / STORE PROTOTYPES IN OBJECT NAMESPACES / TEST LOGICAL FUNCTIONS SEPARATELY

expose it!

Page 35: Getting the Most Out of jQuery Widgets

Richard Lindsey @Velveeta http://conqueringtheclient.com/

PUBLIC FUNCTIONS SHOULD HAVE UNIT TESTS / STORE PROTOTYPES IN OBJECT NAMESPACES / TEST LOGICAL FUNCTIONS SEPARATELY

expose it!

Page 36: Getting the Most Out of jQuery Widgets

Richard Lindsey @Velveeta http://conqueringtheclient.com/

PUBLIC FUNCTIONS SHOULD HAVE UNIT TESTS / STORE PROTOTYPES IN OBJECT NAMESPACES / TEST LOGICAL FUNCTIONS SEPARATELY

expose it!

Page 37: Getting the Most Out of jQuery Widgets

ABC = {};(function ($) {

ABC.Prototypes = ABC.Prototypes || {};ABC.Prototypes.demo = {

_create: function () {if (this. _getInstanceCount() === 1) {this._attachListeners();}},_getInstanceCount: function () {return $(‘:abc-demo’).length;},_attachListeners: function () {$(‘body’).on(‘click.demo’, ‘:abc-demo’, $.proxy(this._clickHandler, this));},_clickHandler: function () {console.log(this._getInstanceCount() + ‘ demo widgets instantiated!’);},destroy: function () {if (this._getInstanceCount() === 1) {$(‘body’).off(‘.demo’);this._super();}}

};$(function () {

$(‘.demo’).demo();});

}(jQuery));(function ($) {

$.each(ABC.Prototypes, function (widgetName, widgetPrototype) {$.widget(‘abc.’ + widgetName, widgetPrototype);

});}(jQuery));

Page 38: Getting the Most Out of jQuery Widgets

ABC = {};(function ($) {

ABC.Prototypes = ABC.Prototypes || {};ABC.Prototypes.demo = {

_create: function () {if (this. _getInstanceCount() === 1) {this._attachListeners();}},_getInstanceCount: function () {return $(‘:abc-demo’).length;},_attachListeners: function () {$(‘body’).on(‘click.demo’, ‘:abc-demo’, $.proxy(this._clickHandler, this));},_clickHandler: function () {console.log(this._getInstanceCount() + ‘ demo widgets instantiated!’);},destroy: function () {if (this._getInstanceCount() === 1) {$(‘body’).off(‘.demo’);this._super();}}

};$(function () {

$(‘.demo’).demo();});

}(jQuery));(function ($) {

$.each(ABC.Prototypes, function (widgetName, widgetPrototype) {$.widget(‘abc.’ + widgetName, widgetPrototype);

});}(jQuery));

Page 39: Getting the Most Out of jQuery Widgets

ABC = {};(function ($) {

ABC.Prototypes = ABC.Prototypes || {};ABC.Prototypes.demo = {

_create: function () {if (this. _getInstanceCount() === 1) {this._attachListeners();}},_getInstanceCount: function () {return $(‘:abc-demo’).length;},_attachListeners: function () {$(‘body’).on(‘click.demo’, ‘:abc-demo’, $.proxy(this._clickHandler, this));},_clickHandler: function () {console.log(this._getInstanceCount() + ‘ demo widgets instantiated!’);},destroy: function () {if (this._getInstanceCount() === 1) {$(‘body’).off(‘.demo’);this._super();}}

};$(function () {

$(‘.demo’).demo();});

}(jQuery));(function ($) {

$.each(ABC.Prototypes, function (widgetName, widgetPrototype) {$.widget(‘abc.’ + widgetName, widgetPrototype);

});}(jQuery));

Page 40: Getting the Most Out of jQuery Widgets

module(‘demo core’);

test(‘_getInstanceCount’, 2, function () {var container = $(‘<div></div>’).appendTo(‘body’),

deferred = $.Deferred(),myDemo;

stop();

deferred.done(function (instanceCount) {equal(instanceCount, 1, ‘Returns proper value with 1 instance’);}).fail(function () {ok(false, ‘Returns proper value with 1 instance’);}).always(function () {container.remove();start();});

equal(ABC.Prototypes.demo._getInstanceCount(), 0, ‘Returns proper value with no instances’);

myDemo = $(‘<div></div>’).appendTo(container).on(‘democreate’, function () {deferred.resolve(ABC.Prototypes.demo._getInstanceCount());}).demo();

setTimeout(function () {deferred.reject();

}, 250);});

Page 41: Getting the Most Out of jQuery Widgets

module(‘demo core’);

test(‘_getInstanceCount’, 2, function () {var container = $(‘<div></div>’).appendTo(‘body’),

deferred = $.Deferred(),myDemo;

stop();

deferred.done(function (instanceCount) {equal(instanceCount, 1, ‘Returns proper value with 1 instance’);}).fail(function () {ok(false, ‘Returns proper value with 1 instance’);}).always(function () {container.remove();start();});

equal(ABC.Prototypes.demo._getInstanceCount(), 0, ‘Returns proper value with no instances’);

myDemo = $(‘<div></div>’).appendTo(container).on(‘democreate’, function () {deferred.resolve(ABC.Prototypes.demo._getInstanceCount());}).demo();

setTimeout(function () {deferred.reject();

}, 250);});

Page 42: Getting the Most Out of jQuery Widgets

module(‘demo core’);

test(‘_getInstanceCount’, 2, function () {var container = $(‘<div></div>’).appendTo(‘body’),

deferred = $.Deferred(),myDemo;

stop();

deferred.done(function (instanceCount) {equal(instanceCount, 1, ‘Returns proper value with 1 instance’);}).fail(function () {ok(false, ‘Returns proper value with 1 instance’);}).always(function () {container.remove();start();});

equal(ABC.Prototypes.demo._getInstanceCount(), 0, ‘Returns proper value with no instances’);

myDemo = $(‘<div></div>’).appendTo(container).on(‘democreate’, function () {deferred.resolve(ABC.Prototypes.demo._getInstanceCount());}).demo();

setTimeout(function () {deferred.reject();

}, 250);});

Page 43: Getting the Most Out of jQuery Widgets

module(‘demo core’);

test(‘_getInstanceCount’, 2, function () {var container = $(‘<div></div>’).appendTo(‘body’),

deferred = $.Deferred(),myDemo;

stop();

deferred.done(function (instanceCount) {equal(instanceCount, 1, ‘Returns proper value with 1 instance’);}).fail(function () {ok(false, ‘Returns proper value with 1 instance’);}).always(function () {container.remove();start();});

equal(ABC.Prototypes.demo._getInstanceCount(), 0, ‘Returns proper value with no instances’);

myDemo = $(‘<div></div>’).appendTo(container).on(‘democreate’, function () {deferred.resolve(ABC.Prototypes.demo._getInstanceCount());}).demo();

setTimeout(function () {deferred.reject();

}, 250);});

Page 44: Getting the Most Out of jQuery Widgets

Wrap it up already, will ya?

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Page 45: Getting the Most Out of jQuery Widgets

ONLY MAKE COMPONENTS AS LARGE AS THEY NEED TO BE / KEEP THEM AS DECOUPLED AS POSSIBLE / CONSUME DOWNWARDS, COMMUNICATE UPWARDS

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Wrap it up

already…

Page 46: Getting the Most Out of jQuery Widgets

ONLY MAKE COMPONENTS AS LARGE AS THEY NEED TO BE / KEEP THEM AS DECOUPLED AS POSSIBLE / CONSUME DOWNWARDS, COMMUNICATE UPWARDS

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Wrap it up

already…

Page 47: Getting the Most Out of jQuery Widgets

ONLY MAKE COMPONENTS AS LARGE AS THEY NEED TO BE / KEEP THEM AS DECOUPLED AS POSSIBLE / CONSUME DOWNWARDS, COMMUNICATE UPWARDS

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Wrap it up

already…

Page 48: Getting the Most Out of jQuery Widgets

DECORATE THE FACTORY, BUT BE CAREFUL ABOUT TYING TO IMPLEMENTATIONS.

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Wrap it up

already…

Page 49: Getting the Most Out of jQuery Widgets

MAKE FUNCTIONS & OPTIONS GRANULAR AND ROBUST FOR POTENTIAL OVERRIDES.

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Wrap it up

already…

Page 50: Getting the Most Out of jQuery Widgets

TEST, TEST, AND TEST! MAKE EVERY ATTEMPT TO ENSURE BACKWARD COMPATIBILITY FOR CONSUMERS.

Richard Lindsey @Velveeta http://conqueringtheclient.com/

Wrap it up

already…

Page 51: Getting the Most Out of jQuery Widgets

thanks!Presentation available online: http://bit.ly/jqwidgets

Richard Lindsey @velveeta http://conqueringtheclient.com/PLATFORM FEE ARCHITECT | THE ADVISORY BOARD COMPANY