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
  2. INLINE JAVASCRIPT IS NOT SUSTAINABLE <div class="presentation"> <a onclick="playPresentation()"> Play

    Presentation </a> </div> 2 Monday, March 28, 2011
  3. POOR MODULARITY / REUSE CODE MAINTENANCE PROBLEMS LACK OF CACHING

    DIFFICULTY SCALING TO LARGER APPS 3 Monday, March 28, 2011
  4. UNOBTRUSIVE JAVASCRIPT PATHWAY TO THE GREAT VALLEY 4 Monday, March

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

    5 Monday, March 28, 2011
  6. SEPARATE BEHAVIOR FROM CONTENT <div class="presentation"> <a class="play"> Play Presentation

    </a> </div> $(“.presentation .play”).click(function(){ // ... }); HTML JS 6 Monday, March 28, 2011
  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
  8. THE GREAT VALLEY EFFECT 8 Monday, March 28, 2011

  9. A BETTER VANTAGE POINT SEE PATTERNS CODE DECOMPOSITION CODE ORGANIZATION

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

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

    class="presentation"> <a class="play"> Play Presentation </a> </div> HTML .presentation .play { background-image: url(...); font-size: 1.em; } CSS 11 Monday, March 28, 2011
  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
  13. BUT THEN 13 Monday, March 28, 2011

  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
  15. MONOLITHIC JAVASCRIPT FORMED OF A SINGLE LARGE FILE. 15 Monday,

    March 28, 2011
  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
  17. PATTERNS FOR AVOIDING MONOLITHS TRADITIONAL CLASS-BASED OO FUNCTIONS, CLOSURES EVENT-DRIVEN

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

    18 Monday, March 28, 2011
  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
  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
  21. var el = $(“.presentation:first”); var prez = new Presentation(prez); prez.play();

    prez.stop(); JQUERY USING THE CLASS 21 Monday, March 28, 2011
  22. BENEFITS DATA/BEHAVIOR ENCAPSULATION CLEAR BOUNDARIES OF RESPONSIBILITIES API DEFINITION MODULAR

    / REUSABLE CODE 22 Monday, March 28, 2011
  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
  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
  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
  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
  27. FEELS A LITTLE HEAVY FEELS A LITTLE AWKWARD 27 Monday,

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

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

    2011
  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
  31. var el = $(“.presentation:first”); var prez = Presentation(prez); prez.play(); prez.stop();

    JQUERY ONLY DIFFERENCE, NO “NEW” 31 Monday, March 28, 2011
  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
  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
  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
  35. FEELS MORE LIKE HOME 35 Monday, March 28, 2011

  36. WHAT IF WE DIDN’T CARE ABOUT A PUBLIC API? 36

    Monday, March 28, 2011
  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
  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
  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
  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
  41. ONE STEP FURTHER 41 Monday, March 28, 2011

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

    28, 2011
  43. 6 GUIDELINES 43 Monday, March 28, 2011

  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
  45. STRUCTURE RELIES ON FUNCTION SCOPE (function(){ })(); MODULE $(function(){ });

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

    46 Monday, March 28, 2011
  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
  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
  49. QUICKER TO SCAN. KEEPING DECLARATIONS ABOVE FUNCTION DEFINITIONS CREATES MORE

    READABLE CODE. 49 Monday, March 28, 2011
  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
  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
  52. 52 Monday, March 28, 2011

  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
  54. 54 Monday, March 28, 2011

  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