File : contextMenus/baseContextMenu/BaseContextMenuOperator.js

1
/*
2
Copyright - 2017 2023 - wwwouaiebe - Contact: https://www.ouaie.be/
3
4
This  program is free software;
5
you can redistribute it and/or modify it under the terms of the
6
GNU General Public License as published by the Free Software Foundation;
7
either version 3 of the License, or any later version.
8
9
This program is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
GNU General Public License for more details.
13
14
You should have received a copy of the GNU General Public License
15
along with this program; if not, write to the Free Software
16
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
*/
18
/*
19
Changes:
20
    - v4.0.0:
21
        - created from v3.6.0
22
Doc reviewed 202208
23
 */
24
25
import theConfig from '../../data/Config.js';
26
import ContextMenuKeyboardKeydownEL from './ContextMenuKeyboardKeydownEL.js';
27
import ContextMenuCancelButtonClickEL from './ContextMenuCancelButtonClickEL.js';
28
import ContextMenuTouchEL from './ContextMenuTouchEL.js';
29
import MenuItemMouseLeaveEL from './MenuItemMouseLeaveEL.js';
30
import MenuItemMouseEnterEL from './MenuItemMouseEnterEL.js';
31
import MenuItemClickEL from './MenuItemClickEL.js';
32
import ContextMenuMouseLeaveEL from './ContextMenuMouseLeaveEL.js';
33
import ContextMenuMouseEnterEL from './ContextMenuMouseEnterEL.js';
34
35
import { NOT_FOUND, ZERO, ONE } from '../../main/Constants.js';
36
37
/* ------------------------------------------------------------------------------------------------------------------------- */
38
/**
39
This class perform all the needed operations for context menus
40
*/
41
/* ------------------------------------------------------------------------------------------------------------------------- */
42
43
class BaseContextMenuOperator {
44
45
    /**
46
    Enum for Item changes from the keyboard
47
    @type {Object}
48
    */
49
50
    static #keyboardItemChange = Object.freeze (
51
        {
52
            // eslint-disable-next-line no-magic-numbers
53
            get previousItem ( ) { return -1; },
54
55
            // eslint-disable-next-line no-magic-numbers
56
            get firstItem ( ) { return 0; },
57
58
            // eslint-disable-next-line no-magic-numbers
59
            get nextItem ( ) { return 1; },
60
61
            // eslint-disable-next-line no-magic-numbers
62
            get lastItem ( ) { return 2; }
63
        }
64
    );
65
66
    /**
67
    A reference to the context menu
68
    @type {BaseContextMenu}
69
    */
70
71
    #contextMenu = null;
72
73
    /**
74
    Keyboard keydown event listener
75
    @type {ContextMenuKeyboardKeydownEL}
76
    */
77
78
    #contextMenuKeyboardKeydownEL;
79
80
    /**
81
    Mouseleave context menu event listener
82
    @type {ContextMenuMouseLeaveEL}
83
    */
84
85
    #contextMenuMouseLeaveEL;
86
87
    /**
88
    mouseenter context menu event listener
89
    @type {ContextMenuMouseEnterEL}
90
    */
91
92
    #contextMenuMouseEnterEL;
93
94
    /**
95
    Click cancel button event listener
96
    @type {ContextMenuCancelButtonClickEL}
97
    */
98
99
    #contextMenuCancelButtonClickEL;
100
101
    /**
102
    Touch event listener for the context menus
103
    @type {ContextMenuTouchEL}
104
    */
105
106
    #contextMenuTouchEL;
107
108
    /**
109
    click menu item event listener
110
    @type {MenuItemClickEL}
111
    */
112
113
    #menuItemClickEL;
114
115
    /**
116
    Mouseleave menu item event listener
117
    @type {MenuItemMouseLeaveEL}
118
    */
119
120
    #menuItemMouseLeaveEL;
121
122
    /**
123
    Mouse enter menu item event listener
124
    @type {MenuItemMouseEnterEL}
125
    */
126
127
    #menuItemMouseEnterEL;
128
129
    /**
130
    The index of the selected by the keyboard menuItem
131
    @type {Number}
132
    */
133
134
    #keyboardSelectedItemObjId = NOT_FOUND;
135
136
    /**
137
    TimerId for the mouseleave context menu action
138
    @type {Number}
139
    */
140
141
    #timerId = null;
142
143
    /**
144
    Remove the css class on all items
145
    */
146
147
    #unselectItems ( ) {
148
        this.#contextMenu.menuItemHTMLElements.forEach (
149
            menuitemHTMLElement => { menuitemHTMLElement.classList.remove ( 'TravelNotes-ContextMenu-MenuItemSelected' ); }
150
        );
151
    }
152
153
    /**
154
    Selected item change by the keyboard
155
    @param {Number} changeValue A value indicating witch menuItem have to be selected
156
    See BaseContextMenuOperator.#keyboardItemChange
157
    */
158
159
    #changeKeyboardSelectedItemObjId ( changeValue ) {
160
161
        this.#unselectItems ( );
162
163
        // change the selected item
164
        switch ( changeValue ) {
165
        case BaseContextMenuOperator.#keyboardItemChange.previousItem :
166
        case BaseContextMenuOperator.#keyboardItemChange.nextItem :
167
            this.#keyboardSelectedItemObjId += changeValue;
168
            if ( NOT_FOUND === this.#keyboardSelectedItemObjId ) {
169
                this.#keyboardSelectedItemObjId = this.#contextMenu.menuItemHTMLElements.length - ONE;
170
            }
171
            if ( this.#contextMenu.menuItemHTMLElements.length === this.#keyboardSelectedItemObjId ) {
172
                this.#keyboardSelectedItemObjId = ZERO;
173
            }
174
            break;
175
        case BaseContextMenuOperator.#keyboardItemChange.firstItem :
176
            this.#keyboardSelectedItemObjId = ZERO;
177
            break;
178
        case BaseContextMenuOperator.#keyboardItemChange.lastItem :
179
            this.#keyboardSelectedItemObjId = this.#contextMenu.menuItemHTMLElements.length - ONE;
180
            break;
181
        default :
182
            break;
183
        }
184
185
        // add css class
186
        this.#contextMenu.menuItemHTMLElements [ this.#keyboardSelectedItemObjId ]
187
            .classList.add ( 'TravelNotes-ContextMenu-MenuItemSelected' );
188
    }
189
190
    /**
191
    constructor
192
    @param {BaseContextMenu} contextMenu The ContextMenu for witch the operator is made
193
    */
194
195
    constructor ( contextMenu ) {
196
197
        Object.freeze ( this );
198
199
        // saving the reference to the menu
200
        this.#contextMenu = contextMenu;
201
202
        // Event listeners creation
203
        this.#contextMenuKeyboardKeydownEL = new ContextMenuKeyboardKeydownEL ( this );
204
        this.#contextMenuMouseLeaveEL = new ContextMenuMouseLeaveEL ( this );
205
        this.#contextMenuMouseEnterEL = new ContextMenuMouseEnterEL ( this );
206
        this.#contextMenuCancelButtonClickEL = new ContextMenuCancelButtonClickEL ( this );
207
        this.#contextMenuTouchEL = new ContextMenuTouchEL ( this );
208
        this.#menuItemClickEL = new MenuItemClickEL ( this );
209
        this.#menuItemMouseLeaveEL = new MenuItemMouseLeaveEL ( this );
210
        this.#menuItemMouseEnterEL = new MenuItemMouseEnterEL ( this );
211
212
        // Adding event listeners to the html elements of the menu and to the document
213
        document.addEventListener ( 'keydown', this.#contextMenuKeyboardKeydownEL, true );
214
        this.#contextMenu.contextMenuHTMLElement.addEventListener ( 'mouseleave', this.#contextMenuMouseLeaveEL );
215
        this.#contextMenu.contextMenuHTMLElement.addEventListener ( 'mouseenter', this.#contextMenuMouseEnterEL );
216
        this.#contextMenu.contextMenuHTMLElement.addEventListener ( 'touchstart', this.#contextMenuTouchEL );
217
        this.#contextMenu.contextMenuHTMLElement.addEventListener ( 'touchend', this.#contextMenuTouchEL );
218
        this.#contextMenu.contextMenuHTMLElement.addEventListener ( 'touchcancel', this.#contextMenuTouchEL );
219
        this.#contextMenu.cancelButton.addEventListener ( 'click', this.#contextMenuCancelButtonClickEL );
220
        this.#contextMenu.menuItemHTMLElements.forEach (
221
            menuItemHTMLElement => {
222
                menuItemHTMLElement.addEventListener ( 'click', this.#menuItemClickEL );
223
                menuItemHTMLElement.addEventListener ( 'mouseleave', this.#menuItemMouseLeaveEL );
224
                menuItemHTMLElement.addEventListener ( 'mouseenter', this.#menuItemMouseEnterEL );
225
            }
226
        );
227
    }
228
229
    /**
230
    Remove event listeners, set event listeners to null so all references to this are removed
231
    and remove the menu from the screen
232
    */
233
234
    destructor ( ) {
235
236
        // Cleaning the timer
237
        if ( this.#timerId ) {
238
            clearTimeout ( this.#timerId );
239
            this.#timerId = null;
240
        }
241
242
        // Removing event listeners
243
        document.removeEventListener ( 'keydown', this.#contextMenuKeyboardKeydownEL, true );
244
        this.#contextMenu.contextMenuHTMLElement.removeEventListener (
245
            'mouseleave',
246
            this.#contextMenuMouseLeaveEL
247
        );
248
        this.#contextMenu.contextMenuHTMLElement.removeEventListener (
249
            'mouseenter',
250
            this.#contextMenuMouseEnterEL
251
        );
252
        this.#contextMenu.contextMenuHTMLElement.removeEventListener ( 'touchstart', this.#contextMenuTouchEL );
253
        this.#contextMenu.contextMenuHTMLElement.removeEventListener ( 'touchend', this.#contextMenuTouchEL );
254
        this.#contextMenu.contextMenuHTMLElement.removeEventListener ( 'touchcancel', this.#contextMenuTouchEL );
255
        this.#contextMenu.cancelButton.removeEventListener (
256
            'click',
257
            this.#contextMenuCancelButtonClickEL
258
        );
259
        this.#contextMenu.menuItemHTMLElements.forEach (
260
            menuItemHTMLElement => {
261
                menuItemHTMLElement.removeEventListener ( 'click', this.#menuItemClickEL );
262
                menuItemHTMLElement.removeEventListener ( 'mouseleave', this.#menuItemMouseLeaveEL );
263
                menuItemHTMLElement.removeEventListener ( 'mouseenter', this.#menuItemMouseEnterEL );
264
            }
265
        );
266
267
        this.#contextMenuKeyboardKeydownEL = null;
268
        this.#contextMenuMouseLeaveEL = null;
269
        this.#contextMenuMouseEnterEL = null;
270
        this.#contextMenuCancelButtonClickEL = null;
271
        this.#contextMenuTouchEL = null;
272
        this.#menuItemClickEL = null;
273
        this.#menuItemMouseLeaveEL = null;
274
        this.#menuItemMouseEnterEL = null;
275
276
        // removing the html elements
277
        document.body.removeChild ( this.#contextMenu.contextMenuHTMLElement );
278
279
        // cleaning the reference to the menu
280
        this.#contextMenu = null;
281
    }
282
283
    /**
284
    Mouse leave context menu action
285
    */
286
287
    onMouseLeaveContainer ( ) {
288
        this.#timerId = setTimeout ( ( ) => this.onCancelMenu ( ), theConfig.contextMenu.timeout );
289
    }
290
291
    /**
292
    Mouse enter context menu action
293
    */
294
295
    onMouseEnterContainer ( ) {
296
        if ( this.#timerId ) {
297
            clearTimeout ( this.#timerId );
298
            this.#timerId = null;
299
        }
300
    }
301
302
    /**
303
    Keydown on the keyboard action
304
    @param {String} key The pressed keyboard key
305
    */
306
307
    onKeydownKeyboard ( key ) {
308
        switch ( key ) {
309
        case 'Escape' :
310
        case 'Esc' :
311
            this.onCancelMenu ( );
312
            break;
313
        case 'ArrowDown' :
314
        case 'ArrowRight' :
315
        case 'Tab' :
316
            this.#changeKeyboardSelectedItemObjId ( BaseContextMenuOperator.#keyboardItemChange.nextItem );
317
            break;
318
        case 'ArrowUp' :
319
        case 'ArrowLeft' :
320
            this.#changeKeyboardSelectedItemObjId ( BaseContextMenuOperator.#keyboardItemChange.previousItem );
321
            break;
322
        case 'Home' :
323
            this.#changeKeyboardSelectedItemObjId ( BaseContextMenuOperator.#keyboardItemChange.firstItem );
324
            break;
325
        case 'End' :
326
            this.#changeKeyboardSelectedItemObjId ( BaseContextMenuOperator.#keyboardItemChange.lastItem );
327
            break;
328
        case 'Enter' :
329
            if (
330
                ( NOT_FOUND === this.#keyboardSelectedItemObjId )
331
                ||
332
                ( this.#contextMenu.menuItemHTMLElements [ this.#keyboardSelectedItemObjId ]
333
                    .classList.contains ( 'TravelNotes-ContextMenu-ItemDisabled' )
334
                )
335
            ) {
336
                return;
337
            }
338
            this.#contextMenu.onOk ( this.#keyboardSelectedItemObjId );
339
            break;
340
        default :
341
            break;
342
        }
343
    }
344
345
    /**
346
    Menu cancellation action
347
    */
348
349
    onCancelMenu ( ) {
350
        this.#contextMenu.onCancel ( 'Canceled by user' );
351
    }
352
353
    /**
354
    Select item action
355
    @param {Number} itemObjId The id of the selected item
356
    */
357
358
    selectItem ( itemObjId ) {
359
        if (
360
            this.#contextMenu.menuItemHTMLElements [ itemObjId ]
361
                .classList.contains ( 'TravelNotes-ContextMenu-ItemDisabled' )
362
        ) {
363
            return;
364
        }
365
        this.#contextMenu.onOk ( itemObjId );
366
    }
367
368
    /**
369
    Mouse leave item action
370
    @param {HTMLElement} menuItem The targeted item
371
    */
372
373
    onMouseLeaveMenuItem ( menuItem ) {
374
        menuItem.classList.remove ( 'TravelNotes-ContextMenu-MenuItemSelected' );
375
    }
376
377
    /**
378
    Mouse enter item action
379
    @param {HTMLElement} menuItem The targeted item
380
    */
381
382
    onMouseEnterMenuItem ( menuItem ) {
383
        this.#unselectItems ( );
384
        this.#keyboardSelectedItemObjId = Number.parseInt ( menuItem.dataset.tanObjId );
385
        menuItem.classList.add ( 'TravelNotes-ContextMenu-MenuItemSelected' );
386
    }
387
}
388
389
export default BaseContextMenuOperator;
390
391
/* --- End of file --------------------------------------------------------------------------------------------------------- */
392