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. POOR MODULARITY / REUSE CODE MAINTENANCE PROBLEMS LACK OF CACHING

    DIFFICULTY SCALING TO LARGER APPS 3 Monday, March 28, 2011
  2. 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
  3. 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
  4. 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
  5. SELECTOR DRIVEN CODE IT CAN BE SCANNED AND RE-ORGANIZED MORE

    EFFECTIVELY. IT’S EASIER TO VISUALIZE AND MANIPULATE. 12 Monday, March 28, 2011
  6. $(".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
  7. 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
  8. 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
  9. 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
  10. var el = $(“.presentation:first”); var prez = new Presentation(prez); prez.play();

    prez.stop(); JQUERY USING THE CLASS 21 Monday, March 28, 2011
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. var el = $(“.presentation:first”); var prez = Presentation(prez); prez.play(); prez.stop();

    JQUERY ONLY DIFFERENCE, NO “NEW” 31 Monday, March 28, 2011
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. STRUCTURE RELIES ON FUNCTION SCOPE (function(){ })(); MODULE $(function(){ });

    MODULE JQUERY EXECUTES IMMEDIATELY EXECUTES AFTER DOM READY JS 45 Monday, March 28, 2011
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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