Successfully reported this slideshow.

JavaScript Code Organizations, Patterns Slides - Zach Dennis

5,385 views

Published on

Slides from the "JavaScript Code Organizations, Pattern" talk by Zach Dennis from Mutually Human Software at the
GRWebDev meeting on March 28th, 2011

Published in: Technology
  • Be the first to comment

JavaScript Code Organizations, Patterns Slides - Zach Dennis

  1. 1. JAVASCRIPT CODE ORGANIZATION, PATTERNS ZACH DENNIS MUTUALLY HUMAN SOFTWAREMonday, March 28, 2011 1
  2. 2. <div class="presentation"> <a onclick="playPresentation()"> Play Presentation </a> </div> INLINE JAVASCRIPT IS NOT SUSTAINABLEMonday, March 28, 2011 2
  3. 3. POOR MODULARITY / REUSE CODE MAINTENANCE PROBLEMS LACK OF CACHING DIFFICULTY SCALING TO LARGER APPSMonday, March 28, 2011 3
  4. 4. UNOBTRUSIVE JAVASCRIPT PATHWAY TO THE GREAT VALLEYMonday, March 28, 2011 4
  5. 5. 2 TECHNIQUES SEPARATE CONTENT FROM BEHAVIOR, REGISTER EVENT HANDLERS PROGRAMMATICALLYMonday, March 28, 2011 5
  6. 6. SEPARATE BEHAVIOR FROM CONTENT <div class="presentation"> <a class="play"> Play Presentation </a> </div> HTML $(“.presentation .play”).click(function(){ // ... }); JSMonday, March 28, 2011 6
  7. 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(){ // ... }); DOJOMonday, March 28, 2011 7
  8. 8. THE GREAT VALLEY EFFECTMonday, March 28, 2011 8
  9. 9. A BETTER VANTAGE POINT SEE PATTERNS CODE DECOMPOSITION CODE ORGANIZATION APPLY PATTERNSMonday, March 28, 2011 9
  10. 10. DRIVES SEMANTIC SELECTOR DRIVEN CODEMonday, March 28, 2011 10
  11. 11. REINFORCE SEMANTIC MARKUP <div class="presentation"> <a class="play"> Play Presentation </a> </div> HTML $(“.presentation .play”).click(function(){ // ... }); JS .presentation .play { background-image: url(...); font-size: 1.em; } CSSMonday, March 28, 2011 11
  12. 12. SELECTOR DRIVEN CODE IT CAN BE SCANNED AND RE-ORGANIZED MORE EFFECTIVELY. IT’S EASIER TO VISUALIZE AND MANIPULATE.Monday, March 28, 2011 12
  13. 13. BUT THENMonday, March 28, 2011 13
  14. 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 22Monday, March 28, 2011 14
  15. 15. MONOLITHIC JAVASCRIPT FORMED OF A SINGLE LARGE FILE.Monday, March 28, 2011 15
  16. 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 / APPSMonday, March 28, 2011 16
  17. 17. PATTERNS FOR AVOIDING MONOLITHS TRADITIONAL CLASS-BASED OO FUNCTIONS, CLOSURES EVENT-DRIVEN JAVASCRIPTMonday, March 28, 2011 17
  18. 18. TRADITIONAL CLASS-BASED OO APPLYING TRIED AND TRUE TECHNIQUES WITH JAVASCRIPTMonday, March 28, 2011 18
  19. 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) { // ... }; JSMonday, March 28, 2011 19
  20. 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(){ // ... }, }); JQUERYMonday, March 28, 2011 20
  21. 21. USING THE CLASS var el = $(“.presentation:first”); var prez = new Presentation(prez); prez.play(); prez.stop(); JQUERYMonday, March 28, 2011 21
  22. 22. BENEFITS DATA/BEHAVIOR ENCAPSULATION CLEAR BOUNDARIES OF RESPONSIBILITIES API DEFINITION MODULAR / REUSABLE CODEMonday, March 28, 2011 22
  23. 23. ASSIGN PSEUDO-PRIVATE VARIABLE 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(){ // ... }, }); JQUERYMonday, March 28, 2011 23
  24. 24. FIND AND ASSIGN ELEMENTS WE NEED ACCESS TO 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(){ // ... }, }); JQUERYMonday, March 28, 2011 24
  25. 25. REGISTER EVENT HANDLERS 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(){ // ... JQUERYMonday, March 28, 2011 25
  26. 26. KEEP REFERENCE TO THIS 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(){ // ... JQUERYMonday, March 28, 2011 26
  27. 27. FEELS A LITTLE HEAVY FEELS A LITTLE AWKWARDMonday, March 28, 2011 27
  28. 28. SAME EXAMPLE, DIFFERENT PATTERNMonday, March 28, 2011 28
  29. 29. FUNCTIONS, CLOSURES TAKING ADVANTAGE OF JAVASCRIPTMonday, March 28, 2011 29
  30. 30. FUNCTIONS, SCOPE, CLOSURES 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 } }; JQUERYMonday, March 28, 2011 30
  31. 31. ONLY DIFFERENCE, NO “NEW” var el = $(“.presentation:first”); var prez = Presentation(prez); prez.play(); prez.stop(); JQUERYMonday, March 28, 2011 31
  32. 32. ACCESSIBLE PRIVATE VARIABLES 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 } }; JQUERYMonday, March 28, 2011 32
  33. 33. STRAIGHT FORWARD EVENT DELEGATION 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 } }; JQUERYMonday, March 28, 2011 33
  34. 34. API DEFINITION 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 }; }; JQUERYMonday, March 28, 2011 34
  35. 35. FEELS MORE LIKE HOMEMonday, March 28, 2011 35
  36. 36. WHAT IF WE DIDN’T CARE ABOUT A PUBLIC API?Monday, March 28, 2011 36
  37. 37. SHORTER. SIMPLER. SWEETER. (function(){ $(".presentation").delegate(".play", "click", play); $(".presentation").delegate(".stop", "click", stop); function play(){ $(this).parents(“.presentation:first”).addClass(“playing”); // ... } function stop(){ // ... } })(); JQUERYMonday, March 28, 2011 37
  38. 38. WHAT JUST HAPPENED? CREATED A FUNCTION, EXECUTED IT REMOVE UNNECESSARY VARS RELY ON SELECTOR-DRIVEN EVENT HANDLERS NO NEED TO MANUALLY BIND TO ELEMENTSMonday, March 28, 2011 38
  39. 39. CLASS-BASED FUNCTION, CLOSURES EVENT-DRIVENvar Presentation = $.Class.create({ var Presentation = (function(element){ function(){ var presentation = element; $(".presentation").delegate(".play", "click", play); initialize: function(element) { presentation.delegate(".play", "click", play); $(".presentation").delegate(".stop", "click", stop); this._element = element; presentation.delegate(".stop", "click", stop); this.play = element.find(“.play”); function play(){ this.stop = element.find(“.stop”); function play(){ $(this).parents(“.presentation:first”).addClass(“playing”); this.play.bind(“click”, $.proxy(this.play, this)); presentation.addClass(“playing”); // ... this.stop.bind(“click”, $.proxy(this.stop, this)); // ... } }, } function stop(){ play: function(){ function stop(){ // ... this._element.addClass(“playing”); // ... } // ... } }(); }, return { stop: function(){ play: playPresentation, // ... stop: stopPresentation } }}); });Monday, March 28, 2011 39
  40. 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 NAMESPACEMonday, March 28, 2011 40
  41. 41. ONE STEP FURTHERMonday, March 28, 2011 41
  42. 42. EVENT-DRIVEN JAVASCRIPT DECLARATIVELY READABLE SELECTOR DRIVEN CODEMonday, March 28, 2011 42
  43. 43. 6 GUIDELINESMonday, March 28, 2011 43
  44. 44. MEANINGFUL FILE NAMES TO ORGANIZE BEHAVIOR PRESENTER.JS PRESENTER/ GRID.JS REMOTE-CONTROLS.JS VIEWER.JS VIEWER/ REMOTE-CONTROLS.JS SLIDES/ RESIZING.JS *USE COMBINATOR, COMPRESSOR, MINIFIER FOR DEPLOYMENTMonday, March 28, 2011 44
  45. 45. STRUCTURE RELIES ON FUNCTION SCOPE EXECUTES IMMEDIATELY (function(){ })(); JS MODULE EXECUTES AFTER DOM READY $(function(){ }); JQUERY MODULEMonday, March 28, 2011 45
  46. 46. DECLARE PAGE CHECKS FIRST $(function(){ if(!$("html.presenter").length) return; PRESENTER.JS }); JQUERYMonday, March 28, 2011 46
  47. 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); }); PRESENTER.JS JQUERYMonday, March 28, 2011 47
  48. 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(){ PRESENTER.JS // ... JQUERYMonday, March 28, 2011 48
  49. 49. QUICKER TO SCAN. KEEPING DECLARATIONS ABOVE FUNCTION DEFINITIONS CREATES MORE READABLE CODE.Monday, March 28, 2011 49
  50. 50. EVENTS DRIVE CROSS-CLOSURE COMMUNICATION PRESENTER.JS function nextSlide(){ var prez = $(this).parents(.presentation:first); _ // ... _ _ current_slide.removeClass(current); next_slide.addClass(current); prez.trigger(slide:changed, { slide: next_slide }); }; THUMBNAIL-CONTROLS.JS $("body").delegate(".presentation", "slide:changed", transitionToSlide); REMOTE-VIEWER-CONTROLS.JS $("body").delegate(".presentation", "slide:changed", changeSlideOnRemoteViewers);Monday, March 28, 2011 50
  51. 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 CODEMonday, March 28, 2011 51
  52. 52. Monday, March 28, 2011 52
  53. 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 COMMUNICATIONMonday, March 28, 2011 53
  54. 54. Monday, March 28, 2011 54
  55. 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 COOUPLINGMonday, March 28, 2011 55

×