LP1829567 Angular access key improvements
[working/Evergreen.git] / Open-ILS / src / eg2 / src / app / share / accesskey / accesskey.service.ts
1 import {Injectable, EventEmitter, HostListener} from '@angular/core';
2
3 export interface AccessKeyAssignment {
4     key: string;      // keyboard command
5     desc: string;     // human-friendly description
6     ctx: string;      // template context
7     action: Function; // handler function
8     shadowed?: boolean; // Has this assignemnt been shadowed by another.
9 }
10
11 @Injectable()
12 export class AccessKeyService {
13
14     // Assignments stored as an array with most recently assigned
15     // items toward the front.  Most recent items have precedence.
16     assignments: AccessKeyAssignment[] = [];
17
18     constructor() {}
19
20     assign(assn: AccessKeyAssignment): void {
21         const list: AccessKeyAssignment[] = [];
22
23         // Avoid duplicate assignments for the same context.
24         // Most recent assignment always wins.
25         this.assignments.forEach(a => {
26             if (a.key === assn.key) {
27                 if (a.ctx === assn.ctx) { 
28                     // If key and context match, keep only the most recent.
29                     return; 
30                 } else {
31                     // An assignment within a different context shadows
32                     // an existing assignment.  Keep the assignment
33                     // but mark it as shadowed.
34                     a.shadowed = true;
35                 }
36             }
37             list.unshift(a);
38         });
39         list.unshift(assn);
40
41         this.assignments = list;
42     }
43
44     /**
45      * Compress a set of single-fire keyboard events into single
46      * string.  For example:  Control and 't' becomes 'ctrl+t'.
47      */
48     compressKeys(evt: KeyboardEvent): string {
49         if (!evt.key) {
50             return null;
51         }
52         let s = '';
53         if (evt.ctrlKey || evt.metaKey) { s += 'ctrl+'; }
54         if (evt.altKey) { s += 'alt+'; }
55         if (evt.shiftKey) { s += 'shift+'; }
56         s += evt.key.toLowerCase();
57
58         return s;
59     }
60
61     /**
62      * Checks for a key assignment and fires the assigned action.
63      */
64     fire(evt: KeyboardEvent): void {
65         const keySpec = this.compressKeys(evt);
66         for (const i in this.assignments) { // for-loop to exit early
67             if (keySpec === this.assignments[i].key) {
68                 const assign = this.assignments[i];
69                 console.debug(`AccessKey assignment found for ${assign.key}`);
70                 // Allow the current digest cycle to complete before
71                 // firing the access key action.
72                 setTimeout(assign.action, 0);
73                 evt.preventDefault();
74                 return;
75             }
76         }
77     }
78
79     /**
80      * Returns a simplified key assignment list containing just
81      * the key spec and the description.  Useful for inspecting
82      * without exposing the actions.
83      */
84     infoIze(): any[] {
85         return this.assignments.map(a => {
86             return {key: a.key, desc: a.desc, ctx: a.ctx};
87         });
88     }
89
90 }
91