Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

fix(select): Fix empty option stlying issue. #8907

Closed
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 src/components/select/demoBasicUsage/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
<md-input-container>
<label>State</label>
<md-select ng-model="ctrl.userState">
<md-option ng-repeat="state in ctrl.states" value="{{state.abbrev}}" ng-disabled="$index === 1">
<md-option><em>None</em></md-option>
<md-option ng-repeat="state in ctrl.states" ng-value="state.abbrev" ng-disabled="$index === 1">
{{state.abbrev}}
</md-option>
</md-select>
Expand Down
43 changes: 43 additions & 0 deletions src/components/select/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -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**
*
* - `<md-option>`
* - `<md-option value>`
* - `<md-option value="">`
* - `<md-option ng-value>`
* - `<md-option ng-value="">`
*
* **NOT Automatically Applied**
*
* - `<md-option ng-value="1">`
* - `<md-option ng-value="''">`
* - `<md-option ng-value="undefined">`
* - `<md-option value="undefined">` (this evaluates to the string `"undefined"`)
* - <code ng-non-bindable>&lt;md-option ng-value="{{someValueThatMightBeUndefined}}"&gt;</code>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happened here? Is this comment meant to be structured like that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, unfortunately, the double curly braces were being interpreted by Angular and thus it was disappearing from the docs site 😆

*
* **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.
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems to be left over from old code. Needs updating or removal.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed; good catch!

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.
Expand Down Expand Up @@ -856,9 +887,21 @@ function OptionDirective($mdButtonInkRipple, $mdUtil) {
element.append(angular.element('<div class="_md-text">').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];
Expand Down
54 changes: 54 additions & 0 deletions src/components/select/select.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,60 @@ describe('<md-select>', 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"',
'<md-option ng-value="undefined"></md-option><md-option ng-value="1">1</md-option>'
);
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 = [
'<md-option></md-option>',
'<md-option value></md-option>',
'<md-option value>None</md-option>',
'<md-option ng-value></md-option>',
'<md-option ng-value>None</md-option>',
'<md-option value=""></md-option>',
'<md-option ng-value=""></md-option>',
];

templates.forEach(function(template) {
var el = setupSelect('ng-model="$root.value"',
template + '<md-option ng-value="1">1</md-option>'
);
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');
Expand Down