SlideShare ist ein Scribd-Unternehmen logo
1 von 51
Richard Lindsey @Velveeta http://conqueringtheclient.com/
PLATFORM FEE ARCHITECT | THE ADVISORY BOARD COMPANY
jQuery
Widgets
GETTING THE MOST OUT OF
Let’s say
we’re making
Widgets…
Richard Lindsey @Velveeta http://conqueringtheclient.com/
What’s a
Widget?
Richard Lindsey @Velveeta http://conqueringtheclient.com/
ELEMENTS / COMPOUNDS
/CELLS / ORGANISMS
Richard Lindsey @Velveeta http://conqueringtheclient.com/
Think
small.
Think
modular.
Communicate through
events.
KEEP COMPONENTS DECOUPLED
/ MAKE THEM SUBSCRIBE AND
RESPOND
Richard Lindsey @Velveeta http://conqueringtheclient.com/
Communicate through
events.
KEEP COMPONENTS DECOUPLED
/ MAKE THEM SUBSCRIBE AND
RESPOND
Richard Lindsey @Velveeta http://conqueringtheclient.com/
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/
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/
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/
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/
$.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();
});
$.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();
});
$.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();
});
$.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();
});
$.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();
});
Richard Lindsey @Velveeta http://conqueringtheclient.com/
BAD IDEA
AHEAD
Decorate ALL the
functions!
Richard Lindsey @Velveeta http://conqueringtheclient.com/
Richard Lindsey @Velveeta http://conqueringtheclient.com/
MODIFY THE FACTORY FUNCTION
IF YOU NEED TO
Decorate ALL the
functions!
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;
});
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;
});
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;
});
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;
});
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;
});
Richard Lindsey @Velveeta http://conqueringtheclient.com/
ALWAYS TRY TO USE PUBLIC API
FOR FORWARD COMPATIBILITY
Decorate ALL the
functions!
Richard Lindsey @Velveeta http://conqueringtheclient.com/
WHO CARES ABOUT INTERNAL
IMPLEMENTATIONS?
Feel free to
mix it up.
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
$.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));
}
});
$.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));
}
});
$.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));
}
});
$.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));
}
});
Make it
testable!
Richard Lindsey @Velveeta http://conqueringtheclient.com/
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/
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/
Richard Lindsey @Velveeta http://conqueringtheclient.com/
PUBLIC FUNCTIONS SHOULD
HAVE UNIT TESTS / STORE
PROTOTYPES IN OBJECT
NAMESPACES / TEST LOGICAL
FUNCTIONS SEPARATELY
expose it!
Richard Lindsey @Velveeta http://conqueringtheclient.com/
PUBLIC FUNCTIONS SHOULD
HAVE UNIT TESTS / STORE
PROTOTYPES IN OBJECT
NAMESPACES / TEST LOGICAL
FUNCTIONS SEPARATELY
expose it!
Richard Lindsey @Velveeta http://conqueringtheclient.com/
PUBLIC FUNCTIONS SHOULD
HAVE UNIT TESTS / STORE
PROTOTYPES IN OBJECT
NAMESPACES / TEST LOGICAL
FUNCTIONS SEPARATELY
expose it!
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));
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));
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));
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);
});
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);
});
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);
});
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);
});
Wrap it up
already,
will ya?
Richard Lindsey @Velveeta http://conqueringtheclient.com/
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…
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…
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…
DECORATE THE FACTORY, BUT BE
CAREFUL ABOUT TYING TO
IMPLEMENTATIONS.
Richard Lindsey @Velveeta http://conqueringtheclient.com/
Wrap it up
already…
MAKE FUNCTIONS & OPTIONS
GRANULAR AND ROBUST FOR
POTENTIAL OVERRIDES.
Richard Lindsey @Velveeta http://conqueringtheclient.com/
Wrap it up
already…
TEST, TEST, AND TEST! MAKE
EVERY ATTEMPT TO ENSURE
BACKWARD COMPATIBILITY FOR
CONSUMERS.
Richard Lindsey @Velveeta http://conqueringtheclient.com/
Wrap it up
already…
thanks!
Presentation available online: http://bit.ly/jqwidgets
Richard Lindsey @velveeta http://conqueringtheclient.com/
PLATFORM FEE ARCHITECT | THE ADVISORY BOARD COMPANY

Weitere ähnliche Inhalte

Was ist angesagt?

Unobtrusive javascript with jQuery
Unobtrusive javascript with jQueryUnobtrusive javascript with jQuery
Unobtrusive javascript with jQuery
Angel Ruiz
 
Stack Overflow Austin - jQuery for Developers
Stack Overflow Austin - jQuery for DevelopersStack Overflow Austin - jQuery for Developers
Stack Overflow Austin - jQuery for Developers
Jonathan Sharp
 
jQuery Anti-Patterns for Performance & Compression
jQuery Anti-Patterns for Performance & CompressionjQuery Anti-Patterns for Performance & Compression
jQuery Anti-Patterns for Performance & Compression
Paul Irish
 

Was ist angesagt? (20)

Unobtrusive javascript with jQuery
Unobtrusive javascript with jQueryUnobtrusive javascript with jQuery
Unobtrusive javascript with jQuery
 
Write Less Do More
Write Less Do MoreWrite Less Do More
Write Less Do More
 
Jquery
JqueryJquery
Jquery
 
jQuery for beginners
jQuery for beginnersjQuery for beginners
jQuery for beginners
 
jQuery
jQueryjQuery
jQuery
 
jQuery in 15 minutes
jQuery in 15 minutesjQuery in 15 minutes
jQuery in 15 minutes
 
jQuery
jQueryjQuery
jQuery
 
jQuery('#knowledge').appendTo('#you');
jQuery('#knowledge').appendTo('#you');jQuery('#knowledge').appendTo('#you');
jQuery('#knowledge').appendTo('#you');
 
Introduction to jQuery
Introduction to jQueryIntroduction to jQuery
Introduction to jQuery
 
jQuery PPT
jQuery PPTjQuery PPT
jQuery PPT
 
Jquery Complete Presentation along with Javascript Basics
Jquery Complete Presentation along with Javascript BasicsJquery Complete Presentation along with Javascript Basics
Jquery Complete Presentation along with Javascript Basics
 
jQuery Performance Tips and Tricks (2011)
jQuery Performance Tips and Tricks (2011)jQuery Performance Tips and Tricks (2011)
jQuery Performance Tips and Tricks (2011)
 
jQuery Introduction
jQuery IntroductionjQuery Introduction
jQuery Introduction
 
JavaScript and jQuery Basics
JavaScript and jQuery BasicsJavaScript and jQuery Basics
JavaScript and jQuery Basics
 
Stack Overflow Austin - jQuery for Developers
Stack Overflow Austin - jQuery for DevelopersStack Overflow Austin - jQuery for Developers
Stack Overflow Austin - jQuery for Developers
 
SharePoint and jQuery Essentials
SharePoint and jQuery EssentialsSharePoint and jQuery Essentials
SharePoint and jQuery Essentials
 
jQuery
jQueryjQuery
jQuery
 
jQuery Anti-Patterns for Performance & Compression
jQuery Anti-Patterns for Performance & CompressionjQuery Anti-Patterns for Performance & Compression
jQuery Anti-Patterns for Performance & Compression
 
jQuery basics
jQuery basicsjQuery basics
jQuery basics
 
Prototype & jQuery
Prototype & jQueryPrototype & jQuery
Prototype & jQuery
 

Andere mochten auch

jQuery UI & Mobile - The Great Merger
jQuery UI & Mobile - The Great MergerjQuery UI & Mobile - The Great Merger
jQuery UI & Mobile - The Great Merger
scottgonzalez
 
jQuery Foundation Keynote
jQuery Foundation KeynotejQuery Foundation Keynote
jQuery Foundation Keynote
Richard Worth
 
jQuery Conference San Diego 2014 - Web Performance
jQuery Conference San Diego 2014 - Web PerformancejQuery Conference San Diego 2014 - Web Performance
jQuery Conference San Diego 2014 - Web Performance
dmethvin
 
[jqconatx] Adaptive Images for Responsive Web Design
[jqconatx] Adaptive Images for Responsive Web Design[jqconatx] Adaptive Images for Responsive Web Design
[jqconatx] Adaptive Images for Responsive Web Design
Christopher Schmitt
 
Sizzle jQCon San Francisco 2012
Sizzle jQCon San Francisco 2012Sizzle jQCon San Francisco 2012
Sizzle jQCon San Francisco 2012
livelogos
 
jQuery Conference 2012 keynote
jQuery Conference 2012 keynotejQuery Conference 2012 keynote
jQuery Conference 2012 keynote
dmethvin
 

Andere mochten auch (12)

jQuery Conference Austin Sept 2013
jQuery Conference Austin Sept 2013jQuery Conference Austin Sept 2013
jQuery Conference Austin Sept 2013
 
jQuery UI & Mobile - The Great Merger
jQuery UI & Mobile - The Great MergerjQuery UI & Mobile - The Great Merger
jQuery UI & Mobile - The Great Merger
 
jQuery Austin 2013 - Building a Development Culture
jQuery Austin 2013 - Building a Development CulturejQuery Austin 2013 - Building a Development Culture
jQuery Austin 2013 - Building a Development Culture
 
jQuery Foundation Keynote
jQuery Foundation KeynotejQuery Foundation Keynote
jQuery Foundation Keynote
 
jQuery Conference San Diego 2014 - Web Performance
jQuery Conference San Diego 2014 - Web PerformancejQuery Conference San Diego 2014 - Web Performance
jQuery Conference San Diego 2014 - Web Performance
 
Transforming Front-End Disaster Code™ Into A Maintainable Masterpiece
Transforming Front-End Disaster Code™ Into A Maintainable MasterpieceTransforming Front-End Disaster Code™ Into A Maintainable Masterpiece
Transforming Front-End Disaster Code™ Into A Maintainable Masterpiece
 
Real World Web components
Real World Web componentsReal World Web components
Real World Web components
 
jQuery Chicago 2014 - Next-generation JavaScript Testing
jQuery Chicago 2014 - Next-generation JavaScript TestingjQuery Chicago 2014 - Next-generation JavaScript Testing
jQuery Chicago 2014 - Next-generation JavaScript Testing
 
New Perspectives on Performance
New Perspectives on PerformanceNew Perspectives on Performance
New Perspectives on Performance
 
[jqconatx] Adaptive Images for Responsive Web Design
[jqconatx] Adaptive Images for Responsive Web Design[jqconatx] Adaptive Images for Responsive Web Design
[jqconatx] Adaptive Images for Responsive Web Design
 
Sizzle jQCon San Francisco 2012
Sizzle jQCon San Francisco 2012Sizzle jQCon San Francisco 2012
Sizzle jQCon San Francisco 2012
 
jQuery Conference 2012 keynote
jQuery Conference 2012 keynotejQuery Conference 2012 keynote
jQuery Conference 2012 keynote
 

Ähnlich wie Getting the Most Out of jQuery Widgets

Ähnlich wie Getting the Most Out of jQuery Widgets (20)

Clean Javascript
Clean JavascriptClean Javascript
Clean Javascript
 
Building complex User Interfaces with Sitecore and React
Building complex User Interfaces with Sitecore and ReactBuilding complex User Interfaces with Sitecore and React
Building complex User Interfaces with Sitecore and React
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
 
WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015
 
Building Large jQuery Applications
Building Large jQuery ApplicationsBuilding Large jQuery Applications
Building Large jQuery Applications
 
Using and reusing CakePHP plugins
Using and reusing CakePHP pluginsUsing and reusing CakePHP plugins
Using and reusing CakePHP plugins
 
Unittests für Dummies
Unittests für DummiesUnittests für Dummies
Unittests für Dummies
 
Bacbkone js
Bacbkone jsBacbkone js
Bacbkone js
 
Magento Live Australia 2016: Request Flow
Magento Live Australia 2016: Request FlowMagento Live Australia 2016: Request Flow
Magento Live Australia 2016: Request Flow
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
Building Lithium Apps
Building Lithium AppsBuilding Lithium Apps
Building Lithium Apps
 
ngMess: AngularJS Dependency Injection
ngMess: AngularJS Dependency InjectionngMess: AngularJS Dependency Injection
ngMess: AngularJS Dependency Injection
 
Saindo da zona de conforto… resolvi aprender android
Saindo da zona de conforto… resolvi aprender androidSaindo da zona de conforto… resolvi aprender android
Saindo da zona de conforto… resolvi aprender android
 
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8
 
Chaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscoreChaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscore
 
Rails is not just Ruby
Rails is not just RubyRails is not just Ruby
Rails is not just Ruby
 
Understanding backbonejs
Understanding backbonejsUnderstanding backbonejs
Understanding backbonejs
 
Aplicacoes dinamicas Rails com Backbone
Aplicacoes dinamicas Rails com BackboneAplicacoes dinamicas Rails com Backbone
Aplicacoes dinamicas Rails com Backbone
 
Extend sdk
Extend sdkExtend sdk
Extend sdk
 
Backbone.js — Introduction to client-side JavaScript MVC
Backbone.js — Introduction to client-side JavaScript MVCBackbone.js — Introduction to client-side JavaScript MVC
Backbone.js — Introduction to client-side JavaScript MVC
 

Kürzlich hochgeladen

+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
?#DUbAI#??##{{(☎️+971_581248768%)**%*]'#abortion pills for sale in dubai@
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Victor Rentea
 

Kürzlich hochgeladen (20)

Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
Artificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyArtificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : Uncertainty
 
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamDEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
 
Spring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUKSpring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUK
 
AXA XL - Insurer Innovation Award Americas 2024
AXA XL - Insurer Innovation Award Americas 2024AXA XL - Insurer Innovation Award Americas 2024
AXA XL - Insurer Innovation Award Americas 2024
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
 
MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
 
Ransomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdfRansomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdf
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challenges
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
 
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
 

Getting the Most Out of jQuery Widgets

  • 1. Richard Lindsey @Velveeta http://conqueringtheclient.com/ PLATFORM FEE ARCHITECT | THE ADVISORY BOARD COMPANY jQuery Widgets GETTING THE MOST OUT OF
  • 2. Let’s say we’re making Widgets… Richard Lindsey @Velveeta http://conqueringtheclient.com/
  • 3. What’s a Widget? Richard Lindsey @Velveeta http://conqueringtheclient.com/
  • 4. ELEMENTS / COMPOUNDS /CELLS / ORGANISMS Richard Lindsey @Velveeta http://conqueringtheclient.com/ Think small. Think modular.
  • 5. Communicate through events. KEEP COMPONENTS DECOUPLED / MAKE THEM SUBSCRIBE AND RESPOND Richard Lindsey @Velveeta http://conqueringtheclient.com/
  • 6. Communicate through events. KEEP COMPONENTS DECOUPLED / MAKE THEM SUBSCRIBE AND RESPOND Richard Lindsey @Velveeta http://conqueringtheclient.com/
  • 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. 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. 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. 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. $.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. $.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. $.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. $.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. $.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. Richard Lindsey @Velveeta http://conqueringtheclient.com/ BAD IDEA AHEAD
  • 17. Decorate ALL the functions! Richard Lindsey @Velveeta http://conqueringtheclient.com/
  • 18. Richard Lindsey @Velveeta http://conqueringtheclient.com/ MODIFY THE FACTORY FUNCTION IF YOU NEED TO Decorate ALL the functions!
  • 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. 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. 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. 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. 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. Richard Lindsey @Velveeta http://conqueringtheclient.com/ ALWAYS TRY TO USE PUBLIC API FOR FORWARD COMPATIBILITY Decorate ALL the functions!
  • 25. Richard Lindsey @Velveeta http://conqueringtheclient.com/ WHO CARES ABOUT INTERNAL IMPLEMENTATIONS? Feel free to mix it up.
  • 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. $.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. $.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. $.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. $.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. Make it testable! Richard Lindsey @Velveeta http://conqueringtheclient.com/
  • 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. 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. Richard Lindsey @Velveeta http://conqueringtheclient.com/ PUBLIC FUNCTIONS SHOULD HAVE UNIT TESTS / STORE PROTOTYPES IN OBJECT NAMESPACES / TEST LOGICAL FUNCTIONS SEPARATELY expose it!
  • 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. Richard Lindsey @Velveeta http://conqueringtheclient.com/ PUBLIC FUNCTIONS SHOULD HAVE UNIT TESTS / STORE PROTOTYPES IN OBJECT NAMESPACES / TEST LOGICAL FUNCTIONS SEPARATELY expose it!
  • 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. 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. 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. 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. 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. 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. 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. Wrap it up already, will ya? Richard Lindsey @Velveeta http://conqueringtheclient.com/
  • 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. 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. 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. DECORATE THE FACTORY, BUT BE CAREFUL ABOUT TYING TO IMPLEMENTATIONS. Richard Lindsey @Velveeta http://conqueringtheclient.com/ Wrap it up already…
  • 49. MAKE FUNCTIONS & OPTIONS GRANULAR AND ROBUST FOR POTENTIAL OVERRIDES. Richard Lindsey @Velveeta http://conqueringtheclient.com/ Wrap it up already…
  • 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. thanks! Presentation available online: http://bit.ly/jqwidgets Richard Lindsey @velveeta http://conqueringtheclient.com/ PLATFORM FEE ARCHITECT | THE ADVISORY BOARD COMPANY

Hinweis der Redaktion

  1. Hi, I’m Richard Lindsey, a front-end architect working for the Advisory Board Company here in Austin. We’re a healthcare consulting company that deals a lot with metrics and regurgitating data out onto the screen in various forms, and we utilize the jQuery widget factory pretty extensively to that end. If you’re not familiar w/ the widget factory, jQuery UI is a separate add-on library, based on and extending jQuery, and providing a pretty nice factory method to create your own library of visual widget components. I’m here to share some tips and tricks on working with that factory and in general component architecture… So…
  2. Let’s say we’re making widgets…
  3. What’s a Widget? In the context of front-end engineering, it’s simply a modular, reusable, self-contained package that handles the visualization of some UI component, as well as any behavior and logic that’s part of that component’s interaction… Think of things like typeaheadautocompleters, or split buttons, or tabbed views, or any number of other things, and those can all be thought of as widgetized components… So what kinds of things should we be considering when we’re developing these components?
  4. Think small… Think modular… You should be trying to build your components so that they fulfill a single, specific need. Anything too complex should be broken down into smaller components that can be used in other facets of your application… Consider the autocompleter: it could be made up of, at a minimum, an input element, some form of data-fetching utility, whether that’s via ajax or some client-side data set, and something to render the suggestions in a dropdown format… If you build all of that into 1 widget, you can’t reuse those bits and pieces in anything else… So whenever possible, think as small as possible… This is an analogy for how your widgets should kind of come together. You can think of the most basic components: text boxes, checkboxes, select elements, overlays, whatever, as elements. From there, you can start coupling them to other elements to form larger and more complex compound components… At that point, you can start thinking larger, about how compound widgets can come together to form cells and organisms, being synonymous with the workflow of an entire page, or even of an entire application…
  5. You should be trying to keep your components as directly decoupled as possible. What does this mean? Well, in our autocompleter example, when something’s typed into the textbox, we can have the input itself tell the data-loader “hey, I have updated data for you to provde me matches against”, and the data-loader can fetch them and render the dropdown list itself, and when something’s selected from it, the dropdown can tell the input “here’s the complete string you should now populate yourself with”… However, that requires these components having direct knowledge of each other everywhere you want to do something like this… Instead, you should make these individual pieces broadcast events whenever they have updates that other interested parties might want to know about…
  6. Now, once you have them broadcasting events, you can have them subscribing to each others’ events, and feeding data to them, but that still doesn’t solve the problem of them being directly coupled to each other…
  7. You should be bundling these smaller modules up and wrapping them in some kind of parent widget that can play traffic cop between them all… The parent should be responsible for subscribing to their events, and for figuring out what needs to be done with that data: whether it should use it directly somehow, or funnel it into one of those child widgets to be acted upon…
  8. You should also be providing some kind of public API on all of your widgets that you can use for calling into them and handing off data. All jQuery UI widgets have at least a partial API provided by the factory: enable and disable, option, widget, and destroy... Whatever purpose your widget serves should have a robust enough API that nobody has to try to hack their way in through the instance data that’s bound to that element, as I’m sure many of you have had to do in the past…
  9. Always try to keep in mind that dependency relationship… None of the element-level components are dependent on the parent, it’s the parent that’s dependent on them… As such, any direct references to those widgets should be done in a top-down fashion, with the parent referencing its children and not the other way around…
  10. Here, I say each layer should listen for events, because you never know when some other widget is going to be consuming a parent widget you’re writing here… That’s the beauty of this design methodology, you don’t really have to care… You just worry about what you’re listening for from your dependencies, and anytime you have something interesting to publish, throw it out into the world as a new event of your own, and other consumers can now consume those events, with your widget never knowing who’s listening, and never having to write specific logic to deal with it…
  11. Here’s a simple example of what we might find in an autocomplete component…
  12. We declare some dependencies on other, smaller widgets…
  13. We then bind listeners to those child widgets…
  14. We supply a couple of functions for interfacing directly with this widget from higher levels…
  15. And finally, we set up some event triggers of our own, to broadcast things out into the world… So here’s what a sample implementation could look like in the browser…
  16. Now, so far this whole talk has been about keeping things nice and abstracted, using public API’s so you don’t have to concern yourself with how things are implemented internally… For the next 2 minutes, I want you to throw out everything I’ve said… See, all those things I’ve been saying, those are the way things work in an ideal world, and there are times when I’m sure we’ve all had to break the rules of good development patterns to satisfy some business need…
  17. I found myself having need of supplying our product’s consumers with a more robust event-publishing system… There are times our developers wanted to be alerted to when a widget had built some dropdown list, or some other internal mechanism had changed, and we just didn’t provide that many events… Our product releases are on a monthly schedule, meaning that when they found a need for something we weren’t currently publishing, their request would have to wait up to a month, *if* we were able to fit that feature request into our current release… Instead, I decided to start decorating all of the prototypes for all of our published widgets, so that they could tell the widget to start auto-publishing an event before or after any function it used… Now we can still handle custom event requests that may include extra data being passed as part of an official custom event request, but the majority of those requests can be handled by just using this new event auto-publishing feature…
  18. So, if you ever have need of decorating an entire prototype somehow, you should do it before it’s run through the factory, meaning you’ll have to decorate the factory function itself to keep this process as transparent as possible…
  19. Here’s an example of how we might decide to decorate our factory function…
  20. Here you can see us overwriting the widget factory function and creating a new version of it to used…
  21. We iterate over our prototype methods, surround the execution of each one with a couple of logical checks, and fire some functions if they pass…
  22. We also store off the return value of our original function, to return at the end of all that…
  23. We then pass our decorated prototype through the original factory method to create the widget class definition, and port any extra properties over to our new factory function… There are a couple of extra functions that are stored as properties on the factory function object, like extend and bridge, so make sure you don’t forget this step or you *will* run into errors…
  24. While this does allow people to tie event publishers to internal private functions, I can’t state strongly enough that you should avoid that development practice whenever possible… Always try to use the public API for forward compatibility, because you never know when internal implementations are going to change from one release to another… That public API acts as a sort of contract between the publisher and consumers of a widget, which should stay as consistent as possible from release to release, but the internal private functions have no such guarantee…
  25. As long as your function interfaces are consistent, meaning the input and output formats stay the same, does it really matter how the guts of that function are implemented? Not really… And if we want to swap out functional implementations, we have a couple of different options for doing so…
  26. The widget factory itself allows you to override any method, private or not… One nice thing about this method is that if directly affects the widget’s prototype, and any instances that are already created… It also maintains a pointer back to the original version of that function, which can call with the super and superApply methods, which allows us to simulate some actual inheritance, which is awesome… The other method is to expose some of your functions as options that can be modified at runtime… This only affects the specific instance you’re modifying that option on… This allows for a lot more flexibility in the behavior of your widgets…
  27. So, here’s an example where we have a generic dataloader widget that just adds a loading class to the element, which can be used to modify the mouse cursor to a busy state, or white out the area being loaded, or whatever… Let’s say we wanted to keep that same behavior for all ajax functionality, but allow Backbone to manage the fetching of our collection data…
  28. As you can see, our fetch function takes no inputs, and returns the promise object that’s coming from the $.ajax function…
  29. This means that we can integrate Backbone’s fetch method by creating our own deferred object, running our fetch, and returning that deferred’s promise… Whenever our success or error functions are called from Backbone, we resolve or reject as appropriate, and if resolving, pass our collection data to any callbacks that have been bound to that promise, and business proceeds as normal…
  30. You’ll also see that we have a success function option we’ve added here, which by default just outputs a JSON string of our result set from that ajax request, but which we can override when we instantiate this widget, to have it run those results through a Handlebars template for output instead…
  31. Now, how many of you know that you should be unittesting your front-end code? …Ok, so, how many of you *actually* unit test your front-end code?We should be writing our code in such a way that it *is* testable… That means you should try to limit the amount of anonymous functions you’re passing back and forth…
  32. Does a function perform some kind of logical operation, or calculation? If so, you may not even need to create an instance of your widget to test that…
  33. Is it part of your widget’s public API, such that your consumers are going to expect a level of continuity from release to release? This one is especially important… And if you’re designing your interface to expect certain inputs and give back certain outputs, you should be able to easily test those conditions…
  34. All public functions should have unit tests written to ensure that they continue to function property from release to release. This is going to be one of the biggest indicators of code quality from the perspective of your consumers… If they can’t upgrade your package without having to refactor their code and test for bugs stemming from broken interfaces, one of 2 things is going to happen… They’ll look for another, more reliable solution, or write one themselves… Or, they’ll stop updating your package, and at some point, they’ll probably look for a different solution anyway…
  35. Consider storing your widget prototypes in an object namespace, and pass that into the factory, instead of passing an object literal directly. That way you maintain a reference to all of those functions outside of having to actually create an instance of your widget…
  36. If a function is strictly a logical check or a calculation, you may be able to test that completely outside the scope of a widget instance, which can make speed up your test runs, as well as decoupling that function’s logic from the context of the widget itself… Decoupling is a good thing, right?
  37. Here’s a simple widget class that just checks to see if it’s the first of its kind, and if so, adds a click handler to the body that outputs the number of these widgets on the page…
  38. First, you’ll notice that we store the prototype object in this ABC.Prototypes namespace, and iterate over that namespace to create our widget classes…
  39. This getInstanceCount does nothing more than scrape the DOM to find the number of these widgets in existence, and return that… It doesn’t use any internal references to do this, and doesn’t need to be run in the context of a widget in order to do its job… And it’s called from a few different places, so you’ll want to make sure this thing doesn’t get broken at some point, which means you’ll want to test it…
  40. Here’s a simple test we could write for it…
  41. We first call this function directly from our ABC.Prototypes namespace, since we don’t yet have any instances spun up, and we expect it to return a count of 0…
  42. We add a create event handler, and instantiate the widget… We execute the getInstanceCount function again and expect it to return 1, and then continue as normal…
  43. We also hedge our bets a little bit here, in case anything goes wrong with the widget creation process… We set a quarter-second timeout to auto-fail the test, since something’s gone wrong, and then let the test engine continue… You would also want to write tests to verify that your click handler is functioning as expected, and of course any public functions you’re providing…
  44. So let me kind of summarize some of the biggest points here…
  45. This one should be obvious, nobody likes doing more work than they have to… Your components shouldn’t do more work than they have to either… Keep them as small as you can while still allowing them to perform the task they’re meant to do…
  46. Good component design means keeping those components unaware of each others’ existence, except when dealing with dependencies…
  47. Higher-level widgets should consume the events and public functions of their dependencies, and broadcast their own events out into the ether for whoever may be consuming them…
  48. If you have to decorate entire prototypes, do it by decorating the factory itself, but be *careful* about tying solutions to internal implementations, they can change at any time!
  49. Don’t write monolithic functions… Just like your widget design, your functions should be broken down into granular pieces that can be be overridden if needed… Try to provide a robust-enough public API for your widgets, to keep people from having to force their way in to use private functions…
  50. If your consumers are in the audience right now, they should be using your public functions as much as possible, riiiiight? That means they’re gonna expect you not to break their code when they update your package… And *that* means you want to make sure you test those functions as much as you can to make sure their interface is consistent from one release to the next…