Creating a loosely-coupled & multi-page JS application using Core(Mediator) / Sandbox(Facade) / Module pattern -- advice?
Asked Answered
G

2

22

I'm building a multi-page javascript application. I've read a lot into design patterns, and creating applications using a Core/Facade/Module approach w/ loose coupling (pub/sub scribing to events).

I have a pretty good system worked out that minifies & combines all of my module files & related dependencies into a single external javascript file at deployment. Minimizing extra HTTP requests for my application is a design goal -- therefore I'm not too interested in AMD (asynchronous module definition).

I'm using the guidelines delinitated in Nicholas Zakas's presentation, Scalable JavaScript Application Architecture

http://www.youtube.com/watch?v=vXjVFPosQHw

&&

Addy Osmani's Patterns For Large-Scale JavaScript Application Architecture http://addyosmani.com/largescalejavascript/

&&

This premium tutorial, by Andrew Burgess from Nettuts, Writing Modular JavaScript http://marketplace.tutsplus.com/item/writing-modular-javascript/128103/?ref=addyosmani&ref=addyosmani&clickthrough_id=90060087&redirect_back=true

My question is advice on how to go about managing different pages of this application & their associated modules. I'm also using Backbonejs's Router class w/ ballupton's History.js library to manipulate the HTML5 history/state API and dynamically load pages without a refresh while maintaining backwards compatibility for older browsers that don't support the HTML state API. All of my pages share a common code base (single minified & compressed js file).

Here's an outline of the structure I'm thinking of using in my application: enter image description here

It's essentially a hybrid approach. The top half consists of a Core/Facade/Module pattern with discrete modules that don't interact directly with each other and publish/subscribe to notifications via the facade.

The bottom half consists of my proposed application structure, which notifies a "main controller" when the state/url changes, the main-controller performs any global operations (such as initializing the header & sidebar-menu of my UI if not already initialized) and the instructs the relevant sub-control to run it's init() method (as well as calling destroy(); on any controller that was previously loaded). Each sub-controller (correlates to ex: home-page, calendar-page, reservations-page, etc.) cherry-picks modules from the pool of available modules and initializes them.

Is this a good approach or am I on a bad track? I can see the modules are still independent of each other which is good for scalability.

I've also considered just treating the Router & Controllers as discrete modules and having them publish/subscribe to the Core, and each controller somehow initializes the necessary modules it needs for it's page.

Glean answered 31/10, 2012 at 2:2 Comment(2)
Your argument for not using AMD to modularize your application is flawed; using the r.js optimizer it's trivial to create a single package out of your modules if you use, for example, requirejs during the optimization phase.Isocline
Added new concepts that we are currently using to my answer.Undulatory
U
6

One thing we did to keep history working smoothly was to first change the URL. On change of the URL an event would get triggered, the router would parse the url then figure out what to do. This event would automatically get triggered on page load as well. If you clicked a link it would simply change the URL, which is fairly simple and completely decouples links/buttons from the application logic. This seemed to work well for our application. We used JQM, but we dropped most of their router since we read most of our instructions from some XML file and didn't have a bunch of HTML pages to load into the main viewport area.

I've often seen backbone apps use the router as the core/mediator. This is a good idea. You can simply listen on change events for the URL and then change the page appropriately. This Mediator should probably be a singleton, although singletons are harder to unit test.

The thing I didn't necessarily agree with Backbone on was its definition of "views". The view sort of seems like an action in a controller (well from some perspectives). We added one more level of separation in our application at that point. Our views made ajax requests to template files which were filled in with some JSON and handlebars.js. I'd say your header/sidebar should just be templates. If you need to refresh them then see how you could do extremely simply otherwise you are looking at creating 4 new modules: collection for a list, model for each item, collection view, and model view. I'd couple templates more tightly with some higher level view until they need to be broken down further (eg. some "Application/Main View").

Having this template layer allows you to make superficial changes without recompiling as well, which is nice. Anytime you can put things into "meta" it is a win (well unless it requires you to read XML (ha)). As a bonus you can then cache the template separately as well (or cache bust it separately for that matter).

Your architecture does seem fine though, and is a valid approach to your problem. One tip I'd give is don't over design up front. Iterating is best. You will need to refactor. It is impossible to foresee what would make your application flow more smoothly 3-6 months in advance.

Update on Dec 18th, 2013

Now a days we are using marionette and more addy osmani tricks. On top of the above items we are using AMD's alternate format:

define(function(require) {
    var myTemplate = require('hb!mytemplate.handlebars'),
        view = require('myview');
    ...
});

We are also using the marionette application class in combination with wreqr which provides a request/response layer. This allows us to set application wide objects cleanly. It also allows us to define classes without explicitly stating the class name. This is a pretty good way to sandbox. EG:

this.app.setHandler('CanvasClass', function() {
    return RaphaelCanvasView;
});

// elsewhere

this.app.request('CanvasClass').text('123', {x:1, y:2});

This all seems to work out pretty well.

You should also checkout aura js and web components. Our directory structure sort of mimics/anticipates those concepts without investing in them yet.

Undulatory answered 2/11, 2012 at 20:2 Comment(0)
R
2

I think it's a good approach. I've been developing something similar in 2 huge commercial web-apps (minus backbone, and with a custom history manager) and it works great. I'm also not using AMD and all interactions are handled by pub/sub. One of my best inspirations (which I'm sure you already know) is: https://github.com/aurajs/aura

Rotter answered 9/11, 2012 at 15:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.