/*! * Angular Material Design * https://github.com/angular/material * @license MIT * v1.1.3 */ 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", "$parse", "$sce", "$injector"]; 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 When set to true, allows for more than one option to be selected. The model is an array with the selected choices. * @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. The value * will be treated as *text* (not html). * @param {expression=} md-selected-html Expression to be evaluated that will return a string * to be displayed as a placeholder in the select input box when it is closed. The value * will be treated as *html*. The value must either be explicitly marked as trustedHtml or * the ngSanitize module must be loaded. * @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, $parse, $sce, $injector) { 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(',