DLG Software


Application development

  with JavaScript, Flex, and AIR

This describes my JavaScript data explorer (JSDE)  demo application, providing implementation details and telling you where to look for things in the code.


JavaScript Data Explorer (JSDE)

Overview

This post describes my JavaScript data explorer (JSDE) application. Originally I created this thing for myself, just a vehicle for exploring JavaScript development and for comparing it to Flex development (basically JSDE is one of my Flex demo apps translated into JavaScript). Later I decided to clean it up a bit (not much, the code is still pretty rough) so it could serve as a companion to my JavaScript web applications primers. JSDE is the result, a simple JavaScript-driven web application that demos some basics of Single Page Applications, a.k.a. SPA's (as in my SPA primer part 1 I'll use the terms SPA and JavaScript web application interchangeably, but see the primer for a more nuanced definition).

This post complements my SPA primer part 2. That post and this one have largely the same structure, covering many of the same topics, but they cover them differently — where the primer provides general info on a topic this post provides JSDE's implementation details. For example, both posts have a section on UI Components, but where the primer describes basic concepts and general options for UI components this post lists the specific techniques and component libs used by JSDE (and gives the names of the code modules where they're used). Here's this doc's structure:


 
As with my SPA primers this post is for SPA noobs but it does assume you're not  a noob when it comes to web development including both JavaScript and jQuery.

JSDE functionality

JSDE lets you explore data on countries of the world in a variety of formats (tables, graphs, videos, maps, etc.). It does this partly through a mashup of functionality from Google Maps, Wikipedia, and YouTube. Nothing special, but then the data viewers aren't the focus of this demo application. Nor is the data — much of the data behind this demo app is old and has some dummy values — details here). Keep in mind that JSDE isn't a "real" application, it's just a vehicle for exploring different aspects of SPA development

Click image to open JSDE

As you can tell from the screengrab above, JSDE is like a dashboard application, letting the user explore data through a customizable UI that can display multiple "data views". This type of data exploration and/or ad hoc analysis is a good fit for SPAs largely because a SPA's client-side data and client-side view generation provides the performance and interactivity these applications require. Of course, JSDE's UI and functionality are crude, giving just a hint of what SPAs can do.


 
JSDE is for developers and isn't a real application. All of its data is old and it does have some dummy data values (so don't let your kids use this thing for schoolwork...)

Here are some of the SPA basics that JSDE demonstrates:

  • views created/managed on the client, not the server — no page refreshes
  • asynchronous (Ajax) data fetches and client-side data management and caching
  • use of libs like jQuery, jQuery UI, and Underscore and also some jQuery plugins like jqPlot
  • most views generated from templates (Underscore's simple microtemplating)
  • caching is used (not just for data but for resources like templates and some views)
  • use of some good js dev practices such as use of an app namespace and minimal use of global namespace, module pattern, loose coupling via pub/sub, etc.
  • architecture is very loosely coupled using MV* and event bus (pub/sub); the model has no knowledge of (dependencies on) other parts of the system

I hope to push a JSDE version 2 which will use more libs including Backbone, probably Wijmo, maybe RequireJS. I also want to improve mobile support and a few other things. So, think of JSDE V1 as for complete SPA noobs, while JSDE v2 will take things up a notch. But I may not get to this for awhile — already this project has taken more time than I planned and I have a backlog of TODOs to catch up on.


Caveats

This demo app is targeted at noobs so it employs simple implementations and favors clarity over performance. That means JSDE's code isn't minified or run through a code optimizer — I don't want to optimize away the code's many comments and long, descriptive variable names. I don't concatenate my .js files for the same reason — having the files kept separate means the application structure is easier to see in developer tools like Chrome's devtools. However, this all has a cost in terms of memory and performance, certainly it's not something you'd want to do in a real application. Bottom line here: I don't follow best practices when they get in the way of maximizing code accessibility/readability.

I should also point out that on occasion I use inconsistent implementations in order to show alternative approaches to solving a problem, For example, mostly I use pub/sub for event-based communication between modules, but I also fire all of my activeCountryChange events from a non-DOM jQuery-wrapped object just to demonstrate this approach.

I also take a few shortcuts, like wiring things up by naming convention, though I generally warn you of these (more on this later�).

Anyway, keep all of this in mind if you look into the application architecture and read the code — this isn't a reference application, it's just a simple demo app.


 
JSDE isn't even remotely a reference application, it's just a demo app with simple implementations intended to demo SPA basics and show how its parts interoperate.

Despite these limitations JSDE does demonstrate many SPA fundamentals working together. My hope is that something here will help SPA noobs in understanding the what, why and how of JavaScript web applications.


JSDE code

I'm not putting the code for this thing on github because it's just not great code (see Caveats above). However, you can easily see the code using your browser's developer tools (Firebug, Chrome devtools, etc.).

New to Chrome devtools?

I love Chrome devtools, especially its USB debugger capabilities for mobile, I can't imagine creating web applications without it (or some similar tool like Firebug). If you're a total Chrome devtools virgin and want to dive right into looking at some code try this:

  1. in desktop Chrome (not mobile) access JSDE: (click this link to run JSDE)
  2. now start up Chrome devtools by opening Chrome's main menu, select Tools, then Developer Tools (on PC's you can just hit F12)
  3. you might find it easier to explore devtools if you undock it, putting it in a separate browser window. To do this use the icon at lower left, choose "Undock into separate window"
  4. at this point you should see tabs or buttons along the top of the devtools pane (Elements, Resources, Network, etc.). Click on the Sources tab. This will display a tree view of the application source code (html/css/js) in the left side pane
  5. in that left-side tree view expand dlgsoftware.com to show its jsdemoapps/jsde subfolder. Now under that select the js subfolder and under that select jsDataExplorer.js. That should open the file in the code pane to the right. Note that you can read this and all JSDE files as plain code because I didn't minify them (though if they were minified you could just use the Chrome devtools "pretty print" option)

As well as showing you source code Chrome devtools lets you see console messages, the DOM tree, CSS styles on each DOM node (it also lets you dynamically modify that CSS), memory usage, and more. See my Primer Part 2 "Developer Utilities" section for a half-dozen good links on this topic.

I mentioned above that I take shortcuts, but note that I generally give you a head's up on these, either in this post or in code comments (search for the word "shortcut"). An example shortcut is the linkage between some data views and their icons. This is purely by convention: icons are wired up to dataViewers (a special type of JSDE view, more on that later) through a CSS class using the name format dataViewer name + "ViewerIcon". For example, the tabular dataViewer's icon is accessed through the .tabularViewerIcon CSS class. This makes it easy to access the icon from anywhere as long as you know the dataViewer's name — and as long as you follow that naming convention (failure to follow it isn't fatal, though, you'll simply get no icon).

On the positive side, the code does have lots of comments (or this could be a negative, since the commenting often veers into the realm of annoyingly-over-commented).

As for where in the code to look for things, well, that's what this post is for — it maps functionality to specific resources like .js files. It also describes how things plug together (architecture).

The rest of this post describes the different SPA features/functionality that JSDE demonstrates. This includes telling you where to look for the code that drives a feature/functionality and sometimes general information on the implementation. Hopefully this will help you figure out how things work. But don't expect too much here, this isn't serious documentation, I'm just pounding this out so I can move on (I've spent way too much time on this thing).

Oh, one last thing, before you go any farther let me warn you I'm no SPA guru, and I apologize in advance for places where my bad design, shortcuts, or sloppy coding makes things harder to grok.


JSDE Tools and Tech

The remaining sections mostly map to sections in my SPA Primer Part 2. While that primer covers general concepts, this post focuses on JSDE's implementation of these concepts.


HTML and the DOM

JSDE is a single page application so its load URL:
        www.dlgsoftware.com/​jsdemoapps/jsde/​jsdataexplorer.html
is the only one the user sees. This URL's hashpart does change, however, since it's used to represent application state. So, you'll see URL's like this:
        www.dlgsoftware.com/​jsdemoapps/jsde/​jsdataexplorer.html#Australia

The hashpart allows the application to make use of the browser's Back and Forward buttons to move through the countries that the user has viewed. This also allows bookmarking of application state. The hashpart is used because (this is very important to SPAs) changing it won't trigger a page reload. For more on this see my JS web apps primer part 2 section Routing, navigation, and state management.

jsDataExplorer.html pulls application CSS via <link> tags and after that it pulls the .js files needed for initialization using <script> tags. Some of these files are concatenated/minified but I don't do this for my own JSDE code (makes it easier for noobs to access/read this code at the cost of startup performance). Because JSDE uses a lot of individual js files this results in lots of <script> tags, and that translates into a pretty slow intial load (better after that first load since cache is primed). Note that jsDataExplorer.html doesn't load all application resources, mostly its just those required for application startup (others are lazy-loaded as described below).

The figure below shows the HTML layout that jsDataExplorer.html creates — a very simple "frame" of <div>'s into which more HTML will be injected by JavaScript. These three div's persist through the lifetime of the application. #navBar and #optionsPane are populated just once, at app init. #dataDisplayContainer is "owned" by the dataViewersManager and its contents change as dataViewers are added or removed.

When the URL has finished loading (specifically, on $(document).ready()) the main JavaScript file (js/jsDataExplorer.js) is executed. It starts by pulling some application resources asynchronously. Some are loaded conditionally — for example, the hashchange polyfill is loaded only if Modernizr shows that the current browser doesn't support the hashchange event. Still other resources aren't loaded at startup at all, they're only loaded as required — for example, templates are pulled only when they're referenced.

Here's a bit more detail on the init process from the HTML perspective:

  1. When dataExplorer.html loads its HTML creates a minimal page layout, a simple 3-container frame (navbar along window top, options pane at left, large data display pane at right). These containers will be populated with views that are generated client-side.
  2. on jQuery's $(document).ready the main .js file (js/dataExplorer.js) executes. This initializes the controllers for both the navbar and optionsPane which in turn generate and inject the HTML markup into the navbar and options containers respectively. At the same time HTML for the "Welcome" popup is generated into a modal jQuery UI dialog and displayed (but conditionally for mobile, only once per half-hour).
  3. the options pane (js/views/optionsPane.js) initialization creates a jQuery UI accordion component and then populates the resulting 3 accordion panels ("DataViewer Manager", "Data Viewers", "Data Filter") as follows:
    1. generates the active dataViewerManager's HTML from a template and injects it into the 'Data Viewers Manager" accordion panel.
    2. generates checkboxes into the "Data Viewers" panel. These are all created dynamically from the metadata of the dataViewers Registry, and the checkboxes generated vary based on the values of JSDE.isMobile and JSDE.isMobileLowRes
    3. generates the markup for the dataFilter option from a template and injects it into the "Data Filter" accordion panel. It then populates the listboxes of the "Data Filter" panel with a list of Continents and Regions
  4. the options pane initialization also determines which dataViewerManager should be initialized (this varies based on user preferences and execution environment). When it calls the dataViewerManager's init function it passes 1 parameter, the ID of the data display container; all dataViewers will be managed within this container. During its init the dataViewerManager fetches all active dataViewers (those selected for use) and puts them on the display (into the #dataDisplayContainer div)

Obviously the above summary is very high level and there's lots more going on, but that's the core HTML processing at app startup.

Here are some practically random HTML-related notes:

  • Most client-side HTML is injected via jQuery's html() function. The markup is usually created using a template (see this post's Views and Templates section for more detail). Example from js/views/dataViewers/​detailViewer.js:

    pass in to Underscore's template function:  1) the HTML template file to use and,  2) the data to be substituted into this HTML
    // tell Underscore to render the template...
    compiledTmpl = _.template(templateID,{country:$activeCountry.country});
    and then take the ready-to-use markup and put it into a <div> and thus onto the DOM
    // ...and then update part of the page:
    $('#' + targetDiv).html(compiledTmpl);
  • In some places I cheat and have to-be-injected HTML embedded in the JS instead of using a template. Mostly this is in the dataViewerManagers since their HTML is simple and I think this approach will make these dataViewerManagers easier to understand.
  • JSDE doesn't use HTML5 semantic elements like <header>, <nav>, etc. and so doesn't use an HTML5 shim.
  • The UI for this version of JSDE definitely isn't optimized for mobile: it doesn't follow mobile conventions, it uses jQuery UI instead of jQuery Mobile, etc. That's because originally I was just writing for desktop browsers and it was late in the game when I decided to add mobile into the mix. This goes against the "mobile first" design principle for multiscreen web apps — that is, design first for that small screen, get that right, and only later take into account the desktop (it's generally easier to scale things up than scale them down). As I say, I violated this rule, and if you look at the application on mobile you'll see the price — it looks odd for a mobile app, perhaps serviceable on a tablet but terrible on a phone.

CSS

JSDE CSS is loaded in two ways:

  1. core CSS is loaded normally, via <link> tags in the main html file (dataExplorer.html). This includes all of the CSS files for libs (e.g., jQuery UI) and individual component plugins (e.g., jqPlot). Only after they're loaded is JSDE's own CSS file loaded (css/jsde.css).
  2. at initialization the application setup program (js/appSetup.js) tries to determine if it's running on a mobile device. If true then it pulls some mobile-specific CSS async via yepNope:
     yepnope.injectCss(​"css/mobile.css")
    Note that appSetup.js executes before the DOM is ready because styles changes are expensive — if you're going to make major style changes you want to do them before the DOM renders, or at least before you have a lot already on the display tree. BTW, if you run JSDE on mobile you can probably see that moment when the mobile.css kicks in, watch for the "Loading�" message text to get larger as fontSize is increased by mobile.css

mobile.css mostly adapts sizing and layout for smaller screens and higher DPI. It starts by setting styles appropriate to both tablets and phones (e.g., increases the font size). After that several media queries override some of these styles for small screen devices (basically we're talking phones). To view the mobile.css file you can use a mobile debugger like the Chrome Android USB debugger or you can just call it up in your browser: www.dlgsoftware.com/​jsdemoapps/jsde/css/​mobile.css

Notes:

  • The application's primary CSS file (css/jsde.css) is almost exclusively focused on desktop clients and has no functional media queries (they're all in mobile.css)
  • if JSDE thinks it's on a mobile device it injects css/mobile.css. While its main purpose is to adapt the UI to smaller screens it also deals with some webkit quirks (e.g., -webkit-tap-highlight-color issues on Android) and applies some mobile-specific enhancements (e.g., applies -webkit-overflow-scrolling to some divs and iframes to get momentum scrolling on iOS devices)
  • iframes don't use the CSS of the application that contains them so the online Help iframes have their own CSS file (css/viewerHelp.css) and their .htm's (in help subdirectoy) have a <link> pointing to this Help-specific CSS file
  • for older browsers that don't support CSS3 I don't bother using any polyfills here. Instead sometimes I use fallbacks (e.g, use a RGB fallback for RGBA) and sometimes I just don't bother (e.g., no blue world map background image for older browsers).
  • CSS isn't one of my strengths, so I'm sure I do some things inefficiently here, not all best practices. In particular I use !important more than I should. However, !important IS needed in at least one place, for forcing use my non-jQuery UI images onto JQUI buttons()'s. Example:

    .tabularViewerIcon { background-image: url(icons/tabular.png) !important; }

    Now assigning this class via the jQuery UI button() icon property will result in my icon image appearing on the JQUI button. Note that my images aren't handled as efficiently as jQuery UI's icon images, which use a sprite sheet (I pull my icon images individually)
  • Wijmo's Rocket theme is used though I do override some of its styles in the JSDE CSS files
  • most DIVs and many components are written to size to their parent containers (e.g., height and width are generally set to 100%). This means they resize when their parent resizes, which simpifies handling browser resizes and mobile orientation changes
  • I use some CSS features you want to use sparingly on mobile: heavy use of alpha, a background image, some shadows, CSS gradients. etc.

JavaScript

Ok, for a JavaScript-driven SPA this section should be very long but I don't have a lot of time so here's what you get:

  • summary of init process from js perspective
  • overview of the two most important initialization modules
  • a summary of what happens after init
  • short list of coding techniques used

Init Process

Here's a summary of the init process from the JavaScript perspective:

  1. the application's main html file loads many js files via <script> blocks. This includes jQuery, jQuery UI, and Underscore accessed through CDNs. Last loaded are JSDE's .js files, more than a dozen <script> tags are used for this (not done efficiently, no concatenation or minification used for my own JSDE files — makes it easier for noobs to read/access).
  2. The first JSDE .js file executed is js/appSetup.js. This does some setup early in the init process, before the window load or jQuery ready events fire. It starts by defining the application namespace, creating a global object named JSDE (so, it's window.JSDE). In then inspects the current execution environment and sets some JSDE vars that reflect these checks (e.g., if appSetup thinks it's running on a mobile device it sets JSDE.isMobile to true). If it determines the application is running on a mobile device it injects mobile.css via yepNope. It also reads the stored state of previous executions so state can be restored. It also defines some critical utils like a pub/sub implementation (more on appSetup.js below).
  3. The next JavaScript file to execute is the main application controller, js/dataExplorer.js. It executes when $(document).ready() fires. This main js module starts by pulling js files that weren't needed for initialization, then initiates data load, then initializes other js modules which populate the navbar and options pane (more on jsDataExplorer.js below).
  4. options.js (called by js/dataExplorer.js) is where the active dataViewerManager is initialized. The dataViewerManager init call is passed 1 parameter, the ID of the container for data viewers (#dataDisplayContainer). This div is "owned" by the dataViewerManager — it manages all dataViewers within this container.
  5. the dataViewerManager init process requests a list of dataViewers from the dataViewer registry/factory/cache (js/dataViewersRegistry.js). The dataViewersRegistry initializes all dataViewers currently selected for use and returns their objIDs to the dataViewerManager. The dataViewerManager then puts the initialized dataViewers on the stage as per its functionality (e.g., tabsMgr puts each dataViewer in a tab pane, mdiWinMgr puts each dataViewer in an MDI window, etc.).

 
To see what's happening inside the application use the "write diagnostics" option. You can turn this on in global preferences. It writes information to the console on internal processes

Important init modules

At initialization two JavaScript modules are particularly important:

js/appSetup.js

js/appSetup.js is the first JSDE module to run. It is run immediately upon load, before the window load event fires. It sets up app-global vars and some utility functions as follows:

  • creates application namespace (JSDE). This is the only global var created by the application
  • detects device capabilities and creates some app-global variables (e.g., JSDE.isMobile)
  • conditionally loads mobile.css (if JSDE.isMobile is true)
  • reads state saved from previous sessions and stores values in some app-global variables
  • sets up some general utilities, including:
    • the template manager (async pull and caching of templates)
    • the routing (hashchange) manager
    • saveState manager (localStorage)
    • Ben Alman's Tiny Pub/Sub
    • popup messaging utility
    • application "Welcome" popup
  • �and possibly does other stuff I add after writing this

One very important task of appSetup.js is determining if the application is running on a mobile device. Based on its checks two variables are assigned Boolean values:

  • isMobile — default: false. Set to true if it seems we're on a mobile device. Determined by a combination of pixel density, presence of features like window.orientation, touch support, some UA sniffing for Apple devices, etc — for exact criteria see the code, which is squishy, certainly not bulletproof, and definitely not good enough for a production application. Some run-time mods are made based on this — for example, if isMobile=true then some heavyweight dataViewerManagers are omitted.
  • isMobileLowRes — default: false. Set to true if isMobile is true and the display size shows a small screen (<1024px max dim) or if a UA sniff finds "ipad" or "ipod". Some run-time mods are made based on this — for example, if isMobileLowres=true then system messages become terser/shorter (to deal with smaller screens). Also, some heavyweight dataViewers are omitted if isMobileLowres=true (see js/dataViewersRegistry.js).

 
The checks for mobile devices are crude and definitely not bulletproof. While they're sufficient for this demo application I certainly wouldn't use them for a prod application.

js/jsDataExplorer.js

js/jsDataExplorer.js is the first JSDE module executed after js/appSetup.js. Basically jsDataExplorer.js is the main application module, though it would be too much to call it the application controller (a lot of application work is done by listeners set in other modules, see After Init below).

jsDataExplorer.js starts by pulling JavaScript files that weren't needed for initialization, pulling some conditionally, including polyfills/shims — for example, TouchPunch and Fastclick are only pulled if Modernizr reports that touch is supported. After that it initiates the data load. Here's a summary of what it does and the order in which it does it:

  • does async pulls of some resources, some done conditionally based on Modernizr tests
  • bootstraps the data, calling the model to initiate server fetches which populate the model and its cache
  • initializes two of the three primary divs/views, the global navbar and the global optionsPane
  • defines some global event handlers for handling things like browser resize and displaying of global messages on the navbar
  • displays that startup "Welcome" screen

After init

Ok, the above described what happens at application init. After init everything is event-driven. Examples:

  • user adds or removes a dataViewer from the display: The user can add or remove dataViewers through their checkbox in the options pane. When the user changes the checked state of a dataViewer checkbox the checkbox change handler passes the new state to the dataViewersRegistry. This module updates the dataViewer's "active" state in the registry (sets it to true if the dataViewer is selected for use, false if not) and then fires a dataViewerActiveStateChange event. All dataViewerManagers listens for these events and respond by adding or removing the dataViewer from the display based on their active state.
  • user changes active country: when the user selects a country (e.g. selects a country from the navbar <select>) the model function JSDE.countriesModel.​set_$activeCountry is called with a single parameter, the name of the selected country. The model responds by updating the activeCountry object with the new active country's data and then it fires the activeCountryChange event. Views listening for this event update themselves to reflect the new active country.
  • user applies data filter criteria: Through the options pane the user can apply a global filter to the data. For example, they can filter by Continent (e.g., continent=Asia) to have dataViewers show only data meeting this criteria (in prev example showing only data for Asian countries). When a filter is applied (or cleared) optionsPane.js calls the model's public API, first updating the model's dataFilter object with the filter criteria and then applying this change to the data. If a data filter is being cleared then the unfiltered data can be served from cache, avoiding a server request. However, in this version if a data filter is being applied then the request is delegated to a server process, which itself delegates to MySQL (which applies the SQL query and returns the data subset). When this async process has completed successfully the model publishes a countriesDataUpdated event. Most dataViewers respond to this event, re-rendering themselves using the changed data, thus reflecting the newly applied (or cleared) data filter criteria.
  • user resizes browser or rotates mobile device: Most child containers of the dataDisplayContainer are written to size to their containers (their height and width are generally set to 100% in CSS) so they automatically resize when their parent resizes. However, jsDataExplorer.js sets a listener for window.resize events, with its event handler setting the height of the dataDisplayContainer so it fills available space. I might have avoided "manually" setting the height of #dataDisplayContainer through the use of flexbox but I'd heard bad things about flexbox consistency and support and so decided to steer clear of it in this demo.

    Some dataViewerManagers also set a listener for window.resize events. For example, the 2x2 matrix mgr (js/dataviewermanagers/​matrixMgr.js) listens for window.resize events, with the handler responsible for adjusting the locations of splitter bars in order to maintain the relative sizes of matrix cells whenever the browser is resized.

    Also, note that popups are written to center/resize on window.resize events (this also handles mobile orientation changes)

  • user resizes a dataViewer: when a dataViewer is resized it dispatches a resize event. Of course, normally resize events are only dispatched by the browser, not individual containers. However, SPAs do a lot of dynamic layout so it can be very useful to have individual containers that fire resize events. So, to provide these container resize events I'm using Ben Alman's resize plugin. It uses polling to watch the height and width properties of a container and fires a resize event whenever these properties change.

    In any case, the dataViewer resize event is handled by its dataViewerWrapper (jquery.jsde.​dataViewerWrapper.js). This is a custom jQuery UI component that wraps all dataViewers and provides them with some common functionality. Among other things, the dataViewer wrapper listens for dataViewer resize events and responds by adjusting the height of the dataViewer's content area to fill available space but not overlap the dataViewer topline (which displays viewer messages and options buttons). Once again this "manual resizing" through js could have been avoided through CSS flexbox. The dataViewer wrapper also deals with displaying dataViewer-specific message, displaying the viewer's Help file, and displaying its options buttons.

GeneralJS coding practices

  • This JSDE demo app creates just one reference in the global namespace, JSDE. This is used as an app-global namespace. This approach avoids polluting the global namespace with lots of refs and minimizes var name collisions.
  • To keep things self-contained and loosely-coupled I use the revealing module pattern [link] a lot. It mimics private vars through a closure, letting you expose only those properties and methods you want public.
  • some globals (e.g., window, JSDE) are passed into modules as parms. This makes these refs available to the module without it having to search up the scope chain to global. Done for efficiency/performance
  • for frequently accessed DOM elements I usually get their DOM reference via jQuery just once and then store this in a var and reuse this reference rather than re-querying the DOM for the same element — important for performance (DOM access is s-l-o-w)
  • I use the common naming convention for jQuery wrapped entities, i.e., I begin their names with "$" (e.g. $someDiv = $("#someDiv") )
  • I frequently use delegation for events. That is, put a listener on a parent of an element rather than putting it on the element itself, then catch the event as it bubbles. For example, its generally better to not put a listener on individual table rows but instead on the table or its container. This reduces the number of event listeners, improves performance, reduces memory use, etc. For an example see js/views/​dataViewers/tabularViewer.js (its table row click handler has some comments on this topic)

Application Architecture

For simplicity JSDE doesn't use a SPA MV* lib like Backbone or Knockout. Nor does it use a full-blown framework like Ember or Sencha. Rather, it does things "manually", implementing an MV* design that adheres to the basic principles including:

  • model code separated from view code
  • model has no dependency on non-model code
  • views and/or controllers have knowledge of the model and make calls on it
  • loose coupling via an event bus

Obviously those are general design goals that can be implemented in many different ways. JSDE uses some approaches common to SPAs, for example its use of the pub/sub pattern for communication between modules. However, other design decisions implemented here aren't necessarily commonly used in SPAs, they're just what I chose to do in this demo app. For example, I employ a dataViewersRegistry which serves as a combination factory/registry/cache for dataViewer instances (dataViewers are just a "class" of views that show country data, more on this below). Another example is the use of dataViewerManagers that let the user choose how dataViewers are displayed and managed (in tab layout, a 2x2 matrix, resizable windows, etc.). This is more of a programming exercise than something you'd do in a "real" application. However, for this demo app it's a great way to explore DOM manipulation.

Since dataViewers and related "classes" like dataViewerManagers are core to the application they're described in more detail below.  

Definitions of primary JSDE "classes"

dataViewer: specialized views that display countriesModel data in some format (table, graph, video, etc.) and which share certain common features (properties like a dataViewer icon, and UI attributes like a dataViewer message line). DataViewers show info for multiple countries (example: summary statistics dataViewer) or they show info only for the current active country (example: active country details dataViewer). DataViewers are created by and cached in the dataViewersRegistry.

dataViewersRegistry: module that serves as registry/factory/cache for dataViewers:

  • creates/maintains a dataViewers registry. This registry is an array of objects containing metadata for each dataViewer. Once a dataViewer has been created its object ID is cached in this registry.
  • acts as a factory and cache manager, creating dataViewers when they are first requested and serving subsequent requests for that dataViewer from a cached dataViewer reference (so dataViewers are basically Singletons)

dataViewerWrapper: all dataViewers are wrapped in a dataViewerWrapper, which is a jQuery UI widget. All dataViewers delegate some UI handling to their wrapper including displaying dataViewer-specific messages, displaying options buttons, displaying the dataViewer's Help files. Note that this design approach is just something I was playing with, providing some common functionality without using inheritance — in Flex I've handled this by creating an abstractDataViewer class and then made concrete dataViewer subclasses of this, but with JavaScript I'm exploring how to do things without inheritance, focusing more on composition. So, head's up, this might be a really dumb implementation for this, but as I say I'm just exploring here�

dataViewerManager: manages how dataViewers are displayed — e.g., as a tab pane of a tabbed layout, or in a moveable/resizable window, or in the cell of a 2x2 matrix, etc. Only 1 dataViewerManager is active at a time. User can select a dataViewerManager from the options pane. Availability varies between desktop/tablet/phone (e.g., for phone only the tabsMgr is available). See here for more info on dataViewerManagers and a summary of the 4 dataViewerManagers layout features.

In the previous paragraphs I used the word classes in quotation marks. That's because JSDE mostly uses functional programming, not OOP. I reckon this is better for SPA noobs, since they may not understand prototypal inheritance. Beyond that, JS and functional programming are a good fit because of JavaScript's dynamic nature and how it handles functions (for more on this see bullet below).

Below are some summary points on the architecture. This is just the 25-cent tour, though note that many of these are described in more detail elsewhere in this post.

  • while JSDE is largely MVC it's all done "manually" — that is, JSDE doesn't make use an MV* lib like Backbone or a framework like Ember. This keeps things "close to the metal" to better illustrate SPA fundamentals.
  • you won't find a lot of reliance on OOP here, it's mostly functional programming, which works well in JS because of JavaScript's dynamic nature, functions as first-class citizens, decoration and composition through use of mixins, etc. (this also helps avoid some of the rigidity of single inheritance and complexity of multiple inheritance — remember, in general favor composition over inheritance).
  • the code is modular, a few dozen .js files organized in package dirs. Heavy use of revealing module pattern with module vars and functions private through closure unless they're explicitly made public. Most modules have an init() function (not a constructor)
  • the data model is contained in a single module (js/models/countries.js). This model handles all server requests for the country data, holds cached data, fires dataChange events, etc.
  • data model is totally decoupled from the rest of the system — it has no dependencies on other modules (communicates only by firing/publishing events and sets no listeners on non-model events)
  • view controllers generate views through one or more client-side templates. For example, the controller for the Active Country Details view (js/views/dataviewers/​detailViewer.js) uses the template _detail.tmpl.htm. Some view controllers use multiple templates. For example, statsViewer.js uses _statsHead.tmpl.htm and _statsBody.tmpl.htm.
  • JSDE defines a special category of views I call dataViewers and also the related modules dataViewersRegistry, dataViewerManager, and dataViewerWrapper — for more info on these see the Definitions table above
  • loose coupling is achieved mostly through use of events, primarily via the pub/sub pattern. Pub/sub is how dataViewers learn of changes to the countriesModel data — whenever data is modified the model publishes change events.
  • an exception to the pub/sub use is the handling of activeCountry changes. These change events are fired through a non-DOM jQuery-wrapped object (events fired through jQuery's trigger function). Am doing this just to demonstrate how you can dispatch and consume your own custom events w/o using an event bus

 
To see what's happening inside the application use the "write diagnostics" option. You can turn this on in global preferences. It writes information to the console on internal processes

Models, Views, Controllers

Model

JSDE keeps its data handling segregated in a model, though it's pretty crude — all in one .js file, js/models/countries.js. Here are some highlights:

  • the model module handles all communication with the server for country data; views and controllers never request country data directly from server, they always makes requests through the model. Data is pulled and processed in JSON format.
  • data is stored locally in objects and arrays and collections (arrays of objects). At the heart of the application is the collection containing data on all countries (countriesData). The countriesData collection contains an object for each country, with each object containing detail data for a country (name, continent, GDP, life expectancy, form of government, etc.).
  • in addition to the detail data stored in the countriesData collection the model also holds summary data (e.g., continentStats, which hold summary statistics data, one object for each continent)
  • the countriesData collection and related data collections (e.g., continentStats) honor any active dataFilter applied by the user. For example, if the user applies the filter Continent="Asia" then each collection will be reduced to only objects whose continent property has a value of "Asia".
  • model broadcasts change events via pub/sub when any collection is modified (in this version this only happens when a data filter is applied or cleared). Example change events: countriesDataUpdated, continentStatsUpdated.
  • model caches the unfiltered countries data at startup and uses this whenever a filter is cleared, reducing server data requests and providing better performance
  • model maintains data for the current "active country" (i.e., the country selected by the user) in a non-DOM jQuery-wrapped object ($activeCountry). This stores a copy of the active country's object in $activeCountry.country. So, if active country was Peru this would be true: $activeCountry.country.​name==="Peru". When no country is active then $activeCountry has no children ($activeCountry.country​===null). In addition, a property named $activeCountry.index is used to store the index position of the active country's data within the countriesData collection. This is simply an optimization that allows the tabular dataViewer to more quickly access its active country row.
  • model fires activeCountryChange events whenever the active country changes. These change events are fired through the $activeCountry object (just to show an alternative to pub/sub). The model exposes a public method (JSDE.countriesModel.​get_$activeCountry) that allows other modules to obtain the $activeCountry object ID since a reference to this object is required by all views that want to listen for these events (needed to set the listener).

Views and Controllers

I'll start this with a head's up — my storage of views and controllers may be a bit odd. Since all (well,almost all) of the HTML for views is within my Underscore templates I store these in a subdir of root named templates. The javaScript modules that use these templates to generate views are stored in the js/views directory (not js/controllers) and its subdirs. Small thing, but I wanted to mention this so you don't think it's standard practice — it's just what makes sense for me, probably reflects the my own fuzziness on views and controllers in JavaScript applications.

Ok, here are the key points on views and controllers:

  • each JSDE view is driven by a single javaScript module that uses one or more templates. For example, the Country Details dataViewer is generated and driven by the controller views/dataViewers/​detailDataViewer.js which uses the template templates/_detail.tmpl.htm.
  • some controllers use more than one template. For example, statsViewer.js uses _statsHead.tmpl.htm and _statsBody.tmpl.htm.
  • while most views are generated from templates, I do cheat in a few places and have some HTML embedded in the JavaScript (mostly in the dataViewerManagers).
  • JSDE has a specialized type of view (functionally a class) that I call dataViewers. These display country data in a variety of formats (graphs, tables, maps, etc.). For more info see the Definitions table above.
  • DataViewers are controlled/managed on screen by dataViewerManagers. For example, one dataViewerManager displays dataViewers in a tab layout, another in resizable/moveable windows, another in a resizable 2x2 matrix cells, etc. For more info see the Definitions table [link] above or the DataViewerManagers section below.
  • When a dataViewerManager needs a dataViewer it requests it from the dataViewersRegistry. This registry creates the dataViewer if it doesn't already exist or serves it up from its dataViewer cache if it does. Each dataViewer is created only as it's requested (user selects it for use by activating its checkbox). Once created a dataViewer is never destroyed — rather, if the user deselects it (removes if from display) it's simply cached (its jQuery-wrapped objID is stored in the dataViewer registry).
  • some views are really just <iframe>'s whose URL is changed by a controller whenever the active country changes (e.g., js/views/​dataviewers/youtube.js). This is a cheap and easy way to mashup functionality from external sites like Wikipedia and Youtube. Of course I could do a lot more iwith Youtube if I used it's js API, but I don't really care about dataViewers — as noted above, this demo app is focuses on showing how SPAs work, not on the dataViewers themselves, so they're mostly dead simple and sometimes kludges — for example, the Weblinks dataViewer has a link to the CIA World Factbook thats wired up by country code, except my country codes don't always match up with what the CIA site expects, so about 20% of the time you get a 404 Not Found. And that's fine, I'd definitely address this in a reall application but in this demo app it's good enough to demonstrate the point

    For a bit more info on issues with iframes in mobile browsers see the online Help for the Wikipedia dataViewer.

 
for info on individual dataViewers see their Help files. For example, the Wikipedia Help describes how it is displayed via <iframe> and some of the problems this causes on mobile.

dataViewerManagers  

Just as dataViewers are specialized views, dataViewerManagers are specialized view managers for dataViewers. Their job is to manage all dataViewers on the display in various layouts as described below.

matrix​Mgr This manages dataViewers within cells of a 2x2 matrix (this version fixed @ 2x2 for simplicity). Matrix cells are resizable through use of Wijmo's splitter component, with 2 horizontal splitters nested within a vertical splitter. When a new dataViewer is selected for use this manager 1) removes any DV currently in the target matrix cell, 2) tells the dataViewersRegistry to change this dataViewer's state (sets active=false) 3) obtains the object ID for the new dataViewer from the dataViewersRegistry and 4) adds the new dataViewer in the target cell. Note that this Wijmo splitter is the only Wijmo component used in JSDE V1. This manager not available when JSDE.isMobile=true.
tabs​Mgr This manager uses a jQuery UI tab component. putting each dataViewer into a tab pane. The icon on the tabs is added via the tabTemplate option of the JQUI tabs component.
mdiWin​Mgr This is a "lite" MDI window manager with minimal window mgmt (e.g., you can minimize and restore but not maximize). I created this by modifying some Tutsplus code which tweaks JQUI's dialog component. This manager not available when JSDE.isMobile=true
dualPane​Mgr Similar to matrixMgr, this uses Wijmo's splitter component. In this case a single splitter is used, dividing dataDisplayContainer into 2 resizable panes. This manager is the default for tablets. QUIRK NOTE: the Wijmo splitter wasn't written for mobile and it's harder to move that splitter bar using touch. However, the manager options includes 3 preset splits (30%, 50%, 70%) and you can zoom a pane by touching the icon button in its upper left corner. This manager not available when JSDE.​isMobileLowRes=​true.

Types of JSDE dataViewerManagers

Except for small-screen devices (basically phones) where the application is locked into the tabsMgr, the user can select a dataViewerManager from the options pane (tall skinny pane at left) via a <select>. The optionsPane's JavaScript (see js/views/optionsPane.js) sets a change event handler on this <select>, listening for the user to select a new manager. When a new manager is selected the <select>'s change handler executes destroy() on the current manager and then init() on the newly selected manager.

The init process for the dataViewerManagers is basically the same for all. They:

  1. begin by using jQuery empty() to clear out the data display area (whose div ID is passed in to init() as a parameter). Then they inject their own markup into the data display area . For example, tabsMgr creates and injects a single <div> that will be converted into a jQuery UI Tab component, while matrixMgr creates nested DIVs to be converted into nested Wijmo splitters. Example from tabsMgr (dataDisplayContainer holds div ID string) :

    var $dataDisplayContainer = $("#" + dataDisplayContainer) ;
    $dataDisplayContainer.empty() ;
    $('<div id="tabs" class="tabsOuterPane"> <ul id="tabsUL" ></ul></div>')
        .appendTo($dataDisplayContainer) ;

  2. manager converts the markup into jQuery UI components as needed (e.g., tabsMgr converts div to tab panes). Code below is from tabsMgr converting its markup into a jQuery UI tab component:

    $( "#tabs" ).tabs() ;

  3. gets the list of active dataViewers and puts them on the display. All dataViewerManagers manage the display differently but they all do the same general init processing (i.e., query the dataViewerRegistry for active dataViewers and put active viewers on the display) so to avoid code duplication they delegate common init tasks to a helper function (js/dataviewermanagers/ viewMgrUtils.js). They pass this function their add() function (see code below) and the helper executes this for each active dataViewer (this passing of a function is good example of how JS functions are first-class citizens, easy to pass a function into a function). The helper function iterates over the list of dataViewers, executing the add() function it was passed once for each dataViewer that is "active" (i.e., selected for display). Example call:

    JSDE.viewMgrUtils.initViewers(add)

  4. As viewMgrUtils executes the add() function for each dataViewer they are put onto the display
  5. One last thing: each dataViewerManager has options, and these are displayed in the options accordion pane titled "Data Viewers Manager". At init each dataViewerManager generates and then injects the markup for its manager-specific options into a container in this "Data Viewers Manager" accordion pane (​#viewerMgrOptionsContainer​)

As noted above, all dataViewerManager's have a destroy function. This clears event listeners and nulls references to ensure that manager elements can be garbage collected.

All dataViewer managers are under js/dataviewermanagers.

Events, dependencies, loose coupling

In general JSDE uses the Publish/Subscribe pattern to fire/publish custom events. Almost all JSDE custom events are handled through $.subscribe and $.publish. For a simple pub/sub implementation JSDE uses Ben Alman's jquery-tiny-pub/sub https://github.com/​cowboy/jquery-tiny-pub/sub.

In addition to demonstrating pub/sub usage I also wanted to demo "manually" firing events through a plain old object, so activeCountrychange events are not fired through pub/sub, they are fired through a jQuery-wrapped object that is created and maintained by the model ($activeCountry).

Below is info on these two approaches to event handling.


Approach #1: custom events via pub/sub

The Publish/Subscribe pattern (pub/sub) keeps modules loosely coupled through an event bus. Modules can fire events through the pub/sub event manager, and modules can register handlers for events fired through the pub/sub event manager. This lets two modules communicate without needing a direct reference to each other, which would create a dependency, or tight coupling. Instead, using pub/sub they both only need a reference to the pub/sub manager.

Almost all JSDE events are fired through $.subscribe and $.publish. Examples:

set listener: $.subscribe( "countriesDataUpdated", render );

broadcast event: $.publish( "countriesDataUpdated" );

The model communicates to the rest of the application only through pub/sub, and only by publishing, never subscribing — the model is totally decoupled from the rest of the application.

As noted above, JSDE uses Ben Alman's simple and lightweight pub/sub jQuery plugin. The Alman pub/sub code is so compact (about 10 lines of code) it's included inline in the setup module js/appSetup.js.

For more info on pub/sub see my SPA primer part 2 section on Events and loose coupling.


Approach 2: custom events through a non-DOM object

I wanted to play with using a javascript object for managing activeCountry data and as a source for its change events so in the model I create an object named $activeCountry. When a country is active this object will have a child object with all of the active country's data.

Whenever activeCountry changes an event is fired through this object — do-able because I have wrapped this object in jQuery, which allows firing events through jQuery trigger().

Because all dataViewers must listen for activeCountryChange events their init() functions all call JSDE.countriesModel.get_$activeCountry to get a reference to the $activeCountry object. Once they have that objID they can set a listener on it for change events. So, in most dataViewer init()'s you'll find something like this:

$activeCountry = JSDE.​countriesModel.​get_$activeCountry() ;
// inconsistency here, NOT using pub/sub for active country changes
$activeCountry.on( "activeCountryChange", selectActiveCountry );

While this approach works there's a dependency cost: modules can listen for activeCountryChange events only if they have a reference to the dispatching object (you need this to set your listener). This results in a tighter coupling between modules than the pub/sub approach. In a real application I would have used pub/sub for active country change events.

General Event handling notes:

  • for "regular" UI events like click I use delegation where it makes sense. For example, in tabular dataViewer I don't set click listeners on the <tr>'s but on the containing <div> and get the event as it bubbles. If you aren't familiar with this then you should look at the tabularViewer code, it has some comments explaining the why and how of this.
  • If you're searching for code that sets event listeners don't search for jQuery bind calls (or live, or delegate) because JSDE uses on() and off() pretty much everywhere

Code Organization

The table below describes the organization of the code. Note that you can also see this structure in Chrome devtools, use the Sources or Resources tabs (do-able because I haven't concatenated these files, cost of this is init performance).

Subdir/pkg Name Description          
css CSS and its resources live here, including 3rd party CSS
help all the online help files here in html format
templates templates from which views are generated
../lib the PHP resides is in a sibling directory to the application's root dir
js core modules, like bootstrap pgm, dataViewer registry, etc.
js/models the Model of MVC; data handling code for the countries data
js/views js that serves as view controllers
js/views/​dataviewers controllers for special class of views for displaying country data
js/​dataviewermanagers dataViewer managers that display dataViewers in different layouts
js/libs 3rd party libs live under here, e.g., jqPlot, Modernizr, etc. Normally you might put jQuery and Underscore here but am serving these up from CDN's

Developer tools and utilities

Here are the tools/libs used by JSDE:

Notes:

  • Touch Punch translates some touch events into mouse events for jQuery UI. For example, it allows drag operations on dialog windows or drag of a slider. While not strictly needed for JSDE functionality I wanted to explore it a bit. To try this out you can drag smaller popups (note that not all popups have draggable enabled). Loaded only if Modernizr reports that touch is supported.
  • Since most web pages are clueless about touch events, listening for clicks instead, mobile browsers translate touch events to click events. Unfortunately they also introduce a 300ms delay in the dispatch of that click event. Fastclick eliminates this delay in on most mobile. Makes big difference in responsiveness of web apps. Does have some side effects — responsiveness of <select>'s seems degraded, and seems it does not always play well with jQuery UI (which shouldn't really be a factor, since JQUI really shouldn't be used on mobile, too heavyweight). For example, in Android 2.3 stock browser the JQUI buttons of the Weblinks dataViewer work fine — unless Fastclick is loaded, then they mostly don't work at all. Fastclick is loaded only if Modernizr reports that touch is supported.
  • Ben Alman is a JavaScript machine, putting out many great utils. His resize plugin uses polling to watch height and width of elements and dispatch a resize event when they change. Default polling interval is 250ms. Polling like this isn't a great thing to do, but given the limitations of HTML/CSS/JS sometimes you have to use duct tape and chicken wire to get what you want. Being able to catch container resizes is very useful for dynamic layout (and something greatly appreciated by ex-Flex developers, where container resize events were built-in and immediate).
  • I use yepnope for async pull of server files. For some info on that see the Resource loaders section below
  • for a bit more info on the UI libs/components see the UI Components section

Client-side data

JSDE is dead simple when it comes to data — we're not talking about large datasets here and the backend data is static and the user isn't editing data on the client (so no syncs back to server).

On the backend I use PHP and MySQL. A traditional use of these two might be to generate dynamic content, for a CMS for example. But JSDE just uses the backend to serve files and data requests (including generating simple statistics). Of course, this backend processing could have been done by Node or Java or whatever (as noted in my SPA Primers, SPAs generally can be agnostic re backend technologies). I've used PHP here simply because I already had the PHP code and SQL queries written for a Flex demo app I'd done previously (sweet: I was able to reuse the PHP with a few simple mods, basically just changing the PHP to return the data as JSON instead of binary AMFPHP format).

The summary stats datasets (continentStats and RegionStats) are generated through SQL queries. They're just the simple stuff you can get from SQL like Min, Mean, Sum. Of course, you could easily have far more complex analytics on the backend and serve up the smaller, summarized result sets. For more on this topic see below.

SPAs shouldn't try to do everything on the client. Especially if you're doing analytics on big data then you'll want to leverage that server (not a great idea to be running things like logistic regressions on a phone). In these cases it makes sense to use server compute services — SAS comes to mind here, with its ability to perform a wide range of analytics and then serve up the results in a variety of formats. In other words, let the server do the heavy lifting and send back the result set (usually a manageable size) and then on the client this data can be sliced and diced and graphically explored etc. This plays to the strengths of SPAs which can provide the interactivity and performance you want when doing ad hoc exploration of data.

To see the actual data you can look at its JSON as it's pulled — use the Network tab of your browser's developer tools (in Chrome devtools you can filter down to the Ajax calls by clicking that XHR button at the screen bottom, then look for .php calls, select one and then use the Preview tab). Alternatively, you can access the model's data directly from the console. Note that you can't directly access the data objects themselves — they're private behind a closure, protected from direct access. Instead you have to use the model's public API, executing a function that returns the data. See model/countries.js for all of the functions you can use to access countries data, but the most basic is JSDE.countriesModel.get_countriesData. Execute this REPL and you'll be able to inspect the structure and values. BTW, I do realize that surfacing the ID of the original data object means it's not really private, it can be modified, I just don't care about that in this demo, just want to keep things simple.

Since there are a lot of comments in the model code (js/models/countries.js) I'll include here just some high points:

  • all data requests for countries data are made through the model; other modules do not directly request this data from the server, they always go through the model
  • the model publishes several different data change events, with views subscribing to the events they're interested in. For example, the Stats dataViewer subscribes to the continentStatsUpdated and regionStatsUpdated events.  
  • I've noted this before, but let me stress again that this countries data is old and some of it got mangled in translation — to get it into MySQL I had to do conversions and joins from different sources (though mostly it's old CIA World Factbook data) and this was all a couple of years ago, so don't ask me to remember the details, it's just demo data. Where I have missing numeric data values (some of the smaller countries) I just set that missing value to 0 (see countries Pitcairn, Nieu, Holy See, etc.). Obviously this substitution can skew the summary stats and sometimes it yields oddities like a LifeExpectancy of 0 (wouldn't want to live in that country�). As I've said elsewhere, don't let your kids use this stuff for their homework assignments�
  • model maintains the "active country" data in a non-DOM object ($activeCountry). This holds a copy of the active county's object in $activeCountry.country. So, if the active country was Peru this would be true: $activeCountry.country.​name==="Peru" . When no country is active then $activeCountry has no children (i.e., $activeCountry.country​===null). The index position in the data is also stored here as $activeCountry.index. This is an optimization that allows faster access to the active country row in tabular dataViewer.
  • Data Filter criteria are applied by the model. This version delegates applying these criteria to the backend, tho this simple filtering could have been done on the client. When filters are cleared no server requests is required — the model just uses the unfiltered data stored in its cache.
  • cities data is handled a bit differently than other countries-related data. The cities dataset is large so it isn't pulled to the client. Instead a subset of cities for the current active country is pulled each time the active country changes. I should cache already-pulled cities to reduce server requests but just haven't bothered with that yet (but for a production application you'd definitely want to add this)
  • while server requests for country data are all made through the model other server requests (e.g., async pull of templates) are not done through the model, they're not the model's responsibility — the model is only responsible for the fetch/store/cache/manage of its client-side data (in this case, the countries data)

UI Components

Most of the UI is implemented using jQuery UI components, though I do pull in a few specialized libs like jqPlot (graphs) and balloon (information popups). I also use a <select> on mobile that's replaced with a jQuery UI autocomplete when running in desktop browsers (look for id's countrySelection_Select and countrySelection_Input).

Here are the component libs or individual components used:

  • jQuery UI — jQuery UI is jQuery's sibling, an OOP component lib that gives you a dozen or so components like icon buttons, accordions, dialog windows, and more. JSDE uses button, buttonset, dialog, accordion, and autocomplete (but autocomplete is used only for desktop browsers — see bullet below). Note that I use these on mobile, as well, which isn't a great idea — see bullets below.
  • jqPlot — very nice graphing component done as a jquery plugin, well documented, quality work, highly recommended
  • Wijmo's splitter — divides an outer container into two containers seperated by a draggable splitter you can use to dynamically resize the two child containers
  • balloon.js — displays popup balloons that are displayed when JSDE's (i) buttons are clicked. A jquery plugin, minified is just 6k
  • formatCurrency.js — this lib isn't a component but is totally tied to the UI. It converts a raw value into a formatted string that's displayed to the user — e.g., can take 12345 and makes it 12,345, or $123.56, or 123.45%, etc.

As for jQuery UI, I have two examples of creating custom widgets using its widget factory:

  • extending an existing component using $.widget. The mdiWinMgr displays dataViewers in moveable/resizable/​minimizeable windows. To create these window containers I extended the JQUI dialog() widget. For info on this see this code (js/jquery.jsde.​MDIWinDialog.js)
  • creating a new component using $.widget. The dataViewerWrapper component (js/jquery.jsde.​dataViewerWrapper.js) is a jQuery UI widget I create from scratch using $.widget(). All dataViewers are wrapped in this component and delegate to it the display of dataViewer-specific messages, display of options buttons, display of dataViewer Help, and more. For details see the comments in the dataViewerWrapper code

 

This version of JSDE wasn't really focused on mobile so it uses jQuery UI components everywhere. But using JQUI components on mobile isn't a great fit and can cause performance, layout, and other problems. For more info on this see the "JSDE on mobile" section.

General notes:

  • there is a component on the navbar that lets you select a country (that is, its selection sets JSDE's "active country"). This component is by default a <select> (see jsDataExplorer.html) but that is replaced by a jQuery UI autocomplete component when running in desktop browsers. That's because on desktop machines autocomplete has some nice functionality like type-ahead, but on mobile it's just too heavyweight. Not only is a <select> more lightweight it's also more responsive on mobile, with smoother scrolling, generally a more native feel.
  • the small icon buttons like those used on the global navbar are jQuery UI buttons. While JQUI does include quite a few icons and does them efficiently with a sprite sheet unfortunately they're somewhat limited in choice and rather bland. So, on most buttons I force my own icons onto the button using CSS !important (see css/jsde.css, an example is .hideOptionsIcon class)
  • re jQuery UI and mobile, I'm using TouchPunch to translate touch gestures to mouse events. This is required for JQUI components that consume mouse events like drags since JQUI knows nothing about touch/gesture events. See Developer Utils section for more on Touch Punch
  • To eliminate the 300ms delay built into most mobile browsers for dispatch of click events I'm using FT's Fastclick. Head's up: I've found that Fastclick and jQuery UI do not always play well together (really shouldn't be an issue, since use of JQUI on mobile should be minimal). See Developer Utils section for more on Fastclick
  • the Wijmo splitter component is used for two of the dataViewerManagers, the 2x2 Matrix and the DualPane managers. While it works on mobile it can be very hard to grab that splitter and drag it with your finger (even with use of Touch Punch). To work aroumd this the dualPane dataViewerMgr (used on tablets) has some splitter preset radio buttons.
  • Similar to splitter, jqPlot isn't written for touch events, and on mobile doing drilldown by touching a bar is finicky — you may have to touch a bar several times to get drilldown, and for very thin bars it may not work at all

Views and templates

JSDE generates all views client-side, creating DOM elements from markup and adding them to the DOM as a child of a DIV. Most markup is generated through templates (for info on templates see primer part 2 section Views, Templates, and Data Binding). An exception to the use of templates are the dataViewerManagers, which have their own simple markup embedded in their JavaScript.

Here's sample code from detailViewer. It takes a template (detailTmpl) and compiles it with a data object (in this case info on the "active country"). The result is valid markup with info on the current active country. Using the jQuery html() function DOM elements are created from this markup and put on the DOM as children of a <div>:

// That 1st parm value is the name of a template file in /templates dir
// Mgr serves template from cache if avail else will fetch from server
JSDE.templateManager.get(detailTmpl, function(rawTemplate) {   
  // tell Underscore to render the template...
  var compiledTemplate = 
      _.template(rawTemplate,{country:$activeCountry.country});  
  // ...and then update part of the page with the new content:
  $('#' + targetDiv).html(compiledTemplate);   
}) ; 

JSDE uses about two dozen templates. Templates can define an entire view or a part of a view. Each template has markup (and occasionally some logic) using the syntax of Underscore's simple microtemplating feature. All template files reside in a subdirectory /templates. They use a naming convention as follows: "_" + template-name + ".tmpl.htm". For example, the template for the options pane is named _optionsPane.tmpl.htm

Templates are lazy loaded, pulled only as they're needed. The pulling of templates is done asynchronously via a template manager (JSDE.templateManager) which is embedded in js/appSetup.js. Look for calls to the templateManager in almost all .js files residing under js/views. BTW, be aware that this pull-on-demand approach has a cost — you see a lag when a template is first accessed. I originally took this approach based on a Derrick Bailey post, though he's subsequently revised his opinion on this — see this post for details.

Notes:

  • To view JSDE templates you can:
    • you can see individual templates in your browser's devtool, they're pulled as seperate files so each will show up as a seperate entry in the tool's Network tab — look for files with a suffix "tmpl.htm"
    • to see the contents of the raw templates as they're pulled from the server use Global Preferences to turn on "Dump templates to console" option. This will dump the raw, unprocessed template into the console window as they're pulled from the server (but remember this pull happens only once, when they're first referenced)
    • once a template has been pulled you can see them in the template cache. Look for JSDE.templateManager.​templates._[ template-name ] (e.g., in your console's command line enter: JSDE.templateManager.​templates. _tabular)

Routing, navigation, and state management

JSDE does very simple routing, just using hashchange events and the URL's hashpart to set the current "active country". Moreoever, its hashpart values are dead simple, always just a single value that maps to a country name. And the hashchange event handler simply uses this value as a parameter in a single function call. This isn't "real world" — usually SPAs aren't mapping to a single action, more likely you'll have multiple hashparts to process and the handler will call different functions based on these hashpart values and optionally it will pass on one or more parms to the function it calls. For more on this see my SPA Primer Part 2.


 
JSDE uses hashchange to keep things simple, but you might do better with the HTML5 history API and pushState.

JSDE's hashChange handling allows navigation using the browser's Back/Forward buttons and allows bookmarking a "page" (i.e., state). And because of its hashchange processing the link below will bring up the application with Bulgaria as the active country: www.dlgsoftware.com/​jsdemoapps/ jsde/​jsDataExplorer.html#Bulgaria

Be aware that that if you refresh a page with the link above it will always return to Bulgaria. This is because at application load any hashpart value on the URL "wins" over other values (that is, it has a higher priority than any localStorage value from the previous session)). Honoring this hashpart value on the URL at startup ensures that bookmarking works as expected. If you want to refresh the page to see a true default state then make sure you have no country value in the hashpart. In other words: www.dlgsoftware.com/​jsdemoapps/jsde/​jsDataExplorer.html

All modern browsers support hashchange events. To deal with older browsers I use Ben Alman's hashchange plugin. The check for hashChange support is done in js/dataExplorer.js, which pulls the polyfill async if Modernizr reports that hashChange is not supported.  

Localstorage

JSDE uses localstorage for session persistence. That is, it saves data values that represent state (e.g, the name of the "active country") into localStorage and then later, at app init, it can use this data to recreate aspects of the previous session (e.g., it can start up with the same "active country" as the previous session). The use of localStorage is simple but if you want session data available to multiple devices you'll want to store this application state data on a server so that users accessing your application from different devices will always have their session data available (localStorage is stored locally so is machine-specific and, beyond that, browser-specific)

The main reason I implemented session save/restore was for mobile. This feature is important to mobile because some mobile browsers (e.g., Chrome) will swap out your page if the user leaves it and then restore the page if they later return. When that page reloads users expect to return where they left off, not be returned to an application default state.

While JSDE does save some state (lastActiveCountry, lastActiveViewManager, most global prefs) it really is minimal — it doesn't yet store/restore other important aspects of state like the last active dataViewers or last active dataFilter. For mobile devices save/restore of state is enabled and can't be disabled; for desktop it is disabled by default and but can be enabled (use Global Preferences option, the middle button on the navbar, driven by js/views/globalPrefs.js)


 
JSDE does implement some restore of state but it's minimal, just enough to demo the basics. To see what it saves you can just use Chrome devtools, select the Resources tab, then select the node for dlgsoftware.com under "Local Storage"

All modern browsers support localstorage. I use no polyfill for older browsers that don't support localStorage, so save/restore of state simply doesn't work on those browsers. However, for this particular feature I really only cared about mobile (to handle the page reloads triggered by some mobile browsers like Chrome — see above) and all mobile supports localStorage.

Notes:

  • writes to localStorage are encapsulated in JSDE.stateMgr which is defined in js/appSetup.js. This centralization makes debugging easier and simplifies future changes.
  • most Global Preferences settings are stored/restored across sessions. However, the "Disable text selection" option setting is always set to true at init, disabling the copying of screen text
  • because JSDE uses hashchange its "pages" aren't accessible to SEO. If you care about search indexing then you'll want to look at HTML5 pushstate. See my js web apps primer part 2 for more on this issue.


Resource loaders and dependency management

JSDE does resource loading async but it doesn't do dependency management, I just make sure I don't call anything before it's loaded. While this works for simple applications, as your application becomes larger and more complex you may find you need something like RequireJS — see my SPA Primer part 2 for more on this.

Notes:

  • In Chrome yepnope will generate a warning: "Resource interpreted as Image but transferred with MIME type application/javascript". While annoying this isn't really a problem. Here's a snip from yenope issues page (see yepnope issues/32):
    "This is intended because we use images to preload resources certain versions of webkit in yepnope. There's nothing we can do about the warnings. They're not breaking your page."
  • You'll see references to yepnope but you won't see me pulling that lib. That's because yepnope is part of Modernizr, aliased as Modernir.load. Since it's in my custom Modernizr build I could reference it as Modernizr.load but I use the original lib name of yepnope in all of my calls.
  • related to above — because my yepnope is packaged into Modernizr all references to yepnope code in the console don't show up as coming from yepnope.js but instead as from modernizr.custom.42094.js (my custom Modernizr build which contains the Moernizr.load/yepnope code)
  • when yepnope pulls a .js file it generates a <script> tag for the file and injects it into the <body>. I have no idea why it does this and haven't had time to look into this, and I mention this only because I feel uncomfortable with the fact that my HTML already has about a gazillion <script> tags...


Testing

JSDE is just a demo app, not a production app, so I didn't create unit tests. So much for that.

As for general testing, it's been tested on current versions of the major desktop browsers (Chrome, Safari, Firefox, and IE) as well as IE9 and some testing on IE8 early on (but not recently, so probably something is broken on IE8 now, god knows it's easy enough to break things in older IE's). I did not test on IE7 and as for IE6 it can burn in hell where it belongs.

For mobile I tested on several Android 2.3 phones, Android 4.1 Xoom, iPad 3 and 4, several iPhone models and iPod Touch (luckily there are 4 different Apple stores here in Manahattan, otherwise the store staff my have wondered why the same guy kept showing up to test drive their different iPhone models). For mobile I mostly just tested the default browsers (e.g., Android stock browser and iOS Safari, though I did test Chrome on some of them, and on Android I tested Firefox (notable mostly for its slow handling of device rotation).

Obviously the above list has a lot of gaps, so it's very possible this demo will break on many other device/OS/browser combinations. Sorry about that, but this is just a demo app and I simply don't have time to make this bulletproof everywhere, that's just too time-consuming (and one of the major drawbacks to web applications — see my comments on this below in the Conclusion section).

BTW, there are some known issues I'm just letting slide. Most of these are on mobile so for a list see the section just below, "JSDE on Mobile".


Deployment

This application is definitely not a model of efficient deployment — as I've stated numerous times, my highest priority is to make the code readable/acessible for noobs. That means I don't use minification or concatenation for JSDE code. This has a big cost at application load. See some of the references in my SPA primer part 2 for deployment best practices.



JSDE on Mobile

This version of JSDE definitely isn't optimized for mobile: it doesn't follow mobile conventions, it uses jQuery UI instead of jQuery Mobile, it uses too much memory for mobile, etc. That's because originally I was just writing for desktop browsers and it was late in the game when I decided to add mobile. This goes against the "mobile first" design principle for multiscreen web apps — that is, design first for that small screen, get that right, and only later take into account the desktop (it's generally easier to scale things up than scale them down). As I say, I violated this rule, and if you look at the application on mobile you'll see the price — it looks odd for a mobile app, perhaps serviceable on a tablet but terrible on a phone.

Another common wisdom is that you should test for capabilities and not try to sniff User Agent and determine the device. This rule I don't agree with. It seems to me that SPAs should do a bit of both, at least when you're targeting mobile devices. After all, this isn't a web page, it's an application which does serious client-side processing, so yes I want to know screen size but I also want an idea of available horsepower and memory (to allow filtering out features that run well on Intel but are dog slow or use an unacceptable amount of memory on mobile platforms like ARM).

Here are some of the ways I do try to adapt to mobile.

  • at application init appSetup.js interrogates its execution environment in an attempt to determine if it's on a mobile device (head's up: crude stuff here, just good enough for a demo app). Based on these checks appSetup sets Boolean values to JSDE.isMobile and JSDE.isMobileLowRes. These are used in js to vary functionality based on device. Some examples: killing jQuery animations, using shorter messages because of limited screen space, filtering out some heavyweight dataViewers and dataViewerManagers.
  • JSDE's default CSS is totally focused on desktop browsers. Only if the init routine thinks it's on mobile is mobile-specific CSS loaded. Mostly this CSS adjusts layout for smaller mobile screens. For example, if JSDE.isMobileLowres=true then the global msgline is hidden for in portrait mode (because of width limitations of phones, just no room for that msgText). It also applies some mobile-specific fixes/enhancement for iOS and Android (e.g., -webkit-tap-highlight, -webkit-overflow-scrolling)
  • in rare cases I vary the template used for a view based on isMobile/isMobileLowRes. For example, the template used for the "Welcome" popup content varies between desktop, tablet, and phone.
  • the dataViewer Registry exposes metadata for dataViewers, but this data varies based on isMobile/isMobileLowRes. Basically heavyweight dataViewers are filtered out
  • similar to previous bullet, dataViewerManagers availability depends on the values of isMobile/isMobileLowRes. Some just don't make sense on mobile (e.g., 4-cell matrix manager on small-screen phones) so won't be available in the datViewerManager's <select>
  • because some jQuery UI functions (e.g., accordion resize, position()) trigger styles recalcs and force layouts I minimize or totally avoid their use when I can
  • also related to JQUI controls, if you use JQUI on mobile then you may want to look into a lib that translates selected gesture events into mouse events, since JQUI knows nothing about gestures. JSDE uses Touch Punch for this (see description above in Dev Utils section)
  • For mobile to kill the 300ms delay in dispath of click events (translated from a touch events) am using FT's Fastclick (see description above in Dev Utils section)

 

As noted above the Boolean values JSDE.isMobile and JSDE.isMobileLowRes are used to adapt the application to mobile devices. Obviously getting these checks wrong can cause problems -- for example, I tested on a surface tablet where isMobile came up as false, and the desktop UI was used. If I do a JSDE version 2 I'll improve the checks used to assign the isMobile vars (right now those checks are quick and dirty).

Here are some of the issues with JSDE on mobile (most of these are also general tips on things to watch out for when writing a web application that targets both desktop and mobile using the same codebase:

  • performance: pretty snappy on desktops but not so much on mobile.
  • memory use: I could do a lot to trim memory on mobile but haven't tacklet that yet, so this is basically a desktop web application running on mobile, with all the memory implications that implies...
  • Layout update on rotation between portrait and landsape: sluggish and jerky. Partly caused by consuming container resize events using a polling mechanism (Ben Alman's resize plugin) that has a built in delay (250ms) since HTML/CSS don't fire resize events on container size change (I miss Flex!). This is especially bad in Firefox on Android
  • there are several problems with the Wikipedia iframe-based dataViewer, most seem to be general problems with iframes on mobile (see wikipedia viewer's Help for more info on this):
    • you'll see a signficantly slower load of content displayed in iframes compared to loading that same content as a "normal" browser page.
    • scrolling is a nightmare. On iOS you get no iframe scrolling unless you apply the style -webkit-overflow-scrolling:touch, and even then it's flaky and occasionally causes browser crashes. Android browsers aren't much better. For more info on this see the online Help for the Wikipedia dataViewer (where these problems are especially evident)
    • layout to container issues — sometimes the page fits to frame width, more often it overruns it so you have to scroll horizontally.
  • on mobile in general and especially in Safari the jQuery UI button highlighting is flaky — sometimes when you touch a button it just doesn't highlight, sometimes it highlights as expected, and sometimes it highlight and just stays highlighted for awhile...
  • for mobile browsers that don't support CSS overflow:scroll JSDE is essentially useless because it makes heavy use of scrollable divs. Mostly this is a problem with older device/browser combos, for example Android 2.3 stock browser (though Firefox on Android 2.3 scrolls divs just fine)
  • jqPlot: in a desktop browser you just click on a barChart bar to drilldown. On mobile this mostly works with touch, but it sometimes takes several touches, and if the bar is short or narrow it may not work at all. Also, touch a bar in Android 4.x stock browser and you may see some "ghost" bars flashing, not sure what that is, haven't had time to even look into that
  • iOS: -webkit-overflow-scrolling:touch does not play well with modal jQuery UI popups. Any background components that have this CSS style to enable smooth scrolling will not behave as protected modal background but will instead receive and respond to gestures (i.e., swipe to scroll). This even happens when you are swiping on the popup itself. To partially handle this I use a workaround of applying -webkit-overflow-scrolling:touch to all popups. That at least protects gestures made on the popup to stay with the popup and not affect background elements (but a swipe gesture oustide of that modal popup can still trigger scrolling of backgrond el's that have -webkit-overflow-scrolling:touch)
  • webkit: when you touch a component it will by default get highlighted briefly — blue on Android, grey on iOS. Unfortunately this highlighting can appear on parent containers, as well. For example on Android if you touched a bar in the jQplot graph the entire canvas elemnt would flash blue. Very ugly. To fix this I change the highlight color to transparent: -webkit-tap-highlight-color: rgba(0,0,0,0) ;
  • Youtube: on mobile you get the HTML5 browser which currently does not support playlists. This means you get the one video that's served up, unlike in the Flash Player where you can get a playlist with thumbnails for multiple videos. See Youtube viewer's online Help for more info on this.

I certainly could add more bullets here since JSDE has no shortage of quirks on mobile right now. Just keep in mind that this version really doesn't focus on mobile, it just addresses basics of SPA development. Bottom line here: this version may or may not run on your specific mobile device, and if it runs it may or may not be painfully slow, and its UI may or may not have flaky highlighting, occasionally unresponsive buttons, and jerky screen redraws. Anyway, if I do a JSDE v2 I'll make thngs a bit more "mobile aware".


Conclusion

Up to now my primers and this post have mostly focused on the benefits of SPAs and how you can achieve these. But JSDE is a general exploration of SPA dev and not surprisingly it also turned up some negatives. When you compare SPAs to other options you'll need to weigh the bad with the good, so I'll end this with a head's up on some of the downsides of SPAs. Note that this isn't a general comparison of features, for that see my SPA primer part 1. No, this is more of a developers perspective, commenting on those things that can turn the fun of developing software into drudgery, making you jump through so many hoops you start feeling like a circus monkey.

  • browser inconsistencies: this is probably the worst of it, because you end up spending your time on things that don't make the application better, just more consistent. All the HTML5 hype a couple of years ago made it sound liike browser inconsistencies were a thing of the past, but don't you believe it. In some important ways things have gotten better, but in still too many ways the problem remains, and you could make a strong argument that latest mobile browsers are worse than the latest desktop browsers in this regard
  • UI/UX is both a plus and a minus. Certainly you can create some great UI but you really have to work for it, sometimes it feels like your running a 10k carrying an anvil (you'll get to that finish line but it takes a lot more energy and time than it should). Largely this is driven by too few good, easy-to-use, sensible components (though it's very possible they're out there and I just havent gotten my hands on them yet). If your application needs to support both mobile and desktop you'll have a challenge to create an adaptive UI, though personally I wouldn't include that in the list of negatives -- that's not monkey work, that's the good stuff, an interesting puzzle to solve
  • Testing: The badness here is mostly an extension of my browser inconsistencies complaint — for any application you'll always have testing to do, but when browsers aren't consistent you need more testing. And if you're targeting mobile things are worse, not just because of differences in screens but because mobile browsers combine with these screens and software versions and multiple browsers to require a muliplicity of test cases. If you didn't have so many browses, and so many inconsistencies between them, testing would just be the normal pain the ass required of every application development project (no offense to software QA professionals, but I consider testing as just more monkey work -- critical monkey work, absolutely, but monkey work all the same).
  • learning curve: Of course, every software development environment has a learning curve, but some are steeper than others. Mature programming environments usually have lots of training tools and and plenty of documentation, and this can provide for an easy trail up the mountain. But using JavaScriptdev to develop full-blown applications is still fairly new and very dynamic, with lots of new tools, some of them a bit unpolished, maybe not so well documented, and all of this results in a rather steeper and more winding and frequently changing path up that mountain. This dynamic environment can be exhilarating, but it does require constant learning and that takes time and some brain effort. So, whether this one is a plus or minus largely depends on what type of programmer you are — if you prefer life in the slow lane then SPA development probably isn't for you.
  • performance on mobile: on desktops performance isn't an issue these days, but on mobile it is and if your application has to support older mobile devices you have a challenge on your hands. However, as with dealing with differences in mobile screen this can be a good challenge. The downside is that, try as you might, when you're on mobile you'll always be envious of those native apps, and when you compare yousrs to theirs yours is always going to come up short. Accept it, that's not going to change any time soon...

Don't let the above list give you the impression that I'm giving SPAs a thumbs down. I'm definitely not, just trying to make clear that while SPAs have a lot of benefits and JavaScript actually turns out to be a great language there are definitely costs. Hopefully the cost/benefit balance shifts in a positive direction, and soon. Unforunately things haven't been looking so good lately — see Paul Irish's recent talks such as "The mobile web is dying and needs your help"), and consider the potential implications of Chrome's move from webkit to blink.

As for me, I'm pleasantly surprised at how much I like JavaScript, it's an incredibly flexible and expressive language. And since I loath the idea of tying myself to a single OS/platform like iOS or Android for now I'll be heading down the JavaScript/SPA path. Of course, if browser improvements and standardization keep dragging along at a pace that makes a snail look like a Ferrari, well, I may yet end up writing native apps after all.

 

Related dlgsoftware posts



Original version: October 2013

Copyright © Dan Gronell 2013. Licensed under the Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 unported license. Creative Commons License

You can contact me at dan@dlgsoftware.com.


Here is the mailto link
if you'd like to use it...