Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Everything I Wish I Had Known About Building Single Page Apps

Everything I Wish I Had Known About Building Single Page Apps

Tessa Harmon

June 13, 2013
Tweet

More Decks by Tessa Harmon

Other Decks in Programming

Transcript

  1. EVERYTHING I WISH I HAD KNOWN ABOUT BUILDING SINGLE PAGE

    APPS. BY TESSA HARMON OF Thursday, June 13, 13
  2. Tessa Harmon [email protected] @TessaHarmon Thursday, June 13, 13 - From

    Charlotte - 6 years of software dev experience, past 9 months focused on SPAs - So glad to be in Portland - Co-organizer of CLTFED for over a year - Local speaker at UX/dev groups
  3. WHAT IS A SINGLE PAGE APP? Thursday, June 13, 13

    - Broad definition: All HTML, CSS, and JS is loaded onto one page (either all at once or dynamically). New content is most commonly loaded via AJAX without a page refresh. Could also be websockets, flash, silverlight, etc Example: GitHub is a collection of single page apps
  4. THE WEB c. 1992 World Wide Web ©CERN Thursday, June

    13, 13 Scientific documents linked together in a way understandable by non-technical people: Hypertext, Hyperlinks Basic personal homepages linking to other pages
  5. PRIMITIVE WEB APPLICATIONS Thursday, June 13, 13 Then...along came Perl,

    and PHP. Dynamically generated pages, ugly dynamic URLs based on query strings = obviously not scientific documents Only way of determining a user interaction was through server. AKA, forms! CGI = interface between form and server No structure enforced, HTML and logic interspersed willy nilly PHP: faster, but had a lot of the same problems Flash/proprietary technologies: slow loading, adverse to the open web Introduction of XHR by MS in ’99, started seeing AJAX bolted on to “classical” websites First single page apps “broke the web” = no bookmarkable URLs, can’t refresh the page, etc
  6. MODERN SINGLE PAGE APPS Thursday, June 13, 13 Further blurs

    the lines between designer, back-end developer, and front-end developer: show app demo Your mission, should you choose to accept it, is to learn application development principles - Loosely coupled front & back end = flexibility - Possible to add other front-ends - Caveats: if SEO is a priority, no go. - Lower overhead for server - Easy to host (static HTML, CSS, JS)
  7. IDEAL USE CASES Thursday, June 13, 13 •Data-intensive web apps

    •Mobile (pair with cache manifest and local storage = offline use) •Highly interactive web apps •They can even be accessible: go to Katie Cunningham’s talk at 3:30 for more information on making JS accessible, ARIA roles, and all that good stuff.
  8. NOT-SO IDEAL USE CASES Thursday, June 13, 13 •Content sites,

    product sites, or blogs because of search indexing • Generally, SPAs are not public and have marketing pages that can be indexed by search engines •If high interactivity is not needed
  9. STATES vs. PAGES Thursday, June 13, 13 •Which states need

    a URL? •Where does one state end and another begin? •How do I organize my code? •Non-linear interaction path
  10. APPLICATION BUILDERS Thursday, June 13, 13 •I dig Brunch, Yeoman

    is also an option, or BYO with Grunt (show new project with brunch) •It pre-wraps your modules for you •It compiles template langs (jade, haml, stylus, sass, mustache, handlebars, etc) •It can run a test suite •JSHint plugin (which breaks the build when there is a jshint error) •Can build in development mode or production mode (compressed) •Builds to static files, so really easy to deploy • Preview server that automatically rebuilds files - Can minify and concat your CSS/JS files for you
  11. CSS CONSIDERATIONS Thursday, June 13, 13 •Flickering before JS initializes

    •OOCSS. Namespacing your classes and using thoughtful selectors is a must.
  12. AVOIDING THE FLICKER Thursday, June 13, 13 - Common problem:

    pieces of other pages showing on load - Flicker is caused by your HTML/CSS loading without JS being fully initialized
  13. AVOIDING THE FLICKER Thursday, June 13, 13 - Common problem:

    pieces of other pages showing on load - Flicker is caused by your HTML/CSS loading without JS being fully initialized
  14. AVOIDING THE FLICKER: CODE .page display none // Eliminate flickering

    on load/reload .active.page display block stylus: jade: // ko if: current.page().name === 'search-results' .page(data-bind='attr: { class: "active" }') //- Page code here // /ko Thursday, June 13, 13 - Hide everything by default and toggle display with the data binding
  15. REALLY LEARN JAVASCRIPT Thursday, June 13, 13 •You must learn

    JS beyond DOM manipulation •Learn design patterns, architeture •Think modularly, not just with JS, but with CSS & templates
  16. CARE MORE ABOUT HOW BROWSERS WORK Thursday, June 13, 13

    •Any performance issues/bad habits will be magnified
  17. MEMORY LEAKS Thursday, June 13, 13 •Why do I care?

    It impacts performance •Occurs when little bits of your application overstay their welcome in the heap •JS is a garbage-collected language, but there are still delays in collection that impact performance •Can affect not only your app, but other tabs that are open •Special consideration for old browsers
  18. EVENT MANAGEMENT Thursday, June 13, 13 •Delegate events •Be sure

    to call “off” when you’re done •Use event namespacing •Debounce (scroll, resize) •Common side effect: listener firing n+1 times
  19. BE A GOOD (EVENT) LISTENER supportVW: function() { var causeRepaintsOn

    = $(".title"); function repaint() { causeRepaintsOn.css({ 'z-index': 1 }) } $(window).off('resize.repaint'); $(window).on('resize.repaint', _.debounce(repaint, 100)); } Thursday, June 13, 13 - Resize event - lots of events fired in Webkit/IE. Firefox only fires one event when resize completes. - Also applicable to scrolling - you probably don’t need every x/y value as it updates. - Namespacing - Calling “off” before “on” so multiple listeners aren’t out there
  20. COMPARISON OF EVENTS FIRED, DEBOUNCING ON & OFF Thursday, June

    13, 13 - This was just for resizing the window bigger and smaller
  21. THINGS CAN GET MESSY Thursday, June 13, 13 The side

    effects of events not unbinding
  22. SO, CLEAN UP AFTER YOURSELF var searchResults; router.register( '#/search/results/:searchQuery(/:resultsPageNumber)', {

    to: function() { searchResults = new pages.searchResults(this.params, appVM.current) }, exit: function() { searchResults.destroy(); } }); Thursday, June 13, 13 Include a destroy function that cleans up anything that you’ve set that you won’t need later: event listeners, observable values, etc.
  23. CLOSURES Thursday, June 13, 13 •Anything inside of a closure

    will remain in memory until the containing reference is destroyed •Reset everything to null when you’re done with it
  24. ARCHITECTURE Thursday, June 13, 13 •KnockoutJS, Backbone, Angular, or Ember

    will not save you •Don’t let frameworks cramp your style. •You will need to write custom stuff no matter which framework you choose.
  25. MODEL VIEW whatever Thursday, June 13, 13 •Don’t get too

    hung up on the name of your design pattern, so long as it’s reasonably organized •I use a hybrid of pub/sub and observable patterns
  26. SAMPLE VIEWMODEL var current = { beer: ko.observable(), beers: ko.observableArray([]),

    totalBeers: ko.observable(), searchQuery: ko.observable(), errorMessages: ko.observableArray([]), page: ko.observable({}), } ko.applyBindings(current); //on dom ready Thursday, June 13, 13 - Define a viewmodel with observables. Biggest misconception = everything needs to be an observable. No! only things that you need to know about when they change. - You can store anything (objects, arrays, etc) - Call applyBindings - Create templates that bind with your observables & other JS objects - Keep track of data without relying on specific DOM elements
  27. SAMPLE TEMPLATE // ko if: current.page().name === 'beer-details' // ko

    with: current.beer .page(data-bind='attr: { "class": "active"}') h2(data-bind='text: name + " by " + brewery.name') p(data-bind='text: abv + "% ABV"') p(data-bind='text: description') a.btn(href='#/') Search Again // /ko // /ko Thursday, June 13, 13 Default: uses HTML comments. Can also use jQuery tmpl, underscore, etc. Much easier to work with in Jade. Can use any JS expression in the data binding, not just observables
  28. BUILDING BLOCKS •Data bindings & models •Routing •DOM/events/XHR •Module loader

    •Event Emitter for inter-module communication Thursday, June 13, 13
  29. USE A ROUTER router.register('#/beer/:id', { to: function() { beerDetails =

    beerDetails(this.params, appVM.current) }, exit: function() { beerDetails.destroy(); } }); Thursday, June 13, 13 Don’t be one of those silly people who builds a single-page app that no one can bookmark or use their back button on! You might as well be using Flash I like PathJS because it’s library agnostic. Sammy is another popular one.
  30. EVENT EMITTERS /** * Handle showing/hiding the progress indicator. */

    radio('progressIndicator').subscribe( function(data) { if (data === 'loading:data') return $(".loader").css({ "display": "block" }); if (data === 'loading:complete') return $(".loader").css({ "display": "none" }); }); Thursday, June 13, 13 Event emitter because observable would be too much overhead Really awesome for quick and easy inter-module communication Any time you set up a subscriber, remove it when you don’t need it anymore! UX rec: explain why progress indicators are important
  31. CUSTOM SUBSCRIBERS & COMPUTED OBSERVABLES (KO SPECIFIC) // Custom Subscriber

    appVM.current.page.subscribe(function(newPage) { if (newPage) { appVM.current.errorMessages.removeAll(); } }); // Computed Observable self.current.beerTotalDelayed = ko.computed(function() { return self.current.totalBeers() + “ on the wall”; }, 500); Thursday, June 13, 13 Useful if the value you’re monitoring is an observable!
  32. SERVER vs. CLIENT Thursday, June 13, 13 •Encourages clear separation

    of concerns •Flexible API backend •Can build a SPA that falls back to purely server-side code •Or you can decide that you don’t care, based on your audience
  33. MODULES ARE YOUR NEW BFF Thursday, June 13, 13 •CSS,

    JS, and templates •One jade template, one stylus file, and one JS module per viewmodel/UI “chunk” •Show file tree of app
  34. USE MODULES! // Page “Glue Code” Module pseudo-code Name the

    module. Set some initial properties. Call methods to kick things off like fetching data from the server and binding event handlers Define unbind/destroy functions Tell the rest of the app your module can be used Thursday, June 13, 13 Modules = JS objects Look at the BeerDetails module & config module to see in practice.
  35. TEMPLATING IS CRUCIAL Thursday, June 13, 13 •Early on, you

    have to decide how HTML will be served •Client-side templates •Static from the server, compiled into one page (my preference) •Depends on backend. Can keep them as separated as you’d like
  36. MINIMIZE DOM MANIPULATION Thursday, June 13, 13 •It’s fragile •Let

    data drive the interface •Example: event listener in KO template; able to find the target/source of the element so the node name/id/whatever doesn’t have to be known
  37. Tessa Harmon [email protected] @TessaHarmon Thursday, June 13, 13 - From

    Charlotte - 7 years of software dev experience, past 9 months focused on SPAs - So glad to be in Portland = my cryptonite, as a vegan and huge Elliott Smith fan. <3 jQuery. May not come home. - Co-organizer of CLTFED for over a year - Local speaker at UX/dev groups - Working locally on new initiatives to educate women about development
  38. BONUS: INTERNET EXPLORER DEBUGGING TIP Thursday, June 13, 13 Use

    the refresh button to refresh the DOM so you can inspect dynamically added elements You will also want to get comfy with Chrome developer tools so you can get a sense of performance