Writing
Maintainable
 JavaScript


                   Andrew Dupont
               http://andrewdupont.net
I help maintain these.
I write ugly JavaScript all the time.
I work here.
We write ugly JavaScript all the time.
“What’s the problem?”
A JavaScript codebase
gets uglier as it grows.
Day 1



$("p.neat").addClass("ohmy").show("slow");
Day 31
var trip = Gowalla.trip;
$.each(trip.spots, function(i, spot) {
  var marker = new GMarker(
    new GLatLng(spot.lat, spot.lng), {
       icon: Gowalla.createLetterIcon(i),
       title: h(spot.name)
     }
  );
  GEvent.addListener(marker, "click", function() {
       marker.openInfoWindowHtml('<div class="map-bubble"><img src="' +
        spot.image_url + '" width="50" height="50" /><b><a href="' +
        spot.url + '" style="color: #37451e;">' + h(spot.name) +
        '</a></b></div>');
       return false;
  });
  Gowalla.map.addOverlay(marker);
});
Gowalla.zoomAndCenter(trip.spots);
Day 90
options = options || {};
var params = this.getSearchParams(options);
Paginator.currentPage = 1;
Paginator.handler = Gowalla.displaySpots;
Paginator.paginate('/spots', params);
if (Gowalla.filterOptions["l"] || Gowalla.filterOptions["sw"] ||
  Gowalla.filterOptions["lat"]) {
   $('#map-wrapper').show();
   $('#spots_search_l').removeClass('off');
   if (options.l) $('#spots_search_l').val(unescape(options.l));
} else {
   $('#map-wrapper').hide();
}
if (Gowalla.mapVisible()) $('#map-placeholder').show();
$('#heading').hide();
$('#featured_spots').hide();
$('#new_spots').hide();
$.getJSON('/spots', this.getSearchParams(options), function(spots) {
   if (spots.length > 0) {
     $('.paging').show();
     $('#filter').show();
     $('#results').show();
     $('#map-placeholder').hide();
     if (Gowalla.mapVisible() && !Gowalla.map) {
       $('#map-placeholder').addClass("transparent");
       Gowalla.createMap();
       GEvent.addListener(Gowalla.map, "dragend", function() {
         var sw = this.getBounds().getSouthWest().toString();
         var ne = this.getBounds().getNorthEast().toString();
         Gowalla.searchSpots({sw:sw, ne:ne, limit:'150'});
       });
     }
   }
   Gowalla.displaySpots(spots);
});
Ugliness of Code over Time




                             (Source: gut feeling)
design patterns
    recipes
     ideas
The solution:
Use existing so ware principles
    to make your codebase
      more maintainable.
Wishes:
WISH #1:
Code that accomplishes a single task
should all live together in one place.
WISH #2:
We should be able to rewrite a component
   without affecting things elsewhere.
WISH #3:
Troubleshooting should be somewhat easy
  even if you’re unfamiliar with the code.
Plan of attack
WISH:
    Code that accomplishes a single task
    should all live together in one place.



             THEREFORE:
Divide your codebase into components,
      placing each in its own file.
“What’s a component?”
WISH:
     We should be able to rewrite a component
       without breaking things elsewhere.



                THEREFORE:
  A component should be whatever size is
necessary to isolate its details from other code.
A “component” is
   something you could
    rewrite from scratch
without affecting other stuff.
Law of Demeter:
“Each unit should have
only limited knowledge
  about other units.”
The fewer “friends”
    a component has,
the less it will be affected
 by changes elsewhere.
Gowalla.Location
handles all client-side geolocation.



 Gowalla.Location.getLocation();
 //=> [30.26800, -97.74283]

 Gowalla.Location.getLocality();
 //=> "Austin, TX"
Gowalla.ActivityFeed
handles all feeds of user activity.
Gowalla.Flash
            handles the display of
          transient status messages.

Gowalla.Flash.success("Your settings were updated.");
Gowalla.Map
handles all interaction
  with Google Maps.
Example: Gowalla.Map

function addSpotsToMap(spots) {
  Gowalla.Map.clearSpots();
  $.each(spots, function(i, spot) {
    Gowalla.Map.addSpot(spot);
  });
}
Example: Gowalla.Map

function addSpotsToMap(spots) {
  Gowalla.Map.clearSpots();
  $.each(spots, function(i, spot) {
    Gowalla.Map.addSpot(spot, { infoWindow: true });
  });
}
WISH:
We should be able to rewrite a component
  without breaking things elsewhere.



           THEREFORE:
  We should standardize the way
  components talk to one another.
Have components communicate
 through a central message bus.
       (“custom events”)
Publisher and subscriber
  don’t need to know
  about one another.
Instead, they only know about
    a central event broker.
WISH:
Troubleshooting should be somewhat easy
  even if you’re unfamiliar with the code.



            THEREFORE:
        Embrace conventions.
“Files are named according to
     their module names.”
“Componets have a
standard way of initializing.”
“Why custom events?”
Every major framework
      has them:
jQuery


$(document).bind('customevent', function(event, data) {
  // stuff
});

$('#troz').trigger('customevent', [someAssociatedData]);
Prototype

$(document).observe('custom:event', function(event) {
  var customData = event.memo;
  // stuff
});

$('troz').fire('custom:event', { foo: "bar" });
Dojo
                 (“pub-sub”)


dojo.subscribe('some-event', function(data) {
  // stuff
});

dojo.publish('some-event', someData);
A custom event is an interface that
publisher and subscriber adhere to.
As long as the interface
remains the same, either part
  can be safely rewritten.
“So I should replace
all my method calls
with custom events?
    Fat chance.”
A consistent public API
  is also an interface.
It’s OK for a subscriber
to call methods on a broadcaster,
         but not vice-versa.
Example: script.aculo.us 2.0
Writing Maintainable JavaScript
The auto-completer knows
    about the menu…

   var menu = new S2.UI.Menu();
   menu.addChoice("Foo");
   menu.addChoice("Bar");
   someElement.insert(menu);
   menu.open();
…but the menu doesn’t know
        about the auto-completer

menu.observe('ui:menu:selected', function(event) {
  console.log('user clicked on:', event.memo.element);
});
“What does a rewrite
    look like?”
Instead of:
function showNearbySpotsInMenu() {
  $.ajax({
    url: '/spots',
    params: { lat: someLat, lng: someLng },
    success: function(spots) {
      var html = $.map(spots, function(spot) {
        return '<li id="spot-"' + spot.id + '>' + spot.name + '</li>';
      });
      $('#spot_menu').html(html.join(''));
    }
  });
}
Do this:
function getNearbySpotsFromServer(lat, lng) {
  $.ajax({
    url: '/spots',
    params: { lat: lat, lng: lng },
    success: function(spots) {
      $(document).trigger('nearby-spots-received', [spots]);
    }
  });
}
And this:
function renderNearbySpots(event, spots) {
  var html = $.map(spots, function(spot) {
    return '<li id="spot-"' + spot.id + '>' + spot.name + '</li>';
  });
  $('#spot_menu').html(html.join(''));
}

$(document).bind('nearby-spots-received', renderNearbySpots);
Or, if you prefer…
function getNearbySpotsFromServer(lat, lng) {
  $.ajax({
    url: '/spots',
    params: { lat: lat, lng: lng },
    success: function(spots) {
      renderNearbySpots(spots);
    }
  });
}

function renderNearbySpots(spots) {
  var html = $.map(spots, function(spot) {
    return '<li id="spot-"' + spot.id + '>' + spot.name + '</li>';
  });
  $('#spot_menu').html(html.join(''));
}
Intra-module organization
(divide code up according to job)
A formal “contract”
Easier testing


function testNearbySpotsRendering() {
  renderNearbySpots(Fixtures.NEARBY_SPOTS);
  assertEqual($('#spot_menu > li').length, 3);
}
“What if it’s not enough?”
More complex web apps might need
    desktop-like architectures.
“Single-page apps” have
a few common characteristics:
maintaining data objects on
the client side, instead of expecting
   the server to do all the work;
creating views on the client side
and mapping them to data objects;
use of the URL hash for routing/permalinking
      (or HTML5 history management).
Is this MVC?
  Perhaps.
Backbone
http://documentcloud.github.com/backbone/
Models

                       define a model class     window.Todo = Backbone.Model.extend({
                                                 EMPTY: "new todo...",

property access wrapped in set/get methods      initialize: function() {
                                                   if (!this.get('content'))
                                                     this.set({ 'content': this.EMPTY });
                                                },

                                                toggle: function() {
                                                  this.set({ done: !this.get('done') });
                                                },

          triggered when the object is saved    validate: function(attributes) {
                                                   if (!attributes.content.test(/S/))
                                                     return "content can't be empty";
                                                },

                                                 // ...
                                               });
Views

                              define a view class    window.Todo.View = Backbone.View.extend({
                                                      tagName: 'li',

                bind events to pieces of the view    events: {
                                                       'dblclick div.todo-content' : 'edit',
                                                       'keypress .todo-input'      : 'updateOnEnter'
                                                     },

                                                     initialize: function() {
map to a model object; re-render when it changes       this.model.bind('change', this.render);
                                                     },

                          set the view’s contents    render: function() {
                                                       // ...
                                                     },

                                                      // ...
                                                    });
Synchronization

                                                 Backbone.sync = function(method, model, yes, no) {
determine the HTTP verb to use for this action     var type = methodMap[method];

                  serialize the object to JSON        var json = JSON.stringify(model.toJSON());

                   send the data to the server        $.ajax({
                                                        url: getUrl(model),
                                                        type: type,
                                                        data: json,
                                                        processData: false,
                                                        contentType: 'application/json',
                                                        dataType: 'json',
                                                        success: yes,
                                                        error: no
                                                      });
                                                 };
Other options:
      SproutCore
 (http://sproutcore.com/)


      Cappuccino
  (http://cappuccino.org/)


    JavaScriptMVC
(http://javascriptmvc.com/)
“Great. How do I start?”
Don’t do a
Grand Rewrite™
One strategy:
Write new code to conform to your architecture.
Improve old code little by little as you revisit it.
Maintainability
is not all-or-nothing.
Questions?



✍   PLEASE FILL OUT
    AN EVALUATION FORM
                                     Andrew Dupont
                            http://andrewdupont.net

More Related Content

PDF
Functionality Focused Code Organization
PDF
Building Large jQuery Applications
PDF
Cleaner, Leaner, Meaner: Refactoring your jQuery
PDF
Mulberry: A Mobile App Development Toolkit
PDF
A New Baseline for Front-End Devs
PDF
Beyond the DOM: Sane Structure for JS Apps
PDF
Dojo Confessions
Functionality Focused Code Organization
Building Large jQuery Applications
Cleaner, Leaner, Meaner: Refactoring your jQuery
Mulberry: A Mobile App Development Toolkit
A New Baseline for Front-End Devs
Beyond the DOM: Sane Structure for JS Apps
Dojo Confessions

What's hot (20)

PDF
Using Objects to Organize your jQuery Code
PDF
Delivering a Responsive UI
PDF
Decoupling with Design Patterns and Symfony2 DIC
PPTX
jQuery Data Manipulate API - A source code dissecting journey
KEY
Advanced jQuery
PDF
International News | World News
PDF
Decoupling the Ulabox.com monolith. From CRUD to DDD
PDF
How I started to love design patterns
PDF
Min-Maxing Software Costs
PDF
Backbone Basics with Examples
PPTX
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
PDF
Symfony CoP: Form component
PDF
06 jQuery #burningkeyboards
PDF
Introduction to CQRS and Event Sourcing
PDF
前端MVC 豆瓣说
PDF
05 JavaScript #burningkeyboards
PDF
How Kris Writes Symfony Apps
PDF
A evolução da persistência de dados (com sqlite) no android
PDF
How Kris Writes Symfony Apps
PDF
¿Cómo de sexy puede hacer Backbone mi código?
Using Objects to Organize your jQuery Code
Delivering a Responsive UI
Decoupling with Design Patterns and Symfony2 DIC
jQuery Data Manipulate API - A source code dissecting journey
Advanced jQuery
International News | World News
Decoupling the Ulabox.com monolith. From CRUD to DDD
How I started to love design patterns
Min-Maxing Software Costs
Backbone Basics with Examples
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Symfony CoP: Form component
06 jQuery #burningkeyboards
Introduction to CQRS and Event Sourcing
前端MVC 豆瓣说
05 JavaScript #burningkeyboards
How Kris Writes Symfony Apps
A evolução da persistência de dados (com sqlite) no android
How Kris Writes Symfony Apps
¿Cómo de sexy puede hacer Backbone mi código?
Ad

Viewers also liked (6)

PPT
An Introduction to Ajax Programming
PPT
Introduction to ajax
PDF
Ajax Introduction Presentation
PPT
Ajax Ppt 1
PPTX
Ajax ppt - 32 slides
An Introduction to Ajax Programming
Introduction to ajax
Ajax Introduction Presentation
Ajax Ppt 1
Ajax ppt - 32 slides
Ad

Similar to Writing Maintainable JavaScript (20)

PDF
Single page webapps & javascript-testing
PDF
Javascript Frameworks for Joomla
PPTX
Taming that client side mess with Backbone.js
PDF
jQuery secrets
PDF
Modular and Event-Driven JavaScript
PDF
PPTX
Jquery optimization-tips
PDF
international PHP2011_Bastian Feder_jQuery's Secrets
PDF
Backbone.js — Introduction to client-side JavaScript MVC
PDF
Javascript: the important bits
PDF
jQuery secrets
PDF
Fun Teaching MongoDB New Tricks
KEY
Object-Oriented JavaScript
KEY
Object-Oriented Javascript
PDF
jQuery: Events, Animation, Ajax
PDF
Ten useful JavaScript tips & best practices
PDF
Grails 1.2 探検隊 -新たな聖杯をもとめて・・・-
PDF
kissy-past-now-future
PPTX
KISSY 的昨天、今天与明天
KEY
JavaScript Growing Up
Single page webapps & javascript-testing
Javascript Frameworks for Joomla
Taming that client side mess with Backbone.js
jQuery secrets
Modular and Event-Driven JavaScript
Jquery optimization-tips
international PHP2011_Bastian Feder_jQuery's Secrets
Backbone.js — Introduction to client-side JavaScript MVC
Javascript: the important bits
jQuery secrets
Fun Teaching MongoDB New Tricks
Object-Oriented JavaScript
Object-Oriented Javascript
jQuery: Events, Animation, Ajax
Ten useful JavaScript tips & best practices
Grails 1.2 探検隊 -新たな聖杯をもとめて・・・-
kissy-past-now-future
KISSY 的昨天、今天与明天
JavaScript Growing Up

More from Andrew Dupont (6)

PDF
Learning new words
PDF
How to Argue about Code
PDF
How to Argue about JavaScript
PDF
Everything is Permitted: Extending Built-ins
PDF
Open Government: An Overview
PDF
Defensive, Cross-Browser Coding with Prototype
Learning new words
How to Argue about Code
How to Argue about JavaScript
Everything is Permitted: Extending Built-ins
Open Government: An Overview
Defensive, Cross-Browser Coding with Prototype

Recently uploaded (20)

PDF
Transform-Quality-Engineering-with-AI-A-60-Day-Blueprint-for-Digital-Success.pdf
PDF
A symptom-driven medical diagnosis support model based on machine learning te...
PDF
CEH Module 2 Footprinting CEH V13, concepts
PDF
ment.tech-Siri Delay Opens AI Startup Opportunity in 2025.pdf
PDF
A hybrid framework for wild animal classification using fine-tuned DenseNet12...
PDF
The-2025-Engineering-Revolution-AI-Quality-and-DevOps-Convergence.pdf
PDF
EIS-Webinar-Regulated-Industries-2025-08.pdf
PPTX
Module 1 Introduction to Web Programming .pptx
PPTX
Internet of Everything -Basic concepts details
PDF
Build Real-Time ML Apps with Python, Feast & NoSQL
PDF
Ensemble model-based arrhythmia classification with local interpretable model...
PDF
NewMind AI Weekly Chronicles – August ’25 Week IV
PDF
Transform-Your-Supply-Chain-with-AI-Driven-Quality-Engineering.pdf
PDF
AI.gov: A Trojan Horse in the Age of Artificial Intelligence
PDF
Altius execution marketplace concept.pdf
PDF
CXOs-Are-you-still-doing-manual-DevOps-in-the-age-of-AI.pdf
PPTX
Build automations faster and more reliably with UiPath ScreenPlay
PDF
Dell Pro Micro: Speed customer interactions, patient processing, and learning...
PDF
Aug23rd - Mulesoft Community Workshop - Hyd, India.pdf
PPTX
SGT Report The Beast Plan and Cyberphysical Systems of Control
Transform-Quality-Engineering-with-AI-A-60-Day-Blueprint-for-Digital-Success.pdf
A symptom-driven medical diagnosis support model based on machine learning te...
CEH Module 2 Footprinting CEH V13, concepts
ment.tech-Siri Delay Opens AI Startup Opportunity in 2025.pdf
A hybrid framework for wild animal classification using fine-tuned DenseNet12...
The-2025-Engineering-Revolution-AI-Quality-and-DevOps-Convergence.pdf
EIS-Webinar-Regulated-Industries-2025-08.pdf
Module 1 Introduction to Web Programming .pptx
Internet of Everything -Basic concepts details
Build Real-Time ML Apps with Python, Feast & NoSQL
Ensemble model-based arrhythmia classification with local interpretable model...
NewMind AI Weekly Chronicles – August ’25 Week IV
Transform-Your-Supply-Chain-with-AI-Driven-Quality-Engineering.pdf
AI.gov: A Trojan Horse in the Age of Artificial Intelligence
Altius execution marketplace concept.pdf
CXOs-Are-you-still-doing-manual-DevOps-in-the-age-of-AI.pdf
Build automations faster and more reliably with UiPath ScreenPlay
Dell Pro Micro: Speed customer interactions, patient processing, and learning...
Aug23rd - Mulesoft Community Workshop - Hyd, India.pdf
SGT Report The Beast Plan and Cyberphysical Systems of Control

Writing Maintainable JavaScript

  • 1. Writing Maintainable JavaScript Andrew Dupont http://andrewdupont.net
  • 2. I help maintain these. I write ugly JavaScript all the time.
  • 3. I work here. We write ugly JavaScript all the time.
  • 5. A JavaScript codebase gets uglier as it grows.
  • 7. Day 31 var trip = Gowalla.trip; $.each(trip.spots, function(i, spot) { var marker = new GMarker( new GLatLng(spot.lat, spot.lng), { icon: Gowalla.createLetterIcon(i), title: h(spot.name) } ); GEvent.addListener(marker, "click", function() { marker.openInfoWindowHtml('<div class="map-bubble"><img src="' + spot.image_url + '" width="50" height="50" /><b><a href="' + spot.url + '" style="color: #37451e;">' + h(spot.name) + '</a></b></div>'); return false; }); Gowalla.map.addOverlay(marker); }); Gowalla.zoomAndCenter(trip.spots);
  • 8. Day 90 options = options || {}; var params = this.getSearchParams(options); Paginator.currentPage = 1; Paginator.handler = Gowalla.displaySpots; Paginator.paginate('/spots', params); if (Gowalla.filterOptions["l"] || Gowalla.filterOptions["sw"] || Gowalla.filterOptions["lat"]) { $('#map-wrapper').show(); $('#spots_search_l').removeClass('off'); if (options.l) $('#spots_search_l').val(unescape(options.l)); } else { $('#map-wrapper').hide(); } if (Gowalla.mapVisible()) $('#map-placeholder').show(); $('#heading').hide(); $('#featured_spots').hide(); $('#new_spots').hide(); $.getJSON('/spots', this.getSearchParams(options), function(spots) { if (spots.length > 0) { $('.paging').show(); $('#filter').show(); $('#results').show(); $('#map-placeholder').hide(); if (Gowalla.mapVisible() && !Gowalla.map) { $('#map-placeholder').addClass("transparent"); Gowalla.createMap(); GEvent.addListener(Gowalla.map, "dragend", function() { var sw = this.getBounds().getSouthWest().toString(); var ne = this.getBounds().getNorthEast().toString(); Gowalla.searchSpots({sw:sw, ne:ne, limit:'150'}); }); } } Gowalla.displaySpots(spots); });
  • 9. Ugliness of Code over Time (Source: gut feeling)
  • 10. design patterns recipes ideas
  • 11. The solution: Use existing so ware principles to make your codebase more maintainable.
  • 13. WISH #1: Code that accomplishes a single task should all live together in one place.
  • 14. WISH #2: We should be able to rewrite a component without affecting things elsewhere.
  • 15. WISH #3: Troubleshooting should be somewhat easy even if you’re unfamiliar with the code.
  • 17. WISH: Code that accomplishes a single task should all live together in one place. THEREFORE: Divide your codebase into components, placing each in its own file.
  • 19. WISH: We should be able to rewrite a component without breaking things elsewhere. THEREFORE: A component should be whatever size is necessary to isolate its details from other code.
  • 20. A “component” is something you could rewrite from scratch without affecting other stuff.
  • 21. Law of Demeter: “Each unit should have only limited knowledge about other units.”
  • 22. The fewer “friends” a component has, the less it will be affected by changes elsewhere.
  • 23. Gowalla.Location handles all client-side geolocation. Gowalla.Location.getLocation(); //=> [30.26800, -97.74283] Gowalla.Location.getLocality(); //=> "Austin, TX"
  • 25. Gowalla.Flash handles the display of transient status messages. Gowalla.Flash.success("Your settings were updated.");
  • 27. Example: Gowalla.Map function addSpotsToMap(spots) { Gowalla.Map.clearSpots(); $.each(spots, function(i, spot) { Gowalla.Map.addSpot(spot); }); }
  • 28. Example: Gowalla.Map function addSpotsToMap(spots) { Gowalla.Map.clearSpots(); $.each(spots, function(i, spot) { Gowalla.Map.addSpot(spot, { infoWindow: true }); }); }
  • 29. WISH: We should be able to rewrite a component without breaking things elsewhere. THEREFORE: We should standardize the way components talk to one another.
  • 30. Have components communicate through a central message bus. (“custom events”)
  • 31. Publisher and subscriber don’t need to know about one another.
  • 32. Instead, they only know about a central event broker.
  • 33. WISH: Troubleshooting should be somewhat easy even if you’re unfamiliar with the code. THEREFORE: Embrace conventions.
  • 34. “Files are named according to their module names.”
  • 35. “Componets have a standard way of initializing.”
  • 38. jQuery $(document).bind('customevent', function(event, data) { // stuff }); $('#troz').trigger('customevent', [someAssociatedData]);
  • 39. Prototype $(document).observe('custom:event', function(event) { var customData = event.memo; // stuff }); $('troz').fire('custom:event', { foo: "bar" });
  • 40. Dojo (“pub-sub”) dojo.subscribe('some-event', function(data) { // stuff }); dojo.publish('some-event', someData);
  • 41. A custom event is an interface that publisher and subscriber adhere to.
  • 42. As long as the interface remains the same, either part can be safely rewritten.
  • 43. “So I should replace all my method calls with custom events? Fat chance.”
  • 44. A consistent public API is also an interface.
  • 45. It’s OK for a subscriber to call methods on a broadcaster, but not vice-versa.
  • 48. The auto-completer knows about the menu… var menu = new S2.UI.Menu(); menu.addChoice("Foo"); menu.addChoice("Bar"); someElement.insert(menu); menu.open();
  • 49. …but the menu doesn’t know about the auto-completer menu.observe('ui:menu:selected', function(event) { console.log('user clicked on:', event.memo.element); });
  • 50. “What does a rewrite look like?”
  • 51. Instead of: function showNearbySpotsInMenu() { $.ajax({ url: '/spots', params: { lat: someLat, lng: someLng }, success: function(spots) { var html = $.map(spots, function(spot) { return '<li id="spot-"' + spot.id + '>' + spot.name + '</li>'; }); $('#spot_menu').html(html.join('')); } }); }
  • 52. Do this: function getNearbySpotsFromServer(lat, lng) { $.ajax({ url: '/spots', params: { lat: lat, lng: lng }, success: function(spots) { $(document).trigger('nearby-spots-received', [spots]); } }); }
  • 53. And this: function renderNearbySpots(event, spots) { var html = $.map(spots, function(spot) { return '<li id="spot-"' + spot.id + '>' + spot.name + '</li>'; }); $('#spot_menu').html(html.join('')); } $(document).bind('nearby-spots-received', renderNearbySpots);
  • 54. Or, if you prefer… function getNearbySpotsFromServer(lat, lng) { $.ajax({ url: '/spots', params: { lat: lat, lng: lng }, success: function(spots) { renderNearbySpots(spots); } }); } function renderNearbySpots(spots) { var html = $.map(spots, function(spot) { return '<li id="spot-"' + spot.id + '>' + spot.name + '</li>'; }); $('#spot_menu').html(html.join('')); }
  • 57. Easier testing function testNearbySpotsRendering() { renderNearbySpots(Fixtures.NEARBY_SPOTS); assertEqual($('#spot_menu > li').length, 3); }
  • 58. “What if it’s not enough?”
  • 59. More complex web apps might need desktop-like architectures.
  • 60. “Single-page apps” have a few common characteristics:
  • 61. maintaining data objects on the client side, instead of expecting the server to do all the work;
  • 62. creating views on the client side and mapping them to data objects;
  • 63. use of the URL hash for routing/permalinking (or HTML5 history management).
  • 64. Is this MVC? Perhaps.
  • 66. Models define a model class window.Todo = Backbone.Model.extend({ EMPTY: "new todo...", property access wrapped in set/get methods initialize: function() { if (!this.get('content')) this.set({ 'content': this.EMPTY }); }, toggle: function() { this.set({ done: !this.get('done') }); }, triggered when the object is saved validate: function(attributes) { if (!attributes.content.test(/S/)) return "content can't be empty"; }, // ... });
  • 67. Views define a view class window.Todo.View = Backbone.View.extend({ tagName: 'li', bind events to pieces of the view events: { 'dblclick div.todo-content' : 'edit', 'keypress .todo-input' : 'updateOnEnter' }, initialize: function() { map to a model object; re-render when it changes this.model.bind('change', this.render); }, set the view’s contents render: function() { // ... }, // ... });
  • 68. Synchronization Backbone.sync = function(method, model, yes, no) { determine the HTTP verb to use for this action var type = methodMap[method]; serialize the object to JSON var json = JSON.stringify(model.toJSON()); send the data to the server $.ajax({ url: getUrl(model), type: type, data: json, processData: false, contentType: 'application/json', dataType: 'json', success: yes, error: no }); };
  • 69. Other options: SproutCore (http://sproutcore.com/) Cappuccino (http://cappuccino.org/) JavaScriptMVC (http://javascriptmvc.com/)
  • 70. “Great. How do I start?”
  • 71. Don’t do a Grand Rewrite™
  • 72. One strategy: Write new code to conform to your architecture. Improve old code little by little as you revisit it.
  • 74. Questions? ✍ PLEASE FILL OUT AN EVALUATION FORM Andrew Dupont http://andrewdupont.net