JavaScript – Degrees of Organization

These days I’ve been working on multiple enterprise-level SPA web applications. These applications involve lots of JavaScript, which is great, because it empowers us to deliver rich client-side functionality (and I love JavaScript). While working on one of my latest projects, we’ve been using require.js and durandal.js, but not all projects require this degree of modularity and organization.

The guidelines below are cumulative depending on how complex your application becomes. Sometimes applications grow over time, so starting from a better place in an organizational sense will help you adapt with virtually no short-term cost to you, reaping long-term benefits.

Simple Sites and CRUD Applications

For simple websites and basic CRUD apps, and by that I mean limited to less than a dozen pages or views altogether that generally only utilize “postbacks” to ferry data, I advocate following some basic guidelines:

  • Avoid global variables. Scripts can execute within a closure, inside which you can turn on “use strict” and write immaculate JavaScript code.
  • Only inject JavaScript directly into markup to communicate server variables. If you’re injecting anything more than raw JSON data in a variable assignment, there’s a good chance you’re making a difficult to maintain mess. This is doubly true if you’re building JavaScript with a StringBuilder or something similar. Why would you do this? Why not a multi-line string, a resource file or other embedded resource? You shouldn’t be doing this anyway, because the functionality can remain in the actual JavaScript files, which picks up the data in the view.
  • Only use global variables for sharing modules. Global variables should be naturally unusual in “web sites” and simple CRUD apps that have very little JavaScript code. I’m not convinced that the whole world needs to use requirejs (although if you work with a modern pipeline like grunt or bower or something, you probably should).
    For projects like these, I use a simple namespace library that allows multiple modules to share a root namespace regardless of which one is loaded first.
    You still have to order your dependencies, but it’s very clear what belongs where.
  • File structure should reflect function. If you have a user account page that has page-specific javascript, it might make sense to have a “users.account.js” file, whose globally accessible variable happens to be an “account” object sitting atop a “users” object that sits on the global scope. This keeps everything very easy to navigate without resorting to “find in all files”. Consider putting shared scripts in a “scripts/lib” folder (ie: “common.js”, “ajax.js”) and individual scripts in a “scripts/view” folder.
  • Hold your JavaScript to the same standards (but not style) as any other language. JavaScript code can be clean, DRY and testable just like any other language, although sometimes it requires adopting a new mode of thinking. That said, the other side of this point is to embrace JavaScript itself. Don’t try to force it to be Java or C#, because it isn’t.
  • Consider using integrated JSHint or similar. I suggest JSHint over JSLint because if it’s integrated, you don’t want any noise from rules your team intends to ignore. You only want to check against a series of rules your team agrees make sense to hold. JSHint is much more configurable in this respect. I say it should be integrated in the same sense that unit tests and broken build notifications shouldn’t require any one person to run them either and you should get feedback as soon as possible.

Simple Web Applications and Multi-Page Apps

So, you’re designing a web application, but you can’t or don’t want to turn it into an SPA. This sort of application has some amount of rich client-side functionality, but wont become a full-on single page app. Multi-Page Apps are not or cannot become SPA’s, possibly because they have to live side-by-side with legacy code, but certain pages have very rich client-side functionality that allow editing data with some amount of business logic or query logic, etc.

So, all of the above, and…

  • Use require.js. Learn to love and embrace the future of JavaScript modularity. If you haven’t already, you should find out if your platform of choice supports requirejs integration. In my opinion, this means you can write your modular code, but then it gets combined on the server such that you don’t have any delay in loading all of the scripts that you need and it’s not any different than loading all of your scripts via <script> tags, except that it’s clean, organized and prerequisites don’t get mixed up. With require, if module A depends on B, and module B depends on C, even if A is loaded first, require will wait a configurable amount of time before executing A such that B and C can be loaded first.
  • Consider using an SPA framework. Durandal.js and other SPA tools can be used for certain pages or areas of the site where it makes sense to do so, leaving the rest of the site undisturbed. This might be overkill for your 3 tab, 6 button form, but if you have a lot of pages with client-side functionality, adopting a pattern will make development and maintenance easier. Adopting an established pattern with documentation, and possibly even paid support, is just so much better.
  • Consider investing in RESTful APIs. If you have client-side grids and filters and such, you should already be leveraging ASP.NET Web API or similar. If your RESTful API is rigid in setting up a query, find a balance between passing too many optional arguments and writing endpoints that return results for specific uses. Keep in mind that the latter is much easier to test as there is much lower cyclomatic complexity. If you have complex query situations, it may be time to invest in OData and/or something like breeze.js, much like graduating from ADO.NET to LINQ.
  • Consider establishing a strategy for homogeneous validation. This sort of thing allows you to leverage your server-side validation rules in your client-side code in a way that allows you to keep it DRY. ASP.NET MVC supports this with Data Annotations and jQuery unobtrusive validation, but at this time doesn’t come with plumbing to do this in an SPA context. If you use knockout.js, you might write or find an adapter that plugs in knockout validation to your viewmodels that your JavaScript uses. If you are using ASP.NET MVC and Razor is still generating your markup, you can write your own adapter for the data-* attributes that Razor generates for you with unobtrusive validation, or you can turn off unobtrusive validation and use the JSON that ASP.MVC can inject into your page. Either way, as we all know, server side validation is a must-have, but any good web app will not force the user to wait for a POST operation just to find out a single field was missed.
  • Establish a method for integrated JavaScript unit tests. If you have to run these tests manually to get feedback, they wont get run. Even Visual Studio and TFS users can leverage the Chutzpah plugin with QUnit and PhantomJS.

Single Page Applications

Hooray for the modern web! If you’re developing an SPA, you’ve managed to catch up with the rest of the world. In my experience, sorting out some of the below will help you when you discover later that you need this information but are trying to fix a bug or meet a pressing deadline. By establishing a pattern early on, it will be easy for developers to collaborate on shared widgets and to maintain legacy code. I assume by this point you have long ago worked out how to load dependencies and share both instance (prototypal) and singleton (JS object) modules across your application. I also assume you’ve worked out how your jQuery plugins and static content resources (ie: JSON or markup) are loaded and cached on demand.

  • Establish a strategy for widgets. In this context, examples of a widget include…
    A disconnected “sub header” module that contains summary or shared information that might need to be referenced from multiple pages, panels or views.
    A dialog or a reusable component that might need to be injected into the current workflow.
  • Establish a strategy for “child routing”. An example of this might be – perhaps your site has a global menu that navigates between major sections. Each section might have a series of tabs or bootstrap pills. These tabs/pills might have tabs/pills of their own, and so on. A good strategy should include details like – how will deep linking work? How can a descendant view/route communicate with a grandparent router/view? How can data be communicated up and down the routing structure?
  • Establish support for your framework of choice. If it’s homegrown, that may be just what you need – but make sure to invest proper resources and time to resolve issues and add functionality as required. If it’s something like durandal, angular, ember, backbone, etc. train your team and/or give them time to dig deep into the framework. There’s going to come a time when you will need to comprehend or otherwise debug the code in that framework, even if the bug is in your own code. Decide whether you want to (or can) pay for support externally (from the author or consultant) or internally (from your developers in the hours they spend).
  1. Leave a comment

Leave a comment