Thirteenth Parallel /archive/sig-slots/

Advanced JavaScript events with
Signals and Slots

By Alex Russell, July 2002

Interface toolkits as we know them today have emerged with a set of reliable, well known end-user interface metaphors out the chaos of early experimentation. As end-user interaction with such toolkits has become largely standardized, so have the ways in which developers interact with the APIs that create them. An important evolution in the implementation of UI APIs has been the maturing of event handling mechanisms.

User interfaces are generally driven by actions generated by end users. In addition to providing the user with interface-specific feedback, GUIs are responsible for “informing” other parts of a program about actions performed by the user. To do this, interface toolkits model these actions through the concept of “events”, discrete actions which can be acted upon. Desipte the clear demarkation of responsibility in modern toolkits, the language of “events” extends past GUIs. In their purely theoretical form, an event is simply an “action” that can be recognized, possibly causing other actions to be performed. It is this broader notion of an “event” with which this article will concern itself, as it provides a way of describing many thorny issues in component design and communication.

Inside programs, events are handled in one of three ways: direct action, callbacks, and abstracted event passing mechanisms. The direct action method can be thought of as hard-coding a function call to be executed directly into the code for the interface widget. This approach is blisteringly fast, but provides no portability or re-usability. Going this one better are systems of callbacks. A component may provide a mechanism for “registering” a function pointer to be called when a particular action is performed. When an event occurs, a list of callbacks is traversed by the “direct action” code within the component, calling each function in turn, “calling back” to the function that is pointed to. Drawbacks to this system include a potential lack of consistency with callback registration mechanism naming. Callbacks also place the burden of tracking “listeners” to the calling component, increasing the amount of time (and space) needed to implement callback interfaces. When implemented well, callbacks can provide all the flexibility needed to support highly complex event systems, yet this requires uncanny discipline not normally found in developers.

Abstracted event passing mechanisms address the deficiencies of callbacks by providing a single infrastructure through which an event can be modeled, irrespective of what is “listening” and what is “emitting” events. Distributed message passing mechanisms such as MQSeries, or even SMTP daemons often qualify as abstracted event mechanisms. Such mechanisms are distinguished from callback mechanisms in that the caller and the callee never need know about interfaces being exposed by each other; a third party handles all references between them. Developers often make the mistake of classifying well-implemented callback systems in this category, but the distinction is not insignificant: callbacks do not provide a means of event passing that is truly anonymous.

One flexible abstracted event system is known as Signals and Slots. This article will present an overview of a JavaScript implementation of this type of system and will then explore how it can make complex component communication simpler.

The terms “signals” and “slots” are somewhat confusing to begin with. A “slot” can be thought of as a listener for an event, known as a “signal”. Those familiar with the Unix system programming convention of handling signals will find the definition of a signal here to be similar, but not strictly analogous. There can be many listeners for a given signal, and unlike JavaScript DOM events, a developer can define new events (“signals”) to be listened for (“connected to”). A unique property of this model is that a function that is a slot may also be a signal, allowing “chaining” of events. The Signals and Slots model is implemented using a single “connector” object or service which is informed when a signal is “emitted” (called). It then references an internal lookup table to determine what (if any) slots are listening on the signal, calling them in turn.

Signals and Slots (“sigslot”) mechanisms work best in languages such as C++ and Java where it is possible to intercept function calls or use a preprocessor to perform transformations that help make sigslot syntactically palatable. The desired condition is for developers not to have to be cognizant that a signal is being emitted. Alas, JavaScript provides neither functors (malleable function objects), nor the possibility of a workable preprocessor. Developers must therefore use slightly more syntax to emit signals in JavaScript than is needed in other languages, but hopefully the benefits will outweigh this small wart.

Functions in the night

The simplest example of signals and slots “connects” two functions that are ignorant of the other. No data structures specific to either function is needed to keep track of the association, the sigslot object handles these details.

function func1(arg){
  alert(arg);
}
function func2(arg){
  window.status = arg;
}
__sig__.connect(null, func1, null, func2);
// the nulls indicate object placeholders.
// since the functions aren't object methods,
// we set these placeholders to null.
__sig__.emit(func1, "foo");

This example will set both the window’s status bar and the contents of an alert box to “foo”. When “emit()” is called, the __sig__ object calls func1, checks to see if it is a signal (if so it is also emitted), and calls slots connected to func1 with the same arguments as were passed to emit() by its optional arguments. The emit method will only cause an error if too few arguments are passed to satisfy the number of explicit arguments specified by both the signal and slot functions. The use of emit() to call functions will likely seem very odd at first, but it is only necessary for invocations of a function or method when one wishes to emit that function’s signal.

Since most web developers think of events only in the context of DOM events, it may be worthwhile to consider the case when one wishes to tie a DOM mouse click event to a signal which can be connected to other listeners:

<html>
<head>
<script>
// define a dummy signal that can be connected to
function click_sig(){ return true; }
function foo(input){
  // act upon input somehow
}

function bar(input){
  // calculate a derived value
}

__sig__.connect(null, click_sig, null, foo);
__sig__.connect(null, click_sig, null, bar);
</script>
</head>
<body onclick="__sig__.emit(click_sig, 'data');">
</body>
</html>

It is also simple to tie the emission of our signal to any number of callback handlers represented by the DOM event model. Note that one can map a more abstracted event system onto a simpler one, as the second is simply a specific case of the first.

“But I LIKE callbacks!”

It may not have escaped notice of the reader that sigslot in essence creates an abstract callback mechanism, one with slightly more smarts than the average event propagation system, but a callback system none the less. What separates sigslot from other devices that can accomplish similar things is the ease with which it turns hard problems about component communication into approachable issues with clear solutions. In short, sigslot lets one easily turn a mess of stringy code into a set of true software components with minimal effort.

As illustration, consider a DHTML layout manager. Graphical toolkits of every sort have included layout managers (of varying success) for quite some time. They provide ways of creating visual relationships between discrete interface components and managing those relationships when parameters change. Sigslot makes the concept of a DHTML layout manager approachable, but for the sake of brevity, only one useful component of a true sigslot based layout system will be examined: a split viewpane. The implementation is available for the reader to examine, but only its use will be explored. The split viewpane is an indispensable tool in many GUI toolkits and allows developers to provide information in layouts that are both fluid yet maleable. Split pane widgets have been implemented before in DHTML (and done very well) yet creating a true reusable drop-and-go component out of this tool has not happened (to the best of the author’s knowledge).

A sample page is provided to illustrate the use of this component, as is the split pane script itself.

The factory function for the viewpane returns an object of the class “split_pane_class”, this object contains two member objects (pane1obj and pane2obj) that model the individual content areas. The DOM nodes the panes correspond to are exposed as: myPane.pane1obj.domNode, etc. Of interest in this article are the ways in which a pane informs its contents that is is being resized.

Each pane_class object (a data member of the split_pane_class object) is connected by it’s resize() function to the resizePane1 and resizePane2 methods of the split_pane_class object. Since this connection is made via our signals and slots mechanism, we can easily turn around and leverage it to inform children of the pane_class object that it is being resized. The example composites two frames; one vertical and one horizontal.

var sp_one = pane_generator(500, 500);
container.appendChild(sp_one.domNode);

// vertical pane
var sp_two = pane_generator(250, 500, true);

//composite our panes
sp_one.pane2obj.domNode.appendChild(sp_two.domNode);

// now connect the panes' resizing operations
__sig__.connect(
    sp_one.pane2obj,
    sp_one.pane2obj.resize,
    sp_two,
    sp_two.resizeContainer
);

The resizeContainer() method of the split_pane_class handles resizing for the entire pane (not simply resizing it’s constituent sub-frames). sp_two.resizeContainer is connected to the resizing operations of the sp_one object. This arrangement ensures that sp_two.resizeContainer() will be fired whenever the second pane of the parent pane is resized. This chaining of resize notification could be continued ad infinitum should the developer wish.

Conclusion

Hopefully this brief and somewhat rambling introduction to the use of signals and slots in JavaScript will be of use to the reader. Signals and Slots is neither the fastest nor the simplest event mechanism around, but when one wishes to create true reusable components, it can be an invaluable tool. The increasing complexity of DHTML applications has begun to require more advanced tools. Advanced event handling mechanisms can aid in developing these new applications, and Signals and Slots appears to be a viable tool within the context of web clients. If you have questions, comments, or feedback about this article, please don’t hesitate to contact the author (alex@netWindows.org).

About the Author

Alex Russell is a web application and infrastructure developer for SecurePipe, Inc. Alex is the tech lead for the netWindows DHTML API project. His weblog is at alex.netWindows.org and he can be reached via email at alex@netWindows.org.