Aura is an event-driven architecture for wrapping your code into reusable widgets and extensions that can easily communicate with each other.
We work great with existing frameworks like Backbone.js or Ember, but are framework-agnostic, adapting many best-practice patterns for developing maintainable applications.
Aura has first-class support for modern tools like Bower], Grunt and Yeoman and uses libraries like RequireJS under the covers (for now). As solutions like ES6 modules and Polymer become stable and usable, the project will move towards using them.
A widget is something atomic with a clear responsibility. A mini-app basically that can be instantiated (possibly multiple times) on an arbitrary part of your application. You might not be accustomed to thinking like this, preferring to build a highly coupled app. That might work just fine initially, but once it gets more complex you can run into trouble. Therefore, next time when you start building something bigger, stop for a moment and try to identify possible widgets.
Consider for example GitHub’s site:
Separating your application into smaller parts is essential for keeping your architecture clean, reusable and mainly maintainable. The principle is a known concept in computer science: “divide and conquer”. Divide everything up into smaller parts which have lower complexity, are easier to test and cause fewer headaches. Then compose them together to form your larger application.
Modules (or widgets) within your application need to communicate with each other. Such communication creates dependencies as widget An eeds to have a reference to widget B if it needs to invoke some operation on it, right? Well, not necessarily, as that would again couple those widgets together and you couldn’t exchange widget B arbitrarily without having to also change widget A.
Therefore, a common practice for creating a modular architecture is to decouple communication among components through event broadcasting mechanisms. Aura comes with global and widget-level messaging patterns, making this a breeze.
Widgets are completely decoupled, they only can talk to each other via events. You can't have a handle on them from the outside, and themselves are just aware of what you explicitely make available throught their sandboxes.
To build your app, you can assemble widgets via AuraJS's HTML API, by using the data-aura-widget attribute.
Let's take an example. Let's say that we want to build a Github Issues app. We need to be able to :
Now let's make some widgets, but first we need a way to talk to Github's API.
Here is a simple AuraJS extension that does just that :
extensions/aura-github.js
define({
initialize: function (app) {
app.sandbox.github = function (path, verb, data) {
var dfd = $.Deferred();
var token = app.config.github.token;
verb = verb || 'get';
if (data && verb != 'get') {
data = JSON.stringify(data);
}
$.ajax({
type: verb,
url: 'https://api.github.com/' + path,
data: data,
headers: {
"Authorization": "token " + token
},
success: dfd.resolve,
error: dfd.reject
});
return dfd;
};
}
});
This extension exposes in all our widgets a way to talk to Github's API via the this.sandbox.github method.
To use it in your aura app :
app.js
var app = new Aura({
github: { token: 'current-user-token-here' }
});
app.use('extensions/aura-github');
app.start({ widgets: 'body' });
And now, let's write the issues widget :
widgets/issues/main.js
define(['underscore', 'text!./issues.html'], function(_, tpl) {
// Allow template to be overriden locally
// via a text/template script tag
var template, customTemplate = $('script['data-aura-template="github/issues"]');
if (customTemplate.length > 0) {
template = _.template(customTemplate.html());
} else {
template = _.template(tpl);
}
return {
initialize: function() {
_.bindAll(this);
this.repo = this.options.repo;
this.filter = this.options.filter || {};
this.sandbox.on('issues.filter', this.fetch, this);
this.fetch();
},
fetch: function(filter) {
this.filter = _.extend(this.filter, filter || {});
var path = 'repos/' + this.repo + '/issues';
return this.sandbox.github(path, 'get', this.filter).then(this.render);
},
render: function(issues) {
this.html(template({
issues: issues,
filter: this.filter,
repo: this.repo
}));
}
};
});
Now we can place this widget everywhere in our app by using Aura's HTML API based on data-attributes.
<div data-aura-widget="issues" data-aura-repo="aurajs/aura"></div>
You can even have multiple instances of this widget in you page :
<div class='row'>
<div class='span4' data-aura-widget="issues" data-aura-repo="aurajs/aura"></div>
<div class='span4' data-aura-widget="issues" data-aura-repo="emberjs/ember.js"></div>
<div class='span4' data-aura-widget="issues" data-aura-repo="documentcloud/backbone"></div>
</div>
Any other widget can now emit issues.filter events that these widgets will respond to. For example in another widget that will allow the user to filter the issues lists, we can have :
this.sandbox.emit('issues.filter', { state: 'closed' });
You can find a Github client demo app based on AuraJS + a bunch of Github widgets here
Web apps are all about the end user experience (UI, DOM elements). The web development ecosystem is all about much more low level stuff. We need a way to package higher level abstractions and make them truly reusable, and that's what Aura is all about.
Need some more reasons to use Aura?:
Aura
objectYour application will be an instance of the Aura
object.
Its responsibilities are to load extensions when the app starts and clean them up when the app stops.
Extensions are loaded in your application when it starts. They allow you to add features to the application, and are available to the widgets through their sandbox
.
The core
implements aliases for DOM manipulation, templating and other lower-level utilities that pipe back to a library of choice. Aliases allow switching libraries with minimum impact on your application.
A sandbox
is just a way to implement the facade pattern on top of features provided by core
. It lets you expose the parts of a JavaScript library that are safe to use instead of exposing the entire API. This is particularly useful when working in teams.
When your app starts, it will create an instance of sandbox
in each of your widgets.
A widget represents a unit of a page. Each widget is independent. This means that they know nothing about each other. To make them communicate, a Publish/Subscribe (Mediator) pattern is used.
The simplest usable Aura app using a widget and extension can be found in our boilerplate repo. We do however recommend reading the rest of the getting started guide below to get acquainted with the general workflow.
npm install
to install build dependencies.bower install
to install lib dependencies.grunt build
and aura.js
will be placed in dist/
.Run grunt
. Then visit http://localhost:8899/spec/
.
Run npm test
.
The first step in creating an Aura application is to make an instance of Aura
.
var app = new Aura();
Now that we have our app
, we can start it.
app.start({
widget: 'body'
});
This starts the app by saying that it should search for widgets anywhere in the body
of your HTML document.
By default widgets are retrieved from a directory called widgets/
that must be at the same level as your HTML document.
Let's say we want to create an “hello” widget. To do that we need to create a widgets/hello/
directory
This directory must contain:
main.js
file. It will bootstrap and describe the widget. It is mandatory, no matter how small it can be.For our “hello” widget the main.js
will be:
define({
initialize: function () {
this.$el.html('<h1>Hello Aura</h1>');
}
});
Add the following code to your HTML document.
<div data-aura-widget="hello"></div>
Aura will call the initialize
method that we have defined in widgets/hello/main.js
.
Imagine that we need an helper to reverse a string. In order to accomplish that we'll need to create an extension.
define('extensions/reverse', {
initialize: function (app) {
app.core.util.reverse = function (string) {
return string.split('').reverse().join('');
};
}
});
The Aura Mediator allows widgets to communicate with each other by subscribing, unsubscribing and emitting sandboxed event notifications. The signatures for these three methods are:
sandbox.on(name, listener, context)
sandbox.off(name, listener)
sandbox.emit(data)
Below we can see an example of a Backbone view using the Mediator to emit a notification when tasks have been cleared and subscribing to changes from tasks.stats
in order to render when they are updated.
define(['hbs!./stats'], function(template) {
return {
type: 'Backbone',
events: {
'click button': 'clearCompleted'
},
initialize: function() {
this.render();
this.sandbox.on('tasks.stats', _.bind(this.render, this));
},
render: function(stats) {
this.html(template(stats || {}));
},
clearCompleted: function() {
this.sandbox.emit('tasks.clear');
}
}
});
To make our reverse
helper available in our app, run the following code:
app.use('extensions/reverse');
This will call the initialize
function of our reverse
extension.
Calling use
when your app
is already started will throw an error.
To make app.logger
available, pass {debug: true}
into Aura constructor:
var app = new Aura({debug: true});
Logger usage:
// You can use logger from widgets or extensions
var logger = sandbox.logger;
logger.log('Hey');
logger.warn('Hey');
logger.error('Hey');
If you want to enable event logging, do this:
var app = new Aura({debug: true, logEvents: true});
Also, when parameter debug
is true, you can declare following function for any debug purposes:
// Function will be called for all Aura apps in your project
window.attachDebugger = function (app) {
// Do cool stuff with app object
console.log(app);
// Maybe you want to have access to Aura app via developer console?
window.aura = app;
};
An Aura scaffolding generator (for Yeoman) is also available at Aura generator.
# First make a new directory, and `cd` into it:
mkdir my-awesome-project && cd $_
# Then install `generator-aura`:
npm install -g generator-aura
# Run `yo aura`, optionally passing an app name:
yo aura [app-name]
# Finally, install npm and bower dependencies:
npm install && bower install --dev
Available generators:
Generates a widget in app/widgets
.
Example:
yo aura:widget sample
Produces app/widgets/sample/main.js
Generates a extension in app/extensions
.
Example:
bash
yo aura:extension storage
Produces app/extensions/storage.js
Generates cool styles.
Example:
bash
yo aura:styles
Want to look at some sample apps built with Aura? Check out:
An Instagram clone built with Aura and Hull.io.
Also implemented in an alternative way.
See the contributing docs