Skip to content
Permalink
Browse files

feat(select): add compareWith property (#17358)

* feat(select): add compareWith property

* style(select): fix lint errors

* test(select): move tests from preview to basic

* refactor(select): improve parameter names in compareOptions method

* chore(): add react usage docs

* chore(): update var names, update examples

* rerun build

* add doc on compareWith
  • Loading branch information...
zakton5 authored and liamdebeasi committed Mar 1, 2019
1 parent 14dd871 commit 69ecebb15917e770097ac855ce4469d4db601acb
@@ -720,7 +720,7 @@ export class IonSegmentButton {
proxyInputs(IonSegmentButton, ['mode', 'checked', 'disabled', 'layout', 'value']);

export declare interface IonSelect extends StencilComponents<'IonSelect'> {}
@Component({ selector: 'ion-select', changeDetection: 0, template: '<ng-content></ng-content>', inputs: ['mode', 'disabled', 'cancelText', 'okText', 'placeholder', 'name', 'selectedText', 'multiple', 'interface', 'interfaceOptions', 'value'] })
@Component({ selector: 'ion-select', changeDetection: 0, template: '<ng-content></ng-content>', inputs: ['mode', 'disabled', 'cancelText', 'okText', 'placeholder', 'name', 'selectedText', 'multiple', 'interface', 'interfaceOptions', 'compareWith', 'value'] })
export class IonSelect {
ionChange!: EventEmitter<CustomEvent>;
ionCancel!: EventEmitter<CustomEvent>;
@@ -734,7 +734,7 @@ export class IonSelect {
}
}
proxyMethods(IonSelect, ['open']);
proxyInputs(IonSelect, ['mode', 'disabled', 'cancelText', 'okText', 'placeholder', 'name', 'selectedText', 'multiple', 'interface', 'interfaceOptions', 'value']);
proxyInputs(IonSelect, ['mode', 'disabled', 'cancelText', 'okText', 'placeholder', 'name', 'selectedText', 'multiple', 'interface', 'interfaceOptions', 'compareWith', 'value']);

export declare interface IonSelectOption extends StencilComponents<'IonSelectOption'> {}
@Component({ selector: 'ion-select-option', changeDetection: 0, template: '<ng-content></ng-content>', inputs: ['disabled', 'selected', 'value'] })
@@ -954,6 +954,7 @@ ion-select-option,prop,value,any,undefined,false,false

ion-select,shadow
ion-select,prop,cancelText,string,'Cancel',false,false
ion-select,prop,compareWith,((currentValue: any, compareValue: any) => boolean) | null | string | undefined,undefined,false,false
ion-select,prop,disabled,boolean,false,false,false
ion-select,prop,interface,"action-sheet" | "alert" | "popover",'alert',false,false
ion-select,prop,interfaceOptions,any,{},false,false
@@ -77,6 +77,9 @@ import {
import {
EventEmitter,
} from '@stencil/core';
import {
SelectCompareFn,
} from './components/select/select-interface';


export namespace Components {
@@ -3993,6 +3996,10 @@ export namespace Components {
*/
'cancelText': string;
/**
* A property name or function used to compare object values
*/
'compareWith'?: string | SelectCompareFn | null;
/**
* If `true`, the user cannot interact with the select.
*/
'disabled': boolean;
@@ -4043,6 +4050,10 @@ export namespace Components {
*/
'cancelText'?: string;
/**
* A property name or function used to compare object values
*/
'compareWith'?: string | SelectCompareFn | null;
/**
* If `true`, the user cannot interact with the select.
*/
'disabled'?: boolean;

Large diffs are not rendered by default.

@@ -1,5 +1,7 @@
export type SelectInterface = 'action-sheet' | 'popover' | 'alert';

export type SelectCompareFn = (currentValue: any, compareValue: any) => boolean;

export interface SelectChangeEventDetail {
value: any | any[] | undefined | null;
}
@@ -4,6 +4,8 @@ import { ActionSheetButton, ActionSheetOptions, AlertOptions, CssClassMap, Mode,
import { findItemLabel, renderHiddenInput } from '../../utils/helpers';
import { hostContext } from '../../utils/theme';

import { SelectCompareFn } from './select-interface';

@Component({
tag: 'ion-select',
styleUrls: {
@@ -82,6 +84,11 @@ export class Select implements ComponentInterface {
*/
@Prop() interfaceOptions: any = {};

/**
* A property name or function used to compare object values
*/
@Prop() compareWith?: string | SelectCompareFn | null;

/**
* the value of the select.
*/
@@ -346,7 +353,7 @@ export class Select implements ComponentInterface {
// iterate all options, updating the selected prop
let canSelect = true;
for (const selectOption of this.childOpts) {
const selected = canSelect && isOptionSelected(this.value, selectOption.value);
const selected = canSelect && isOptionSelected(this.value, selectOption.value, this.compareWith);
selectOption.selected = selected;

// if current option is selected and select is single-option, we can't select
@@ -370,7 +377,7 @@ export class Select implements ComponentInterface {
if (selectedText != null && selectedText !== '') {
return selectedText;
}
return generateText(this.childOpts, this.value);
return generateText(this.childOpts, this.value, this.compareWith);
}

private setFocus() {
@@ -468,33 +475,45 @@ function parseValue(value: any) {
return value.toString();
}

function isOptionSelected(currentValue: any[] | any, optionValue: any) {
function isOptionSelected(currentValue: any[] | any, compareValue: any, compareWith?: string | SelectCompareFn | null) {
if (currentValue === undefined) {
return false;
}
if (Array.isArray(currentValue)) {
return currentValue.includes(optionValue);
return currentValue.some(val => compareOptions(val, compareValue, compareWith));
} else {
return compareOptions(currentValue, compareValue, compareWith);
}
}

function compareOptions(currentValue: any, compareValue: any, compareWith?: string | SelectCompareFn | null): boolean {
if (typeof compareWith === 'function') {
return compareWith(currentValue, compareValue);
} else if (typeof compareWith === 'string') {
return currentValue[compareWith] === compareValue[compareWith];
} else {
return currentValue === optionValue;
return currentValue === compareValue;
}
}

function generateText(opts: HTMLIonSelectOptionElement[], value: any | any[]) {
function generateText(opts: HTMLIonSelectOptionElement[], value: any | any[], compareWith?: string | SelectCompareFn | null) {
if (value === undefined) {
return '';
}
if (Array.isArray(value)) {
return value
.map(v => textForValue(opts, v))
.map(v => textForValue(opts, v, compareWith))
.filter(opt => opt !== null)
.join(', ');
} else {
return textForValue(opts, value) || '';
return textForValue(opts, value, compareWith) || '';
}
}

function textForValue(opts: HTMLIonSelectOptionElement[], value: any): string | null {
const selectOpt = opts.find(opt => opt.value === value);
function textForValue(opts: HTMLIonSelectOptionElement[], value: any, compareWith?: string | SelectCompareFn | null): string | null {
const selectOpt = opts.find(opt => {
return compareOptions(opt.value, value, compareWith);
});
return selectOpt
? selectOpt.textContent
: null;
@@ -55,6 +55,15 @@

</ion-list>

<ion-list>
<ion-list-header>Object Values with trackBy</ion-list-header>

<ion-item>
<ion-label>Users</ion-label>
<ion-select id="objectSelectCompareWith"></ion-select>
</ion-item>
</ion-list>

<ion-list>
<ion-list-header>Select - Custom Interface Options</ion-list-header>

@@ -280,6 +289,48 @@
</style>

<script>
let objectOptions = [
{
id: 1,
first: 'Alice',
last: 'Smith',
},
{
id: 2,
first: 'Bob',
last: 'Davis',
},
{
id: 3,
first: 'Charlie',
last: 'Rosenburg',
}
];
let compareWithFn = (o1, o2) => {
return o1 && o2 ? o1.id === o2.id : o1 === o2;
};
let objectSelectElement = document.getElementById('objectSelectCompareWith');
objectSelectElement.compareWith = compareWithFn; // 'id';
objectOptions.forEach((option, i) => {
let selectOption = document.createElement('ion-select-option');
selectOption.value = option;
selectOption.textContent = option.first + ' ' + option.last;
selectOption.selected = (i === 0);
objectSelectElement.appendChild(selectOption)
});
setTimeout(() => {
objectSelectElement.value = {
id: 1,
first: 'Alice',
last: 'Smith',
};
}, 3000);
var pets = document.getElementById('pets');
pets.value = ['bird', 'dog'];
@@ -152,4 +152,4 @@
</ion-app>
</body>

</html>
</html>
@@ -59,6 +59,56 @@
</ion-list>
```

## Objects as Values

```html
<ion-list>
<ion-list-header>Objects as Values (compareWith)</ion-list-header>

<ion-item>
<ion-label>Users</ion-label>
<ion-select [compareWith]="compareWith">
<ion-select-option *ngFor="let user of users">{{user.first + ' ' + user.last}}</ion-select-option>
</ion-select>
</ion-item>
</ion-list>
```

```typescript
import { Component } from '@angular/core';
@Component({
selector: 'select-example',
templateUrl: 'select-example.html',
styleUrls: ['./select-example.css'],
})
export class SelectExample {
users: any[] = [
{
id: 1,
first: 'Alice',
last: 'Smith',
},
{
id: 2,
first: 'Bob',
last: 'Davis',
},
{
id: 3,
first: 'Charlie',
last: 'Rosenburg',
}
];
compareWithFn = (o1, o2) => {
return o1 && o2 ? o1.id === o2.id : o1 === o2;
};
compareWith = compareWithFn;
}
```

## Interface Options

```html
@@ -59,6 +59,56 @@
</ion-list>
```

## Objects as Values

```html
<ion-list>
<ion-list-header>Objects as Values (compareWith)</ion-list-header>

<ion-item>
<ion-label>Users</ion-label>
<ion-select id="objectSelectCompareWith"></ion-select>
</ion-item>
</ion-list>
```

```javascript
let objectOptions = [
{
id: 1,
first: 'Alice',
last: 'Smith',
},
{
id: 2,
first: 'Bob',
last: 'Davis',
},
{
id: 3,
first: 'Charlie',
last: 'Rosenburg',
}
];
let compareWithFn = (o1, o2) => {
return o1 && o2 ? o1.id === o2.id : o1 === o2;
};
let objectSelectElement = document.getElementById('objectSelectCompareWith');
objectSelectElement.compareWith = compareWithFn;
objectOptions.forEach((option, i) => {
let selectOption = document.createElement('ion-select-option');
selectOption.value = option;
selectOption.textContent = option.first + ' ' + option.last;
selectOption.selected = (i === 0);
objectSelectElement.appendChild(selectOption)
});
}
```

## Interface Options

```html
@@ -21,6 +21,28 @@ const customActionSheetOptions = {
subHeader: 'Select your favorite color'
};
const objectOptions = [
{
id: 1,
first: 'Alice',
last: 'Smith'
},
{
id: 2,
first: 'Bob',
last: 'Davis'
},
{
id: 3,
first: 'Charlie',
last: 'Rosenburg'
}
];
const compareWith = (o1: any, o2: any) => {
return o1 && o2 ? o1.id === o2.id : o1 === o2;
};
const Example: React.SFC<{}> = () => (
<>
## Single Selection
@@ -81,6 +103,20 @@ const Example: React.SFC<{}> = () => (
</IonSelect>
</IonItem>
</IonList>
## Objects as Values
<IonList>
<IonListHeader>Objects as Values (compareWith)</IonListHeader>
<IonItem>
<IonLabel>Users</IonLabel>
<IonSelect compareWith={compareWith}>
{objectOptions.map((object, i) => {
return <IonSelectOption key={object.id} value={object.id}>{object.first} {object.last}</IonSelectOption>
})}
</IonSelect>
</IonItem>
</IonList>
## Interface Options

0 comments on commit 69ecebb

Please sign in to comment.
You can’t perform that action at this time.