/*!
* Angular Material Design
* https://github.com/angular/material
* @license MIT
* v1.1.1
*/
goog.provide('ngmaterial.components.select');
goog.require('ngmaterial.components.backdrop');
goog.require('ngmaterial.core');
/**
* @ngdoc module
* @name material.components.select
*/
/***************************************************
### TODO - POST RC1 ###
- [ ] Abstract placement logic in $mdSelect service to $mdMenu service
***************************************************/
SelectDirective.$inject = ["$mdSelect", "$mdUtil", "$mdConstant", "$mdTheming", "$mdAria", "$compile", "$parse"];
SelectMenuDirective.$inject = ["$parse", "$mdUtil", "$mdConstant", "$mdTheming"];
OptionDirective.$inject = ["$mdButtonInkRipple", "$mdUtil"];
SelectProvider.$inject = ["$$interimElementProvider"];
var SELECT_EDGE_MARGIN = 8;
var selectNextId = 0;
var CHECKBOX_SELECTION_INDICATOR =
angular.element('
');
angular.module('material.components.select', [
'material.core',
'material.components.backdrop'
])
.directive('mdSelect', SelectDirective)
.directive('mdSelectMenu', SelectMenuDirective)
.directive('mdOption', OptionDirective)
.directive('mdOptgroup', OptgroupDirective)
.directive('mdSelectHeader', SelectHeaderDirective)
.provider('$mdSelect', SelectProvider);
/**
* @ngdoc directive
* @name mdSelect
* @restrict E
* @module material.components.select
*
* @description Displays a select box, bound to an ng-model.
*
* When the select is required and uses a floating label, then the label will automatically contain
* an asterisk (`*`). This behavior can be disabled by using the `md-no-asterisk` attribute.
*
* 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.
*
* **Note:** Using the `value` attribute (as opposed to `ng-value`) always evaluates to a string, so
* `value="null"` will require the test `ng-if="myValue != 'null'"` rather than `ng-if="!myValue"`.
*
* @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.
* @param {expression=} md-on-open Expression to be evaluated when opening the select.
* Will hide the select options and show a spinner until the evaluated promise resolves.
* @param {expression=} md-selected-text Expression to be evaluated that will return a string
* to be displayed as a placeholder in the select input box when it is closed.
* @param {string=} placeholder Placeholder hint text.
* @param md-no-asterisk {boolean=} When set to true, an asterisk will not be appended to the
* floating label. **Note:** This attribute is only evaluated once; it is not watched.
* @param {string=} aria-label Optional label for accessibility. Only necessary if no placeholder or
* explicit label is present.
* @param {string=} md-container-class Class list to get applied to the `.md-select-menu-container`
* element (for custom styling).
*
* @usage
* With a placeholder (label and aria-label are added dynamically)
*
*
*
* {{ opt }}
*
*
*
*
* With an explicit label
*
*
*
*
* {{ opt }}
*
*
*
*
* With a select-header
*
* When a developer needs to put more than just a text label in the
* md-select-menu, they should use the md-select-header.
* The user can put custom HTML inside of the header and style it to their liking.
* One common use case of this would be a sticky search bar.
*
* When using the md-select-header the labels that would previously be added to the
* OptGroupDirective are ignored.
*
*
*
*
*
* Neighborhoods -
*
* {{ opt }}
*
*
*
*
* ## Selects and object equality
* When using a `md-select` to pick from a list of objects, it is important to realize how javascript handles
* equality. Consider the following example:
*
* angular.controller('MyCtrl', function($scope) {
* $scope.users = [
* { id: 1, name: 'Bob' },
* { id: 2, name: 'Alice' },
* { id: 3, name: 'Steve' }
* ];
* $scope.selectedUser = { id: 1, name: 'Bob' };
* });
*
*
*
*
* {{ user.name }}
*
*
*
*
* At first one might expect that the select should be populated with "Bob" as the selected user. However,
* this is not true. To determine whether something is selected,
* `ngModelController` is looking at whether `$scope.selectedUser == (any user in $scope.users);`;
*
* Javascript's `==` operator does not check for deep equality (ie. that all properties
* on the object are the same), but instead whether the objects are *the same object in memory*.
* In this case, we have two instances of identical objects, but they exist in memory as unique
* entities. Because of this, the select will have no value populated for a selected user.
*
* To get around this, `ngModelController` provides a `track by` option that allows us to specify a different
* expression which will be used for the equality operator. As such, we can update our `html` to
* make use of this by specifying the `ng-model-options="{trackBy: '$value.id'}"` on the `md-select`
* element. This converts our equality expression to be
* `$scope.selectedUser.id == (any id in $scope.users.map(function(u) { return u.id; }));`
* which results in Bob being selected as desired.
*
* Working HTML:
*
*
*
* {{ user.name }}
*
*
*
*/
function SelectDirective($mdSelect, $mdUtil, $mdConstant, $mdTheming, $mdAria, $compile, $parse) {
var keyCodes = $mdConstant.KEY_CODE;
var NAVIGATION_KEYS = [keyCodes.SPACE, keyCodes.ENTER, keyCodes.UP_ARROW, keyCodes.DOWN_ARROW];
return {
restrict: 'E',
require: ['^?mdInputContainer', 'mdSelect', 'ngModel', '?^form'],
compile: compile,
controller: function() {
} // empty placeholder controller to be initialized in link
};
function compile(element, attr) {
// add the select value that will hold our placeholder or selected option value
var valueEl = angular.element('');
valueEl.append('');
valueEl.addClass('md-select-value');
if (!valueEl[0].hasAttribute('id')) {
valueEl.attr('id', 'select_value_label_' + $mdUtil.nextUid());
}
// There's got to be an md-content inside. If there's not one, let's add it.
if (!element.find('md-content').length) {
element.append(angular.element('').append(element.contents()));
}
// Add progress spinner for md-options-loading
if (attr.mdOnOpen) {
// Show progress indicator while loading async
// Use ng-hide for `display:none` so the indicator does not interfere with the options list
element
.find('md-content')
.prepend(angular.element(
'
' +
' ' +
'
'
));
// Hide list [of item options] while loading async
element
.find('md-option')
.attr('ng-show', '$$loadingAsyncDone');
}
if (attr.name) {
var autofillClone = angular.element('