Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

Getting the Most Out of jQuery Widgets

4 926 vues

Publié le

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

Publié dans : Technologie, Business
  • Dating direct: ❶❶❶ http://bit.ly/369VOVb ❶❶❶
       Répondre 
    Voulez-vous vraiment ?  Oui  Non
    Votre message apparaîtra ici
  • Dating for everyone is here: ❤❤❤ http://bit.ly/369VOVb ❤❤❤
       Répondre 
    Voulez-vous vraiment ?  Oui  Non
    Votre message apparaîtra ici
  • Soyez le premier à aimer ceci

Getting the Most Out of jQuery Widgets

  1. 1. Richard Lindsey @Velveeta http://conqueringtheclient.com/ PLATFORM FEE ARCHITECT | THE ADVISORY BOARD COMPANY jQuery Widgets GETTING THE MOST OUT OF
  2. 2. Let’s say we’re making Widgets… Richard Lindsey @Velveeta http://conqueringtheclient.com/
  3. 3. What’s a Widget? Richard Lindsey @Velveeta http://conqueringtheclient.com/
  4. 4. ELEMENTS / COMPOUNDS /CELLS / ORGANISMS Richard Lindsey @Velveeta http://conqueringtheclient.com/ Think small. Think modular.
  5. 5. Communicate through events. KEEP COMPONENTS DECOUPLED / MAKE THEM SUBSCRIBE AND RESPOND Richard Lindsey @Velveeta http://conqueringtheclient.com/
  6. 6. Communicate through events. KEEP COMPONENTS DECOUPLED / MAKE THEM SUBSCRIBE AND RESPOND Richard Lindsey @Velveeta http://conqueringtheclient.com/
  7. 7. 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/
  8. 8. 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/
  9. 9. 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/
  10. 10. 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/
  11. 11. $.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(); });
  12. 12. $.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(); });
  13. 13. $.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(); });
  14. 14. $.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(); });
  15. 15. $.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(); });
  16. 16. Richard Lindsey @Velveeta http://conqueringtheclient.com/ BAD IDEA AHEAD
  17. 17. Decorate ALL the functions! Richard Lindsey @Velveeta http://conqueringtheclient.com/
  18. 18. Richard Lindsey @Velveeta http://conqueringtheclient.com/ MODIFY THE FACTORY FUNCTION IF YOU NEED TO Decorate ALL the functions!
  19. 19. 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; });
  20. 20. 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; });
  21. 21. 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; });
  22. 22. 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; });
  23. 23. 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; });
  24. 24. Richard Lindsey @Velveeta http://conqueringtheclient.com/ ALWAYS TRY TO USE PUBLIC API FOR FORWARD COMPATIBILITY Decorate ALL the functions!
  25. 25. Richard Lindsey @Velveeta http://conqueringtheclient.com/ WHO CARES ABOUT INTERNAL IMPLEMENTATIONS? Feel free to mix it up.
  26. 26. 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
  27. 27. $.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)); } });
  28. 28. $.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)); } });
  29. 29. $.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)); } });
  30. 30. $.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)); } });
  31. 31. Make it testable! Richard Lindsey @Velveeta http://conqueringtheclient.com/
  32. 32. 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/
  33. 33. 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/
  34. 34. Richard Lindsey @Velveeta http://conqueringtheclient.com/ PUBLIC FUNCTIONS SHOULD HAVE UNIT TESTS / STORE PROTOTYPES IN OBJECT NAMESPACES / TEST LOGICAL FUNCTIONS SEPARATELY expose it!
  35. 35. Richard Lindsey @Velveeta http://conqueringtheclient.com/ PUBLIC FUNCTIONS SHOULD HAVE UNIT TESTS / STORE PROTOTYPES IN OBJECT NAMESPACES / TEST LOGICAL FUNCTIONS SEPARATELY expose it!
  36. 36. Richard Lindsey @Velveeta http://conqueringtheclient.com/ PUBLIC FUNCTIONS SHOULD HAVE UNIT TESTS / STORE PROTOTYPES IN OBJECT NAMESPACES / TEST LOGICAL FUNCTIONS SEPARATELY expose it!
  37. 37. 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));
  38. 38. 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));
  39. 39. 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));
  40. 40. 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); });
  41. 41. 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); });
  42. 42. 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); });
  43. 43. 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); });
  44. 44. Wrap it up already, will ya? Richard Lindsey @Velveeta http://conqueringtheclient.com/
  45. 45. 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…
  46. 46. 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…
  47. 47. 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…
  48. 48. DECORATE THE FACTORY, BUT BE CAREFUL ABOUT TYING TO IMPLEMENTATIONS. Richard Lindsey @Velveeta http://conqueringtheclient.com/ Wrap it up already…
  49. 49. MAKE FUNCTIONS & OPTIONS GRANULAR AND ROBUST FOR POTENTIAL OVERRIDES. Richard Lindsey @Velveeta http://conqueringtheclient.com/ Wrap it up already…
  50. 50. TEST, TEST, AND TEST! MAKE EVERY ATTEMPT TO ENSURE BACKWARD COMPATIBILITY FOR CONSUMERS. Richard Lindsey @Velveeta http://conqueringtheclient.com/ Wrap it up already…
  51. 51. thanks! Presentation available online: http://bit.ly/jqwidgets Richard Lindsey @velveeta http://conqueringtheclient.com/ PLATFORM FEE ARCHITECT | THE ADVISORY BOARD COMPANY

×