1 | var __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 | })();
|
11 | import { Component, ContentChildren, ElementRef, EventEmitter, HostListener, Input, Optional, Output, Renderer, ViewEncapsulation } from '@angular/core';
|
12 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
13 | import { ActionSheet } from '../action-sheet/action-sheet';
|
14 | import { Alert } from '../alert/alert';
|
15 | import { Popover } from '../popover/popover';
|
16 | import { App } from '../app/app';
|
17 | import { Config } from '../../config/config';
|
18 | import { DeepLinker } from '../../navigation/deep-linker';
|
19 | import { Form } from '../../util/form';
|
20 | import { BaseInput } from '../../util/base-input';
|
21 | import { deepCopy, deepEqual, isCheckedProperty, isTrueProperty } from '../../util/util';
|
22 | import { Item } from '../item/item';
|
23 | import { Option } from '../option/option';
|
24 | import { 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 | */
|
161 | var 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));
|
490 | export { Select };
|
491 | //# sourceMappingURL=select.js.map |
\ | No newline at end of file |