UNPKG

11.3 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 onsElements from '../ons/elements.js';
19import util from '../ons/util.js';
20import autoStyle from '../ons/autostyle.js';
21import ModifierUtil from '../ons/internal/modifier-util.js';
22import BaseElement from './base/base-element.js';
23import TabbarElement from './ons-tabbar.js';
24import contentReady from '../ons/content-ready.js';
25import { PageLoader, defaultPageLoader } from '../ons/page-loader.js';
26
27const defaultClassName = 'tabbar__item';
28
29const scheme = {
30 '': 'tabbar--*__item',
31 '.tabbar__button': 'tabbar--*__button'
32};
33
34/**
35 * @element ons-tab
36 * @category tabbar
37 * @description
38 * [en]Represents a tab inside tab bar. Each `<ons-tab>` represents a page.[/en]
39 * [ja]
40 * タブバーに配置される各アイテムのコンポーネントです。それぞれのons-tabはページを表します。
41 * ons-tab要素の中には、タブに表示されるコンテンツを直接記述することが出来ます。
42 * [/ja]
43 * @codepen pGuDL
44 * @tutorial vanilla/Reference/tabbar
45 * @guide fundamentals.html#managing-pages
46 * [en]Managing multiple pages.[/en]
47 * [ja]複数のページを管理する[/ja]]
48 * @guide appsize.html#removing-icon-packs [en]Removing icon packs.[/en][ja][/ja]
49 * @guide faq.html#how-can-i-use-custom-icon-packs [en]Adding custom icon packs.[/en][ja][/ja]
50 * @seealso ons-tabbar
51 * [en]ons-tabbar component[/en]
52 * [ja]ons-tabbarコンポーネント[/ja]
53 * @seealso ons-page
54 * [en]ons-page component[/en]
55 * [ja]ons-pageコンポーネント[/ja]
56 * @seealso ons-icon
57 * [en]ons-icon component[/en]
58 * [ja]ons-iconコンポーネント[/ja]
59 * @example
60 * <ons-tabbar>
61 * <ons-tab
62 * page="home.html"
63 * label="Home"
64 * active>
65 * </ons-tab>
66 * <ons-tab
67 * page="settings.html"
68 * label="Settings"
69 * active>
70 * </ons-tab>
71 * </ons-tabbar>
72 *
73 * <template id="home.html">
74 * ...
75 * </template>
76 *
77 * <template id="settings.html">
78 * ...
79 * </template>
80
81 */
82export default class TabElement extends BaseElement {
83
84 /**
85 * @attribute page
86 * @initonly
87 * @type {String}
88 * @description
89 * [en]The page that is displayed when the tab is tapped.[/en]
90 * [ja]ons-tabが参照するページへのURLを指定します。[/ja]
91 */
92
93 /**
94 * @attribute icon
95 * @type {String}
96 * @description
97 * [en]
98 * The icon name for the tab. Can specify the same icon name as `<ons-icon>`. Check [See also](#seealso) section for more information.
99 * [/en]
100 * [ja]
101 * アイコン名を指定します。ons-iconと同じアイコン名を指定できます。
102 * 個別にアイコンをカスタマイズする場合は、background-imageなどのCSSスタイルを用いて指定できます。
103 * [/ja]
104 */
105
106 /**
107 * @attribute active-icon
108 * @type {String}
109 * @description
110 * [en]The name of the icon when the tab is active.[/en]
111 * [ja]アクティブの際のアイコン名を指定します。[/ja]
112 */
113
114 /**
115 * @attribute label
116 * @type {String}
117 * @description
118 * [en]The label of the tab item.[/en]
119 * [ja]アイコン下に表示されるラベルを指定します。[/ja]
120 */
121
122 /**
123 * @attribute badge
124 * @type {String}
125 * @description
126 * [en]Display a notification badge on top of the tab.[/en]
127 * [ja]バッジに表示する内容を指定します。[/ja]
128 */
129
130 /**
131 * @attribute active
132 * @description
133 * [en]This attribute should be set to the tab that is active by default.[/en]
134 * [ja][/ja]
135 */
136
137 constructor() {
138 super();
139
140 if (['label', 'icon', 'badge'].some(this.hasAttribute.bind(this))) {
141 this._compile();
142 } else {
143 contentReady(this, () => this._compile());
144 }
145
146 this._pageLoader = defaultPageLoader;
147 this._onClick = this._onClick.bind(this);
148
149 const {onConnected, onDisconnected} = util.defineListenerProperty(this, 'click');
150 this._connectOnClick = onConnected;
151 this._disconnectOnClick = onDisconnected;
152 }
153
154 set pageLoader(loader) {
155 if (!(loader instanceof PageLoader)) {
156 util.throwPageLoader();
157 }
158 this._pageLoader = loader;
159 }
160
161 get pageLoader() {
162 return this._pageLoader;
163 }
164
165 _compile() {
166 autoStyle.prepare(this);
167 this.classList.add(defaultClassName);
168
169 if (this._button) {
170 return;
171 }
172
173 const button = util.create('button.tabbar__button');
174 while (this.childNodes[0]) {
175 button.appendChild(this.childNodes[0]);
176 }
177
178 const input = util.create('input', { display: 'none' });
179 input.type = 'radio';
180
181 this.appendChild(input);
182 this.appendChild(button);
183
184 this._updateButtonContent();
185 ModifierUtil.initModifier(this, scheme);
186 this._updateRipple();
187 }
188
189 _updateRipple() {
190 this._button && util.updateRipple(this._button, this.hasAttribute('ripple'));
191 }
192
193 _updateButtonContent() {
194 const button = this._button;
195
196 let iconWrapper = this._icon;
197 if (this.hasAttribute('icon')) {
198 iconWrapper = iconWrapper || util.createElement('<div class="tabbar__icon"><ons-icon></ons-icon></div>');
199 const icon = iconWrapper.children[0];
200 const fix = (last => () => icon.attributeChangedCallback('icon', last, this.getAttribute('icon')))(icon.getAttribute('icon'));
201 if (this.hasAttribute('icon') && this.hasAttribute('active-icon')) {
202 icon.setAttribute('icon', this.getAttribute(this.isActive() ? 'active-icon' : 'icon'));
203 } else if (this.hasAttribute('icon')) {
204 icon.setAttribute('icon', this.getAttribute('icon'));
205 }
206 iconWrapper.parentElement !== button && button.insertBefore(iconWrapper, button.firstChild);
207
208 // dirty fix for https://github.com/OnsenUI/OnsenUI/issues/1654
209 icon.attributeChangedCallback instanceof Function
210 ? fix()
211 : setImmediate(() => icon.attributeChangedCallback instanceof Function && fix());
212 } else {
213 iconWrapper && iconWrapper.remove();
214 }
215
216 ['label', 'badge'].forEach((attr, index) => {
217 let prop = this.querySelector(`.tabbar__${attr}`);
218 if (this.hasAttribute(attr)) {
219 prop = prop || util.create(`.tabbar__${attr}` + (attr === 'badge' ? ' notification' : ''));
220 prop.textContent = this.getAttribute(attr);
221 prop.parentElement !== button && button.appendChild(prop);
222 } else {
223 prop && prop.remove();
224 }
225 });
226 }
227
228 get _input() {
229 return util.findChild(this, 'input');
230 }
231
232 get _button() {
233 return util.findChild(this, '.tabbar__button');
234 }
235
236 get _icon() {
237 return this.querySelector('.tabbar__icon');
238 }
239
240 get _tabbar() {
241 return util.findParent(this, 'ons-tabbar');
242 }
243
244 get index() {
245 return Array.prototype.indexOf.call(this.parentElement.children, this);
246 }
247
248 _onClick(event) {
249 setTimeout(() => {
250 if (!event.defaultPrevented) {
251 this._tabbar.setActiveTab(this.index, { reject: false });
252 }
253 });
254 }
255
256 setActive(active = true) {
257 contentReady(this, () => {
258 this._input.checked = active;
259 this.classList.toggle('active', active);
260 util.toggleAttribute(this, 'active', active);
261
262 if (this.hasAttribute('icon') && this.hasAttribute('active-icon')) {
263 this._icon.children[0].setAttribute('icon', this.getAttribute(active ? 'active-icon' : 'icon'));
264 }
265 });
266 }
267
268 _loadPageElement(parent, page) {
269 this._hasLoaded = true;
270
271 return new Promise(resolve => {
272 this._pageLoader.load({ parent, page }, pageElement => {
273 parent.replaceChild(pageElement, parent.children[this.index]); // Ensure position
274 this._loadedPage = pageElement;
275 resolve(pageElement);
276 });
277 });
278 }
279
280 get pageElement() {
281 // It has been loaded by ons-tab
282 if (this._loadedPage) {
283 return this._loadedPage;
284 }
285 // Manually attached to DOM, 1 per tab
286 const tabbar = this._tabbar;
287 if (tabbar.pages.length === tabbar.tabs.length) {
288 return tabbar.pages[this.index];
289 }
290 // Loaded in another way
291 return null;
292 }
293
294 /**
295 * @return {Boolean}
296 */
297 isActive() {
298 return this.classList.contains('active');
299 }
300
301 disconnectedCallback() {
302 this.removeEventListener('click', this._onClick, false);
303 if (this._loadedPage) {
304 this._hasLoaded = false;
305 this.loaded = null;
306 }
307
308 this._disconnectOnClick();
309 }
310
311 connectedCallback() {
312 this.addEventListener('click', this._onClick, false);
313
314 if (!util.isAttached(this) || this.loaded) {
315 return; // ons-tabbar compilation may trigger this
316 }
317
318 const deferred = util.defer();
319 this.loaded = deferred.promise;
320
321 contentReady(this, () => {
322 const index = this.index;
323 const tabbar = this._tabbar;
324 if (!tabbar) {
325 util.throw('Tab elements must be children of Tabbar');
326 }
327
328 if (tabbar.hasAttribute('modifier')) {
329 util.addModifier(this, tabbar.getAttribute('modifier'));
330 }
331
332 if (!this._hasLoaded) {
333 if (this.hasAttribute('active')) {
334 this.setActive(true);
335 tabbar.activeIndex = index;
336 }
337
338 if (index === tabbar.tabs.length - 1) {
339 tabbar._onRefresh();
340 setImmediate(() => tabbar._onRefresh());
341 }
342
343 TabbarElement.rewritables.ready(tabbar, () => {
344 const pageTarget = this.page || this.getAttribute('page');
345 if (!this.pageElement && pageTarget) {
346 const parentTarget = tabbar._targetElement;
347 const dummyPage = util.create('div', { height: '100%', width: '100%', visibility: 'hidden' });
348 parentTarget.insertBefore(dummyPage, parentTarget.children[index]); // Ensure position
349
350 const load = () => this._loadPageElement(parentTarget, pageTarget).then(deferred.resolve);
351 return this.isActive() ? load() : tabbar._loadInactive.promise.then(load);
352 }
353
354 return deferred.resolve(this.pageElement);
355 });
356 }
357 });
358
359 this._connectOnClick();
360 }
361
362 static get observedAttributes() {
363 return ['modifier', 'ripple', 'icon', 'label', 'page', 'badge', 'class'];
364 }
365
366 attributeChangedCallback(name, last, current) {
367 switch (name) {
368 case 'class':
369 util.restoreClass(this, defaultClassName, scheme);
370 break;
371 case 'modifier':
372 contentReady(this, () => ModifierUtil.onModifierChanged(last, current, this, scheme));
373 break;
374 case 'ripple':
375 contentReady(this, () => this._updateRipple());
376 break;
377 case 'icon':
378 case 'label':
379 case 'badge':
380 contentReady(this, () => this._updateButtonContent());
381 break;
382 case 'page':
383 this.page = current || '';
384 break;
385 }
386 }
387}
388
389onsElements.Tab = TabElement;
390customElements.define('ons-tab', TabElement);