diff --git a/src/components/select/demoBasicUsage/index.html b/src/components/select/demoBasicUsage/index.html index 2f6c3bda447..3beee57c179 100755 --- a/src/components/select/demoBasicUsage/index.html +++ b/src/components/select/demoBasicUsage/index.html @@ -29,7 +29,8 @@ - + None + {{state.abbrev}} diff --git a/src/components/select/select.js b/src/components/select/select.js index d7e035708b1..51d200757e7 100755 --- a/src/components/select/select.js +++ b/src/components/select/select.js @@ -40,6 +40,32 @@ angular.module('material.components.select', [ * By default, the select will display with an underline to match other form elements. This can be * disabled by applying the `md-no-underline` CSS class. * + * ### Option Params + * + * When applied, `md-option-empty` will mark the option as "empty" allowing the option to clear the + * select and put it back in it's default state. You may supply this attribute on any option you + * wish, however, it is automatically applied to an option whose `value` or `ng-value` are not + * defined. + * + * **Automatically Applied** + * + * - `` + * - `` + * - `` + * - `` + * - `` + * + * **NOT Automatically Applied** + * + * - `` + * - `` + * - `` + * - `` (this evaluates to the string `"undefined"`) + * - <md-option ng-value="{{someValueThatMightBeUndefined}}"> + * + * **Note:** A value of `undefined` ***is considered a valid value*** (and does not auto-apply this + * attribute) since you may wish this to be your "Not Available" or "None" option. + * * @param {expression} ng-model The model! * @param {boolean=} multiple Whether it's multiple. * @param {expression=} md-on-close Expression to be evaluated when the select is closed. @@ -723,6 +749,11 @@ function SelectMenuDirective($parse, $mdUtil, $mdTheming) { // Map the given element to its innerHTML string. If the element has a child ripple // container remove it from the HTML string, before returning the string. mapFn = function(el) { + // If we do not have a `value` or `ng-value`, assume it is an empty option which clears the select + if (el.hasAttribute('md-option-empty')) { + return ''; + } + var html = el.innerHTML; // Remove the ripple container from the selected option, copying it would cause a CSP violation. @@ -856,9 +887,21 @@ function OptionDirective($mdButtonInkRipple, $mdUtil) { element.append(angular.element('
').append(element.contents())); element.attr('tabindex', attr.tabindex || '0'); + + if (!hasDefinedValue(attr)) { + element.attr('md-option-empty', ''); + } + return postLink; } + function hasDefinedValue(attr) { + var value = attr.value; + var ngValue = attr.ngValue; + + return value || ngValue; + } + function postLink(scope, element, attr, ctrls) { var optionCtrl = ctrls[0]; var selectCtrl = ctrls[1]; diff --git a/src/components/select/select.spec.js b/src/components/select/select.spec.js index da2cb422f8b..9295d9ba4e2 100755 --- a/src/components/select/select.spec.js +++ b/src/components/select/select.spec.js @@ -255,6 +255,60 @@ describe('', function() { expect(el).not.toHaveClass('md-input-has-value'); })); + it('should add has-value class on container for option ng-value="undefined"', inject(function($rootScope) { + var el = setupSelect('ng-model="$root.value"', + '1' + ); + var select = el.find('md-select'); + + document.body.appendChild(el[0]); + + openSelect(select); + waitForSelectOpen(); + clickOption(select, 0); + expect(el).toHaveClass('md-input-has-value'); + + openSelect(select); + waitForSelectOpen(); + clickOption(select, 1); + expect(el).toHaveClass('md-input-has-value'); + + el.remove(); + })); + + it('should unset has-value class on container for empty value option', inject(function($rootScope) { + var templates = [ + '', + '', + 'None', + '', + 'None', + '', + '', + ]; + + templates.forEach(function(template) { + var el = setupSelect('ng-model="$root.value"', + template + '1' + ); + var select = el.find('md-select'); + + document.body.appendChild(el[0]); + + openSelect(select); + waitForSelectOpen(); + clickOption(select, 1); + expect(el).toHaveClass('md-input-has-value'); + + openSelect(select); + waitForSelectOpen(); + clickOption(select, 0); + expect(el).not.toHaveClass('md-input-has-value'); + + el.remove(); + }); + })); + it('should match label to given input id', function() { var el = setupSelect('ng-model="$root.value" id="foo"'); expect(el.find('label').attr('for')).toBe('foo');