Skip to content
This repository was archived by the owner on Oct 7, 2020. It is now read-only.

Commit 7dcb7ab

Browse files
authored
feat(select): Improvements + ngModel fix (#326)
`mdc-select` improvements: * Fix `ngModel` pre-select issues * Add `getValue` method * Add `getSelectedIndex` method * Add `setSelectedIndex` method * Add `setLabel` method * Add `clearSelection` method * Add `setSelectionByValue` method * Add `resize` method `mdc-select-item` improvements: * Add `selected` method * Add `id` property * Add `label` method * Add `select` method * Add `deselect` method * Add `focus` method Closes #319
1 parent c2586ef commit 7dcb7ab

File tree

8 files changed

+175
-62
lines changed

8 files changed

+175
-62
lines changed

src/demo-app/components/inputs-controls/select-demo/select-demo.html

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div fxLayout="column" fxLayoutAlign="start start" class="mdc-padding">
22
<h1 mdc-typography-display1>Select Menus</h1>
3-
<div mdc-typography-subheading2>MDC Select provides Material Design single-option and multi-option select menus.</div>
3+
<div mdc-typography-subheading2>MDC Select provides Material Design single-option select menus.</div>
44
<div class="info-banner" mdc-typography-subheading1>
55
<![CDATA[import { MdcSelectModule } from '@angular-mdc/web';]]>
66
</div>
@@ -91,6 +91,22 @@ <h1 mdc-typography-display1>Select Menus</h1>
9191
<td>setDisabled(disabled: boolean)</td>
9292
<td>Enables/disables the select.</td>
9393
</tr>
94+
<tr>
95+
<td>setLabel(text: string)</td>
96+
<td>Sets the label text.</td>
97+
</tr>
98+
<tr>
99+
<td>clearSelection()</td>
100+
<td>Clears the current selected item.</td>
101+
</tr>
102+
<tr>
103+
<td>setSelectionByValue(value)</td>
104+
<td>Sets the selected item by value.</td>
105+
</tr>
106+
<tr>
107+
<td>resize()</td>
108+
<td>Resizes the menu list if styling required.</td>
109+
</tr>
94110
</tbody>
95111
<thead>
96112
<tr>
@@ -135,6 +151,37 @@ <h1 mdc-typography-display1>Select Menus</h1>
135151
<td>Disable selection of the select item.</td>
136152
</tr>
137153
</tbody>
154+
<thead>
155+
<tr>
156+
<th>Methods</th>
157+
<th>Parameters</th>
158+
</tr>
159+
</thead>
160+
<tr>
161+
<td>id(): string</td>
162+
<td>The unique ID of the option.</td>
163+
</tr>
164+
<tr>
165+
<td>selected(): boolean</td>
166+
<td>Whether or not the option is currently selected.</td>
167+
</tr>
168+
<tr>
169+
<td>label(): string</td>
170+
<td>The displayed label of the option.</td>
171+
</tr>
172+
<tr>
173+
<td>select()</td>
174+
<td>Selects the option.</td>
175+
</tr>
176+
<tr>
177+
<td>deselect()</td>
178+
<td>Deselects the option.</td>
179+
</tr>
180+
<tr>
181+
<td>focus()</td>
182+
<td>Sets focus onto this option.</td>
183+
</tr>
184+
<tbody>
138185
</table>
139186
</div>
140187
</div>

src/demo-app/components/inputs-controls/textfield-demo/textfield-demo.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import { Component } from '@angular/core';
66
})
77
export class TextfieldDemo {
88
username: string;
9-
prefill:string = 'John Doe';
10-
comments:string;
11-
subject:string;
12-
message:string;
9+
prefill: string = 'John Doe';
10+
comments: string;
11+
subject: string;
12+
message: string;
1313
isDisabled = false;
1414
isRequired = true;
1515
isDense = false;

src/demo-app/components/menu-demo/menu-demo.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ <h1 mdc-typography-headline>API reference</h1>
6565
<td>focus()</td>
6666
<td>Set focus to the menu element.</td>
6767
</tr>
68+
<tr>
69+
<td>getFocusedItemIndex(): number</td>
70+
<td>Returns the index of the currently focused menu item (-1 if none).</td>
71+
</tr>
6872
</tbody>
6973
<thead>
7074
<tr>

src/demo-app/components/menu-demo/menu-demo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { MdcMenuComponent } from '../../../lib/public_api';
1212
export class MenuDemo {
1313
selectedIndex = -1;
1414
openingPoint: string = "topLeft";
15-
@ViewChild('menu') menu: MdcMenuComponent;
15+
@ViewChild('menu') menu: MdcMenu;
1616

1717
showMenu() {
1818
this.menu.open();

src/demo-app/sass/main.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ body {
141141
color: rgba(0, 0, 0, .87);
142142
border-collapse: collapse;
143143
border-spacing: 0;
144-
box-shadow: 0 2px 2px rgba(0, 0, 0, .24), 0 0 2px rgba(0, 0, 0, .12);
144+
box-shadow: 0 1px 1px rgba(0, 0, 0, .24), 0 0 1px rgba(0, 0, 0, .12);
145145

146146
tbody tr td:first-child {
147147
min-width: 5em;

src/lib/select/index.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,14 @@ import { FormsModule } from '@angular/forms';
44

55
import {
66
MdcSelect,
7-
MdcSelectedText,
87
MdcSelectItem,
98
MdcSelectItems,
109
MdcSelectLabel,
1110
MdcSelectMenu,
12-
} from './select.component';
11+
} from './select';
1312

1413
export const SELECT_COMPONENTS = [
1514
MdcSelect,
16-
MdcSelectedText,
1715
MdcSelectItem,
1816
MdcSelectItems,
1917
MdcSelectLabel,
@@ -27,4 +25,4 @@ export const SELECT_COMPONENTS = [
2725
})
2826
export class MdcSelectModule { }
2927

30-
export * from './select.component';
28+
export * from './select';

src/lib/select/select.component.ts renamed to src/lib/select/select.ts

Lines changed: 86 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export interface MdcSelectedData {
4141
}
4242

4343
let nextUniqueId = 0;
44+
let uniqueIdCounter = 0;
4445

4546
@Directive({
4647
selector: 'mdc-select-label'
@@ -51,15 +52,6 @@ export class MdcSelectLabel {
5152
constructor(public elementRef: ElementRef) { }
5253
}
5354

54-
@Directive({
55-
selector: 'mdc-selected-text'
56-
})
57-
export class MdcSelectedText {
58-
@HostBinding('class.mdc-select__selected-text') isHostClass = true;
59-
60-
constructor(public elementRef: ElementRef) { }
61-
}
62-
6355
@Directive({
6456
selector: 'mdc-select-menu',
6557
})
@@ -81,30 +73,56 @@ export class MdcSelectItems {
8173
}
8274

8375
@Directive({
84-
selector: 'mdc-select-item'
76+
selector: 'mdc-select-item',
77+
host: {
78+
'[id]': 'id',
79+
'role': 'option',
80+
'[attr.aria-selected]': 'selected',
81+
'[attr.aria-disabled]': 'disabled',
82+
}
8583
})
8684
export class MdcSelectItem {
85+
private _selected = false;
8786
private _disabled: boolean = false;
87+
private _id = `mdc-select-item-${uniqueIdCounter++}`;
8888

89-
@Input() value: string;
89+
/** The unique ID of the option. */
90+
get id(): string { return this._id; }
91+
92+
/** Whether or not the option is currently selected. */
93+
get selected(): boolean { return this._selected; }
94+
95+
/** The displayed label of the option. */
96+
get label(): string {
97+
return (this.elementRef.nativeElement.textContent || '').trim();
98+
}
99+
100+
@Input() value: any;
90101
@Input()
91102
get disabled(): boolean {
92103
return this._disabled;
93104
}
94105
set disabled(value: boolean) {
95-
this._disabled = value;
96-
if (value) {
97-
this._renderer.setAttribute(this.elementRef.nativeElement, 'aria-disabled', 'true');
98-
this.tabIndex = -1;
99-
} else {
100-
this._renderer.removeAttribute(this.elementRef.nativeElement, 'aria-disabled');
101-
this.tabIndex = 0;
102-
}
106+
this._disabled = toBoolean(value);
107+
value ? this.tabIndex = -1 : this.tabIndex = 0;
103108
}
104109
@HostBinding('class.mdc-list-item') isHostClass = true;
105-
@HostBinding('attr.role') role: string = 'option';
106110
@HostBinding('tabindex') tabIndex: number = 0;
107111

112+
/** Selects the option. */
113+
select(): void {
114+
this._selected = true;
115+
}
116+
117+
/** Deselects the option. */
118+
deselect(): void {
119+
this._selected = false;
120+
}
121+
122+
focus(): void {
123+
this.elementRef.nativeElement.focus();
124+
}
125+
108126
constructor(
109127
private _renderer: Renderer2,
110128
public elementRef: ElementRef) { }
@@ -117,8 +135,7 @@ export class MdcSelectItem {
117135
},
118136
template:
119137
`
120-
<mdc-select-label *ngIf="!value">{{label}}</mdc-select-label>
121-
<mdc-selected-text>{{selectedText}}</mdc-selected-text>
138+
<mdc-select-label>{{label}}</mdc-select-label>
122139
<mdc-select-menu>
123140
<mdc-select-items>
124141
<ng-content></ng-content>
@@ -133,22 +150,28 @@ export class MdcSelectItem {
133150
export class MdcSelect implements AfterViewInit, ControlValueAccessor, OnChanges, OnDestroy {
134151
private _itemsSubscription: ISubscription;
135152
private _scrollStream: ISubscription;
136-
private _open: boolean = false;
137153
private _label: string = '';
138-
private _value: string = '';
154+
private _value: any;
139155
private _uniqueId: string = `mdc-select-${++nextUniqueId}`;
140156
private _menuFactory: any;
141-
private _controlValueAccessorChangeFn: (value: any) => void = () => { };
142-
onTouched: () => any = () => { };
143-
selectedText: string = '';
157+
private _controlValueAccessorChangeFn: (value: any) => void = (value) => { };
158+
private _onChange: (value: any) => void = () => { };
159+
private _onTouched = () => { };
144160

145161
@Input() id: string = this._uniqueId;
146162
@Input() name: string | null = null;
147-
@Input() label: string = '';
148163
@Input()
149-
get value(): string { return this._foundation.getValue(); }
150-
set value(v: string) {
151-
this._value = v;
164+
get label(): string { return this._label; }
165+
set label(value: string) {
166+
this._label = value;
167+
}
168+
@Input()
169+
get value(): any { return this._value; }
170+
set value(newValue: any) {
171+
if (newValue !== this._value) {
172+
this.writeValue(newValue);
173+
this._value = newValue;
174+
}
152175
}
153176
@Input()
154177
get disabled(): boolean { return this.isDisabled(); }
@@ -215,7 +238,7 @@ export class MdcSelect implements AfterViewInit, ControlValueAccessor, OnChanges
215238
},
216239
isMenuOpen: () => this._menuFactory.open,
217240
setSelectedTextContent: (textContent: string) => {
218-
this.selectedText = textContent;
241+
this._label = textContent;
219242
},
220243
getNumberOfOptions: () => {
221244
return this.options ? this.options.length : 0;
@@ -247,6 +270,7 @@ export class MdcSelect implements AfterViewInit, ControlValueAccessor, OnChanges
247270
value: this._foundation.getValue(),
248271
});
249272
this._controlValueAccessorChangeFn(this._foundation.getValue());
273+
this.setSelectedIndex(this._foundation.getSelectedIndex());
250274
},
251275
getWindowInnerHeight: () => isBrowser() ? window.innerHeight : 0,
252276
};
@@ -271,8 +295,8 @@ export class MdcSelect implements AfterViewInit, ControlValueAccessor, OnChanges
271295
this._itemsSubscription = this.options.changes.subscribe(_ => {
272296
this._foundation.resize();
273297
});
274-
this._foundation.init();
275298
this._menuFactory = new MDCSimpleMenu(this.selectMenu.elementRef.nativeElement);
299+
this._foundation.init();
276300
}
277301

278302
ngOnDestroy() {
@@ -288,10 +312,10 @@ export class MdcSelect implements AfterViewInit, ControlValueAccessor, OnChanges
288312
}
289313

290314
ngOnChanges(changes: { [key: string]: SimpleChange }) {
291-
let closeOnScroll = changes['closeOnScroll'];
315+
let _closeOnScroll = changes['closeOnScroll'];
292316

293-
if (closeOnScroll && isBrowser()) {
294-
if (closeOnScroll.currentValue && (!this._scrollStream || this._scrollStream.closed)) {
317+
if (_closeOnScroll && isBrowser()) {
318+
if (_closeOnScroll.currentValue && (!this._scrollStream || this._scrollStream.closed)) {
295319
this._scrollStream = Observable.fromEvent(window, 'scroll')
296320
.subscribe(res => {
297321
if (this._mdcAdapter.isMenuOpen()) {
@@ -306,28 +330,47 @@ export class MdcSelect implements AfterViewInit, ControlValueAccessor, OnChanges
306330
}
307331
}
308332

309-
writeValue(value: any) {
310-
this.value = value;
333+
writeValue(value: any): void {
334+
if (this.options) {
335+
this.setSelectionByValue(value);
336+
}
311337
}
312338

313-
registerOnChange(fn: (value: any) => void) {
339+
registerOnChange(fn: (value: any) => void): void {
314340
this._controlValueAccessorChangeFn = fn;
315341
}
316342

317-
registerOnTouched(fn: any) {
318-
this.onTouched = fn;
343+
registerOnTouched(fn: () => {}): void {
344+
this._onTouched = fn;
319345
}
320346

321347
getValue(): string {
322348
return this._foundation.getValue();
323349
}
324350

351+
setLabel(text: string): void {
352+
this._label = text;
353+
}
354+
325355
getSelectedIndex(): number {
326356
return this._foundation.getSelectedIndex();
327357
}
328358

329-
setSelectedIndex(selectedIndex: number): void {
330-
this._foundation.setSelectedIndex(selectedIndex);
359+
clearSelection(): void {
360+
this.options.forEach((_) => _.deselect);
361+
}
362+
363+
setSelectionByValue(value: any): void {
364+
this.clearSelection();
365+
if (value) {
366+
this.setSelectedIndex(this.options.toArray().findIndex((_) => _.value == value));
367+
}
368+
}
369+
370+
setSelectedIndex(index: number): void {
371+
this.clearSelection();
372+
this._foundation.setSelectedIndex(index);
373+
this.options.toArray()[this._foundation.getSelectedIndex()].select();
331374
}
332375

333376
open(index: number = 0): void {

0 commit comments

Comments
 (0)