xref: /dokuwiki/lib/scripts/hotkeys.js (revision 0636144287f7acf89c633d3d2f5214e1d229e033)
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+alt';
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     * Secondly, initialization registers listeners on document to catch all
31     * keyboard events.
32     *
33     * @author Marek Sacha <sachamar@fel.cvut.cz>
34     */
35    this.initialize = function() {
36        var t = this;
37        /**
38         * Lookup all anchors with accesskey and register event - go to anchor
39         * target.
40         */
41        var anchors = document.getElementsByTagName("a");
42        t.each(anchors, function(a) {
43            if (a.accessKey != "") {
44                t.addShortcut(t.modifier + '+' + a.accessKey, function() {
45                    a.click();
46                });
47            }
48        });
49
50        /**
51         * Lookup all input [type="submit"] with accesskey and register event -
52         * perform "click" on a button.
53         */
54        var inputs = document.getElementsByTagName("input");
55        t.each(inputs, function(i) {
56            if (i.type == "submit" && i.accessKey != "") {
57                t.addShortcut(t.modifier + '+' + i.accessKey, function() {
58                    i.click();
59                });
60            }
61        });
62
63        /**
64         * Lookup all buttons with accesskey and register event -
65         * perform "click" on a button.
66         */
67        var buttons = document.getElementsByTagName("button");
68        t.each(buttons, function(b) {
69            if (b.accessKey != "") {
70                t.addShortcut(t.modifier + '+' + b.accessKey, function() {
71                    b.click();
72                });
73            }
74        });
75
76        /**
77         * Register listeners on document to catch keyboard events.
78         */
79
80        addEvent(document,'keyup',function (e) {
81            return t.onkeyup.call(t,e);
82        });
83
84        addEvent(document,'keypress',function (e) {
85            return t.onkeypress.call(t,e);
86        });
87
88        addEvent(document,'keydown',function (e) {
89            return t.onkeydown.call(t,e);
90        });
91    };
92
93    /**
94     * Keyup processing function
95     * Function returns true if keyboard event has registered handler, and
96     * executes the handler function.
97     *
98     * @param e KeyboardEvent
99     * @author Marek Sacha <sachamar@fel.cvut.cz>
100     * @return b boolean
101     */
102    this.onkeyup = function(e) {
103        var t = this;
104        var v = t.findShortcut(e);
105        if (v != null && v != false) {
106            v.func.call(t);
107            return false;
108        }
109        return true;
110    };
111
112    /**
113     * Keydown processing function
114     * Function returns true if keyboard event has registered handler
115     *
116     * @param e KeyboardEvent
117     * @author Marek Sacha <sachamar@fel.cvut.cz>
118     * @return b boolean
119     */
120    this.onkeydown = function(e) {
121        var t = this;
122        var v = t.findShortcut(e);
123        if (v != null && v != false) {
124            return false;
125        }
126        return true;
127    };
128
129    /**
130     * Keypress processing function
131     * Function returns true if keyboard event has registered handler
132     *
133     * @param e KeyboardEvent
134     * @author Marek Sacha <sachamar@fel.cvut.cz>
135     * @return b
136     */
137    this.onkeypress = function(e) {
138        var t = this;
139        var v = t.findShortcut(e);
140        if (v != null && v != false) {
141            return false;
142        }
143        return true;
144    };
145
146    /**
147     * Register new shortcut
148     *
149     * This function registers new shortcuts, each shortcut is defined by its
150     * modifier keys and a key (with + as delimiter). If shortcut is pressed
151     * cmd_function is performed.
152     *
153     * For example:
154     *  pa = "ctrl+alt+p";
155     *  pa = "shift+alt+s";
156     *
157     * Full example of method usage:
158     *  hotkeys.addShortcut('ctrl+s',function() {
159     *      document.getElementByID('form_1').submit();
160     *  });
161     *
162     * @param pa String description of the shortcut (ctrl+a, ctrl+shift+p, .. )
163     * @param cmd_func Function to be called if shortcut is pressed
164     * @author Marek Sacha <sachamar@fel.cvut.cz>
165     */
166    this.addShortcut = function(pa, cmd_func) {
167        var t = this;
168
169        var o = {
170            func : cmd_func,
171            alt : false,
172            ctrl : false,
173            shift : false
174        };
175
176        t.each(t.explode(pa, '+'), function(v) {
177            switch (v) {
178                case 'alt':
179                case 'ctrl':
180                case 'shift':
181                    o[v] = true;
182                    break;
183
184                default:
185                    o.charCode = v.charCodeAt(0);
186                    o.keyCode = v.toUpperCase().charCodeAt(0);
187            }
188        });
189
190        t.shortcuts.push((o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode,  o);
191
192        return true;
193    };
194
195    /**
196     * @property isMac
197     */
198    this.isMac = (navigator.userAgent.indexOf('Mac') != -1);
199
200    /**
201     * Apply function cb on each element of o in the namespace of s
202     * @param o Array of objects
203     * @param cb Function to be called on each object
204     * @param s Namespace to be used during call of cb (default namespace is o)
205     * @author Marek Sacha <sachamar@fel.cvut.cz>
206     */
207    this.each = function(o, cb, s) {
208        var n, l;
209
210        if (!o)
211            return 0;
212
213        s = s || o;
214
215        if (o.length !== undefined) {
216            // Indexed arrays, needed for Safari
217            for (n=0, l = o.length; n < l; n++) {
218                if (cb.call(s, o[n], n, o) === false)
219                    return 0;
220            }
221        } else {
222            // Hashtables
223            for (n in o) {
224                if (o.hasOwnProperty(n)) {
225                    if (cb.call(s, o[n], n, o) === false)
226                        return 0;
227                }
228            }
229        }
230
231        return 1;
232    };
233
234    /**
235     * Explode string according to delimiter
236     * @param s String
237     * @param d Delimiter (default ',')
238     * @author Marek Sacha <sachamar@fel.cvut.cz>
239     * @return a Array of tokens
240     */
241    this.explode = function(s, d) {
242        return  s.split(d || ',');
243    };
244
245    /**
246     * Find if the shortcut was registered
247     *
248     * @param e KeyboardEvent
249     * @author Marek Sacha <sachamar@fel.cvut.cz>
250     * @return v Shortcut structure or null if not found
251     */
252    this.findShortcut = function (e) {
253        var t = this;
254        var v = null;
255
256        /* No modifier key used - shortcut does not exist */
257        if (!e.altKey && !e.ctrlKey && !e.metaKey) {
258            return v;
259        }
260
261        t.each(t.shortcuts, function(o) {
262            if (t.isMac && o.ctrl != e.metaKey)
263                return;
264            else if (!t.isMac && o.ctrl != e.ctrlKey)
265                return;
266
267            if (o.alt != e.altKey)
268                return;
269
270            if (o.shift != e.shiftKey)
271                return;
272
273            if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
274                v = o;
275                return;
276            }
277        });
278        return v;
279    };
280}
281
282/**
283 * Init function for hotkeys. Called from js.php, to ensure hotkyes are initialized after toolbar.
284 * Call of addInitEvent(initializeHotkeys) is unnecessary now.
285 *
286 * @author Marek Sacha <sachamar@fel.cvut.cz>
287 */
288function initializeHotkeys() {
289    var hotkeys = new Hotkeys();
290    hotkeys.initialize();
291}
292