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
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.
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.
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 code you see above has also been incorporated on this page, so here's the resulting widget set:
Input text:
Output appears here:
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.
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 ?
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.
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);
}
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();
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.
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 );
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>