Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(select): Add value retrieval mechanisms to JS API #353

Merged
merged 2 commits into from
Feb 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion demos/select.html
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ <h2>Custom Menu + Native Menu on mobile</h2>
root.addEventListener('MDCSelect:change', function() {
var item = select.selectedOptions[0];
var index = select.selectedIndex;
currentlySelected.textContent = '"' + item.textContent + '" at index ' + index;
currentlySelected.textContent = '"' + item.textContent + '" at index ' + index +
' with value "' + select.value + '"';
});

var demoWrapper = document.getElementById('demo-wrapper');
Expand Down
12 changes: 10 additions & 2 deletions packages/mdc-select/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ import {MDCSelect} from 'mdc-select';

const select = new MDCSelect(document.querySelector('.mdc-select'));
select.listen('MDCSelect:change', () => {
alert(`Selected "${select.selectedOptions[0].textContent}" at index ${select.selectedIndex}"`);
alert(`Selected "${select.selectedOptions[0].textContent}" at index ${select.selectedIndex} ` +
`with value "${select.value}"`);
});
```

Expand Down Expand Up @@ -195,6 +196,7 @@ is outlined below.

| Property Name | Type | Description |
| --- | --- | --- |
| `value` | `string` | _(read-only)_ The `id` of the currently selected option. If no `id` is present on the selected option, its `textContent` is used. Returns an empty string when no option is selected. |
| `options` | `HTMLElement[]` | _(read-only)_ An _array_ of menu items comprising the select's options. |
| `selectedIndex` | `number` | The index of the currently selected option. Set to -1 if no option is currently selected. Changing this property will update the select element. |
| `selectedOptions` | `HTMLElement[]` | _(read-only)_ A NodeList of either the currently selected option, or no elements if nothing is selected. |
Expand Down Expand Up @@ -237,7 +239,7 @@ need it nonetheless.
MDC Select ships with a foundation class that framework authors can use to integrate MDC Select
into their custom components. Note that due to the nature of MDC Select, the adapter is quite
complex. We try to provide as much guidance as possible, but we encourage developers to reach out
to use via GH Issues or on Gitter if they run into problems.
to us via GH Issues if they run into problems.

### Notes for component implementors

Expand Down Expand Up @@ -285,6 +287,8 @@ within `componentDidUpdate`.
| `setSelectedTextContent(selectedTextContent: string) => void` | Sets the text content of the `.mdc-select__selected-text` element to `selectedTextContent`. |
| `getNumberOfOptions() => number` | Returns the number of options contained in the select's menu. |
| `getTextForOptionAtIndex(index: number) => string` | Returns the text content for the option at the specified index within the select's menu. |
| `getValueForOptionAtIndex(index: number) => string` | Returns the value for the option at the specified index within the select's menu. We adhere to the conventions of `HTMLSelectElement` -
as described above - returning the value of the selected option's `id` in place of a `value` attribute and falling back to its `textContent`. Framework implementations may want to customize this method to suit their needs. |
| `setAttrForOptionAtIndex(index: number, attr: string, value: string) => void` | Sets an attribute `attr` to value `value` for the option at the specified index within the select's menu. |
| `rmAttrForOptionAtIndex(index: number, attr: string) => void` | Removes an attribute `attr` for the option at the specified index within the select's menu. |
| `registerMenuInteractionHandler(type: string, handler: EventListener) => void` | Registers an event listener on the menu component's root element. Note that we will always listen for `MDCSimpleMenu:selected` for change events, and `MDCSimpleMenu:cancel` to know that we need to close the menu. If you are using a different events system, you could check the event type for either one of these strings and take the necessary steps to wire it up. |
Expand All @@ -293,6 +297,10 @@ within `componentDidUpdate`.

### The full foundation API

#### MDCSelectFoundation.getValue() => string

Returns the value of the currently selected option, or an empty string if no option is selected.

#### MDCSelectFoundation.getSelectedIndex() => number

Returns the index of the currently selected option. Returns -1 if no option is currently selected.
Expand Down
5 changes: 5 additions & 0 deletions packages/mdc-select/foundation.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export default class MDCSelectFoundation extends MDCFoundation {
setSelectedTextContent: (/* textContent: string */) => {},
getNumberOfOptions: () => /* number */ 0,
getTextForOptionAtIndex: (/* index: number */) => /* string */ '',
getValueForOptionAtIndex: (/* index: number */) => /* string */ '',
setAttrForOptionAtIndex: (/* index: number, attr: string, value: string */) => {},
rmAttrForOptionAtIndex: (/* index: number, attr: string */) => {},
getOffsetTopForOptionAtIndex: (/* index: number */) => /* number */ 0,
Expand Down Expand Up @@ -114,6 +115,10 @@ export default class MDCSelectFoundation extends MDCFoundation {
this.adapter_.deregisterMenuInteractionHandler('MDCSimpleMenu:cancel', this.cancelHandler_);
}

getValue() {
return this.selectedIndex_ >= 0 ? this.adapter_.getValueForOptionAtIndex(this.selectedIndex_) : '';
}

getSelectedIndex() {
return this.selectedIndex_;
}
Expand Down
5 changes: 5 additions & 0 deletions packages/mdc-select/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export class MDCSelect extends MDCComponent {
return new MDCSelect(root);
}

get value() {
return this.foundation_.getValue();
}

get options() {
return this.menu_.items;
}
Expand Down Expand Up @@ -100,6 +104,7 @@ export class MDCSelect extends MDCComponent {
},
getNumberOfOptions: () => this.options.length,
getTextForOptionAtIndex: (index) => this.options[index].textContent,
getValueForOptionAtIndex: (index) => this.options[index].id || this.options[index].textContent,
setAttrForOptionAtIndex: (index, attr, value) => this.options[index].setAttribute(attr, value),
rmAttrForOptionAtIndex: (index, attr) => this.options[index].removeAttribute(attr),
getOffsetTopForOptionAtIndex: (index) => this.options[index].offsetTop,
Expand Down
3 changes: 2 additions & 1 deletion test/unit/helpers/foundation.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export function verifyDefaultAdapter(FoundationClass, expectedMethods) {
const methods = Object.keys(defaultAdapter).filter((k) => typeof defaultAdapter[k] === 'function');

assert.equal(methods.length, Object.keys(defaultAdapter).length, 'Every adapter key must be a function');
assert.deepEqual(methods, expectedMethods);
// Test for equality without requiring that the array be in a specific order
assert.deepEqual(methods.slice().sort(), expectedMethods.slice().sort());
// Test default methods
methods.forEach((m) => assert.doesNotThrow(defaultAdapter[m]));
}
Expand Down
19 changes: 18 additions & 1 deletion test/unit/mdc-select/foundation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ test('default adapter returns a complete adapter implementation', () => {
'isMenuOpen', 'setSelectedTextContent', 'getNumberOfOptions', 'getTextForOptionAtIndex',
'setAttrForOptionAtIndex', 'rmAttrForOptionAtIndex', 'getOffsetTopForOptionAtIndex',
'registerMenuInteractionHandler', 'deregisterMenuInteractionHandler', 'notifyChange',
'getWindowInnerHeight',
'getWindowInnerHeight', 'getValueForOptionAtIndex',
]);
});

Expand Down Expand Up @@ -226,3 +226,20 @@ test('#destroy deregisters all events registered within init()', () => {
);
});
});

test('#getValue() returns the value of the option at the selected index', () => {
const {foundation, mockAdapter} = setupTest();
const opts = ['a', 'SELECTED', 'b'];
const selectedIndex = 1;
td.when(mockAdapter.getNumberOfOptions()).thenReturn(opts.length);
td.when(mockAdapter.getValueForOptionAtIndex(selectedIndex)).thenReturn(opts[selectedIndex]);
td.when(mockAdapter.getTextForOptionAtIndex(selectedIndex)).thenReturn(`${opts[selectedIndex]} text`);

foundation.setSelectedIndex(selectedIndex);
assert.equal(foundation.getValue(), opts[selectedIndex]);
});

test('#getValue() returns an empty string if selected index < 0', () => {
const {foundation} = setupTest();
assert.equal(foundation.getValue(), '');
});
21 changes: 21 additions & 0 deletions test/unit/mdc-select/mdc-select.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class FakeMenu {
bel`<div id="item-1">Item 1</div>`,
bel`<div id="item-2">Item 2</div>`,
bel`<div id="item-3">Item 3</div>`,
bel`<div>Item 4 no id</div>`,
];
this.listen = td.func('menu.listen');
this.unlisten = td.func('menu.unlisten');
Expand Down Expand Up @@ -91,6 +92,15 @@ test('#get/setDisabled', () => {
assert.isOk(component.disabled);
});

test('#get value', () => {
const {component} = setupTest();
assert.equal(component.value, '');
component.selectedIndex = 1;
assert.equal(component.value, 'item-2');
component.selectedIndex = 3;
assert.equal(component.value, 'Item 4 no id');
});

test('#item returns the menu item at the specified index', () => {
const {menu, component} = setupTest();
assert.equal(component.item(1), menu.items[1]);
Expand Down Expand Up @@ -356,3 +366,14 @@ test('adapter#getWindowInnerHeight returns window.innerHeight', () => {
const {component} = setupTest();
assert.equal(component.getDefaultFoundation().adapter_.getWindowInnerHeight(), window.innerHeight);
});

test('adapter#getValueForOptionAtIndex returns the id of the option at the given index', () => {
const {component} = setupTest();
assert.equal(component.getDefaultFoundation().adapter_.getValueForOptionAtIndex(1), 'item-2');
});

test('adapter#getValueForOptionAtIndex returns the textContent of the option at given index when ' +
'no id value present', () => {
const {component} = setupTest();
assert.equal(component.getDefaultFoundation().adapter_.getValueForOptionAtIndex(3), 'Item 4 no id');
});