KEMBAR78
Functionality Focused Code Organization | PDF
Functionality-Focused Code Organization
             Rebecca Murphey • @rmurphey • rebeccamurphey.com
             jQuery Boston 2010


Saturday, October 16, 2010
Saturday, October 16, 2010
Saturday, October 16, 2010
object laterals literals
                module pattern
                revealing module pattern
                etc.

Saturday, October 16, 2010
Saturday, October 16, 2010
$('#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('')
                                       );
                                   }
                                 );
                               });
                             });




              a “traditional” jQuery way of solving the problem
Saturday, October 16, 2010
what happens when the spec evolves?




Saturday, October 16, 2010
Saturday, October 16, 2010
Saturday, October 16, 2010
is the codebase suitable for collaboration?




Saturday, October 16, 2010
$('#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('')
                               );
                           }
                        );
                      });
                    });




Saturday, October 16, 2010
is it testable?




Saturday, October 16, 2010
$('#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
is it readable & maintainable?




Saturday, October 16, 2010
“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
maintainable code is ...
                             readable
                             consistent
                             predictable
                             looks like one person wrote it
                             documented



Saturday, October 16, 2010
$('#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('')
                               );
                           }
                        );
                      });
                    });




Saturday, October 16, 2010
// 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 });
(By the way, buy this book.)




Saturday, October 16, 2010
Saturday, October 16, 2010
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
there’s a better way*
                             *lots of them, in fact. this is one.




Saturday, October 16, 2010
Functionality-focused code organization
                means identifying the pieces of functionality in
                your application and teasing them apart.


Saturday, October 16, 2010
diversion: pubsub 101
                             like custom events, but without the overhead!




Saturday, October 16, 2010
$('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
$('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
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
diversion: require.def
                                      possibly my new favorite thing
                             (also a great tool for this modularization stuff)




Saturday, October 16, 2010
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
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
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
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
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
Functionality-focused code organization
                means identifying the pieces of functionality in
                your application and teasing them apart.


Saturday, October 16, 2010
mediators
                views
                services




Saturday, October 16, 2010
Saturday, October 16, 2010
• 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
• 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
mediators set up views and services, and
                broker communications between them,
                eliminating the need for direct communication




Saturday, October 16, 2010
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
views in our sample application
Saturday, October 16, 2010
services manage data & state, exposing a
                limited public API for mediators




Saturday, October 16, 2010
• 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
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
sample app
                             http://github.com/rmurphey/ffco




Saturday, October 16, 2010
Saturday, October 16, 2010
mediators
                app mediator, page mediator
                views
                message, recent searches, search input, results
                services
                searchers, results


Saturday, October 16, 2010
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
rebeccamurphey.com

                             blog.rebeccamurphey.com

                             @rmurphey

                             http://github.com/rmurphey/ffco

                             http://spkr8.com/t/4650

                             http://pinboard.in/u:rmurphey/t:ffco/




Saturday, October 16, 2010

Functionality Focused Code Organization

  • 1.
    Functionality-Focused Code Organization Rebecca Murphey • @rmurphey • rebeccamurphey.com jQuery Boston 2010 Saturday, October 16, 2010
  • 2.
  • 3.
  • 4.
    object laterals literals module pattern revealing module pattern etc. Saturday, October 16, 2010
  • 5.
  • 6.
    $('#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('') ); } ); }); }); a “traditional” jQuery way of solving the problem Saturday, October 16, 2010
  • 7.
    what happens whenthe spec evolves? Saturday, October 16, 2010
  • 8.
  • 9.
  • 10.
    is the codebasesuitable for collaboration? Saturday, October 16, 2010
  • 11.
    $('#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('') ); } ); }); }); Saturday, October 16, 2010
  • 12.
    is it testable? Saturday,October 16, 2010
  • 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 beread 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
  • 17.
    $('#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('') ); } ); }); }); 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
  • 20.
  • 21.
    When the heavylifting 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 betterway* *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 nesa 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 Returnsan 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 Returnsa 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
  • 36.
  • 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 upviews 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 upviews 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 oursample application Saturday, October 16, 2010
  • 42.
    services manage data& state, exposing a limited public API for mediators Saturday, October 16, 2010
  • 43.
    • sets upviews 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
  • 46.
  • 47.
    mediators app mediator, page mediator views message, recent searches, search input, results services searchers, results 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
  • 49.
    rebeccamurphey.com blog.rebeccamurphey.com @rmurphey http://github.com/rmurphey/ffco http://spkr8.com/t/4650 http://pinboard.in/u:rmurphey/t:ffco/ Saturday, October 16, 2010