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

Sneaking structure into your DOM-based application

Garann Means
November 16, 2011

Sneaking structure into your DOM-based application

You thought you were building a proof of concept, but then that proof of concept went live. Or you had two weeks to build what should have taken two months. Or the handful of progressive enhancements you threw onto a page to make the user experience a little nicer somehow evolved into an entire single-page app. Whatever the reason, you find yourself with a full-blown application built around click events and a staggering number of plugins you can't even remember downloading. If you could rewrite it, you'd use a framework built with your scenario in mind, but it gets 17 zillion hits a day and there's only one of you and starting from scratch isn't an option. No, what you need is the philosophy of a framework broken into discrete pieces that fit into a one- or two-week release cycle. This talk aims to provide bite-sized strategies you can implement in a short amount of time with minimal disruption to unchain your application from the DOM.

Garann Means

November 16, 2011
Tweet

More Decks by Garann Means

Other Decks in Technology

Transcript

  1. sneaking structure into
    your DOM-based
    application
    1
    Monday, November 14, 2011

    View full-size slide

  2. ohai
    ☛ Garann Means
    ☛ Austin All-Girl Hack Night
    ☛ Girl Develop It Austin
    ☛ @garannm / garann.com
    2
    Monday, November 14, 2011

    View full-size slide

  3. $(document).ready(function() {
    ! $.get("pages/title.html",function(r) {
    ! ! $(".page_text").html(r);
    ! }, "html");
    ! setInterval(function() {
    ! ! var currentpage = parseInt($("#currentpage").val()),
    ! ! ! num = window.location.hash.substring(1) || 0;
    ! ! if (currentpage != num) {
    ! ! ! goToPage(num);
    ! ! }
    ! }, 100);
    ! $(".continue a").click(function(e) {
    ! ! e.preventDefault();
    ! ! goToPage(parseInt($("#currentpage").val())+1);
    ! });!
    ! $(".choose a").click(function(e) {
    ! ! e.preventDefault();
    ! ! var newpage = $(this).attr("rel");
    ! ! goToPage(newpage);
    ! });!
    ! $(document).bind("keydown",function(e) {! !
    ! ! clearTimeout(typingPause);! !
    ! ! var currentpage = parseInt($("#currentpage").val());
    ! ! var k = keys[e.keyCode];
    ! ! if (k == "left") {
    ! ! ! currentpage--;
    ! ! ! goToPage(currentpage);
    ! ! } else if (k == "right") {
    ! ! ! currentpage++;
    ! ! ! goToPage(currentpage);
    ! ! } else if (k) {
    ! ! ! newpage += "" + k;
    ! ! ! typingPause = setTimeout(
    ! ! ! ! function() {
    ! ! ! ! ! goToPage(newpage);
    ! ! ! ! }, 500);
    ! ! }! !
    ! });!
    ! $('.pan-container').each(function(){
    ! ! var $this=$(this).css({position:'relative', overflow:'hidden', cursor:'move'})
    ! ! var $img=$this.children().eq(0) //image to pan
    ! ! var options={$pancontainer:$this, pos:$this.attr('data-orient'), curzoom:1, canzoom:$this.attr('data-canzoom'), wrappersize:[$this.width(),
    ! ! $img.imgmover(options)
    ! })
    });
    var newpage = "";
    var typingPause;
    function goToPage(pagenum, pagetext, pageimg) {!
    ! $("#currentpage").val(pagenum);
    ! window.location.hash = pagenum;!
    ! newpage = "";
    ! parseInt(pagenum) ? $("h3 span").text(pagenum) : $("h3 span").text("");! !
    ! $.get("pages/" + pagetext,function(r) {
    ! ! $(".page_text").html(r);! !
    ! ! pageimg ? $(".page_image").html('') : $(".page_image").html("");
    ! ! if ($(".page_image").children().length) {
    ! ! ! $(".page_image").children().each(function() {
    ! ! ! ! $(this).zoomin({
    ! ! ! ! ! bgcolor: "#999"
    ! ! ! ! });
    ! ! ! });
    ! ! }! !
    ! ! $(".continue a").click(function(e) {
    ! ! ! e.preventDefault();
    ! ! ! goToPage(parseInt($("#currentpage").val())+1);
    ! ! });! !
    ! ! $(".choose a").click(function(e) {
    ! ! ! e.preventDefault();
    ! ! ! var newpage = $(this).attr("rel");
    ! ! ! goToPage(newpage);
    ! ! });
    3
    Monday, November 14, 2011

    View full-size slide

  4. you have a problem.
    ☛ old code
    ☛ other people’s code
    ☛ written too fast
    ☛ scope creep
    4
    Monday, November 14, 2011

    View full-size slide

  5. but..
    http://www.flickr.com/photos/vintagechica/5597949576/
    5
    Monday, November 14, 2011

    View full-size slide

  6. your baby is ugly.
    http://www.flickr.com/photos/quinnanya/2885102816/in/photostream/
    6
    Monday, November 14, 2011

    View full-size slide

  7. your baby is ugly.
    ☛ window.everything
    ☛ data in HTML
    ☛ all the plugins ever!!
    ☛ $(...).click(manageState)
    ☛ jQuery 1.oldAndBusted
    7
    Monday, November 14, 2011

    View full-size slide

  8. wontfix?
    ☛ performance gets worse
    ☛ hacks beget hacks
    ☛ unmaintainable
    ☛ un-upgradable
    8
    Monday, November 14, 2011

    View full-size slide

  9. rewrite from scratch?
    ☛ undocumented business logic
    ☛ new bugs for old bugs
    ☛ business people will freak out
    ☛ users will be impatient
    ☛ how long you got?
    9
    Monday, November 14, 2011

    View full-size slide

  10. code written under
    duress is probably what
    got you here
    10
    Monday, November 14, 2011

    View full-size slide

  11. why ‘sneaky’
    ☛ business people see their features
    ☛ users see bugs fixed
    ☛ you see something other than the back of a
    giant boulder
    11
    Monday, November 14, 2011

    View full-size slide

  12. it’s gonna get dirty
    http://www.flickr.com/photos/johnpaulgoguen/3359392738/
    12
    Monday, November 14, 2011

    View full-size slide

  13. it’s gonna get ridiculous
    http://www.flickr.com/photos/mhaithaca/447863503/
    13
    Monday, November 14, 2011

    View full-size slide

  14. where you want to be
    14
    Monday, November 14, 2011

    View full-size slide

  15. where you want to be
    ☛ namespacing
    ☛ data separate from DOM
    ☛ not dependent on plugins
    ☛ state management separate from DOM
    ☛ jQuery 1.newHotness
    14
    Monday, November 14, 2011

    View full-size slide

  16. (and then you can think
    about..)
    ☛ MVC
    ☛ AMD
    ☛ unit tests
    ☛ event delegation
    ☛ deferreds
    ☛ etc.
    15
    Monday, November 14, 2011

    View full-size slide

  17. keep it simple
    ☛ one release at a time
    ☛ stick to the plan
    ☛ don’t get scared
    16
    Monday, November 14, 2011

    View full-size slide

  18. let’s get cracking
    17
    Monday, November 14, 2011

    View full-size slide

  19. release 0
    ☛ take stock
    ☛ add TODOs
    ☛ basic reuse
    18
    Monday, November 14, 2011

    View full-size slide

  20. TODO
    ! // TODO: find something less icky
    ! setInterval(function() {
    ! ! var currentpage = parseInt($("#currentpage").val()),
    ! ! ! num = window.location.hash.substring(1) || 0;
    ! ! if (currentpage != num) {
    ! ! ! goToPage(num);
    ! ! }
    ! }, 100);
    !
    ! // TODO: rethink this whole thing
    ! $(document).bind("keydown",function(e) {
    ! !
    ! ! clearTimeout(typingPause);
    ! ! ...
    ! ! var currentpage = parseInt($("#currentpage").val());
    ! ! var k = keys[e.keyCode];
    19
    Monday, November 14, 2011

    View full-size slide

  21. cache/improve selectors
    ! newpage = "";
    ! parseInt(pagenum) ?
    ! ! $("h3 span").text(pagenum) :
    ! ! $("h3 span").text("");!
    ! $.get("pages/" + pagetext,function(r) {
    ! ! $(".page_text").html(r);! !
    ! ! pageimg ?
    ! ! ! $(".page_image").html('! ! ! $(".page_image").html("");
    ! ! if ($(".page_image").children().length) {
    ! ! ! $(".page_image").children().each(function() {
    ! ! ! ! $(this).zoomin({
    ! ! ! ! ! bgcolor: "#999"
    ! ! ! ! });
    ! ! ! });
    ! ! }
    ! ! ...
    20
    Monday, November 14, 2011

    View full-size slide

  22. cache/improve selectors
    ! // r0: saved .page_image selector
    ! var $h = $("h3 span"),
    ! ! $p = $("div.page_image");
    ! newpage = "";
    ! parseInt(pagenum) ? $h.text(pagenum) : $h.text("");!
    ! $.get("pages/" + pagetext,function(r) {
    ! ! $(".page_text").html(r);! !
    ! ! pageimg ?
    ! ! ! $p.html('') :
    ! ! ! $p.html("");
    ! ! var imgs = $p.children();
    ! ! if (imgs.length) {
    ! ! ! $.each(imgs,function() {
    ! ! ! ! $(this).zoomin({
    ! ! ! ! ! bgcolor: "#999"
    ! ! ! ! });
    ! ! ! });
    21
    Monday, November 14, 2011

    View full-size slide

  23. avoid repetition
    $(document).ready(function() {
    ! $.get("pages/title.html",function(r) {
    ! ! $(".page_text").html(r);
    ! }, "html");
    !
    ! $(".continue a").click(function(e) {
    ! ! e.preventDefault();
    ! ! goToPage(parseInt($("#currentpage").val())+1);
    ! });
    !
    ! $(".choose a").click(function(e) {
    ! ! e.preventDefault();
    ! ! var newpage = $(this).attr("rel");
    ! ! goToPage(newpage);
    ! });
    });
    22
    Monday, November 14, 2011

    View full-size slide

  24. avoid repetition
    $(document).ready(function() {
    !
    ! // r0: removed repeated code (+ link wireups)
    ! goToPage(0);
    });
    23
    Monday, November 14, 2011

    View full-size slide

  25. now you have..
    ☛ tasks defined
    ☛ reuse
    ☛ abstraction
    24
    Monday, November 14, 2011

    View full-size slide

  26. release 1
    ☛ all your code under one namespace
    ☛ that’s it.
    ☛ find, replace, test
    ☛ memory lane
    25
    Monday, November 14, 2011

    View full-size slide

  27. leave window alone
    var newpage = "";
    // TODO: is this necessary?
    var keys = {
    ! "37": "left",
    ! "39": "right"
    };
    var typingPause;
    // TODO: fewer arguments
    function goToPage(pagenum, pagetext, pageimg) {
    ! ...
    26
    Monday, November 14, 2011

    View full-size slide

  28. leave window alone
    // r1: added this namespace
    var cyoa = cyoa || {
    ! ! newpage: "",
    ! ! // TODO: is this necessary?
    ! ! keys: {
    ! ! ! "37": "left",
    ! ! ! "39": "right"
    ! ! },
    ! ! typingPause: null
    ! };
    // TODO: fewer arguments
    cyoa.goToPage = function(pagenum, pagetext, pageimg) {
    27
    Monday, November 14, 2011

    View full-size slide

  29. check for inline JS
    click here!!1
    <br/>if (newpage == “”) document.write(“no new page to load”);<br/>
    28
    Monday, November 14, 2011

    View full-size slide

  30. check external code
    // r1: added this namespace
    var cyoa = cyoa || {
    ! ! newpage: "",
    ! ! ...
    ! ! typingPause: null
    ! };
    // r1: well it would have been cool, anyway..
    var newpage = function() {
    ! return cyoa.newpage;
    };
    29
    Monday, November 14, 2011

    View full-size slide

  31. now you have..
    ☛ your stuff is isolated
    ☛ group pieces of app
    ☛ good overview
    30
    Monday, November 14, 2011

    View full-size slide

  32. release 2
    ☛ hidden fields
    ☛ attributes
    ☛ add new objects at the right level
    ☛ stay out of display code.. for now
    31
    Monday, November 14, 2011

    View full-size slide

  33. val()
    // TODO: fewer arguments
    cyoa.goToPage = function(pagenum, pagetext, pageimg) {
    ! ...
    ! $("#currentpage").val(pagenum);
    ! window.location.hash = pagenum;
    32
    Monday, November 14, 2011

    View full-size slide

  34. val()
    // TODO: fewer arguments
    cyoa.goToPage = function(pagenum, pagetext, pageimg) {
    ! ...
    ! cyoa.currentPage = pagenum;
    ! window.location.hash = pagenum;
    33
    Monday, November 14, 2011

    View full-size slide

  35. attr()
    If you decide to refactor, turn to page 4.
    $("div.choose a").click(function(e) {
    ! e.preventDefault();
    ! var newpage = $(this).attr("rel");
    ! cyoa.goToPage(newpage);
    });
    34
    Monday, November 14, 2011

    View full-size slide

  36. attr()
    If you decide to refactor, turn to page 4.
    (or)
    // TODO: add to state object
    $("div.choose a").click(function(e) {
    ! e.preventDefault();
    ! var newpage = $(this).attr("rel");
    ! cyoa.goToPage(newpage);
    });
    35
    Monday, November 14, 2011

    View full-size slide

  37. now you have..
    ☛ DOM data vs. non-DOM data
    ☛ access data more quickly
    ☛ change HTML without breaking app
    36
    Monday, November 14, 2011

    View full-size slide

  38. release 3
    ☛ isolate existing plugins
    ☛ formalize widgets
    ☛ my.plugin = function($t) or $.fn.plugin
    37
    Monday, November 14, 2011

    View full-size slide

  39. wrapping plugins
    ! ! var imgs = $p.children();
    ! ! if (imgs.length) {
    ! ! ! $.each(imgs,function() {
    ! ! ! ! $(this).zoomin({
    ! ! ! ! ! bgcolor: "#999"
    ! ! ! ! });
    ! ! ! });
    ! ! }
    38
    Monday, November 14, 2011

    View full-size slide

  40. wrapping plugins
    ! ! // r3: removed plugin setup code
    ! ! cyoa.setUpZoom($p.children());
    ...
    cyoa = {
    ! ! setUpZoom: function($t) {
    ! ! ! if ($t.length) {
    ! ! ! ! $t.each(function() {
    ! ! ! ! ! cyoa.zoomin($(this), {
    ! ! ! ! ! ! bgcolor: "#999"
    ! ! ! ! ! });
    ! ! ! ! });
    ! ! ! }
    ! ! }
    }
    39
    Monday, November 14, 2011

    View full-size slide

  41. not $.fn.everything
    ☛ known element type?
    ☛ known class?
    ☛ known properties?
    ☛ known length?
    ☛ that’s a widget, y’all
    40
    Monday, November 14, 2011

    View full-size slide

  42. now you have..
    ☛ easy plugin swap/upgrade
    ☛ app-specific stock of widgets
    ☛ your junk out of $.fn.*
    ☛ widgets within relevant state in...
    41
    Monday, November 14, 2011

    View full-size slide

  43. release 4
    ☛ state objects
    ☛ steps in arrays
    ☛ non-linear states: myApp.states[“thisState”]
    ☛ state functions
    ☛ init
    ☛ change state
    ☛ error
    42
    Monday, November 14, 2011

    View full-size slide

  44. confine state info
    // TODO: fewer arguments
    cyoa.goToPage = function(pagenum, pagetext, pageimg) {
    ! // TODO: put this logic someplace else
    ! if (!pagetext) {
    ! ! switch (parseInt(pagenum)) {
    ! ! ! case 0:
    ! ! ! ! cyoa.goToPage0();
    ! ! ! ! break;
    ! ! ! case 1:
    ! ! ! ! cyoa.goToPage1();
    ! ! ! ! break;
    ! ! ! case 2:
    ! ! ! ! cyoa.goToPage2();
    ! ! ! ! break;
    ! ! ! ...
    43
    Monday, November 14, 2011

    View full-size slide

  45. confine state info
    // r4: added more structured state management
    cyoa.state = {
    ! init: function() {
    ! ! var num = window.location.hash.substring(1) || 0;
    ! ! cyoa.state.goToPage(num);
    ! },
    !
    ! goToPage: function(pagenum) {
    ! !
    ! ! // r4: check that page is valid
    ! ! pagenum = parseInt(pagenum);
    ! ! if (pagenum < 0 || pagenum >= cyoa.states.length)
    ! ! ! return;
    ! !
    ! ! cyoa.currentPage = pagenum;
    ! ! window.location.hash = pagenum;
    44
    Monday, November 14, 2011

    View full-size slide

  46. confine state info
    // TODO: NOT THIS.
    cyoa.goToPage0 = function() {
    ! cyoa.goToPage(0,"title.html");
    }
    cyoa.goToPage1 = function() {
    ! cyoa.goToPage(1,"whatToDo.html");
    }
    cyoa.goToPage2 = function() {
    ! cyoa.goToPage(2,"youHaveDied.html","explosion.jpg");
    }
    cyoa.goToPage3 = function() {
    ! cyoa.goToPage(3,"youHaveDied.html","sisyphus.jpg");
    }
    cyoa.goToPage4 = function() {
    ! cyoa.goToPage(4,"sellIt.html");
    }
    45
    Monday, November 14, 2011

    View full-size slide

  47. confine state info
    // r4: created array with page/state info
    cyoa.states = [
    ! {page: "title.html"},!
    ! {page: "whatToDo.html"},!
    ! {page: "youHaveDied.html", image: "explosion.jpg"},!
    ! {page: "youHaveDied.html", image: "sisyphus.jpg"},!
    ! {page: "sellIt.html"}
    ];
    // extra credit: routing info
    46
    Monday, November 14, 2011

    View full-size slide

  48. now you have..
    ☛ state differences encapsulated
    ☛ single place to switch state
    ☛ clear definitions
    ☛ add states cleanly
    ☛ add in routing with less risk
    47
    Monday, November 14, 2011

    View full-size slide

  49. release 5
    ☛ pub/sub instead of event handlers
    ☛ reduce anonymous functions
    ☛ event handlers manage $(this)
    ☛ onreadystatechange publishes
    48
    Monday, November 14, 2011

    View full-size slide

  50. publish/subscribe
    ! ! $.get("pages/" + state.page,function(r) {
    ! ! ! $("div.page_text").html(r);
    ! ! !
    ! ! ! img ?
    ! ! ! ! $p.html('') :
    ! ! ! ! $p.html("");
    ! ! ! // r3: removed plugin setup code
    ! ! ! cyoa.setUpZoom($p.children());
    ! ! !
    ! ! ! $("div.continue a").click(function(e) {
    ! ! ! ! e.preventDefault();
    ! ! ! ! cyoa.state.goToPage(cyoa.currentPage+1);
    ! ! ! });
    ! ! !
    ! ! ! ...!
    ! ! !
    ! ! }, "html");
    49
    Monday, November 14, 2011

    View full-size slide

  51. publish/subscribe
    ! ! $.get("pages/" + state.page,function(r) {!
    ! !
    ! ! ! cyoa.event.publish("pageLoaded", [r]);!
    ! ! }, "html");
    ...
    ! cyoa.event.subscribe("pageLoaded", function(r) {!
    ! !
    ! ! $("div.page_text").html(r);
    ! !
    ! ! $("div.continue a").click(function(e) {
    ! ! ! e.preventDefault();
    ! ! ! cyoa.currentPage += 1;
    ! ! });
    ! ! !
    ! });
    50
    Monday, November 14, 2011

    View full-size slide

  52. pub/sub and get/set
    cyoa = {
    ! ! _currentPage: 0,
    ! ! // r5: getter and setter for currentPage
    ! ! get currentPage() { return this._currentPage; },
    ! ! set currentPage(n) {
    ! ! ! if (n < 0 || n >= cyoa.states.length) return;
    ! ! ! this._currentPage = n;
    ! ! ! cyoa.event.publish("pageChanged");
    ! ! }
    }
    ...
    cyoa.event.subscribe("pageChanged", function(r) {!!
    ! cyoa.state.goToPage();
    });
    51
    Monday, November 14, 2011

    View full-size slide

  53. now you have..
    ☛ application events vs. DOM events
    ☛ no manual callback chains
    ☛ easily add features that observe events
    ☛ control state through properties
    52
    Monday, November 14, 2011

    View full-size slide

  54. release 6
    ☛ upgrade
    ☛ regression test
    ☛ swap out non-forward-compatible plugins
    ☛ regression test
    ☛ maybe roll back (sorries :( )
    53
    Monday, November 14, 2011

    View full-size slide

  55. upgrade and test
    ! cyoa.event.subscribe("pageLoaded", function(r) {!
    ! !
    ! ! $("div.page_text").html(r);
    ! !
    ! ! $("div.continue a").click(function(e) {
    ! ! ! e.preventDefault();
    ! ! ! cyoa.currentPage += 1;
    ! ! });
    ! ! !
    ! });
    54
    Monday, November 14, 2011

    View full-size slide

  56. upgrade and test
    ! // r6: don't keep binding this every time
    ! $("div.page_text")
    ! ! .delegate("div.continue a","click",function(e) {
    ! ! e.preventDefault();
    ! ! cyoa.currentPage += 1;
    ! });
    55
    Monday, November 14, 2011

    View full-size slide

  57. when you can’t upgrade
    ☛ bugs should be easier to find
    ☛ fix them
    ☛ keep trying
    ☛ ala carte features
    56
    Monday, November 14, 2011

    View full-size slide

  58. but once you do.. !!
    ☛ dependency management
    ☛ event delegation
    ☛ deferreds
    ☛ unit tests
    ☛ documentation
    ☛ ..framework? maybe?
    57
    Monday, November 14, 2011

    View full-size slide

  59. nobody saw a thing
    ☛ business people: “Oh I thought you finished
    that five releases ago?”
    ☛ users: less “It’s too slow,” more “Why can’t I
    make this have polka dots?”
    58
    Monday, November 14, 2011

    View full-size slide

  60. you:
    ☛ “:D”
    ☛ can develop faster
    ☛ can fix easier
    ☛ can get hit by all the buses you want
    59
    Monday, November 14, 2011

    View full-size slide

  61. hey, since you did such a
    super job on that
    refactor...
    60
    Monday, November 14, 2011

    View full-size slide

  62. thanks!
    ☛ who’s got questions?
    ☛ shy questions:
    ☛ @garannm
    [email protected]
    61
    Monday, November 14, 2011

    View full-size slide