import { Component, ContentChildren, ElementRef, EventEmitter, Input, HostListener, OnDestroy, Optional, Output, Renderer, QueryList, ViewEncapsulation } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { ActionSheet } from '../action-sheet/action-sheet'; import { Alert } from '../alert/alert'; import { Popover } from '../popover/popover'; import { App } from '../app/app'; import { Config } from '../../config/config'; import { DeepLinker } from '../../navigation/deep-linker'; import { Form } from '../../util/form'; import { BaseInput } from '../../util/base-input'; import { isCheckedProperty, isTrueProperty, deepCopy, deepEqual, assert } from '../../util/util'; import { Item } from '../item/item'; import { NavController } from '../../navigation/nav-controller'; import { Option } from '../option/option'; import { SelectPopover, SelectPopoverOption } from './select-popover-component'; /** * @name Select * @description * The `ion-select` component is similar to an HTML ` = new EventEmitter(); constructor( private _app: App, form: Form, public config: Config, elementRef: ElementRef, renderer: Renderer, @Optional() item: Item, @Optional() private _nav: NavController, public deepLinker: DeepLinker ) { super(config, elementRef, renderer, 'select', [], form, item, null); } @HostListener('click', ['$event']) _click(ev: UIEvent) { if (ev.detail === 0) { // do not continue if the click event came from a form submit return; } ev.preventDefault(); ev.stopPropagation(); this.open(ev); } @HostListener('keyup.space') _keyup() { this.open(); } /** * @hidden */ getValues(): any[] { const values = Array.isArray(this._value) ? this._value : [this._value]; assert(this._multi || values.length <= 1, 'single only can have one value'); return values; } /** * Open the select interface. */ open(ev?: UIEvent) { if (this.isFocus() || this._disabled) { return; } console.debug('select, open alert'); // the user may have assigned some options specifically for the alert const selectOptions = deepCopy(this.selectOptions); // make sure their buttons array is removed from the options // and we create a new array for the alert's two buttons selectOptions.buttons = [{ text: this.cancelText, role: 'cancel', handler: () => { this.ionCancel.emit(this); } }]; // if the selectOptions didn't provide a title then use the label's text if (!selectOptions.title && this._item) { selectOptions.title = this._item.getLabelText(); } let options = this._options.toArray(); if (this.interface === 'action-sheet' && options.length > 6) { console.warn('Interface cannot be "action-sheet" with more than 6 options. Using the "alert" interface.'); this.interface = 'alert'; } if ((this.interface === 'action-sheet' || this.interface === 'popover') && this._multi) { console.warn('Interface cannot be "' + this.interface + '" with a multi-value select. Using the "alert" interface.'); this.interface = 'alert'; } if (this.interface === 'popover' && !ev) { console.warn('Interface cannot be "popover" without UIEvent.'); this.interface = 'alert'; } let overlay: ActionSheet | Alert | Popover; if (this.interface === 'action-sheet') { selectOptions.buttons = selectOptions.buttons.concat(options.map(input => { return { role: (input.selected ? 'selected' : ''), text: input.text, handler: () => { this.value = input.value; input.ionSelect.emit(input.value); } }; })); var selectCssClass = 'select-action-sheet'; // If the user passed a cssClass for the select, add it selectCssClass += selectOptions.cssClass ? ' ' + selectOptions.cssClass : ''; selectOptions.cssClass = selectCssClass; overlay = new ActionSheet(this._app, selectOptions, this.config); } else if (this.interface === 'popover') { let popoverOptions: SelectPopoverOption[] = options.map(input => ({ text: input.text, checked: input.selected, disabled: input.disabled, value: input.value, handler: () => { this.value = input.value; input.ionSelect.emit(input.value); } })); overlay = new Popover(this._app, SelectPopover, { options: popoverOptions }, { cssClass: 'select-popover' }, this.config, this.deepLinker); // ev.target is readonly. // place popover regarding to ion-select instead of .button-inner Object.defineProperty(ev, 'target', { value: ev.currentTarget }); selectOptions.ev = ev; } else { // default to use the alert interface this.interface = 'alert'; // user cannot provide inputs from selectOptions // alert inputs must be created by ionic from ion-options selectOptions.inputs = this._options.map(input => { return { type: (this._multi ? 'checkbox' : 'radio'), label: input.text, value: input.value, checked: input.selected, disabled: input.disabled, handler: (selectedOption: any) => { // Only emit the select event if it is being checked // For multi selects this won't emit when unchecking if (selectedOption.checked) { input.ionSelect.emit(input.value); } } }; }); var selectCssClass = 'select-alert'; // create the alert instance from our built up selectOptions overlay = new Alert(this._app, selectOptions, this.config); if (this._multi) { // use checkboxes selectCssClass += ' multiple-select-alert'; } else { // use radio buttons selectCssClass += ' single-select-alert'; } // If the user passed a cssClass for the select, add it selectCssClass += selectOptions.cssClass ? ' ' + selectOptions.cssClass : ''; overlay.setCssClass(selectCssClass); overlay.addButton({ text: this.okText, handler: (selectedValues) => this.value = selectedValues }); } overlay.present(selectOptions); this._fireFocus(); overlay.onDidDismiss((value: any) => { this._fireBlur(); this._overlay = undefined; }); this._overlay = overlay; } /** * Close the select interface. */ close() { if (!this._overlay || !this.isFocus()) { return; } return this._overlay.dismiss(); } /** * @input {boolean} If true, the element can accept multiple values. */ @Input() get multiple(): any { return this._multi; } set multiple(val: any) { this._multi = isTrueProperty(val); } /** * @hidden */ get text() { return (this._multi ? this._texts : this._texts.join()); } /** * @private */ @ContentChildren(Option) set options(val: QueryList