FugueJS

FugueJS is a JavaScript library to encapsulate components in rich web applications and allow these components to communicate without coupling them. This is a useful technique because it improves the reusability and testability of individual components. FugueJS is a flexible, configurable, unopinionated, Vanilla JavaScript based library without any dependencies that can function both as an adhoc tool or as a framework. It can work in a 'progressive enhancement' style application as well as in SPA context.
Download FugueJS

Quick Example

For the impatient, here is a quick example of how FugueJS works:

function MyButton() { //your component
 this.click = function() {
  alert('hello world');
 }
}

var div = document.createElement( 'div' ); //the HTML
html = '<button data-fg-component="MyButton">';
div.innerHTML = html;

var fugue = new Fugue(div);

//Trigger event
var e = new Event( 'click', { bubbles: true } );
div.querySelector('button').dispatchEvent( e );

This example will display an alert box containing the text: 'hello world'. The rest of this page will show you more complex examples and explain how FugueJS can improve the quality of your application by applying a certain structure.

Overview

To begin using FugueJS you have to include the following script tag in your code:

<script src="fugue.js" ></script>

By default, FugueJS will assume it has to bootstrap your application, thus acting more like a framework, however it's possible to configure FugueJS to allow custom bootstrapping (like has been done in the quick example) - but we'll come to that later.

The HTML

Here is a HTML document we're going to use to demonstrate a slightly more elaborate FugueJS application:

<html>
<head>
  <title>FugueJS</title>
</head>
<body
 data-fg-controller="myapp"
 data-fg-component="Application.Controller" >

<input
 name="phrase"
 type="text"
 data-fg-component="Application.Components.InputWidget"
 data-fg-delegate="myapp"
 data-fg-write-to="output"
/>

<output
 data-fg-component="Application.Components.OutputWidget"
 data-fg-delegate="myapp"
 data-fg-listen-to="readTextStream"
>
</output>

</body>
</html>

In this application we want to create a basic one-way binding, whatever you type in the input field has to appear in the output box. The goal is to accomplish this without having the involved components know about eachother, because that keeps them easily testable and reusable. Let's take a look at the code defining the 3 components:

Application.Controller = function() {
 var outputStream;

 this.registerTextStream = function(stream) {
  outputStream = stream;
 }

 this.output = function(c) {
  outputStream(c);
 }
}

Application.Components.InputWidget =
function(node, cx, delegate) {
 this.keyup = function(e) {
  delegate('write-to')(node.value);
 }
}

Application.Components.OutputWidget =
function(node, cx, delegate) {
 delegate('listen-to')(function(html){
  node.innerHTML = html;
 });
}

The result

The code you see above has also been incorporated on this page, so here's the resulting widget set:

Input text:




Output appears here:


How does it work ?

Once the page is loaded, FugueJS will search for any HTML tags containing the data-fg-component attribute. For every component it will process the expression provided in the attribute. For instance, the expression for InputWidget in the HTML document is:

data-component="Application.Components.InputWidget"

FugueJS will process this as:

new Application.Components.InputWidget( node, cx, delegate );

FugueJS will pass the DOM NODE as the first argument to the constructor.
The second argument is context object that can be used to share services.
The third argument is the delegate function.

Events

Any time an event occurs (like a click), the event is dispatched to the component instance that contains the DOM element that was the target of the event.

So if we press a key, while the input element has focus, the 'KeyUp' event will be dispatched to InputWidget instance we just created. This will cause the following method to be invoked:

this.keyup = function(e) {
 delegate('write-to')(node.value);
}

Now, our InputWidget has to communicate about the event in order to make the OutputWidget display the contents of its node.

However, they are not allowed to talk to eachother, because that would create a highly coupled application, that's difficult to test.

So, how do we solve this ?

Delegation

To solve the problem sketched above, we use delegation.

As we've already discussed, FugueJS thinks in components. Except from service objects, everything is a component.

Some components are reusable, like our InputWidget and OutputWidget, others are not. A Controller widget is an application specific component, used to connect and manage various components within it's node. In this example, we use the Controller called myapp to orchestrate the interaction between our pair of widgets.

You might have noticed this little line in our InputWidget HTML:

 data-fg-delegate="myapp"

Here, we tell InputWidget it should delegate certain tasks to myapp. How does it know where to find myapp ?

FugueJS will traverse the DOM upwards and look for a DOM node having the 'data-fg-controller' attribute matching the name 'myapp'. It will thus find that 'myapp' is the component associated with the <body> element.
To send a message to its delegate, our InputWidget does:

delegate('write-to')(node.value);

The delegate function is the most essential part of FugueJS. It's the function that creates bridges among components without coupling them.
The delegate function takes a single argument and returns a function that will invoke the desired method on the delegate component.
This approach has several advantages: you can cache / alias delegation methods and the method will be executed within it's own context (this will point to the delegate object).
How the specified operation, specified in the argument of delegate is to be delegated is described in the HTML document:

data-fg-write-to="output"

Here, the HTML document tells us that if the component invokes 'write-to' on its delegate, this call should be mapped to a method called output. In turn, this mechanism will invoke:

this.output = function(c) {
 outputStream(c);
}

on the Controller component.

Don't call us, we call you

Now it would be tempting to just say:

document.getElementByTagName('output').innerHTML = html;

In our MainController. But that's not allowed because the Main Controller is not supposed to know anything about the components it manages. So, how do we accomplish this ?

The solution is to reverse the direction of operation, instead of having our controller actively calling our component, we register our component with the controller ordering it to notify it if necessary - this is sometimes referred to as the don't call use, we call you principle or the hollywoord principle.

When FugueJS starts awaking the components, it will execute the constructor of the OutputWidget:

function OutputWidget(node, cx, delegate) {
 delegate('listen-to')(function(html){
  node.innerHTML = html;
 });
}

Here, the OutputWidget will register itself with the MainController to listen for updates.

To this end, it will send the message listen-to to its delegate, which happens to be the MainController myapp. Thanks to the mapping in the HTML this message will be mapped to the method registerTextStream...

this.registerTextStream = function(stream) {
  outputStream = stream;
}

...which actually does nothing special, except storing the stream handler.

Whenever myapp gets notified by the InputWidget it will use this handler to pass the content to its registered stream:

this.output = function(c) {
  outputStream(c);
}

Service Objects

As you have probably noted, Component constructors accept 3 parameters:

function( node, cx, delegate )

In the middle you see cx. CX is the context object. The context objects serves a dual purpose. First of all it has a property called instances containing all component instances. You can attach this property to the global window object for debugging purposes. Second, and more importantly, the context object is used to share services.

Service objects are created by Service Components. In most cases, the only Service Component is the main controller, it can make various service objects available to the current page like this:

cx.dictionary = new Dictionary();

Watching

By default, if you insert new nodes dynamically in the DOM region managed by FugueJS, these components will be scanned for fg-components. Likewise, when removing nodes, the corresponding instances will be deleted as well. Before deletion however the method:

instance.destroy();

will be invoked to allow the component to clean up its mess.

Broadcasting

Components can also communicate using a more generic system called broadcasting. To broadcast a message to all interested components in the DOM region:

CX.services.broadcast( 'mymessage', messageObject );

to listen for broadcasts of a specific type:

CX.services.listenFor( 'broadcast',
function( eventObject ) {
 node.innerHTML = eventObject.detail.message;
});

to stop listening:

CX.services.noLongerListenTo( 'mymessage', callback );

Bootstrapping

FugueJS in an unopiniated tool, as such it's quite flexible and allows you to configure a lot. For instance, by default, FugueJS assumes you want to use it as a framework, and it will automatically add listeners, start watching and scanning for components. However, it's also possible to perform the bootstrapping yourself, simply add the following attribute to the script tag:

<script data-fg-custom-bootstrap="yes"
src="fugue.js" ></script>