UNPKG

19.6 kBJavaScriptView Raw
1var __extends = (this && this.__extends) || (function () {
2 var extendStatics = Object.setPrototypeOf ||
3 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
4 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
5 return function (d, b) {
6 extendStatics(d, b);
7 function __() { this.constructor = d; }
8 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
9 };
10})();
11import { Component, ContentChildren, ElementRef, EventEmitter, HostListener, Input, Optional, Output, Renderer, ViewEncapsulation } from '@angular/core';
12import { NG_VALUE_ACCESSOR } from '@angular/forms';
13import { ActionSheet } from '../action-sheet/action-sheet';
14import { Alert } from '../alert/alert';
15import { Popover } from '../popover/popover';
16import { App } from '../app/app';
17import { Config } from '../../config/config';
18import { DeepLinker } from '../../navigation/deep-linker';
19import { Form } from '../../util/form';
20import { BaseInput } from '../../util/base-input';
21import { deepCopy, deepEqual, isCheckedProperty, isTrueProperty } from '../../util/util';
22import { Item } from '../item/item';
23import { Option } from '../option/option';
24import { SelectPopover } from './select-popover-component';
25/**
26 * @name Select
27 * @description
28 * The `ion-select` component is similar to an HTML `<select>` element, however,
29 * Ionic's select component makes it easier for users to sort through and select
30 * the preferred option or options. When users tap the select component, a
31 * dialog will appear with all of the options in a large, easy to select list
32 * for users.
33 *
34 * The select component takes child `ion-option` components. If `ion-option` is not
35 * given a `value` attribute then it will use its text as the value.
36 *
37 * If `ngModel` is bound to `ion-select`, the selected value will be based on the
38 * bound value of the model. Otherwise, the `selected` attribute can be used on
39 * `ion-option` components.
40 *
41 * ### Interfaces
42 *
43 * By default, the `ion-select` uses the {@link ../../alert/AlertController AlertController API}
44 * to open up the overlay of options in an alert. The interface can be changed to use the
45 * {@link ../../action-sheet/ActionSheetController ActionSheetController API} or
46 * {@link ../../popover/PopoverController PopoverController API} by passing `action-sheet` or `popover`,
47 * respectively, to the `interface` property. Read on to the other sections for the limitations
48 * of the different interfaces.
49 *
50 * ### Single Value: Radio Buttons
51 *
52 * The standard `ion-select` component allows the user to select only one
53 * option. When selecting only one option the alert interface presents users with
54 * a radio button styled list of options. The action sheet interface can only be
55 * used with a single value select. If the number of options exceed 6, it will
56 * use the `alert` interface even if `action-sheet` is passed. The `ion-select`
57 * component's value receives the value of the selected option's value.
58 *
59 * ```html
60 * <ion-item>
61 * <ion-label>Gender</ion-label>
62 * <ion-select [(ngModel)]="gender">
63 * <ion-option value="f">Female</ion-option>
64 * <ion-option value="m">Male</ion-option>
65 * </ion-select>
66 * </ion-item>
67 * ```
68 *
69 * ### Multiple Value: Checkboxes
70 *
71 * By adding the `multiple="true"` attribute to `ion-select`, users are able
72 * to select multiple options. When multiple options can be selected, the alert
73 * overlay presents users with a checkbox styled list of options. The
74 * `ion-select multiple="true"` component's value receives an array of all the
75 * selected option values. In the example below, because each option is not given
76 * a `value`, then it'll use its text as the value instead.
77 *
78 * Note: the `action-sheet` and `popover` interfaces will not work with a multi-value select.
79 *
80 * ```html
81 * <ion-item>
82 * <ion-label>Toppings</ion-label>
83 * <ion-select [(ngModel)]="toppings" multiple="true">
84 * <ion-option>Bacon</ion-option>
85 * <ion-option>Black Olives</ion-option>
86 * <ion-option>Extra Cheese</ion-option>
87 * <ion-option>Mushrooms</ion-option>
88 * <ion-option>Pepperoni</ion-option>
89 * <ion-option>Sausage</ion-option>
90 * </ion-select>
91 * </ion-item>
92 * ```
93 *
94 * ### Select Buttons
95 * By default, the two buttons read `Cancel` and `OK`. Each button's text
96 * can be customized using the `cancelText` and `okText` attributes:
97 *
98 * ```html
99 * <ion-select okText="Okay" cancelText="Dismiss">
100 * ...
101 * </ion-select>
102 * ```
103 *
104 * The `action-sheet` and `popover` interfaces do not have an `OK` button, clicking
105 * on any of the options will automatically close the overlay and select
106 * that value.
107 *
108 * ### Select Options
109 *
110 * Since `ion-select` uses the `Alert`, `Action Sheet` and `Popover` interfaces, options can be
111 * passed to these components through the `selectOptions` property. This can be used
112 * to pass a custom title, subtitle, css class, and more. See the
113 * {@link ../../alert/AlertController/#create AlertController API docs},
114 * {@link ../../action-sheet/ActionSheetController/#create ActionSheetController API docs}, and
115 * {@link ../../popover/PopoverController/#create PopoverController API docs}
116 * for the properties that each interface accepts.
117 *
118 * For example, to change the `mode` of the overlay, pass it into `selectOptions`.
119 *
120 * ```html
121 * <ion-select [selectOptions]="selectOptions">
122 * ...
123 * </ion-select>
124 * ```
125 *
126 * ```ts
127 * this.selectOptions = {
128 * title: 'Pizza Toppings',
129 * subTitle: 'Select your toppings',
130 * mode: 'md'
131 * };
132 * ```
133 *
134 * ### Object Value References
135 *
136 * When using objects for select values, it is possible for the identities of these objects to
137 * change if they are coming from a server or database, while the selected value's identity
138 * remains the same. For example, this can occur when an existing record with the desired object value
139 * is loaded into the select, but the newly retrieved select options now have different identities. This will
140 * result in the select appearing to have no value at all, even though the original selection in still intact.
141 *
142 * Using the `compareWith` `Input` is the solution to this problem
143 *
144 * ```html
145 * <ion-item>
146 * <ion-label>Employee</ion-label>
147 * <ion-select [(ngModel)]="employee" [compareWith]="compareFn">
148 * <ion-option *ngFor="let employee of employees" [value]="employee">{{employee.name}}</ion-option>
149 * </ion-select>
150 * </ion-item>
151 * ```
152 *
153 * ```ts
154 * compareFn(e1: Employee, e2: Employee): boolean {
155 * return e1 && e2 ? e1.id === e2.id : e1 === e2;
156 * }
157 * ```
158 *
159 * @demo /docs/demos/src/select/
160 */
161var Select = (function (_super) {
162 __extends(Select, _super);
163 function Select(_app, form, config, elementRef, renderer, item, deepLinker) {
164 var _this = _super.call(this, config, elementRef, renderer, 'select', [], form, item, null) || this;
165 _this._app = _app;
166 _this.config = config;
167 _this.deepLinker = deepLinker;
168 _this._multi = false;
169 _this._texts = [];
170 _this._text = '';
171 _this._compareWith = isCheckedProperty;
172 /**
173 * @input {string} The text to display on the cancel button. Default: `Cancel`.
174 */
175 _this.cancelText = 'Cancel';
176 /**
177 * @input {string} The text to display on the ok button. Default: `OK`.
178 */
179 _this.okText = 'OK';
180 /**
181 * @input {any} Any additional options that the `alert` or `action-sheet` interface can take.
182 * See the [AlertController API docs](../../alert/AlertController/#create) and the
183 * [ActionSheetController API docs](../../action-sheet/ActionSheetController/#create) for the
184 * create options for each interface.
185 */
186 _this.selectOptions = {};
187 /**
188 * @input {string} The interface the select should use: `action-sheet`, `popover` or `alert`. Default: `alert`.
189 */
190 _this.interface = '';
191 /**
192 * @input {string} The text to display instead of the selected option's value.
193 */
194 _this.selectedText = '';
195 /**
196 * @output {any} Emitted when the selection was cancelled.
197 */
198 _this.ionCancel = new EventEmitter();
199 return _this;
200 }
201 Object.defineProperty(Select.prototype, "compareWith", {
202 /**
203 * @input {Function} The function that will be called to compare object values
204 */
205 set: function (fn) {
206 if (typeof fn !== 'function') {
207 throw new Error("compareWith must be a function, but received " + JSON.stringify(fn));
208 }
209 this._compareWith = fn;
210 },
211 enumerable: true,
212 configurable: true
213 });
214 Select.prototype._click = function (ev) {
215 ev.preventDefault();
216 ev.stopPropagation();
217 this.open(ev);
218 };
219 Select.prototype._keyup = function () {
220 this.open();
221 };
222 /**
223 * @hidden
224 */
225 Select.prototype.getValues = function () {
226 var values = Array.isArray(this._value) ? this._value : [this._value];
227 (void 0) /* assert */;
228 return values;
229 };
230 /**
231 * Open the select interface.
232 */
233 Select.prototype.open = function (ev) {
234 var _this = this;
235 if (this.isFocus() || this._disabled) {
236 return;
237 }
238 (void 0) /* console.debug */;
239 // the user may have assigned some options specifically for the alert
240 var selectOptions = deepCopy(this.selectOptions);
241 // make sure their buttons array is removed from the options
242 // and we create a new array for the alert's two buttons
243 selectOptions.buttons = [{
244 text: this.cancelText,
245 role: 'cancel',
246 handler: function () {
247 _this.ionCancel.emit(_this);
248 }
249 }];
250 // if the selectOptions didn't provide a title then use the label's text
251 if (!selectOptions.title && this._item) {
252 selectOptions.title = this._item.getLabelText();
253 }
254 var options = this._options.toArray();
255 if ((this.interface === 'action-sheet' || this.interface === 'popover') && this._multi) {
256 console.warn('Interface cannot be "' + this.interface + '" with a multi-value select. Using the "alert" interface.');
257 this.interface = 'alert';
258 }
259 if (this.interface === 'popover' && !ev) {
260 console.warn('Interface cannot be "popover" without UIEvent.');
261 this.interface = 'alert';
262 }
263 var overlay;
264 if (this.interface === 'action-sheet') {
265 selectOptions.buttons = selectOptions.buttons.concat(options.map(function (input) {
266 return {
267 role: (input.selected ? 'selected' : ''),
268 text: input.text,
269 handler: function () {
270 _this.value = input.value;
271 input.ionSelect.emit(input.value);
272 }
273 };
274 }));
275 var selectCssClass = 'select-action-sheet';
276 // If the user passed a cssClass for the select, add it
277 selectCssClass += selectOptions.cssClass ? ' ' + selectOptions.cssClass : '';
278 selectOptions.cssClass = selectCssClass;
279 overlay = new ActionSheet(this._app, selectOptions, this.config);
280 }
281 else if (this.interface === 'popover') {
282 var popoverOptions = options.map(function (input) { return ({
283 text: input.text,
284 checked: input.selected,
285 disabled: input.disabled,
286 value: input.value,
287 handler: function () {
288 _this.value = input.value;
289 input.ionSelect.emit(input.value);
290 }
291 }); });
292 var popoverCssClass = 'select-popover';
293 // If the user passed a cssClass for the select, add it
294 popoverCssClass += selectOptions.cssClass ? ' ' + selectOptions.cssClass : '';
295 overlay = new Popover(this._app, SelectPopover, {
296 options: popoverOptions
297 }, {
298 cssClass: popoverCssClass
299 }, this.config, this.deepLinker);
300 // ev.target is readonly.
301 // place popover regarding to ion-select instead of .button-inner
302 Object.defineProperty(ev, 'target', { value: ev.currentTarget });
303 selectOptions.ev = ev;
304 }
305 else {
306 // default to use the alert interface
307 this.interface = 'alert';
308 // user cannot provide inputs from selectOptions
309 // alert inputs must be created by ionic from ion-options
310 selectOptions.inputs = this._options.map(function (input) {
311 return {
312 type: (_this._multi ? 'checkbox' : 'radio'),
313 label: input.text,
314 value: input.value,
315 checked: input.selected,
316 disabled: input.disabled,
317 handler: function (selectedOption) {
318 // Only emit the select event if it is being checked
319 // For multi selects this won't emit when unchecking
320 if (selectedOption.checked) {
321 input.ionSelect.emit(input.value);
322 }
323 }
324 };
325 });
326 var selectCssClass_1 = 'select-alert';
327 // create the alert instance from our built up selectOptions
328 overlay = new Alert(this._app, selectOptions, this.config);
329 if (this._multi) {
330 // use checkboxes
331 selectCssClass_1 += ' multiple-select-alert';
332 }
333 else {
334 // use radio buttons
335 selectCssClass_1 += ' single-select-alert';
336 }
337 // If the user passed a cssClass for the select, add it
338 selectCssClass_1 += selectOptions.cssClass ? ' ' + selectOptions.cssClass : '';
339 overlay.setCssClass(selectCssClass_1);
340 overlay.addButton({
341 text: this.okText,
342 handler: function (selectedValues) { return _this.value = selectedValues; }
343 });
344 }
345 overlay.present(selectOptions);
346 this._fireFocus();
347 overlay.onDidDismiss(function () {
348 _this._fireBlur();
349 _this._overlay = undefined;
350 });
351 this._overlay = overlay;
352 };
353 /**
354 * Close the select interface.
355 */
356 Select.prototype.close = function () {
357 if (!this._overlay || !this.isFocus()) {
358 return;
359 }
360 return this._overlay.dismiss();
361 };
362 Object.defineProperty(Select.prototype, "multiple", {
363 /**
364 * @input {boolean} If true, the element can accept multiple values.
365 */
366 get: function () {
367 return this._multi;
368 },
369 set: function (val) {
370 this._multi = isTrueProperty(val);
371 },
372 enumerable: true,
373 configurable: true
374 });
375 Object.defineProperty(Select.prototype, "text", {
376 /**
377 * @hidden
378 */
379 get: function () {
380 return (this._multi ? this._texts : this._texts.join());
381 },
382 enumerable: true,
383 configurable: true
384 });
385 Object.defineProperty(Select.prototype, "options", {
386 /**
387 * @private
388 */
389 set: function (val) {
390 this._options = val;
391 var values = this.getValues();
392 if (values.length === 0) {
393 // there are no values set at this point
394 // so check to see who should be selected
395 // we use writeValue() because we don't want to update ngModel
396 this.writeValue(val.filter(function (o) { return o.selected; }).map(function (o) { return o.value; }));
397 }
398 else {
399 this._updateText();
400 }
401 },
402 enumerable: true,
403 configurable: true
404 });
405 Select.prototype._inputShouldChange = function (val) {
406 return !deepEqual(this._value, val);
407 };
408 /**
409 * TODO: REMOVE THIS
410 * @hidden
411 */
412 Select.prototype._inputChangeEvent = function () {
413 return this.value;
414 };
415 /**
416 * @hidden
417 */
418 Select.prototype._updateText = function () {
419 var _this = this;
420 this._texts.length = 0;
421 if (this._options) {
422 this._options.forEach(function (option) {
423 // check this option if the option's value is in the values array
424 option.selected = _this.getValues().some(function (selectValue) {
425 return _this._compareWith(selectValue, option.value);
426 });
427 if (option.selected) {
428 _this._texts.push(option.text);
429 }
430 });
431 }
432 this._text = this._texts.join(', ');
433 };
434 /**
435 * @hidden
436 */
437 Select.prototype._inputUpdated = function () {
438 this._updateText();
439 _super.prototype._inputUpdated.call(this);
440 };
441 Select.decorators = [
442 { type: Component, args: [{
443 selector: 'ion-select',
444 template: '<div *ngIf="!_text" class="select-placeholder select-text">{{placeholder}}</div>' +
445 '<div *ngIf="_text" class="select-text">{{selectedText || _text}}</div>' +
446 '<div class="select-icon">' +
447 '<div class="select-icon-inner"></div>' +
448 '</div>' +
449 '<button aria-haspopup="true" ' +
450 'type="button" ' +
451 '[id]="id" ' +
452 'ion-button="item-cover" ' +
453 '[attr.aria-labelledby]="_labelId" ' +
454 '[attr.aria-disabled]="_disabled" ' +
455 'class="item-cover">' +
456 '</button>',
457 host: {
458 '[class.select-disabled]': '_disabled'
459 },
460 providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: Select, multi: true }],
461 encapsulation: ViewEncapsulation.None,
462 },] },
463 ];
464 /** @nocollapse */
465 Select.ctorParameters = function () { return [
466 { type: App, },
467 { type: Form, },
468 { type: Config, },
469 { type: ElementRef, },
470 { type: Renderer, },
471 { type: Item, decorators: [{ type: Optional },] },
472 { type: DeepLinker, },
473 ]; };
474 Select.propDecorators = {
475 'cancelText': [{ type: Input },],
476 'okText': [{ type: Input },],
477 'placeholder': [{ type: Input },],
478 'selectOptions': [{ type: Input },],
479 'interface': [{ type: Input },],
480 'selectedText': [{ type: Input },],
481 'compareWith': [{ type: Input },],
482 'ionCancel': [{ type: Output },],
483 '_click': [{ type: HostListener, args: ['click', ['$event'],] },],
484 '_keyup': [{ type: HostListener, args: ['keyup.space',] },],
485 'multiple': [{ type: Input },],
486 'options': [{ type: ContentChildren, args: [Option,] },],
487 };
488 return Select;
489}(BaseInput));
490export { Select };
491//# sourceMappingURL=select.js.map
\No newline at end of file