1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | import platform from '../platform.js';
|
19 |
|
20 | const 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 | };
|
65 | window.addEventListener('DOMContentLoaded', () => util._onDOMContentLoaded(), false);
|
66 |
|
67 | const 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 |
|
115 | class DeviceBackButtonDispatcher {
|
116 | constructor() {
|
117 | this._isEnabled = false;
|
118 | this._boundCallback = this._callback.bind(this);
|
119 | }
|
120 |
|
121 |
|
122 | |
123 |
|
124 |
|
125 | enable() {
|
126 | if (!this._isEnabled) {
|
127 | util.addBackButtonListener(this._boundCallback);
|
128 | this._isEnabled = true;
|
129 | }
|
130 | }
|
131 |
|
132 | |
133 |
|
134 |
|
135 | disable() {
|
136 | if (this._isEnabled) {
|
137 | util.removeBackButtonListener(this._boundCallback);
|
138 | this._isEnabled = false;
|
139 | }
|
140 | }
|
141 |
|
142 | |
143 |
|
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 |
|
157 |
|
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 |
|
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 |
|
277 |
|
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 |
|
311 | export default new DeviceBackButtonDispatcher();
|