The magic of jQuery's CSS-based selection makes it easy to think about our code in terms of the DOM, and sometimes that approach is exactly right. Other times, though, what we're trying to accomplish is only tangentially related to our nodes, and opting for an approach where we think in terms of functionality -- not how that functionality is manifested on our page -- can pay big dividends in terms of flexibility. In this talk, we'll look at a small sample application where the DOM takes a back seat to functionality-focused modules, and see how the approach can change the way we write and organize our code.
13. $('#search').submit(function(e) {
e.preventDefault();
resultsContainer.empty();
var term = $(this).find('input').val();
if (!$.trim(term)) { return; }
$.each(['search.news', 'search.web'], function(i, svcName) {
$.getJSON(
searchUrl,
{ q : buildQuery(term, svcName), format : 'json' },
function(resp) {
$('<li><h3>' + svcName + '</h3><ul></ul></li>')
.appendTo(resultsContainer)
.find('ul')
.html(
$.map(resp.query.results.result, function(r) {
return Mustache.to_html(resultsTpl, r);
}).join('')
);
}
);
});
});
how to test all of the important pieces of this?
Saturday, October 16, 2010
14. is it readable & maintainable?
Saturday, October 16, 2010
15. “Writing to be read means writing code ... with
the idea that someone else will read it. is fact
alone will make you edit and think of better
ways to solve the problem you have at hand.”
Stoyan Stefanov, “JavaScript Patterns”
Saturday, October 16, 2010
16. maintainable code is ...
readable
consistent
predictable
looks like one person wrote it
documented
Saturday, October 16, 2010
18. // NAVIGATION
function togglePage(section) {
// if the clicked section is already the current section AND we're in full page mode
// minimize the current tab
if (jQuery('#md_tab_'+ section).hasClass('current') && jQuery('#md_tab_'+ section + ' a').hasClass('md_fullpage')) {
// alert('clicked section is current section AND fullpage mode is active; teaser should load');
// Minimize
jQuery('#md_tabs_navigation a').removeClass('md_fullpage');
jQuery('.md_body').hide();
jQuery('#md_feature').slideDown('normal',function(){
var bodyContent = jQuery('#md_body_'+ section);
bodyContent.fadeOut('normal',function(){
jQuery('#md_tabs_navigation a').each(function(){
var thisSection = jQuery(this).html().replace('<span<','').replace('</span<','');
var thisSection_comp = thisSection.toLowerCase().replace(' ','_');
jQuery('#md_body_'+ thisSection_comp).load(
'/app/modules/info/loadTeaser.php?sect='+ thisSection_comp,
function(){
tb_init('.md_body a.thickbox, .md_body area.thickbox, .md_body input.thickbox');
bodyContent.animate({ height: 'toggle', opacity: 'toggle' },"slow");
}
);
});
});
});
jQuery('#expandtabs span').empty().append('Expand Tabs');
} else {
// if the clicked section is NOT the current section OR we're NOT in full page mode
// then let's go to full page mode and show the whole tab
// Maximize
// alert('clicked section is not the current section OR full page mode is not active; full section should load');
jQuery('#md_tabs_navigation li').removeClass('current');
jQuery('#md_tab_'+ section).addClass('current');
jQuery('#md_tabs_navigation a').addClass('md_fullpage');
jQuery('.md_body').hide();
jQuery('#md_feature').slideUp('normal',function(){
var bodyContent = jQuery('#md_body_'+ section);
bodyContent.fadeOut('normal',function(){
bodyContent.empty();
var pageLoader = 'info/loadSection.php?sect='+ section;
if (section == 'contact_us') {
pageLoader = 'contact/loadContactForm.php?form_id=1';
}
bodyContent.load('/app/modules/'+ pageLoader,function(){
// ADD THICKBOXES
tb_init('.md_body a.thickbox, .md_body area.thickbox, .md_body input.thickbox');
$recent_news_links = jQuery('ul.md_news li a.recent_news_link');
$recent_news_links
.unbind('click')
.each(function(){
var hrefMod = this.href;
hrefMod = hrefMod.replace(/article/,'loadNews').replace(/storyid/,'id');
this.href = hrefMod;
})
.click(function(){
var t = this.title || this.name || null;
var a = this.href || this.alt;
var g = this.rel || false;
tb_show(t,a,g);
this.blur();
return false;
Saturday, October 16, 2010 });
19. (By the way, buy this book.)
Saturday, October 16, 2010
21. When the heavy lifting of manipulating,
displaying, and interacting with data falls
to the browser, it makes sense to reconsider
the typical DOM-centric approach.
Saturday, October 16, 2010
22. there’s a better way*
*lots of them, in fact. this is one.
Saturday, October 16, 2010
23. Functionality-focused code organization
means identifying the pieces of functionality in
your application and teasing them apart.
Saturday, October 16, 2010
24. diversion: pubsub 101
like custom events, but without the overhead!
Saturday, October 16, 2010
25. $('input.magic').click(function(e) {
// publish a "topic" when something happens
$.publish('/something/interesting', [ e.target.value ]);
});
// register our interest in knowing when something happens
$.subscribe('/something/interesting', function(val) {
alert(val);
});
a simple pubsub example
Saturday, October 16, 2010
26. $('input.magic').click(function(e) {
$(document).trigger('/something/interesting', [ this.value ]);
});
$(document).bind('/something/interesting', function(e, val) {
alert(val);
});
pubsub with custom events works too
Saturday, October 16, 2010
27. In jQuery itself, $.fn.ajaxStart and
$.fn.ajaxStop basically let you subscribe to
topics published by jQuery’s underlying
Ajax code.
Saturday, October 16, 2010
28. diversion: require.def
possibly my new favorite thing
(also a great tool for this modularization stuff)
Saturday, October 16, 2010
29. require.def() De nes a function to be run
when the module is included. e function can
return a value, but it doesn’t have to. If
dependencies are speci ed, they’re available to
the function as arguments.
Saturday, October 16, 2010
30. pattern: object Returns an object, though
the de nition function can close other
variables that won’t be visible outside the
function.
Saturday, October 16, 2010
31. require.def(function() {
var privateThing = 'myPrivateThing',
privateObj = {
maxLength : 5,
setPrivateThing : function(val) {
if (val.length > this.maxLength) {
console.log('TOO MUCH');
return;
}
privateThing = val;
},
otherMethod : function() {
console.log(privateThing);
}
};
return {
setPrivateThing : $.proxy(privateObj, 'setPrivateThing'),
publicMethod : $.proxy(privateObj, 'otherMethod')
};
});
closes private vars, returns a public API
Saturday, October 16, 2010
32. pattern: factory Returns a function, that,
when called, returns an instance of an object
that is de ned inside the module. e factory
function may optionally bake in instance
property options or overrides. e base object
is not exposed publicly.
Saturday, October 16, 2010
33. require.def(function(){
var Person = {
intro : 'My name is ',
outro : '. You killed my father. Prepare to die.',
speak : function() {
console.log(
this.intro,
this.firstName,
this.lastName,
this.outro
);
}
};
return function(config) {
return $.extend(Object.create(Person), {
firstName : config.firstName,
lastName : config.lastName
});
};
});
returns a “factory” for creating Person instances
Saturday, October 16, 2010
34. Functionality-focused code organization
means identifying the pieces of functionality in
your application and teasing them apart.
Saturday, October 16, 2010
35. mediators
views
services
Saturday, October 16, 2010
37. • user submits
search input search form
• user input is
validated
• if input is valid,
the search service is
searcher
contacted
searcher • when the search
service returns
results, they are
displayed in the
results container
results
Saturday, October 16, 2010
38. • sets up views and services
search input view • brokers communication
between views and services
• sets up search input form
• listens for user to submit
search form search page
• verifies form data
mediator
• announces the user’s search
if it is valid (non-empty)
results view
• provides an API for
mediators to add results and
clear the results container
• listens for user interaction
with results and broadcasts
searcher service results service
information about it to be
handled by the mediator • provides an API for
searcher service mediators to use to register
user interaction with results,
and to get information about
• provides an API for those interactions later
mediators to communicate
with
• performs searches and pre-
processes results into a
consistent format
• accepts callback to allow
results to be used by mediator
Saturday, October 16, 2010
39. mediators set up views and services, and
broker communications between them,
eliminating the need for direct communication
Saturday, October 16, 2010
40. views display data, observe user input, and
broadcast messages that mediators can react
to; may also provide an API for updating data
Saturday, October 16, 2010
41. views in our sample application
Saturday, October 16, 2010
42. services manage data & state, exposing a
limited public API for mediators
Saturday, October 16, 2010
43. • sets up views and services
search input view • brokers communication
between views and services
• sets up search input form
• listens for user to submit
search form search page
• verifies form data
mediator
• announces the user’s search
if it is valid (non-empty)
results view
• provides an API for
mediators to add results and
clear the results container
• listens for user interaction
with results and broadcasts
searcher service results service
information about it to be
handled by the mediator • provides an API for
searcher service mediators to use to register
user interaction with results,
and to get information about
• provides an API for those interactions later
mediators to communicate
with
• performs searches and pre-
processes results into a
consistent format
• accepts callback to allow
results to be used by mediator
Saturday, October 16, 2010
44. user requests page
app mediator
app mediator hands
request to appropriate
page mediator
page mediator sets up page mediator
views that will be required
for the page
page mediator sets up
services that will be required
for the page
views and services
DO NOT communicate
directly
view service
view service
view service
Saturday, October 16, 2010
45. sample app
http://github.com/rmurphey/ffco
Saturday, October 16, 2010
48. MOAR FEATURES PLZ?
indicate search term in URL
persist recent searches across page reloads
tabbed search results view
improve the code I didn’t show you
Saturday, October 16, 2010