summaryrefslogtreecommitdiffstats
path: root/ecomp-portal-FE/client/bower_components/angular-material/modules/js/select/select.js
diff options
context:
space:
mode:
Diffstat (limited to 'ecomp-portal-FE/client/bower_components/angular-material/modules/js/select/select.js')
-rw-r--r--ecomp-portal-FE/client/bower_components/angular-material/modules/js/select/select.js1056
1 files changed, 1056 insertions, 0 deletions
diff --git a/ecomp-portal-FE/client/bower_components/angular-material/modules/js/select/select.js b/ecomp-portal-FE/client/bower_components/angular-material/modules/js/select/select.js
new file mode 100644
index 00000000..969e06b7
--- /dev/null
+++ b/ecomp-portal-FE/client/bower_components/angular-material/modules/js/select/select.js
@@ -0,0 +1,1056 @@
+/*!
+ * Angular Material Design
+ * https://github.com/angular/material
+ * @license MIT
+ * v0.9.8
+ */
+(function( window, angular, undefined ){
+"use strict";
+
+/**
+ * @ngdoc module
+ * @name material.components.select
+ */
+
+/***************************************************
+
+### TODO ###
+**DOCUMENTATION AND DEMOS**
+
+- [ ] ng-model with child mdOptions (basic)
+- [ ] ng-model="foo" ng-model-options="{ trackBy: '$value.id' }" for objects
+- [ ] mdOption with value
+- [ ] Usage with input inside
+
+### TODO - POST RC1 ###
+- [ ] Abstract placement logic in $mdSelect service to $mdMenu service
+
+***************************************************/
+
+var SELECT_EDGE_MARGIN = 8;
+var selectNextId = 0;
+
+angular.module('material.components.select', [
+ 'material.core',
+ 'material.components.backdrop'
+])
+.directive('mdSelect', SelectDirective)
+.directive('mdSelectMenu', SelectMenuDirective)
+.directive('mdOption', OptionDirective)
+.directive('mdOptgroup', OptgroupDirective)
+.provider('$mdSelect', SelectProvider);
+
+
+/**
+ * @ngdoc directive
+ * @name mdSelect
+ * @restrict E
+ * @module material.components.select
+ *
+ * @description Displays a select box, bound to an ng-model.
+ *
+ * @param {expression} ng-model The model!
+ * @param {boolean=} multiple Whether it's multiple.
+ * @param {string=} placeholder Placeholder hint text.
+ * @param {string=} aria-label Optional label for accessibility. Only necessary if no placeholder or
+ * explicit label is present.
+ *
+ * @usage
+ * With a placeholder (label and aria-label are added dynamically)
+ * <hljs lang="html">
+ * <md-select
+ * ng-model="someModel"
+ * placeholder="Select a state">
+ * <md-option ng-value="opt" ng-repeat="opt in neighborhoods2">{{ opt }}</md-option>
+ * </md-select>
+ * </hljs>
+ *
+ * With an explicit label
+ * <hljs lang="html">
+ * <md-select
+ * ng-model="someModel">
+ * <md-select-label>Select a state</md-select-label>
+ * <md-option ng-value="opt" ng-repeat="opt in neighborhoods2">{{ opt }}</md-option>
+ * </md-select>
+ * </hljs>
+ */
+function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $interpolate, $compile, $parse) {
+ return {
+ restrict: 'E',
+ require: ['mdSelect', 'ngModel', '?^form'],
+ compile: compile,
+ controller: function() { } // empty placeholder controller to be initialized in link
+ };
+
+ function compile(element, attr) {
+ // The user is allowed to provide a label for the select as md-select-label child
+ var labelEl = element.find('md-select-label').remove();
+
+ // If not provided, we automatically make one
+ if (!labelEl.length) {
+ labelEl = angular.element('<md-select-label><span></span></md-select-label>');
+ } else {
+ if (!labelEl[0].firstElementChild) {
+ var spanWrapper = angular.element('<span>');
+ spanWrapper.append(labelEl.contents());
+ labelEl.append(spanWrapper);
+ }
+ }
+ labelEl.append('<span class="md-select-icon" aria-hidden="true"></span>');
+ labelEl.addClass('md-select-label');
+ if (!labelEl[0].hasAttribute('id')) {
+ labelEl.attr('id', 'select_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) {
+ element.find('md-content').prepend(
+ angular.element('<md-progress-circular>')
+ .attr('md-mode', 'indeterminate')
+ .attr('ng-hide', '$$loadingAsyncDone')
+ .wrap('<div>')
+ .parent()
+ );
+ }
+
+ if (attr.name) {
+ var autofillClone = angular.element('<select class="md-visually-hidden">');
+ autofillClone.attr({
+ 'name': '.' + attr.name,
+ 'ng-model': attr.ngModel,
+ '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);
+ });
+
+ element.parent().append(autofillClone);
+ }
+
+ // Use everything that's left inside element.contents() as the contents of the menu
+ var selectTemplate = '<div class="md-select-menu-container">' +
+ '<md-select-menu ' +
+ (angular.isDefined(attr.multiple) ? 'multiple' : '') + '>' +
+ element.html() +
+ '</md-select-menu></div>';
+
+ element.empty().append(labelEl);
+
+ attr.tabindex = attr.tabindex || '0';
+
+ return function postLink(scope, element, attr, ctrls) {
+ var isOpen;
+ var isDisabled;
+
+ var mdSelectCtrl = ctrls[0];
+ var ngModel = ctrls[1];
+ var formCtrl = ctrls[2];
+
+ var labelEl = element.find('md-select-label');
+ var customLabel = labelEl.text().length !== 0;
+ var selectContainer, selectScope, selectMenuCtrl;
+ createSelect();
+
+ $mdTheming(element);
+
+ if (attr.name && formCtrl) {
+ var selectEl = element.parent()[0].querySelector('select[name=".' + attr.name + '"]')
+ formCtrl.$removeControl(angular.element(selectEl).controller());
+ }
+
+ var originalRender = ngModel.$render;
+ ngModel.$render = function() {
+ originalRender();
+ syncLabelText();
+ };
+
+ mdSelectCtrl.setLabelText = function(text) {
+ if (customLabel) return; // Assume that user is handling it on their own
+ mdSelectCtrl.setIsPlaceholder(!text);
+ text = text || attr.placeholder || '';
+ var target = customLabel ? labelEl : labelEl.children().eq(0);
+ target.text(text);
+ };
+
+ mdSelectCtrl.setIsPlaceholder = function(val) {
+ val ? labelEl.addClass('md-placeholder') : labelEl.removeClass('md-placeholder');
+ };
+
+ scope.$$postDigest(function() {
+ setAriaLabel();
+ syncLabelText();
+ });
+
+ function setAriaLabel() {
+ var labelText = element.attr('placeholder');
+ if (!labelText) {
+ labelText = element.find('md-select-label').text();
+ }
+ $mdAria.expect(element, 'aria-label', labelText);
+ }
+
+ function syncLabelText() {
+ if (selectContainer) {
+ selectMenuCtrl = selectMenuCtrl || selectContainer.find('md-select-menu').controller('mdSelectMenu');
+ mdSelectCtrl.setLabelText(selectMenuCtrl.selectedLabels());
+ }
+ }
+
+ 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');
+ }
+ if (selectContainer) {
+ selectMenuCtrl.setMultiple(multiple);
+ originalRender = ngModel.$render;
+ ngModel.$render = function() {
+ originalRender();
+ syncLabelText();
+ };
+ selectMenuCtrl.refreshViewValue();
+ ngModel.$render();
+ }
+ });
+ });
+
+ attr.$observe('disabled', function(disabled) {
+ if (typeof disabled == "string") {
+ disabled = true;
+ }
+ // Prevent click event being registered twice
+ if (isDisabled !== undefined && isDisabled === disabled) {
+ return;
+ }
+ isDisabled = disabled;
+ if (disabled) {
+ element.attr({'tabindex': -1, 'aria-disabled': 'true'});
+ element.off('click', openSelect);
+ element.off('keydown', handleKeypress);
+ } else {
+ element.attr({'tabindex': attr.tabindex, 'aria-disabled': 'false'});
+ element.on('click', openSelect);
+ element.on('keydown', handleKeypress);
+ }
+ });
+ if (!attr.disabled && !attr.ngDisabled) {
+ element.attr({'tabindex': attr.tabindex, 'aria-disabled': 'false'});
+ element.on('click', openSelect);
+ element.on('keydown', handleKeypress);
+ }
+
+ var ariaAttrs = {
+ role: 'combobox',
+ 'aria-expanded': 'false'
+ };
+ if (!element[0].hasAttribute('id')) {
+ ariaAttrs.id = 'select_' + $mdUtil.nextUid();
+ }
+ element.attr(ariaAttrs);
+
+ scope.$on('$destroy', function() {
+ if (isOpen) {
+ $mdSelect.cancel().then(function() {
+ selectContainer.remove();
+ });
+ } else {
+ selectContainer.remove();
+ }
+ });
+
+
+ // Create a fake select to find out the label value
+ function createSelect() {
+ selectContainer = angular.element(selectTemplate);
+ var selectEl = selectContainer.find('md-select-menu');
+ selectEl.data('$ngModelController', ngModel);
+ selectEl.data('$mdSelectController', mdSelectCtrl);
+ selectScope = scope.$new();
+ selectContainer = $compile(selectContainer)(selectScope);
+ selectMenuCtrl = selectContainer.find('md-select-menu').controller('mdSelectMenu');
+ }
+
+ function handleKeypress(e) {
+ var allowedCodes = [32, 13, 38, 40];
+ if (allowedCodes.indexOf(e.keyCode) != -1 ) {
+ // prevent page scrolling on interaction
+ e.preventDefault();
+ openSelect(e);
+ } else {
+ if (e.keyCode <= 90 && e.keyCode >= 31) {
+ e.preventDefault();
+ var node = selectMenuCtrl.optNodeForKeyboardSearch(e);
+ if (!node) 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();
+ ngModel.$render();
+ }
+ }
+ }
+
+ function openSelect() {
+ scope.$evalAsync(function() {
+ isOpen = true;
+ $mdSelect.show({
+ scope: selectScope,
+ preserveScope: true,
+ skipCompile: true,
+ element: selectContainer,
+ target: element[0],
+ hasBackdrop: true,
+ loadingAsync: attr.mdOnOpen ? scope.$eval(attr.mdOnOpen) || true : false,
+ }).then(function(selectedText) {
+ isOpen = false;
+ });
+ });
+ }
+ };
+ }
+}
+SelectDirective.$inject = ["$mdSelect", "$mdUtil", "$mdTheming", "$mdAria", "$interpolate", "$compile", "$parse"];
+
+function SelectMenuDirective($parse, $mdUtil, $mdTheming) {
+
+ SelectMenuController.$inject = ["$scope", "$attrs", "$element"];
+ return {
+ restrict: 'E',
+ require: ['mdSelectMenu', '?ngModel'],
+ 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];
+ var ngModel = ctrls[1];
+
+ $mdTheming(element);
+ element.on('click', clickListener);
+ element.on('keypress', keyListener);
+ if (ngModel) selectCtrl.init(ngModel);
+ configureAria();
+
+ function configureAria() {
+ element.attr({
+ 'id': 'select_menu_' + $mdUtil.nextUid(),
+ 'role': 'listbox',
+ 'aria-multiselectable': (selectCtrl.isMultiple ? 'true' : 'false')
+ });
+ }
+
+ 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;
+
+ 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.$watch(function() { return self.options; }, function() {
+ self.ngModel.$render();
+ }, true);
+
+ var deregisterCollectionWatch;
+ self.setMultiple = function(isMultiple) {
+ var ngModel = self.ngModel;
+ 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($attrs.ngModel, function(value) {
+ if (validateArray(value)) renderMultiple(value);
+ });
+ } 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);
+ searchStr += String.fromCharCode(e.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) {
+ self.ngModel = ngModel;
+
+ // 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
+ if (ngModel.$options && ngModel.$options.trackBy) {
+ var trackByLocals = {};
+ var trackByParsed = $parse(ngModel.$options.trackBy);
+ 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() {
+ var selectedOptionEls = nodesToArray($element[0].querySelectorAll('md-option[selected]'));
+ if (selectedOptionEls.length) {
+ return selectedOptionEls.map(function(el) { return el.textContent; }).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);
+ 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]);
+ }
+ }
+ self.ngModel.$setViewValue(self.isMultiple ? values : values[0]);
+ };
+
+ 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 );
+ }
+ }
+
+}
+SelectMenuDirective.$inject = ["$parse", "$mdUtil", "$mdTheming"];
+
+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');
+ return postLink;
+ }
+
+ function postLink(scope, element, attr, ctrls) {
+ var optionCtrl = ctrls[0];
+ var selectCtrl = ctrls[1];
+
+ 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(); }, setOptionValue);
+ }
+
+ scope.$$postDigest(function() {
+ attr.$observe('selected', function(selected) {
+ if (!angular.isDefined(selected)) return;
+ 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();
+ selectCtrl.ngModel.$render();
+ });
+ });
+
+ $mdButtonInkRipple.attach(scope, element);
+ configureAria();
+
+ function setOptionValue(newValue, oldValue) {
+ 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;
+ };
+ }
+
+}
+OptionDirective.$inject = ["$mdButtonInkRipple", "$mdUtil"];
+
+function OptgroupDirective() {
+ return {
+ restrict: 'E',
+ compile: compile
+ };
+ function compile(el, attrs) {
+ var labelElement = el.find('label');
+ if (!labelElement.length) {
+ labelElement = angular.element('<label>');
+ el.prepend(labelElement);
+ }
+ if (attrs.label) labelElement.text(attrs.label);
+ }
+}
+
+function SelectProvider($$interimElementProvider) {
+ selectDefaultOptions.$inject = ["$mdSelect", "$mdConstant", "$$rAF", "$mdUtil", "$mdTheming", "$timeout", "$window"];
+ return $$interimElementProvider('$mdSelect')
+ .setDefaults({
+ methods: ['target'],
+ options: selectDefaultOptions
+ });
+
+ /* ngInject */
+ function selectDefaultOptions($mdSelect, $mdConstant, $$rAF, $mdUtil, $mdTheming, $timeout, $window ) {
+ return {
+ parent: 'body',
+ onShow: onShow,
+ onRemove: onRemove,
+ hasBackdrop: true,
+ disableParentScroll: true,
+ themable: true
+ };
+
+ function onShow(scope, element, opts) {
+ if (!opts.target) {
+ throw new Error('$mdSelect.show() expected a target element in options.target but got ' +
+ '"' + opts.target + '"!');
+ }
+
+ angular.extend(opts, {
+ isRemoved: false,
+ target: angular.element(opts.target), //make sure it's not a naked dom node
+ parent: angular.element(opts.parent),
+ selectEl: element.find('md-select-menu'),
+ contentEl: element.find('md-content'),
+ backdrop: opts.hasBackdrop && angular.element('<md-backdrop class="md-select-backdrop md-click-catcher">')
+ });
+
+ opts.resizeFn = function() {
+ $$rAF(function() {
+ $$rAF(function() {
+ animateSelect(scope, element, opts);
+ });
+ });
+ };
+
+ angular.element($window).on('resize', opts.resizeFn);
+ angular.element($window).on('orientationchange', opts.resizeFn);
+
+
+ configureAria();
+
+ element.removeClass('md-leave');
+
+ var optionNodes = opts.selectEl[0].getElementsByTagName('md-option');
+
+ if (opts.loadingAsync && opts.loadingAsync.then) {
+ opts.loadingAsync.then(function() {
+ scope.$$loadingAsyncDone = true;
+ // Give ourselves two frames for the progress loader to clear out.
+ $$rAF(function() {
+ $$rAF(function() {
+ // Don't go forward if the select has been removed in this time...
+ if (opts.isRemoved) return;
+ animateSelect(scope, element, opts);
+ });
+ });
+ });
+ } else if (opts.loadingAsync) {
+ scope.$$loadingAsyncDone = true;
+ }
+
+ if (opts.disableParentScroll && !$mdUtil.getClosest(opts.target, 'MD-DIALOG')) {
+ opts.restoreScroll = $mdUtil.disableScrollAround(opts.target);
+ } else {
+ opts.disableParentScroll = false;
+ }
+ // Only activate click listeners after a short time to stop accidental double taps/clicks
+ // from clicking the wrong item
+ $timeout(activateInteraction, 75, false);
+
+ if (opts.backdrop) {
+ $mdTheming.inherit(opts.backdrop, opts.parent);
+ opts.parent.append(opts.backdrop);
+ }
+ opts.parent.append(element);
+
+ // Give the select a frame to 'initialize' in the DOM,
+ // so we can read its height/width/position
+ $$rAF(function() {
+ $$rAF(function() {
+ if (opts.isRemoved) return;
+ animateSelect(scope, element, opts);
+ });
+ });
+
+ return $mdUtil.transitionEndPromise(opts.selectEl, {timeout: 350});
+
+ function configureAria() {
+ opts.target.attr('aria-expanded', 'true');
+ }
+
+ function activateInteraction() {
+ if (opts.isRemoved) return;
+ var selectCtrl = opts.selectEl.controller('mdSelectMenu') || {};
+ element.addClass('md-clickable');
+
+ opts.backdrop && opts.backdrop.on('click', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ opts.restoreFocus = false;
+ scope.$apply($mdSelect.cancel);
+ });
+
+ // Escape to close
+ opts.selectEl.on('keydown', function(ev) {
+ switch (ev.keyCode) {
+ case $mdConstant.KEY_CODE.SPACE:
+ case $mdConstant.KEY_CODE.ENTER:
+ var option = $mdUtil.getClosest(ev.target, 'md-option');
+ if (option) {
+ opts.selectEl.triggerHandler({
+ type: 'click',
+ target: option
+ });
+ ev.preventDefault();
+ }
+ break;
+ case $mdConstant.KEY_CODE.TAB:
+ case $mdConstant.KEY_CODE.ESCAPE:
+ ev.preventDefault();
+ opts.restoreFocus = true;
+ scope.$apply($mdSelect.cancel);
+ }
+ });
+
+ // Cycling of options, and closing on enter
+ opts.selectEl.on('keydown', function(ev) {
+ switch (ev.keyCode) {
+ case $mdConstant.KEY_CODE.UP_ARROW: return focusPrevOption();
+ case $mdConstant.KEY_CODE.DOWN_ARROW: return focusNextOption();
+ default:
+ if (ev.keyCode >= 31 && ev.keyCode <= 90) {
+ var optNode = opts.selectEl.controller('mdSelectMenu').optNodeForKeyboardSearch(ev);
+ optNode && optNode.focus();
+ }
+ }
+ });
+
+
+ function focusOption(direction) {
+ var optionsArray = nodesToArray(optionNodes);
+ var index = optionsArray.indexOf(opts.focusedNode);
+ 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--;
+ }
+ var newOption = opts.focusedNode = optionsArray[index];
+ newOption && newOption.focus();
+ }
+ function focusNextOption() {
+ focusOption('next');
+ }
+ function focusPrevOption() {
+ focusOption('prev');
+ }
+
+ opts.selectEl.on('click', checkCloseMenu);
+ opts.selectEl.on('keydown', function(e) {
+ if (e.keyCode == 32 || e.keyCode == 13) {
+ checkCloseMenu();
+ }
+ });
+
+ function checkCloseMenu() {
+ if (!selectCtrl.isMultiple) {
+ opts.restoreFocus = true;
+ scope.$evalAsync(function() {
+ $mdSelect.hide(selectCtrl.ngModel.$viewValue);
+ });
+ }
+ }
+ }
+
+ }
+
+ function onRemove(scope, element, opts) {
+ opts.isRemoved = true;
+ element.addClass('md-leave')
+ .removeClass('md-clickable');
+ opts.target.attr('aria-expanded', 'false');
+
+
+ angular.element($window).off('resize', opts.resizeFn);
+ angular.element($window).off('orientationchange', opts.resizefn);
+ opts.resizeFn = undefined;
+
+ var mdSelect = opts.selectEl.controller('mdSelect');
+ if (mdSelect) {
+ mdSelect.setLabelText(opts.selectEl.controller('mdSelectMenu').selectedLabels());
+ }
+
+ return $mdUtil.transitionEndPromise(element, { timeout: 350 }).then(function() {
+ element.removeClass('md-active');
+ opts.backdrop && opts.backdrop.remove();
+ if (element[0].parentNode === opts.parent[0]) {
+ opts.parent[0].removeChild(element[0]); // use browser to avoid $destroy event
+ }
+ if (opts.disableParentScroll) {
+ opts.restoreScroll();
+ }
+ if (opts.restoreFocus) opts.target.focus();
+ });
+ }
+
+ function animateSelect(scope, element, opts) {
+ var containerNode = element[0],
+ targetNode = opts.target[0].firstElementChild.firstElementChild, // target the first span, functioning as the label
+ parentNode = opts.parent[0],
+ 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,
+ isScrollable = contentNode.scrollHeight > contentNode.offsetHeight,
+ selectedNode = selectNode.querySelector('md-option[selected]'),
+ optionNodes = selectNode.getElementsByTagName('md-option'),
+ optgroupNodes = selectNode.getElementsByTagName('md-optgroup');
+
+
+ var centeredNode;
+ // 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, 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;
+ }
+
+ if (contentNode.offsetWidth > maxWidth) {
+ contentNode.style['max-width'] = maxWidth + 'px';
+ }
+ 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');
+ }
+
+ // Get the selectMenuRect *after* max-width is possibly set above
+ 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;
+ }
+
+ var focusedNode = centeredNode;
+ if ((focusedNode.tagName || '').toUpperCase() === 'MD-OPTGROUP') {
+ focusedNode = optionNodes[0] || contentNode.firstElementChild || contentNode;
+ }
+
+ 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;
+ 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;
+ top = Math.floor(targetRect.top + targetRect.height / 2 - centeredRect.height / 2 -
+ centeredRect.top + contentNode.scrollTop);
+
+
+ transformOrigin = (centeredRect.left + targetRect.width / 2) + 'px ' +
+ (centeredRect.top + centeredRect.height / 2 - contentNode.scrollTop) + 'px 0px';
+
+ containerNode.style.minWidth = targetRect.width + centeredRect.paddingLeft +
+ centeredRect.paddingRight + 'px';
+ }
+
+ // Keep left and top within the window
+ var containerRect = containerNode.getBoundingClientRect();
+ containerNode.style.left = clamp(bounds.left, left, bounds.right - containerRect.width) + 'px';
+ containerNode.style.top = clamp(bounds.top, top, bounds.bottom - containerRect.height) + 'px';
+ selectNode.style[$mdConstant.CSS.TRANSFORM_ORIGIN] = transformOrigin;
+
+ selectNode.style[$mdConstant.CSS.TRANSFORM] = 'scale(' +
+ Math.min(targetRect.width / selectMenuRect.width, 1.0) + ',' +
+ Math.min(targetRect.height / selectMenuRect.height, 1.0) +
+ ')';
+
+
+ $$rAF(function() {
+ element.addClass('md-active');
+ selectNode.style[$mdConstant.CSS.TRANSFORM] = '';
+ if (focusedNode) {
+ opts.focusedNode = focusedNode;
+ focusedNode.focus();
+ }
+ });
+ }
+
+ }
+
+ 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 };
+ }
+}
+SelectProvider.$inject = ["$$interimElementProvider"];
+
+// Annoying method to copy nodes to an array, thanks to IE
+function nodesToArray(nodes) {
+ var results = [];
+ for (var i = 0; i < nodes.length; ++i) {
+ results.push(nodes.item(i));
+ }
+ return results;
+}
+
+})(window, window.angular); \ No newline at end of file