mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-04-26 13:05:35 +08:00
refactor(select): reduce duplication and reorder functions
This commit is contained in:
@@ -77,6 +77,24 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* used to identify the objects in the array. The `trackexpr` will most likely refer to the
|
||||
* `value` variable (e.g. `value.propertyName`). With this the selection is preserved
|
||||
* even when the options are recreated (e.g. reloaded from the server).
|
||||
|
||||
* <div class="alert alert-info">
|
||||
* **Note:** Using `selectAs` together with `trackexpr` is not possible (and will throw).
|
||||
* TODO: Add some nice reasoning here, add a minErr and a nice error page.
|
||||
* reasoning:
|
||||
* - Example: <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
|
||||
* values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItemß'}}],
|
||||
* $scope.selected = {name: 'aSubItem'};
|
||||
* - trackBy is always applied to `value`, with purpose to preserve the selection,
|
||||
* (to `item` in this case)
|
||||
* - to calculate whether an item is selected we do the following:
|
||||
* 1. apply `trackBy` to the values in the array, e.g.
|
||||
* In the example: [1,2]
|
||||
* 2. apply `trackBy` to the already selected value in `ngModel`:
|
||||
* In the example: this is not possible, as `trackBy` refers to `item.id`, but the selected
|
||||
* value from `ngModel` is `{name: aSubItem}`.
|
||||
*
|
||||
* </div>
|
||||
*
|
||||
* @example
|
||||
<example module="selectExample">
|
||||
@@ -345,7 +363,9 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// 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:''}]];
|
||||
optionGroupsCache = [[{element: selectElement, label:''}]],
|
||||
//re-usable object to represent option's locals
|
||||
locals = {};
|
||||
|
||||
if (nullOption) {
|
||||
// compile the element since there might be bindings in it
|
||||
@@ -363,146 +383,109 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// clear contents, we'll add what's needed based on the model
|
||||
selectElement.empty();
|
||||
|
||||
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) {
|
||||
value.push(getViewValue(optionElement, locals, collection));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
value = getViewValue(selectElement, locals, collection);
|
||||
}
|
||||
ctrl.$setViewValue(value);
|
||||
render();
|
||||
});
|
||||
});
|
||||
selectElement.on('change', selectionChanged);
|
||||
|
||||
ctrl.$render = render;
|
||||
|
||||
scope.$watchCollection(valuesFn, scheduleRendering);
|
||||
scope.$watchCollection(function () {
|
||||
var locals = {},
|
||||
values = valuesFn(scope);
|
||||
if (values) {
|
||||
var toDisplay = new Array(values.length);
|
||||
for (var i = 0, ii = values.length; i < ii; i++) {
|
||||
locals[valueName] = values[i];
|
||||
toDisplay[i] = displayFn(scope, locals);
|
||||
}
|
||||
return toDisplay;
|
||||
}
|
||||
}, scheduleRendering);
|
||||
scope.$watchCollection(getLabels, scheduleRendering);
|
||||
|
||||
if (multiple) {
|
||||
scope.$watchCollection(function() { return ctrl.$modelValue; }, scheduleRendering);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
function getSelectedSet() {
|
||||
var selectedSet = false;
|
||||
function callExpression(exprFn, key, value) {
|
||||
locals[valueName] = value;
|
||||
if (keyName) locals[keyName] = key;
|
||||
return exprFn(scope, locals);
|
||||
}
|
||||
|
||||
function selectionChanged() {
|
||||
scope.$apply(function() {
|
||||
var optionGroup,
|
||||
collection = valuesFn(scope) || [],
|
||||
key, value, optionElement, index, groupIndex, length, groupLength, trackIndex;
|
||||
var viewValue;
|
||||
if (multiple) {
|
||||
viewValue = [];
|
||||
forEach(selectElement.val(), function(selectedKey) {
|
||||
viewValue.push(getViewValue(selectedKey, collection[selectedKey]));
|
||||
});
|
||||
} else {
|
||||
var selectedKey = selectElement.val();
|
||||
viewValue = getViewValue(selectedKey, collection[selectedKey]);
|
||||
}
|
||||
ctrl.$setViewValue(viewValue);
|
||||
render();
|
||||
});
|
||||
}
|
||||
|
||||
function getViewValue(key, value) {
|
||||
if (key === '?') {
|
||||
return undefined;
|
||||
} else if (key === '') {
|
||||
return null;
|
||||
} else {
|
||||
var viewValueFn = selectAsFn ? selectAsFn : valueFn;
|
||||
return callExpression(viewValueFn, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
function getLabels() {
|
||||
var values = valuesFn(scope);
|
||||
var toDisplay;
|
||||
if (values && isArray(values)) {
|
||||
toDisplay = new Array(values.length);
|
||||
for (var i = 0, ii = values.length; i < ii; i++) {
|
||||
toDisplay[i] = callExpression(displayFn, i, values[i]);
|
||||
}
|
||||
return toDisplay;
|
||||
} else if (values) {
|
||||
// TODO: Add a test for this case
|
||||
toDisplay = {};
|
||||
for (var prop in values) {
|
||||
if (values.hasOwnProperty(prop)) {
|
||||
toDisplay[prop] = callExpression(displayFn, prop, values[prop]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return toDisplay;
|
||||
}
|
||||
|
||||
function createIsSelectedFn(viewValue) {
|
||||
var selectedSet;
|
||||
if (multiple) {
|
||||
var viewValue = ctrl.$viewValue;
|
||||
if (trackFn && isArray(viewValue) && !selectAs) {
|
||||
if (!selectAs && trackFn && isArray(viewValue)) {
|
||||
|
||||
selectedSet = new HashMap([]);
|
||||
var locals = {};
|
||||
for (var trackIndex = 0; trackIndex < viewValue.length; trackIndex++) {
|
||||
locals[valueName] = viewValue[trackIndex];
|
||||
selectedSet.put(trackFn(scope, locals), viewValue[trackIndex]);
|
||||
// tracking by key
|
||||
selectedSet.put(callExpression(trackFn, null, viewValue[trackIndex]), true);
|
||||
}
|
||||
} else {
|
||||
selectedSet = new HashMap(viewValue);
|
||||
}
|
||||
} else if (!selectAsFn && trackFn) {
|
||||
viewValue = callExpression(trackFn, null, viewValue);
|
||||
}
|
||||
return selectedSet;
|
||||
}
|
||||
return function isSelected(key, value) {
|
||||
var compareValueFn;
|
||||
if (selectAsFn) {
|
||||
compareValueFn = selectAsFn;
|
||||
} else if (trackFn) {
|
||||
compareValueFn = trackFn;
|
||||
} else {
|
||||
compareValueFn = valueFn;
|
||||
}
|
||||
|
||||
function getViewValue (el, locals, collection) {
|
||||
var key = el.val();
|
||||
var value;
|
||||
var calculateViewValue;
|
||||
|
||||
if (selectAsFn || trackFn) {
|
||||
calculateViewValue = function () {
|
||||
var getterFn = selectAsFn || trackFn;
|
||||
var wrappedCollectionValue = {};
|
||||
wrappedCollectionValue[valueName] = collection[key];
|
||||
wrappedCollectionValue[keyName] = key;
|
||||
|
||||
for (var i in collection) {
|
||||
if (collection.hasOwnProperty(i)) {
|
||||
locals[valueName] = collection[i];
|
||||
if (keyName) locals[keyName] = i;
|
||||
if (getterFn(scope, locals) ==
|
||||
getterFn(scope, wrappedCollectionValue)) {
|
||||
/*
|
||||
* trackBy should not be used for final calculation, because it doesn't
|
||||
* necessarily return the expected value.
|
||||
*/
|
||||
return (selectAsFn||valueFn)(scope, locals);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
calculateViewValue = function() {
|
||||
locals[valueName] = collection[key];
|
||||
if (keyName) locals[keyName] = key;
|
||||
return valueFn(scope, locals);
|
||||
};
|
||||
if (multiple) {
|
||||
return isDefined(selectedSet.remove(callExpression(compareValueFn, key, value)));
|
||||
} else {
|
||||
return viewValue == callExpression(compareValueFn, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (multiple) {
|
||||
if (keyName) locals[keyName] = key;
|
||||
calculateViewValue();
|
||||
return (selectAsFn || valueFn)(scope, locals);
|
||||
}
|
||||
|
||||
if (key == '?') {
|
||||
value = undefined;
|
||||
} else if (key === ''){
|
||||
value = null;
|
||||
} else {
|
||||
value = calculateViewValue();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function isSelected(viewValue, locals, selectedSet) {
|
||||
var compareValueFn;
|
||||
if (selectAsFn) {
|
||||
compareValueFn = selectAsFn;
|
||||
} else if (trackFn) {
|
||||
var withValueName = {};
|
||||
withValueName[valueName] = viewValue;
|
||||
compareValueFn = trackFn;
|
||||
|
||||
viewValue = trackFn(withValueName);
|
||||
} else {
|
||||
compareValueFn = valueFn;
|
||||
}
|
||||
|
||||
var optionValue = compareValueFn(scope, locals);
|
||||
|
||||
if (multiple) {
|
||||
return isDefined(selectedSet.remove(optionValue));
|
||||
}
|
||||
return viewValue === optionValue;
|
||||
}
|
||||
|
||||
function scheduleRendering() {
|
||||
@@ -512,11 +495,10 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function render() {
|
||||
renderScheduled = false;
|
||||
|
||||
// Temporary location for the option groups before we render them
|
||||
// Temporary location for the option groups before we render them
|
||||
var optionGroups = {'':[]},
|
||||
optionGroupNames = [''],
|
||||
optionGroupName,
|
||||
@@ -527,11 +509,12 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
values = valuesFn(scope) || [],
|
||||
keys = keyName ? sortedKeys(values) : values,
|
||||
key,
|
||||
value,
|
||||
groupLength, length,
|
||||
groupIndex, index,
|
||||
locals = {},
|
||||
selected,
|
||||
selectedSet = getSelectedSet(),
|
||||
isSelected = createIsSelectedFn(viewValue),
|
||||
anySelected = false,
|
||||
lastElement,
|
||||
element,
|
||||
label;
|
||||
@@ -542,24 +525,19 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
if (keyName) {
|
||||
key = keys[index];
|
||||
if ( key.charAt(0) === '$' ) continue;
|
||||
locals[keyName] = key;
|
||||
}
|
||||
value = values[key];
|
||||
|
||||
locals[valueName] = values[key];
|
||||
|
||||
optionGroupName = groupByFn(scope, locals) || '';
|
||||
optionGroupName = callExpression(groupByFn, key, value) || '';
|
||||
if (!(optionGroup = optionGroups[optionGroupName])) {
|
||||
optionGroup = optionGroups[optionGroupName] = [];
|
||||
optionGroupNames.push(optionGroupName);
|
||||
}
|
||||
|
||||
selected = isSelected(
|
||||
viewValue,
|
||||
locals,
|
||||
selectedSet);
|
||||
selectedSet = selectedSet || selected;
|
||||
selected = isSelected(key, value);
|
||||
anySelected = anySelected || selected;
|
||||
|
||||
label = displayFn(scope, locals); // what will be seen by the user
|
||||
label = callExpression(displayFn, key, value); // what will be seen by the user
|
||||
|
||||
// doing displayFn(scope, locals) || '' overwrites zero values
|
||||
label = isDefined(label) ? label : '';
|
||||
@@ -573,8 +551,8 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
if (!multiple) {
|
||||
if (nullOption || viewValue === null) {
|
||||
// insert null option if we have a placeholder, or the model is null
|
||||
optionGroups[''].unshift({id:'', label:'', selected:!selectedSet});
|
||||
} else if (!selectedSet) {
|
||||
optionGroups[''].unshift({id:'', label:'', selected:!anySelected});
|
||||
} else if (!anySelected) {
|
||||
// option could not be found, we have to insert the undefined item
|
||||
optionGroups[''].unshift({id:'?', label:'', selected:true});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user