Keylistener Component
Bobo Häggström, Software Architect, Easit AB, Sweden
November 19, 2007
The Problem
ZK is a great framework for writing web applications with a rich interface. In fact I have talked to many people that have tried our application and they don't realize that it is actually web and not a real windows application. But one problem I stumbled across was handling keyboard shortcuts. Power-users want to be able to navigate through the most common tasks in the application without using the mouse. ZK has some ways to handle this, for example most components works with keyboard input. But when it comes to setting up shortcuts for actions in the application (like CTRL+S for saving, or CTRL+N for new) you are forced to use the ctrlKeys attribute and the onCtrlKey event on Window. In the application I'm writing it is not neccessary to have a Window as a base for the GUI, and because of a highly configurable GUI we couldn't force the developers use Window. And the fact that Window need the focus to be inside that Window for triggering keyboard events didn't fit our needs.
The Solution
So how did I solve this? I extracted some of the code from Window (and au.js) and created a small new component that I call Keylistener. Basicly this component listens to keyboard commands and triggers an event based on them. I decided to keep the format of keyboard shortcuts from Window and use the same attribute name and events. The component is very easy to use
The ZUL way:
<keylistener ctrlKeys="^s" onCtrlKey="alert("CTRL+S pressed!");"/>
The Java way:
Keylistener keyListener = new Keylistener();
keyListener.setParent(parent);
keyListener.setCtrlKeys("^s");
keyListener.addEventListener(Events.ON_CTRL_KEY, new EventListener() {
public void onEvent(Event event){
// CTRL+S Pressed!!
}
});
Thats it! When you are done with it you can always detach it like any other component.
One problem that I ran into was that if a user presses for example CTRL+S while standing in a textbox, the value of this textbox will not be synchronized with the server until the textbox looses focus (if you don't have an onChanging event listener hooked to it), meaning that this value won't be updated at the server when your onCtrlKey event is triggered. This problem was solved by letting the keylistener automaticly move focus to itself before triggering the event to the server. If this behaviour is not wanted, you can set autoBlur="false" on the keylistener.
The keylistener will hook global key listener for the document, so be carefull when choosing control keys so you don't override already defined control keys or hook keys used by other components.
Under the hood
The keylistener component consists of three files
- Keylistener.java
- Keylistener.dsp
- Keylistener.js
Keylistener.java is the Java implementation. It basicly has two setters/getters (for ctrlKeys and autoBlur), and some code for parsing the ctrlKeys value (same code as used in Window).
Keylistener.dsp is the template for the client side html, which basicly is a div with some attributes.
Keylistener.js is the javascript responsible for hooking and monitoring the keyboard presses. This is a very simple component built with some parts from the Window component and some ctrl key parsing code from au.js The keylistener is one class on the client side called zk.KeyListener. It has two important methods called keyDown and inCtrlKeys. keyDown is called whenever a key is pressed, it will then use the inCtrlKeys method to see if the control keys registered match the keys pressed, if that is the case it will trigger an event.
When the keylistener object is created and initialized (zk.KeyListener.init) at the client it will register a callback (zk.KeyListener.keyDown) for keydown events on document using zk.listen method.
init: function () {
var el = $e(this.id);
if (!el) return;
this.element = el;
var meta = this;
this.fnOnKeyDown = function (evt) {
meta.keyDown(evt);
};
zk.listen(document, "keydown", this.fnOnKeyDown);
}
When a key is pressed the callback will be triggered and check what keys have been pressed and match these against the ctrlKeys registered by the user (zk.KeyListener.inCtrlKeys). If a match is found, the component will check for autoblur, if true focus will be set to the keylistener component.
keyDown: function(evt) {
if (!evt) evt = window.event;
var keycode = evt.keyCode, zkcode; //zkcode used to search z.ctkeys
switch (keycode) {
...
}
// If keyboard command is registered for this component, send request
if(this.inCtrlKeys(evt, zkcode, getZKAttr(this.comp, "ctkeys")) ) {
// If autoblur is specified, set focus to keylistener to
// trigger onBlur for focused component
if(getZKAttr(this.comp, "autoblur")=="true"){
this.comp.focus();
}
// Send request
...
Then a request is created holding all data (what keys have been pressed) and then it's sent to the component on the server side using zkau.send.
...
var req = {uuid: this.comp.id, cmd: "onCtrlKey",
ctl: true, data: [keycode, evt.ctrlKey, evt.shiftKey, evt.altKey]};
// Do not send request directly, otherwise onChange events won't be fired correctly in IE
setTimeout(function () {
zkau.send(req, 38);
}, 10);
Event.stop(evt);
return false;
}
return true;
}
When a component is detached the cleanup method will be called. Only clean up we need to do is unregister the keydown listener.
cleanup: function () {
zk.unlisten(document, "keydown", this.fnOnKeyDown);
}
Bobo Häggström is a Software Architect for a Swedish company called Easit. Easit develops web based applications for business processes such as ITIL service desk management, asset management and project management. The applications are built with ZK, Java, Hibernate, Spring and Maven.
Copyright © Bobo Häggström. This article is licensed under GNU Free Documentation License. |