mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-04-05 23:51:03 +00:00
Objects received from outside AngularJS may have had their `hasOwnProperty` method overridden with something else. In cases where we can do this without incurring a performance penalty we call directly on Object.prototype.hasOwnProperty to ensure that we use the correct method. Also, we have some internal hash objects, where the keys for the map are provided from outside AngularJS. In such cases we either prevent `hasOwnProperty` from being used as a key or provide some other way of preventing our objects from having their `hasOwnProperty` overridden. BREAKING CHANGE: Inputs with name equal to "hasOwnProperty" are not allowed inside form or ngForm directives. Before, inputs whose name was "hasOwnProperty" were quietly ignored and not added to the scope. Now a badname exception is thrown. Using "hasOwnProperty" for an input name would be very unusual and bad practice. Either do not include such an input in a `form` or `ngForm` directive or change the name of the input. Closes #3331
619 lines
24 KiB
JavaScript
619 lines
24 KiB
JavaScript
'use strict';
|
|
|
|
var ngOptionsMinErr = minErr('ngOptions');
|
|
/**
|
|
* @ngdoc directive
|
|
* @name ng.directive:select
|
|
* @restrict E
|
|
*
|
|
* @description
|
|
* HTML `SELECT` element with angular data-binding.
|
|
*
|
|
* # `ngOptions`
|
|
*
|
|
* The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
|
|
* elements for the `<select>` element using the array or object obtained by evaluating the
|
|
* `ngOptions` comprehension_expression.
|
|
*
|
|
* When an item in the `<select>` menu is selected, the array element or object property
|
|
* represented by the selected option will be bound to the model identified by the `ngModel`
|
|
* directive.
|
|
*
|
|
* Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
|
|
* be nested into the `<select>` element. This element will then represent the `null` or "not selected"
|
|
* option. See example below for demonstration.
|
|
*
|
|
* Note: `ngOptions` provides iterator facility for `<option>` element which should be used instead
|
|
* of {@link ng.directive:ngRepeat ngRepeat} when you want the
|
|
* `select` model to be bound to a non-string value. This is because an option element can only
|
|
* be bound to string values at present.
|
|
*
|
|
* @param {string} ngModel Assignable angular expression to data-bind to.
|
|
* @param {string=} name Property name of the form under which the control is published.
|
|
* @param {string=} required The control is considered valid only if value is entered.
|
|
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
|
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
|
* `required` when you want to data-bind to the `required` attribute.
|
|
* @param {comprehension_expression=} ngOptions in one of the following forms:
|
|
*
|
|
* * for array data sources:
|
|
* * `label` **`for`** `value` **`in`** `array`
|
|
* * `select` **`as`** `label` **`for`** `value` **`in`** `array`
|
|
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
|
|
* * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
|
|
* * for object data sources:
|
|
* * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
|
|
* * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
|
|
* * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
|
|
* * `select` **`as`** `label` **`group by`** `group`
|
|
* **`for` `(`**`key`**`,`** `value`**`) in`** `object`
|
|
*
|
|
* Where:
|
|
*
|
|
* * `array` / `object`: an expression which evaluates to an array / object to iterate over.
|
|
* * `value`: local variable which will refer to each item in the `array` or each property value
|
|
* of `object` during iteration.
|
|
* * `key`: local variable which will refer to a property name in `object` during iteration.
|
|
* * `label`: The result of this expression will be the label for `<option>` element. The
|
|
* `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
|
|
* * `select`: The result of this expression will be bound to the model of the parent `<select>`
|
|
* element. If not specified, `select` expression will default to `value`.
|
|
* * `group`: The result of this expression will be used to group options using the `<optgroup>`
|
|
* DOM element.
|
|
* * `trackexpr`: Used when working with an array of objects. The result of this expression will be
|
|
* used to identify the objects in the array. The `trackexpr` will most likely refer to the
|
|
* `value` variable (e.g. `value.propertyName`).
|
|
*
|
|
* @example
|
|
<doc:example>
|
|
<doc:source>
|
|
<script>
|
|
function MyCntrl($scope) {
|
|
$scope.colors = [
|
|
{name:'black', shade:'dark'},
|
|
{name:'white', shade:'light'},
|
|
{name:'red', shade:'dark'},
|
|
{name:'blue', shade:'dark'},
|
|
{name:'yellow', shade:'light'}
|
|
];
|
|
$scope.color = $scope.colors[2]; // red
|
|
}
|
|
</script>
|
|
<div ng-controller="MyCntrl">
|
|
<ul>
|
|
<li ng-repeat="color in colors">
|
|
Name: <input ng-model="color.name">
|
|
[<a href ng-click="colors.splice($index, 1)">X</a>]
|
|
</li>
|
|
<li>
|
|
[<a href ng-click="colors.push({})">add</a>]
|
|
</li>
|
|
</ul>
|
|
<hr/>
|
|
Color (null not allowed):
|
|
<select ng-model="color" ng-options="c.name for c in colors"></select><br>
|
|
|
|
Color (null allowed):
|
|
<span class="nullable">
|
|
<select ng-model="color" ng-options="c.name for c in colors">
|
|
<option value="">-- chose color --</option>
|
|
</select>
|
|
</span><br/>
|
|
|
|
Color grouped by shade:
|
|
<select ng-model="color" ng-options="c.name group by c.shade for c in colors">
|
|
</select><br/>
|
|
|
|
|
|
Select <a href ng-click="color={name:'not in list'}">bogus</a>.<br>
|
|
<hr/>
|
|
Currently selected: {{ {selected_color:color} }}
|
|
<div style="border:solid 1px black; height:20px"
|
|
ng-style="{'background-color':color.name}">
|
|
</div>
|
|
</div>
|
|
</doc:source>
|
|
<doc:scenario>
|
|
it('should check ng-options', function() {
|
|
expect(binding('{selected_color:color}')).toMatch('red');
|
|
select('color').option('0');
|
|
expect(binding('{selected_color:color}')).toMatch('black');
|
|
using('.nullable').select('color').option('');
|
|
expect(binding('{selected_color:color}')).toMatch('null');
|
|
});
|
|
</doc:scenario>
|
|
</doc:example>
|
|
*/
|
|
|
|
var ngOptionsDirective = valueFn({ terminal: true });
|
|
var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
|
//0000111110000000000022220000000000000000000000333300000000000000444444444444444000000000555555555555555000000066666666666666600000000000000007777000000000000000000088888
|
|
var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/,
|
|
nullModelCtrl = {$setViewValue: noop};
|
|
|
|
return {
|
|
restrict: 'E',
|
|
require: ['select', '?ngModel'],
|
|
controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
|
|
var self = this,
|
|
optionsMap = {},
|
|
ngModelCtrl = nullModelCtrl,
|
|
nullOption,
|
|
unknownOption;
|
|
|
|
|
|
self.databound = $attrs.ngModel;
|
|
|
|
|
|
self.init = function(ngModelCtrl_, nullOption_, unknownOption_) {
|
|
ngModelCtrl = ngModelCtrl_;
|
|
nullOption = nullOption_;
|
|
unknownOption = unknownOption_;
|
|
};
|
|
|
|
|
|
self.addOption = function(value) {
|
|
assertNotHasOwnProperty(value, '"option value"');
|
|
optionsMap[value] = true;
|
|
|
|
if (ngModelCtrl.$viewValue == value) {
|
|
$element.val(value);
|
|
if (unknownOption.parent()) unknownOption.remove();
|
|
}
|
|
};
|
|
|
|
|
|
self.removeOption = function(value) {
|
|
if (this.hasOption(value)) {
|
|
delete optionsMap[value];
|
|
if (ngModelCtrl.$viewValue == value) {
|
|
this.renderUnknownOption(value);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
self.renderUnknownOption = function(val) {
|
|
var unknownVal = '? ' + hashKey(val) + ' ?';
|
|
unknownOption.val(unknownVal);
|
|
$element.prepend(unknownOption);
|
|
$element.val(unknownVal);
|
|
unknownOption.prop('selected', true); // needed for IE
|
|
};
|
|
|
|
|
|
self.hasOption = function(value) {
|
|
return optionsMap.hasOwnProperty(value);
|
|
};
|
|
|
|
$scope.$on('$destroy', function() {
|
|
// disable unknown option so that we don't do work when the whole select is being destroyed
|
|
self.renderUnknownOption = noop;
|
|
});
|
|
}],
|
|
|
|
link: function(scope, element, attr, ctrls) {
|
|
// if ngModel is not defined, we don't need to do anything
|
|
if (!ctrls[1]) return;
|
|
|
|
var selectCtrl = ctrls[0],
|
|
ngModelCtrl = ctrls[1],
|
|
multiple = attr.multiple,
|
|
optionsExp = attr.ngOptions,
|
|
nullOption = false, // if false, user will not be able to select it (used by ngOptions)
|
|
emptyOption,
|
|
// we can't just jqLite('<option>') since jqLite is not smart enough
|
|
// to create it in <select> and IE barfs otherwise.
|
|
optionTemplate = jqLite(document.createElement('option')),
|
|
optGroupTemplate =jqLite(document.createElement('optgroup')),
|
|
unknownOption = optionTemplate.clone();
|
|
|
|
// find "null" option
|
|
for(var i = 0, children = element.children(), ii = children.length; i < ii; i++) {
|
|
if (children[i].value == '') {
|
|
emptyOption = nullOption = children.eq(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
selectCtrl.init(ngModelCtrl, nullOption, unknownOption);
|
|
|
|
// required validator
|
|
if (multiple && (attr.required || attr.ngRequired)) {
|
|
var requiredValidator = function(value) {
|
|
ngModelCtrl.$setValidity('required', !attr.required || (value && value.length));
|
|
return value;
|
|
};
|
|
|
|
ngModelCtrl.$parsers.push(requiredValidator);
|
|
ngModelCtrl.$formatters.unshift(requiredValidator);
|
|
|
|
attr.$observe('required', function() {
|
|
requiredValidator(ngModelCtrl.$viewValue);
|
|
});
|
|
}
|
|
|
|
if (optionsExp) Options(scope, element, ngModelCtrl);
|
|
else if (multiple) Multiple(scope, element, ngModelCtrl);
|
|
else Single(scope, element, ngModelCtrl, selectCtrl);
|
|
|
|
|
|
////////////////////////////
|
|
|
|
|
|
|
|
function Single(scope, selectElement, ngModelCtrl, selectCtrl) {
|
|
ngModelCtrl.$render = function() {
|
|
var viewValue = ngModelCtrl.$viewValue;
|
|
|
|
if (selectCtrl.hasOption(viewValue)) {
|
|
if (unknownOption.parent()) unknownOption.remove();
|
|
selectElement.val(viewValue);
|
|
if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy
|
|
} else {
|
|
if (isUndefined(viewValue) && emptyOption) {
|
|
selectElement.val('');
|
|
} else {
|
|
selectCtrl.renderUnknownOption(viewValue);
|
|
}
|
|
}
|
|
};
|
|
|
|
selectElement.on('change', function() {
|
|
scope.$apply(function() {
|
|
if (unknownOption.parent()) unknownOption.remove();
|
|
ngModelCtrl.$setViewValue(selectElement.val());
|
|
});
|
|
});
|
|
}
|
|
|
|
function Multiple(scope, selectElement, ctrl) {
|
|
var lastView;
|
|
ctrl.$render = function() {
|
|
var items = new HashMap(ctrl.$viewValue);
|
|
forEach(selectElement.find('option'), function(option) {
|
|
option.selected = isDefined(items.get(option.value));
|
|
});
|
|
};
|
|
|
|
// we have to do it on each watch since ngModel watches reference, but
|
|
// we need to work of an array, so we need to see if anything was inserted/removed
|
|
scope.$watch(function selectMultipleWatch() {
|
|
if (!equals(lastView, ctrl.$viewValue)) {
|
|
lastView = copy(ctrl.$viewValue);
|
|
ctrl.$render();
|
|
}
|
|
});
|
|
|
|
selectElement.on('change', function() {
|
|
scope.$apply(function() {
|
|
var array = [];
|
|
forEach(selectElement.find('option'), function(option) {
|
|
if (option.selected) {
|
|
array.push(option.value);
|
|
}
|
|
});
|
|
ctrl.$setViewValue(array);
|
|
});
|
|
});
|
|
}
|
|
|
|
function Options(scope, selectElement, ctrl) {
|
|
var match;
|
|
|
|
if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) {
|
|
throw ngOptionsMinErr('iexp',
|
|
"Expected expression in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '{0}'. Element: {1}",
|
|
optionsExp, startingTag(selectElement));
|
|
}
|
|
|
|
var displayFn = $parse(match[2] || match[1]),
|
|
valueName = match[4] || match[6],
|
|
keyName = match[5],
|
|
groupByFn = $parse(match[3] || ''),
|
|
valueFn = $parse(match[2] ? match[1] : valueName),
|
|
valuesFn = $parse(match[7]),
|
|
track = match[8],
|
|
trackFn = track ? $parse(match[8]) : null,
|
|
// This is an array of array of existing option groups in DOM. We try to reuse these if possible
|
|
// optionGroupsCache[0] is the options with no option group
|
|
// optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
|
|
optionGroupsCache = [[{element: selectElement, label:''}]];
|
|
|
|
if (nullOption) {
|
|
// compile the element since there might be bindings in it
|
|
$compile(nullOption)(scope);
|
|
|
|
// remove the class, which is added automatically because we recompile the element and it
|
|
// becomes the compilation root
|
|
nullOption.removeClass('ng-scope');
|
|
|
|
// we need to remove it before calling selectElement.html('') because otherwise IE will
|
|
// remove the label from the element. wtf?
|
|
nullOption.remove();
|
|
}
|
|
|
|
// clear contents, we'll add what's needed based on the model
|
|
selectElement.html('');
|
|
|
|
selectElement.on('change', function() {
|
|
scope.$apply(function() {
|
|
var optionGroup,
|
|
collection = valuesFn(scope) || [],
|
|
locals = {},
|
|
key, value, optionElement, index, groupIndex, length, groupLength, trackIndex;
|
|
|
|
if (multiple) {
|
|
value = [];
|
|
for (groupIndex = 0, groupLength = optionGroupsCache.length;
|
|
groupIndex < groupLength;
|
|
groupIndex++) {
|
|
// list of options for that group. (first item has the parent)
|
|
optionGroup = optionGroupsCache[groupIndex];
|
|
|
|
for(index = 1, length = optionGroup.length; index < length; index++) {
|
|
if ((optionElement = optionGroup[index].element)[0].selected) {
|
|
key = optionElement.val();
|
|
if (keyName) locals[keyName] = key;
|
|
if (trackFn) {
|
|
for (trackIndex = 0; trackIndex < collection.length; trackIndex++) {
|
|
locals[valueName] = collection[trackIndex];
|
|
if (trackFn(scope, locals) == key) break;
|
|
}
|
|
} else {
|
|
locals[valueName] = collection[key];
|
|
}
|
|
value.push(valueFn(scope, locals));
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
key = selectElement.val();
|
|
if (key == '?') {
|
|
value = undefined;
|
|
} else if (key == ''){
|
|
value = null;
|
|
} else {
|
|
if (trackFn) {
|
|
for (trackIndex = 0; trackIndex < collection.length; trackIndex++) {
|
|
locals[valueName] = collection[trackIndex];
|
|
if (trackFn(scope, locals) == key) {
|
|
value = valueFn(scope, locals);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
locals[valueName] = collection[key];
|
|
if (keyName) locals[keyName] = key;
|
|
value = valueFn(scope, locals);
|
|
}
|
|
}
|
|
}
|
|
ctrl.$setViewValue(value);
|
|
});
|
|
});
|
|
|
|
ctrl.$render = render;
|
|
|
|
// TODO(vojta): can't we optimize this ?
|
|
scope.$watch(render);
|
|
|
|
function render() {
|
|
var optionGroups = {'':[]}, // Temporary location for the option groups before we render them
|
|
optionGroupNames = [''],
|
|
optionGroupName,
|
|
optionGroup,
|
|
option,
|
|
existingParent, existingOptions, existingOption,
|
|
modelValue = ctrl.$modelValue,
|
|
values = valuesFn(scope) || [],
|
|
keys = keyName ? sortedKeys(values) : values,
|
|
key,
|
|
groupLength, length,
|
|
groupIndex, index,
|
|
locals = {},
|
|
selected,
|
|
selectedSet = false, // nothing is selected yet
|
|
lastElement,
|
|
element,
|
|
label;
|
|
|
|
if (multiple) {
|
|
if (trackFn && isArray(modelValue)) {
|
|
selectedSet = new HashMap([]);
|
|
for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) {
|
|
locals[valueName] = modelValue[trackIndex];
|
|
selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]);
|
|
}
|
|
} else {
|
|
selectedSet = new HashMap(modelValue);
|
|
}
|
|
}
|
|
|
|
// We now build up the list of options we need (we merge later)
|
|
for (index = 0; length = keys.length, index < length; index++) {
|
|
|
|
key = index;
|
|
if (keyName) {
|
|
key = keys[index];
|
|
if ( key.charAt(0) === '$' ) continue;
|
|
locals[keyName] = key;
|
|
}
|
|
|
|
locals[valueName] = values[key];
|
|
|
|
optionGroupName = groupByFn(scope, locals) || '';
|
|
if (!(optionGroup = optionGroups[optionGroupName])) {
|
|
optionGroup = optionGroups[optionGroupName] = [];
|
|
optionGroupNames.push(optionGroupName);
|
|
}
|
|
if (multiple) {
|
|
selected = selectedSet.remove(trackFn ? trackFn(scope, locals) : valueFn(scope, locals)) !== undefined;
|
|
} else {
|
|
if (trackFn) {
|
|
var modelCast = {};
|
|
modelCast[valueName] = modelValue;
|
|
selected = trackFn(scope, modelCast) === trackFn(scope, locals);
|
|
} else {
|
|
selected = modelValue === valueFn(scope, locals);
|
|
}
|
|
selectedSet = selectedSet || selected; // see if at least one item is selected
|
|
}
|
|
label = displayFn(scope, locals); // what will be seen by the user
|
|
label = label === undefined ? '' : label; // doing displayFn(scope, locals) || '' overwrites zero values
|
|
optionGroup.push({
|
|
id: trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index), // either the index into array or key from object
|
|
label: label,
|
|
selected: selected // determine if we should be selected
|
|
});
|
|
}
|
|
if (!multiple) {
|
|
if (nullOption || modelValue === null) {
|
|
// insert null option if we have a placeholder, or the model is null
|
|
optionGroups[''].unshift({id:'', label:'', selected:!selectedSet});
|
|
} else if (!selectedSet) {
|
|
// option could not be found, we have to insert the undefined item
|
|
optionGroups[''].unshift({id:'?', label:'', selected:true});
|
|
}
|
|
}
|
|
|
|
// Now we need to update the list of DOM nodes to match the optionGroups we computed above
|
|
for (groupIndex = 0, groupLength = optionGroupNames.length;
|
|
groupIndex < groupLength;
|
|
groupIndex++) {
|
|
// current option group name or '' if no group
|
|
optionGroupName = optionGroupNames[groupIndex];
|
|
|
|
// list of options for that group. (first item has the parent)
|
|
optionGroup = optionGroups[optionGroupName];
|
|
|
|
if (optionGroupsCache.length <= groupIndex) {
|
|
// we need to grow the optionGroups
|
|
existingParent = {
|
|
element: optGroupTemplate.clone().attr('label', optionGroupName),
|
|
label: optionGroup.label
|
|
};
|
|
existingOptions = [existingParent];
|
|
optionGroupsCache.push(existingOptions);
|
|
selectElement.append(existingParent.element);
|
|
} else {
|
|
existingOptions = optionGroupsCache[groupIndex];
|
|
existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element
|
|
|
|
// update the OPTGROUP label if not the same.
|
|
if (existingParent.label != optionGroupName) {
|
|
existingParent.element.attr('label', existingParent.label = optionGroupName);
|
|
}
|
|
}
|
|
|
|
lastElement = null; // start at the beginning
|
|
for(index = 0, length = optionGroup.length; index < length; index++) {
|
|
option = optionGroup[index];
|
|
if ((existingOption = existingOptions[index+1])) {
|
|
// reuse elements
|
|
lastElement = existingOption.element;
|
|
if (existingOption.label !== option.label) {
|
|
lastElement.text(existingOption.label = option.label);
|
|
}
|
|
if (existingOption.id !== option.id) {
|
|
lastElement.val(existingOption.id = option.id);
|
|
}
|
|
// lastElement.prop('selected') provided by jQuery has side-effects
|
|
if (lastElement[0].selected !== option.selected) {
|
|
lastElement.prop('selected', (existingOption.selected = option.selected));
|
|
}
|
|
} else {
|
|
// grow elements
|
|
|
|
// if it's a null option
|
|
if (option.id === '' && nullOption) {
|
|
// put back the pre-compiled element
|
|
element = nullOption;
|
|
} else {
|
|
// jQuery(v1.4.2) Bug: We should be able to chain the method calls, but
|
|
// in this version of jQuery on some browser the .text() returns a string
|
|
// rather then the element.
|
|
(element = optionTemplate.clone())
|
|
.val(option.id)
|
|
.attr('selected', option.selected)
|
|
.text(option.label);
|
|
}
|
|
|
|
existingOptions.push(existingOption = {
|
|
element: element,
|
|
label: option.label,
|
|
id: option.id,
|
|
selected: option.selected
|
|
});
|
|
if (lastElement) {
|
|
lastElement.after(element);
|
|
} else {
|
|
existingParent.element.append(element);
|
|
}
|
|
lastElement = element;
|
|
}
|
|
}
|
|
// remove any excessive OPTIONs in a group
|
|
index++; // increment since the existingOptions[0] is parent element not OPTION
|
|
while(existingOptions.length > index) {
|
|
existingOptions.pop().element.remove();
|
|
}
|
|
}
|
|
// remove any excessive OPTGROUPs from select
|
|
while(optionGroupsCache.length > groupIndex) {
|
|
optionGroupsCache.pop()[0].element.remove();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}];
|
|
|
|
var optionDirective = ['$interpolate', function($interpolate) {
|
|
var nullSelectCtrl = {
|
|
addOption: noop,
|
|
removeOption: noop
|
|
};
|
|
|
|
return {
|
|
restrict: 'E',
|
|
priority: 100,
|
|
compile: function(element, attr) {
|
|
if (isUndefined(attr.value)) {
|
|
var interpolateFn = $interpolate(element.text(), true);
|
|
if (!interpolateFn) {
|
|
attr.$set('value', element.text());
|
|
}
|
|
}
|
|
|
|
return function (scope, element, attr) {
|
|
var selectCtrlName = '$selectController',
|
|
parent = element.parent(),
|
|
selectCtrl = parent.data(selectCtrlName) ||
|
|
parent.parent().data(selectCtrlName); // in case we are in optgroup
|
|
|
|
if (selectCtrl && selectCtrl.databound) {
|
|
// For some reason Opera defaults to true and if not overridden this messes up the repeater.
|
|
// We don't want the view to drive the initialization of the model anyway.
|
|
element.prop('selected', false);
|
|
} else {
|
|
selectCtrl = nullSelectCtrl;
|
|
}
|
|
|
|
if (interpolateFn) {
|
|
scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
|
|
attr.$set('value', newVal);
|
|
if (newVal !== oldVal) selectCtrl.removeOption(oldVal);
|
|
selectCtrl.addOption(newVal);
|
|
});
|
|
} else {
|
|
selectCtrl.addOption(attr.value);
|
|
}
|
|
|
|
element.on('$destroy', function() {
|
|
selectCtrl.removeOption(attr.value);
|
|
});
|
|
};
|
|
}
|
|
};
|
|
}];
|