UNPKG

7.44 kBJavaScriptView Raw
1/*
2Copyright 2013-2015 ASIAL CORPORATION
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15
16*/
17
18import platform from '../platform.js';
19
20const util = {
21 _ready: false,
22
23 _domContentLoaded: false,
24
25 _onDOMContentLoaded: () => {
26 util._domContentLoaded = true;
27
28 if (platform.isWebView()) {
29 window.document.addEventListener('deviceready', () => {
30 util._ready = true;
31 }, false);
32 } else {
33 util._ready = true;
34 }
35 },
36
37 addBackButtonListener: function(fn) {
38 if (!this._domContentLoaded) {
39 throw new Error('This method is available after DOMContentLoaded');
40 }
41
42 if (this._ready) {
43 window.document.addEventListener('backbutton', fn, false);
44 } else {
45 window.document.addEventListener('deviceready', function() {
46 window.document.addEventListener('backbutton', fn, false);
47 });
48 }
49 },
50
51 removeBackButtonListener: function(fn) {
52 if (!this._domContentLoaded) {
53 throw new Error('This method is available after DOMContentLoaded');
54 }
55
56 if (this._ready) {
57 window.document.removeEventListener('backbutton', fn, false);
58 } else {
59 window.document.addEventListener('deviceready', function() {
60 window.document.removeEventListener('backbutton', fn, false);
61 });
62 }
63 }
64};
65window.addEventListener('DOMContentLoaded', () => util._onDOMContentLoaded(), false);
66
67const HandlerRepository = {
68 _store: {},
69
70 _genId: (() => {
71 let i = 0;
72 return () => i++;
73 })(),
74
75 set: function(element, handler) {
76 if (element.dataset.deviceBackButtonHandlerId) {
77 this.remove(element);
78 }
79 const id = element.dataset.deviceBackButtonHandlerId = HandlerRepository._genId();
80 this._store[id] = handler;
81 },
82
83 remove: function(element) {
84 if (element.dataset.deviceBackButtonHandlerId) {
85 delete this._store[element.dataset.deviceBackButtonHandlerId];
86 delete element.dataset.deviceBackButtonHandlerId;
87 }
88 },
89
90 get: function(element) {
91 if (!element.dataset.deviceBackButtonHandlerId) {
92 return undefined;
93 }
94
95 const id = element.dataset.deviceBackButtonHandlerId;
96
97 if (!this._store[id]) {
98 throw new Error();
99 }
100
101 return this._store[id];
102 },
103
104 has: function(element) {
105 if (!element.dataset) {
106 return false;
107 }
108
109 const id = element.dataset.deviceBackButtonHandlerId;
110
111 return !!this._store[id];
112 }
113};
114
115class DeviceBackButtonDispatcher {
116 constructor() {
117 this._isEnabled = false;
118 this._boundCallback = this._callback.bind(this);
119 }
120
121
122 /**
123 * Enable to handle 'backbutton' events.
124 */
125 enable() {
126 if (!this._isEnabled) {
127 util.addBackButtonListener(this._boundCallback);
128 this._isEnabled = true;
129 }
130 }
131
132 /**
133 * Disable to handle 'backbutton' events.
134 */
135 disable() {
136 if (this._isEnabled) {
137 util.removeBackButtonListener(this._boundCallback);
138 this._isEnabled = false;
139 }
140 }
141
142 /**
143 * Fire a 'backbutton' event manually.
144 */
145 fireDeviceBackButtonEvent() {
146 const event = document.createEvent('Event');
147 event.initEvent('backbutton', true, true);
148 document.dispatchEvent(event);
149 }
150
151 _callback() {
152 this._dispatchDeviceBackButtonEvent();
153 }
154
155 /**
156 * @param {HTMLElement} element
157 * @param {Function} callback
158 */
159 createHandler(element, callback) {
160 if (!(element instanceof HTMLElement)) {
161 throw new Error('element must be an instance of HTMLElement');
162 }
163
164 if (!(callback instanceof Function)) {
165 throw new Error('callback must be an instance of Function');
166 }
167
168 const handler = {
169 _callback: callback,
170 _element: element,
171
172 disable: function() {
173 HandlerRepository.remove(element);
174 },
175
176 setListener: function(callback) {
177 this._callback = callback;
178 },
179
180 enable: function() {
181 HandlerRepository.set(element, this);
182 },
183
184 isEnabled: function() {
185 return HandlerRepository.get(element) === this;
186 },
187
188 destroy: function() {
189 HandlerRepository.remove(element);
190 this._callback = this._element = null;
191 }
192 };
193
194 handler.enable();
195
196 return handler;
197 }
198
199 _dispatchDeviceBackButtonEvent() {
200 const tree = this._captureTree();
201
202 const element = this._findHandlerLeafElement(tree);
203
204 let handler = HandlerRepository.get(element);
205 handler._callback(createEvent(element));
206
207 function createEvent(element) {
208 return {
209 _element: element,
210 callParentHandler: function() {
211 let parent = this._element.parentNode;
212
213 while (parent) {
214 handler = HandlerRepository.get(parent);
215 if (handler) {
216 return handler._callback(createEvent(parent));
217 }
218 parent = parent.parentNode;
219 }
220 }
221 };
222 }
223 }
224
225 /**
226 * @return {Object}
227 */
228 _captureTree() {
229 return createTree(document.body);
230
231 function createTree(element) {
232 const tree = {
233 element: element,
234 children: Array.prototype.concat.apply([], arrayOf(element.children).map(function(childElement) {
235
236 if (childElement.style.display === 'none' || childElement._isShown === false) {
237 return [];
238 }
239
240 if (childElement.children.length === 0 && !HandlerRepository.has(childElement)) {
241 return [];
242 }
243
244 const result = createTree(childElement);
245
246 if (result.children.length === 0 && !HandlerRepository.has(result.element)) {
247 return [];
248 }
249
250 return [result];
251 }))
252 };
253
254 if (!HandlerRepository.has(tree.element)) {
255 for (let i = 0; i < tree.children.length; i++){
256 const subTree = tree.children[i];
257 if (HandlerRepository.has(subTree.element)) {
258 return subTree;
259 }
260 }
261 }
262
263 return tree;
264 }
265
266 function arrayOf(target) {
267 const result = [];
268 for (let i = 0; i < target.length; i++) {
269 result.push(target[i]);
270 }
271 return result;
272 }
273 }
274
275 /**
276 * @param {Object} tree
277 * @return {HTMLElement}
278 */
279 _findHandlerLeafElement(tree) {
280 return find(tree);
281
282 function find(node) {
283 if (node.children.length === 0) {
284 return node.element;
285 }
286
287 if (node.children.length === 1) {
288 return find(node.children[0]);
289 }
290
291 return node.children.map(function(childNode) {
292 return childNode.element;
293 }).reduce(function(left, right) {
294 if (!left) {
295 return right;
296 }
297
298 const leftZ = parseInt(window.getComputedStyle(left, '').zIndex, 10);
299 const rightZ = parseInt(window.getComputedStyle(right, '').zIndex, 10);
300
301 if (!isNaN(leftZ) && !isNaN(rightZ)) {
302 return leftZ > rightZ ? left : right;
303 }
304
305 throw new Error('Capturing backbutton-handler is failure.');
306 }, null);
307 }
308 }
309}
310
311export default new DeviceBackButtonDispatcher();