diff --git a/demos/src/select/pages/page-one/page-one.html b/demos/src/select/pages/page-one/page-one.html
index e48f644bae..e8eced2919 100644
--- a/demos/src/select/pages/page-one/page-one.html
+++ b/demos/src/select/pages/page-one/page-one.html
@@ -65,6 +65,62 @@
+
+ Popover Interface Select
+
+
+ Gender
+
+ Female
+ Male
+
+
+
+
+ Gaming
+
+ NES
+ Nintendo64
+ PlayStation
+ Sega Genesis
+ Sega Saturn
+ SNES
+
+
+
+
+ Date
+
+ January
+ February
+ March
+ April
+ May
+ June
+ July
+ August
+ September
+ October
+ November
+ December
+
+
+ 1989
+ 1990
+ 1991
+ 1992
+ 1993
+ 1994
+ 1995
+ 1996
+ 1997
+ 1998
+ 1999
+
+
+
+
+
Multiple Value Select
diff --git a/src/components/select/select-popover-component.ts b/src/components/select/select-popover-component.ts
new file mode 100644
index 0000000000..27b27b2efd
--- /dev/null
+++ b/src/components/select/select-popover-component.ts
@@ -0,0 +1,46 @@
+import { Component, OnInit } from '@angular/core';
+import { NavParams } from '../../navigation/nav-params';
+import { ViewController } from '../../navigation/view-controller';
+
+/** @private */
+export interface SelectPopoverOption {
+ text: string;
+ value: string;
+ disabled: boolean;
+ checked: boolean;
+}
+
+/** @private */
+@Component({
+ template: `
+
+
+ {{option.text}}
+
+
+
+ `
+})
+export class SelectPopover implements OnInit {
+
+ public get value() {
+ let checkedOption = this.options.find(option => option.checked);
+
+ return checkedOption ? checkedOption.value : undefined;
+ }
+
+ public set value(value: any) {
+ this.viewController.dismiss(value);
+ }
+
+ private options: SelectPopoverOption[];
+
+ constructor(
+ private navParams: NavParams,
+ private viewController: ViewController
+ ) { }
+
+ public ngOnInit() {
+ this.options = this.navParams.data.options;
+ }
+}
diff --git a/src/components/select/select.module.ts b/src/components/select/select.module.ts
index d45f10c4c4..b4e8e449b9 100644
--- a/src/components/select/select.module.ts
+++ b/src/components/select/select.module.ts
@@ -1,18 +1,37 @@
import { CommonModule } from '@angular/common';
import { NgModule, ModuleWithProviders } from '@angular/core';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Select } from './select';
+import { SelectPopover } from './select-popover-component';
+
+import { ItemModule } from '../item/item.module';
+import { LabelModule } from '../label/label.module';
+import { ListModule } from '../list/list.module';
+import { RadioModule } from '../radio/radio.module';
+
/** @hidden */
@NgModule({
imports: [
- CommonModule
+ CommonModule,
+ FormsModule,
+ ReactiveFormsModule,
+ ItemModule,
+ LabelModule,
+ ListModule,
+ RadioModule
],
declarations: [
- Select
+ Select,
+ SelectPopover
],
exports: [
- Select
+ Select,
+ SelectPopover
+ ],
+ entryComponents: [
+ SelectPopover
]
})
export class SelectModule {
diff --git a/src/components/select/select.scss b/src/components/select/select.scss
index 9831751787..74f13c087f 100644
--- a/src/components/select/select.scss
+++ b/src/components/select/select.scss
@@ -3,6 +3,19 @@
// Select
// --------------------------------------------------
+/// @prop - Margin top of the select popover list
+$select-popover-list-margin-top: -1px !default;
+
+/// @prop - Margin right of the select popover list
+$select-popover-list-margin-right: 0 !default;
+
+/// @prop - Margin bottom of the select popover list
+$select-popover-list-margin-bottom: -1px !default;
+
+/// @prop - Margin left of the select popover list
+$select-popover-list-margin-left: 0 !default;
+
+
ion-select {
display: flex;
overflow: hidden;
@@ -32,3 +45,7 @@ ion-select {
pointer-events: none;
}
+
+.select-popover ion-list {
+ margin: $select-popover-list-margin-top $select-popover-list-margin-right $select-popover-list-margin-bottom $select-popover-list-margin-left;
+}
diff --git a/src/components/select/select.ts b/src/components/select/select.ts
index 96670089db..7343540be7 100644
--- a/src/components/select/select.ts
+++ b/src/components/select/select.ts
@@ -3,14 +3,17 @@ 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 } 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';
export const SELECT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
@@ -30,7 +33,7 @@ export const SELECT_VALUE_ACCESSOR: any = {
* The select component takes child `ion-option` components. If `ion-option` is not
* given a `value` attribute then it will use its text as the value.
*
- * If `ngModel` is bound to `ion-select`, the selected value will be based on the
+ * If `ngModel` is bound to `ion-select`, the selected value will be based on the
* bound value of the model. Otherwise, the `selected` attribute can be used on
* `ion-option` components.
*
@@ -38,9 +41,10 @@ export const SELECT_VALUE_ACCESSOR: any = {
*
* By default, the `ion-select` uses the {@link ../../alert/AlertController AlertController API}
* to open up the overlay of options in an alert. The interface can be changed to use the
- * {@link ../../action-sheet/ActionSheetController ActionSheetController API} by passing
- * `action-sheet` to the `interface` property. Read the other sections for the limitations of the
- * action sheet interface.
+ * {@link ../../action-sheet/ActionSheetController ActionSheetController API} or
+ * {@link ../../popover/PopoverController PopoverController API} by passing `action-sheet` or `popover`,
+ * respectively, to the `interface` property. Read on to the other sections for the limitations
+ * of the different interfaces.
*
* ### Single Value: Radio Buttons
*
@@ -70,7 +74,7 @@ export const SELECT_VALUE_ACCESSOR: any = {
* selected option values. In the example below, because each option is not given
* a `value`, then it'll use its text as the value instead.
*
- * Note: the action sheet interface will not work with a multi-value select.
+ * Note: the `action-sheet` and `popover` interfaces will not work with a multi-value select.
*
* ```html
*
@@ -96,19 +100,22 @@ export const SELECT_VALUE_ACCESSOR: any = {
*
* ```
*
- * The action sheet interface does not have an `OK` button, clicking
+ * The `action-sheet` and `popover` interfaces do not have an `OK` button, clicking
* on any of the options will automatically close the overlay and select
* that value.
*
* ### Select Options
*
- * Since `ion-select` uses the `Alert` and `Action Sheet` interfaces, options can be
+ * Since `ion-select` uses the `Alert`, `Action Sheet` and `Popover` interfaces, options can be
* passed to these components through the `selectOptions` property. This can be used
* to pass a custom title, subtitle, css class, and more. See the
- * {@link ../../alert/AlertController/#create AlertController API docs} and
- * {@link ../../action-sheet/ActionSheetController/#create ActionSheetController API docs}
+ * {@link ../../alert/AlertController/#create AlertController API docs},
+ * {@link ../../action-sheet/ActionSheetController/#create ActionSheetController API docs}, and
+ * {@link ../../popover/PopoverController/#create PopoverController API docs}
* for the properties that each interface accepts.
*
+ * For example, to change the `mode` of the overlay, pass it into `selectOptions`.
+ *
* ```html
*
* ...
@@ -118,7 +125,8 @@ export const SELECT_VALUE_ACCESSOR: any = {
* ```ts
* this.selectOptions = {
* title: 'Pizza Toppings',
- * subTitle: 'Select your toppings'
+ * subTitle: 'Select your toppings',
+ * mode: 'md'
* };
* ```
*
@@ -176,7 +184,7 @@ export class Select extends BaseInput implements AfterViewInit, OnDest
@Input() selectOptions: any = {};
/**
- * @input {string} The interface the select should use: `action-sheet` or `alert`. Default: `alert`.
+ * @input {string} The interface the select should use: `action-sheet`, `popover` or `alert`. Default: `alert`.
*/
@Input() interface: string = '';
@@ -197,7 +205,8 @@ export class Select extends BaseInput implements AfterViewInit, OnDest
elementRef: ElementRef,
renderer: Renderer,
@Optional() item: Item,
- @Optional() private _nav: NavController
+ @Optional() private _nav: NavController,
+ public deepLinker: DeepLinker
) {
super(config, elementRef, renderer, 'select', [], form, item, null);
}
@@ -215,7 +224,7 @@ export class Select extends BaseInput implements AfterViewInit, OnDest
}
ev.preventDefault();
ev.stopPropagation();
- this.open();
+ this.open(ev);
}
@HostListener('keyup.space')
@@ -226,7 +235,7 @@ export class Select extends BaseInput implements AfterViewInit, OnDest
/**
* Open the select interface.
*/
- open() {
+ open(ev?: UIEvent) {
if (this.isFocus() || this._disabled) {
return;
}
@@ -257,12 +266,18 @@ export class Select extends BaseInput implements AfterViewInit, OnDest
this.interface = 'alert';
}
- if (this.interface === 'action-sheet' && this._multi) {
- console.warn('Interface cannot be "action-sheet" with a multi-value select. Using the "alert" interface.');
+ 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';
}
- let overlay: ActionSheet | 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 {
@@ -282,6 +297,25 @@ export class Select extends BaseInput implements AfterViewInit, OnDest
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
+ }));
+
+ 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';
@@ -330,9 +364,15 @@ export class Select extends BaseInput implements AfterViewInit, OnDest
}
overlay.present(selectOptions);
+
this._fireFocus();
- overlay.onDidDismiss(() => {
+ overlay.onDidDismiss((value: any) => {
this._fireBlur();
+
+ if (this.interface === 'popover' && value) {
+ this.value = value;
+ this.ionChange.emit(value);
+ }
});
}
diff --git a/src/components/select/test/select.spec.ts b/src/components/select/test/select.spec.ts
index c289784c2b..1ff033d05e 100644
--- a/src/components/select/test/select.spec.ts
+++ b/src/components/select/test/select.spec.ts
@@ -1,6 +1,6 @@
import { Select } from '../select';
-import { mockApp, mockConfig, mockElementRef, mockRenderer, mockItem, mockForm } from '../../../util/mock-providers';
+import { mockApp, mockConfig, mockDeepLinker, mockElementRef, mockRenderer, mockItem, mockForm } from '../../../util/mock-providers';
import { commonInputTest } from '../../../util/input-tester';
describe('Select', () => {
@@ -9,11 +9,12 @@ describe('Select', () => {
const app = mockApp();
const config = mockConfig();
+ const deepLinker = mockDeepLinker();
const elementRef = mockElementRef();
const renderer = mockRenderer();
const item: any = mockItem();
const form = mockForm();
- const select = new Select(app, form, config, elementRef, renderer, item, null);
+ const select = new Select(app, form, config, elementRef, renderer, item, null, deepLinker);
commonInputTest(select, {
defaultValue: [],
diff --git a/src/components/select/test/single-value/pages/page-one/page-one.html b/src/components/select/test/single-value/pages/page-one/page-one.html
index 0cdbd1a477..4852b27174 100644
--- a/src/components/select/test/single-value/pages/page-one/page-one.html
+++ b/src/components/select/test/single-value/pages/page-one/page-one.html
@@ -17,6 +17,15 @@
+
+ Popover
+
+ Select
+ Action Sheet
+ Popover
+
+
+
Gaming
diff --git a/src/index.ts b/src/index.ts
index fd63d97467..1ba51ef8ef 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -240,6 +240,7 @@ export { Searchbar } from './components/searchbar/searchbar';
export { Segment } from './components/segment/segment';
export { SegmentButton } from './components/segment/segment-button';
export { Select } from './components/select/select';
+export { SelectPopover } from './components/select/select-popover-component';
export { ShowWhen } from './components/show-hide-when/show-when';
export { DisplayWhen } from './components/show-hide-when/display-when';
export { HideWhen } from './components/show-hide-when/hide-when';