A Practical Example Of Events In Foundry
In the previous tutorial, we outlined two kinds of events that modules deal with. In this tutorial, we dive into a real world implementation of Application and Notification events in Foundry.
What You'll Need For This Tutorial
- The Foundry Starter Project
- A basic understanding of how Modules work in Foundry
- A basic understanding of events in Foundry
What You'll Learn
- How to subscribe to and publish Notification Events
- How to subscribe to and publish Application Events
Overview
We will create three modules, each with their own responsibilities:
TaskListModule- Adds tasks to a list
- Publishes an Application Event when a task is added
- Has a
SelectionModule, and listens for a Notification Event
SelectionModule- Selects and deselects items in a list
- Publishes a Notification Event when the selection size has changed
RecentTasksModule- Shows a list of recently added tasks
First, let's look into wiring up Application Events.
Using Application Events
The TaskListModule publishes an Application Event when a task is added. The
RecentTasksModule subscribes to this event, and adds the newly created task to
its own list. First, let's tackle the easy part: subscribing to the event.
Contents of recent_tasks_module.js
var RecentTasksModule = Module.Base.extend({
prototype: {
_ready: function() {
Module.Base.prototype._ready.call(this);
this.subscribe("task.added", this, "handleTaskAdded");
},
handleTaskAdded: function(publisher, data) {
var item = this.document.createElement("li");
item.innerHTML = data.task;
this.element.querySelector("ol").appendChild(item);
}
}
});
In the _ready method, we subscribe to the task.added event, which will
invoke handleTaskAdded. In the event handler, we get the following arguments:
publisher: Any instance ofTaskListModule.data: Arbitrary data passed along in the event. In this case, it will take the form of:{ "task": "task name", "item": [object HTMLLIElement] }
Now that we have a subscriber to the task.added event, let's look at how we'll
publish this event in TaskListModule.
Contents of task_list_module.js
var TaskListModule = Module.Base.extend({
prototype: {
add: function submit(event, element, params) {
event.stop();
var form = this.element,
input = form.elements.taskName,
taskName = input.value;
if (/^\s*$/.test(taskName)) {
alert("Please enter a task");
}
else {
var item = this.document.createElement("li");
item.innerHTML = taskName;
this.element.querySelector("ol").appendChild(item);
this.publish("task.added", {
task: taskName,
item: item
});
}
input.value = "";
input.focus();
}
}
});
We have one Module Action called add, which is triggered by a submit event.
All we do is add a new <li> tag to a list containing the text of the new task.
The call to this.publish(...) is where TaskListModule and
RecentTasksModule get wired together.
Let's look at the HTML necessary for both of these modules to exist:
<form method="GET" action="#"
data-modules="TaskListModule"
data-module-options='{
"controllerId": "tasks",
"defaultModule": true
}'
data-actions="tasks.add"
>
<h2>Tasks</h2>
<p>
<label>Task: <input type="text" name="taskName"></label>
<button type="submit">Add Task</button>
</p>
<ol></ol>
</form>
<div data-modules="RecentTasksModule">
<h2>Recently Added Tasks</h2>
<ol></ol>
</div>
We have a <form> tag with data-modules="TaskListModule" which is the root
element of the TaskListModule. Below that is a <div> tag with
data-modules="RecentTasksModule". Foundry creates those modules for you, so
no additional code is necessary.
Application Events are the easiest to deal with. Notification Events require a little more knowledge about a module's surrounding environment.
Using Notification Events
In this section, we will create a SelectionModule, which will handle the
selecting and deselecting of items in a list. The TaskListModule will hold a
reference to an instance of SelectionModule, and listen for notification
events on it. First, let's see the source code for SelectionModule:
Contents of selection_module.js
var SelectionModule = Module.Base.extend({
prototype: {
options: {
selectedClass: "selected"
},
addItem: function(item) {
item.setAttribute("data-actions", this.controllerId + ".toggle");
this.element.querySelector("ol").appendChild(item);
},
getSelectedCount: function() {
return this.element.querySelectorAll("ol>li." + this.options.selectedClass).length;
},
toggle: function click(event, element, params) {
element.classList.toggle(this.options.selectedClass);
this.notify("item.selectionSizeChanged");
}
}
});
Our SelectionModule has three main responsibilities:
- Add a new item to the list
- Return the number of selected items
- Toggle the selection on a single item
The toggle method is the sole Module Action, responding to a click event. In
this method we call this.notify(...), which sends the Notification Event. Now
we will change TaskListModule so it has a reference to a SelectionModule.
Changes to task_list_module.js
var TaskListModule = Module.Base.extend({
prototype: {
selection: null,
_ready: function() {
Module.Base.prototype._ready.call(this);
this.selection.listen("item.selectionSizeChanged", this, "handleItemSelectionSizeChanged");
},
destructor: function() {
if (this.selection) {
this.selection.ignore("item.selectionSizeChanged", this);
this.selection.destructor();
this.selection = null;
}
},
handleItemSelectionSizeChanged: function(publisher, data) {
this.element.querySelector(".selection-count").innerHTML =
this.selection.getSelectedCount();
},
add: function submit(event, element, params) {
...
if (/^\s*$/.test(taskName)) {
...
}
else {
var item = this.document.createElement("li");
item.innerHTML = taskName;
this.selection.addItem(item);
...
}
...
}
}
});
There is one small change to the add(...) method that is worth noting. When we
add a new item to the list, we call this.selection.addItem(...) — the
SelectionModule is responsible for actually attaching the new task item to the
document.
selection property will be created by Foundry, and an
instance of SelectionModule will be injected for us. Later
on in this tutorial, we'll see how the data-module-property
HTML5 attribute allows Foundry to do this. For now, just know that
Foundry will give TaskListModule a new instance of
SelectionModule with no additional work on our end.
The _ready method calls listen(...) on the selection property. Our
TaskListModule is listening for the same notification that SelectionModule
sends in the toggle method.
The destructor method is invoked by Foundry when a module is no longer needed,
which is where we stop listening for notifications on the SelectionModule and
destroy it.
The onControllerRegistered and onControllerUnregistered methods are required
by Foundry in order to register the SelectionModule for DOM events.
Lastly, handleItemSelectionSizeChanged is the event handler for the
Notification Event sent by the SelectionModule. It has the usual publisher
and data arguments, except this time we know that publisher and
this.selection are the same object. In this event handler, the
TaskListModule just updates a visible count of the number of selected items.
Let's look at how the HTML structure for the TaskListModule has changed to
support the SelectionModule:
<form method="GET" action="#"
data-modules="TaskListModule"
data-module-options='{
"controllerId": "tasks",
"defaultModule": true
}'
data-actions="tasks.add"
>
<h2>Tasks</h2>
<p>
<label>Task: <input type="text" name="taskName"></label>
<button type="submit">Add Task</button>
</p>
<div data-module-property="selection"
data-modules="SelectionModule"
data-module-options='{"controllerId": "tasks-selection"}'>
<ol></ol>
<p>
Selected: <span class="selection-count">0</span>
</p>
</div>
</form>
The main difference is the addition of a new <div> tag:
<div data-module-property="selection"
data-modules="SelectionModule"
data-module-options='{"controllerId": "tasks-selection"}'>
...
</div>
This is where Foundry does a little heavy lifting for us. When creating the
TaskListModule, Foundry looks for any element inside the root element for
TaskListModule that has an HTML5 attribute called data-module-property. This
causes Foundry to create a new SelectionModule and inject it into the new
instance of TaskListModule as the selection property.
This gives you the basic idea of how to use Notification Events. A module holds
a reference to another module, and calls listen(...) on it. The other module
calls notify(...) to send the Notification Event.
Demo: Events In Foundry
A Quick Recap
We learned how two modules that do not have knowledge about one another can
communicate via Application Events. The TaskListModule publishes an
Application Event called task.added. The RecentTasksModule subscribes to the
task.added event and adds the new task to its own list.
We also found out how two modules can communicate directly with one another via
Notification Events. The TaskListModule holds a reference to SelectionModule
using the this.selection property. In the _ready method, TaskListModule
calls this.selection.listen(...). Then in SelectionModule, the toggle
method calls this.notify(...) to send the Notification Event, causing
TaskListModule to respond.
Up Next: Dependency Injection With Foundry
So far we've been hard coding JavaScript class names in the data-modules
HTML5 attributes. Behind the scenes, Foundry is using Dependency Injection to
wire together the framework. Learn how to leverage Dependency Injection and
Inversion of Control when creating your own modules.