HTML5 has an eventing mechanism that works for HTML elements. But it cannot be used to add events to arbitrary objects, such as controls and collections.
Wijmo's Event Class: Define Events by Declaring as Fields
Because of this, Wijmo defines an Event class. Wijmo events are similar to .NET events. Any class may define events by declaring them as fields. Any class may subscribe to events using the event's addHandler method and unsubscribe using the removeHandler method. All Wijmo event handlers take two parameters: sender and args. The first is the object that raised the event, and the second is an object that contains the event parameters. Classes that define events follow the .NET pattern where for every event there is an on[EVENTNAME] method that raises the event. This pattern allows derived classes to override the on[EVENTNAME] method and handle the event before and/or after the base class raises the event. Derived classes may even suppress the event by not calling the base class implementation. For example, the code below shows how you can attach a handler to the FlexGrid's formatItem event (a Wijmo event) to customize the way grid cells are rendered:
// handling a Wijmo event
flex.formatItem.addHandler(function(s, e) {
if (e.col == 0) {
e.cell.style.fontWeight = 'bold';
}
});
The addHandler call specifies the event handler. In this case, the handler gets called whenever the grid has prepared a cell for display. The "s" parameter is the event sender, in this case the flex control. The "e" parameter contains the event arguments, in this case an object of type FormatItemEventArgs which contains detailed information about the cell and a reference to the cell element itself.
Complement Wijmo with HTML Events: Capturing and Bubbling
Wijmo events do not replace HTML events, they are a complement. For example, the Control class does not define events for pointer or keyboard actions. You should use the native HTML events for that. The main differences between Wijmo and HTML events are:
- Wijmo events can be defined by any object, while HTML events only apply to HTML elements.
- Wijmo events have addHandler and removeHandler methods, while HTML events use addEventListener and removeEventListener.
- Wijmo events notify listeners once, while most HTML events have "capturing" and "bubbling" phases.
The "capturing" and "bubbling" phases are one of the nicest things about HTML events. The events do not fire only once, instead the traverse the DOM hierarchy from the top element, "capturing" on the way down to the "target" element, and then "bubbling" back up the hierarchy. This allows handlers attached to parent elements to get notifications about events that were triggered by child elements. The diagram below illustrates this process: When the user clicks the innermost element (the "target"), the "click" event fires six times:
- Capturing: elements 1, 2, and 3.
- Bubbling: elements 3, 2, and 1.
Any of these event handlers may call the preventDefault method to indicate that they have handled the event and it should be ignored by other handlers, or they may call the stopPropagation method to stop the event from firing further, so it won't even be seen by other handlers. The last parameter in the addEventListener method specifies whether you want to handle the event in the "capturing" or "bubbling" phases. For example:
element1.addEventListener('click', handler, true); // element1, capturing phase
element2.addEventListener('click', handler, false); // element2, bubble phase
Handlers may stop this traversal process by calling the event's stopPropagation method from the event handler. This is convenient when you're dealing with containers that have many child elements. For example, if you have a container with hundreds of child elements, and you want to do something when the user clicks one of the child elements, you can subscribe to the "click" event on the parent element only, and check the event's "target" property to determine which child was clicked. This technique is called "event delegation", and is more convenient and efficient than adding handlers for each child element and removing the handlers when child elements are removed from the parent. For more details, see David Walsh's blog.
Monitor Keyboard Events and Character Length
The next example shows how you'd handle an HTML event to monitor the keyboard and prevent users from entering any values longer than three characters:
// handling an HTML event
flex.hostElement.addEventListener('keypress', function (e) {
// check that we editing
var edt = flex.activeEditor;
if (edt) {
// limit the text to three characters
if (edt.selectionStart == edt.selectionEnd && edt.value.length >= 3) {
e.preventDefault();
}
}
});
Notice how the event is not attached to the control, but to its hostElement, which is a regular HTML element. Notice also that the event handler in this case takes a single parameter "e", which contains the event arguments. The event handler starts by checking whether the grid is currently in edit mode. If it is, the cell editor has the focus and was actually the element that triggered the event. But the keypress event bubbles, so the host element also gets the event. If the editor contains already contains three characters, the handler calls preventDefault on the event parameter to cancel the effect of the key press.
Detect When Controls Lose Focus
The ability to capture events also comes in handy in a few situations. For example, most browsers support 'blur' and 'focusout' events. These are similar, except 'focusout' bubbles and 'blur' does not. So if you have a control composed of several elements that may receive focus, and want to detect when the control (and all its parts) lost the focus, you could do this:
var self = this;
self.hostElement.addEventListener('focusout', function () {
setTimeout(function () {
if (!self.containsFocus()) {
// LOST THE FOCUS
}
}, 100);
});
Because 'focusout' bubbles, the handler will be called when the control's hostElement or any of its children loses the focus. The test for 'containsFocus' handles the situation when a child loses focus to another child. This is great, but it does not work in FireFox, because surprisingly FireFox does not support the 'focusout' event. The alternative is to use the 'blur' event instead, but since it doesn't bubble, we have to handle it in the capture phase:
var self = this;
self.hostElement.addEventListener('blur', function () {
setTimeout(function () {
if (!self.containsFocus()) {
// LOST THE FOCUS
}
}, 100);
}, true); // CAPTURE
This version works on all browsers. The 'blur' event does not bubble, but it can still be captured on the way down from the parent.
Wijmo Support for HTML events
If you use the addEventListener to subscribe to HTML events, it is important to disconnect the listeners when the listener object is destroyed, or there will be memory leaks. This is especially important in Single Page Applications, where user interface elements such as controls are created and disposed many times within the same session/page. JavaScript has a removeEventListener method that takes care of removing the event listeners, but there are some caveats in using this method. For example, you cannot remove an event listener if the event handler is not an actual function:
myElement.addEventListener('click', function(e) {
// can't remove this event listener!
});
Also, if you use the bind method to ensure the event handler function has a meaningful "this" object to work with, you have to use the same bound instance when removing the event listener:
// can't remove this one either
myElement.addEventListener('click ', this.eventHander.bind(this));
You also have to remember if the event listener was attached to the bubble or capture phases, and which element it was attached to. If you call removeEventListener with the wrong parameters, you won't get any errors or warnings, but the event won't be properly disconnected, the listener object won't be garbage-collected, and there will be memory leaks. To simplify all this, and ensure all event handlers are properly disconnected, Wijmo's Control class includes these utility functions:
Control.addEventListener(element, eventName, eventHandler, capture);
Control.removeEventListener(element, eventName, eventHandler, capture);
These functions can be used much like the regular functions of the same name, but with three important differences:
- The Control.addEventListener method keeps a list of all the listeners attached to the control,
- The Control.removeEventListener method allows you to omit all parameters, in which case they will match whatever handlers were declared, and
- The Control.destroy method calls Control.removeEventListener to remove all event listeners added with Control.addEventListener.
This simple mechanism ensures all event listeners are properly removed when controls are destroyed. The result is safer and much shorter code. We strongly encourage you to use these methods when authoring your own Wijmo controls.