UNPKG

8.54 kBJavaScriptView Raw
1
2import './../../constants/keycodes';
3//import {BunnyElement} from "../../BunnyElement";
4
5/**
6 * Adds event listener to element and stores a function in this element's custom property
7 * and returns unique ID which can be used to remove event listener later
8 * even anonymous functions, component methods, functions with arguments
9 *
10 * Simple example:
11 *
12 * const Component = {
13 * docBodyClickEventId: null,
14 * anonymousEventId: null,
15 *
16 * init(param1, param2) {
17 * this.docBodyClickEventId = addEvent(document.body, 'click', this.bodyClicked.bind(this, param1, param2));
18 *
19 * this.anonymousEventId = addEvent(document.body, 'click', e => {
20 * console.log(e)
21 * });
22 * },
23 *
24 * destroy() {
25 * this.docBodyClickEventId = removeEvent(document.body, 'click', this.docBodyClickEventId);
26 *
27 * this.anonymousEventId = removeEvent(document.body, 'click', this.anonymousEventId)'
28 * },
29 *
30 * bodyClicked(param1, param2) {
31 * console.log(this.internalAction(param1, param2));
32 * },
33 *
34 * internalAction(param1, param2) {
35 * return param1 + param2;
36 * }
37 * }
38 *
39 * @param {HTMLElement} element
40 * @param {String} eventName
41 * @param {Function} eventListener
42 *
43 * @returns {Number}
44 */
45export function addEvent(element, eventName, eventListener) {
46 if (element.__bunny_event_handlers === undefined) {
47 element.__bunny_event_handlers = {
48 handlers: {},
49 counter: 0
50 }
51 }
52 element.__bunny_event_handlers.handlers[element.__bunny_event_handlers.counter] = eventListener;
53 element.addEventListener(
54 eventName,
55 element.__bunny_event_handlers.handlers[element.__bunny_event_handlers.counter]
56 );
57 element.__bunny_event_handlers.counter++;
58 return element.__bunny_event_handlers.counter - 1;
59}
60
61/**
62 * Remove event listener
63 *
64 * @param {HTMLElement} element
65 * @param {String} eventName
66 * @param {Number} eventIndex
67 *
68 * @returns {null}
69 */
70export function removeEvent(element, eventName, eventIndex) {
71 if (element.__bunny_event_handlers !== undefined &&
72 element.__bunny_event_handlers.handlers[eventIndex] !== undefined
73 ) {
74 element.removeEventListener(eventName, element.__bunny_event_handlers.handlers[eventIndex]);
75 delete element.__bunny_event_handlers.handlers[eventIndex];
76 // do not decrement counter, each new event handler should have next unique index
77 }
78 return null;
79}
80
81/**
82 * Call event listener only once after "delay" ms
83 * Useful for scroll, keydown and other events
84 * when the actions must be done only once
85 * when user stopped typing or scrolling for example
86 *
87 * @param {HTMLElement} element
88 * @param {String} eventName
89 * @param {Function} eventListener
90 * @param {Number} delay
91 * @returns {Number}
92 */
93export function addEventOnce(element, eventName, eventListener, delay = 500) {
94 let timeout = 0;
95 return addEvent(element, eventName, (e) => {
96 clearTimeout(timeout);
97 timeout = setTimeout(() => {
98 eventListener(e);
99 }, delay)
100 });
101}
102
103export function isEventCursorInside(e, element) {
104 const bounds = element.getBoundingClientRect();
105 return (e.clientX > bounds.left && e.clientX < bounds.right
106 && e.clientY > bounds.top && e.clientY < bounds.bottom
107 );
108}
109
110export function onClickOutside(element, callback) {
111 if (document.__bunny_core_outside_callbacks === undefined) {
112 document.__bunny_core_outside_callbacks = [];
113 }
114 const handler = (event) => {
115 const target = event.target;
116 const bTargetExists = document.contains(target) !== false;
117 const bTargetIsElOrChild = event.target === element || element.contains(event.target);
118 if (bTargetExists && !bTargetIsElOrChild) {
119 callback(event);
120 }
121 };
122
123 if (element.__bunny_core_outside_callbacks === undefined) {
124 element.__bunny_core_outside_callbacks = [];
125 }
126 element.__bunny_core_outside_callbacks.push(handler);
127 document.__bunny_core_outside_callbacks.push(handler);
128
129 if (document.__bunny_core_outside_handler === undefined) {
130 document.__bunny_core_outside_handler = (event) => {
131 document.__bunny_core_outside_callbacks.forEach(callback => {
132 callback(event);
133 })
134 };
135 document.addEventListener('click', document.__bunny_core_outside_handler);
136 document.addEventListener('touchstart', document.__bunny_core_outside_handler);
137 }
138
139 return handler;
140}
141
142export function removeClickOutside(element, callback) {
143 if (document.__bunny_core_outside_callbacks !== undefined) {
144 const index = document.__bunny_core_outside_callbacks.indexOf(callback);
145 if (index !== -1) {
146 document.__bunny_core_outside_callbacks.splice(index, 1);
147 if (document.__bunny_core_outside_callbacks.length === 0) {
148 document.removeEventListener('click', document.__bunny_core_outside_handler);
149 document.removeEventListener('touchstart', document.__bunny_core_outside_handler);
150 delete document.__bunny_core_outside_handler;
151 }
152 }
153 }
154
155 if (element.__bunny_core_outside_callbacks !== undefined) {
156 const index = element.__bunny_core_outside_callbacks.indexOf(callback);
157 if (index !== -1) {
158 element.__bunny_core_outside_callbacks.splice(index, 1);
159 }
160 }
161}
162
163/**
164 * Adds up, down, esc, enter keypress event on 'element' to traverse though 'items'
165 *
166 * @param {HTMLElement} element
167 * @param {HTMLCollection|NodeList} items
168 * @param {function} itemSelectCallback
169 * callback(null) if Enter was pressed and no item was selected (for example custom value entered)
170 * callback(false) if Esc was pressed (canceled)
171 * callback({HTMLElement} item) - selected item on Enter
172 * @param {function} itemSwitchCallback = null
173 * callback({HTMLElement} item) - new item on arrow up/down
174 *
175 * @returns {function(*)}
176 */
177export function addEventKeyNavigation(element, items, itemSelectCallback, itemSwitchCallback = null) {
178
179 let currentItemIndex = null;
180 for (let k = 0; k < items.length; k++) {
181 if (items[k].hasAttribute('aria-selected')) {
182 currentItemIndex = k;
183 break;
184 }
185 }
186
187 /*let currentActiveItems = [];
188 for (let k = 0; k < items.length; k++) {
189 if (items[k].classList.contains(activeClass)) {
190 currentActiveItems.push(items[k]);
191 }
192 }*/
193
194 const _itemAdd = () => {
195 items[currentItemIndex].focus();
196 //items[currentItemIndex].classList.add(activeClass);
197 //items[currentItemIndex].setAttribute('aria-selected', 'true');
198 /*if (!BunnyElement.isInViewport(items[currentItemIndex])) {
199 BunnyElement.scrollTo(items[currentItemIndex], 400, -200);
200 }*/
201 //items[currentItemIndex].scrollIntoView(false);
202 if (itemSwitchCallback !== null) {
203 itemSwitchCallback(items[currentItemIndex]);
204 }
205 };
206
207 const _itemRemove = () => {
208 //items[currentItemIndex].classList.remove(activeClass);
209 //items[currentItemIndex].removeAttribute('aria-selected');
210 };
211
212 const handler = (e) => {
213 const c = e.keyCode;
214
215 const maxItemIndex = items.length - 1;
216
217 if (c === KEY_ENTER || c === KEY_SPACE) {
218 e.preventDefault();
219 if (currentItemIndex !== null) {
220 items[currentItemIndex].click();
221 itemSelectCallback(items[currentItemIndex]);
222 } else {
223 itemSelectCallback(null);
224 }
225
226 } else if (c === KEY_ESCAPE) {
227 e.preventDefault();
228 /*for (let k = 0; k < items.length; k++) {
229 if (currentActiveItems.indexOf(items[k]) === -1) {
230 // remove active state
231 items[k].classList.remove(activeClass);
232 items[k].removeAttribute('aria-selected');
233 } else {
234 // set active state
235 items[k].classList.add(activeClass);
236 items[k].setAttribute('aria-selected', 'true');
237 }
238 }*/
239 itemSelectCallback(false);
240
241 } else if (c === KEY_ARROW_UP || c === KEY_ARROW_LEFT) {
242 e.preventDefault();
243 if (currentItemIndex !== null && currentItemIndex > 0) {
244 _itemRemove();
245 currentItemIndex -= 1;
246 _itemAdd();
247 }
248
249 } else if (c === KEY_ARROW_DOWN || c === KEY_ARROW_RIGHT) {
250 e.preventDefault();
251 if (currentItemIndex === null) {
252 currentItemIndex = 0;
253 _itemAdd();
254 } else if (currentItemIndex < maxItemIndex) {
255 _itemRemove();
256 currentItemIndex += 1;
257 _itemAdd();
258 }
259 }
260 };
261
262 if (items.length > 0) {
263 element.addEventListener('keydown', handler);
264 }
265
266 return handler;
267}
268
269export function removeEventKeyNavigation(element, handler) {
270 element.removeEventListener('keydown', handler);
271}