diff options
author | 2017-03-08 11:54:56 +0530 | |
---|---|---|
committer | 2017-03-08 11:54:56 +0530 | |
commit | 80b299e8c4f290e3b16f35eea922cac989b6e767 (patch) | |
tree | b99086734507728ae096349cca4d0258335dbf4f /vnfmarket/common/thirdparty/angular-material/modules/closure/select/select.js | |
parent | 5fa92b4eb456b5f4959c39578a3c6e3555c4ae7d (diff) |
VnfMarket place refactor
IssueId : CLIENT-4
Change-Id: Ia1b076400f9c3bedf9db714099a608ece392aa59
Signed-off-by: seshukm <seshu.kumar.m@huawei.com>
Diffstat (limited to 'vnfmarket/common/thirdparty/angular-material/modules/closure/select/select.js')
-rw-r--r-- | vnfmarket/common/thirdparty/angular-material/modules/closure/select/select.js | 1694 |
1 files changed, 0 insertions, 1694 deletions
diff --git a/vnfmarket/common/thirdparty/angular-material/modules/closure/select/select.js b/vnfmarket/common/thirdparty/angular-material/modules/closure/select/select.js deleted file mode 100644 index e1c57939..00000000 --- a/vnfmarket/common/thirdparty/angular-material/modules/closure/select/select.js +++ /dev/null @@ -1,1694 +0,0 @@ -/*! - * 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('<div class="md-container"><div class="md-icon"></div></div>'); - -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** - * - * - `<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><md-option ng-value="{{someValueThatMightBeUndefined}}"></code> - * - * **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) - * <hljs lang="html"> - * <md-input-container> - * <md-select - * ng-model="someModel" - * placeholder="Select a state"> - * <md-option ng-value="opt" ng-repeat="opt in neighborhoods2">{{ opt }}</md-option> - * </md-select> - * </md-input-container> - * </hljs> - * - * With an explicit label - * <hljs lang="html"> - * <md-input-container> - * <label>State</label> - * <md-select - * ng-model="someModel"> - * <md-option ng-value="opt" ng-repeat="opt in neighborhoods2">{{ opt }}</md-option> - * </md-select> - * </md-input-container> - * </hljs> - * - * 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. - * - * <hljs lang="html"> - * <md-input-container> - * <md-select ng-model="someModel"> - * <md-select-header> - * <span> Neighborhoods - </span> - * </md-select-header> - * <md-option ng-value="opt" ng-repeat="opt in neighborhoods2">{{ opt }}</md-option> - * </md-select> - * </md-input-container> - * </hljs> - * - * ## 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: - * <hljs lang="js"> - * 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' }; - * }); - * </hljs> - * <hljs lang="html"> - * <div ng-controller="MyCtrl"> - * <md-select ng-model="selectedUser"> - * <md-option ng-value="user" ng-repeat="user in users">{{ user.name }}</md-option> - * </md-select> - * </div> - * </hljs> - * - * 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: - * <hljs lang="html"> - * <div ng-controller="MyCtrl"> - * <md-select ng-model="selectedUser" ng-model-options="{trackBy: '$value.id'}"> - * <md-option ng-value="user" ng-repeat="user in users">{{ user.name }}</md-option> - * </md-select> - * </div> - * </hljs> - */ -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('<md-select-value><span></span></md-select-value>'); - valueEl.append('<span class="md-select-icon" aria-hidden="true"></span>'); - 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('<md-content>').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( - '<div>' + - ' <md-progress-circular md-mode="indeterminate" ng-if="$$loadingAsyncDone === false" md-diameter="25px"></md-progress-circular>' + - '</div>' - )); - - // Hide list [of item options] while loading async - element - .find('md-option') - .attr('ng-show', '$$loadingAsyncDone'); - } - - if (attr.name) { - var autofillClone = angular.element('<select class="md-visually-hidden">'); - autofillClone.attr({ - 'name': attr.name, - 'aria-hidden': 'true', - 'tabindex': '-1' - }); - var opts = element.find('md-option'); - angular.forEach(opts, function(el) { - var newEl = angular.element('<option>' + el.innerHTML + '</option>'); - if (el.hasAttribute('ng-value')) newEl.attr('ng-value', el.getAttribute('ng-value')); - else if (el.hasAttribute('value')) newEl.attr('value', el.getAttribute('value')); - autofillClone.append(newEl); - }); - - // Adds an extra option that will hold the selected value for the - // cases where the select is a part of a non-angular form. This can be done with a ng-model, - // however if the `md-option` is being `ng-repeat`-ed, Angular seems to insert a similar - // `option` node, but with a value of `? string: <value> ?` which would then get submitted. - // This also goes around having to prepend a dot to the name attribute. - autofillClone.append( - '<option ng-value="' + attr.ngModel + '" selected></option>' - ); - - element.parent().append(autofillClone); - } - - var isMultiple = $mdUtil.parseAttributeBoolean(attr.multiple); - - // Use everything that's left inside element.contents() as the contents of the menu - var multipleContent = isMultiple ? 'multiple' : ''; - var selectTemplate = '' + - '<div class="md-select-menu-container" aria-hidden="true">' + - '<md-select-menu {0}>{1}</md-select-menu>' + - '</div>'; - - selectTemplate = $mdUtil.supplant(selectTemplate, [multipleContent, element.html()]); - element.empty().append(valueEl); - element.append(selectTemplate); - - if(!attr.tabindex){ - attr.$set('tabindex', 0); - } - - return function postLink(scope, element, attr, ctrls) { - var untouched = true; - var isDisabled, ariaLabelBase; - - var containerCtrl = ctrls[0]; - var mdSelectCtrl = ctrls[1]; - var ngModelCtrl = ctrls[2]; - var formCtrl = ctrls[3]; - // grab a reference to the select menu value label - var valueEl = element.find('md-select-value'); - var isReadonly = angular.isDefined(attr.readonly); - var disableAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk); - - if (disableAsterisk) { - element.addClass('md-no-asterisk'); - } - - if (containerCtrl) { - var isErrorGetter = containerCtrl.isErrorGetter || function() { - return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (formCtrl && formCtrl.$submitted)); - }; - - if (containerCtrl.input) { - // We ignore inputs that are in the md-select-header (one - // case where this might be useful would be adding as searchbox) - if (element.find('md-select-header').find('input')[0] !== containerCtrl.input[0]) { - throw new Error("<md-input-container> can only have *one* child <input>, <textarea> or <select> element!"); - } - } - - containerCtrl.input = element; - if (!containerCtrl.label) { - $mdAria.expect(element, 'aria-label', element.attr('placeholder')); - } - - scope.$watch(isErrorGetter, containerCtrl.setInvalid); - } - - var selectContainer, selectScope, selectMenuCtrl; - - findSelectContainer(); - $mdTheming(element); - - if (formCtrl && angular.isDefined(attr.multiple)) { - $mdUtil.nextTick(function() { - var hasModelValue = ngModelCtrl.$modelValue || ngModelCtrl.$viewValue; - if (hasModelValue) { - formCtrl.$setPristine(); - } - }); - } - - var originalRender = ngModelCtrl.$render; - ngModelCtrl.$render = function() { - originalRender(); - syncLabelText(); - syncAriaLabel(); - inputCheckValue(); - }; - - attr.$observe('placeholder', ngModelCtrl.$render); - - if (containerCtrl && containerCtrl.label) { - attr.$observe('required', function (value) { - // Toggle the md-required class on the input containers label, because the input container is automatically - // applying the asterisk indicator on the label. - containerCtrl.label.toggleClass('md-required', value && !disableAsterisk); - }); - } - - mdSelectCtrl.setLabelText = function(text) { - mdSelectCtrl.setIsPlaceholder(!text); - - // Whether the select label has been given via user content rather than the internal - // template of <md-option> - var isSelectLabelFromUser = false; - - if (attr.mdSelectedText && attr.mdSelectedHtml) { - throw Error('md-select cannot have both `md-selected-text` and `md-selected-html`'); - } - - if (attr.mdSelectedText || attr.mdSelectedHtml) { - text = $parse(attr.mdSelectedText || attr.mdSelectedHtml)(scope); - isSelectLabelFromUser = true; - } else if (!text) { - // Use placeholder attribute, otherwise fallback to the md-input-container label - var tmpPlaceholder = attr.placeholder || - (containerCtrl && containerCtrl.label ? containerCtrl.label.text() : ''); - - text = tmpPlaceholder || ''; - isSelectLabelFromUser = true; - } - - var target = valueEl.children().eq(0); - - if (attr.mdSelectedHtml) { - // Using getTrustedHtml will run the content through $sanitize if it is not already - // explicitly trusted. If the ngSanitize module is not loaded, this will - // *correctly* throw an sce error. - target.html($sce.getTrustedHtml(text)); - } else if (isSelectLabelFromUser) { - target.text(text); - } else { - // If we've reached this point, the text is not user-provided. - target.html(text); - } - }; - - mdSelectCtrl.setIsPlaceholder = function(isPlaceholder) { - if (isPlaceholder) { - valueEl.addClass('md-select-placeholder'); - if (containerCtrl && containerCtrl.label) { - containerCtrl.label.addClass('md-placeholder'); - } - } else { - valueEl.removeClass('md-select-placeholder'); - if (containerCtrl && containerCtrl.label) { - containerCtrl.label.removeClass('md-placeholder'); - } - } - }; - - if (!isReadonly) { - element - .on('focus', function(ev) { - // Always focus the container (if we have one) so floating labels and other styles are - // applied properly - containerCtrl && containerCtrl.setFocused(true); - }); - - // Attach before ngModel's blur listener to stop propagation of blur event - // to prevent from setting $touched. - element.on('blur', function(event) { - if (untouched) { - untouched = false; - if (selectScope._mdSelectIsOpen) { - event.stopImmediatePropagation(); - } - } - - if (selectScope._mdSelectIsOpen) return; - containerCtrl && containerCtrl.setFocused(false); - inputCheckValue(); - }); - } - - mdSelectCtrl.triggerClose = function() { - $parse(attr.mdOnClose)(scope); - }; - - scope.$$postDigest(function() { - initAriaLabel(); - syncLabelText(); - syncAriaLabel(); - }); - - function initAriaLabel() { - var labelText = element.attr('aria-label') || element.attr('placeholder'); - if (!labelText && containerCtrl && containerCtrl.label) { - labelText = containerCtrl.label.text(); - } - ariaLabelBase = labelText; - $mdAria.expect(element, 'aria-label', labelText); - } - - scope.$watch(function() { - return selectMenuCtrl.selectedLabels(); - }, syncLabelText); - - function syncLabelText() { - if (selectContainer) { - selectMenuCtrl = selectMenuCtrl || selectContainer.find('md-select-menu').controller('mdSelectMenu'); - mdSelectCtrl.setLabelText(selectMenuCtrl.selectedLabels()); - } - } - - function syncAriaLabel() { - if (!ariaLabelBase) return; - var ariaLabels = selectMenuCtrl.selectedLabels({mode: 'aria'}); - element.attr('aria-label', ariaLabels.length ? ariaLabelBase + ': ' + ariaLabels : ariaLabelBase); - } - - var deregisterWatcher; - attr.$observe('ngMultiple', function(val) { - if (deregisterWatcher) deregisterWatcher(); - var parser = $parse(val); - deregisterWatcher = scope.$watch(function() { - return parser(scope); - }, function(multiple, prevVal) { - if (multiple === undefined && prevVal === undefined) return; // assume compiler did a good job - if (multiple) { - element.attr('multiple', 'multiple'); - } else { - element.removeAttr('multiple'); - } - element.attr('aria-multiselectable', multiple ? 'true' : 'false'); - if (selectContainer) { - selectMenuCtrl.setMultiple(multiple); - originalRender = ngModelCtrl.$render; - ngModelCtrl.$render = function() { - originalRender(); - syncLabelText(); - syncAriaLabel(); - inputCheckValue(); - }; - ngModelCtrl.$render(); - } - }); - }); - - attr.$observe('disabled', function(disabled) { - if (angular.isString(disabled)) { - disabled = true; - } - // Prevent click event being registered twice - if (isDisabled !== undefined && isDisabled === disabled) { - return; - } - isDisabled = disabled; - if (disabled) { - element - .attr({'aria-disabled': 'true'}) - .removeAttr('tabindex') - .off('click', openSelect) - .off('keydown', handleKeypress); - } else { - element - .attr({'tabindex': attr.tabindex, 'aria-disabled': 'false'}) - .on('click', openSelect) - .on('keydown', handleKeypress); - } - }); - - if (!attr.hasOwnProperty('disabled') && !attr.hasOwnProperty('ngDisabled')) { - element.attr({'aria-disabled': 'false'}); - element.on('click', openSelect); - element.on('keydown', handleKeypress); - } - - var ariaAttrs = { - role: 'listbox', - 'aria-expanded': 'false', - 'aria-multiselectable': isMultiple && !attr.ngMultiple ? 'true' : 'false' - }; - - if (!element[0].hasAttribute('id')) { - ariaAttrs.id = 'select_' + $mdUtil.nextUid(); - } - - var containerId = 'select_container_' + $mdUtil.nextUid(); - selectContainer.attr('id', containerId); - ariaAttrs['aria-owns'] = containerId; - element.attr(ariaAttrs); - - scope.$on('$destroy', function() { - $mdSelect - .destroy() - .finally(function() { - if (containerCtrl) { - containerCtrl.setFocused(false); - containerCtrl.setHasValue(false); - containerCtrl.input = null; - } - ngModelCtrl.$setTouched(); - }); - }); - - - - function inputCheckValue() { - // The select counts as having a value if one or more options are selected, - // or if the input's validity state says it has bad input (eg string in a number input) - containerCtrl && containerCtrl.setHasValue(selectMenuCtrl.selectedLabels().length > 0 || (element[0].validity || {}).badInput); - } - - function findSelectContainer() { - selectContainer = angular.element( - element[0].querySelector('.md-select-menu-container') - ); - selectScope = scope; - if (attr.mdContainerClass) { - var value = selectContainer[0].getAttribute('class') + ' ' + attr.mdContainerClass; - selectContainer[0].setAttribute('class', value); - } - selectMenuCtrl = selectContainer.find('md-select-menu').controller('mdSelectMenu'); - selectMenuCtrl.init(ngModelCtrl, attr.ngModel); - element.on('$destroy', function() { - selectContainer.remove(); - }); - } - - function handleKeypress(e) { - if ($mdConstant.isNavigationKey(e)) { - // prevent page scrolling on interaction - e.preventDefault(); - openSelect(e); - } else { - if ($mdConstant.isInputKey(e) || $mdConstant.isNumPadKey(e)) { - e.preventDefault(); - - var node = selectMenuCtrl.optNodeForKeyboardSearch(e); - if (!node || node.hasAttribute('disabled')) return; - var optionCtrl = angular.element(node).controller('mdOption'); - if (!selectMenuCtrl.isMultiple) { - selectMenuCtrl.deselect(Object.keys(selectMenuCtrl.selected)[0]); - } - selectMenuCtrl.select(optionCtrl.hashKey, optionCtrl.value); - selectMenuCtrl.refreshViewValue(); - } - } - } - - function openSelect() { - selectScope._mdSelectIsOpen = true; - element.attr('aria-expanded', 'true'); - - $mdSelect.show({ - scope: selectScope, - preserveScope: true, - skipCompile: true, - element: selectContainer, - target: element[0], - selectCtrl: mdSelectCtrl, - preserveElement: true, - hasBackdrop: true, - loadingAsync: attr.mdOnOpen ? scope.$eval(attr.mdOnOpen) || true : false - }).finally(function() { - selectScope._mdSelectIsOpen = false; - element.focus(); - element.attr('aria-expanded', 'false'); - ngModelCtrl.$setTouched(); - }); - } - - }; - } -} - -function SelectMenuDirective($parse, $mdUtil, $mdConstant, $mdTheming) { - // We want the scope to be set to 'false' so an isolated scope is not created - // which would interfere with the md-select-header's access to the - // parent scope. - SelectMenuController['$inject'] = ["$scope", "$attrs", "$element"]; - return { - restrict: 'E', - require: ['mdSelectMenu'], - scope: false, - controller: SelectMenuController, - link: {pre: preLink} - }; - - // We use preLink instead of postLink to ensure that the select is initialized before - // its child options run postLink. - function preLink(scope, element, attr, ctrls) { - var selectCtrl = ctrls[0]; - - element.addClass('_md'); // private md component indicator for styling - - $mdTheming(element); - element.on('click', clickListener); - element.on('keypress', keyListener); - - function keyListener(e) { - if (e.keyCode == 13 || e.keyCode == 32) { - clickListener(e); - } - } - - function clickListener(ev) { - var option = $mdUtil.getClosest(ev.target, 'md-option'); - var optionCtrl = option && angular.element(option).data('$mdOptionController'); - if (!option || !optionCtrl) return; - if (option.hasAttribute('disabled')) { - ev.stopImmediatePropagation(); - return false; - } - - var optionHashKey = selectCtrl.hashGetter(optionCtrl.value); - var isSelected = angular.isDefined(selectCtrl.selected[optionHashKey]); - - scope.$apply(function() { - if (selectCtrl.isMultiple) { - if (isSelected) { - selectCtrl.deselect(optionHashKey); - } else { - selectCtrl.select(optionHashKey, optionCtrl.value); - } - } else { - if (!isSelected) { - selectCtrl.deselect(Object.keys(selectCtrl.selected)[0]); - selectCtrl.select(optionHashKey, optionCtrl.value); - } - } - selectCtrl.refreshViewValue(); - }); - } - } - - function SelectMenuController($scope, $attrs, $element) { - var self = this; - self.isMultiple = angular.isDefined($attrs.multiple); - // selected is an object with keys matching all of the selected options' hashed values - self.selected = {}; - // options is an object with keys matching every option's hash value, - // and values matching every option's controller. - self.options = {}; - - $scope.$watchCollection(function() { - return self.options; - }, function() { - self.ngModel.$render(); - }); - - var deregisterCollectionWatch; - var defaultIsEmpty; - self.setMultiple = function(isMultiple) { - var ngModel = self.ngModel; - defaultIsEmpty = defaultIsEmpty || ngModel.$isEmpty; - - self.isMultiple = isMultiple; - if (deregisterCollectionWatch) deregisterCollectionWatch(); - - if (self.isMultiple) { - ngModel.$validators['md-multiple'] = validateArray; - ngModel.$render = renderMultiple; - - // watchCollection on the model because by default ngModel only watches the model's - // reference. This allowed the developer to also push and pop from their array. - $scope.$watchCollection(self.modelBinding, function(value) { - if (validateArray(value)) renderMultiple(value); - self.ngModel.$setPristine(); - }); - - ngModel.$isEmpty = function(value) { - return !value || value.length === 0; - }; - } else { - delete ngModel.$validators['md-multiple']; - ngModel.$render = renderSingular; - } - - function validateArray(modelValue, viewValue) { - // If a value is truthy but not an array, reject it. - // If value is undefined/falsy, accept that it's an empty array. - return angular.isArray(modelValue || viewValue || []); - } - }; - - var searchStr = ''; - var clearSearchTimeout, optNodes, optText; - var CLEAR_SEARCH_AFTER = 300; - - self.optNodeForKeyboardSearch = function(e) { - clearSearchTimeout && clearTimeout(clearSearchTimeout); - clearSearchTimeout = setTimeout(function() { - clearSearchTimeout = undefined; - searchStr = ''; - optText = undefined; - optNodes = undefined; - }, CLEAR_SEARCH_AFTER); - - // Support 1-9 on numpad - var keyCode = e.keyCode - ($mdConstant.isNumPadKey(e) ? 48 : 0); - - searchStr += String.fromCharCode(keyCode); - var search = new RegExp('^' + searchStr, 'i'); - if (!optNodes) { - optNodes = $element.find('md-option'); - optText = new Array(optNodes.length); - angular.forEach(optNodes, function(el, i) { - optText[i] = el.textContent.trim(); - }); - } - for (var i = 0; i < optText.length; ++i) { - if (search.test(optText[i])) { - return optNodes[i]; - } - } - }; - - self.init = function(ngModel, binding) { - self.ngModel = ngModel; - self.modelBinding = binding; - - // Setup a more robust version of isEmpty to ensure value is a valid option - self.ngModel.$isEmpty = function($viewValue) { - // We have to transform the viewValue into the hashKey, because otherwise the - // OptionCtrl may not exist. Developers may have specified a trackBy function. - return !self.options[self.hashGetter($viewValue)]; - }; - - // Allow users to provide `ng-model="foo" ng-model-options="{trackBy: 'foo.id'}"` so - // that we can properly compare objects set on the model to the available options - var trackByOption = $mdUtil.getModelOption(ngModel, 'trackBy'); - - if (trackByOption) { - var trackByLocals = {}; - var trackByParsed = $parse(trackByOption); - self.hashGetter = function(value, valueScope) { - trackByLocals.$value = value; - return trackByParsed(valueScope || $scope, trackByLocals); - }; - // If the user doesn't provide a trackBy, we automatically generate an id for every - // value passed in - } else { - self.hashGetter = function getHashValue(value) { - if (angular.isObject(value)) { - return 'object_' + (value.$$mdSelectId || (value.$$mdSelectId = ++selectNextId)); - } - return value; - }; - } - self.setMultiple(self.isMultiple); - }; - - self.selectedLabels = function(opts) { - opts = opts || {}; - var mode = opts.mode || 'html'; - var selectedOptionEls = $mdUtil.nodesToArray($element[0].querySelectorAll('md-option[selected]')); - if (selectedOptionEls.length) { - var mapFn; - - if (mode == 'html') { - // 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. - var rippleContainer = el.querySelector('.md-ripple-container'); - if (rippleContainer) { - html = html.replace(rippleContainer.outerHTML, ''); - } - - // Remove the checkbox container, because it will cause the label to wrap inside of the placeholder. - // It should be not displayed inside of the label element. - var checkboxContainer = el.querySelector('.md-container'); - if (checkboxContainer) { - html = html.replace(checkboxContainer.outerHTML, ''); - } - - return html; - }; - } else if (mode == 'aria') { - mapFn = function(el) { return el.hasAttribute('aria-label') ? el.getAttribute('aria-label') : el.textContent; }; - } - - // Ensure there are no duplicates; see https://github.com/angular/material/issues/9442 - return $mdUtil.uniq(selectedOptionEls.map(mapFn)).join(', '); - } else { - return ''; - } - }; - - self.select = function(hashKey, hashedValue) { - var option = self.options[hashKey]; - option && option.setSelected(true); - self.selected[hashKey] = hashedValue; - }; - self.deselect = function(hashKey) { - var option = self.options[hashKey]; - option && option.setSelected(false); - delete self.selected[hashKey]; - }; - - self.addOption = function(hashKey, optionCtrl) { - if (angular.isDefined(self.options[hashKey])) { - throw new Error('Duplicate md-option values are not allowed in a select. ' + - 'Duplicate value "' + optionCtrl.value + '" found.'); - } - - self.options[hashKey] = optionCtrl; - - // If this option's value was already in our ngModel, go ahead and select it. - if (angular.isDefined(self.selected[hashKey])) { - self.select(hashKey, optionCtrl.value); - - // When the current $modelValue of the ngModel Controller is using the same hash as - // the current option, which will be added, then we can be sure, that the validation - // of the option has occurred before the option was added properly. - // This means, that we have to manually trigger a new validation of the current option. - if (angular.isDefined(self.ngModel.$modelValue) && self.hashGetter(self.ngModel.$modelValue) === hashKey) { - self.ngModel.$validate(); - } - - self.refreshViewValue(); - } - }; - self.removeOption = function(hashKey) { - delete self.options[hashKey]; - // Don't deselect an option when it's removed - the user's ngModel should be allowed - // to have values that do not match a currently available option. - }; - - self.refreshViewValue = function() { - var values = []; - var option; - for (var hashKey in self.selected) { - // If this hashKey has an associated option, push that option's value to the model. - if ((option = self.options[hashKey])) { - values.push(option.value); - } else { - // Otherwise, the given hashKey has no associated option, and we got it - // from an ngModel value at an earlier time. Push the unhashed value of - // this hashKey to the model. - // This allows the developer to put a value in the model that doesn't yet have - // an associated option. - values.push(self.selected[hashKey]); - } - } - var usingTrackBy = $mdUtil.getModelOption(self.ngModel, 'trackBy'); - - var newVal = self.isMultiple ? values : values[0]; - var prevVal = self.ngModel.$modelValue; - - if (usingTrackBy ? !angular.equals(prevVal, newVal) : (prevVal + '') !== newVal) { - self.ngModel.$setViewValue(newVal); - self.ngModel.$render(); - } - }; - - function renderMultiple() { - var newSelectedValues = self.ngModel.$modelValue || self.ngModel.$viewValue || []; - if (!angular.isArray(newSelectedValues)) return; - - var oldSelected = Object.keys(self.selected); - - var newSelectedHashes = newSelectedValues.map(self.hashGetter); - var deselected = oldSelected.filter(function(hash) { - return newSelectedHashes.indexOf(hash) === -1; - }); - - deselected.forEach(self.deselect); - newSelectedHashes.forEach(function(hashKey, i) { - self.select(hashKey, newSelectedValues[i]); - }); - } - - function renderSingular() { - var value = self.ngModel.$viewValue || self.ngModel.$modelValue; - Object.keys(self.selected).forEach(self.deselect); - self.select(self.hashGetter(value), value); - } - } - -} - -function OptionDirective($mdButtonInkRipple, $mdUtil) { - - OptionController['$inject'] = ["$element"]; - return { - restrict: 'E', - require: ['mdOption', '^^mdSelectMenu'], - controller: OptionController, - compile: compile - }; - - function compile(element, attr) { - // Manual transclusion to avoid the extra inner <span> that ng-transclude generates - 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]; - - if (selectCtrl.isMultiple) { - element.addClass('md-checkbox-enabled'); - element.prepend(CHECKBOX_SELECTION_INDICATOR.clone()); - } - - if (angular.isDefined(attr.ngValue)) { - scope.$watch(attr.ngValue, setOptionValue); - } else if (angular.isDefined(attr.value)) { - setOptionValue(attr.value); - } else { - scope.$watch(function() { - return element.text().trim(); - }, setOptionValue); - } - - attr.$observe('disabled', function(disabled) { - if (disabled) { - element.attr('tabindex', '-1'); - } else { - element.attr('tabindex', '0'); - } - }); - - scope.$$postDigest(function() { - attr.$observe('selected', function(selected) { - if (!angular.isDefined(selected)) return; - if (typeof selected == 'string') selected = true; - if (selected) { - if (!selectCtrl.isMultiple) { - selectCtrl.deselect(Object.keys(selectCtrl.selected)[0]); - } - selectCtrl.select(optionCtrl.hashKey, optionCtrl.value); - } else { - selectCtrl.deselect(optionCtrl.hashKey); - } - selectCtrl.refreshViewValue(); - }); - }); - - $mdButtonInkRipple.attach(scope, element); - configureAria(); - - function setOptionValue(newValue, oldValue, prevAttempt) { - if (!selectCtrl.hashGetter) { - if (!prevAttempt) { - scope.$$postDigest(function() { - setOptionValue(newValue, oldValue, true); - }); - } - return; - } - var oldHashKey = selectCtrl.hashGetter(oldValue, scope); - var newHashKey = selectCtrl.hashGetter(newValue, scope); - - optionCtrl.hashKey = newHashKey; - optionCtrl.value = newValue; - - selectCtrl.removeOption(oldHashKey, optionCtrl); - selectCtrl.addOption(newHashKey, optionCtrl); - } - - scope.$on('$destroy', function() { - selectCtrl.removeOption(optionCtrl.hashKey, optionCtrl); - }); - - function configureAria() { - var ariaAttrs = { - 'role': 'option', - 'aria-selected': 'false' - }; - - if (!element[0].hasAttribute('id')) { - ariaAttrs.id = 'select_option_' + $mdUtil.nextUid(); - } - element.attr(ariaAttrs); - } - } - - function OptionController($element) { - this.selected = false; - this.setSelected = function(isSelected) { - if (isSelected && !this.selected) { - $element.attr({ - 'selected': 'selected', - 'aria-selected': 'true' - }); - } else if (!isSelected && this.selected) { - $element.removeAttr('selected'); - $element.attr('aria-selected', 'false'); - } - this.selected = isSelected; - }; - } - -} - -function OptgroupDirective() { - return { - restrict: 'E', - compile: compile - }; - function compile(el, attrs) { - // If we have a select header element, we don't want to add the normal label - // header. - if (!hasSelectHeader()) { - setupLabelElement(); - } - - function hasSelectHeader() { - return el.parent().find('md-select-header').length; - } - - function setupLabelElement() { - var labelElement = el.find('label'); - if (!labelElement.length) { - labelElement = angular.element('<label>'); - el.prepend(labelElement); - } - labelElement.addClass('md-container-ignore'); - if (attrs.label) labelElement.text(attrs.label); - } - } -} - -function SelectHeaderDirective() { - return { - restrict: 'E', - }; -} - -function SelectProvider($$interimElementProvider) { - selectDefaultOptions['$inject'] = ["$mdSelect", "$mdConstant", "$mdUtil", "$window", "$q", "$$rAF", "$animateCss", "$animate", "$document"]; - return $$interimElementProvider('$mdSelect') - .setDefaults({ - methods: ['target'], - options: selectDefaultOptions - }); - - /* ngInject */ - function selectDefaultOptions($mdSelect, $mdConstant, $mdUtil, $window, $q, $$rAF, $animateCss, $animate, $document) { - var ERROR_TARGET_EXPECTED = "$mdSelect.show() expected a target element in options.target but got '{0}'!"; - var animator = $mdUtil.dom.animator; - var keyCodes = $mdConstant.KEY_CODE; - - return { - parent: 'body', - themable: true, - onShow: onShow, - onRemove: onRemove, - hasBackdrop: true, - disableParentScroll: true - }; - - /** - * Interim-element onRemove logic.... - */ - function onRemove(scope, element, opts) { - opts = opts || { }; - opts.cleanupInteraction(); - opts.cleanupResizing(); - opts.hideBackdrop(); - - // For navigation $destroy events, do a quick, non-animated removal, - // but for normal closes (from clicks, etc) animate the removal - - return (opts.$destroy === true) ? cleanElement() : animateRemoval().then( cleanElement ); - - /** - * For normal closes (eg clicks), animate the removal. - * For forced closes (like $destroy events from navigation), - * skip the animations - */ - function animateRemoval() { - return $animateCss(element, {addClass: 'md-leave'}).start(); - } - - /** - * Restore the element to a closed state - */ - function cleanElement() { - - element.removeClass('md-active'); - element.attr('aria-hidden', 'true'); - element[0].style.display = 'none'; - - announceClosed(opts); - - if (!opts.$destroy && opts.restoreFocus) { - opts.target.focus(); - } - } - - } - - /** - * Interim-element onShow logic.... - */ - function onShow(scope, element, opts) { - - watchAsyncLoad(); - sanitizeAndConfigure(scope, opts); - - opts.hideBackdrop = showBackdrop(scope, element, opts); - - return showDropDown(scope, element, opts) - .then(function(response) { - element.attr('aria-hidden', 'false'); - opts.alreadyOpen = true; - opts.cleanupInteraction = activateInteraction(); - opts.cleanupResizing = activateResizing(); - - return response; - }, opts.hideBackdrop); - - // ************************************ - // Closure Functions - // ************************************ - - /** - * Attach the select DOM element(s) and animate to the correct positions - * and scalings... - */ - function showDropDown(scope, element, opts) { - opts.parent.append(element); - - return $q(function(resolve, reject) { - - try { - - $animateCss(element, {removeClass: 'md-leave', duration: 0}) - .start() - .then(positionAndFocusMenu) - .then(resolve); - - } catch (e) { - reject(e); - } - - }); - } - - /** - * Initialize container and dropDown menu positions/scale, then animate - * to show... and autoFocus. - */ - function positionAndFocusMenu() { - return $q(function(resolve) { - if (opts.isRemoved) return $q.reject(false); - - var info = calculateMenuPositions(scope, element, opts); - - info.container.element.css(animator.toCss(info.container.styles)); - info.dropDown.element.css(animator.toCss(info.dropDown.styles)); - - $$rAF(function() { - element.addClass('md-active'); - info.dropDown.element.css(animator.toCss({transform: ''})); - - autoFocus(opts.focusedNode); - resolve(); - }); - - }); - } - - /** - * Show modal backdrop element... - */ - function showBackdrop(scope, element, options) { - - // If we are not within a dialog... - if (options.disableParentScroll && !$mdUtil.getClosest(options.target, 'MD-DIALOG')) { - // !! DO this before creating the backdrop; since disableScrollAround() - // configures the scroll offset; which is used by mdBackDrop postLink() - options.restoreScroll = $mdUtil.disableScrollAround(options.element, options.parent); - } else { - options.disableParentScroll = false; - } - - if (options.hasBackdrop) { - // Override duration to immediately show invisible backdrop - options.backdrop = $mdUtil.createBackdrop(scope, "md-select-backdrop md-click-catcher"); - $animate.enter(options.backdrop, $document[0].body, null, {duration: 0}); - } - - /** - * Hide modal backdrop element... - */ - return function hideBackdrop() { - if (options.backdrop) options.backdrop.remove(); - if (options.disableParentScroll) options.restoreScroll(); - - delete options.restoreScroll; - }; - } - - /** - * - */ - function autoFocus(focusedNode) { - if (focusedNode && !focusedNode.hasAttribute('disabled')) { - focusedNode.focus(); - } - } - - /** - * Check for valid opts and set some sane defaults - */ - function sanitizeAndConfigure(scope, options) { - var selectEl = element.find('md-select-menu'); - - if (!options.target) { - throw new Error($mdUtil.supplant(ERROR_TARGET_EXPECTED, [options.target])); - } - - angular.extend(options, { - isRemoved: false, - target: angular.element(options.target), //make sure it's not a naked dom node - parent: angular.element(options.parent), - selectEl: selectEl, - contentEl: element.find('md-content'), - optionNodes: selectEl[0].getElementsByTagName('md-option') - }); - } - - /** - * Configure various resize listeners for screen changes - */ - function activateResizing() { - var debouncedOnResize = (function(scope, target, options) { - - return function() { - if (options.isRemoved) return; - - var updates = calculateMenuPositions(scope, target, options); - var container = updates.container; - var dropDown = updates.dropDown; - - container.element.css(animator.toCss(container.styles)); - dropDown.element.css(animator.toCss(dropDown.styles)); - }; - - })(scope, element, opts); - - var window = angular.element($window); - window.on('resize', debouncedOnResize); - window.on('orientationchange', debouncedOnResize); - - // Publish deactivation closure... - return function deactivateResizing() { - - // Disable resizing handlers - window.off('resize', debouncedOnResize); - window.off('orientationchange', debouncedOnResize); - }; - } - - /** - * If asynchronously loading, watch and update internal - * '$$loadingAsyncDone' flag - */ - function watchAsyncLoad() { - if (opts.loadingAsync && !opts.isRemoved) { - scope.$$loadingAsyncDone = false; - - $q.when(opts.loadingAsync) - .then(function() { - scope.$$loadingAsyncDone = true; - delete opts.loadingAsync; - }).then(function() { - $$rAF(positionAndFocusMenu); - }); - } - } - - /** - * - */ - function activateInteraction() { - if (opts.isRemoved) return; - - var dropDown = opts.selectEl; - var selectCtrl = dropDown.controller('mdSelectMenu') || {}; - - element.addClass('md-clickable'); - - // Close on backdrop click - opts.backdrop && opts.backdrop.on('click', onBackdropClick); - - // Escape to close - // Cycling of options, and closing on enter - dropDown.on('keydown', onMenuKeyDown); - dropDown.on('click', checkCloseMenu); - - return function cleanupInteraction() { - opts.backdrop && opts.backdrop.off('click', onBackdropClick); - dropDown.off('keydown', onMenuKeyDown); - dropDown.off('click', checkCloseMenu); - - element.removeClass('md-clickable'); - opts.isRemoved = true; - }; - - // ************************************ - // Closure Functions - // ************************************ - - function onBackdropClick(e) { - e.preventDefault(); - e.stopPropagation(); - opts.restoreFocus = false; - $mdUtil.nextTick($mdSelect.hide, true); - } - - function onMenuKeyDown(ev) { - ev.preventDefault(); - ev.stopPropagation(); - - switch (ev.keyCode) { - case keyCodes.UP_ARROW: - return focusPrevOption(); - case keyCodes.DOWN_ARROW: - return focusNextOption(); - case keyCodes.SPACE: - case keyCodes.ENTER: - var option = $mdUtil.getClosest(ev.target, 'md-option'); - if (option) { - dropDown.triggerHandler({ - type: 'click', - target: option - }); - ev.preventDefault(); - } - checkCloseMenu(ev); - break; - case keyCodes.TAB: - case keyCodes.ESCAPE: - ev.stopPropagation(); - ev.preventDefault(); - opts.restoreFocus = true; - $mdUtil.nextTick($mdSelect.hide, true); - break; - default: - if ($mdConstant.isInputKey(ev) || $mdConstant.isNumPadKey(ev)) { - var optNode = dropDown.controller('mdSelectMenu').optNodeForKeyboardSearch(ev); - opts.focusedNode = optNode || opts.focusedNode; - optNode && optNode.focus(); - } - } - } - - function focusOption(direction) { - var optionsArray = $mdUtil.nodesToArray(opts.optionNodes); - var index = optionsArray.indexOf(opts.focusedNode); - - var newOption; - - do { - if (index === -1) { - // We lost the previously focused element, reset to first option - index = 0; - } else if (direction === 'next' && index < optionsArray.length - 1) { - index++; - } else if (direction === 'prev' && index > 0) { - index--; - } - newOption = optionsArray[index]; - if (newOption.hasAttribute('disabled')) newOption = undefined; - } while (!newOption && index < optionsArray.length - 1 && index > 0); - - newOption && newOption.focus(); - opts.focusedNode = newOption; - } - - function focusNextOption() { - focusOption('next'); - } - - function focusPrevOption() { - focusOption('prev'); - } - - function checkCloseMenu(ev) { - if (ev && ( ev.type == 'click') && (ev.currentTarget != dropDown[0])) return; - if ( mouseOnScrollbar() ) return; - - var option = $mdUtil.getClosest(ev.target, 'md-option'); - if (option && option.hasAttribute && !option.hasAttribute('disabled')) { - ev.preventDefault(); - ev.stopPropagation(); - if (!selectCtrl.isMultiple) { - opts.restoreFocus = true; - - $mdUtil.nextTick(function () { - $mdSelect.hide(selectCtrl.ngModel.$viewValue); - }, true); - } - } - /** - * check if the mouseup event was on a scrollbar - */ - function mouseOnScrollbar() { - var clickOnScrollbar = false; - if (ev && (ev.currentTarget.children.length > 0)) { - var child = ev.currentTarget.children[0]; - var hasScrollbar = child.scrollHeight > child.clientHeight; - if (hasScrollbar && child.children.length > 0) { - var relPosX = ev.pageX - ev.currentTarget.getBoundingClientRect().left; - if (relPosX > child.querySelector('md-option').offsetWidth) - clickOnScrollbar = true; - } - } - return clickOnScrollbar; - } - } - } - - } - - /** - * To notify listeners that the Select menu has closed, - * trigger the [optional] user-defined expression - */ - function announceClosed(opts) { - var mdSelect = opts.selectCtrl; - if (mdSelect) { - var menuController = opts.selectEl.controller('mdSelectMenu'); - mdSelect.setLabelText(menuController ? menuController.selectedLabels() : ''); - mdSelect.triggerClose(); - } - } - - - /** - * Calculate the - */ - function calculateMenuPositions(scope, element, opts) { - var - containerNode = element[0], - targetNode = opts.target[0].children[0], // target the label - parentNode = $document[0].body, - selectNode = opts.selectEl[0], - contentNode = opts.contentEl[0], - parentRect = parentNode.getBoundingClientRect(), - targetRect = targetNode.getBoundingClientRect(), - shouldOpenAroundTarget = false, - bounds = { - left: parentRect.left + SELECT_EDGE_MARGIN, - top: SELECT_EDGE_MARGIN, - bottom: parentRect.height - SELECT_EDGE_MARGIN, - right: parentRect.width - SELECT_EDGE_MARGIN - ($mdUtil.floatingScrollbars() ? 16 : 0) - }, - spaceAvailable = { - top: targetRect.top - bounds.top, - left: targetRect.left - bounds.left, - right: bounds.right - (targetRect.left + targetRect.width), - bottom: bounds.bottom - (targetRect.top + targetRect.height) - }, - maxWidth = parentRect.width - SELECT_EDGE_MARGIN * 2, - selectedNode = selectNode.querySelector('md-option[selected]'), - optionNodes = selectNode.getElementsByTagName('md-option'), - optgroupNodes = selectNode.getElementsByTagName('md-optgroup'), - isScrollable = calculateScrollable(element, contentNode), - centeredNode; - - var loading = isPromiseLike(opts.loadingAsync); - if (!loading) { - // If a selected node, center around that - if (selectedNode) { - centeredNode = selectedNode; - // If there are option groups, center around the first option group - } else if (optgroupNodes.length) { - centeredNode = optgroupNodes[0]; - // Otherwise - if we are not loading async - center around the first optionNode - } else if (optionNodes.length) { - centeredNode = optionNodes[0]; - // In case there are no options, center on whatever's in there... (eg progress indicator) - } else { - centeredNode = contentNode.firstElementChild || contentNode; - } - } else { - // If loading, center on progress indicator - centeredNode = contentNode.firstElementChild || contentNode; - } - - if (contentNode.offsetWidth > maxWidth) { - contentNode.style['max-width'] = maxWidth + 'px'; - } else { - contentNode.style.maxWidth = null; - } - if (shouldOpenAroundTarget) { - contentNode.style['min-width'] = targetRect.width + 'px'; - } - - // Remove padding before we compute the position of the menu - if (isScrollable) { - selectNode.classList.add('md-overflow'); - } - - var focusedNode = centeredNode; - if ((focusedNode.tagName || '').toUpperCase() === 'MD-OPTGROUP') { - focusedNode = optionNodes[0] || contentNode.firstElementChild || contentNode; - centeredNode = focusedNode; - } - // Cache for autoFocus() - opts.focusedNode = focusedNode; - - // Get the selectMenuRect *after* max-width is possibly set above - containerNode.style.display = 'block'; - var selectMenuRect = selectNode.getBoundingClientRect(); - var centeredRect = getOffsetRect(centeredNode); - - if (centeredNode) { - var centeredStyle = $window.getComputedStyle(centeredNode); - centeredRect.paddingLeft = parseInt(centeredStyle.paddingLeft, 10) || 0; - centeredRect.paddingRight = parseInt(centeredStyle.paddingRight, 10) || 0; - } - - if (isScrollable) { - var scrollBuffer = contentNode.offsetHeight / 2; - contentNode.scrollTop = centeredRect.top + centeredRect.height / 2 - scrollBuffer; - - if (spaceAvailable.top < scrollBuffer) { - contentNode.scrollTop = Math.min( - centeredRect.top, - contentNode.scrollTop + scrollBuffer - spaceAvailable.top - ); - } else if (spaceAvailable.bottom < scrollBuffer) { - contentNode.scrollTop = Math.max( - centeredRect.top + centeredRect.height - selectMenuRect.height, - contentNode.scrollTop - scrollBuffer + spaceAvailable.bottom - ); - } - } - - var left, top, transformOrigin, minWidth, fontSize; - if (shouldOpenAroundTarget) { - left = targetRect.left; - top = targetRect.top + targetRect.height; - transformOrigin = '50% 0'; - if (top + selectMenuRect.height > bounds.bottom) { - top = targetRect.top - selectMenuRect.height; - transformOrigin = '50% 100%'; - } - } else { - left = (targetRect.left + centeredRect.left - centeredRect.paddingLeft) + 2; - top = Math.floor(targetRect.top + targetRect.height / 2 - centeredRect.height / 2 - - centeredRect.top + contentNode.scrollTop) + 2; - - transformOrigin = (centeredRect.left + targetRect.width / 2) + 'px ' + - (centeredRect.top + centeredRect.height / 2 - contentNode.scrollTop) + 'px 0px'; - - minWidth = Math.min(targetRect.width + centeredRect.paddingLeft + centeredRect.paddingRight, maxWidth); - - fontSize = window.getComputedStyle(targetNode)['font-size']; - } - - // Keep left and top within the window - var containerRect = containerNode.getBoundingClientRect(); - var scaleX = Math.round(100 * Math.min(targetRect.width / selectMenuRect.width, 1.0)) / 100; - var scaleY = Math.round(100 * Math.min(targetRect.height / selectMenuRect.height, 1.0)) / 100; - - return { - container: { - element: angular.element(containerNode), - styles: { - left: Math.floor(clamp(bounds.left, left, bounds.right - containerRect.width)), - top: Math.floor(clamp(bounds.top, top, bounds.bottom - containerRect.height)), - 'min-width': minWidth, - 'font-size': fontSize - } - }, - dropDown: { - element: angular.element(selectNode), - styles: { - transformOrigin: transformOrigin, - transform: !opts.alreadyOpen ? $mdUtil.supplant('scale({0},{1})', [scaleX, scaleY]) : "" - } - } - }; - - } - - } - - function isPromiseLike(obj) { - return obj && angular.isFunction(obj.then); - } - - function clamp(min, n, max) { - return Math.max(min, Math.min(n, max)); - } - - function getOffsetRect(node) { - return node ? { - left: node.offsetLeft, - top: node.offsetTop, - width: node.offsetWidth, - height: node.offsetHeight - } : {left: 0, top: 0, width: 0, height: 0}; - } - - function calculateScrollable(element, contentNode) { - var isScrollable = false; - - try { - var oldDisplay = element[0].style.display; - - // Set the element's display to block so that this calculation is correct - element[0].style.display = 'block'; - - isScrollable = contentNode.scrollHeight > contentNode.offsetHeight; - - // Reset it back afterwards - element[0].style.display = oldDisplay; - } finally { - // Nothing to do - } - return isScrollable; - } -} - -ngmaterial.components.select = angular.module("material.components.select");
\ No newline at end of file |