xref: /dokuwiki/lib/scripts/hotkeys.js (revision 7ea8e5925d4d2e18975712870d48912761ea1503)
1/**
2 * Some of these scripts were taken from TinyMCE (http://tinymce.moxiecode.com/) and were modified for DokuWiki
3 *
4 * Class handles accesskeys using javascript and also provides ability
5 * to register and use other hotkeys as well.
6 *
7 * @author Marek Sacha <sachamar@fel.cvut.cz>
8 */
9function Hotkeys() {
10
11    this.shortcuts = new Array();
12
13    /**
14     * Set modifier keys, for instance:
15     *  this.modifier = 'ctrl';
16     *  this.modifier = 'ctrl+shift';
17     *  this.modifier = 'ctrl+alt+shift';
18     *  this.modifier = 'alt';
19     *  this.modifier = 'alt+shift';
20     */
21    this.modifier = 'ctrl';
22
23    /**
24     * Initialization
25     *
26     * This function looks up all the accesskeys used in the current page
27     * (at anchor elements and input elements [type="submit"]) and registers
28     * appropriate shortcuts.
29     *
30     * @author Marek Sacha <sachamar@fel.cvut.cz>
31     */
32    this.initialize = function() {
33        var t = this;
34        /**
35         * Lookup all anchors with accesskey and register event - go to anchor
36         * target.
37         */
38        var anchors = document.getElementsByTagName("a");
39        t.each(anchors, function(a) {
40            if (a.accessKey != "") {
41                t.addShortcut(t.modifier + '+' + a.accessKey, function() {
42                    window.location.href = a.href;
43                });
44            }
45        });
46
47        /**
48         * Lookup all input [type="submit"] with accesskey and register event -
49         * perform "click" on a button.
50         */
51        var inputs = document.getElementsByTagName("input");
52        t.each(inputs, function(i) {
53            if (i.type == "submit") {
54                t.addShortcut(t.modifier + '+' + i.accessKey, function() {
55                    i.click();
56                });
57            }
58        });
59    };
60
61    /**
62     * Keyup processing function
63     * Function returns true if keyboard event has registered handler, and
64     * executes the handler function.
65     *
66     * @param e KeyboardEvent
67     * @author Marek Sacha <sachamar@fel.cvut.cz>
68     * @return b boolean
69     */
70    this.onkeyup = function(e) {
71        var t = this;
72        var v = t.findShortcut(e);
73        if (v != null && v != false) {
74            v.func.call(t);
75            return false;
76        }
77        return true;
78    };
79
80    /**
81     * Keydown processing function
82     * Function returns true if keyboard event has registered handler
83     *
84     * @param e KeyboardEvent
85     * @author Marek Sacha <sachamar@fel.cvut.cz>
86     * @return b boolean
87     */
88    this.onkeydown = function(e) {
89        var t = this;
90        var v = t.findShortcut(e);
91        if (v != null && v != false) {
92            return false;
93        }
94        return true;
95    };
96
97    /**
98     * Keypress processing function
99     * Function returns true if keyboard event has registered handler
100     *
101     * @param e KeyboardEvent
102     * @author Marek Sacha <sachamar@fel.cvut.cz>
103     * @return b
104     */
105    this.onkeypress = function(e) {
106        var t = this;
107        var v = t.findShortcut(e);
108        if (v != null && v != false) {
109            return false;
110        }
111        return true;
112    };
113
114    /**
115     * Register new shortcut
116     *
117     * This function registers new shortcuts, each shortcut is defined by its
118     * modifier keys and a key (with + as delimiter). If shortcut is pressed
119     * cmd_function is performed.
120     *
121     * For example:
122     *  pa = "ctrl+alt+p";
123     *  pa = "shift+alt+s";
124     *
125     * Full example of method usage:
126     *  hotkeys.addShortcut('ctrl+s',function() {
127     *      document.getElementByID('form_1').submit();
128     *  });
129     *
130     * @param pa String description of the shortcut (ctrl+a, ctrl+shift+p, .. )
131     * @param cmd_func Function to be called if shortcut is pressed
132     * @author Marek Sacha <sachamar@fel.cvut.cz>
133     */
134    this.addShortcut = function(pa, cmd_func) {
135        var t = this;
136
137        var o = {
138            func : cmd_func,
139            alt : false,
140            ctrl : false,
141            shift : false
142        };
143
144        t.each(t.explode(pa, '+'), function(v) {
145            switch (v) {
146                case 'alt':
147                case 'ctrl':
148                case 'shift':
149                    o[v] = true;
150                    break;
151
152                default:
153                    o.charCode = v.charCodeAt(0);
154                    o.keyCode = v.toUpperCase().charCodeAt(0);
155            }
156        });
157
158        t.shortcuts.push((o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode,  o);
159
160        return true;
161    };
162
163    /**
164     * @property isMac
165     */
166    this.isMac = (navigator.userAgent.indexOf('Mac') != -1);
167
168    /**
169     * Apply function cb on each element of o in the namespace of s
170     * @param o Array of objects
171     * @param cb Function to be called on each object
172     * @param s Namespace to be used during call of cb (default namespace is o)
173     * @author Marek Sacha <sachamar@fel.cvut.cz>
174     */
175    this.each = function(o, cb, s) {
176        var n, l;
177
178        if (!o)
179            return 0;
180
181        s = s || o;
182
183        if (o.length !== undefined) {
184            // Indexed arrays, needed for Safari
185            for (n=0, l = o.length; n < l; n++) {
186                if (cb.call(s, o[n], n, o) === false)
187                    return 0;
188            }
189        } else {
190            // Hashtables
191            for (n in o) {
192                if (o.hasOwnProperty(n)) {
193                    if (cb.call(s, o[n], n, o) === false)
194                        return 0;
195                }
196            }
197        }
198
199        return 1;
200    };
201
202    /**
203     * Explode string according to delimiter
204     * @param s String
205     * @param d Delimiter (default ',')
206     * @author Marek Sacha <sachamar@fel.cvut.cz>
207     * @return a Array of tokens
208     */
209    this.explode = function(s, d) {
210        return  s.split(d || ',');
211    };
212
213    /**
214     * Find if the shortcut was registered
215     *
216     * @param e KeyboardEvent
217     * @author Marek Sacha <sachamar@fel.cvut.cz>
218     * @return v Shortcut structure or null if not found
219     */
220    this.findShortcut = function (e) {
221        var t = this;
222        var v = null;
223
224        /* No modifier key used - shortcut does not exist */
225        if (!e.altKey && !e.ctrlKey && !e.metaKey) {
226            return v;
227        }
228
229        t.each(t.shortcuts, function(o) {
230            if (t.isMac && o.ctrl != e.metaKey)
231                return;
232            else if (!t.isMac && o.ctrl != e.ctrlKey)
233                return;
234
235            if (o.alt != e.altKey)
236                return;
237
238            if (o.shift != e.shiftKey)
239                return;
240
241            if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
242                v = o;
243                return;
244            }
245        });
246        return v;
247    };
248}
249
250addInitEvent(function(){
251    var hotkeys = new Hotkeys();
252    hotkeys.initialize();
253
254    addEvent(document,'keyup',function (e) {
255        return hotkeys.onkeyup.call(hotkeys,e);
256    });
257
258    addEvent(document,'keypress',function (e) {
259        return hotkeys.onkeypress.call(hotkeys,e);
260    });
261
262    addEvent(document,'keydown',function (e) {
263        return hotkeys.onkeydown.call(hotkeys,e);
264    });
265});