This post is a primer on JavaScript web applications development. It's targeted at noobs so is high level, mostly concepts not code (tho it does include links to in-depth resources).
Introduction
This is part 2 of my primer on JavaScript web applications development, with emphasis on Single Page Applications, or SPAs. Part 1 was an introduction to the topic. Part 2 goes deeper, providing an overview of the tools you'll use and knowledge you'll need to develop SPAs. This should be particularly useful to noobs since there are a lot of moving parts to SPAs and lots of development options (basically this is the terrain map I wish I'd had when I first entered SPA territory).
The target audience for this post is SPA noobs. However, this assumes you're not a noob when it comes to web development, and that includes a working knowledge of JavaScript and jQuery. |
Ok, I'll start with a summary, follow with a brief explanation of the differences between libs and frameworks, and then I'll consider each topic in a bit more detail. Here's the structure:
Tools you'll need for developing JavaScript web applications
What you need to know to develop JavaScript web applications
Developing using JavaScript libraries
Developing using JavaScript frameworks
Media queries and responsive design
Events, dependencies, loose coupling
Synchronizing local data edits back to the server
Views, templates, and data binding
Generating views from client-side data
Keeping views in sync with their data
Routing, navigation, and state management
Before getting started let me be clear that I'm no SPA guru, and much of this was written as I was diving into JavaScript development myself, so I apologize in advance for any errors or omissions. Luckily for you this primer contains lots of great alternative resources, for example pointers to some of Addy Osmani's many articles, videos, books (including free online books), and presentations (the image below is from one of his talks).
Overview
From Addy Osmani's "Tools for jQuery Application Architecture"
This post covers the tools and technologies you need to develop SPAs. There are a lot, so it's worth starting this by summarizing them. Below is a list of the key constituents (IMO) of a JavaScript web application (topics are covered in detail later in this primer). You won't always need all of these, but you will always need most of them.
Tools you'll need for developing JavaScript web applications
- cross-browser utility: Unless your project is targeting a single browser you'll want a JavaScript library that smoothes out browser differences for things like DOM manipulation and event handling. The best-known lib for this is, of course, jQuery.
- developer utils: You'll always need tools to simplify development, and jQuery fits in this category, too, with utility functions like each, map, extend, etc. Still, jQuery by itself isn't enough and you'll want to look at other tools that coexist with jQuery and fill in some blanks. A great example of this is Underscore, which has tools for data manipulation and more.
- rich UI component set: Standard browser controls are dumb, you can't create a rich UI with them, so you'll always need some third-party UI components. You have a decent range of choices, from simple jquery plugins to more sophisticated plugins like jqPlot.js to small libs of stateful components like jQuery UI (modal windows, sliders, accordions, etc.) to really large libs of stateful components like Wijmo or Kendo (which have what you'll need for most projects including charting).
- lib or framework with MV* architecture: When you're creating an application (and not just a collection of related web pages) you need to pay attention to architecture — how your application's requirements can be translated into code modules with clear inputs/outputs, how these modules will communicate, how to keep them loosely coupled, etc. SPAs often employ an architecture such as MVC (Model-View-Controller) or MVVM or MVP or MVwhatever (a.k.a. MV*). The main thing is that it separates Model (data and data handling code) from View (UI components and the code that drives them). This is important for most any application, but it's especially true for client-side web apps. For simple applications you can architect things on your own, but mostly you'll want to use a framework or lib for this (e.g., Backbone).
- events management: Applications frequently keep things loosely coupled via events, and SPAs often use some implementation of the Publish/Subscribe (Pub/Sub) pattern. Having modules communicate via an event bus rather than having them talk directly to each other (and thus creating a dependency) simplifies maintenance, testing, team development, and more.
- client-side templating tool: Because you'll be generating views on the client and usually building these views from client-side data you'll want a client-side templating tool. Templates let you generate bits of HTML markup from input data. You write a chunk of HTML with placeholders where you want data values to be inserted (this is your template) and at runtime the template compiler does the data insertion, generating markup you can inject into the DOM. The data you feed to a template can be anything — a single numeric value or string, an object, or a collection of objects. For example, using a template you can convert an array of objects into an HTML table. If you're new to templates don't worry, they're simple and you have lots of options.
- resource loaders and dependency management: JavaScript applications can have many moving parts and sometimes files/functions depend on each other — for example, function A calls function B, which means B must be loaded and ready when A calls it. Tools like RequireJS can handle load dependencies for you, ensuring that resources are there when you need them. They (and simple resource loaders like YepNope) can also fetch things more efficiently (e.g. parallel async load instead of blocking synchronous load) and they can let you load modules lazily and/or conditionally, letting you load a module only when or if it's actually needed.
- CSS preprocessor: While not required for developing SPAs, if your application's CSS starts getting out of hand you may want to use a CSS preprocessor that lets you put some logic in your stylesheets. You can have global variables, conditionals, even functions.
- deployment tools and procedures: For optimal performance you'll want deployment tools and procedures for packaging your application. This can be the basics like minification and concatenation or you can take things a step farther and make use of a code optimizer.
- testing tools: The nature of JavaScript (dynamic, loose typing, no block scope, global variables as default, signficant potential for namespace collision, etc.) means you really need to do more testing than you might in other languages. Not surprisingly there are a variety of libs to assist you with this task.
What you'll need to know or learn to develop JavaScript web applications
- HTML, CSS, JavaScript: Obviously to create JavaScript web applications you'll need a solid knowledge of HTML, CSS and JavaScript (including DOM manipulation)
- Ajax/XHR: Because JavaScript web applications generate views from client-side data without a page refresh they often fetch data (and JS files, templates, CSS, etc.) from the server behind the scenes (i.e. in background, with no page refresh). Probably you'll be doing this with Ajax so you'll need to know at least the basics here — how to make Ajax requests, how to handle asynchronous request results, the JSON data transfer format. The good news: it's not complicated and libs like jQuery and yepnope.js make things easy.
- Client-side data management: To minimize server calls and improve response time SPAs bring data to the client. Once the data is local you need to know how to store and manipulate it. For this you'll often find client-side data models useful. Models (the M of MVC) can hold data entities that mirror the entities in your backend db. In the model you'll often aggregate these entities in arrays or objects so you'll need to know how to work with these data structures — insert, sort, filter, map, pluck, etc. Some libs/frameworks provide array-like Collections which fire events when their data changes, and if you use these you'll need to learn how to make use of the events they fire. And if the user can edit data you'll also need to know how to sync local edits back into your backend master datastore (some MV* libs' help with this, e.g. Backbone's sync).
- code structure/organization: Code organization is important to any application and JavaScript applications are no exception. If you're one of those people who keep all of your JavaScript embedded in your .html's or dumped into a single thousand-line file, well, you have some reading to do. You need to read up on code decomposition, use of packages and modules, using smaller .js files focused on a single operation, etc.
- JavaScript debugger and related devtools: As you run more code (and more complex code) on the client you'll need familiarity with tools for debugging JavaScript, inspecting the DOM and CSS values, and analyzing network traffic. Most browsers have excellent tools for this, e.g, Chrome's devtools and Firefox's Firebug.
- routing, history management, application state: Server-based web applications are driven by URLs — you give the server a URL and it sends back a page. And this page can be bookmarked and shared through its URL. But js web apps aren't driven by URLs (in fact a true SPA uses a single URL, the one that loads the application). This means it's up to you to provide application navigation, client-side state management, and the ability to bookmark or share an application state. You may also want to save application state and restore it when a user returns to your application, especially for mobile.
You may have noticed there's nothing about the backend here. That's because JavaScript web applications should be agnostic regarding the backend — you should be able to use any number of server/database technologies from Node.js to Java, from MySQL to MongoDB. To SPA developers the server is primarily just a RESTful endpoint or RPC provider that serves up raw data and files which are exploited on the client.
I also don't mention HTML5 or CSS3. While it's great to have the extra functionality and consistency that these standards provide, you can still create great JavaScript web applications without them.
Finally, remember that you won't always use everything listed above — your application's requirements determine the tools you actually need.
Below are some general resources on SPAs (subsequent sections include in-depth resources).
Resources: SPA tech overviews
Some good books:
∙ Alex MacCaw's incredibly useful book JavaScript Web Applications.
∙ Addy Osmani has several books, both hardcopy and online, see http://addyosmani.com/blog/ for a complete list.
∙ Mikito Takada's (a.k.a. Mixu) single page app online book: http://singlepageappbook.com
∙ MSDN has some good content on Javascript applications development, including the book-length description of their project Silk demo app
Several useful online resources:
∙ Addy Osmani's "Tools for jQuery app architecture"
∙ Aaron Hardy's JavaScript architecture basics posts
∙ "Important Considerations When Building Single Page Web Apps"
∙ "A mobile web application tech stack"
∙ Smashing's list of talks to help you become a better front end engineer
Some podcasts that mostly focus on general concepts:
∙ JavaScript Jabber "The Right Way to build Web Applications"
∙ Herding code "Ward Bell on Single Page Applications and Breeze"
And if you're coming from a Flex dev world you'll find these interesting:
∙ http://aaronhardy.com/category/flex
∙ Joao Saleiro's post on moving from Flex to HTML5
∙ Ryan Stewart' post on web apps future
∙ Universalmind.com post on transitioning from Flex to HTML5
Libs versus Frameworks
Before diving into more detail on the topics listed above we need to take a moment to consider the differences between JavaScript dev libraries and full-blown frameworks.
JavaScript web application development has two primary paths: either you create your application using a collection of libraries that you choose (libs like jQuery, Underscore, jQuery UI, etc.) or you use a tightly integrated and more comprehensive JavaScript framework (Ember, Angular, Sencha, etc.) that provides most everything you need. The former is basically a mix and match approach to application development while the latter's integrated approach is generally more prescriptive, making choices for you at the cost of some design flexibility (but with the benefit of simplified development once you learn how the framework works). What follows is my take on the differences between these two. Keep in mind that some of this is subjective (I'm sure to get complaints that I've over-generalized or just plain gotten it wrong, but here goes...).
Developing using JavaScript libraries
JavaScript libraries are like individual tools you can use to construct your web applications, and you fill up your tool chest with tools you select — you choose the hammer, the screwdrivers, glue, etc. Each lib does one or maybe even a few things well but it doesn't try to cover all the application development bases. Think back to those summary bullets listed above, the constituents of a JavaScript application (MV* architecture, template tool, UI components, etc.). For each of these you can find several JavaScript libs (sometimes dozens). An example of this is DOM manipulation. There are many libs and frameworks that let you manipulate the DOM without having to deal with browser differences, the most popular being jQuery, but there are also lightweight alternatives like the Zepto lib. You also have multiple UI component libs to choose from, libs like jQuery's sibling jQuery UI, or maybe something larger like Wijmo or Kendo. And for dependency management there are specialized libs like Require.js or LabJS. For templating there are also lots of options, from the basic functionality of Underscore's microtemplating to the very popular Moustache or Handlebars. And even if you don't use Underscore for templating you might still want this lib because it has many great utilities for manipulating objects and arrays.
So, the libs "mix and match" path means you're using a variety of libs which you select. This gives you enormous flexibility, since you can choose a simple, lightweight lib for a simple application or a more full-featured lib for larger applications. And this means you can implement any architecture you want, there's no overall structure imposed on you.
Of course, there are downsides to this approach — you need to know about these libs, their pros and cons, how they work, and how to use them. And sometimes getting multiple libs to play well together isn't straightforward. And when you use multiple libs you have multiple product dependencies, multiple release cycles, multiple places to go for info on bugs and patches. And having greater flexibility in architecture means greater chance to make mistakes. In other words, the libs development path doesn't just give you lots of flexibility, it also gives you a lot of rope with which to hang yourself if you don't know what you're doing.
Developing using JavaScript frameworks
Frameworks are more ambitious than libs, providing a much broader range of functionality. While JavaScript libs are like individual tools that you select, frameworks provide a preselected collection of tools, a toolchest filled for you. They're usually a rich set, in fact there may be tools you won't even need, but key here is that all of these tools have been chosen because they work well together (and frameworks include conventions and features that enhance interoperability between its parts). Unlike simple libs, frameworks often strive to give you everything you need for web application development. Need something to smooth out cross-browser inconsistencies? The framework will include it (sometimes by incorporating jQuery). Want widgets? Some frameworks provide those. Templating, routing, dependency management, deployment tools? A framework will probably have them. And a framework's tight integration means that all these pieces are more likely to interoperate seamlessly.
Frameworks also provide an application structure. There are rules as to where to put files, how your modules will interact, and how the application will be segmented — e.g., use of MVC or MVVM or MVP, that choice won't be up to you, because that's basically baked into the framework. This saves you having to work this out yourself, but it can also restrict your design options. With frameworks you're less designing the system architecture than adopting someone else's design.
Frameworks often incorporate an application lifecycle into which you integrate your code. This is different than the libs approach. With libs you call code when you need it, whereas with frameworks you're usually writing code that will be called by the framework itself. For example, if you have some initialization code you might have to register it to the framework so that it knows to call that code during the application's Initialization lifecycle event. That's why you'll often read that frameworks follow the Hollywood principle: "don't call us, we'll call you".
Frameworks are generally heavyweight, and for small projects they can be overkill. Some also come with a fairly steep learning curve. On the other hand, frameworks bring a lot to the party. Once you've climbed that learning curve a framework's integrated environment offers the promise of simplified and accelerated development. For large projects they can bring order out of potential chaos — your development team can all follow the framework's clearly defined paths using common tools and terms (obviously this is also do-able with libs, but with frameworks you don't have to reach consensus on tools and standards, it's basically handed to you).
A final word on libs versus frameworks: there are grey areas here. SPA developers can choose from some great MV* libraries that provide application structure without being too prescriptive. Libs like Backbone and Knockout incorporate architectural design patterns that have proven themselves useful in client-side JavaScript applications but still give you lots of control over your overall application architecture and let you keep working with your favorite libs. And some frameworks like Ember strive to be flexible by letting you override default libs like its Handlebars template engine with a similar lib that you prefer (though depending on the lib you are overriding and the features of the lib you substitute this may come at the cost of some of the framework's integrated functionality).
A clarification on terminology: This section makes a distinction between libs and frameworks in order to clarify the two main development paths for SPAs. Unfortunately this semantic distinction breaks down when it comes to MV* libs like Backbone, Spine, Knockout, etc. By my definition these are libs, just another tool you can choose to use.. However, since their primary role is to provide your application with an MV* architecture you'll often see them referred to as "architectural frameworks". Not an unreasonable name for this category of libs given what they do, but when you see them called "frameworks" it might be confusing after reading my definitions above, so I wanted to highlight this semantic distinction. |
Other Development paths
I said there were two primary paths to SPA development, using either libs or frameworks, but there are other ways to develop JavaScript applications that I won't cover here but which I need to at least mention.
One approach to creating JavaScript applications is JavaScript generators. These let you work in something other than JavaScript but then convert that code into JavaScript (and HTML/CSS). Probably the best known is Google Web Toolkit (GWT), which lets you work in Java. There are others, such as Cappuccino for Apple-centric developers and CoffeeScript (from Jeremy Ashkenas, the prolific developer who also created Backbone and Underscore).
Another approach is the one taken by PhoneGap: the hybrid app. Here you write your application in JavaScript but PhoneGap provides an enhanced API that gives you access to mobile features not normally available to web apps, for example a phone's camera. When you run your code through the PhoneGap compiler the result is a native app that wraps a chromeless browser instance. Your JavaScript application runs in this browser instance and through the native app wrapper it has access to mobile features like that camera. Your application targets must support HTML5 and CSS3.
Another approach is the one taken by Appcelerator Titanium, which lets you develop your application in JavaScript but then compile this into a true mobile app. This lets you develop one codebase that can be used to target both iOS and Android. The end result isn't really a browser-based JavasScript application, though, it's a true native app.
Finally there's Adobe AIR, which also isn't browser-based but instead runs in a virtual machine. With AIR you can create your application with JavaScript and HTML or Adobe's ActionScript and Apache Flex. The final application is compiled into a file which executes in a virtual machine, the AIR runtime. This runtime is available for a wide variety of platforms, both desktop and mobile (for iOS it is compiled into the application itself).
This primer focuses on developing SPAs using libs rather than a framework. My JavaScript Data Explorer (JSDE) demo application uses this approach, combining jQuery and jQuery UI and Underscore and more. JSDE is part of this primer series, a SPA that demonstrates most of what's covered here. To learn more see the JSDE overview or just run the demo app. |
Resources: JavaScript libraries and frameworks:
∙ Very interesting on this topic is the JSJabber podcast on Backbone, which ends up spending quite a bit of time comparing the nature and relative merits of libs versus frameworks, with libs represented by Jeremy Ashkenas (Backbone) and frameworks represented by Yehuda Katz (Ember)
∙ Stackoverflow question on the differences between a framework and a library
∙ Stackexchange questionon library vs framework vs api
∙ Smashing article on "the JavaScript MVC jungle
∙ Aaron Hardy's JavaScript architecture posts
∙ Steven Sanderson's Throne of JS, 2012 article compares seven JS frameworks (2012, so is a lttle dated, but still has some useful content)
∙ Infoq's Throne of JS videos, hours of vids that provide overviews of individual MV* libs and frameworks, many presented by their creators
∙ TodoMVC, a project that creates the same TODO application in a mulititude of JS MV* libs and frameworks
∙ tutsplus tutorial comparing Backbone and Ember
SPA tools and tech
The remainder of this post covers the topics summarized above in a bit more detail. However, keep in mind that this is just a primer, so it doesn't dive deep into any topic (but each section does include references to more in-depth resources).
This primer covers developing SPAs using a combination of JavaScript libs (e.g., jQuery + jQuery UI + Underscore + Backbone + etc.) not through use of a full-blown framework like Sencha/ExtJS. While the concepts covered here apply to building any JavaScript application, if you use a framework then you won't need many (or possibly any) of the libs mentioned below. |
HTML and the DOM
Anyone reading a primer on SPAs probably knows HTML fairly well, so I won't spend time on this topic except to emphasize that you need to know how to work with the DOM. Why? Think about the nature of a Single Page Application — by definition it has a single page load, the one that initializes the application. After that you're on your own — changes to a SPA's UI are done on the client by your client-side code. In general you'll do this by generating content from local data, often updating just a portion of the page. Since we're running in a browser this means working with the DOM — not just selecting HTML elements but also creating and populating them, attaching them to the DOM, maybe reparenting them, destroying them, etc. This skill is essential to SPA development.
If you're a jQuery user this translates into knowing how to use its $.() factory function to create elements — for example var newParagraph = $("<p>Your content here</p>"). You'll also need to know its basic DOM manipulation functions, things like append(), empty() and remove(). For many SPAs, though, you'll need more than the basics. For example, you'll want to know things like the difference between remove() and detach() and why the latter is especially useful to a client-side application (Answer: remove() totally whacks a DOM node, including clearing things like event listeners and jQuery data associated with the element, while detach() leaves the node intact, allowing you to temporarily take something off the DOM and later restore it without losing its state (and without incurring the cost of recreating it).
In JSDE views that display countries data are created only once. If they're later deselected by the user they're just detached and cached. Subsequent requests for these dataViewers are served from this cache. More on JSDE implementation... |
BTW, note that this section's title references HTML, not HTML 5. You can write highly functional web applications in legacy browsers that don't support HTML 5, especially if you make use of polyfills, which can bring some HTML5 functionality to older browsers. On the other hand, if possible you'd really like to leverage the features of HTML 5 if you can. Of course, if you are targeting only mobile then you're sweet, since mobile browsers are all HTML 5.
JSDE doesn't use HTML5 semantic elements like <header>, <nav>, etc. and so doesn't need a full HTML5 shim. However, polyfills for some HTML5 features are used (e.g., hashchange) |
References: DOM and DOM manipulation
∙ Cody Lindley's online book "DOM Enlightenment"
∙ jQuery docs on DOM manipulation/
∙ MDN book chapter on "Manipulating cliient-side HTML"
CSS
As with HTML, so it is with CSS: your knowledge of the fundamentals needs to be solid if you want to develop SPAs. That's because SPAs do view creation and management client-side, and this involves setting or modifying elements' size, position, visibility, and other appearance properties whose API is CSS. If you're targeting mobile, with its wide variety of screen sizes and pixel densities, you'll probably make use of CSS 3 features like media queries to dynamically adapt a view to the current display's capabilities. Bottom line, you don't need to be a CSS guru, but you do need a firm grasp of CSS fundamentals, especially layout.
Here are some things to consider re CSS and SPAs:
∙ know CSS selectors. Yes, it's basic, but it's really important. You'll use them a lot as you work with views on the client side. And because you'll be doing a lot on the client side you need not only to be able to select things, you need to do it efficiently (always true, but more so with JavaScript web applications, which put greater demands on the client).
∙ when you need to target both old and new browsers you can use CSS3 features but also provide fallbacks for browsers that don't support those features (e.g., if you specify an alpha using rgba you can also provide a solid color as fallback for older browsers). And you can often get some backward compatibility through polyfills.
∙ when your CSS has errors (e.g. a missing closing bracket) you sometimes see the problem immediately but other times it can cause very subtle problems. One thing you won't see is error messages like the console's JavaScript errors and warnings that tell you what went wrong and where it went wrong. Fortunately there are tools like CSSLint, which can scan your CSS and report errors and warnings based on the options you select. CSS preprocessors (see below) will also validate your CSS.
∙ Chrome devtools can warn you of syntax problems with your CSS, in its Console window use the filter button at screen bottom and activate that "CSS" checkbox.
∙ if you are using jQuery UI then you need to understand its CSS classes — these will help: http://learn.jquery.com/jquery-ui/theming/api/ , http://api.jqueryui.com/theming/icons/ , jQuery UI page on its CSS Framework
∙ if you are using jQuery UI you'll also want to look into using predefined themes, which can do a lot of work for you and can help give your application a consistent look. See the UI Components section below for more on themes and jQuery's themeroller.
∙ layout is done with CSS, and you might want to use a grid-based layout. Grids are an integral part of some CSS frameworks like Twitter Bootstrap (see next bullet). Grids divide the screen up into columns that can be used with responsive design CSS to adapt your page to screen capabilities (basically its width and height).
∙ there are CSS "frameworks" (there's that word again) that assist you with layout and styling of your application. Probably the best known of these is Twitter Boostrap. TB is a collection of tools and standards for creating web sites and applications. Since it employs best practices and web standards and incorporates handling of cross-browser differences it can save you a lot of time and make your design more solid. And Twitter Bootstrap doesn't only address styling and layout, it also comes with some jQuery plugins for things like dialog windows, tooltips and carousels.
∙ if your requirements allow you to use CSS3's flexible box model you'll find layout easier, but unfortunately support/consistency for this isn't great. See the Developer tools and utilities section for a bit more on this.
Resources: General CSS
Here's a grab-bag of resources on CSS you might find interesting:
∙ Smashing's post on mastering CSS principles
∙ vanseodesign on CSS specificity, inheritance, cascade
∙ alistapart.com article on CSS positioning 101
∙ barelyfitz.com post on CSS positioning
∙ http://weblog.bocoup.com/dive-into-flexbox/
∙ twitter bootstrap on its grid system
Media queries and responsive design
Web page design got a lot more complicated with the advent of mobile devices with their wide range of screen sizes and pixel densities and their ability to change orientation betweeen portrait and landscape. Even simple web pages can sometimes become useless when displayed on a small-screen phone. This problem has driven something called responsive design, where the web page (or web application) is written to adjust to its environment. This adaptation is often done via CSS3 media queries, which let you apply CSS styles conditionally based on screen characteristics like screen size, pixel density, orientation, etc. For example, you could use the media query below to adjust the font size for a phone's lower resolution screen:
@media (min-device-width:320px) and (max-device-width:480px){ html, body { font-size: 110% ; } }
Media queries are especially useful for SPAs that target mobile devices, even more useful when your SPA is expected to run on both desktop and mobile. They offer a clean, simple way to adapt your views at runtime, letting you resize or reposition or even hide elements based on the available screen real estate. For example, the code below can be used reclaim some precious pixels when the user rotates their phone into portrait mode. This query hides descriptive field labels in portrait mode but when the user rotates their phone back to landscape the labels reappear. All without a single line of JavaScript.
@media only screen and (orientation:portrait) and (min-device-width:320px) and (max-device-width:480px) { .fieldLabel { display:none ; } } @media only screen and (orientation:landscape) and (min-device-width:320px) and (max-device-width:480px) { .fieldLabel { display:inline ; } }
Of course, since SPA developers are used to using JavaScript to change CSS properties one temptation is to not bother with media queries. After all, it's easy to listen for orientation change events and run an event handler that would change field label visiblity, attempting to replicate the media query above, something like:
// isMobile defined at init based on device capabilities if (isMobile && ($(window).width() < $(window).height()) ) { $(".fieldLabel").css("display","none") ; } else { $(".fieldLabel").css("display","inline") ; }
However, the above doesn't really replicate the media query, since it runs only once on rotation, and any views created after device rotation won't have their field label's display value modified. You could write more code to address this, but why bother — media queries handle this cleanly and in a very performant way. While SPAs do often need to change CSS values via JavaScript, when it comes to view adaptation you should try to use media queries — i.e., the pure CSS solution. This adheres to the HTML/CSS/JavaScript separation of concerns — HTML is for content, CSS is for appearance, and JavaScript is for behavior. Just as you'd avoid embedding styles in your HTML markup so should you avoid putting them in your JavaScript whenever possible. Moreover, if you're working with a page designer then keeping style information in style sheets can make the developer/designer collaboration much simpler.
On the other hand, not all adaptation can be handled by media queries. For example, for phone's smaller screens you might want to substitute shorter messages that will fit in smaller message fields or use popups for important messages that won't fit in available screen space (JSDE does both of these). In cases like this media queries won't do the job and JavaScript is the way to go.
Resources: media queries and responsive design
∙ IBM developer works library on media queries
∙ Adobe devnet intro to media queries
∙ alistapart articles on responsive web design
∙ Smashing guidelines for responsive web design
∙ Smashing on techniques for gracefully degrading through media queries
∙ html5rocks on mobile responsive design
CSS preprocessors
You can develop SPAs using plain vanilla CSS, no problem. However, as you build larger applications which use more CSS you may want to look into using a CSS preprocessor. CSS preprocessors can make your life a bit easier by adding new capabilities to CSS, like the ability to create variables and functions, to import CSS mixins from external files, etc. They also provide some nice convenience functions. Below is an example of a color adjustment function (both Less and SASS have lighten and darken — below is the SASS syntax).
darken($color, 10%); /* returns a color 10% darker than $color */
The preprocessor performs its operations (variable resolution, conditionals evaluation, mixin imports, etc.) when your enhanced CSS (that is, CSS with preprocessor statements and directives) is "compiled". The output is a standard CSS file that you'll use in your application. Note that the preprocessor's capabilities resolve at compile time, not at runtime in the browser (hence the pre of preprocessor).
Another benefit of using a preprocessor is CSS validation. Normally when you make an error in your CSS, for example omitting a closing bracket, you get nothing to tell you of the problem — you only realize it at runtime when odd things start happening with your layout or your UI's appearance. A preprocessor can serve as your early warning system for CSS errors since it checks everything in the compile phase and will highlight any errors.
As with most JavaScript tools you have several good options to choose from, though you might want to start by looking into LESS or Sass (they're the focus of the resources list below).
Resources: CSS Preprocessors
∙ lynda.com's introduction to Less and Sass - pre-processed CSS languages
∙ css-tricks post on Sass vs. Less
∙ tutsplus article on HTML CSS preprocessors, Sass vs. Less vs. Stylus
∙ hongkiat post on Sass vs. Less
JSDE doesn't use a CSS framework like Twitter Bootstrap and it doesn't use a preprocessor. It does makes use of media queries and does use some Modernizr tests to conditionally inject mobile-specific CSS via JavaScript. More on JSDE implementation... |
JavaScript
What's true for HTML and CSS is true also for JavaScript — if you're going to develop SPAs you don't need to be a guru but your knowledge of the fundamentals does need to be solid. A lot of web developers have tinkered with JavaScript when building web sites, using it for important but fairly simple tasks like forms validation or populating an autocomplete control or effects and animations. But that won't be knowledge enough for developing full-blown client-side applications. Here are some things you should be comfortable with if you want to develop JavaScript applications:
∙ know the good parts and the bad parts of JavaScript. And there are some pretty bad parts. You need to know what they are and how to avoid them. For example, global variables are evil and in JS they're the default, but you can minimize them by faithfully using the var statement and using an application namespace (the "use strict" directive and JSLint can help here, too). Another example: the language doesn't have private scoping, but you can mimic this with closures (which are one of the very good parts of JavaScript). One more: historically JS hasn't had block scope, which isn't good at all, but if you're using the latest version of JavaScript (Ecmascript 6, or ES6) you can get block scope using the new goodness of its let statement.
∙ JavaScript design patterns. Design patterns are just proven solutions to common problems. Think of them as your application development playbook. As you build your applications you return to that playbook constantly, choosing from it whatever patterns help you get the job done. Some patterns are very general and are used in many languages, like the Observer, Singleton and Factory patterns. And some are more specific to JavaScript, for example the Revealing Module pattern, which uses closures to create private variables, exposing only the data and behavior you choose to make public.
∙ jQuery. Yes, I know it's not really part of JavaScript, and I know some people don't like it, but you'll likely need something that abstracts away browser differences for DOM manipulation and event handling so why not choose the de facto standard for this? Besides, jQuery does a lot of great stuff via a brilliantly simple API. More about jQuery in the developer tools section.
∙ JavaScript OOP. A huge topic, and one of the quirkier ones. JavaScript is an object-oriented language, but OOP in JavaScript isn't OOP as you find it in languages like Java or Flex. For example, there's inheritance, but it's prototypal inheritance. Some libs and frameworks require a solid understanding of JS OOP but you can certainly create SPAs without it. In fact, because of the way JavaScript handles functions you can get a lot of code reuse through use of mixins and features like call() and apply(). However, for large-scale SPA development you'll probably find that you need to know not just OOP general concepts (inheritance, constructors, use of this, etc.) but OOP the JavaScript way (quirks and all).
This list could be longer but you get the idea: you need to take the language seriously. Without a solid JavaScript foundation there's a good chance your JavaScript application will end up a JavaScript train wreck. If you've just used JavaScript for simple web pages as opposed to web applications then you probably want to check out the resources below.
Resources: JavaScript books
∙ Flanagan's JavaScript: The Definitive Guide is a must-have, one of the best and most comprehensive books on JavaScript
∙ Crockford's JavaScript: The Good Parts — esp. good for js noobs
∙ A bit more advanced is Stepanov's book Javascript Patterns
Resources: JavaScript online
∙ Dmitry Soshnikov reference on core JavaScript
∙ MDN refresher "A re-introduction to JavaScript"
∙ Misko Hevery Youtube: Introduction to JavaScript and Browser DOM
∙ JavaScript Jabber podcast 026 Code Organization and reuse — includes nice discussion on JS OOP v. use of composition/mixins/aggregation. See also this useful post on mixins.
∙ Addy Osmani's online book "Learning JavaWScript design patterns"
∙ Part 1 of a series on js design patterns: Adobe devnet article on JS design patterns - Singleton, Composite, and Facade
∙ Murphey's original book on jQuery fundamentals
There are also some good video tutorials available, most notably Crockford's video series.
Application architecture
Much of this section is Applications Development 101, it's not specific to JavaScript appdev, so if you're already an applications developer you may want to skim down to the lists of JavaScript-specific resources
Architecture is important to any non-trivial application. Get it right and you have prospects of a smooth application development journey. Get it wrong and the journey will likely be longer and over a much rockier road. Of course there are many ways to cover this ground efficiently, yet the most efficient methods do have commonalities — for example, better to be modular, better when these modules are loosely coupled so that a change to one module doesn't require edits to lots of other modules, and usually it's better to keep your data handling code seperate from your view code. With a good architecture your application is more scalable and easier to understand, modify, maintain, and test. This section covers SPA architecture in the broadest sense, including not just Model-View-Controller (MVC) but also code modularity and code organization.
Your application architecture depends on your application requirements. Items covered in this section won't apply to all SPAs. In general, the larger your application the more you'll need the architecture elements described below. |
Models, Views, Controllers
MVC incorporates a separation of concerns, breaking your code into parts based on their role in your application. It separates data and data management code (the Model) from the UI code that displays that data to the user (the View). Controllers glue these two together, containing logic for things like handling user input, responding to UI events, initiating data fetches, etc. There are variations on MVC, such as MVVM and MVP, but most applications at least separate Model from View, so as a group these architectures are often called MV*. Here's a very simple schematic of MVC actors and interactions:
From Google's developer page on MVC Architecture
Why is this important? Here's a useful snip on this from Martin Fowler's "GUI Architectures":
"At the heart of MVC, and the idea that was the most influential to later frameworks, is what I call Separated Presentation. The idea behind Separated Presentation is to make a clear division between domain objects that model our perception of the real world, and presentation objects that are the GUI elements we see on the screen. Domain objects should be completely self contained and work without reference to the presentation, they should also be able to support multiple presentations, possibly simultaneously."
Of course, decomposing your application like this has costs, and if you're new to MV* architectures there is a learning curve. But for non-trivial SPAs the benefits usually outweight the costs. Having a model gives you an authoritative local copy of data to work with, incredibly useful for data binding and for cases where you're representing a single data point multiple places in the DOM. And a model that has no dependencies on the other parts of your application is more easily reused. And MV* facilitates creating applications that run on a wide range of devices because it's relatively easy to vary their views (simple UI for phone, richer for tablet, richer still for desktop) while often reusing much of your controller code and all of your model. If you want to leverage JavaScript's promise of creating cross-device multiscreen applications then you should consider employing a MV* architecture.
BTW, while JavaScript applications usually use a Model separated and decoupled from the rest of the application, in the SPA world the distinction between View and Controller seems to be less rigid. It's not uncommon to see these two roles combined, or at least sometimes overlapping. For example, Backbone has no clearly defined Controller layer (for an interesting discussion on this listen to the Javascript Jabber podcast "Backbone.js with Jeremy Ashkenas". Also related: Addy Osmani blog post on understanding MVC and MVP for JavaScript and Backbone developers).
Bottom line here: it's not happenstance that all the popular architecture-related SPA libs and frameworks employ an MV* architecture. If you want to develop SPAs and you're unfamiliar with MV* architectures then now is a good time to dive into this topic. Below are some resources to help you...
To keep things simple JSDE doesn't use a MV* lib such as Backbone, though MV* principles are followed. I hope to soon start working on a JSDE V2 which will make use of Backbone. |
Resources: MV* Architecture:
While you'll find no shortage of content on the topic of MV* architecture (no shortage of MV* libs and frameworks, either) the folllowing resources are specific to JavaScript development:
∙ Chrome developer article on MVC
∙ Addy Osmani on large scale JS applications
∙ Addy Osmani "Tools for jQuery application architecture"
∙ Addy Osmani on essential JS design patterns
∙ Addy Osmani on understanding MVC and MVP for JS and Backbone-developers
∙ Addy Osmani short musings on JS MV* tech stacks
∙ Addy Osmani on MVC pattern abuse or evolution
∙ Aaron Hardy JS apps basics articles
Also, Alex MacCaw's book JavaScript Web Applications not only covers this topic but it has separate chapters on three "architectural framework" MV* libs: Backbone, Spine, and JavaScriptMVC.
Addy Osmani's book Backbone Fundamentals has a nice chapter on MV* fundamentals.
Events, dependencies, loose coupling
When you break your application up into Model, View and Controller you need a way for the modules across and within these layers talk to each other. One way to do this is to let them talk directly — module A and module B have references to each other and call methods on each other. But this creates a dependency, or tight coupling. Dependencies often mean that changes to one module require changes in dependant modules; fewer dependencies generally translates into fewer code mods, lower risk of side effects from code mods, more flexibility, more code reuse.
Fundamental to MV* is that the model should be decoupled from the rest of your application. That is, it should know nothing about the views that display its data, in fact shouldn't know or care whether its data is even being consumed.
However, if the model doesn't know about the views then how does it notify views of data changes? Because views usually do need to know about changes to the data they're displaying in order to keep current (i.e., to avoid showing "stale" values). So, how does the model communicate data changes? One answer is to have the model broadcast events.
Models can communicate their changes indirectly to the rest of the system by firing custom events such as countryDataUpdated, activeCountryChange, countriesFilterCleared, etc. Views (or their controllers) simply listen for these events and respond appropriately when the events fire. For example, if a <table> is displaying a list of countries and the data that drives that <table> changes (e.g. the user applies a global filter to the data: show only countries where Continent=Asia) then you may want your <table> to reflect this global change (e.g. it updates to show only rows for Asian countries). If the model fires a change event whenever a filter is applied then views that care about these changes can simply listen for this event and refresh their data whenever it fires.
Note that this event handling differs slightly from that used for built-in UI events like click or mouseover. Events fired from DOM elements bubble, which is great, the fact that these events bubble up lets you couple things loosely through delegation. However, for non-DOM elements like data collections or plain old objects bubbling doesn't apply — for model objects not in the DOM hierarchy there's no bubbling up because they're not in a hierarchy, hence there's no up.
Of course, any non-DOM object can listen for events fired from another non-DOM object, it just has to know the event dispatcher's objectID in order to set an event listener on it. But that sets up a dependency, and we'd rather avoid that. And one way to do this with minimal dependencies is to employ a central event manager for these events, something that can insulate the participants from knowing about each other. Instead, they only need to know about that centralized event manager. Not surpsingly, something this useful is already a common design pattern, a pattern called Publish/Subscribe.
SPAs often implement an event bus using the Publish/Subscribe pattern, or Pub/Sub. Pub/Sub is like Observer except that the participants don't need to know about each other. With Pub/Sub a central event manager serves as middleman for dispatchers and listeners. Any two objects/components/modules can communicate by having one fire an event through the Pub/Sub manager (the publish part of Publish/Subscribe) while the other registers a handler for the event with the manager (the subscribe part). Whenever a managed event is published the manager invokes the event handler(s) of any subscriber(s) to that event. This approach means the participants don't need to know about each other (i.e., have an object reference) which would create a dependency — they only need a reference to the pub/sub manager.
To illustrate how this works here's a quick summary of how JSDE uses Pub/Sub to keep data views in sync with model data.
First, here's some background on JSDE data handling and data display:
∙ JSDE lets you explore info on the countries of the world
∙ at startup the countries data is pulled from the backend db into a client-side model
∙ this client-side countries data is consumed by a variety of views, each displaying data in a different format — one uses a simple <table>, another uses maps, another a bar chart, etc.
∙ to see subsets of the data a user can apply a global data filter (e.g., show me only countries in Asia).
∙ all data views honor global filters. This means that when a filter is applied to the data each data view updates so it reflects the filter criteria (in our example, the tabular view would show rows only for Asian countries, the bar chart would show only bars for Asian countries, etc.)
Now here's how the views are kept in sync with the model through Pub/Sub (note how simple and clean this approach is):
∙ when a filter is applied (actually, when there's any change to the data) the model dispatches (publishes) change events such as countryDataUpdated through the Pub/Sub manager
∙ all views that display country data (let's call them dataViewers) subscribe to the model's data change events through the Pub/Sub manager. On change events their event handlers execute the dataViewer's render method, updating the view so it reflects any change in the data. This ensures that these views stay in sync with the data they're presenting.
This Pub/Sub approach allows for very loose coupling, making it easy for JSDE to have any number of dataViewers active because:
∙ the model has no knowledge of the dataViewers and thus needs no references to them. As a result, when dataViewers are added or removed there is no change to the model — in fact the model isn't even notified of these dataViewer state changes
∙ adding new dataViewers is simple: just initialize the dataViewer and have it register with Pub/Sub for data change events
∙ removing a dataViewer is equally simple: you simply unsubscribe from data change events and then destroy (or cache) that viewer
JSDE uses both Pub/Sub (as described above) and custom events fired from non-DOM objects (using jQuery's trigger function). More... |
Of course, loose coupling is good, but somewhere you need dependencies — for example, something needs to know about the model so it can initiate data refreshes, query data attributes, maybe update one of the model's data values based on user input. In MVC this is the job of the controller. The controller layer knows about both the views and the model. That is, it can have object references to them and make direct calls on both. However, the level of knowledge of the controller (even the existence of a discrete controller) varies by MV* architectures (in other words, some don't have a discrete "C" at all, instead embedding the controller functionality right in the view).
Resources: Events and Pub/Sub:
∙ MSDN book chapter on inter-app communication
∙ tutsplus tutorials on loose coupling with pubsub
∙ http://msdn.microsoft.com/en-us/magazine/hh201955.aspx
∙ Matt Lunn's article on event delegation in JavaScript/ Andi Smith on jQuery events methods on and off
∙ Michael Hamrah's post on event pooling with jQuery
Modularity
Some parts of this primer aren't specific to JavaScript web applications, they're true for applications development in general, and this is particularly true for the topic of modularity. If you have any experience developing applications then the benefits of modularity is not news to you — you already decompose your code into functional units which you combine to construct your applications. Unfortunately some web developers already have experience with JavaScript and may have acquired bad habits they'll carry over into JavaScript application development. For example, in developing web sites it's very common to have a chunk of JavaScript in a single <script> block that's embedded in an HTML file. Which is fine for simple web pages, but for JavaScript web applications this just won't do.
A basic implementation of modularity in JavaScript is breaking up your js code into individual .js files that are external to your HTML (e.g., they become .js files under a project /js directory). These are best stored in subdirectories, essentially organized into packages that reflect their functional role in the application (this is covered in the next section, Code Organization).
Modularity works best when your module's internals are hidden from other modules except for those variables and methods that you intentionally expose through a public interface. While JavaScript has no concept of private variables or methods you can mimic encapsulation through closures. One JavaScript design pattern that provides this is the revealing module pattern.
There's an aspect of this topic I won't cover here but want to mention — module standards. Here we're talking about a way package code into reusable units that can be plugged into any application. Unfortunately this seems to be in flux right now, with several standards, and several is not a good word to be using when you're talking about standards. if you're interested in this topic look into AMD, CommonJS, of course node.js has its own mobule standards, and ES Harmony addresses this. Addy Osmani has an overview "Writing Modular JavaScript With AMD, CommonJS & ES Harmony" so you can read his post to get started.
In addition to JavaScript files your application may also use templates, which are basically snippets of HTML used to generate views (details in the Views, Templates, and Data Binding section). While you can have template code embedded within your HTML this gets messy quickly if you have more than a few templates. Better to use an approach similar to that of your JavaScript files, breaking your templates out into separate files in a /templates subdirectory. This makes for easier editing, management, and lazy loading. JSDE uses about two dozen template files, and because they're external files it's able to pull them from the server only when they're needed (templates are covered in more detail below).
JSDE is comprised of many individual files. Because JSDE doesn't use concatenation you can see these indivdual files (and the directory structure they use) through developer tools like Google Chrome's devtools (use its Sources tab). |
I'll let Alex MacCaw have the final word here, this is from the very first pages of his book Javascript Web Applications:
"The secret to making large JavaScript applications is to not make large JavaScript applications. Instead, you should decouple your application into a series of fairly independent components. The mistake developers often make is creating applications with a lot of interdependency, with huge linear JavaScript files generating a slew of HTML tags. These sorts of applications are difficult to maintain and extend, so they should be avoided at all costs."
Resources: Modularity
∙ Nick Zakas slideshare on scalable JS application architecture
∙ Bocoup post on organizing your Backbone application with modules
∙ online book on SPAs, chapter on maintainability1
∙ Addy Osmani on large scale javascript - module pattern
∙ David Litmark's post on intro to the revealing module pattern
Code Organization
If you've developed server-side web applications or even created basic web sites you already use a directory structure to keep your files organized. This organization is just as important for SPAs.
If you decompose your SPA into modules focused on a single function/responsibility you'll get many individual .js files. Your application will also have numerous .html and .css files, probably some template files, also some libs like jQuery, etc.
So, where should all of these files go in a directory structure, what should that structure look like, how should they be organized into packages? If doing it from scratch it might look a bit like the hierarchy shown below (this is just an outline, with only one or two files shown in each directory):
/jsde (application root)
│
├── jsDataExplorer.html
│
├──/css
│ ├── jsde.css
│ ├──/jqueryui
│ │ └── jquery-ui-1.8.23.custom.css
│ ├──/icons
│ │ ├── stats.png
│ │ └── tabular.png
│ └──/images
│ └── mapBackground.jpg
├──/js
│ ├──/libs
│ │ ├──/jquery
│ │ │ └── jquery-1.8.0.min.js
│ │ │ └── jquery-ui-1.8.23.min.js
│ │ └──/underscore
│ │ └── underscore.min.js
│ ├──/models
│ │ └── countries.js
│ ├──/views
│ │ ├── globalOptions.js
│ │ ├── navbar.js
│ │ └──/dataViewers
│ │ ├── statsViewer.js
│ │ └── tabularViewer.js
│ │
│ ├── jsDataExplorer.js
│ └── jsdeUtils.js
│
└──/templates
├── globalOptions.tmpl.html
├── navbar.tmpl.html
├── stats.tmpl.html
└── tabular.tmpl.html
The point here isn't any specific structure, it's just structure — don't dump files into a single directory, instead try grouping related files in subdirectories that reflect their usage. Of course, you may be required to adhere to some corporate standard structure, which is fine, it's still structure (hopefully designed by someone who knew what they were doing).
If you want some guidance on how to organize your files then you should check out one of the poular application boilerplates that provide structure and more. For example take a look at the very popular HTML5Boilerplate or make use of Initializr to generate a "custom boilerplate". These provide an application skeleton that's proven useful over time (similar concept to design patterns — don't reinvent the wheel, rely on proven solutions to common problems in order to simplify/speed development). Some of these boilerplates are focused on the needs of mobile and HTML5 developers, and many have utilities that assist you with code management, optimization, and deployment.
JSDE uses a structure similar to that shown above. More... |
Resources: Code Organization
∙ Bocoup article "Organizing Backbone using modules"
∙ backbonetutorials.com "Organizing Backbone using modules (RequireJS)"
∙ tutsplus "The official guide to HTML5 boilerplate"
On this topic you should also check out Nicholas Zakas' book Maintainable JavaScript.
Developer tools and utilities
This section interprets "developer utilities" broadly, covering utils for developing and managing your code (e.g.,CSS preprocessors, Firebug, Grunt, etc.) and also utils used at runtime by your application (things like Modernizr, YepNope, Ben Alman's resize plugin, etc.)
Remember, this post assumes you are not using a framework like Sencha, Ember, etc. A high quality framework usually includes a wealth of utilities. |
If you've done any work with JavaScript you probably already use at least one utility library — something to smooth out browser differences. Probably you use jQuery for this, and probably you also use some jQuery plugins. And that may be all that you need for a simple web application. However, this section makes some suggestions as to other libs and tools you might find useful for SPA development (just some basics).
As just mentioned, a fundamental tool is a lib for DOM manipulation and event handling that deals with browser differences — not just differences between browsers (e.g., IE v. Chrome) but also differences between versions (e.g., IE6 v. IE9). Obviously this need isn't specific to SPAs, you pretty much need this for any content you want displayed in more than one browser (sad, but true). jQuery is the de facto standard here, and great stuff, but you also have good alternatives like Zepto.
You'll also need tools for debugging code that's running in the browser. Some IDE's have great debuggers but you'll also want to learn your way around the developer tools of the major browsers (Firefox's Firebug add-on or the Chrome/Safari/IE integrated Developer Tools). All have a JavaScript debugger and console, obviously essential, but equally important are their other features — tools that let you inspect the DOM, that let you view and dynamically modify the CSS for any element, that let you monitor network traffic and view server request results, that let you analyze performance including memory usage (more on that in a moment). Suggestion: learn one tool in-depth, it's one of the best SPA developer investments you'll make. But also learn the basics of the other browsers tools — you'll find this useful when you hit browser-specific problems whose debugging sometimes requires using tools in the browser where the problem is occuring.
If you're developing for Android then take a look at Chrome's remote USB debugging feature, which lets you use the same Chrome devtools you use for desktop. |
An important but probably underused feature of these browser devtools are their profilers and analyzers. These tools can help you optimize your application's performance and memory use. That last one is critical to SPAs, and something most web page developers haven't needed to worry about previously. After all, you rarely encounter memory problems when you're working with simple web pages that live only until the next browser refresh. Memory leaks usually go unnoticed if a page isn't around long enough to consume significant memory, since any problems go away when the page is refreshed and memory allocated to that page is reclaimed. But SPAs don't have page refreshes and this makes memory leaks a concern. A major leak can affect not only your own application's performance but also impact other browser pages and processes. On mobile where memory is limited an application that gobbles memory can crash its page or even the entire browser session. Which can be very embarassing. So, take the time to learn how to identify and kill memory leaks. Chrome devtools make this very accessible so you have no excuse not knowing how.
Also related to code quality are tools for checking your code for potential problems. While JavaScript doesn't have a compiler to catch errors early there are tools like jsLint and jsHint that can scan your code for potential problems. They warn you of code which does something risky (e.g., creates an implied global variable or implied octal) or is simply dead or incorrect code (e.g., unneeded semi-colons, missing semi-colons).
Another tool for improving code quality is the "use strict" directive. This ES5 enhancement restricts usage of some JavaScript features that weren't well thought out (e.g., with, eval) and turns some silent errors into runtime errors. It also makes your code more "future-proof", stopping you from using words that aren't reserved in earlier versions of JavaScript but which will be reserved in the future (e.g., class, interface, implements, etc).
Next let's talk about working with arrays and objects and performing other data manipluation tasks. While jQuery has some great tools for this you may want to augment it with Underscore. Underscore describes itself as "a utility-belt library for JavaScript that provides a lot of the functional programming support that you would expect in Prototype.js (or Ruby), but without extending any of the built-in JavaScript objects. It's the tie to go along with jQuery's tux, and Backbone.js's suspenders." It's well worth a few minutes of your time to go to Underscore's site and check out the features of this powerful and very lightweight library.
Another great SPA developer tool is Modernizr. The Modernizr library lets your JavaScript query the HTML5/CSS3 capabilities of the browser you're running in (does it support touch? canvas? geolocation?). Based on these checks you can use Modernizr's dead simple asynchronous resource loader (yepNope.js) to load polyfills which "fill in" missing capabilities (e.g., on older browsers that don't dispatch window.onhashchange events you can use yepNope to load Ben Alman's hashhange polyfill). And Modernizr even lets you create a custom build with only the capability tests and other options you want, thus keeping its size to a minimum. See Modernizr's site for more info.
You'll also want something to help you with layout, since you'll be doing a lot of dynamic positioning and sizing. So far I haven't found anything that stands out in this category (and certainly nothing that comes close to the great layout capabilities of Flex). CSS3's flexible box (a.k.a flexbox) would help but it has poor/inconsistent browser support. Twitter Bootstrap's grid layout is another option. One layout-related problem you'll encounter is that HTML containers don't fire events on changes like resize. When doing dynamic layout it's very useful to know when a container has been resized, but HTML/DOM lets you set listeners only for browser/window resize, not individual container resizes. Fortunately there's a jQuery plugin for this, Ben Alman's great little resize plugin (check out this examples page to see it in action). Admitedly this is an imperfect solution since it uses polling to "watch" for changes in container attributes, but for now I've found nothing better for this task, if I do I'll update this section or write a dedicated post on the topic.
I covered CSS preprocessors up above in the CSS section, so I'll just mention them here — when you find yourself working a lot with CSS it's time to look into using something that provides the missing bits you always wanted, like the ability to have variables and conditionals and mixins. And a compiler that can catch errors before runtime.
I'll also mention the build tool Grunt here, though it too is covered in another section (see Deployment tools). Grunt is a build tool that automates deployment tasks, from minification to concatentation to linting to running unit tests and more.
Of course every developer has their own favorite tools, and this section could be much longer — one great aspect of SPA development is that there are lots of great tools available (even better, most of them are free and open source).
For a list of libs and plugins used by JSDE see the JSDE post's Developer tools and utilities section. |
Resources: Developer utilities
Here are some browser devtools links you might find useful (mostly for Chrome devtools). Also see my post Browser-based developer tools, which includes a section on using USB debugging to use desktop Chrome devtools for your mobile devices.
∙ Google main page on Chrome developer tools
∙ Paul Irish's video "Google Chrome Developer Tools: 12 Tricks to Develop Quicker"
∙ Sixrevisions.com has a list of quality "Google Chrome DevTools Tutorials"
∙ Maj Taby's article on webkit web inspector -part 1
∙ Andi Smith article on dev tool secrets
∙ Smashing's article on JS profiling with Chrome devtools
∙ Addy Osmani Breakpoint video - episode 1
∙ Addy Osmani on performance optimisation with Chrome devtools timeline and profiles
∙ Addy Osmani article "Taming the unicorn - easing JS memory profiling in devtools
∙ alistapart article on taking advantage of html5 and css3 with modernizr
∙ Youtube: The Breakpoint Ep. 7: Profiling a mobile site with Chrome DevTools and Android (and other episodes, esp 6 and 8)
Here are some resources specific to Underscore:
∙ developerdrive.com's Introduction to underscore -- part-2 -- array-like collections
∙ Stackoverflow question "do Underscore and jQuery complement each other"
∙ tutsplus tutorial "Getting cozy with Underscore"
∙ Aaron Hardy's JS basics section on Underscore
Client-side data
A big part of SPAs is client-side data. This data can be created client-side (e.g. info on application state) and it can be backend data pulled from the server (e.g. one or more db tables from which views are frequently generated). Note that the latter isn't some shadow copy of your entire backend db, it's just selected data whose "local-ness" lets you reduce or even eliminate some trips to the server. This section describes benefits of client-side data and how it is stored and managed.
One obvious reason SPAs use local data is faster view generation. When views can be generated from client-side data the latency of a trip to the server is eliminated; the speed of in-memory data access can result in very snappy screen updates and a very responsive UI. However, improved performance and better UX aren't the only benefits of having client-side data.
Having data stored in a client-side model that broadcasts its data changes can help you keep views in sync with the data they're presenting. In the past many web applications that needed to store data on the client just used the DOM. But the DOM isn't a great data store. For one thing, we come back to performance — DOM access is slow. And using the DOM for your datastore can complicate your life when a data value is being presented in multiple places (are you sure they're all in sync?). And if a DOM-stored data value is changed — perhaps recalculated based on some user input — how do you ensure that all view representations of the data value (or derivations of it) are updated so they reflect that change? Here's a relevant quote from Backbone's main page:
"When working on a web application that involves a lot of JavaScript, one of the first things you learn is to stop tying your data to the DOM. It's all too easy to create JavaScript applications that end up as tangled piles of jQuery selectors and callbacks, all trying frantically to keep data in sync between the HTML UI, your JavaScript logic, and the database on your server. For rich client-side applications, a more structured approach is often helpful."
To deal with these issues SPAs usually store data in a client-side model, with the model handling the fetching, storing, and managing of data on the client. Data is stored in arrays, objects, arrays of objects, etc. This model holds the canonical client-side copy of your data — all representations of this data on the client are driven by what's in the model. This simplifies keeping things in sync. It also facilitates the use of data binding, which can automatically update any view representation of a data value when that data value changes (more on data binding below in the section Views, templates, and data binding).
The remainder of this section covers three aspects of client-side data — getting the data to the client, storing it and manipulating it on the client, and keeping it in sync with the backend master copy.
Ajax, JSON, and caching
SPAs generally fetch data from a server using Ajax, which lets your JavaScript application interact with a server without page refreshes. With Ajax you use JavaScript to call server processes and the call's results are returned asynchronously to the client. This type of server interaction is fundamental to JavaScript web applications, where your server requests are less for fully-formed web pages generated by the server than for raw data and other resources which you exploit on the client, usually to generate views.
I'm including Ajax in the data management section because you'll mostly use it to transfer data between client and server. However, keep in mind that it's not just for data, you can use it to pull other files as well (e.g., HTML templates, .js files, CSS, etc.). |
Ajax is straightforward, though if you haven't previously done asynchronous programming you'll need to make that mind shift of using result handlers to process your request results (i.e., you set handlers for request success and request failure, one of which will be called with the request results when your request completes). If you're using jQuery then you can just use its ajax method or one of its related convenience methods (e.g., get, getScript, getJSON, etc.).
Implicit in Ajax is that you have backend processes that you can invoke and which return data in a usable format. This backend might use simple REST calls or it might employ remote procedures created with PHP, Node, Ruby, etc. As mentioned previously, SPAs are generally agnostic regarding the backend. That's because SPAs alter the relationship between front-end and back-end — no longer is the backend's primary role content generation, now it's mostly a file/data server and perhaps a compute server. To the SPA developer the backend is primarily just a RESTful endpoint or RPC provider that serves up raw data and files which are exploited on the client.
JSDE uses a backend of PHP procedures which make queries against a MySQL db. Results are returned as JSON (note that you can inspect the returned data using the Network option of browser tools like Chrome devtools). More... |
You'll often see SPA data transferred in JSON format. JSON is an alternative to XML for serializing and transferring data. Familiarity with JSON isn't exactly required for SPA development but you'll see it a lot and some libs actually do assume you know it. The good news — there's almost nothing to learn, JSON basically uses the same data format as JavaScript objects, hence the name JSON, or JavaScript object notation (though this data structure isn't unique to JavaScript, it's just a hash common to other languages like Ruby, ActionScript, etc.). Since JSON is object-based (stringified for data transfer) it's easy to work with. However, note that JSON stores only primitive values (strings, numbers, boolean, null) and arrays and objects.
Once that data is on the client you'll want to make sure you don't fetch it again unless you have to so you'll want to implement some sort of caching mechanism (the browser will cache files like .html's and .png's for you but when it comes to data you're on your own). Local data stored in memory can make for very fast retrieval, though don't go overboard on mobile devices, which have limited memory, so there you need to be judicious on the amount of memory you use. To maximize performance you can also prefretch data and other resources, attempting to anticipate the user's needs.
JSDE uses Ajax to pull its data and caches most of this data. It also pulls all templates via Ajax and caches them, as well. However, in this version the applying of data filter criteria is delegated to the server and not performed locally (though clearing of data filters is satisfied from cache). More... |
Resources: Ajax, JSON, Caching
∙ Wikipedia page on ajax
∙ tutsplus tutorial on ajax calls with jQuery
∙ MSDN book chapter on client-side data management and caching
∙ http://www.johnpapa.net/spapost8/
∙ Wikipedia page on JSON
Working with client-side data
Once you've pulled your application data to the client you need to store it in a format that's easy to work with and which helps you keep it in sync with your server-based master data. One choice here is a JavaScript object. JavaScript objects are just simple hashes and thus easy to work with. They can store basic JavaScript types (string, boolean, array, etc.), and their structure can be as simple or complex as you need. Another advantage is that js objects can also implement (or inherit) behavior (i.e., methods) that can help with dispatching change events, performing validation, and more.
SPA libs and frameworks use js objects as the store for data entities. For example, if you're working with data on countries you could have an object named country with attributes such as name, continent, region, population, surfaceArea, etc. Here's a sample using object literal notation:
var country = { name:"Afghanistan", code:"AFG", continent:"Asia", region:"Southern and Central Asia", surfaceArea:647500, indepYear:1919, population:22664136, lifeExpectancy:45.9, gnp:5976 }
Of course, you'll often aggregate related data entities into list structures. To continue with the above example, if you're work with countries data you'll have many country objects (one for Afghanistan, yes, but also one for Albania, Algeria, Andorra, etc.). You'll want to put these in a list structure that lets you add new items, delete items, sort the list of items, etc. To store and manage these related objects you can use an array or an object, and that's fine. However, some SPA libs provide "smarter" data structures that can make your life easier. An example of this is Backbone's Collection class. The advantage of Backbone's collections over arrays is that collections fire change events when an item is inserted or removed, when the list is sorted, etc. (see this for a summary of BB events). As noted previously, events can simplify keeping your views in sync with the data they are presenting. An example here might be a selection list. If the data driving a list is a collection then that list can listen for collection change events. When an item is removed (or added, etc.) from the collection then the list can respond to this event by updating itself, thus always keeping in sync with its data.
JSDE doesn't use Backbone and so isn't using its Collection class. In this version the model fires data change events "manually", mostly using Pub/Sub but also firing its activeCountryChange events from a jQuery-wrapped non-DOM object using $.trigger(). More... |
For manipulating arrays and objects you can use jQuery functions like map and extend. However, for this you may also want to look to Underscore. This lightweight lib provides many great data manipulation utilities as well as other utils useful in SPA development. Below is a relevant quote from the Underscore main page. For more on Underscore see the previous section on Developer Tools and Utilities.
"Underscore provides 80-odd functions that support both the usual functional suspects: map, select, invoke — as well as more specialized helpers: function binding, javascript templating, deep equality testing, and so on. It delegates to built-in functions, if present, so modern browsers will use the native implementations of forEach, map, reduce, filter, every, some and indexOf."
Synchronizing local data edits back to the server
Just like server-driven CRUD applications a JavaScript-driven CRUD application needs to sync data that's been created/updated/deleted on the client back to the server-side master db. In this regard SPAs match what server-driven applications do — validate the input data, handle validation errors, serialize data to send it, maybe send via an async Ajax call, maybe even deal with network connection loss by storing edits locally for deferred synchronization. Lots to do, but with SPAs you don't have to work everything out for yourself, since some SPA libs and frameworks assist with this sync-to-db issue. To illustrate this let's look at how Backbone supports synchronization.
JSDE isn't a CRUD application and doesn't sync any data back to a db. More... |
First a bit of background on Backbone infrastructure. Backbone has a Model class that's used to store client-side data entities (the Backbone Model class is basically an object enhanced with Backbone-specific properties and behavior). You'll aggregate your model instances into Collections (another Backbone class, as noted above this data structure assists you by firing events when its data changes). To use the countries data example once again, for each country in your data you'd create a Backbone model instance having properties like name, continent, population, lifeExpectancy, surfaceArea, etc. and you would store these instances together in a Backbone collection (which you'd probably name countries or countriesCollection). In the case of JSDE this collection would contain 238 country model instances, with 238 = number of countries in the JSDE countries dataset).
Backbone's Model and Collection classes have built-in sync-related properties and methods. You initiate a sync-to-db by calling save() on a model instance. This kicks off a process that begins with data validation. While no validation is required, if you do specify a validation routine then Backbone runs it before save() executes. If validation fails then the save won't happen — instead you'll get an error event and any error callback you've provided will execute.
If the data passes validation then the model will delegate to the sync() method. Sync() sends model data to a server based on the properties you've set on the model or its containing collection (e.g., to its url property you assign a RESTful endpoint, which is your back-end data handling process). The server call is made via jQuery's (or Zepto's) ajax method, to which you can pass parameters. If the model you are saving is a new record then the data is sent with POST; for edits of existing records it's sent with PUT. Serialization is handled by the sync routine, with the data sent in JSON format.
As with so much in Backbone, there's a lot of flexibility throughout this process. For example, if the standard sync() processing doesn't meet your needs you can override it (and you can do this globally, or at the collection level, or even at the individual model level). You might do this to insert your own serialization routine, or if your backend doesn't use a RESTful JSON API, or you might add a persistence layer.
The point here is that you don't have to work out the entire CRUD commit process yourself, some SPA libs and frameworks provide you with a flexible mechanism for this that you can use as-is or build upon.
For more on Backbone see my JSCT SPA/Backbone demo app and its long JSCT overview doc that explains Backbone basics. |
For more info on Backbone's sync method:
∙ dailyjs.com 2012 Backbone tutorial #4
∙ devaddiction.com tutorial on Backbone synchronization and persistence
∙ Aaron Hardy's JS basics page on Backbone models/
∙ tutsplus tutorials on building a contacts manager using Backbone - part-5
∙ Addy Osmani has an online book on Backbone which has a section on the sync API
Resources: SPA data management
Most of these relate to Backbone simply because that's what I'm exploring right now:
∙ Bocoup on Backbone live collections
∙ Aaron Hardy on Backbone models
∙ Aaron Hardy on Backbone collections
∙ developerdrive.com introduction to underscore -- part-2 -- array-like collections
UI Components
This primer focuses on developing SPAs using a collection of libs, not a framework like Sencha. Some frameworks provide extremely rich UI component sets, so if you use one of these you may not need any of the UI libs mentioned here...
If you want your SPA to have a rich UI you'll need some rich components. Standard HTML form controls simply aren't up to the task — you'll want component sets that include things like accordions, splitters, grids, even data visualization components (e.g., scatter plots, bar charts, bar/QR code widgets, etc.).
In your components you should look for more than just rich end-user functionality. Yes, you want components with lots of options for the users of your application, for example a grid that has built-in sorting and filtering and grouping. But end-user functionality is only half the picture. You also want components that give you, the developer, more functionality. For example you want components that let you change options after creation, with each component instance maintaining its own state, and each able to respond to option change requests individually. So, while standard plugins are fine for many things you should also consider using stateful components.
What is a stateful component? Here's a snip from the MS Silk Chapter 3 that addresses this from the jQuery point of view:
"A stateful plugin is an advanced type of jQuery plugin that is self-aware — it maintains its own state, and often provides an external interface for outside code to interact with and alter the plugins state. Stateful plugins, or widgets as they are often called, often trigger events and provide callback hooks into important parts of their functionality. A plugin of this type normally focuses on solving a single task, often constrained to a single area of a finished web page. These widgets try to make as few assumptions about their final use as possible, choosing to expose options instead of internal hard coded settings wherever possible."
So, really good SPA widgets are more than standard jQuery plugins, they're plugins with enhanced capabilities that let you store state and change that state. They have convenience features like merging your instance's option values into the component's default option values. And they incorporate a lifecycle, having methods and events like create and init and destroy so you have hooks that allow you to build and teardown components. This last one is important since failing to dispose of components properly can result in memory leaks.
When it comes to memory use SPAs can encounter problems you won't see in traditional short-lived pages which persist only until the next (frequent) page load. For short-lived pages if you have a problem like a memory leak you won't notice it if the page is refreshed before the leak becomes a problem. Refresh the page, memory is reclaimed. But for long-lived SPA pages which constantly create and dispose of views (and their components) you need to pay attention to memory usage. When you're done with a component you need to clean up resources it uses and clear any references to the component so its memory can be reclaimed by the garbage collector. Failing to do this can inhibit garbage collection, and that can result in memory leaks that can cause browser performance or even stability problems. Bottom line: while your widget may be short-lived your application may run for hours, so your widgets should be written and used in a way that doesn't prevent their garbage collection.
Another thing to look for in components is extensibility. This is, you want components that you can use as a starting point for creating your own components. jQuery UI (and libs that build on its architecture, like Wijmo) provide this extensibility through its widget factory.
JSDE has simple examples of using, extending, and creating-from-scratch jQuery UI widgets. More... |
For simple applications you can build your UI using components from various sources and hope everything integrates cleanly, but for more complex applications you'll want a lib of integrated components. These range from the basic set of components included in jQuery UI to more comprehensive libs like Wijmo and Kendo. Larger libs can contain everything from popup windows to mini text editors, simple splitters to carousels, grids, even charting. However, for larger, more comprehensive sets of components you'll usually need to pull out your wallet (e.g., Wijmo base set is free but the full set including charting is not).
One thing to note about libs like jQuery UI (and components based on it, like Wijmo) is that they extend standard DOM elements like divs and anchors. This lets them to incorporate progressive enhancement, allowing your application to still function when running in older browsers or those that have JavaScript disabled. Your application will of course look and act differently in "fallback mode", but the goal is that it will still be able to deliver your core functionality.
For its UI JSDE uses standard form elements such as <select>'s and checkboxes, jQuery plugins like jqPlot, many components from jQuery UI, and even one Wijmo component (its splitter). More... |
Next up is aesthetics. You can do amazing things with CSS, but if you aren't a designer this can be time consuming and even a bit tedious (and problematic if you are aesthetically-challenged). If you want your components to have a unified look-and-feel then choose components that support theming. Themed components incorporate common CSS classes that can be used to set a global look and feel for your application. Predefined themes are available and can be customized. A great example of this is jQuery UI's Themeroller.
If you are targeting only browsers that support CSS3 you might consider Twitter bootstrap. It has a lot of UI capabilities, from a grid layout system to nice styling for controls. It also comes with jquery plugins for components like a navbar, modal windows, etc. While these are jQuery plugins they aren't stateful components like jquery UI widgets. However, there is a mashup of Twitter Bootstrap and jQuery UI created by Addy Osmani, something he calls jQuery UI Bootstrap. See his blog post for more info.
JSDE uses Wijmo's Rocket theme with tweaks (I especially added a lot of alpha, i.e., RGBA with RGB fallbacks). |
This post and JSDE don't really address mobile, but in a section on UI and widgets it's worth mentioning that what's good for a desktop SPA isn't always good on mobile. This is especially true of UI and widgets. Many components designed for desktop browsers will be less functional or even completely non-functional on a mobile device. They're also often too heavyweight for mobile and don't adhere to the mobile UI idiom. For running on mobile you really want "mobile aware" components that do things like handle touch events, account for mobile screens' wide range of screen sizes and dpi's, honor mobile UI conventions, etc. I'll cover this in future posts and JSDE version 2 will focus more on mobile.
The current version of JSDE wasn't really written for mobile. While it does use CSS to deal with sizing issues it isn't a mobile UI, and on phones it's pretty much useless (especially on devices that don't support CSS overflow:scroll). On tablets things are better mostly because of the larger screen. It also uses too much memory for mobile and is slow in spots (especially initialization). More... |
Resources: UI Components (most of these are specific to jQuery UI):
∙ ajpiano.com - widgetfactory/#slide1
∙ tutsplus tutorials -- Coding your first jQuery UI plugin
∙ MSDN on stateful plugins and the widget factory
∙ MSDN intro to complex UIS using jQuery UI
∙ jquery.com post on writinng stateful plugins with widget factory
∙ jqueryui.com on the Widget factory
∙ bililite tutorial on jQuery UI widgets
∙ Eric Hynds tips for developing jQuery UI widgets
∙ nemikor on building stateful jquery plugins
∙ JavaScript Jabber podcast 059 on jQuery Mobile
Views, templates, and data binding
One big benefit of SPAs is UI. SPAs can provide a smooth UI because they avoid page refreshes by using JavaScript to create or update views from client-side data. Without the constant disruption of page loads everything is less web-ish, more application-like. And reducing server requests (and their inherent latency) can yield a more responsive UI. All in all, the user experience can be a marked improvement over traditional server-driven applications (though, as noted in my JS web apps primer part 1, traditional apps can still do a lot of client-side processing and provide smooth UI between page loads — the "islands of interactivity" or "part-time SPA" approach).
While a SPA can deliver a better UI this doesn't come for free — the SPA developer needs to not only asynchronously fetch the data used to generate views and to manage this data on the client but also to exploit it, generating HTML markup from it and then getting those views onto the DOM. And of course you probably want your views to stay in sync with the data they're presenting — i.e., if that data changes then you need to update your views to reflect those changes. This section covers two aspects of this, client-side templating and data binding.
Generating views from client-side data
Turning data into views isn't difficult, you just need some JavaScript that generates HTML markup from input data (which could be a string, a set of numbers, a collection of objects, whatever). Of course you could generate this markup using string manipulation, something like:
viewMarkup = "<div><h1>" + myData.title + "</h1><p class=introText>" + myData.intro + etc...
As you might expect this can get messy quickly, especially with complex views. It also can be hard to read, and of course this approach more tightly ties HTML into your JavaScript code. An alternate approach to generating views is the use of client-side templating.
Templates are basically snippets of HTML with placeholders for data. At runtime a template "compiler" is passed a template and some data and the result is HTML markup. The template processor generates your markup by substituting the data you pass it into the template's data placeholders. In other words you can take a template such as this:
<div class="detailDataPane" >
<span class="detailTitleText"> <%= country.name %> </span>
<p> Country code: <span> <%= country.code %></span></p>
<p> Continent: <span> <%= country.continent %> </span></p>
<p> Region: <span> <%= country.region %></span></p>
<p> Population: <span class="numberData"> <%= country.population %></span></p>
</div>
...and submit it to a template processor with the country data we saw earlier...
var country = {"name":"Afghanistan ","code":" AFG","continent":" Asia","region":" Southern and Central Asia", "surfaceArea":647500 , "indepYear":1919, "population":22664136, "lifeExpectancy":45.9, "gnp":5976 }
...and with a little CSS for styling (e.g., #detailDiv span { color:#369 } ) you can get some HTML that will render as:
Afghanistan
Country code: AFG
Continent: Asia
Region: Southern and Central Asia
Population: 22,664,136
In this example the country object is referenced within the substitution operators <%= and %>. When you submit your template and data to the template processor it resolves those data references and the result is HTML tailored to your data. All without a trip to the server so it's very fast. Note that this example is using Underscore's template syntax; other libs use different syntax (e.g., Handlebars uses {{ and }} for its substitution operators).
Obviously the above example is simple, using only a single data object. More complex markup, for example converting an array with many rows of data into an HTML <table>, requires some iteration over the data and maybe some other logic, and template engines provide this in varying degrees. Some have their own constructs to control things like iterating over a data structure, while others like Underscore let you directly incorporate JavaScript for your controlling logic (though be wary here, avoid mixing a lot of JavaScript into your HTML).
There are numerous template libs with a range of functionality, syntax, and complexity. If you're new to client-side templating a good starting place is Underscore's microtemplates, which are dead simple. |
So, where do you put your templates? The simplest approach is to define them inline in a <script> block with the type property set to something other than type="text/javascript". For example the template code from above could be embedded in your HTML like this:
<script id='tmpl-detail' type='text/template'> <div class="detailDataPane"> <span class="detailTitleText"><%= country.name %></span> <p>Country code:<span> <%= country.code %></span></p> <p>Continent:<span> <%= country.continent %></span></p> <p>Region: <span> <%= country.region %></span></p> <p>Population: <span class="numberData"><%= country.population %></span></p> </div> </script>
Now your code can reference this template by that id, tmpl-detail. And once that template is available you can "compile" it into a view and then put that view on the DOM. Here's sample code using Underscore to process the template and jQuery to put the view on the DOM:
// use jQuery to get the template's markup var tmplMarkup = $("#tmpl-detail").html(); // tell Underscore to compile the template with active country's data var compiledTmpl = _.template(tmplMarkup,{country:$activeCountry.country}); // now use jQuery to update part of the page with the generated markup $("#detailDiv").html(compiledTmpl);
This approach of putting your templates inline in your HTML is a great way to get started, very simple since your templates are fetched along with your HTML. However, this approach does have drawbacks. For one thing, your HTML editor won't recognize your HTML as HTML (because of the type= value) so it won't highlight keywords correctly and code hinting won't work. Also, templates can be hard to manage when embedded in HTML files. If you have more than a few templates it's probably better to extract them out into a separate files. This makes them makes them easier to work with and gives you more control over which files are pulled and when they're pulled. And giving these files an extension of .htm or .html means they'll be handled correctly by your editor (you get the code coloring/hinting you expect).
As for when you pull your templates, you may want to pull them only as they're needed. This avoids pulling templates you'll never display, and can potentially boost performance at startup. However, while pulling templates async only as they're needed has benefits it also a cost — on first access you'll see a delay in getting that template on screen (latency as the template is fetched from the server). For some applications this may not be acceptable, and you may do better pulling your templates earlier. For a bit more on this topic see this Derick Bailey post on async loading of templates.
JSDE uses Underscore's simple microtemplating feature, with about two dozen templates stored in external files. Templates are pulled async only as they're needed and then cached via a simple template manager. More... |
Bottom line on generating views: while simple applications might get away with using string manipulation to generate views, templates are generally more readable than string manipulation code and easier to maintain. Templates also let you extract this UI code from other code, allowing you to store it in seperate files/directories.
Keeping views in sync with their data
Most of the time you want your views kept in sync with the data they're presenting — if the data behind a view changes you usually want that view refreshed so it reflects those changed value(s). A common pattern for addressing this is to have the data broadcast any changes and the views listen for those broadcasts, updating themselves with the new data values. That's the approach JSDE takes — the model publishes custom events like countryDataChanged and continentStatsUpdated, the views are listening for the events, and the event handlers execute the necessary view renders. While this is straightforward coding it can require a fair amount of boilerplate code so it's worth mentioning features of SPA MV* libs and frameworks that can help you out with this.
Some libs have classes with this functionality built in. That is, they have a data store class (model) and data display class (view) that are designed to work together. They can be easily wired up so that a change to data will trigger an event handler for updating the view. If you build with those classes you're saved from writing this code yourself. For example, Backbone has Model and Collection classes that fire data change events. And instances of Backbone's View class can easily be wired up to respond to these events. If you build your application using these classes then keeping your views in sync with the data they're presenting can be as simple as assigning some properties and writing the event handler code. Here's a snip from Backbone's main page on this:
"Whenever a UI action causes an attribute of a model to change, the model triggers a "change" event; all the Views that display the model's state can be notified of the change, so that they are able to respond accordingly, re-rendering themselves with the new information. In a finished Backbone app, you don't have to write the glue code that looks into the DOM to find an element with a specific id, and update the HTML manually — when the model changes, the views simply update themselves."
There's another way to have your views stay in sync with their data, something that Backbone doesn't have (though it can be added with plugins): data binding. With data binding you can create a "live connection" between a DOM element and local data. If this data later changes then its representation(s) on screen will automatically update without you having to write any event handling code — the update just happens. This can be brilliant, especially when you have one data point represented in multiple places in the DOM. Some frameworks even offer two-way binding where a change to a bound DOM element (for example, the value of an input field) will update the model data it's bound to (i.e., the DOM element's new value will get written back into the model).
This should sound good, and it is, but not all architecture libs and frameworks have data binding and some do it better than others. Note that most of the magic of binding is achieved through events (setting a watch on an observable property). You can achieve a similar "connection" between model data and UI elements by writing your own code as described above, but bindings free you from having to write that code. Note that a good binding lib does more than set up some implicit event listeners/handlers, it's code also handles potential errors and does its updates efficiently (e.g. in some cases uses dirty flags to defer commit or render processing).
While the simplicity of data binding can make your SPA developer's life easier it's also a potential banana peel waiting to trip you up — when a lot of bindings fire all at once you can see a performance hit, so be judicious in their usage. In any case, while binding can make your life easier it isn't required for SPA development.
JSDE doesn't use data binding, it keeps views' representation of model data in sync the old fashioned way — by having the model publish change events to which controllers subscribe, with the controller's event handler code responsible for updating the view with the new data value. More... |
Resources: Client-side templates
∙ Aaron Hardy JS basics section on Underscore
∙ headspring.com primer on Underscore templates
∙ Stackoverflow questions on external templates in Underscore
∙ Smashingmagazine on client-side templating
∙ encosia.com on using external templates with jQuery templates
∙ Derick Bailey on asynchronously loading templates for Backbone views
∙ Derick Bailey on view helpers for Underscore templates
∙ Rob Dodson on using jQuery deferred- to load an Underscore template
∙ tutsplus tutorials on best practices when working with JS templates
Routing, navigation, and state management
Web pages and server-based web applications are driven by URLs — you give the server an address and maybe some parameters and the server sends you back a page. Through its URL a page can be bookmarked or shared with others. URLs also provide site or application navigation — change the URL and you get a new page, or view, or state. And of course your browser's Back and Forward buttons work by changing the browser's active URL from a session list of previously visited URLs.
However, this use of URLs isn't so straightforward with SPAs, which have just one URL, the one that loads your application. After that a SPA isn't driven by URLs, it's driven by your SPA's JavaScript. So, for SPAs you need a way to bookmark and share a SPA's "page" (i.e., application state) that doesn't rely on "normal" URLs that will trigger a page load. And you need a way to tie into the browser's history so you can honor the browser's Back and Forward buttons. This last one is especially important because users are accustomed to using those buttons for navigation, but without some special handling a Back button click in a SPA will result in the user exiting your application, returning to the web page they were viewing before they accessed your application. Here's a snip from Google's developer's site on this:
With SPAs "there is no longer a 1-1 relationship between HTML pages and the many states which can exist in any given web app. Fortunately, browsers allow us to work around this problem by manually creating new entries in its location history. By doing so, you can create artificial app states throughout your web app and enable the use of browser BACK events to navigate backwards to a previous app state. Leveraging this ability can help you improve user experience by protecting users from losing their state when hitting the browser BACK key."
One solution to all of this is to make of use a URL's hash part, that tacked-on-the-end part of a URL that you sometimes see after a # symbol (#! is also used). For example:
http://www.dlgsoftware.com/jsdemoapps/jsde/jsdataexplorer.html#Afghanistan
The hashpart of a URL was originally intended for in-page navigation, letting you scroll to a specific location within a page. Since changes to the hashpart don't trigger page reloads SPAs can safely use the hashpart to represent an application state. This use of a URL's hashpart allows you to share this URL or bookmark it just like a "normal" URL. For example, load the above URL to display JSDE with information for Afghanistan. Then change the hashpart value to France and JSDE will display the "France state" (actually in JSDE it's the activeCountry state with get_$activeCountry().country.name==France)
Of course, just setting the hashpart to some string doesn't magically change your application into the appropriate state. You still need some code that makes the appropriate state changes, and you need some middleman that watches for hashpart changes and then executes the appropriate code (this code will map the hashpart to a new state). So, when the hashpart is "#Afghanistan" you need something to execute the code that puts the application into the "Afghanistan state". This mapping of hashpart or URL into some action is called routing.
There are two main ways to do routing — either using hashparts and hashchange events or using the more functional HTML5 pushstate. The latter is better but I''ll focus on hashchange because it illustrates the concepts adequately and is a bit simpler.
The HTML5 history API and pushState have advantages over using hashchange. Perhaps the biggest advantage is that they let you change the browsers URL without triggering a page load. This lets you map state to a "normal" URL (with no hashes or hashbangs). However, incomplete/inconsistent implementations means you still might want to make use of a lib like history.js, which smooths out cross-browser differences and also implements a polyfill that falls back to hashchange for older browsers.. |
Applications that use hashtag routing listen for changes to the URL's hash part, translating the hashtag value into an application state. To do this you set a listener for the haschange event, which fires whenever the hashpart changes (note that this event is available for HTML5 browsers; for older browsers you can use a polling polyfill such as Ben Alman's hashchange plugin). In your hashchange event handler you can obtain or modify the current URL's hashtag value via the global location.hash. Once you have this value your code can set the application state based on that hashtag value. Of course, what states your application supports and how you execute state changes is up to you.
This may sound like a lot of work but really it can be straightforward if your states are simple. Let's look at JSDE for an example. It uses routing to control which country's information is displayed — if the hashpart is "Afghanistan" then the application will display or highlight information on Afghanistan, if "France" then info on France is displayed, etc. This simple routing takes less than a dozen lines of code (see below). The event handler simply reads the hashpart value and then updates the application's "country state", changing the current active country. This change will fire an activeCountryChange event, and since JSDE views are written to respond to activeCountryChange events the result is an update of views to show or highlight info on the new active country.
$(window).on('hashchange', function() { // hashpart in JSDE should be a country name, e.g. Afghanistan var decodedHash = decodeURIComponent(location.hash.slice(1)) ; // get current active country (data is in child object 'country') var $activeCountry = JSDE.countriesModel.get_$activeCountry() ; // if new state matches prev state (i.e. hashpart value is same // as current active country) then bail out, no changed needed. // Use short-circuit eval to catch null $activeCountry.country. if ($activeCountry.country && $activeCountry.country.name === decodedHash ) { return ; } // now just set active country by updating model's value JSDE.countriesModel.set_$activeCountry(decodedHash) ; }
Note that this relationship between URL hash and application state is bidirectional. That is, while a change in the URL's hashpart can trigger a change in application state, it's also true that changes your application makes to its state may require a corresponding change to your URL's hashpart. This ensures that the current URL and application state stay in sync. So, to continue with the above example, in JSDE if Afghanistan is the active country but then the user selects a different country, for example they select "United States" from the country selection comboBox, then JSDE must update the URL's hashpart accordingly:
http://www.dlgsoftware.com/jsdemoapps/jsde/jsdataexplorer.html#United%20States
Doing this requires one line code (in the code below this holds the active country object so this.name is what we want as our hashpart, the active country's name):
// ensure that URL reflects changes to active country location.hash = "#" + encodeURIComponent(this.name) ;
Note that putting this into location.hash updates your session history. And this lets your user make use of the browser's Forward and Back buttons. Continuing with the above example, now if the Back button is clicked you return to Afghanistan. If you then hit Forward you come back to United States.
Keep in mind that the above example is simple, and it's also a bit of a cheat. JSDE's hashpart contains a single value (the active country's name) and always executes the same action (setting the active country state). More common is for a hashpart to include an action value and optionally some parms, e.g., somedomain.com/#action/parm1/parm2. In this case the routing code parses the hashpart, mapping the action value to a function call and passing the parms along to that function. As your mapping gets more complex you'll want to check out libs that can assist you with this task (e.g., history.js). In addition, some architecture libs like Backbone and frameworks like Ember incorporate routing functionality.
Of course, it isn't required that your application implement routing. However, browser users have certain expectations — they expect to be able to bookmark a page, and especially to be able to use the browser's Back and Forward buttons. And remember that if the user clicks that Back button and you've done nothing to handle that click then the browser will happily take them to the previous page, which for a SPA means they'll be exiting your application.
One last thing to mention: driving your application through hashparts like this has an SEO impact: web crawlers like Google won't "see" your logical web pages (i.e., its different states), though you may be able to address this using Google's Ajax Crawlable specs. This is one significant downside to an SPA — if your application needs to be exposed to search engines then you must do work that you wouldn't need to do for a URL-driven application. Of course, many applications just don't need or even want SEO — if you're developing an internal business application that isn't surfaced to the public, say something for analyzing private financial information, then having your application content available to search engines like Google isn't likely to be on your list of requirements.
JSDE keeps things simple, using hashchange routing, not pushstate. And It limits the state you can bookmark or access through the browser's Back and Forward keys to the active country. It doesn't restore the entire UI state such as active dataViewers or window positions. More... |
Persisting state between sessions
While we're talking about state it's a good time to mention session persistence — that is, having your application "remember" what the user was last doing and restoring that state when they next access your application. When it comes to application persistence it's all about saving data and later, when the user returns to your application, reading that data and using it at application initialization to restore all or part of the user's previous session.
It's important to note that the session state you save and restore goes beyond what we talked about above — for session data we're talking about state in a larger sense, encompassing a wider range of information, things like user preferences, current container positions and/or sizes, the values of input fields, etc. The application state that you save and restore is entirely up to you, it can be a little or a lot, but the more you restore the happier your user will be.
All of this is particulary important for mobile. That's partly because of the way some mobile browsers work. Some browsers like Chrome manage memory aggressively, swapping out of memory the contents of a browser tab when it isn't active, then reloading those contents if the user later returns to that tab. This keeps memory usage down, but it's also a problem for SPAs since the restoration of a tab's contents is often done by reloading your application via its current URL (including hashpart). That will restore any state you've stored in that URL but any state not stored in the URL (e.g. the current application layout, or the criteria behind a temporary filter, or session-specific options like the hiding/unhiding of columns in a dataTable) will be lost unless you do something to restore it. Of course, you could just stuff more info in that URL, but really that isn't feasible — putting every aspect of state into the hashpart of all of your URLs is overkill, especially since some values need to be restored only at initialization time. Far better to save any state info you want to restore into a persistent datastore (e.g., localstorage) and then read this datastore whenever onLoad fires and then execute code that restores the state. Now when the user returns to that mobile browser tab they'll be able to pick up work right where they left off (something mobile app users are accustomed to).
For server-side applications the norm for storing application state data is a backend db, which makes sense when content is generated on the backend. But for SPAs there are times when you'd prefer to store state on the client. One option for that you is the localStorage feature of the Web Storage spec.
LocalStorage is widely available (major browsers including back to IE8) and its simple key/value store can be sufficient for saving/restoring basic application state. However, it is very limited in capabilities (stores only strings, requires you to serialize/deserialize objects, is not secure) so depending on your application requirements you might want to look at more functional alternatives. One option is Indexed Database, which is a W3C spec, though there's a catch — it currently isn't consistently implemented across browsers. There's also Web SQL Database, but this isn't a long-term solution, as W3C has abandoned its development.
JSDE uses localStorage to persist state between sessions. This includes saving/restoring the active country and some other information such as global preferences. However, many other aspects of state are not restored in this version, such as window locations/sizes and any active data filters. More... |
Resources: Routing/Navigation
∙ Google developers article on hash navigation
∙ diveintohtml5 book chapter on browser history
∙ tutsplus tutorials -- A first look at the history API
∙ backbonetutorials "What is a router"
∙ MSDN "A History (API) Lesson"
∙ post on "Intelligent State Handling"
∙ MDN on Manipulating the browser history
∙ podcast: javascriptjabber podcast on browser history and tools/libs
∙ sample app: Adobe devnet demo app using history/deep linking
Resources: Local storage and application persistence
∙ diveintohtml5 chapter on client data storage
∙ tml5please on support for html storage
∙ html5rocks on client-side data storage
∙ sixrevisions.com introduction to web storage
∙ MDN: "There is no simple solution for local storage
Resource loaders and dependency management
SPAs generally have many individual JavaScript files that need to be pulled from the server. Of course, you can load these via a slew of <script> tags, but this won't be efficient. For one thing, that's a lot of server requests, each of which adds overhead. You can reduce these through concatenation (i.e., make one file out of many) but that can result in a large file size and may result in pulling files you won't actually need at application startup. Or perhaps never need at all — for example, if the user never accesses your superDuperView then you really don't need to pull superDuperView.js to the client. And what if they do access superDuperView but not until they've been in the application for ten minutes — if you load superDuperView.js at initialization when it isn't needed then you've introduced some unnecessary startup latency.
Application startup is of particular concern since the loading of each <script> file is blocking, or synchronous, meaning that nothing else will happen when your js files are being processed . This can result in a noticeable delay at initial load, with the user staring at a "frozen" web page. So, one issue you need to address is getting all of your JavaScript to the client efficiently. For optimal performance you want granular control over when or even whether to pull a file.
And then there are dependencies to deal with — i.e., if Module B uses code from Module A you'll need to make sure that Module A is always loaded before Module B.
To deal with these kinds of loading issues you should consider using a resource loader or dependency management library.
Resource loaders help you get your resources (not just js files but also other file types) from the server to the client without a page refresh. Of course you can pull files with jQuery's $.get, but a dedicated resource loader can simplify things and can be very lightweight. For example, the simple script loader yepNope.js is built into Modernizr (if you do a custom Modernizr build look to include it as Modernizr.load) and makes it easy to pull a file based on browser capabilities. Example:
// If touch support add TouchPunch (maps touch->mouse events for JQUI) yepnope({ test : Modernizr.touch, yep : 'js/libs/jquery.ui.touch-punch.min.js' });
The code above loads the .js file conditionally, in this case only if the target device supports touch. More common is the reverse situation — i.e., you'll do a conditional load when a feature is not supported. This is usually how you load polyfills, which provide backwards compatibility patches for older browsers. For example, if an older browser does not support hashchange events you can test for this and load a polyfill such as Ben Alman's hashchange plugin.
Dependency management libs take things a step father, not just fetching files you need but also fetching them in the right order. With a dependency management lib you tell the system what the dependencies are — e.g., that module B depends on module A — and the lib collects all of this information into a master manifest which it uses to load files for you. Once you've set things up your application needs only one script tag to load the dependency management lib and then that lib will load everything else.
There are a number of libs in this category, from lightweight resource loaders like yepnope through the full bells and whistles dependency management of RequireJS.
JSDE does not do hardcore dependency managment, simply using yepNope to defer loading some modules and to conditionally load others. More... |
Resources: Dependency Management
∙ MSDN on dynamically loading JS resources
∙ tutsplus on script loading with the yepnope lib
∙ Adobe devnet article on JS and requirejs dependency management
∙ MSDN's "How, When, And Why Script Loaders Are Appropriate"
∙ quora.com question What are the use cases for RequireJS vs Yepnope vs LABjs
∙ javascriptplayground's Intro to RequireJS
∙ Ben Nadel's article "Lazy Loading RequireJS\ Modules When They Are First Requested"
∙ requirejs.org's page on "Why AMD"
∙ unscriptable.com article (a bit dated now, from 2011) AMD versus CJS - what's the best format/
Testing
This section just touches on the topic of testing, mostly referring you to more detailed resources. For ad hoc development testing using tools like Chrome devtools see the Developer Tools section.
The nature of JavaScript (loosely typed with implicit type coercion, no block scope (unless you use ES6), default global scope with significant potential for var name collision, semicolon insertion, etc.) means you really need to do more testing than you might in other languages. Of course, how test-able your application is will be directly related to how it's structured. As noted in the Application architecture section, modularity and separation of concerns is important for a number of reasons and one of these is testability. Your testing will be easier and you'll have a wider choice of testing tools if you build with modules that are focused on a single task, which have clearly defined inputs and outputs, which can easily be fed mock data.
Unit tests call your modules with a variety of inputs and then verify that the module's output is as expected. Having a suite of these tests that you can run on demand allows you to constantly validate your code as it develops. Large-scale projects often take this approach, running the test suite both on demand for builds and as a nightly automated process. If this is the kind of thing you're intrested in then you're in luck, JavaScript application developers have several good options for running unit tests and validating the outputs. The references listed below have links related to several of the more popular unit testing tools.
Then there's UI testing. One benefit of mobile web apps is their ability to run on many OS's and mobile devices. But mobile devices have a wide range of screens, performance, and (this next one is the extra burden web apps carry that native apps don't) browsers. So the ability to run on a wide range of targets comes with some special costs, and one of these is UI testing.
And UI testing is a downside to SPAs, becaase on mobile it's hard (well, maybe not so much hard as time-consuming and tedious). If you go to JavaScript conferences you've probably seen at least one presentation on developing mobile web apps which shows a photo of a testing lab with dozens of phones and tablets lined up on a bench, each patiently awaiting some unfortunate developer who needs to validate application UI, the developer spending hours clearing browser caches and manually running test scripts on different devices (and too often finding inconsistencies between browsers that require workarounds). If you're targeting mobile devices and want to be sure your app runs as expected on each of these targets then manual testing on a lot of devices is unavoidable (automating UI test is hard enough on desktop, much worse on mobile). Of course, there are emulators, but they're never quite the same as the real thing. On the other hand, obtaining all of those "real things" can cost real money. Bottom line: UI testing is one of the more painful downsides to web app development.
So, are web apps worth that extra testing effort? Of course the answer to that is simple — it all depends on your project requirements. The key thing is to budget for testing, especially if you care about mobile. And if you care about mobile then have some aspirin handy, because browser consistency hasn't improved as much as you'd expect on mobile where's there's no such beast as IE6 (though I have heard Android's stock browser called "the new IE6").
JSDE is just a demo app, not a production app, so I didn't create unit tests — it's the classic case of not wanting to budget the time, although since I've been doing all of this on my own time I can't feel too guilty (well, a little guilty, just not guilty enough). More... |
Resources: Testing
∙ Nick Zakas slideshare on JS error handling
∙ singlepageappbook.com book chapter on maintainability
∙ MSDN Project Silk post chapter on "Unit Testing Web Applications"
∙ This page is the entry point into an MSDN book-length series of posts on the topic of testing web apps, from 2007 so the envionment has changed but most of the concepts here still apply
∙ Addy Osmani on Backbone fundamentals: unittesting
∙ netmagazine.com article on top 5 JS testing libs
∙ MSDN "Unit Testing 101: Are You Testing Your JavaScript?"
∙ MSDN "Testing mobile web experiences"
Deployment tools
Optimized deployment is important to any web application, even to simple web pages, and you probably already use things like minification, concatenation, CDNs, etc, so I won't spend a lot of time on this. Just keep in mind that optimization is particularly important for client-side JavaScript applications since they usually have a lot to load at initialization, and the cumulative size of those resources can be significant.
There are various deployment tools that provide basic concatenation and minification, things like YUI Compressor, Minify, Grunt (with its many plugins for optimization and deployment tasks), etc. Some shell projects like HTML5Boilerplate also have built-in tools to assist with deployment. And some dependency management libs provide deployment tools — for example, RequireJS includes an optimizer that can shorten var names, concatenate files, minify, etc.
One thing to keep in mind is differences in the needs of a web application versus a web page. For web pages it's pretty well accepted that you should use concatenation to reduce HTTP requests, combining all of your CSS into one file and your JS into another. This means you're able to get all of your code to the client in just two HTTP requests. And this makes sense for web pages. But web applications are larger and putting all of your js into a single file can actually hurt performance since you may pull files at init that you won't actually need. For more on this see the section above, Resource loaders and dependency management . The yepnope main page also has a nice bit on this topic (look for the section titled "Why not just concatenate all my scripts and put them at the bottom?").
One deployment tool worth special mention is Grunt, another great js appdev lib from Ben Alman. Grunt is a build tool that automates deployment tasks, from minification to concatentation to linting to running unit tests and more. In other words, it takes care of the grunt work for you. There are many plugins for Grunt and more coming every day (note that Grunt is a Node plugin so it and its plugins are managed with npm, familiar to many javascript devs). I'll probably add a blog post on Grunt in the near future.
For a bit more info on Grunt see my post Task automation with Grunt which includes a quick start guide to installing Grunt and its JSHint module. |
JSDE intentionally does NOT optimize, opting for clarity over efficiency. So, no minification or concatenation for JSDE code, many <script> tags, long descriptive names and tons of comments, etc. All of this is to make the code more accessible to noobs. |
Resources: Deployment tools
∙ Nick Zakas' book "High Performance JavaScript" has info on optimizing deployment
∙ Alex MacCaw's book "JavaScript Web Applications" has a chapter on this topic
∙ developer.yahoo.com - rules for performance
∙ encosia.com on CDN's -- "3 reasons why you should let Google host jQuery for you"
∙ tutsplus.com on the build tool Grunt
∙ speakerdeck.com intro to Grunt -- the JS task runner
∙ Alex Sexton on deploying javascript applications
Conclusion
As you can see from this post, creating a SPA isn't trivial — there's lots to learn, many different libs to plug together, plenty of interfaces where things may fit imperfectly (though you can minimize integration problems by adopting a well-integrated JavaScript framework).
Of course, you don't have to start out by creating a SPA. While this primer has focused on SPAs keep in mind what I said in my JavaScript web applications primer part 1 — web applications live on a continuum ranging from those where JavaScript plays a small role to those where it's the main driver of the application. In particular you should consider that middle ground, what MS calls the "islands of interactivity" approach, and what I call "part-time SPAs" — that is, web applications which may have occasional page reloads but where these might be few and infrequent, with most application pages persisting for significant periods and providing a high level of client-side processing.
As for mobile, if you need to target both iOS and Android you should also consider hybrids like PhoneGap apps, which let you create your application from a single codebase using JavaScript as your bridge across that iOS/Android divide (yes, PhoneGap also covers several other mobile platforms, but honestly it's iOS and Android that really count right now). Hybrids are HTML/CSS/JS-driven and browser-based, but it's a headless browser wrapped in a native app. And that native app exposes APIs not normally available to browser-based apps (e.g. camera) and let's you play outside the browser's security sandbox. Certainly there are cases where this hybrid solution could be effective.
For the near term SPAs will probably remain an interesting niche, though I suspect a constantly expanding niche. Whether SPAs are appropriate for a specific application will (as I've said repeatedly in this primer) depend on your requirements: the design you use and the tools you choose are always dependent on your application requirements. SPAs are just one more application design option, albeit one that can bring major benefits in regards UX and performance, and for some types of applications these benefits will be well worth the costs.
Related dlgsoftware posts
- Javascript web apps primer part 1
- JavaScript Data Explorer Overview (describes the SPA demo app)
- JavaScript Data Explorer (runs the SPA demo app)
Original version: October 2013
Copyright © Dan Gronell 2013. Licensed under the Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 unported license.