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