Grow your CSS skills. Land your dream job.

Can You “Over Organize” JavaScript?

Published by Chris Coyier

There's no question that on sites with lots of JavaScript, you need a strict organizational pattern to keep everything making sense and the code approachable. I've mentioned in the past I like grouping things into individual files each containing a functionality-specific object literal. Taking things a wee bit further, we can be strict about this pattern and make sure we group together all sectors in one place, all "init" functions in one place, all event binding in once place, and have the rest be little well-named mini functions that do very specific things.

I wonder though, is this too organized?

I have little doubt that on a large heavy-JavaScript site that strict organization like this is of huge benefit. But on smaller sites perhaps not so much.

I recently needed to fix up some JavaScript here on CSS-Tricks. Since I've been writing so much JavaScript in the object literal pattern I thought I'd convert it over to my regular format. CSS-Tricks is not a JavaScript heavy site. The vast majority of the JS is in one global.js file. I saved the JavaScript in its "before" and "after" states so we could take a look and discuss.

The "Before"

The dependencies are up top. They get minified/concatenated into the final global.js file. The rest of the script is wrapped in an IIFE so that variables aren't global. You can just kinda read it top-to-bottom as it's just section after section of functionality. All the code that deals with that functionality is all together in those blocks, separated only by space and a brief comment.

// @codekit-prepend "jquery.fitvids.js"
// @codekit-prepend "placeholder.js"
// @codekit-append "prism.js"

// Protect global namespace
(function($) {


  // Make videos fluid width
  $("article, .photo-grid, .single-video-wrapper, .gallery-grid .grid-5-6").fitVids({
    customSelector: "video"
  });



  // IE 9 placeholders
  $('input, textarea').placeholder();



  // Search stuff
  var openSearch = $(".open-search, #x-search, #refine-search");
  var body = $("body");

  function toggleSearch() {
    if (body.hasClass("show-nav")) {
      $("body").toggleClass("show-nav");
    } else {
      $(".search").toggleClass("open");
      $(".open-search").toggle();
      $(".close-search").toggle();
    }
  }
  openSearch.on("click", function(e) {
    e.preventDefault();
    toggleSearch();
    setTimeout(function(){
      $(".search-field").focus();
    }, 100);
  });
  var searchParts = $(".search-parts > a:not(.x-search)");
  var searchForm = $("#search-form");
  searchParts.on("click", function() {
    var el = $(this);
    var newActionURL = el.data("url");
    searchForm.attr("action", newActionURL);
    searchParts.removeClass("active");
    el.addClass("active");
  });



  // Small screen navigation stuff
  $("#show-hide-navigation").on("click", function(e) {
    e.preventDefault();
    $("body").toggleClass("show-nav");
  });



  // Code highlighting stuff
  $("pre.lang-html, pre[rel=HTML]").find("code").addClass("language-markup");
  $("code.html, code.lang-html").removeClass().addClass("language-markup").parent().attr("rel", "HTML");

  $("code.javascript").removeClass().addClass("language-javascript").attr("rel", "JavaScript");
  $("pre[rel=JavaScript], pre.lang-js, pre[rel=jQuery], pre.JavaScript").attr("rel", "JavaScript").find("code").removeClass().addClass("language-javascript");

  $("pre[rel='CSS'], pre[rel='LESS'], pre[rel='Sass'], pre[rel='SASS'], pre[rel='SCSS']").find("code").removeClass().addClass("language-css");
  $("code.css, code.lang-css").removeClass().addClass("language-css").parent().attr("rel", "CSS");

  $("pre[rel=PHP]").attr("rel", "PHP").find("code").removeClass().addClass("language-javascript");
  $("code.php").removeClass().addClass("language-javascript").parent().attr("rel", "PHP");



  // Comments Stuff
  $(".comment.buried").on("click", function() {
    $(this).removeClass("buried");
  });
  $("#view-comments-button").on("click", function(e) {
    e.preventDefault();
    $("#comments").show();
    $(this).hide();
  });



  // Illustrator links
  var timer;
  var illustratorLink = $(".illustrator-link").hide();
  $(".deals-header, .almanac-title, .videos-title, .snippets-title, .demos-title, .gallery-header, .forums-title")
    .mouseenter(function() {
      timer = setTimeout(function() {
        illustratorLink.slideDown(200);
      }, 3000);
    })
    .mouseleave(function() {
      clearTimeout(timer);
    });
    

})(jQuery);

The "After"

Dependencies still up top, only I moved out the whole chunk regarding code highlighting into its open separate file, since it required no binding and created no variables.

All selectors are up top, grouped together. All init functions are together. All event binding is together. All "actions" are mini well-named functions.

// @codekit-prepend "jquery.fitvids.js"
// @codekit-prepend "placeholder.js"
// @codekit-prepend "highlighting-fixes.js"
// @codekit-append "prism.js"

var csstricks = {

  el: {

    body: $("body"),

    allInputs: $('input, textarea'),

    searchForm: $("#search-form"),
    searchOpeners: $(".open-search, #x-search, #refine-search"),
    searchSections: $(".search-parts > a:not(.x-search)"),
    searchField: $(".search-field"),
    search: $(".search"),
    openSearch: $(".open-search"),
    closeSearch: $(".close-search"),

    videoWrappers: $("article, .photo-grid, .single-video-wrapper, .gallery-grid .grid-5-6"),

    navToggle: $("#show-hide-navigation"),

    illustratorLink: $(".illustrator-link"),
    headerAreas: $(".deals-header, .almanac-title, .videos-title, .snippets-title, .demos-title, .gallery-header, .forums-title"),

    buriedComments: $(".comment.buried"),
    viewCommentsButton: $("#view-comments-button"),
    commentsArea: $("#comments")

  },

  timer: 0,

  init: function() {
    csstricks.bindUIActions();

    csstricks.makeVideosFluidWidth();
    csstricks.polyfillPlaceholders();

    csstricks.el.illustratorLink.hide();
  },

  bindUIActions: function() {
    csstricks.el.searchOpeners.on("click", csstricks.handleSearchClick);
    csstricks.el.searchSections.on("click", csstricks.handleSearchPartsClick);

    csstricks.el.navToggle.on("click", csstricks.mobileNavToggle);

    csstricks.el.headerAreas.on("mouseenter", csstricks.openIllustratorLinkArea);
    csstricks.el.headerAreas.on("mouseleave", csstricks.closeIllustratorLinkArea);

    csstricks.el.buriedComments.on("click", csstricks.revealComment);
    csstricks.el.viewCommentsButton.on("click", csstricks.revealCommentsArea);
  },

  makeVideosFluidWidth: function() {
    csstricks.el.videoWrappers.fitVids({
      customSelector: "video"
    });
  },

  polyfillPlaceholders: function() {
    csstricks.el.allInputs.placeholder();
  },

  handleSearchClick: function(event) {
    event.preventDefault();
    csstricks.toggleSearch();
    setTimeout(function() {
      csstricks.focusSearchField();
    }, 100);
  },

  mobileNavToggle: function(event) {
    event.preventDefault();
    csstricks.el.body.toggleClass("show-nav");
  },

  focusSearchField: function() {
    csstricks.el.searchField.focus();
  },

  handleSearchPartsClick: function(event) {
    var el = $(event.target);
    var newActionURL = el.data("url");
    csstricks.el.searchForm.attr("action", newActionURL);
    csstricks.el.searchSections.removeClass("active");
    el.addClass("active");
  },

  toggleSearch: function() {
    if (csstricks.el.body.hasClass("show-nav")) {
      csstricks.el.body.toggleClass("show-nav");
    } else {
      csstricks.el.search.toggleClass("open");
      csstricks.el.openSearch.toggle();
      csstricks.el.closeSearch.toggle();
    }
  },

  openIllustratorLinkArea: function() {
    csstricks.timer = setTimeout(function() {
      csstricks.el.illustratorLink.slideDown(200);
    }, 3000);
  },

  closeIllustratorLinkArea: function() {
    clearTimeout(csstricks.timer);
  },

  revealComment: function(event) {
    $(event.target).removeClass("buried");
  },

  revealCommentsArea: function(event) {
    event.preventDefault();
    csstricks.el.commentsArea.show();
    csstricks.el.viewCommentsButton.hide();
  }

};

csstricks.init();

How do they compare?

Regarding "before", I like how you can read it top-to-bottom and everything related is grouped together. Since each of those groups only averages about 10 lines, it's fairly readable. If things got more complicated, I'd worry the file would become harder to manage.

Regarding "after", it's a bit weird to see all the selectors grouped together. You have no idea why those elements are being selected - but you can see every element on the page that matters to this file and manage them independently of their functions. If you were totally unfamiliar with this file and trying to familiarize yourself with it, does reading the init and bindUIActions functions make that clear? Is it faster to read that than scan the entire "before" file? Is it confusing how both elements and functions both start with "csstricks."?

"After" is also 25 lines longer despite moving a big chunk of code away to a separate file.

I don't have all the answers. I'm still not 100% sure which I like better or if a hybrid approach would be better or worse.

Comments

  1. Permalink to comment#

    This is precisely why I have grown to love Backbone.js with Require.js so much.

  2. Permalink to comment#

    In general, I think you might be touching on something I’ve felt for a while, which is that I don’t particularly like the pattern of using JavaScript object literals to organize application code.

    To me, blocks of code feel isolated. You have to put everything in functions with an equal level of importance (visually). I find that less readable, as well as forcing you to write it exactly that way, as opposed to organizing the relationship between functions and variables however makes the most sense for the use in question.

  3. eryk.piast
    Permalink to comment#

    Almost fine, but you have ugly global in “after” code. Why you didn’t closure it? ;)

    I prefer the second approach, for the same reasons as you. Additionaly, grouping all selectors in one place is good idea if you have “moody” backend, that likes changing elements’ names (ex. if you make userscripts for 3rd party pages).

  4. Bradley Staples
    Permalink to comment#

    To be fair, your site is much of hobby-ist level on its JavaScript and is mainly jQuery. If you can keep your JS in one (or even just a few) files with < 500-1000 lines of code total, it doesn’t really matter all that much. Once you exceed those guidelines of a limit, JS organization starts to really matter.

    The other reason you’d have it organized differently is the structure of your code. Having real, separate modules of code (whether using a library or home grown enclosures and ‘classes’), adding unit tests, and needing to change which modules appear on which pages, organization matters far more.

    On a site with JS as simple as yours currently is? Do whichever makes more sense to you.

    • Totally. I thought that’s why this would be an interesting comparison. There is no question an actually large scale JavaScript app needs structure, the “after” example in this post being one possible way. The question is if this strict structure is a hinderance or help on a small “hobby-ist” site like this.

    • Evan Hobbs
      Permalink to comment#

      Agreed. Once you pass a certain point of complexity lack of or poor organization really starts to hurt you. I wish there were more resources on overall app organization for large js apps. I’m using Backbone, Marionette, and Require together and love them but structurewise I feel a bit like I’m reinventing the wheel (poorly at times)…

  5. steve oldner
    Permalink to comment#

    I’m not a real javascripter, more of a script kitty. My main language is ABAP, SAP’s language. That said, what you bring up is apropos to any language. That’s modularization and readability. This is a classic discussion between object oriented, procedural, and functional styles of coding.

    I agree with Bradley. And that’s like our coding standards at work. If is is small and simple, keep it small and simple. If it gets larger, break into small and simple components and organize for readability. We have to code for others to maintain.

  6. Great article and a good way to structure your javascript. If I’m building bigger and more complex javascript I use to create own files for every “object” and the use namespaces for nice structure.

  7. Scott Keller
    Permalink to comment#

    I like Backbone.js also, but I love using Marionette.js for even further code organization. Even when not using the REST syncing feature of Backbone, Marionette is still great for organization. It really takes backbone to a new level. The modules are really nice for keeping related code together. How much it reduces boilerplate code really comes down to how many of the features you use.

  8. Alex
    Permalink to comment#

    I keep hearing about backbone.js, require.js, etc… but isn’t the real issue is that javascript is not easy to maintain. Number of factors play role in maintenance of small to medium size site. For example if more then one developer is working on the site you may choose a different strategy all together.
    I worked with jquery pretty extensively before and every time project got larger we ened up building some type of framework around it. Currently doing some work with GWT and got to say that’s as far as organization goes it’s the best out there. (Note I am not commenting on functionality or how easy it is to develop in, just structural aspect of GWT :)))

  9. Permalink to comment#

    I’d like to echo above statements in support of Require.JS.

    Also, just as in other languages, design patterns can help organise code: http://addyosmani.com/resources/essentialjsdesignpatterns/book/

    More simply, JSLint complains if functions are declared out of sequence, so conformant code trends to have dependencies up top as you suggest.

  10. On a web app I’m working on right now, I have functions similar to csstricks.makeVideosFluidWidth and csstricks.polyfillPlaceholders that fire when the DOM is ready.

    If I AJAX load content with a video or placeholder, those functions obviously don’t execute, but the loaded HTML sometimes needs them.

    Below is the approach I’ve been using to solve that problem, but I’m really curious how as to how others approach it. I create a function for “prepping” the DOM and execute it across the whole document when it’s ready and only on the dynamically added sections when they are ready.

    function prepDOM(_trunk) {
        $(_trunk).find('input, textarea').placeholder();
    }
    
    $(document).ready(function() {
        prepDOM(document);
    });
    
    $.ajax({ url: 'example.html' }).done(function(_html) {
        $('#example').append(_html);
        prepDOM('#example');
    });
    
    • Seems like a reasonable approach to me. Especially how you are limiting the scope of what needs to be prepped in subsequent calls. As I’m sure you know, this is ideally just for stuff that absolutely needs to be re-ran. If it’s just event binding, event delegation stuff should help you out there (e.g. .live())

    • Chris, if you’re referring to jQuery.live(), that was deprecated in jQuery 1.7 and removed in 1.9.
      http://api.jquery.com/live/

    • Yeah yeah, good to note. But you know what I mean, event delegation, however you do it. Bind events to elements higher up the DOM tree and check the target when it bubbles up. So you don’t have to re-bind when new stuff gets added.

  11. Maciej Baron
    Permalink to comment#

    In your “after” you cannot have “private” variables really, the ideal solution would be:

    (function(namespaceName, $, undefined) {
        var myPrivateVar;
        namespaceName.moduleName = {
               // Your "after" code here
        }
    })(window.namespaceName = window.namespaceName || {}, jQuery);
    

    However the right patterns depends on what you want to achieve. There is no right solution.

  12. Permalink to comment#

    I think you can “over organize” your JavaScript code. While the “After” appears more organized at first glance there are a few notable drawbacks to it. There are no code comments, an unnecessary global variable declaration & some extra organization for the sake of organization. For example, is the “polyfillPlaceholders” function really adding organization to the code or just adding bloat? Why not just put it in the init function like the “Before” example with the nice code comment. I could go into full code-review mode, but I don’t think that’s the point of the question. :)

    From my experience sites grow and change in unexpected ways. A “hobby-ist” to “profesh-ist” (or something) site could happen over night. This is why you would want your code organized nicely. I think the “After” approach is nice and will certainty be the better approach, although I wouldn’t be afraid to continually tweak it as it makes sense to you.

  13. Erez
    Permalink to comment#

    I actually treat the JS code as object oriented code. We have folders that called “project” in each project there are files that located in folder and each file can be depended by other files. This allows us to separate the files to small units of “Classes” or functionalities areas and the manage them in separate files. This infrastructure allowed me to create “base” file for all basic operations in the site and “dedicated” files for unique pages, in this way I can reduce the amount of resources that the common pages in the website are using and make it faster. Dedicated files are loaded in specific pages after the “base file”.

    Advantages:

    • Smaller files are more understandable Source control conflicts are more rare. my website uses 3 configuration “modes” so we can separate files and load special JS code for each “mode”.

    Disadvantages:

    • Sometimes 400 JS files are not understandable. Combine the files together depends on external process that should run before checking the code in the browser.

    BTW – We are using the same infrastructure for our CSS files. in addition to above the infrastructure allows us to make adaptation to the CSS code for each lanauge that we want to support (handling RTL adaptations, replacing images by language automatically, generating sprites files from individual images). but this is another issue.

  14. Johan Feldt
    Permalink to comment#

    For fairly small JS-files, I usually do something similar to your first example. I like to group the code in smaller functions and call them from an init() function, though. I think that makes the code a little more readable, plus you can just go to the init() function to see which functions get called and in which order. Kind of reads like an old C-program.

    (function ($) {
        function makeVideosFluidWidth() {
            $("article, .photo-grid, .single-video-wrapper, .gallery-grid .grid-5-6").fitVids({
                customSelector: "video"
            });
        }
    
        function polyfillPlaceholders() {
            $('input, textarea').placeholder();
        }
    
        // Other functions here...
    
        function init() {
            makeVideosFluidWidth();
            polyfillPlaceholders();
            // More function calls...
        }
    
        init();
    }(jQuery));
    

    For a very JS-heavy site, or a web application, I’d take a different approach when it comes to structuring the code, though.

    Not sure what I think of grouping the selectors and bindings the way you do in the “after”. Could be tricky to remove a function, since you’d have to look in a number of places to make sure you remove everything related to it, so you don’t end up with a bunch of selectors no longer used for instance. Or try to add event handlers to elements that no longer exist.

  15. In the “before” version, you wrapped everything in an anonymous function to protect the global namespace. In your “after” version, you moved everything into a single global object. Have you considered a mixed approach, where you still protect the global namespace, but you have a single “namespace” object within an anonymous function to hold all of your code? For example:

    (function() {
        var csstricks = {
            init: function() {
                // ...
            },
            // ...
        };
    
        csstricks.init();
    })();
    

    In nearly half of your functions, your object literal is the current object in context, so you don’t need to do:
    csstricks.someMethod();
    Instead, you can use “this”. For example:

    init: function() {
        this.bindUIActions();
    
        this.makeVideosFluidWidth();
        this.polyfillPlaceholders();
    
        this.el.illustratorLink.hide();
    },
    

    In your event handlers the HTMLElement is the current object in context, so you’ll still want those to use “csstricks.someMethod();” internally.

    In methods where you find yourself accessing an object property multiple times, you should create a short alias to it. For example, here you’re accessing csstricks.el over and over:

    bindUIActions: function() {
        csstricks.el.searchOpeners.on("click", csstricks.handleSearchClick);
        csstricks.el.searchSections.on("click", csstricks.handleSearchPartsClick);
    
        csstricks.el.navToggle.on("click", csstricks.mobileNavToggle);
    
        csstricks.el.headerAreas.on("mouseenter", csstricks.openIllustratorLinkArea);
        csstricks.el.headerAreas.on("mouseleave", csstricks.closeIllustratorLinkArea);
    
        csstricks.el.buriedComments.on("click", csstricks.revealComment);
        csstricks.el.viewCommentsButton.on("click", csstricks.revealCommentsArea);
    },
    

    Since csstricks is the current object in scope anyway, you could change these all to this.el. And better yet, create a short alias to it like so:

    bindUIActions: function() {
        var el = this.el;
        el.searchOpeners.on("click", this.handleSearchClick);
        el.searchSections.on("click", this.handleSearchPartsClick);
    
        el.navToggle.on("click", this.mobileNavToggle);
    
        el.headerAreas.on("mouseenter", this.openIllustratorLinkArea);
        el.headerAreas.on("mouseleave", this.closeIllustratorLinkArea);
    
        el.buriedComments.on("click", this.revealComment);
        el.viewCommentsButton.on("click", this.revealCommentsArea);
    },
    

    Notice I also replaced “csstricks.” with “this.” in the second parameter of the on method.

    You might also restructure your code so that all of the event listeners were grouped together. You’ve got some methods, like focusSearchField and toggleSearch, that are called by the event listeners, but are not actually event listener methods themselves, so I would rearrange those slightly.

    With this amount of code there really are minimal gains one way or the other, so it probably mostly depends on what approach you personally find easier to work with. Overall, you’ve got nice, small functions, which makes it easy to maintain and understand. Could benefit from some comments, though.

    Side notes:

    In your openIllustratorLinkArea, you should add clearTimeout(csstricks.timer); before calling setTimeout. There is a (VERY slight) chance of a race condition in your code, though the only side effect would be a stuttering effect on the slideDown.

    Some things to keep in mind. When you say “grouping all the selectors”, you’re actually doing more than just grouping the selectors, you’re grouping all of the elements. That is, you’re actually fetching them and storing references to those elements. It’s not clear by looking at them where or when they are used, so you also run the risk of removing some bit of code that uses one or more of those, but then forgetting to remove the code that’s fetching the item (so you’d be fetching them for no reason). But given the size of this file, I don’t think that would be much of an issue.

    Here’s how I might restructure it:

    (function() {
        var csstricks = {
            el: {
                body: $("body"),
                allInputs: $('input, textarea'),
                searchForm: $("#search-form"),
                searchOpeners: $(".open-search, #x-search, #refine-search"),
                searchSections: $(".search-parts > a:not(.x-search)"),
                searchField: $(".search-field"),
                search: $(".search"),
                openSearch: $(".open-search"),
                closeSearch: $(".close-search"),
                videoWrappers: $("article, .photo-grid, .single-video-wrapper, .gallery-grid .grid-5-6"),
                navToggle: $("#show-hide-navigation"),
                illustratorLink: $(".illustrator-link"),
                headerAreas: $(".deals-header, .almanac-title, .videos-title, .snippets-title, .demos-title, .gallery-header, .forums-title"),
                buriedComments: $(".comment.buried"),
                viewCommentsButton: $("#view-comments-button"),
                commentsArea: $("#comments")
            },
    
            timer: 0,
    
            init: function() {
                this.bindUIActions();
    
                this.makeVideosFluidWidth();
                this.polyfillPlaceholders();
    
                this.el.illustratorLink.hide();
            },
    
            bindUIActions: function() {
                var el = this.el;
                el.searchOpeners.on("click", this.handleSearchClick);
                el.searchSections.on("click", this.handleSearchPartsClick);
    
                el.navToggle.on("click", this.mobileNavToggle);
    
                el.headerAreas.on("mouseenter", this.openIllustratorLinkArea);
                el.headerAreas.on("mouseleave", this.closeIllustratorLinkArea);
    
                el.buriedComments.on("click", this.revealComment);
                el.viewCommentsButton.on("click", this.revealCommentsArea);
            },
    
            makeVideosFluidWidth: function() {
                this.el.videoWrappers.fitVids({
                    customSelector: "video"
                });
            },
    
            polyfillPlaceholders: function() {
                this.el.allInputs.placeholder();
            },
    
            focusSearchField: function() {
                this.el.searchField.focus();
            },
    
            toggleSearch: function() {
                var el = this.el;
                if (el.body.hasClass("show-nav")) {
                    el.body.toggleClass("show-nav");
                } else {
                    el.search.toggleClass("open");
                    el.openSearch.toggle();
                    el.closeSearch.toggle();
                }
            },
    
            // --------------------------------
            // Event Listeners
            // --------------------------------
            handleSearchClick: function(event) {
                event.preventDefault();
                csstricks.toggleSearch();
                setTimeout(function() {
                    csstricks.focusSearchField();
                }, 100);
            },
    
            mobileNavToggle: function(event) {
                event.preventDefault();
                csstricks.el.body.toggleClass("show-nav");
            },
    
            handleSearchPartsClick: function(event) {
                var el = $(event.target);
                var newActionURL = el.data("url");
                csstricks.el.searchForm.attr("action", newActionURL);
                csstricks.el.searchSections.removeClass("active");
                el.addClass("active");
            },
    
            openIllustratorLinkArea: function() {
                clearTimeout(csstricks.timer);
                csstricks.timer = setTimeout(function() {
                    csstricks.el.illustratorLink.slideDown(200);
                }, 3000);
            },
    
            closeIllustratorLinkArea: function() {
                clearTimeout(csstricks.timer);
            },
    
            revealComment: function(event) {
                $(event.target).removeClass("buried");
            },
    
            revealCommentsArea: function(event) {
                event.preventDefault();
                csstricks.el.commentsArea.show();
                csstricks.el.viewCommentsButton.hide();
            }
        };
    
        csstricks.init();
    })();
    
    • Have you considered a mixed approach, where you still protect the global namespace, but you have a single “namespace” object within an anonymous function to hold all of your code?

      It depends on the situation. Being aware that it is in the global namespace is important. Usually I like having it be global so other code might be able to use it.

      In nearly half of your functions, your object literal is the current object in context, so you don’t need to do:
      csstricks.someMethod();
      Instead, you can use “this”.

      Yep that’s a great point. I started out always using this to refer to it, but I just found it confusing after a while. As you know this changes depending on context quite a bit in JavaScript, and I found referring to the object specifically was more clear. Totally just a preference though.

      Thanks for all your thoughts and work on this, much appreciated!

    • Sure, it can be helpful to have a single global “namespace” object. In your case, I got the impression that you were keeping all of your code in this one file, in which case there probably wouldn’t be a need for the global. I agree, it depends on the situation.

      Thanks for all your thoughts and work on this, much appreciated!

      My pleasure. :)

  16. Matti
    Permalink to comment#

    I’ve used object literal notation for a lot of projects lately large and small and do not feel that it is over-kill. I believe much of this can come down to preference in terms of what pattern to apply, but I certainly think anything is better than nothing. I come from a oop background so I prefer seeing my javascript with namespaces and levels of isolation. That is certainly not the same for all developers. We all have our preferences and affinities towards particular patterns or methodologies.

    For those that disagree, how can you discount a more clear and logical layout to your javascript? It seems to always be a win for me when it not only propagates better coding habits but reduces bugs and makes performance improvements easier to find.

    In terms of javascript that is only a page or less; I’ve noticed javascript has this tendency to grow and starting off using some pattern or school of thought will keep it from one day becoming a giant, unmanageable, monolithic beast that no one will want to wrangle with.

  17. Permalink to comment#

    Is it harder to read? For noobs and anybody not familiar with organization or not used to read other ppl’s code, yes.

    But please, don’t do your work thinking on the noobs and unexperienced. They should go to school and man up before trying – or complaining when unable – to touch real software.

    When you are in a didactic environment, you should worry if reader will easily understand. He may even be paying you to learn from your code. But in production/real world development environment, it’s maintainability and reliability (which includes stability and I’ll also include performance) that matters.

    If you have your code well organized and you need somebody to maintain it and he can’t understand your good organization, don’t change it so that noobs can read it. It’s better to teach those noobs about your organization.

    Being specific about JavaScript, If I’d think in making it easy to read code and see where it applies and what applies to each element, I’d never use a .js file to put my code. I’d just put all functions inside HTML Document, and use onMouseOver, onClick and all those XHTML 1.0 Strict invalid attributes to attach these functions to each HTML element. Once I remove these attributes and use jQuery selectors to make bindings, it gets WAY harder to figure out what the heck is controlling an element behavior.

  18. I’m fairly new to Javascript, but my suggestion would be to split everything up in logical chunks, like this: http://snippi.com/s/r5kl9vo

    I’m not sure that the code actually works since I can’t try really it out. I ran it through JSHint and I didn’t give me any errors though.

    Just a side note: I’m a whitespace junkie and that’s why there’s some many lines of code.

  19. Yeah, I really like having an explicit config part like that. I also find that it’s much easier to find what I’m looking for when there’s a clear structure to everything. It makes it pretty easy to add and remove stuff too.

    The obvious downside is, like, I mentioned earlier, that the code gets much longer. That’s probably not that big of a deal when you’re minifying it for production anyway though. :)

  20. I write a lot of large-scale JavaScript applications, and I would say I actually prefer your Before, for this reason: there you have everything organized by “functionality module” rather than “type of thing”. In my experience, the former scales much better than the latter, and it’s clearer for small projects as well, so you win either way.

    “Area of functionality” is a higher level of abstraction than “type of thing”, so it more closely represents the programmer’s intention, and is therefore more resilient to future changes, as well as being more readable.

    As a side note, the “functionality” versus “type” dichotomy in program organization is similar to the management dichotomy of “product groups” versus “discipline groups”. Your mileage may vary, but I feel you get better results from a single interdisciplinary team focusing on a single product.

  21. Permalink to comment#

    Absolutely, the before above for me is much better.

  22. Interesting post questioning the re-factorization of JavaScript code.

    http://www.frankysnotes.com/2013/04/reading-notes-90.html

  23. Saif
    Permalink to comment#

    I really like the second one as well, since it scales better, but like Ellen suggested, it may be better to break it into chunks.
    What i’ve been doing is create one global object, then break it into appropriate chunks.. e.g (bear with syntax errors, not testing)

    if (typeof(csstricks) == "undefined") { var csstricks = {}; } // create global object if it doesnt exist.
    csstricks.searchStuff = {
      init: function(){
        //call searchStuff init functions here
      },
      toggleSearch: function(){}
      
    }
    
    csstricks.responsiveStuff = {};
    csstricks.socialStuff = {};
    

    and so on.. I guess its a hybrid of what Ian and Ellen above suggested. Has the benefits of only having one global var, giving access to other apps/code/etc.

    • @saif, agreed… that’s how I organize most of my projects, big or small. Usually each module is in its own file, but if it’s small enough they could all be in the same file. Just a couple tweaks to your code:

      (function() {
      
        window.csstricks = window.csstricks || {};
      
        csstricks.searchStuff = {
          init: function(){
            //call searchStuff init functions here
          },
          toggleSearch: function(){}
        }
      
        csstricks.responsiveStuff = {};
        csstricks.socialStuff = {};
      
      })();
      
  24. While your second version is more organized, it’d be prudent to defer execution of some of the DOM searching calls into jQuery and lazy load them as necessary.

  25. The “after” example is how I’ve been coding javascript for the last 4 years. I still see a lot of spaghetti javascript code these days, but I think with all the new MVC frameworks out there, nocely organized code is getting more common.

    I wrote this little blurb a few years ago on how to use prototype as the base for well organized javascript libraries:

    http://www.pdvictor.com/en/?sv=&category=just%20code&title=Create%20Object%20Oriented%20Javascript

  26. I personally divide the application into little modules with one clear objective. And i generaly implement thouse modules as instances of a class. I keep every class in it’s own file, so my javascripts files are very small and easy to manage.

    I keep the “public” accessors on top, and the “private” methods on the bottom of every class.

    In your example, i would had probably divide the “tasks” into separated files, and then concatenated them togheder. But i would work on them in separated files.

  27. Permalink to comment#

    Thanks to the author and all the commenters. Very useful post for a javascript newbie like myself!

    John.

  28. GREAT post. Sure would love a li’l ole button to share it to twitter, :hint, hint:
    Well, I’ll just do it muhself through @DRicardoDesigns….

  29. Hello

    Definitely a mix between the two, but be sure to start the code with approach 1 (not the other way around).

    Approach 2 becomes very unmaintainable once your code hits 2000+ lines. Scroll to top to figure out which selector, scroll to bottom, attach your functionality… where with approach 1 you just go and code.

    Generally I find that as long as there is a some form of thought through structure it will be maintainable. Don’t mix and match too much as then the code just becomes sloppy. Also don’t start with approach 2 and then go back to 1. Once you’ve gone down route 2 you should basically stick to it. Whereas using approach 1 you can have certain “bigger” parts of the code use approach 2 without being “different” to the rest of the code.

    Cheers

    PS: Nice site!

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".