UNPKG

9.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 contentReady from '../ons/content-ready.js';
24
25const defaultClassName = 'segment';
26const scheme = {
27 '': 'segment--*',
28 '.segment__item': 'segment--*__item',
29 '.segment__input': 'segment--*__input',
30 '.segment__button': 'segment--*__button'
31};
32
33const generateId = (() => {
34 let i = 0;
35 return () => 'ons-segment-gen-' + (i++);
36})();
37
38/**
39 * @element ons-segment
40 * @category control
41 * @modifier material
42 * [en]Material Design segment[/en]
43 * [ja][/ja]
44 * @description
45 * [en]
46 * Segment component. Use this component to have a button bar with automatic styles that switch on click of another button.
47 *
48 * Will automatically display as a Material Design segment on Android.
49 * [/en]
50 * [ja][/ja]
51 * @codepen hLayx
52 * @tutorial vanilla/Reference/segment
53 * @guide theming.html#modifiers [en]More details about the `modifier` attribute[/en][ja]modifier属性の使い方[/ja]
54 * @guide theming.html#cross-platform-styling-autostyling [en]Information about cross platform styling[/en][ja]Information about cross platform styling[/ja]
55 * @example
56 * <ons-segment>
57 * <ons-button>Label 1</ons-button>
58 * <ons-button>Label 2</ons-button>
59 * <ons-button>Label 3</ons-button>
60 * </ons-segment>
61 */
62
63export default class SegmentElement extends BaseElement {
64
65 /**
66 * @event postchange
67 * @description
68 * [en]Fires after the active button is changed.[/en]
69 * [ja][/ja]
70 * @param {Object} event
71 * [en]Event object.[/en]
72 * [ja][/ja]
73 * @param {Number} event.index
74 * [en]Tapped button index.[/en]
75 * [ja][/ja]
76 * @param {Object} event.segmentItem
77 * [en]Segment item object.[/en]
78 * [ja][/ja]
79 */
80
81 /**
82 * @attribute modifier
83 * @type {String}
84 * @description
85 * [en]The appearance of the segment.[/en]
86 * [ja][/ja]
87 */
88
89 /**
90 * @attribute tabbar-id
91 * @initonly
92 * @type {String}
93 * @description
94 * [en]ID of the tabbar element to "connect" to the segment. Must be inside the same page.[/en]
95 * [ja][/ja]
96 */
97
98 /**
99 * @attribute active-index
100 * @default 0
101 * @type {Number}
102 * @description
103 * [en]Index of the active button. If a tabbar is connected, this will be set to the tabbar's active index.[/en]
104 * [ja][/ja]
105 */
106
107 /**
108 * @property activeIndex
109 * @default 0
110 * @type {Number}
111 * @description
112 * [en]Index of the active button. If a tabbar is connected, this will be set to the tabbar's active index.[/en]
113 * [ja][/ja]
114 */
115
116 /**
117 * @attribute disabled
118 * @description
119 * [en]Specify if segment should be disabled.[/en]
120 * [ja]ボタンを無効化する場合は指定します。[/ja]
121 */
122
123
124 constructor() {
125 super();
126
127 this._segmentId = generateId();
128 this._tabbar = null;
129 this._onChange = this._onChange.bind(this);
130 this._onTabbarPreChange = this._onTabbarPreChange.bind(this);
131
132 contentReady(this, () => {
133 this._compile();
134 setImmediate(() => this._lastActiveIndex = this._tabbar ? this._tabbar.getActiveTabIndex() : this.getActiveButtonIndex());
135 });
136 }
137
138 _compile() {
139 autoStyle.prepare(this);
140 this.classList.add(defaultClassName);
141
142 for (let index = this.children.length - 1; index >= 0; index--) {
143 const item = this.children[index];
144 item.classList.add('segment__item');
145
146 const input = util.findChild(item, '.segment__input') || util.create('input.segment__input');
147 input.type = 'radio';
148 input.value = index;
149 input.name = input.name || this._segmentId;
150 input.checked = !this.hasAttribute('tabbar-id') && index === (this.activeIndex || 0);
151
152 const button = util.findChild(item, '.segment__button') || util.create('.segment__button');
153 if (button.parentElement !== item) {
154 while (item.firstChild) {
155 button.appendChild(item.firstChild);
156 }
157 }
158
159 item.appendChild(input);
160 item.appendChild(button);
161 }
162
163 ModifierUtil.initModifier(this, scheme);
164 }
165
166 connectedCallback() {
167 contentReady(this, () => {
168 if (this.hasAttribute('tabbar-id')) {
169 const page = util.findParent(this, 'ons-page');
170 this._tabbar = page && page.querySelector('#' + this.getAttribute('tabbar-id'));
171 if (!this._tabbar || this._tabbar.tagName !== 'ONS-TABBAR') {
172 util.throw(`No tabbar with id ${this.getAttribute('tabbar-id')} was found.`);
173 }
174
175 this._tabbar.setAttribute('hide-tabs', '');
176 setImmediate(() => {
177 const index = this._tabbar.getActiveTabIndex();
178 this._setChecked(index);
179 this.activeIndex = index;
180 });
181
182 this._tabbar.addEventListener('prechange', this._onTabbarPreChange);
183 }
184 });
185
186 this.addEventListener('change', this._onChange);
187 }
188
189 disconnectedCallback() {
190 contentReady(this, () => {
191 if (this._tabbar) {
192 this._tabbar.removeEventListener('prechange', this._onTabbarPreChange);
193 this._tabbar = null;
194 }
195 });
196 this.removeEventListener('change', this._onChange);
197 }
198
199 _setChecked(index) {
200 this.children[index].firstElementChild.checked = true;
201 }
202
203 /**
204 * @method setActiveButton
205 * @signature setActiveButton(index, [options])
206 * @param {Number} index
207 * [en]Button index.[/en]
208 * [ja][/ja]
209 * @param {Object} [options]
210 * [en]Parameter object, works only if there is a connected tabbar. Supports the same options as `ons-tabbar`'s `setActiveTab` method.[/en]
211 * [ja][/ja]
212 * @description
213 * [en]Make button with the specified index active. If there is a connected tabbar it shows the corresponding tab page. In this case animations and their options can be specified by the second parameter.[/en]
214 * [ja][/ja]
215 * @return {Promise}
216 * [en]Resolves to the selected index or to the new page element if there is a connected tabbar.[/en]
217 * [ja][/ja]
218 */
219 setActiveButton(index, options) {
220 if (this._tabbar) {
221 return this._tabbar.setActiveTab(index, options);
222 }
223
224 this._setChecked(index);
225 this._postChange(index);
226 return Promise.resolve(index);
227 }
228
229 /**
230 * @method getActiveButtonIndex
231 * @signature getActiveButtonIndex()
232 * @return {Number}
233 * [en]The index of the currently active button.[/en]
234 * [ja][/ja]
235 * @description
236 * [en]Returns button index of current active button. If active button is not found, returns -1.[/en]
237 * [ja][/ja]
238 */
239 getActiveButtonIndex() {
240 for (let i = this.children.length - 1; i >= 0; i--) { // Array.findIndex
241 if (this.children[i].firstElementChild.checked) {
242 return i;
243 }
244 }
245 return -1;
246 }
247
248 _onChange(event) {
249 event.stopPropagation();
250 this._tabbar
251 ? this._tabbar.setActiveTab(this.getActiveButtonIndex(), { reject: false })
252 : this._postChange(this.getActiveButtonIndex());
253 }
254
255 _onTabbarPreChange(event) {
256 setImmediate(() => {
257 if (!event.detail.canceled) {
258 this._setChecked(event.index);
259 this._postChange(event.index);
260 }
261 });
262 }
263
264 _postChange(index) {
265 util.triggerElementEvent(this, 'postchange', {
266 index,
267 activeIndex: index,
268 lastActiveIndex: this._lastActiveIndex,
269 segmentItem: this.children[index]
270 });
271 this._lastActiveIndex = index;
272 this.activeIndex = index;
273 }
274
275 /**
276 * @property disabled
277 * @type {Boolean}
278 * @description
279 * [en]Whether the segment is disabled or not.[/en]
280 * [ja]無効化されている場合に`true`。[/ja]
281 */
282
283 get activeIndex() {
284 return parseInt(this.getAttribute('active-index'));
285 }
286
287 set activeIndex(value) {
288 if (value !== null && value !== undefined) {
289 this.setAttribute('active-index', value);
290 }
291 }
292
293 static get observedAttributes() {
294 return ['class', 'modifier', 'active-index'];
295 }
296
297 attributeChangedCallback(name, last, current) {
298 switch (name) {
299 case 'class':
300 util.restoreClass(this, defaultClassName, scheme);
301 break;
302 case 'modifier':
303 ModifierUtil.onModifierChanged(last, current, this, scheme);
304 break;
305 case 'active-index':
306 contentReady(this, () => {
307 if (this.getActiveButtonIndex() !== this.activeIndex) {
308 this.setActiveButton(this.activeIndex);
309 }
310 });
311 break;
312 }
313 }
314
315 static get events() {
316 return ['postchange'];
317 }
318}
319
320util.defineBooleanProperties(SegmentElement, ['disabled']);
321
322onsElements.Segment = SegmentElement;
323customElements.define('ons-segment', SegmentElement);