1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | import onsElements from '../ons/elements.js';
|
19 | import util from '../ons/util.js';
|
20 | import internal from '../ons/internal/index.js';
|
21 | import autoStyle from '../ons/autostyle.js';
|
22 | import ModifierUtil from '../ons/internal/modifier-util.js';
|
23 | import BaseElement from './base/base-element.js';
|
24 | import deviceBackButtonDispatcher from '../ons/internal/device-back-button-dispatcher.js';
|
25 | import contentReady from '../ons/content-ready.js';
|
26 |
|
27 | import './ons-toolbar.js';
|
28 |
|
29 | const defaultClassName = 'page';
|
30 | const scheme = {
|
31 | '': 'page--*',
|
32 | '.page__content': 'page--*__content',
|
33 | '.page__background': 'page--*__background'
|
34 | };
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 | export default class PageElement extends BaseElement {
|
98 |
|
99 | |
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 | |
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 | |
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 | |
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 | |
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 | |
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 | constructor() {
|
148 | super();
|
149 |
|
150 | this._deriveHooks();
|
151 |
|
152 | this._defaultClassName = defaultClassName;
|
153 | this.classList.add(defaultClassName);
|
154 |
|
155 | this._initialized = false;
|
156 |
|
157 | contentReady(this, () => {
|
158 | this._compile();
|
159 |
|
160 | this._isShown = false;
|
161 | this._contentElement = this._getContentElement();
|
162 | this._backgroundElement = this._getBackgroundElement();
|
163 | });
|
164 | }
|
165 |
|
166 | _compile() {
|
167 | autoStyle.prepare(this);
|
168 |
|
169 | const toolbar = util.findChild(this, 'ons-toolbar');
|
170 |
|
171 | const background = util.findChild(this, '.page__background') || util.findChild(this, '.background') || document.createElement('div');
|
172 | background.classList.add('page__background');
|
173 | this.insertBefore(background, !toolbar && this.firstChild || toolbar && toolbar.nextSibling);
|
174 |
|
175 | const content = util.findChild(this, '.page__content') || util.findChild(this, '.content') || document.createElement('div');
|
176 | content.classList.add('page__content');
|
177 | if (!content.parentElement) {
|
178 | util.arrayFrom(this.childNodes).forEach(node => {
|
179 | if (node.nodeType !== 1 || this._elementShouldBeMoved(node)) {
|
180 | content.appendChild(node);
|
181 | }
|
182 | });
|
183 | }
|
184 |
|
185 | this._tryToFillStatusBar(content);
|
186 | this.insertBefore(content, background.nextSibling);
|
187 |
|
188 | if ((!toolbar || !util.hasModifier(toolbar, 'transparent'))
|
189 | && content.children.length === 1
|
190 | && util.isPageControl(content.children[0])
|
191 | ) {
|
192 | this._defaultClassName += ' page--wrapper';
|
193 | this.attributeChangedCallback('class');
|
194 | }
|
195 |
|
196 | const bottomToolbar = util.findChild(this, 'ons-bottom-toolbar');
|
197 | if (bottomToolbar) {
|
198 | this._defaultClassName += ' page-with-bottom-toolbar';
|
199 | this.attributeChangedCallback('class');
|
200 | }
|
201 |
|
202 | ModifierUtil.initModifier(this, scheme);
|
203 | }
|
204 |
|
205 | _elementShouldBeMoved(el) {
|
206 | if (el.classList.contains('page__background')) {
|
207 | return false;
|
208 | }
|
209 | const tagName = el.tagName.toLowerCase();
|
210 | if (tagName === 'ons-fab') {
|
211 | return !el.hasAttribute('position');
|
212 | }
|
213 | const fixedElements = ['script', 'ons-toolbar', 'ons-bottom-toolbar', 'ons-modal', 'ons-speed-dial', 'ons-dialog', 'ons-alert-dialog', 'ons-popover', 'ons-action-sheet'];
|
214 | return el.hasAttribute('inline') || fixedElements.indexOf(tagName) === -1;
|
215 | }
|
216 |
|
217 | _tryToFillStatusBar(content = this._contentElement) {
|
218 | internal.autoStatusBarFill(() => {
|
219 | util.toggleAttribute(this, 'status-bar-fill',
|
220 | !util.findParent(this, e => e.hasAttribute('status-bar-fill'))
|
221 | && (this._canAnimateToolbar(content) || !util.findChild(content, util.isPageControl))
|
222 | );
|
223 | });
|
224 | }
|
225 |
|
226 | _canAnimateToolbar(content = this._contentElement) {
|
227 | if (util.findChild(this, 'ons-toolbar')) {
|
228 | return true;
|
229 | }
|
230 |
|
231 | return !!util.findChild(content, el => {
|
232 | return util.match(el, 'ons-toolbar') && !el.hasAttribute('inline');
|
233 | });
|
234 | }
|
235 |
|
236 | connectedCallback() {
|
237 | if (!util.isAttached(this)) {
|
238 | return;
|
239 | }
|
240 |
|
241 | contentReady(this, () => {
|
242 | this._tryToFillStatusBar();
|
243 |
|
244 | if (this.hasAttribute('on-infinite-scroll')) {
|
245 | this.attributeChangedCallback('on-infinite-scroll', null, this.getAttribute('on-infinite-scroll'));
|
246 | }
|
247 |
|
248 | if (!this._initialized) {
|
249 | this._initialized = true;
|
250 |
|
251 | setImmediate(() => {
|
252 | this.onInit && this.onInit();
|
253 | util.triggerElementEvent(this, 'init');
|
254 | });
|
255 |
|
256 | if (!util.hasAnyComponentAsParent(this)) {
|
257 | setImmediate(() => this._show());
|
258 | }
|
259 | }
|
260 | });
|
261 | }
|
262 |
|
263 | updateBackButton(show) {
|
264 | if (this.backButton) {
|
265 | show ? this.backButton.show() : this.backButton.hide();
|
266 | }
|
267 | }
|
268 |
|
269 | set name(str) {
|
270 | this.setAttribute('name', str);
|
271 | }
|
272 |
|
273 | get name() {
|
274 | return this.getAttribute('name');
|
275 | }
|
276 |
|
277 | get backButton() {
|
278 | return this.querySelector('ons-back-button');
|
279 | }
|
280 |
|
281 | |
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 | set onInfiniteScroll(value) {
|
288 | if (value && !(value instanceof Function)) {
|
289 | util.throw('"onInfiniteScroll" must be function or null');
|
290 | }
|
291 |
|
292 | contentReady(this, () => {
|
293 | if (!value) {
|
294 | this._contentElement.removeEventListener('scroll', this._boundOnScroll);
|
295 | } else if (!this._onInfiniteScroll) {
|
296 | this._infiniteScrollLimit = 0.9;
|
297 | this._boundOnScroll = this._onScroll.bind(this);
|
298 | setImmediate(() => this._contentElement.addEventListener('scroll', this._boundOnScroll));
|
299 | }
|
300 | this._onInfiniteScroll = value;
|
301 | });
|
302 | }
|
303 |
|
304 | get onInfiniteScroll() {
|
305 | return this._onInfiniteScroll;
|
306 | }
|
307 |
|
308 | _onScroll() {
|
309 | const c = this._contentElement,
|
310 | overLimit = (c.scrollTop + c.clientHeight) / c.scrollHeight >= this._infiniteScrollLimit;
|
311 |
|
312 | if (this._onInfiniteScroll && !this._loadingContent && overLimit) {
|
313 | this._loadingContent = true;
|
314 | this._onInfiniteScroll(() => this._loadingContent = false);
|
315 | }
|
316 | }
|
317 |
|
318 | |
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 |
|
325 | get onDeviceBackButton() {
|
326 | return this._backButtonHandler;
|
327 | }
|
328 |
|
329 | set onDeviceBackButton(callback) {
|
330 | if (this._backButtonHandler) {
|
331 | this._backButtonHandler.destroy();
|
332 | }
|
333 |
|
334 | this._backButtonHandler = deviceBackButtonDispatcher.createHandler(this, callback);
|
335 | }
|
336 |
|
337 | get scrollTop() {
|
338 | return this._contentElement.scrollTop;
|
339 | }
|
340 |
|
341 | set scrollTop(newValue) {
|
342 | this._contentElement.scrollTop = newValue;
|
343 | }
|
344 |
|
345 | _getContentElement() {
|
346 | const result = util.findChild(this, '.page__content');
|
347 | if (result) {
|
348 | return result;
|
349 | }
|
350 | util.throw('Fail to get ".page__content" element');
|
351 | }
|
352 |
|
353 | _getBackgroundElement() {
|
354 | const result = util.findChild(this, '.page__background');
|
355 | if (result) {
|
356 | return result;
|
357 | }
|
358 | util.throw('Fail to get ".page__background" element');
|
359 | }
|
360 |
|
361 | _getBottomToolbarElement() {
|
362 | return util.findChild(this, 'ons-bottom-toolbar') || internal.nullElement;
|
363 | }
|
364 |
|
365 | _getToolbarElement() {
|
366 | return util.findChild(this, 'ons-toolbar') || document.createElement('ons-toolbar');
|
367 | }
|
368 |
|
369 | static get observedAttributes() {
|
370 | return ['modifier', 'on-infinite-scroll', 'class'];
|
371 | }
|
372 |
|
373 | attributeChangedCallback(name, last, current) {
|
374 | switch (name) {
|
375 | case 'class':
|
376 | util.restoreClass(this, this._defaultClassName, scheme);
|
377 | break;
|
378 | case 'modifier':
|
379 | ModifierUtil.onModifierChanged(last, current, this, scheme);
|
380 | break;
|
381 | case 'on-infinite-scroll':
|
382 | if (current === null) {
|
383 | this.onInfiniteScroll = null;
|
384 | } else {
|
385 | this.onInfiniteScroll = (done) => {
|
386 | const f = util.findFromPath(current);
|
387 | this.onInfiniteScroll = f;
|
388 | f(done);
|
389 | };
|
390 | }
|
391 | break;
|
392 | }
|
393 | }
|
394 |
|
395 | _show() {
|
396 | if (!this._isShown && util.isAttached(this)) {
|
397 | this._isShown = true;
|
398 | this.setAttribute('shown', '');
|
399 | this.onShow && this.onShow();
|
400 | util.triggerElementEvent(this, 'show');
|
401 | util.propagateAction(this, '_show');
|
402 | }
|
403 | }
|
404 |
|
405 | _hide() {
|
406 | if (this._isShown) {
|
407 | this._isShown = false;
|
408 | this.removeAttribute('shown');
|
409 | this.onHide && this.onHide();
|
410 | util.triggerElementEvent(this, 'hide');
|
411 | util.propagateAction(this, '_hide');
|
412 | }
|
413 | }
|
414 |
|
415 | _destroy() {
|
416 | this._hide();
|
417 |
|
418 | this.onDestroy && this.onDestroy();
|
419 | util.triggerElementEvent(this, 'destroy');
|
420 |
|
421 | if (this.onDeviceBackButton) {
|
422 | this.onDeviceBackButton.destroy();
|
423 | }
|
424 |
|
425 | util.propagateAction(this, '_destroy');
|
426 |
|
427 | this.remove();
|
428 | }
|
429 |
|
430 | _deriveHooks() {
|
431 | this.constructor.events.forEach(event => {
|
432 | const key = 'on' + event.charAt(0).toUpperCase() + event.slice(1);
|
433 | Object.defineProperty(this, key, {
|
434 | configurable: true,
|
435 | enumerable: true,
|
436 | get: () => this[`_${key}`],
|
437 | set: value => {
|
438 | if (!(value instanceof Function)) {
|
439 | util.throw(`"${key}" hook must be a function`);
|
440 | }
|
441 | this[`_${key}`] = value.bind(this);
|
442 | }
|
443 | });
|
444 | });
|
445 | }
|
446 |
|
447 | static get events() {
|
448 | return ['init', 'show', 'hide', 'destroy'];
|
449 | }
|
450 |
|
451 | |
452 |
|
453 |
|
454 |
|
455 |
|
456 |
|
457 |
|
458 | }
|
459 |
|
460 | onsElements.Page = PageElement;
|
461 | customElements.define('ons-page', PageElement);
|