@angular-wave/angular.ts 0.7.7 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/@types/animations/raf-scheduler.d.ts +2 -2
- package/@types/animations/shared.d.ts +0 -1
- package/@types/core/compile/attributes.d.ts +3 -3
- package/@types/core/compile/compile.d.ts +1 -1
- package/@types/core/di/injector.d.ts +0 -1
- package/@types/core/di/internal-injector.d.ts +1 -0
- package/@types/core/di/ng-module.d.ts +5 -0
- package/@types/core/filter/filter.d.ts +11 -13
- package/@types/core/sanitize/sanitize-uri.d.ts +3 -6
- package/@types/core/scope/scope.d.ts +1 -1
- package/@types/directive/attrs/attrs.d.ts +7 -1
- package/@types/directive/events/events.d.ts +9 -3
- package/@types/directive/http/http.d.ts +6 -2
- package/@types/directive/include/include.d.ts +2 -2
- package/@types/directive/input/input.d.ts +2 -12
- package/@types/directive/messages/messages.d.ts +9 -48
- package/@types/directive/model/model.d.ts +3 -3
- package/@types/directive/options/options.d.ts +13 -20
- package/@types/directive/switch/switch.d.ts +1 -0
- package/@types/directive/transclude/transclude.d.ts +10 -6
- package/@types/interface.d.ts +54 -18
- package/@types/router/common/glob.d.ts +5 -1
- package/@types/router/directives/view-directive.d.ts +2 -19
- package/@types/router/globals.d.ts +1 -2
- package/@types/router/state/state-registry.d.ts +1 -2
- package/@types/router/url/url-service.d.ts +7 -9
- package/@types/services/anchor-scroll.d.ts +1 -1
- package/@types/{core → services/exception}/exception-handler.d.ts +4 -4
- package/@types/{core/error-handler.d.ts → services/exception/interface.d.ts} +1 -1
- package/@types/services/http/http.d.ts +0 -2
- package/@types/services/http/interface.d.ts +2 -2
- package/@types/services/http-backend/http-backend.d.ts +13 -21
- package/@types/services/location/interface.d.ts +8 -0
- package/@types/{core → services}/location/location.d.ts +52 -12
- package/@types/{core → services}/sce/sce.d.ts +1 -1
- package/@types/services/template-cache/interface.d.ts +8 -2
- package/@types/services/template-cache/template-cache.d.ts +1 -1
- package/@types/services/template-request.d.ts +1 -1
- package/@types/shared/cache.d.ts +0 -2
- package/@types/shared/dom.d.ts +6 -0
- package/@types/shared/test-utils.d.ts +1 -0
- package/@types/shared/url-utils/interface.d.ts +47 -0
- package/@types/{core → shared}/url-utils/url-utils.d.ts +26 -13
- package/@types/shared/utils.d.ts +15 -0
- package/Makefile +3 -2
- package/dist/angular-ts.esm.js +982 -1190
- package/dist/angular-ts.umd.js +982 -1190
- package/dist/angular-ts.umd.min.js +1 -1
- package/docs/content/docs/directive/bind.md +9 -7
- package/docs/content/docs/directive/get.md +203 -0
- package/docs/content/docs/provider/templateCacheProvider.md +66 -1
- package/docs/content/docs/service/templateCache.md +2 -2
- package/docs/layouts/partials/hooks/head-end.html +1 -1
- package/docs/layouts/shortcodes/showcss.html +2 -0
- package/docs/static/examples/ng-bind/ng-bind.html +2 -2
- package/docs/static/typedoc/assets/hierarchy.js +1 -1
- package/docs/static/typedoc/assets/navigation.js +1 -1
- package/docs/static/typedoc/assets/search.js +1 -1
- package/docs/static/typedoc/classes/NgModule.html +32 -0
- package/docs/static/typedoc/classes/TemplateCacheProvider.html +1 -1
- package/docs/static/typedoc/hierarchy.html +1 -1
- package/docs/static/typedoc/index.html +1 -1
- package/docs/static/typedoc/interfaces/Directive.html +5 -4
- package/docs/static/typedoc/interfaces/HttpProviderDefaults.html +1 -1
- package/docs/static/typedoc/interfaces/HttpResponse.html +2 -3
- package/docs/static/typedoc/interfaces/Provider.html +15 -10
- package/docs/static/typedoc/interfaces/RequestConfig.html +1 -1
- package/docs/static/typedoc/interfaces/RequestShortcutConfig.html +1 -1
- package/docs/static/typedoc/interfaces/TemplateCache.html +7 -0
- package/docs/static/typedoc/types/AnnotatedDirectiveFactory.html +1 -0
- package/docs/static/typedoc/types/DirectiveFactory.html +1 -2
- package/docs/static/typedoc/types/DirectiveFactoryFn.html +1 -0
- package/docs/static/typedoc/types/HttpResponseStatus.html +1 -0
- package/docs/static/typedoc/types/{TemplateCache.html → SwapModeType.html} +1 -1
- package/docs/static/typedoc/variables/SwapMode.html +11 -0
- package/legacy.d.ts +0 -10
- package/package.json +1 -3
- package/src/animations/animate-children-directive.js +2 -2
- package/src/animations/raf-scheduler.js +1 -1
- package/src/animations/shared.js +0 -9
- package/src/core/compile/attributes.js +1 -1
- package/src/core/compile/compile.js +3 -3
- package/src/core/di/injector.js +4 -17
- package/src/core/di/internal-injector.js +4 -1
- package/src/core/di/ng-module.js +12 -27
- package/src/core/filter/filter.js +28 -28
- package/src/core/parse/interpreter.js +32 -38
- package/src/core/sanitize/sanitize-uri.js +3 -3
- package/src/core/scope/scope.js +2 -2
- package/src/directive/attrs/attrs.js +7 -4
- package/src/directive/events/events.js +6 -2
- package/src/directive/http/delete.spec.js +2 -0
- package/src/directive/http/get.spec.js +280 -3
- package/src/directive/http/http.js +100 -12
- package/src/directive/http/http.test.js +2 -2
- package/src/directive/http/post.spec.js +2 -0
- package/src/directive/http/put.spec.js +2 -0
- package/src/directive/include/include.js +7 -7
- package/src/directive/input/input.js +6 -28
- package/src/directive/messages/messages.js +4 -0
- package/src/directive/model/model.js +1 -1
- package/src/directive/options/options.js +454 -464
- package/src/directive/setter/setter.js +12 -14
- package/src/directive/setter/setter.spec.js +39 -16
- package/src/directive/switch/switch.js +1 -0
- package/src/directive/transclude/transclude.js +87 -89
- package/src/injection-tokens.js +1 -1
- package/src/interface.ts +68 -19
- package/src/loader.js +4 -9
- package/src/public.js +9 -15
- package/src/router/common/glob.js +5 -0
- package/src/router/directives/state-directives.spec.js +1 -1
- package/src/router/directives/view-directive.js +9 -1
- package/src/router/globals.js +0 -1
- package/src/router/state/state-registry.js +0 -1
- package/src/router/state-filters.js +2 -2
- package/src/router/url/url-service.js +5 -9
- package/src/services/anchor-scroll.html +0 -7
- package/src/services/anchor-scroll.js +1 -1
- package/src/{core → services/exception}/exception-handler.js +2 -2
- package/src/{core/error-handler.ts → services/exception/interface.ts} +1 -1
- package/src/services/http/http.js +2 -13
- package/src/services/http/interface.ts +2 -2
- package/src/services/http-backend/http-backend.js +4 -14
- package/src/services/http-backend/http-backend.spec.js +1 -4
- package/src/services/location/interface.ts +8 -0
- package/src/{core → services}/location/location.html +4 -1
- package/src/{core → services}/location/location.js +128 -26
- package/src/{core → services}/location/location.spec.js +2 -2
- package/src/{core → services}/location/location.test.js +1 -1
- package/src/{core → services}/sce/sce.html +1 -1
- package/src/{core → services}/sce/sce.js +9 -3
- package/src/{core → services}/sce/sce.spec.js +2 -3
- package/src/{core → services}/sce/sce.test.js +1 -1
- package/src/services/template-cache/interface.ts +8 -2
- package/src/services/template-cache/template-cache.js +3 -1
- package/src/services/template-cache/template-cache.spec.js +72 -0
- package/src/services/template-request.js +2 -1
- package/src/shared/cache.js +0 -2
- package/src/shared/dom.js +10 -0
- package/src/shared/test-utils.js +1 -0
- package/src/shared/url-utils/interface.ts +56 -0
- package/src/{core → shared}/url-utils/url-utils.html +4 -1
- package/src/{core → shared}/url-utils/url-utils.js +26 -23
- package/src/{core → shared}/url-utils/url-utils.spec.js +0 -8
- package/src/{core → shared}/url-utils/url-utils.test.js +1 -1
- package/src/shared/utils.js +28 -0
- package/utils/express.js +9 -1
- package/@types/core/task-tracker-factory.d.ts +0 -76
- package/@types/services/browser.d.ts +0 -101
- package/docs/static/typedoc/types/SwapInsertPosition.html +0 -2
- package/jsdoc.json +0 -22
- package/src/core/task-tracker-factory.js +0 -145
- package/src/services/browser.js +0 -212
- /package/src/{core → services}/location/location.md +0 -0
- /package/src/{core → services}/sce/sce.md +0 -0
- /package/src/{core → shared}/url-utils/url-utils.md +0 -0
|
@@ -30,522 +30,512 @@ const NG_OPTIONS_REGEXP =
|
|
|
30
30
|
// 8: collection expression
|
|
31
31
|
// 9: track by expression
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
ngOptionsDirective.$inject = ["$compile", "$parse"];
|
|
34
|
+
/**
|
|
35
|
+
*
|
|
36
|
+
* @param {import("../../core/compile/compile.js").CompileFn} $compile
|
|
37
|
+
* @param {import("../../core/parse/interface.ts").ParseService} $parse
|
|
38
|
+
* @returns {import("../../interface.ts").Directive}
|
|
39
|
+
*/
|
|
40
|
+
export function ngOptionsDirective($compile, $parse) {
|
|
36
41
|
/**
|
|
37
|
-
*
|
|
38
|
-
* @param {
|
|
39
|
-
* @param {import(
|
|
42
|
+
* @param {import('../../interface.ts').Expression} optionsExp
|
|
43
|
+
* @param {HTMLSelectElement} selectElement
|
|
44
|
+
* @param {import('../../core/scope/scope.js').Scope} scope
|
|
40
45
|
* @returns
|
|
41
46
|
*/
|
|
42
|
-
function (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
: function getHashOfValue(value) {
|
|
86
|
-
return hashKey(value);
|
|
87
|
-
};
|
|
88
|
-
const getTrackByValue = function (value, key) {
|
|
89
|
-
return getTrackByValueFn(value, getLocals(value, key));
|
|
90
|
-
};
|
|
47
|
+
function parseOptionsExpression(optionsExp, selectElement, scope) {
|
|
48
|
+
const match = optionsExp.match(NG_OPTIONS_REGEXP);
|
|
49
|
+
if (!match) {
|
|
50
|
+
throw ngOptionsMinErr(
|
|
51
|
+
"iexp",
|
|
52
|
+
"Expected expression in form of " +
|
|
53
|
+
"'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
|
|
54
|
+
" but got '{0}'. Element: {1}",
|
|
55
|
+
optionsExp,
|
|
56
|
+
startingTag(selectElement),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
// Extract the parts from the ngOptions expression
|
|
60
|
+
|
|
61
|
+
// The variable name for the value of the item in the collection
|
|
62
|
+
const valueName = match[5] || match[7];
|
|
63
|
+
// The variable name for the key of the item in the collection
|
|
64
|
+
const keyName = match[6];
|
|
65
|
+
|
|
66
|
+
// An expression that generates the viewValue for an option if there is a label expression
|
|
67
|
+
const selectAs = / as /.test(match[0]) && match[1];
|
|
68
|
+
// An expression that is used to track the id of each object in the options collection
|
|
69
|
+
const trackBy = match[9];
|
|
70
|
+
// An expression that generates the viewValue for an option if there is no label expression
|
|
71
|
+
const valueFn = $parse(match[2] ? match[1] : valueName);
|
|
72
|
+
const selectAsFn = selectAs && $parse(selectAs);
|
|
73
|
+
const viewValueFn = selectAsFn || valueFn;
|
|
74
|
+
const trackByFn = trackBy && $parse(trackBy);
|
|
75
|
+
|
|
76
|
+
// Get the value by which we are going to track the option
|
|
77
|
+
// if we have a trackFn then use that (passing scope and locals)
|
|
78
|
+
// otherwise just hash the given viewValue
|
|
79
|
+
const getTrackByValueFn = trackBy
|
|
80
|
+
? function (value, locals) {
|
|
81
|
+
return trackByFn(scope, locals);
|
|
82
|
+
}
|
|
83
|
+
: function getHashOfValue(value) {
|
|
84
|
+
return hashKey(value);
|
|
85
|
+
};
|
|
86
|
+
const getTrackByValue = function (value, key) {
|
|
87
|
+
return getTrackByValueFn(value, getLocals(value, key));
|
|
88
|
+
};
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
: function (value) {
|
|
105
|
-
locals[valueName] = value;
|
|
106
|
-
return locals;
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
class Option {
|
|
110
|
-
constructor(selectValue, viewValue, label, group, disabled) {
|
|
111
|
-
this.selectValue = selectValue;
|
|
112
|
-
this.viewValue = viewValue;
|
|
113
|
-
this.label = label;
|
|
114
|
-
this.group = group;
|
|
115
|
-
this.disabled = disabled;
|
|
90
|
+
const displayFn = $parse(match[2] || match[1]);
|
|
91
|
+
const groupByFn = $parse(match[3] || "");
|
|
92
|
+
const disableWhenFn = $parse(match[4] || "");
|
|
93
|
+
const valuesFn = $parse(match[8]);
|
|
94
|
+
|
|
95
|
+
const locals = {};
|
|
96
|
+
let getLocals = keyName
|
|
97
|
+
? function (value, key) {
|
|
98
|
+
locals[keyName] = key;
|
|
99
|
+
locals[valueName] = value;
|
|
100
|
+
return locals;
|
|
116
101
|
}
|
|
102
|
+
: function (value) {
|
|
103
|
+
locals[valueName] = value;
|
|
104
|
+
return locals;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
class Option {
|
|
108
|
+
constructor(selectValue, viewValue, label, group, disabled) {
|
|
109
|
+
this.selectValue = selectValue;
|
|
110
|
+
this.viewValue = viewValue;
|
|
111
|
+
this.label = label;
|
|
112
|
+
this.group = group;
|
|
113
|
+
this.disabled = disabled;
|
|
117
114
|
}
|
|
115
|
+
}
|
|
118
116
|
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
function getOptionValuesKeys(optionValues) {
|
|
118
|
+
let optionValuesKeys;
|
|
121
119
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
120
|
+
if (!keyName && isArrayLike(optionValues)) {
|
|
121
|
+
optionValuesKeys = optionValues;
|
|
122
|
+
} else {
|
|
123
|
+
// if object, extract keys, in enumeration order, unsorted
|
|
124
|
+
optionValuesKeys = [];
|
|
125
|
+
for (const itemKey in optionValues) {
|
|
126
|
+
if (hasOwn(optionValues, itemKey) && itemKey.charAt(0) !== "$") {
|
|
127
|
+
optionValuesKeys.push(itemKey);
|
|
131
128
|
}
|
|
132
129
|
}
|
|
133
|
-
return optionValuesKeys;
|
|
134
130
|
}
|
|
131
|
+
return optionValuesKeys;
|
|
132
|
+
}
|
|
135
133
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
// Only need to watch the displayFn if there is a specific label expression
|
|
160
|
-
if (match[2] || match[1]) {
|
|
161
|
-
const label = displayFn(scope, locals);
|
|
162
|
-
watchedArray.push(label);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Only need to watch the disableWhenFn if there is a specific disable expression
|
|
166
|
-
if (match[4]) {
|
|
167
|
-
const disableWhen = disableWhenFn(scope, locals);
|
|
168
|
-
watchedArray.push(disableWhen);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return watchedArray;
|
|
172
|
-
}),
|
|
173
|
-
|
|
174
|
-
getOptions() {
|
|
175
|
-
/** @type {Option[]} */
|
|
176
|
-
const optionItems = [];
|
|
177
|
-
/** @type {Object.<string, Option>} */
|
|
178
|
-
const selectValueMap = {};
|
|
179
|
-
|
|
180
|
-
// The option values were already computed in the `getWatchables` fn,
|
|
181
|
-
// which must have been called to trigger `getOptions`
|
|
182
|
-
const optionValues = valuesFn(scope) || [];
|
|
183
|
-
const optionValuesKeys = getOptionValuesKeys(optionValues);
|
|
184
|
-
const optionValuesLength = optionValuesKeys.length;
|
|
185
|
-
|
|
186
|
-
for (let index = 0; index < optionValuesLength; index++) {
|
|
187
|
-
const key =
|
|
188
|
-
optionValues === optionValuesKeys
|
|
189
|
-
? index
|
|
190
|
-
: optionValuesKeys[index];
|
|
191
|
-
const value = optionValues[key];
|
|
192
|
-
const locals = getLocals(value, key);
|
|
193
|
-
const viewValue = viewValueFn(scope, locals);
|
|
194
|
-
const selectValue = getTrackByValueFn(viewValue, locals);
|
|
134
|
+
return {
|
|
135
|
+
trackBy,
|
|
136
|
+
getTrackByValue,
|
|
137
|
+
getWatchables: $parse(valuesFn, (optionValues) => {
|
|
138
|
+
// Create a collection of things that we would like to watch (watchedArray)
|
|
139
|
+
// so that they can all be watched using a single $watchCollection
|
|
140
|
+
// that only runs the handler once if anything changes
|
|
141
|
+
const watchedArray = [];
|
|
142
|
+
optionValues = optionValues || [];
|
|
143
|
+
|
|
144
|
+
const optionValuesKeys = getOptionValuesKeys(optionValues);
|
|
145
|
+
const optionValuesLength = optionValuesKeys.length;
|
|
146
|
+
for (let index = 0; index < optionValuesLength; index++) {
|
|
147
|
+
const key =
|
|
148
|
+
optionValues === optionValuesKeys ? index : optionValuesKeys[index];
|
|
149
|
+
const value = optionValues[key];
|
|
150
|
+
|
|
151
|
+
const locals = getLocals(value, key);
|
|
152
|
+
const selectValue = getTrackByValueFn(value, locals);
|
|
153
|
+
watchedArray.push(selectValue);
|
|
154
|
+
|
|
155
|
+
// Only need to watch the displayFn if there is a specific label expression
|
|
156
|
+
if (match[2] || match[1]) {
|
|
195
157
|
const label = displayFn(scope, locals);
|
|
196
|
-
|
|
197
|
-
const disabled = disableWhenFn(scope, locals);
|
|
198
|
-
const optionItem = new Option(
|
|
199
|
-
selectValue,
|
|
200
|
-
viewValue,
|
|
201
|
-
label,
|
|
202
|
-
group,
|
|
203
|
-
disabled,
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
optionItems.push(optionItem);
|
|
207
|
-
selectValueMap[selectValue] = optionItem;
|
|
158
|
+
watchedArray.push(label);
|
|
208
159
|
}
|
|
209
160
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
},
|
|
216
|
-
getViewValueFromOption(option) {
|
|
217
|
-
// If the viewValue could be an object that may be mutated by the application,
|
|
218
|
-
// we need to make a copy and not return the reference to the value on the option.
|
|
219
|
-
return trackBy
|
|
220
|
-
? structuredClone(option.viewValue)
|
|
221
|
-
: option.viewValue;
|
|
222
|
-
},
|
|
223
|
-
};
|
|
224
|
-
},
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
*
|
|
230
|
-
* @param {import("../../core/scope/scope.js").Scope} scope
|
|
231
|
-
* @param {HTMLSelectElement} selectElement
|
|
232
|
-
* @param {import("../../core/compile/attributes.js").Attributes} attr
|
|
233
|
-
* @param {*} ctrls
|
|
234
|
-
*/
|
|
235
|
-
function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
|
|
236
|
-
const selectCtrl = ctrls[0];
|
|
237
|
-
const ngModelCtrl = ctrls[1];
|
|
238
|
-
const multiple = attr["multiple"];
|
|
239
|
-
|
|
240
|
-
// The emptyOption allows the application developer to provide their own custom "empty"
|
|
241
|
-
// option when the viewValue does not match any of the option values.
|
|
242
|
-
for (
|
|
243
|
-
let i = 0, children = selectElement.childNodes, ii = children.length;
|
|
244
|
-
i < ii;
|
|
245
|
-
i++
|
|
246
|
-
) {
|
|
247
|
-
if (/** @type {HTMLOptionElement} */ (children[i]).value === "") {
|
|
248
|
-
selectCtrl.hasEmptyOption = true;
|
|
249
|
-
selectCtrl.emptyOption = children[i];
|
|
250
|
-
break;
|
|
161
|
+
// Only need to watch the disableWhenFn if there is a specific disable expression
|
|
162
|
+
if (match[4]) {
|
|
163
|
+
const disableWhen = disableWhenFn(scope, locals);
|
|
164
|
+
watchedArray.push(disableWhen);
|
|
165
|
+
}
|
|
251
166
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
167
|
+
return watchedArray;
|
|
168
|
+
}),
|
|
169
|
+
|
|
170
|
+
getOptions() {
|
|
171
|
+
/** @type {Option[]} */
|
|
172
|
+
const optionItems = [];
|
|
173
|
+
/** @type {Object.<string, Option>} */
|
|
174
|
+
const selectValueMap = {};
|
|
175
|
+
|
|
176
|
+
// The option values were already computed in the `getWatchables` fn,
|
|
177
|
+
// which must have been called to trigger `getOptions`
|
|
178
|
+
const optionValues = valuesFn(scope) || [];
|
|
179
|
+
const optionValuesKeys = getOptionValuesKeys(optionValues);
|
|
180
|
+
const optionValuesLength = optionValuesKeys.length;
|
|
181
|
+
|
|
182
|
+
for (let index = 0; index < optionValuesLength; index++) {
|
|
183
|
+
const key =
|
|
184
|
+
optionValues === optionValuesKeys ? index : optionValuesKeys[index];
|
|
185
|
+
const value = optionValues[key];
|
|
186
|
+
const locals = getLocals(value, key);
|
|
187
|
+
const viewValue = viewValueFn(scope, locals);
|
|
188
|
+
const selectValue = getTrackByValueFn(viewValue, locals);
|
|
189
|
+
const label = displayFn(scope, locals);
|
|
190
|
+
const group = groupByFn(scope, locals);
|
|
191
|
+
const disabled = disableWhenFn(scope, locals);
|
|
192
|
+
const optionItem = new Option(
|
|
193
|
+
selectValue,
|
|
194
|
+
viewValue,
|
|
195
|
+
label,
|
|
196
|
+
group,
|
|
197
|
+
disabled,
|
|
198
|
+
);
|
|
258
199
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
200
|
+
optionItems.push(optionItem);
|
|
201
|
+
selectValueMap[selectValue] = optionItem;
|
|
202
|
+
}
|
|
262
203
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
// Update the controller methods for multiple selectable options
|
|
278
|
-
if (!multiple) {
|
|
279
|
-
selectCtrl.writeValue = function writeNgOptionsValue(value) {
|
|
280
|
-
// The options might not be defined yet when ngModel tries to render
|
|
281
|
-
if (!options) return;
|
|
282
|
-
|
|
283
|
-
const selectedOption =
|
|
284
|
-
selectElement.options[selectElement.selectedIndex];
|
|
285
|
-
const option = options.getOptionFromViewValue(value);
|
|
286
|
-
|
|
287
|
-
// Make sure to remove the selected attribute from the previously selected option
|
|
288
|
-
// Otherwise, screen readers might get confused
|
|
289
|
-
if (selectedOption) selectedOption.removeAttribute("selected");
|
|
290
|
-
|
|
291
|
-
if (option) {
|
|
292
|
-
// Don't update the option when it is already selected.
|
|
293
|
-
// For example, the browser will select the first option by default. In that case,
|
|
294
|
-
// most properties are set automatically - except the `selected` attribute, which we
|
|
295
|
-
// set always
|
|
296
|
-
|
|
297
|
-
if (selectElement.value !== option.selectValue) {
|
|
298
|
-
selectCtrl.removeUnknownOption();
|
|
299
|
-
|
|
300
|
-
selectElement.value = option.selectValue;
|
|
301
|
-
option.element.selected = true;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
option.element.setAttribute("selected", "selected");
|
|
305
|
-
} else {
|
|
306
|
-
selectCtrl.selectUnknownOrEmptyOption(value);
|
|
307
|
-
}
|
|
204
|
+
return {
|
|
205
|
+
items: optionItems,
|
|
206
|
+
selectValueMap,
|
|
207
|
+
getOptionFromViewValue(value) {
|
|
208
|
+
return selectValueMap[getTrackByValue(value)];
|
|
209
|
+
},
|
|
210
|
+
getViewValueFromOption(option) {
|
|
211
|
+
// If the viewValue could be an object that may be mutated by the application,
|
|
212
|
+
// we need to make a copy and not return the reference to the value on the option.
|
|
213
|
+
return trackBy
|
|
214
|
+
? structuredClone(option.viewValue)
|
|
215
|
+
: option.viewValue;
|
|
216
|
+
},
|
|
308
217
|
};
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
309
221
|
|
|
310
|
-
|
|
311
|
-
|
|
222
|
+
/**
|
|
223
|
+
*
|
|
224
|
+
* @param {import("../../core/scope/scope.js").Scope} scope
|
|
225
|
+
* @param {HTMLSelectElement} selectElement
|
|
226
|
+
* @param {import("../../core/compile/attributes.js").Attributes} attr
|
|
227
|
+
* @param {*} ctrls
|
|
228
|
+
*/
|
|
229
|
+
function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
|
|
230
|
+
const selectCtrl = ctrls[0];
|
|
231
|
+
const ngModelCtrl = ctrls[1];
|
|
232
|
+
const multiple = attr["multiple"];
|
|
233
|
+
|
|
234
|
+
// The emptyOption allows the application developer to provide their own custom "empty"
|
|
235
|
+
// option when the viewValue does not match any of the option values.
|
|
236
|
+
for (
|
|
237
|
+
let i = 0, children = selectElement.childNodes, ii = children.length;
|
|
238
|
+
i < ii;
|
|
239
|
+
i++
|
|
240
|
+
) {
|
|
241
|
+
if (/** @type {HTMLOptionElement} */ (children[i]).value === "") {
|
|
242
|
+
selectCtrl.hasEmptyOption = true;
|
|
243
|
+
selectCtrl.emptyOption = children[i];
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
312
247
|
|
|
313
|
-
|
|
314
|
-
|
|
248
|
+
// The empty option will be compiled and rendered before we first generate the options
|
|
249
|
+
emptyElement(selectElement);
|
|
250
|
+
|
|
251
|
+
const providedEmptyOption = !!selectCtrl.emptyOption;
|
|
252
|
+
|
|
253
|
+
const unknownOption = optionTemplate.cloneNode(false);
|
|
254
|
+
// TODO double check
|
|
255
|
+
unknownOption.nodeValue = "?";
|
|
256
|
+
|
|
257
|
+
let options;
|
|
258
|
+
const ngOptions = parseOptionsExpression(
|
|
259
|
+
attr["ngOptions"],
|
|
260
|
+
selectElement,
|
|
261
|
+
scope,
|
|
262
|
+
);
|
|
263
|
+
// This stores the newly created options before they are appended to the select.
|
|
264
|
+
// Since the contents are removed from the fragment when it is appended,
|
|
265
|
+
// we only need to create it once.
|
|
266
|
+
const listFragment = document.createDocumentFragment();
|
|
267
|
+
|
|
268
|
+
// Overwrite the implementation. ngOptions doesn't use hashes
|
|
269
|
+
selectCtrl.generateUnknownOptionValue = () => "?";
|
|
270
|
+
|
|
271
|
+
// Update the controller methods for multiple selectable options
|
|
272
|
+
if (!multiple) {
|
|
273
|
+
selectCtrl.writeValue = function writeNgOptionsValue(value) {
|
|
274
|
+
// The options might not be defined yet when ngModel tries to render
|
|
275
|
+
if (!options) return;
|
|
276
|
+
|
|
277
|
+
const selectedOption =
|
|
278
|
+
selectElement.options[selectElement.selectedIndex];
|
|
279
|
+
const option = options.getOptionFromViewValue(value);
|
|
280
|
+
|
|
281
|
+
// Make sure to remove the selected attribute from the previously selected option
|
|
282
|
+
// Otherwise, screen readers might get confused
|
|
283
|
+
if (selectedOption) selectedOption.removeAttribute("selected");
|
|
284
|
+
|
|
285
|
+
if (option) {
|
|
286
|
+
// Don't update the option when it is already selected.
|
|
287
|
+
// For example, the browser will select the first option by default. In that case,
|
|
288
|
+
// most properties are set automatically - except the `selected` attribute, which we
|
|
289
|
+
// set always
|
|
290
|
+
|
|
291
|
+
if (selectElement.value !== option.selectValue) {
|
|
315
292
|
selectCtrl.removeUnknownOption();
|
|
316
|
-
|
|
293
|
+
|
|
294
|
+
selectElement.value = option.selectValue;
|
|
295
|
+
option.element.selected = true;
|
|
317
296
|
}
|
|
318
|
-
return null;
|
|
319
|
-
};
|
|
320
297
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (ngOptions.trackBy) {
|
|
325
|
-
scope.$watch(
|
|
326
|
-
ngOptions.getTrackByValue(ngModelCtrl.$viewValue),
|
|
327
|
-
() => {
|
|
328
|
-
ngModelCtrl.$render();
|
|
329
|
-
},
|
|
330
|
-
);
|
|
298
|
+
option.element.setAttribute("selected", "selected");
|
|
299
|
+
} else {
|
|
300
|
+
selectCtrl.selectUnknownOrEmptyOption(value);
|
|
331
301
|
}
|
|
332
|
-
}
|
|
333
|
-
selectCtrl.writeValue = function writeNgOptionsMultiple(values) {
|
|
334
|
-
// The options might not be defined yet when ngModel tries to render
|
|
335
|
-
if (!options) return;
|
|
336
|
-
|
|
337
|
-
// Only set `<option>.selected` if necessary, in order to prevent some browsers from
|
|
338
|
-
// scrolling to `<option>` elements that are outside the `<select>` element's viewport.
|
|
339
|
-
const selectedOptions =
|
|
340
|
-
(values && values.map(getAndUpdateSelectedOption)) || [];
|
|
341
|
-
|
|
342
|
-
options.items.forEach((option) => {
|
|
343
|
-
if (option.element.selected && !includes(selectedOptions, option)) {
|
|
344
|
-
option.element.selected = false;
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
selectCtrl.readValue = function readNgOptionsMultiple() {
|
|
350
|
-
const selectedValues = selectElement.value || [];
|
|
351
|
-
const selections = [];
|
|
352
|
-
// @ts-ignore
|
|
353
|
-
selectedValues.forEach((value) => {
|
|
354
|
-
const option = options.selectValueMap[value];
|
|
355
|
-
if (option && !option.disabled)
|
|
356
|
-
selections.push(options.getViewValueFromOption(option));
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
return selections;
|
|
360
|
-
};
|
|
302
|
+
};
|
|
361
303
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
// if (ngOptions.trackBy) {
|
|
365
|
-
// scope.$watchCollection(
|
|
366
|
-
// () => {
|
|
367
|
-
// if (Array.isArray(ngModelCtrl.$viewValue)) {
|
|
368
|
-
// return ngModelCtrl.$viewValue.map((value) =>
|
|
369
|
-
// ngOptions.getTrackByValue(value),
|
|
370
|
-
// );
|
|
371
|
-
// }
|
|
372
|
-
// },
|
|
373
|
-
// () => {
|
|
374
|
-
// ngModelCtrl.$render();
|
|
375
|
-
// },
|
|
376
|
-
// );
|
|
377
|
-
// }
|
|
378
|
-
}
|
|
304
|
+
selectCtrl.readValue = function readNgOptionsValue() {
|
|
305
|
+
const selectedOption = options.selectValueMap[selectElement.value];
|
|
379
306
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
selectElement.prepend(selectCtrl.emptyOption);
|
|
385
|
-
linkFn(scope);
|
|
386
|
-
|
|
387
|
-
if (selectCtrl.emptyOption.nodeType === Node.COMMENT_NODE) {
|
|
388
|
-
// This means the empty option has currently no actual DOM node, probably because
|
|
389
|
-
// it has been modified by a transclusion directive.
|
|
390
|
-
selectCtrl.hasEmptyOption = false;
|
|
391
|
-
|
|
392
|
-
// Redefine the registerOption function, which will catch
|
|
393
|
-
// options that are added by ngIf etc. (rendering of the node is async because of
|
|
394
|
-
// lazy transclusion)
|
|
395
|
-
selectCtrl.registerOption = function (optionScope, optionEl) {
|
|
396
|
-
if (optionEl.value === "") {
|
|
397
|
-
selectCtrl.hasEmptyOption = true;
|
|
398
|
-
selectCtrl.emptyOption = optionEl;
|
|
399
|
-
// This ensures the new empty option is selected if previously no option was selected
|
|
400
|
-
ngModelCtrl.$render();
|
|
401
|
-
|
|
402
|
-
optionEl.addEventListener("$destroy", () => {
|
|
403
|
-
const needsRerender = selectCtrl.$isEmptyOptionSelected();
|
|
404
|
-
|
|
405
|
-
selectCtrl.hasEmptyOption = false;
|
|
406
|
-
selectCtrl.emptyOption = undefined;
|
|
407
|
-
|
|
408
|
-
if (needsRerender) ngModelCtrl.$render();
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
};
|
|
307
|
+
if (selectedOption && !selectedOption.disabled) {
|
|
308
|
+
selectCtrl.unselectEmptyOption();
|
|
309
|
+
selectCtrl.removeUnknownOption();
|
|
310
|
+
return options.getViewValueFromOption(selectedOption);
|
|
412
311
|
}
|
|
312
|
+
return null;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// If we are using `track by` then we must watch the tracked value on the model
|
|
316
|
+
// since ngModel only watches for object identity change
|
|
317
|
+
// FIXME: When a user selects an option, this watch will fire needlessly
|
|
318
|
+
if (ngOptions.trackBy) {
|
|
319
|
+
scope.$watch(ngOptions.getTrackByValue(ngModelCtrl.$viewValue), () => {
|
|
320
|
+
ngModelCtrl.$render();
|
|
321
|
+
});
|
|
413
322
|
}
|
|
323
|
+
} else {
|
|
324
|
+
selectCtrl.writeValue = function writeNgOptionsMultiple(values) {
|
|
325
|
+
// The options might not be defined yet when ngModel tries to render
|
|
326
|
+
if (!options) return;
|
|
414
327
|
|
|
415
|
-
|
|
328
|
+
// Only set `<option>.selected` if necessary, in order to prevent some browsers from
|
|
329
|
+
// scrolling to `<option>` elements that are outside the `<select>` element's viewport.
|
|
330
|
+
const selectedOptions =
|
|
331
|
+
(values && values.map(getAndUpdateSelectedOption)) || [];
|
|
416
332
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
updateOptions,
|
|
424
|
-
);
|
|
333
|
+
options.items.forEach((option) => {
|
|
334
|
+
if (option.element.selected && !includes(selectedOptions, option)) {
|
|
335
|
+
option.element.selected = false;
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
};
|
|
425
339
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
);
|
|
435
|
-
parent.appendChild(optionElement);
|
|
436
|
-
updateOptionElement(option, optionElement);
|
|
437
|
-
}
|
|
340
|
+
selectCtrl.readValue = function readNgOptionsMultiple() {
|
|
341
|
+
const selectedValues = selectElement.value || [];
|
|
342
|
+
const selections = [];
|
|
343
|
+
// @ts-ignore
|
|
344
|
+
selectedValues.forEach((value) => {
|
|
345
|
+
const option = options.selectValueMap[value];
|
|
346
|
+
if (option && !option.disabled)
|
|
347
|
+
selections.push(options.getViewValueFromOption(option));
|
|
348
|
+
});
|
|
438
349
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const element = option && option.element;
|
|
350
|
+
return selections;
|
|
351
|
+
};
|
|
442
352
|
|
|
443
|
-
|
|
353
|
+
// If we are using `track by` then we must watch these tracked values on the model
|
|
354
|
+
// since ngModel only watches for object identity change
|
|
355
|
+
// if (ngOptions.trackBy) {
|
|
356
|
+
// scope.$watchCollection(
|
|
357
|
+
// () => {
|
|
358
|
+
// if (Array.isArray(ngModelCtrl.$viewValue)) {
|
|
359
|
+
// return ngModelCtrl.$viewValue.map((value) =>
|
|
360
|
+
// ngOptions.getTrackByValue(value),
|
|
361
|
+
// );
|
|
362
|
+
// }
|
|
363
|
+
// },
|
|
364
|
+
// () => {
|
|
365
|
+
// ngModelCtrl.$render();
|
|
366
|
+
// },
|
|
367
|
+
// );
|
|
368
|
+
// }
|
|
369
|
+
}
|
|
444
370
|
|
|
445
|
-
|
|
446
|
-
|
|
371
|
+
if (providedEmptyOption) {
|
|
372
|
+
// compile the element since there might be bindings in it
|
|
373
|
+
const linkFn = $compile(selectCtrl.emptyOption);
|
|
374
|
+
assertArg(linkFn, "LinkFn required");
|
|
375
|
+
selectElement.prepend(selectCtrl.emptyOption);
|
|
376
|
+
linkFn(scope);
|
|
377
|
+
|
|
378
|
+
if (selectCtrl.emptyOption.nodeType === Node.COMMENT_NODE) {
|
|
379
|
+
// This means the empty option has currently no actual DOM node, probably because
|
|
380
|
+
// it has been modified by a transclusion directive.
|
|
381
|
+
selectCtrl.hasEmptyOption = false;
|
|
382
|
+
|
|
383
|
+
// Redefine the registerOption function, which will catch
|
|
384
|
+
// options that are added by ngIf etc. (rendering of the node is async because of
|
|
385
|
+
// lazy transclusion)
|
|
386
|
+
selectCtrl.registerOption = function (optionScope, optionEl) {
|
|
387
|
+
if (optionEl.value === "") {
|
|
388
|
+
selectCtrl.hasEmptyOption = true;
|
|
389
|
+
selectCtrl.emptyOption = optionEl;
|
|
390
|
+
// This ensures the new empty option is selected if previously no option was selected
|
|
391
|
+
ngModelCtrl.$render();
|
|
447
392
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
element.disabled = option.disabled;
|
|
451
|
-
// Support: IE 11 only, Edge 12-13 only
|
|
452
|
-
// NOTE: The label must be set before the value, otherwise IE 11 & Edge create unresponsive
|
|
453
|
-
// selects in certain circumstances when multiple selects are next to each other and display
|
|
454
|
-
// the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
|
|
455
|
-
// See https://github.com/angular/angular.js/issues/11314 for more info.
|
|
456
|
-
// This is unfortunately untestable with unit / e2e tests
|
|
457
|
-
if (option.label !== element.label) {
|
|
458
|
-
element.label = option.label;
|
|
459
|
-
element.textContent = option.label;
|
|
460
|
-
}
|
|
461
|
-
element.value = option.selectValue;
|
|
462
|
-
}
|
|
393
|
+
optionEl.addEventListener("$destroy", () => {
|
|
394
|
+
const needsRerender = selectCtrl.$isEmptyOptionSelected();
|
|
463
395
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
// must preserve.
|
|
470
|
-
// Instead, iterate over the current option elements and remove them or their optgroup
|
|
471
|
-
// parents
|
|
472
|
-
if (options) {
|
|
473
|
-
for (let i = options.items.length - 1; i >= 0; i--) {
|
|
474
|
-
const option = options.items[i];
|
|
475
|
-
if (isDefined(option.group)) {
|
|
476
|
-
removeElement(option.element.parentNode);
|
|
477
|
-
} else {
|
|
478
|
-
removeElement(option.element);
|
|
479
|
-
}
|
|
396
|
+
selectCtrl.hasEmptyOption = false;
|
|
397
|
+
selectCtrl.emptyOption = undefined;
|
|
398
|
+
|
|
399
|
+
if (needsRerender) ngModelCtrl.$render();
|
|
400
|
+
});
|
|
480
401
|
}
|
|
481
|
-
}
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
482
405
|
|
|
483
|
-
|
|
406
|
+
// We will re-render the option elements if the option values or labels change
|
|
407
|
+
|
|
408
|
+
// let watchables = ngOptions.getWatchables();
|
|
409
|
+
// watchables.forEach((i) => {
|
|
410
|
+
// scope.$watch(i, updateOptions);
|
|
411
|
+
// });
|
|
412
|
+
scope.$watch(
|
|
413
|
+
ngOptions.getWatchables.decoratedNode.body[0].expression.name,
|
|
414
|
+
updateOptions,
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
// ------------------------------------------------------------------ //
|
|
418
|
+
|
|
419
|
+
function addOptionElement(option, parent) {
|
|
420
|
+
/**
|
|
421
|
+
* @type {HTMLOptionElement}
|
|
422
|
+
*/
|
|
423
|
+
const optionElement = /** @type {HTMLOptionElement} */ (
|
|
424
|
+
optionTemplate.cloneNode(false)
|
|
425
|
+
);
|
|
426
|
+
parent.appendChild(optionElement);
|
|
427
|
+
updateOptionElement(option, optionElement);
|
|
428
|
+
}
|
|
484
429
|
|
|
485
|
-
|
|
430
|
+
function getAndUpdateSelectedOption(viewValue) {
|
|
431
|
+
const option = options.getOptionFromViewValue(viewValue);
|
|
432
|
+
const element = option && option.element;
|
|
486
433
|
|
|
487
|
-
|
|
488
|
-
let groupElement;
|
|
434
|
+
if (element && !element.selected) element.selected = true;
|
|
489
435
|
|
|
436
|
+
return option;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function updateOptionElement(option, element) {
|
|
440
|
+
option.element = element;
|
|
441
|
+
element.disabled = option.disabled;
|
|
442
|
+
// Support: IE 11 only, Edge 12-13 only
|
|
443
|
+
// NOTE: The label must be set before the value, otherwise IE 11 & Edge create unresponsive
|
|
444
|
+
// selects in certain circumstances when multiple selects are next to each other and display
|
|
445
|
+
// the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
|
|
446
|
+
// See https://github.com/angular/angular.js/issues/11314 for more info.
|
|
447
|
+
// This is unfortunately untestable with unit / e2e tests
|
|
448
|
+
if (option.label !== element.label) {
|
|
449
|
+
element.label = option.label;
|
|
450
|
+
element.textContent = option.label;
|
|
451
|
+
}
|
|
452
|
+
element.value = option.selectValue;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function updateOptions() {
|
|
456
|
+
const previousValue = options && selectCtrl.readValue();
|
|
457
|
+
|
|
458
|
+
// We must remove all current options, but cannot simply set innerHTML = null
|
|
459
|
+
// since the providedEmptyOption might have an ngIf on it that inserts comments which we
|
|
460
|
+
// must preserve.
|
|
461
|
+
// Instead, iterate over the current option elements and remove them or their optgroup
|
|
462
|
+
// parents
|
|
463
|
+
if (options) {
|
|
464
|
+
for (let i = options.items.length - 1; i >= 0; i--) {
|
|
465
|
+
const option = options.items[i];
|
|
490
466
|
if (isDefined(option.group)) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
467
|
+
removeElement(option.element.parentNode);
|
|
468
|
+
} else {
|
|
469
|
+
removeElement(option.element);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
494
473
|
|
|
495
|
-
|
|
496
|
-
groupElement = optGroupTemplate.cloneNode(false);
|
|
497
|
-
listFragment.appendChild(groupElement);
|
|
474
|
+
options = ngOptions.getOptions();
|
|
498
475
|
|
|
499
|
-
|
|
500
|
-
// "null" is special cased because of Safari
|
|
501
|
-
/** @type {HTMLOptGroupElement} */
|
|
502
|
-
(groupElement).label =
|
|
503
|
-
option.group === null ? "null" : option.group;
|
|
476
|
+
const groupElementMap = {};
|
|
504
477
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}
|
|
478
|
+
options.items.forEach((option) => {
|
|
479
|
+
let groupElement;
|
|
508
480
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}
|
|
514
|
-
});
|
|
481
|
+
if (isDefined(option.group)) {
|
|
482
|
+
// This option is to live in a group
|
|
483
|
+
// See if we have already created this group
|
|
484
|
+
groupElement = groupElementMap[option.group];
|
|
515
485
|
|
|
516
|
-
|
|
486
|
+
if (!groupElement) {
|
|
487
|
+
groupElement = optGroupTemplate.cloneNode(false);
|
|
488
|
+
listFragment.appendChild(groupElement);
|
|
517
489
|
|
|
518
|
-
|
|
490
|
+
// Update the label on the group element
|
|
491
|
+
// "null" is special cased because of Safari
|
|
492
|
+
/** @type {HTMLOptGroupElement} */
|
|
493
|
+
(groupElement).label =
|
|
494
|
+
option.group === null ? "null" : option.group;
|
|
519
495
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
const nextValue = selectCtrl.readValue();
|
|
523
|
-
const isNotPrimitive = ngOptions.trackBy || multiple;
|
|
524
|
-
if (
|
|
525
|
-
isNotPrimitive
|
|
526
|
-
? !equals(previousValue, nextValue)
|
|
527
|
-
: previousValue !== nextValue
|
|
528
|
-
) {
|
|
529
|
-
ngModelCtrl.$setViewValue(nextValue);
|
|
530
|
-
ngModelCtrl.$render();
|
|
496
|
+
// Store it for use later
|
|
497
|
+
groupElementMap[option.group] = groupElement;
|
|
531
498
|
}
|
|
499
|
+
|
|
500
|
+
addOptionElement(option, groupElement);
|
|
501
|
+
} else {
|
|
502
|
+
// This option is not in a group
|
|
503
|
+
addOptionElement(option, listFragment);
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
selectElement.appendChild(listFragment);
|
|
508
|
+
|
|
509
|
+
ngModelCtrl.$render();
|
|
510
|
+
|
|
511
|
+
// Check to see if the value has changed due to the update to the options
|
|
512
|
+
if (!ngModelCtrl.$isEmpty(previousValue)) {
|
|
513
|
+
const nextValue = selectCtrl.readValue();
|
|
514
|
+
const isNotPrimitive = ngOptions.trackBy || multiple;
|
|
515
|
+
if (
|
|
516
|
+
isNotPrimitive
|
|
517
|
+
? !equals(previousValue, nextValue)
|
|
518
|
+
: previousValue !== nextValue
|
|
519
|
+
) {
|
|
520
|
+
ngModelCtrl.$setViewValue(nextValue);
|
|
521
|
+
ngModelCtrl.$render();
|
|
532
522
|
}
|
|
533
523
|
}
|
|
534
524
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
post: ngOptionsPostLink,
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return {
|
|
528
|
+
restrict: "A",
|
|
529
|
+
terminal: true,
|
|
530
|
+
require: ["select", "ngModel"],
|
|
531
|
+
link: {
|
|
532
|
+
pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
|
|
533
|
+
// Deactivate the SelectController.register method to prevent
|
|
534
|
+
// option directives from accidentally registering themselves
|
|
535
|
+
// (and unwanted $destroy handlers etc.)
|
|
536
|
+
ctrls[0].registerOption = () => {};
|
|
548
537
|
},
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
538
|
+
post: ngOptionsPostLink,
|
|
539
|
+
},
|
|
540
|
+
};
|
|
541
|
+
}
|