What is scaleApp?
scaleApp is a tiny JavaScript framework for scalable One-Page-Applications / Single-Page-Applications. The framework allows you to easily create complex web applications.
You can dynamically start and stop/destroy modules that acts as small parts of your whole application.
Architecture overview
scaleApp is based on a decoupled, event-driven architecture that is inspired by the talk of Nicholas C. Zakas - "Scalable JavaScript Application Architecture" (Slides). There also is a little Article that describes the basic ideas.

Unlike Zakas recommendations to abstract DOM manipulations and separating the framework from the base library, scaleApp does not implement any DOM methods.
Instead scaleApp can be extended by plugins. So you can just use one of your
favorite libs (e.g. jQuery) as base library or you are going to implement all
your needed DOM methods into the DOM plugin (scaleApp.dom.coffee) for a more
clean and scaleable architecture.
Features
- loose coupling of modules
- small (about 340 sloc / 10k min / 3.4k gz)
- no dependencies
- modules can be tested separately
- replacing any module without affecting other modules
- extendable with plugins
- browser and node.js support
- flow control
Extendable
scaleApp itself is very small but it can be extended with plugins. There already
are some plugins available (e.g. mvc, i18n, permission, state, etc.) but
you can easily define your own one.
Download latest version
Quick Start
Link scaleApp.min.js in your HTML file:
<script src="scaleApp.min.js"></script>
If you're going to use it with node:
npm install scaleapp
var sa = require("scaleapp");
or use bower:
bower install scaleapp
Register modules
scaleApp.register( "myModuleId", function( sb ){
return {
init: function(){ /*...*/ },
destroy: function(){ /*...*/ }
};
});
As you can see the module is a function that takes the sandbox as a parameter
and returns an object that has two functions init and destroy.
Of course your module can be any usual class with those two functions.
Here an coffee-script example:
class MyGreatModule
constructor: (@sb) ->
init: -> alert "Hello world!"
destroy: -> alert "Bye bye!"
scaleApp.register "myGreatModule", MyGreatModule
The init function is called by the framework when the module is supposed to
start. The destroy function is called when the module has to shut down.
Show registered modules
scaleApp.lsModules() // returns an array of module names
Show running instances
scaleApp.lsInstances() // returns an array of instance names
Show registered plugins
scaleApp.lsPlugins() // returns an array of plugin names
Asynchronous initialization
You can also init or destroy you module in a asynchronous way:
class MyAsyncModule
constructor: (@sb) ->
init: (options, done) ->
doSomethingAsync (err) ->
done err
destroy: (done) ->
doSomethingAsync (err) ->
done err
scaleApp.register "myGreatModule", MyGreatModule
end -> alert "now the initialization is done"
scaleApp.start "myGreatModule", callback: end
Unregister modules
It's simple:
scaleApp.unregister("myGreatModule");
Start modules
After your modules are registered, start your modules:
scaleApp.start( "myModuleId" );
scaleApp.start( "anOtherModule" );
Start options
You may also want to start several instances of a module:
scaleApp.start( "myModuleId", {instanceId: "myInstanceId" } );
scaleApp.start( "myModuleId", {instanceId: "anOtherInstanceId" });
If you pass a callback function it will be called after the module started:
scaleApp.start( "myModuleId", {callback: function(){ /*...*/ } );
All other options you pass are available through the sandbox:
scaleApp.register( "mod", function(sandbox){
return {
init: function(opt){
(opt === sandbox.options) // true
(opt.myProperty === "myValue") // true
},
destroy: function(){ /*...*/ }
};
});
scaleApp.start("mod", {
instanceId: "test",
options: { myProperty: "myValue" }
});
If all your modules just needs to be instanciated once, you can simply starting them all:
scaleApp.startAll();
To start some special modules at once you can pass an array with the module names:
scaleApp.startAll(["moduleA","moduleB"]);
You can also pass a callback function:
scaleApp.startAll(function(){
// do something when all modules were initialized
});
Stopping
It's obvious:
scaleApp.stop("moduleB");
scaleApp.stopAll();
Listing modules and instances
lsModules() // returns an array of all registered module IDs
lsInstances() // returns an array of all running instance IDs
Publish/Subscribe
If the module needs to communicate with others, you can use the publish and
subscribe methods.
Publish
The publish function takes three parameters whereas the last one is optional:
- topic : the channel name you want to publish to
- data : the data itself
- opt : options or callback
- publishReference : If the data should be passed as a reference to the
other modules this parameter has to be set to true.
By default the data object gets copied so that other modules can't
influence the original object.
The publish function is accessible through the sandbox:
sb.publish( "myEventTopic", myData );
You can also use the shorter method alias emit.
Subscribe
A message handler could look like this:
var messageHandler = function( data, topic ){
switch( topic ){
case "somethingHappend":
sb.publish( "myEventTopic", processData(data) );
break;
case "aNiceTopic":
justProcess( data );
break;
}
};
... and it can listen to one or more channels:
sub1 = sb.subscribe( "somthingHappend", messageHandler );
sub2 = sb.subscribe( "aNiceTopic", messageHandler );
Or just do it at once:
sb.subscribe({
topicA: cbA
topicB: cbB
topicC: cbC
});
You can also subscribe to several channels at once:
sb.subscribe(["a", "b"], cb);
If you prefer a shorter method name you can use the alias on.
attache and detache
A subscription can be detached and attached again:
sub.detach(); // don't listen any more
sub.attach(); // receive upcoming messages
Unsubscribe
You can unsubscribe a function from a channel
sb.unsubscribe("a-channel", callback);
And you can remove a callback function from all channels
sb.unsubscribe(callback);
Or remove all subscriptions from a channel:
sb.unsubscribe("channelName");
Flow control
Series
var task1 = function(next){
setTimeout(function(){next(null, "one");},0);
};
var task2 = function(next){
next(null, "two");
};
scaleApp.util.runSeries([task1, task2], function(err, results){
// result is ["one", "two"]
});
Waterfall
var task1 = function(next){
setTimeout(function(){
next(null, "one", "two");
},0);
};
var task2 = function(res1, res2, next){
// res1 is "one"
// res2 is "two"
next(null, "yeah!");
};
scaleApp.util.runWaterfall([task1, task2], function(err, result){
// result is "yeah!"
});
Plugins
i18n - Multi language UIs
Link scaleApp.i18n.min.js in your HTML file:
<script src="scaleApp.min.js"></script>
<script src="scaleApp.i18n.min.js"></script>
If your application has to support multiple languages, you can pass an objects containing the localized strings with the options object.
var myLocalization =
{
en: { welcome: "Welcome", ... },
de: { welcome: "Willkommen", ... },
...
}
...
scaleApp.register( "moduleId", myModule, { i18n: myLocalization } );
Now you can access these strings easily trough the sandbox using the _ method.
Depending on which language is set globally it returns the corresponding
localized string.
sb._("myStringId");
You can set the language globally by using the setLanguage method:
scaleApp.i18n.setLanguage( "de" );
You can also set a global i18n object which can be used by all modules:
scaleApp.i18n.setGlobal( myGlobalObj );
mvc - very simple MVC

Here is a sample use case for using the MVC plugin (in coffeescript).
class MyModel extends scaleApp.Model name: "Noname"
class MyView extends scaleApp.View
constructor: (@model, @sb, @template) -> super @model
# The render method gets automatically called when the model changes
# The 'getContainer' method is provided by the dom plugin
render: -> @sb.getContainer.innerHTML = @template @model
class MyController extends scaleApp.Controller
changeName: (name) -> @model.set "name", name
registerModule "myModule", (@sb) ->
init: (opt) ->
# You can use any template engine you like. Here it's
# just a simple function
template = (model) -> "<h1>Hello #{model.name}</h1>"
@m = new MyModel
@v = new MyView @m, @sb, @template
@c = new MyController @m, @v
# listen to the "changeName" event
@sb.subscribe "changeName", @c.changeName, @c
destroy: ->
delete @c
delete @v
delete @m
@sb.unsubscribe @
scaleApp.publish "changeName", "Peter"
state - Finite State Machine
The state plugin is an approach to implement a Finite State Machine that can be used to keep track of your applications state.

s = new scaleApp.StateMachine
start: "a"
states:
a: { enter: (ev) -> console.log "entering state #{ev.to}" }
b: { leave: (ev) -> console.log "leaving state #{ev.from}" }
c: { enter: [cb1, cb2], leave: cb3 }
fatal: { enter: -> console.error "something went wrong" }
transitions:
x: { from: "a" to: "b" }
y: { from: ["b","c"] to: "c" }
uups: { from: "*" to: "fatal" }
s.addState "d", { enter: -> } # add an additional state
s.addState { y: {}, z: { enter: cb } } # or add multiple states
s.addTransition "t", { from: "b", to: "d" } # add a transition
s.can "t" # false because 'a' is current state
s.can "x" # true
s.onLeave "a", (transition, eventName, next) ->
# ...
next()
s.onEnter "b", (transitioin, eventName, next) ->
doSomething (err) -> next err
s.fire "x"
s.current # b
permission - controll all messages
If you include the permission plugin, all Mediator methods will be rejected
by default to enforce you to permit any message method explicitely.
scaleApp.permission.add "instanceA", "subscribe", "a"
scaleApp.permission.add "instanceB", "publish", ["b", "c"]
Now instanceA is allowed to subscribe to channel a but instanceB cannot
subscribe to it. Therefore instanceB can publish data on channel a and
instanceB can not.
Of course you can remove a permission at any time:
scaleApp.permission.remove "moduleA", "publish", "x"
Or remove the subscribe permissions of all channels:
scaleApp.permission.remove "moduleB", "subscribe"
strophe - XMPP plugin
This is an adapter plugin for Strophe.js with some helpful features (e.g. automatically reconnect on page refresh).
scaleApp.xmpp.login("myjid@server.tld", "myPassword");
scaleApp.xmpp.logout();
scaleApp.xmpp.jid // the current JID
util - some helper functions
sb.mixin(receivingClass, givingClass, override=false)sb.countObjectKeys(object)
Other plugins
- dom - basic DOM manipulations (currently only used for
getContainer)
Write your own plugin
scaleApp.registerPlugin
# set the ID of your plugin
id: "myPlgin"
# define the core extensions
core:
myCoreFunction: -> alert "Hello core plugin"
myBoringProperty: "boring"
# define the sandbox extensions
sandbox: (@sb) ->
appendFoo: -> @sb.getContainer.append "foo"
Usage:
scaleApp.myCoreFunction() # alerts "Hello core plugin"
class MyModule
constructor: (@sb) ->
init: -> @sb.appendFoo() # appends "foo" to the container
destroy: ->
Existing modules
You can find some example modules in src/modules/.
Build browser bundles
If you want scaleApp bundled with special plugins type
grunt custom[:PLUGIN_NAME]
e.g. cake custom:dom:mvc creates the file scaleApp.custom.js that
contains scaleApp itself the dom plugin and the mvc plugin.
Changelog
v0.3.9 (12-2012)
- extended clock module
- grunt as build systemt
- added waterfall flow control method
- improved permission plugin
- improved state plugin (thanks to Strathausen)
- added xmpp (stropje.js) plugin
- added a simple clock module
- added bower support
- added this changelog
v0.3.8 (08-2012)
- bug fixes
- added support for async. callback of the
publishmethod - added amd support
v0.3.7 (07-2012)
- bug fixes
- added permission plugin
- ported specs to buster.js
- support for global i18n properties
v0.3.6 (03-2012)
- support for async. and sync. module initialization
v0.3.5 (03-2012)
- simpified Mediator code
v0.3.4 (03-2012)
- bugfixes
- added
lsModulesandlsInstances - improved README
v0.3.3 (02-2012)
- run tests with jasmine-node instead of JSTestDriver
- added travis testing
- improved README
v0.3.2 (01-2012)
- bugfixes
- improved Mediator
v0.3.0 (11-2011)
- ported to Coffee-Script
- removed jQuery dependency
v0.2.0 (07-2011)
- bugfixes
- improvements
v0.1.0 (02-2011)
- first release
Testing
npm test
Demo
WARNING: the demo is out of date
You can try out the sample application that is build on scaleApp. Also have a look at the source code.
Licence
scaleApp is licensed under the MIT license. For more information have a look at LICENCE.txt.
