feat(select): ion-select using alert radio/checkbox options

Closes #890 Closes #826 Closes #886
This commit is contained in:
Adam Bradley
2016-01-08 16:25:58 -06:00
parent 471f0f5df9
commit 270fca0c50
23 changed files with 658 additions and 75 deletions

View File

@@ -0,0 +1,5 @@
@import "../../globals.ios";
@import "./select";
// iOS Select
// --------------------------------------------------

View File

@@ -0,0 +1,5 @@
@import "../../globals.md";
@import "./select";
// Material Design Select
// --------------------------------------------------

View File

@@ -0,0 +1,35 @@
@import "../../globals.core";
// Select
// --------------------------------------------------
.select-icon {
position: relative;
min-width: 16px;
}
.select-icon:after {
position: absolute;
top: 50%;
left: 50%;
margin-top: -3px;
width: 0;
height: 0;
border-top: 5px solid;
border-right: 5px solid transparent;
border-left: 5px solid transparent;
color: #999;
content: "";
pointer-events: none;
}
.select-text-value {
max-width: 120px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
ion-select ion-label {
margin: 0;
}

View File

@@ -0,0 +1,249 @@
import {Component, Directive, Optional, ElementRef, Input, Renderer, HostListener, ContentChild, ContentChildren} from 'angular2/core';
import {NgControl} from 'angular2/common';
import {Alert} from '../alert/alert';
import {Form} from '../../util/form';
import {Label} from '../label/label';
import {NavController} from '../nav/nav-controller';
import {Option} from '../option/option';
import {Form} from '../../util/form';
import {merge} from '../../util/util';
/**
* @usage
* ```html
* <ion-select [(ngModel)]="gender">
* <ion-label>Gender</ion-label>
* <ion-option value="f">Female</ion-option>
* <ion-option value="m">Male</ion-option>
* </ion-select>
* ```
*/
@Component({
selector: 'ion-select',
host: {
'class': 'item',
'tappable': '',
'tabindex': 0,
'[attr.aria-disabled]': 'disabled'
},
template:
'<ng-content select="[item-left]"></ng-content>' +
'<div class="item-inner">' +
'<ion-item-content id="{{labelId}}">' +
'<ng-content select="ion-label"></ng-content>' +
'</ion-item-content>' +
'<div class="select-text-value" item-right>{{selectedText}}</div>' +
'<div class="select-icon" item-right></div>' +
'</div>'
})
export class Select {
@Input() public value: string = '';
@Input() public alertOptions: any = {};
@Input() public checked: any = false;
@Input() disabled: boolean = false;
@Input() id: string = '';
@Input() multiple: string = '';
@ContentChild(Label) label: Label;
@ContentChildren(Option) options;
constructor(
private _form: Form,
private _elementRef: ElementRef,
private _renderer: Renderer,
@Optional() private _navCtrl: NavController,
@Optional() ngControl: NgControl
) {
_form.register(this);
this.selectedText = '';
if (ngControl) {
ngControl.valueAccessor = this;
}
if (!_navCtrl) {
console.error('parent <ion-nav> required for <ion-select>');
}
}
/**
* @private
*/
ngOnInit() {
if (!this.id) {
this.id = 'sel-' + this._form.nextId();
this._renderer.setElementAttribute(this._elementRef, 'id', this.id);
}
this.labelId = 'lbl-' + this.id;
this._renderer.setElementAttribute(this._elementRef, 'aria-labelledby', this.labelId);
}
/**
* @private
*/
ngAfterContentInit() {
let selectedOption = this.options.toArray().find(o => o.checked);
if (selectedOption) {
this.value = selectedOption.value;
this.selectedText = selectedOption.text;
setTimeout(()=> {
this.onChange(this.value);
});
}
}
/**
* @private
*/
@HostListener('click')
_click() {
let cancelText = 'Cancel';
let submitText = 'OK';
let isMulti = (this.multiple === true || this.multiple === 'true');
// the user may have assigned some options specifically for the alert
let alertOptions = merge({}, this.alertOptions);
// user can provide buttons, but only two of them, and only as text
// index 0 becomes the cancel text, index 1 becomes the submit (ok) text
if (alertOptions.buttons && alertOptions.buttons.length === 2) {
cancelText = alertOptions.buttons[0];
submitText = alertOptions.buttons[1];
}
// make sure their buttons array is removed from the options
// and we create a new array for the alert's two buttons
alertOptions.buttons = [cancelText];
// if the alertOptions didn't provide an title then use the label's text
if (!alertOptions.title) {
alertOptions.title = this.label.text;
}
// user cannot provide inputs from alertOptions
// alert inputs must be created by ionic from ion-options
alertOptions.inputs = this.options.toArray().map(input => {
return {
type: (isMulti ? 'checkbox' : 'radio'),
label: input.text,
value: input.value,
checked: !!input.checked
}
});
// create the alert instance from our built up alertOptions
let alert = Alert.create(alertOptions);
if (isMulti) {
// use checkboxes
alert.setCssClass('select-alert multiple-select-alert');
alert.addButton({
text: submitText,
handler: selectedValues => {
// passed an array of all the values which were checked
this.value = selectedValues;
// keep a list of all the selected texts
let selectedTexts = [];
this.options.toArray().forEach(option => {
if (selectedValues.indexOf(option.value) > -1) {
// this option is one that was checked
option.checked = true;
selectedTexts.push(option.text);
} else {
// this option was not checked
option.checked = false;
}
});
this.selectedText = selectedTexts.join(', ');
this.onChange(selectedValues);
}
});
} else {
// use radio buttons
alert.setCssClass('select-alert single-select-alert');
alert.addButton({
text: submitText,
handler: selectedValue => {
// passed the single value that was checked
// or undefined if nothing was checked
this.value = selectedValue;
this.selectedText = '';
this.options.toArray().forEach(option => {
if (option.value === selectedValue) {
// this option was the one that was checked
option.checked = true;
this.selectedText = option.text;
} else {
// this option was not checked
option.checked = false;
}
});
this.onChange(selectedValue);
}
});
}
this._navCtrl.present(alert);
}
/**
* @private
* Angular2 Forms API method called by the model (Control) on change to update
* the checked value.
* https://github.com/angular/angular/blob/master/modules/angular2/src/forms/directives/shared.ts#L34
*/
writeValue(value) {
this.value = value;
}
/**
* @private
*/
onChange(val) {
// TODO: figure the whys and the becauses
}
/**
* @private
*/
onTouched(val) {
// TODO: figure the whys and the becauses
}
/**
* @private
* Angular2 Forms API method called by the view (NgControl) to register the
* onChange event handler that updates the model (Control).
* https://github.com/angular/angular/blob/master/modules/angular2/src/forms/directives/shared.ts#L27
* @param {Function} fn the onChange event handler.
*/
registerOnChange(fn) { this.onChange = fn; }
/**
* @private
* Angular2 Forms API method called by the the view (NgControl) to register
* the onTouched event handler that marks model (Control) as touched.
* @param {Function} fn onTouched event handler.
*/
registerOnTouched(fn) { this.onTouched = fn; }
/**
* @private
*/
ngOnDestroy() {
this._form.deregister(this);
}
}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,17 @@
import {App, Page} from 'ionic/ionic';
@Page({
templateUrl: 'main.html'
})
class E2EPage {}
@App({
template: '<ion-nav [root]="root"></ion-nav>'
})
class E2EApp {
constructor() {
this.root = E2EPage;
}
}

View File

@@ -0,0 +1,34 @@
<ion-toolbar><ion-title>Select Item: Multiple Value</ion-title></ion-toolbar>
<ion-content class="outer-content">
<ion-select [(ngModel)]="topings" multiple="true">
<ion-label>Topings</ion-label>
<ion-option value="bacon">Bacon</ion-option>
<ion-option value="olives">Black Olives</ion-option>
<ion-option value="xcheese">Extra Cheese</ion-option>
<ion-option value="peppers">Green Peppers</ion-option>
<ion-option value="mushrooms">Mushrooms</ion-option>
<ion-option value="onions">Onions</ion-option>
<ion-option value="pepperoni">Pepperoni</ion-option>
<ion-option value="pineapple">Pineapple</ion-option>
<ion-option value="Sausage">Sausage</ion-option>
<ion-option value="Spinach">Spinach</ion-option>
</ion-select>
<ion-select [(ngModel)]="carOptions" multiple="true">
<ion-label>Car Options</ion-label>
<ion-option value="backupcamera">Backup Camera</ion-option>
<ion-option value="heatedseats">Headted Seats</ion-option>
<ion-option value="keyless">Keyless Entry</ion-option>
<ion-option value="navigation">Navigation</ion-option>
<ion-option value="parkingassist">Parking Assist</ion-option>
<ion-option value="sunroof">Sun Roof</ion-option>
</ion-select>
<p aria-hidden="true" padding>
<code>topings: {{topings}}</code><br>
<code>carOptions: {{carOptions}}</code><br>
</p>
</ion-content>

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,17 @@
import {App, Page} from 'ionic/ionic';
@Page({
templateUrl: 'main.html'
})
class E2EPage {}
@App({
template: '<ion-nav [root]="root"></ion-nav>'
})
class E2EApp {
constructor() {
this.root = E2EPage;
}
}

View File

@@ -0,0 +1,52 @@
<ion-toolbar><ion-title>Select Item: Single Value</ion-title></ion-toolbar>
<ion-content class="outer-content">
<ion-select [(ngModel)]="gender">
<ion-label>Gender</ion-label>
<ion-option value="f" checked="true">Female</ion-option>
<ion-option value="m">Male</ion-option>
</ion-select>
<ion-select [(ngModel)]="gaming">
<ion-label>Gaming</ion-label>
<ion-option value="nes">NES</ion-option>
<ion-option value="n64">Nintendo64</ion-option>
<ion-option value="ps">PlayStation</ion-option>
<ion-option value="genesis">Sega Genesis</ion-option>
<ion-option value="saturn">Sega Saturn</ion-option>
<ion-option value="snes">SNES</ion-option>
</ion-select>
<ion-select [(ngModel)]="os" submitText="Okay" cancelText="Nah">
<ion-label>Operating System</ion-label>
<ion-option value="dos">DOS</ion-option>
<ion-option value="lunix">Linux</ion-option>
<ion-option value="mac7">Mac OS 7</ion-option>
<ion-option value="mac8">Mac OS 8</ion-option>
<ion-option value="win3.1" checked>Windows 3.1</ion-option>
<ion-option value="win95">Windows 95</ion-option>
<ion-option value="win98">Windows 98</ion-option>
</ion-select>
<ion-select [(ngModel)]="music">
<ion-label>Music</ion-label>
<ion-option value="aliceinchains">Alice in Chains</ion-option>
<ion-option value="greenday">Green Day</ion-option>
<ion-option value="hole">Hole</ion-option>
<ion-option value="korn">Korn</ion-option>
<ion-option value="nirvana">Nirvana</ion-option>
<ion-option value="pearljam">Pearl Jam</ion-option>
<ion-option value="smashingpumpkins">Smashing Pumpkins</ion-option>
<ion-option value="soundgarden">Soundgarden</ion-option>
<ion-option value="stp">Stone Temple Pilots</ion-option>
</ion-select>
<p aria-hidden="true" padding>
<code>gender: {{gender}}</code><br>
<code>gaming: {{gaming}}</code><br>
<code>os: {{os}}</code><br>
<code>music: {{music}}</code><br>
</p>
</ion-content>