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

JavaScript Code, Organization, and Patterns

JavaScript Code, Organization, and Patterns

These are the slides from a talk I gave at Grand Rapids Web Development group in March 2011. It was right before I became familiar with CoffeeScript.

Zach Dennis

March 20, 2012
Tweet

More Decks by Zach Dennis

Other Decks in Programming

Transcript

  1. JAVASCRIPT CODE
    ORGANIZATION, PATTERNS
    ZACH DENNIS
    MUTUALLY HUMAN SOFTWARE
    1
    Monday, March 28, 2011

    View Slide

  2. INLINE JAVASCRIPT
    IS NOT SUSTAINABLE


    Play Presentation


    2
    Monday, March 28, 2011

    View Slide

  3. POOR MODULARITY / REUSE
    CODE MAINTENANCE PROBLEMS
    LACK OF CACHING
    DIFFICULTY SCALING TO LARGER APPS
    3
    Monday, March 28, 2011

    View Slide

  4. UNOBTRUSIVE JAVASCRIPT
    PATHWAY TO THE GREAT VALLEY
    4
    Monday, March 28, 2011

    View Slide

  5. 2 TECHNIQUES
    SEPARATE CONTENT FROM BEHAVIOR,
    REGISTER EVENT HANDLERS PROGRAMMATICALLY
    5
    Monday, March 28, 2011

    View Slide

  6. SEPARATE BEHAVIOR FROM
    CONTENT


    Play Presentation


    $(“.presentation .play”).click(function(){
    // ...
    });
    HTML
    JS
    6
    Monday, March 28, 2011

    View Slide

  7. REGISTER HANDLERS
    PROGRAMMATICALLY
    $(“.presentation .play”).click(function(){
    // ...
    }); JQUERY
    $(“.presentation .play”).addEvent(“click”, function(){
    // ...
    });
    MOOTOOLS
    var el = dojo.query(“.presentation .play”);
    el.connect(“click”, function(){
    // ...
    });
    DOJO
    7
    Monday, March 28, 2011

    View Slide

  8. THE GREAT VALLEY EFFECT
    8
    Monday, March 28, 2011

    View Slide

  9. A BETTER VANTAGE POINT
    SEE PATTERNS
    CODE DECOMPOSITION
    CODE ORGANIZATION
    APPLY PATTERNS
    9
    Monday, March 28, 2011

    View Slide

  10. DRIVES SEMANTIC
    SELECTOR DRIVEN CODE
    10
    Monday, March 28, 2011

    View Slide

  11. REINFORCE SEMANTIC MARKUP
    $(“.presentation .play”).click(function(){
    // ...
    });
    JS


    Play Presentation

    HTML
    .presentation .play {
    background-image: url(...);
    font-size: 1.em;
    } CSS
    11
    Monday, March 28, 2011

    View Slide

  12. SELECTOR DRIVEN CODE
    IT CAN BE SCANNED AND
    RE-ORGANIZED MORE
    EFFECTIVELY. IT’S EASIER TO
    VISUALIZE AND MANIPULATE.
    12
    Monday, March 28, 2011

    View Slide

  13. BUT THEN
    13
    Monday, March 28, 2011

    View Slide

  14. $(".presentation .play").click(function(){
    var presentation = $(this).parents('.presentation:first');
    presentation.addClass('playing');
    selectSlide(presentation.find(".slides:first"));
    });
    $(".presentation .stop").click(function(){
    $(this).parents('.deck:first').addClass('stopping');
    });
    $('.presentation a.destroy').live('ajax:success', function(data){
    var deck = $(this).parents('.deck:first');
    deck.fadeOut('fast', deck.remove);
    });
    $('#grid .slide a:last').click(function(){
    selectSlide($(this).parents(".slide:first"));
    return false;
    });
    $('img.slide').live("slide:loaded", function(){
    resizeSlide($(this));
    });
    // any time the window resizes, resize the main slide
    $(window).resize(function(){
    resizeSlide($(".slide_viewer img.slide"));
    });
    function resizeSlide(img) {
    var viewer_width = $('.slide_viewer').width();
    var viewer_height = $('.slide_viewer').height();
    // Use original width and height since the image may be scaled
    // down to a smaller size, and we want to use the original size to scale
    // the image rather than the size it is currently scaled to
    var slide_width = img.data('original_width');
    var slide_height = img.data('original_height');
    if(slide_width > viewer_width){
    ratio = viewer_width / slide_width;
    $('.slide_viewer img.slide').css({width: viewer_width, height: slide_height * ratio});
    }
    }
    page 1 of 22
    14
    Monday, March 28, 2011

    View Slide

  15. MONOLITHIC JAVASCRIPT
    FORMED OF A SINGLE LARGE FILE.
    15
    Monday, March 28, 2011

    View Slide

  16. CODE MONOLITHS
    GREAT FOR DEPLOYMENT
    BAD FOR DEVELOPMENT
    LOSES CONTEXT, HIERARCHY, SCOPE
    VISUALLY HARD TO SCAN
    CODE WITH DIFFERENT BEHAVIORS OR REASONS TO
    EXIST, CO-EXIST
    EXCEPT VERY SMALL SITES / APPS
    16
    Monday, March 28, 2011

    View Slide

  17. PATTERNS FOR AVOIDING
    MONOLITHS
    TRADITIONAL CLASS-BASED OO
    FUNCTIONS, CLOSURES
    EVENT-DRIVEN JAVASCRIPT
    17
    Monday, March 28, 2011

    View Slide

  18. TRADITIONAL CLASS-BASED OO
    APPLYING TRIED AND TRUE TECHNIQUES WITH JAVASCRIPT
    18
    Monday, March 28, 2011

    View Slide

  19. function Presentation(element) {
    this._element = element;
    this.play = element.find(“.play”);
    this.stop = element.find(“.stop”);
    this.play.bind(“click”, $.proxy(this.play, this));
    this.stop.bind(“click”, $.proxy(this.stop, this));
    };
    Presentation.prototype.play = function() {
    this._element.addClass(“playing”);
    // ...
    };
    Presentation.prototype.stop = function(hours) {
    // ...
    };
    JS
    19
    Monday, March 28, 2011

    View Slide

  20. var Presentation = $.Class.create({
    initialize: function(element) {
    this._element = element;
    this.play = element.find(“.play”);
    this.stop = element.find(“.stop”);
    this.play.bind(“click”, $.proxy(this.play, this));
    this.stop.bind(“click”, $.proxy(this.stop, this));
    },
    play: function(){
    this._element.addClass(“playing”);
    // ...
    },
    stop: function(){
    // ...
    },
    });
    JQUERY
    20
    Monday, March 28, 2011

    View Slide

  21. var el = $(“.presentation:first”);
    var prez = new Presentation(prez);
    prez.play();
    prez.stop();
    JQUERY
    USING THE CLASS
    21
    Monday, March 28, 2011

    View Slide

  22. BENEFITS
    DATA/BEHAVIOR ENCAPSULATION
    CLEAR BOUNDARIES OF RESPONSIBILITIES
    API DEFINITION
    MODULAR / REUSABLE CODE
    22
    Monday, March 28, 2011

    View Slide

  23. ASSIGN PSEUDO-PRIVATE VARIABLE
    var Presentation = $.Class.create({
    initialize: function(element) {
    this.play = element.find(“.play”);
    this.stop = element.find(“.stop”);
    this.play.bind(“click”, $.proxy(this.play, this));
    this.stop.bind(“click”, $.proxy(this.stop, this));
    },
    play: function(){
    // ...
    },
    stop: function(){
    // ...
    },
    });
    this._element = element;
    this._element.addClass(“playing”);
    JQUERY
    23
    Monday, March 28, 2011

    View Slide

  24. var Presentation = $.Class.create({
    initialize: function(element) {
    this._element = element;
    this.play.bind(“click”, $.proxy(this.play, this));
    this.stop.bind(“click”, $.proxy(this.stop, this));
    },
    play: function(){
    this._element.addClass(“playing”);
    // ...
    },
    stop: function(){
    // ...
    },
    });
    this.play = element.find(“.play”);
    this.stop = element.find(“.stop”);
    FIND AND ASSIGN ELEMENTS WE NEED ACCESS TO
    JQUERY
    24
    Monday, March 28, 2011

    View Slide

  25. var Presentation = $.Class.create({
    initialize: function(element) {
    this._element = element;
    this.play = element.find(“.play”);
    this.stop = element.find(“.stop”);
    },
    play: function(){
    this._element.addClass(“playing”);
    // ...
    },
    stop: function(){
    // ...
    this.play.bind(“click”, $.proxy(this.play, this));
    this.stop.bind(“click”, $.proxy(this.stop, this));
    JQUERY
    REGISTER EVENT HANDLERS
    25
    Monday, March 28, 2011

    View Slide

  26. var Presentation = $.Class.create({
    initialize: function(element) {
    this._element = element;
    this.play = element.find(“.play”);
    this.stop = element.find(“.stop”);
    },
    play: function(){
    ._element.addClass(“playing”);
    // ...
    },
    stop: function(){
    // ...
    this.play.bind(“click”, );
    this.stop.bind(“click”, );
    JQUERY
    KEEP REFERENCE TO THIS
    $.proxy(this.play, this);
    $.proxy(this.stop, this)
    this
    26
    Monday, March 28, 2011

    View Slide

  27. FEELS A LITTLE HEAVY
    FEELS A LITTLE AWKWARD
    27
    Monday, March 28, 2011

    View Slide

  28. SAME EXAMPLE,
    DIFFERENT PATTERN
    28
    Monday, March 28, 2011

    View Slide

  29. FUNCTIONS, CLOSURES
    TAKING ADVANTAGE OF JAVASCRIPT
    29
    Monday, March 28, 2011

    View Slide

  30. var Presentation = function(element){
    var presentation = element;
    presentation.delegate(".play", "click", play);
    presentation.delegate(".stop", "click", stop);
    function play(){
    presentation.addClass(“playing”);
    // ...
    }
    function stop(){
    // ...
    }
    return {
    play: play,
    stop: stop
    }
    };
    JQUERY
    FUNCTIONS, SCOPE, CLOSURES
    30
    Monday, March 28, 2011

    View Slide

  31. var el = $(“.presentation:first”);
    var prez = Presentation(prez);
    prez.play();
    prez.stop();
    JQUERY
    ONLY DIFFERENCE, NO “NEW”
    31
    Monday, March 28, 2011

    View Slide

  32. var Presentation = function(element){
    presentation.delegate(".play", "click", play);
    presentation.delegate(".stop", "click", stop);
    function play(){
    // ...
    }
    function stop(){
    // ...
    }
    return {
    play: play,
    stop: stop
    }
    }; JQUERY
    var presentation = element;
    ACCESSIBLE PRIVATE VARIABLES
    presentation.addClass(“playing”);
    32
    Monday, March 28, 2011

    View Slide

  33. var Presentation = function(element){
    var presentation = element;
    function play(){
    presentation.addClass(“playing”);
    // ...
    }
    function stop(){
    // ...
    }
    return {
    play: play,
    stop: stop
    }
    }; JQUERY
    STRAIGHT FORWARD EVENT DELEGATION
    presentation.delegate(".play", "click", play);
    presentation.delegate(".stop", "click", stop);
    33
    Monday, March 28, 2011

    View Slide

  34. var Presentation = function(element){
    var presentation = element;
    presentation.delegate(".play", "click", play);
    presentation.delegate(".stop", "click", stop);
    function play(){
    presentation.addClass(“playing”);
    // ...
    };
    function stop(){
    // ...
    };
    return {
    };
    }; JQUERY
    API DEFINITION
    play: play,
    stop: stop
    34
    Monday, March 28, 2011

    View Slide

  35. FEELS MORE LIKE HOME
    35
    Monday, March 28, 2011

    View Slide

  36. WHAT IF WE DIDN’T
    CARE ABOUT A
    PUBLIC API?
    36
    Monday, March 28, 2011

    View Slide

  37. (function(){
    $(".presentation").delegate(".play", "click", play);
    $(".presentation").delegate(".stop", "click", stop);
    function play(){
    $(this).parents(“.presentation:first”).addClass(“playing”);
    // ...
    }
    function stop(){
    // ...
    }
    })();
    SHORTER. SIMPLER. SWEETER.
    JQUERY
    37
    Monday, March 28, 2011

    View Slide

  38. CREATED A FUNCTION, EXECUTED IT
    REMOVE UNNECESSARY VARS
    RELY ON SELECTOR-DRIVEN EVENT HANDLERS
    NO NEED TO MANUALLY BIND TO ELEMENTS
    WHAT JUST HAPPENED?
    38
    Monday, March 28, 2011

    View Slide

  39. var Presentation = (function(element){
    var presentation = element;
    presentation.delegate(".play", "click", play);
    presentation.delegate(".stop", "click", stop);
    function play(){
    presentation.addClass(“playing”);
    // ...
    }
    function stop(){
    // ...
    }
    return {
    play: playPresentation,
    stop: stopPresentation
    }
    });
    function(){
    $(".presentation").delegate(".play", "click", play);
    $(".presentation").delegate(".stop", "click", stop);
    function play(){
    $(this).parents(“.presentation:first”).addClass(“playing”);
    // ...
    }
    function stop(){
    // ...
    }
    }();
    var Presentation = $.Class.create({
    initialize: function(element) {
    this._element = element;
    this.play = element.find(“.play”);
    this.stop = element.find(“.stop”);
    this.play.bind(“click”, $.proxy(this.play, this));
    this.stop.bind(“click”, $.proxy(this.stop, this));
    },
    play: function(){
    this._element.addClass(“playing”);
    // ...
    },
    stop: function(){
    // ...
    }
    });
    CLASS-BASED FUNCTION, CLOSURES EVENT-DRIVEN
    39
    Monday, March 28, 2011

    View Slide

  40. FUNCTIONS, CLOSURES RECAP
    USE FUNCTIONS AND CLOSURES TO CREATE SCOPE
    PRESERVE PRIVATE METHODS AND PROPERTIES WITH
    VAR STATEMENTS
    RETURN PUBLIC METHODS, PROPERTIES (OPTIONAL)
    AND WE DON’T POLLUTE GLOBAL NAMESPACE
    40
    Monday, March 28, 2011

    View Slide

  41. ONE STEP FURTHER
    41
    Monday, March 28, 2011

    View Slide

  42. EVENT-DRIVEN JAVASCRIPT
    DECLARATIVELY READABLE SELECTOR DRIVEN CODE
    42
    Monday, March 28, 2011

    View Slide

  43. 6 GUIDELINES
    43
    Monday, March 28, 2011

    View Slide

  44. MEANINGFUL FILE NAMES TO
    ORGANIZE BEHAVIOR
    PRESENTER.JS
    VIEWER.JS
    SLIDES/
    PRESENTER/
    REMOTE-CONTROLS.JS
    VIEWER/
    RESIZING.JS
    REMOTE-CONTROLS.JS
    GRID.JS
    *USE COMBINATOR, COMPRESSOR, MINIFIER FOR DEPLOYMENT
    44
    Monday, March 28, 2011

    View Slide

  45. STRUCTURE RELIES ON
    FUNCTION SCOPE
    (function(){
    })();
    MODULE
    $(function(){
    });
    MODULE
    JQUERY
    EXECUTES IMMEDIATELY
    EXECUTES AFTER DOM READY
    JS
    45
    Monday, March 28, 2011

    View Slide

  46. DECLARE PAGE CHECKS FIRST
    $(function(){
    });
    JQUERY
    if(!$("html.presenter").length) return;
    PRESENTER.JS
    46
    Monday, March 28, 2011

    View Slide

  47. DECLARE HANDLERS AND VARS
    SECOND
    $(function(){
    if(!$("html.presenter").length) return;
    });
    var presentation = $(“.presentation”),
    attendee_count = 0;
    presentation.delegate(“.play”, “click”, play);
    presentation.delegate(“.stop”, “click”, stop);
    presentation.delegate(“.slide .next”, “click”, nextSlide);
    presentation.delegate(“.slide .prev”, “click”, prevSlide);
    JQUERY
    PRESENTER.JS
    47
    Monday, March 28, 2011

    View Slide

  48. DECLARE FUNCTIONS LAST
    $(function(){
    if(!$("html.presenter").length) return;
    var presentation = $(“.presentation”),
    attendee_count = 0;
    presentation.delegate(“.play”, “click”, play);
    presentation.delegate(“.stop”, “click”, stop);
    presentation.delegate(“.slide .next”, “click”, nextSlide);
    presentation.delegate(“.slide .prev”, “click”, prevSlide);
    function play(){
    // ...
    };
    function stop(){
    // ...
    JQUERY
    PRESENTER.JS
    48
    Monday, March 28, 2011

    View Slide

  49. QUICKER TO SCAN. KEEPING
    DECLARATIONS ABOVE
    FUNCTION DEFINITIONS
    CREATES MORE
    READABLE CODE.
    49
    Monday, March 28, 2011

    View Slide

  50. EVENTS DRIVE CROSS-CLOSURE
    COMMUNICATION
    function nextSlide(){
    var prez = $(this).parents('.presentation:first');
    // ...
    current_slide.removeClass('current');
    next_slide.addClass('current');
    };
    prez.trigger('slide:changed', { slide: next_slide });
    $("body").delegate(".presentation", "slide:changed", transitionToSlide);
    THUMBNAIL-CONTROLS.JS
    PRESENTER.JS
    $("body").delegate(".presentation", "slide:changed", changeSlideOnRemoteViewers);
    REMOTE-VIEWER-CONTROLS.JS
    _
    _
    _
    50
    Monday, March 28, 2011

    View Slide

  51. BENEFITS
    FUNCTIONS AND CLOSURES ALLOW GROUPING OF
    COMMON BEHAVIOR AND DATA
    CUSTOM EVENTS ARE AWESOME
    NO NEED TO HAVE REFERENCES TO EXTERNAL
    OBJECTS THROUGHOUT OUR APP
    LOOSER COUPLING
    EASIER TO HOOK IN NEW PARTS OF OUR APP WITH
    MINIMAL IMPACT TO EXISTING CODE
    51
    Monday, March 28, 2011

    View Slide

  52. 52
    Monday, March 28, 2011

    View Slide

  53. EVENT-DRIVEN HOW WE GOT THERE
    MEANINGFUL DIRECTORY AND FILE NAMES
    FOLLOW MODULE OR SIMILAR CLOSURE-PATTERN
    GENERAL GUIDELINES FOR READABILITY:
    DECLARE PAGE CHECKS FIRST
    DECLARE HANDLERS AND VARS SECOND
    DECLARE FUNCTIONS LAST
    USE EVENTS TO DRIVE CROSS-CLOSURE
    COMMUNICATION
    53
    Monday, March 28, 2011

    View Slide

  54. 54
    Monday, March 28, 2011

    View Slide

  55. IN SUMMARY
    INLINE JAVASCRIPT IS NOT A SUSTAINABLE APPROACH.
    UNOBTRUSIVE JAVASCRIPT IS A PATH TO “THE GREAT VALLEY”
    MONOLITHIC UJS WORKS FINE FOR SMALL APPS, BUT DOESN’T SCALE WELL.
    FORTUNATELY SELECTOR-DRIVEN JS IS EASIER TO MANIPULATE AND
    REORGANIZE.
    TRADITIONAL CLASS-BASED OO + UJS WORKS WELL, BUT CAN AT TIMES BE A
    BIT HEAVY
    FUNCTION/CLOSURES ARE A LIGHTER WEIGHT APPROACH THAN TRADITIONAL
    OO. FEELS MORE JAVASCRIPTY.
    EVENT-DRIVEN APPROACH WITH EMPHASIS ON FUNCTION/CLOSURES FOR SCOPE
    AND DECLARATIVE SELECTOR-DRIVEN CODE IS LEANER, SCALABLE AND
    PROMOTES LOOSE COOUPLING
    55
    Monday, March 28, 2011

    View Slide