@angular-wave/angular.ts 0.0.1
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/.eslintignore +1 -0
- package/.eslintrc.cjs +29 -0
- package/.github/workflows/playwright.yml +27 -0
- package/CHANGELOG.md +17974 -0
- package/CODE_OF_CONDUCT.md +3 -0
- package/CONTRIBUTING.md +246 -0
- package/DEVELOPERS.md +488 -0
- package/LICENSE +22 -0
- package/Makefile +31 -0
- package/README.md +115 -0
- package/RELEASE.md +98 -0
- package/SECURITY.md +16 -0
- package/TRIAGING.md +135 -0
- package/css/angular.css +22 -0
- package/dist/angular-ts.cjs.js +36843 -0
- package/dist/angular-ts.esm.js +36841 -0
- package/dist/angular-ts.umd.js +36848 -0
- package/dist/build/angular-animate.js +4272 -0
- package/dist/build/angular-aria.js +426 -0
- package/dist/build/angular-message-format.js +1072 -0
- package/dist/build/angular-messages.js +829 -0
- package/dist/build/angular-mocks.js +3757 -0
- package/dist/build/angular-parse-ext.js +1275 -0
- package/dist/build/angular-resource.js +911 -0
- package/dist/build/angular-route.js +1266 -0
- package/dist/build/angular-sanitize.js +891 -0
- package/dist/build/angular-touch.js +368 -0
- package/dist/build/angular.js +36600 -0
- package/e2e/unit.spec.ts +15 -0
- package/images/android-chrome-192x192.png +0 -0
- package/images/android-chrome-512x512.png +0 -0
- package/images/apple-touch-icon.png +0 -0
- package/images/favicon-16x16.png +0 -0
- package/images/favicon-32x32.png +0 -0
- package/images/favicon.ico +0 -0
- package/images/site.webmanifest +1 -0
- package/index.html +104 -0
- package/package.json +47 -0
- package/playwright.config.ts +78 -0
- package/public/circle.html +1 -0
- package/public/my_child_directive.html +1 -0
- package/public/my_directive.html +1 -0
- package/public/my_other_directive.html +1 -0
- package/public/test.html +1 -0
- package/rollup.config.js +31 -0
- package/src/animations/animateCache.js +55 -0
- package/src/animations/animateChildrenDirective.js +105 -0
- package/src/animations/animateCss.js +1139 -0
- package/src/animations/animateCssDriver.js +291 -0
- package/src/animations/animateJs.js +367 -0
- package/src/animations/animateJsDriver.js +67 -0
- package/src/animations/animateQueue.js +851 -0
- package/src/animations/animation.js +506 -0
- package/src/animations/module.js +779 -0
- package/src/animations/ngAnimateSwap.js +119 -0
- package/src/animations/rafScheduler.js +50 -0
- package/src/animations/shared.js +378 -0
- package/src/constants.js +20 -0
- package/src/core/animate.js +845 -0
- package/src/core/animateCss.js +73 -0
- package/src/core/animateRunner.js +195 -0
- package/src/core/attributes.js +199 -0
- package/src/core/cache.js +45 -0
- package/src/core/compile.js +4727 -0
- package/src/core/controller.js +225 -0
- package/src/core/exceptionHandler.js +63 -0
- package/src/core/filter.js +146 -0
- package/src/core/interpolate.js +442 -0
- package/src/core/interval.js +188 -0
- package/src/core/intervalFactory.js +57 -0
- package/src/core/location.js +1086 -0
- package/src/core/parser/parse.js +2562 -0
- package/src/core/parser/parse.md +13 -0
- package/src/core/q.js +746 -0
- package/src/core/rootScope.js +1596 -0
- package/src/core/sanitizeUri.js +85 -0
- package/src/core/sce.js +1161 -0
- package/src/core/taskTrackerFactory.js +125 -0
- package/src/core/timeout.js +121 -0
- package/src/core/urlUtils.js +187 -0
- package/src/core/utils.js +1349 -0
- package/src/directive/a.js +37 -0
- package/src/directive/attrs.js +283 -0
- package/src/directive/bind.js +51 -0
- package/src/directive/bind.md +142 -0
- package/src/directive/change.js +12 -0
- package/src/directive/change.md +25 -0
- package/src/directive/cloak.js +12 -0
- package/src/directive/cloak.md +24 -0
- package/src/directive/events.js +75 -0
- package/src/directive/events.md +166 -0
- package/src/directive/form.js +725 -0
- package/src/directive/init.js +15 -0
- package/src/directive/init.md +41 -0
- package/src/directive/input.js +1783 -0
- package/src/directive/list.js +46 -0
- package/src/directive/list.md +22 -0
- package/src/directive/ngClass.js +249 -0
- package/src/directive/ngController.js +64 -0
- package/src/directive/ngCsp.js +82 -0
- package/src/directive/ngIf.js +134 -0
- package/src/directive/ngInclude.js +217 -0
- package/src/directive/ngModel.js +1356 -0
- package/src/directive/ngModelOptions.js +509 -0
- package/src/directive/ngOptions.js +670 -0
- package/src/directive/ngRef.js +90 -0
- package/src/directive/ngRepeat.js +650 -0
- package/src/directive/ngShowHide.js +255 -0
- package/src/directive/ngSwitch.js +178 -0
- package/src/directive/ngTransclude.js +98 -0
- package/src/directive/non-bindable.js +11 -0
- package/src/directive/non-bindable.md +17 -0
- package/src/directive/script.js +30 -0
- package/src/directive/select.js +624 -0
- package/src/directive/style.js +25 -0
- package/src/directive/style.md +23 -0
- package/src/directive/validators.js +329 -0
- package/src/exts/aria.js +544 -0
- package/src/exts/messages.js +852 -0
- package/src/filters/filter.js +207 -0
- package/src/filters/filter.md +69 -0
- package/src/filters/filters.js +239 -0
- package/src/filters/json.md +16 -0
- package/src/filters/limit-to.js +43 -0
- package/src/filters/limit-to.md +19 -0
- package/src/filters/order-by.js +183 -0
- package/src/filters/order-by.md +83 -0
- package/src/index.js +13 -0
- package/src/injector.js +1034 -0
- package/src/jqLite.js +1117 -0
- package/src/loader.js +1320 -0
- package/src/public.js +215 -0
- package/src/routeToRegExp.js +41 -0
- package/src/services/anchorScroll.js +135 -0
- package/src/services/browser.js +321 -0
- package/src/services/cacheFactory.js +398 -0
- package/src/services/cookieReader.js +72 -0
- package/src/services/document.js +64 -0
- package/src/services/http.js +1537 -0
- package/src/services/httpBackend.js +206 -0
- package/src/services/log.js +160 -0
- package/src/services/templateRequest.js +139 -0
- package/test/angular.spec.js +2153 -0
- package/test/aria/aria.spec.js +1245 -0
- package/test/binding.spec.js +504 -0
- package/test/build-test.html +14 -0
- package/test/injector.spec.js +2327 -0
- package/test/jasmine/jasmine-5.1.2/boot0.js +65 -0
- package/test/jasmine/jasmine-5.1.2/boot1.js +133 -0
- package/test/jasmine/jasmine-5.1.2/jasmine-html.js +963 -0
- package/test/jasmine/jasmine-5.1.2/jasmine.css +320 -0
- package/test/jasmine/jasmine-5.1.2/jasmine.js +10824 -0
- package/test/jasmine/jasmine-5.1.2/jasmine_favicon.png +0 -0
- package/test/jasmine/jasmine-browser.json +17 -0
- package/test/jasmine/jasmine.json +9 -0
- package/test/jqlite.spec.js +2133 -0
- package/test/loader.spec.js +219 -0
- package/test/messages/messages.spec.js +1146 -0
- package/test/min-err.spec.js +174 -0
- package/test/mock-test.html +13 -0
- package/test/module-test.html +15 -0
- package/test/ng/anomate.spec.js +606 -0
- package/test/ng/cache-factor.spec.js +334 -0
- package/test/ng/compile.spec.js +17956 -0
- package/test/ng/controller-provider.spec.js +227 -0
- package/test/ng/cookie-reader.spec.js +98 -0
- package/test/ng/directive/a.spec.js +192 -0
- package/test/ng/directive/bind.spec.js +334 -0
- package/test/ng/directive/boolean.spec.js +136 -0
- package/test/ng/directive/change.spec.js +71 -0
- package/test/ng/directive/class.spec.js +858 -0
- package/test/ng/directive/click.spec.js +38 -0
- package/test/ng/directive/cloak.spec.js +44 -0
- package/test/ng/directive/constoller.spec.js +194 -0
- package/test/ng/directive/element-style.spec.js +92 -0
- package/test/ng/directive/event.spec.js +282 -0
- package/test/ng/directive/form.spec.js +1518 -0
- package/test/ng/directive/href.spec.js +143 -0
- package/test/ng/directive/if.spec.js +402 -0
- package/test/ng/directive/include.spec.js +828 -0
- package/test/ng/directive/init.spec.js +68 -0
- package/test/ng/directive/input.spec.js +3810 -0
- package/test/ng/directive/list.spec.js +170 -0
- package/test/ng/directive/model-options.spec.js +1008 -0
- package/test/ng/directive/model.spec.js +1905 -0
- package/test/ng/directive/non-bindable.spec.js +55 -0
- package/test/ng/directive/options.spec.js +3583 -0
- package/test/ng/directive/ref.spec.js +575 -0
- package/test/ng/directive/repeat.spec.js +1675 -0
- package/test/ng/directive/script.spec.js +52 -0
- package/test/ng/directive/scrset.spec.js +67 -0
- package/test/ng/directive/select.spec.js +2541 -0
- package/test/ng/directive/show-hide.spec.js +253 -0
- package/test/ng/directive/src.spec.js +157 -0
- package/test/ng/directive/style.spec.js +178 -0
- package/test/ng/directive/switch.spec.js +647 -0
- package/test/ng/directive/validators.spec.js +717 -0
- package/test/ng/document.spec.js +52 -0
- package/test/ng/filter/filter.spec.js +714 -0
- package/test/ng/filter/filters.spec.js +35 -0
- package/test/ng/filter/limit-to.spec.js +251 -0
- package/test/ng/filter/order-by.spec.js +891 -0
- package/test/ng/filter.spec.js +149 -0
- package/test/ng/http-backend.spec.js +398 -0
- package/test/ng/http.spec.js +4071 -0
- package/test/ng/interpolate.spec.js +642 -0
- package/test/ng/interval.spec.js +343 -0
- package/test/ng/location.spec.js +3488 -0
- package/test/ng/on.spec.js +229 -0
- package/test/ng/parse.spec.js +4655 -0
- package/test/ng/prop.spec.js +805 -0
- package/test/ng/q.spec.js +2904 -0
- package/test/ng/root-element.spec.js +16 -0
- package/test/ng/sanitize-uri.spec.js +249 -0
- package/test/ng/sce.spec.js +660 -0
- package/test/ng/scope.spec.js +3442 -0
- package/test/ng/template-request.spec.js +236 -0
- package/test/ng/timeout.spec.js +351 -0
- package/test/ng/url-utils.spec.js +156 -0
- package/test/ng/utils.spec.js +144 -0
- package/test/original-test.html +21 -0
- package/test/public.spec.js +34 -0
- package/test/sanitize/bing-html.spec.js +36 -0
- package/test/server/express.js +158 -0
- package/test/test-utils.js +11 -0
- package/tsconfig.json +17 -0
- package/types/angular.d.ts +138 -0
- package/types/global.d.ts +9 -0
- package/types/index.d.ts +2357 -0
- package/types/jqlite.d.ts +558 -0
- package/vite.config.js +14 -0
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
import { jqLite, jqLiteRemove, startingTag } from "../jqLite";
|
|
2
|
+
import {
|
|
3
|
+
equals,
|
|
4
|
+
forEach,
|
|
5
|
+
hashKey,
|
|
6
|
+
includes,
|
|
7
|
+
isArray,
|
|
8
|
+
isArrayLike,
|
|
9
|
+
isDefined,
|
|
10
|
+
minErr,
|
|
11
|
+
} from "../core/utils";
|
|
12
|
+
|
|
13
|
+
const ngOptionsMinErr = minErr("ngOptions");
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @ngdoc directive
|
|
17
|
+
* @name ngOptions
|
|
18
|
+
* @restrict A
|
|
19
|
+
*
|
|
20
|
+
* @description
|
|
21
|
+
*
|
|
22
|
+
* The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
|
|
23
|
+
* elements for the `<select>` element using the array or object obtained by evaluating the
|
|
24
|
+
* `ngOptions` comprehension expression.
|
|
25
|
+
*
|
|
26
|
+
* In many cases, {@link ng.directive:ngRepeat ngRepeat} can be used on `<option>` elements instead of
|
|
27
|
+
* `ngOptions` to achieve a similar result. However, `ngOptions` provides some benefits:
|
|
28
|
+
* - more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
|
|
29
|
+
* comprehension expression
|
|
30
|
+
* - reduced memory consumption by not creating a new scope for each repeated instance
|
|
31
|
+
* - increased render speed by creating the options in a documentFragment instead of individually
|
|
32
|
+
*
|
|
33
|
+
* When an item in the `<select>` menu is selected, the array element or object property
|
|
34
|
+
* represented by the selected option will be bound to the model identified by the `ngModel`
|
|
35
|
+
* directive.
|
|
36
|
+
*
|
|
37
|
+
* Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
|
|
38
|
+
* be nested into the `<select>` element. This element will then represent the `null` or "not selected"
|
|
39
|
+
* option. See example below for demonstration.
|
|
40
|
+
*
|
|
41
|
+
* ## Complex Models (objects or collections)
|
|
42
|
+
*
|
|
43
|
+
* By default, `ngModel` watches the model by reference, not value. This is important to know when
|
|
44
|
+
* binding the select to a model that is an object or a collection.
|
|
45
|
+
*
|
|
46
|
+
* One issue occurs if you want to preselect an option. For example, if you set
|
|
47
|
+
* the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection,
|
|
48
|
+
* because the objects are not identical. So by default, you should always reference the item in your collection
|
|
49
|
+
* for preselections, e.g.: `$scope.selected = $scope.collection[3]`.
|
|
50
|
+
*
|
|
51
|
+
* Another solution is to use a `track by` clause, because then `ngOptions` will track the identity
|
|
52
|
+
* of the item not by reference, but by the result of the `track by` expression. For example, if your
|
|
53
|
+
* collection items have an id property, you would `track by item.id`.
|
|
54
|
+
*
|
|
55
|
+
* A different issue with objects or collections is that ngModel won't detect if an object property or
|
|
56
|
+
* a collection item changes. For that reason, `ngOptions` additionally watches the model using
|
|
57
|
+
* `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute.
|
|
58
|
+
* This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection
|
|
59
|
+
* has not changed identity, but only a property on the object or an item in the collection changes.
|
|
60
|
+
*
|
|
61
|
+
* Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection
|
|
62
|
+
* if the model is an array). This means that changing a property deeper than the first level inside the
|
|
63
|
+
* object/collection will not trigger a re-rendering.
|
|
64
|
+
*
|
|
65
|
+
* ## `select` **`as`**
|
|
66
|
+
*
|
|
67
|
+
* Using `select` **`as`** will bind the result of the `select` expression to the model, but
|
|
68
|
+
* the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
|
|
69
|
+
* or property name (for object data sources) of the value within the collection. If a **`track by`** expression
|
|
70
|
+
* is used, the result of that expression will be set as the value of the `option` and `select` elements.
|
|
71
|
+
*
|
|
72
|
+
*
|
|
73
|
+
* ### `select` **`as`** and **`track by`**
|
|
74
|
+
*
|
|
75
|
+
* <div class="alert alert-warning">
|
|
76
|
+
* Be careful when using `select` **`as`** and **`track by`** in the same expression.
|
|
77
|
+
* </div>
|
|
78
|
+
*
|
|
79
|
+
* Given this array of items on the $scope:
|
|
80
|
+
*
|
|
81
|
+
* ```js
|
|
82
|
+
* $scope.items = [{
|
|
83
|
+
* id: 1,
|
|
84
|
+
* label: 'aLabel',
|
|
85
|
+
* subItem: { name: 'aSubItem' }
|
|
86
|
+
* }, {
|
|
87
|
+
* id: 2,
|
|
88
|
+
* label: 'bLabel',
|
|
89
|
+
* subItem: { name: 'bSubItem' }
|
|
90
|
+
* }];
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* This will work:
|
|
94
|
+
*
|
|
95
|
+
* ```html
|
|
96
|
+
* <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select>
|
|
97
|
+
* ```
|
|
98
|
+
* ```js
|
|
99
|
+
* $scope.selected = $scope.items[0];
|
|
100
|
+
* ```
|
|
101
|
+
*
|
|
102
|
+
* but this will not work:
|
|
103
|
+
*
|
|
104
|
+
* ```html
|
|
105
|
+
* <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select>
|
|
106
|
+
* ```
|
|
107
|
+
* ```js
|
|
108
|
+
* $scope.selected = $scope.items[0].subItem;
|
|
109
|
+
* ```
|
|
110
|
+
*
|
|
111
|
+
* In both examples, the **`track by`** expression is applied successfully to each `item` in the
|
|
112
|
+
* `items` array. Because the selected option has been set programmatically in the controller, the
|
|
113
|
+
* **`track by`** expression is also applied to the `ngModel` value. In the first example, the
|
|
114
|
+
* `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with
|
|
115
|
+
* no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`**
|
|
116
|
+
* expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value
|
|
117
|
+
* is not matched against any `<option>` and the `<select>` appears as having no selected value.
|
|
118
|
+
*
|
|
119
|
+
*
|
|
120
|
+
* @param {string} ngModel Assignable AngularJS expression to data-bind to.
|
|
121
|
+
* @param {comprehension_expression} ngOptions in one of the following forms:
|
|
122
|
+
*
|
|
123
|
+
* * for array data sources:
|
|
124
|
+
* * `label` **`for`** `value` **`in`** `array`
|
|
125
|
+
* * `select` **`as`** `label` **`for`** `value` **`in`** `array`
|
|
126
|
+
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
|
|
127
|
+
* * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array`
|
|
128
|
+
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
|
|
129
|
+
* * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
|
|
130
|
+
* * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
|
|
131
|
+
* (for including a filter with `track by`)
|
|
132
|
+
* * for object data sources:
|
|
133
|
+
* * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
|
|
134
|
+
* * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
|
|
135
|
+
* * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
|
|
136
|
+
* * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object`
|
|
137
|
+
* * `select` **`as`** `label` **`group by`** `group`
|
|
138
|
+
* **`for` `(`**`key`**`,`** `value`**`) in`** `object`
|
|
139
|
+
* * `select` **`as`** `label` **`disable when`** `disable`
|
|
140
|
+
* **`for` `(`**`key`**`,`** `value`**`) in`** `object`
|
|
141
|
+
*
|
|
142
|
+
* Where:
|
|
143
|
+
*
|
|
144
|
+
* * `array` / `object`: an expression which evaluates to an array / object to iterate over.
|
|
145
|
+
* * `value`: local variable which will refer to each item in the `array` or each property value
|
|
146
|
+
* of `object` during iteration.
|
|
147
|
+
* * `key`: local variable which will refer to a property name in `object` during iteration.
|
|
148
|
+
* * `label`: The result of this expression will be the label for `<option>` element. The
|
|
149
|
+
* `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
|
|
150
|
+
* * `select`: The result of this expression will be bound to the model of the parent `<select>`
|
|
151
|
+
* element. If not specified, `select` expression will default to `value`.
|
|
152
|
+
* * `group`: The result of this expression will be used to group options using the `<optgroup>`
|
|
153
|
+
* DOM element.
|
|
154
|
+
* * `disable`: The result of this expression will be used to disable the rendered `<option>`
|
|
155
|
+
* element. Return `true` to disable.
|
|
156
|
+
* * `trackexpr`: Used when working with an array of objects. The result of this expression will be
|
|
157
|
+
* used to identify the objects in the array. The `trackexpr` will most likely refer to the
|
|
158
|
+
* `value` variable (e.g. `value.propertyName`). With this the selection is preserved
|
|
159
|
+
* even when the options are recreated (e.g. reloaded from the server).
|
|
160
|
+
* @param {string=} name Property name of the form under which the control is published.
|
|
161
|
+
* @param {string=} required The control is considered valid only if value is entered.
|
|
162
|
+
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
|
163
|
+
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
|
164
|
+
* `required` when you want to data-bind to the `required` attribute.
|
|
165
|
+
* @param {string=} ngAttrSize sets the size of the select element dynamically. Uses the
|
|
166
|
+
* {@link guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes ngAttr} directive.
|
|
167
|
+
*
|
|
168
|
+
*/
|
|
169
|
+
|
|
170
|
+
const NG_OPTIONS_REGEXP =
|
|
171
|
+
/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([$\w][$\w]*)|(?:\(\s*([$\w][$\w]*)\s*,\s*([$\w][$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
|
|
172
|
+
// 1: value expression (valueFn)
|
|
173
|
+
// 2: label expression (displayFn)
|
|
174
|
+
// 3: group by expression (groupByFn)
|
|
175
|
+
// 4: disable when expression (disableWhenFn)
|
|
176
|
+
// 5: array item variable name
|
|
177
|
+
// 6: object item key variable name
|
|
178
|
+
// 7: object item value variable name
|
|
179
|
+
// 8: collection expression
|
|
180
|
+
// 9: track by expression
|
|
181
|
+
|
|
182
|
+
export const ngOptionsDirective = [
|
|
183
|
+
"$compile",
|
|
184
|
+
"$document",
|
|
185
|
+
"$parse",
|
|
186
|
+
function ($compile, $document, $parse) {
|
|
187
|
+
function parseOptionsExpression(optionsExp, selectElement, scope) {
|
|
188
|
+
const match = optionsExp.match(NG_OPTIONS_REGEXP);
|
|
189
|
+
if (!match) {
|
|
190
|
+
throw ngOptionsMinErr(
|
|
191
|
+
"iexp",
|
|
192
|
+
"Expected expression in form of " +
|
|
193
|
+
"'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
|
|
194
|
+
" but got '{0}'. Element: {1}",
|
|
195
|
+
optionsExp,
|
|
196
|
+
startingTag(selectElement),
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Extract the parts from the ngOptions expression
|
|
201
|
+
|
|
202
|
+
// The variable name for the value of the item in the collection
|
|
203
|
+
const valueName = match[5] || match[7];
|
|
204
|
+
// The variable name for the key of the item in the collection
|
|
205
|
+
const keyName = match[6];
|
|
206
|
+
|
|
207
|
+
// An expression that generates the viewValue for an option if there is a label expression
|
|
208
|
+
const selectAs = / as /.test(match[0]) && match[1];
|
|
209
|
+
// An expression that is used to track the id of each object in the options collection
|
|
210
|
+
const trackBy = match[9];
|
|
211
|
+
// An expression that generates the viewValue for an option if there is no label expression
|
|
212
|
+
const valueFn = $parse(match[2] ? match[1] : valueName);
|
|
213
|
+
const selectAsFn = selectAs && $parse(selectAs);
|
|
214
|
+
const viewValueFn = selectAsFn || valueFn;
|
|
215
|
+
const trackByFn = trackBy && $parse(trackBy);
|
|
216
|
+
|
|
217
|
+
// Get the value by which we are going to track the option
|
|
218
|
+
// if we have a trackFn then use that (passing scope and locals)
|
|
219
|
+
// otherwise just hash the given viewValue
|
|
220
|
+
const getTrackByValueFn = trackBy
|
|
221
|
+
? function (value, locals) {
|
|
222
|
+
return trackByFn(scope, locals);
|
|
223
|
+
}
|
|
224
|
+
: function getHashOfValue(value) {
|
|
225
|
+
return hashKey(value);
|
|
226
|
+
};
|
|
227
|
+
const getTrackByValue = function (value, key) {
|
|
228
|
+
return getTrackByValueFn(value, getLocals(value, key));
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const displayFn = $parse(match[2] || match[1]);
|
|
232
|
+
const groupByFn = $parse(match[3] || "");
|
|
233
|
+
const disableWhenFn = $parse(match[4] || "");
|
|
234
|
+
const valuesFn = $parse(match[8]);
|
|
235
|
+
|
|
236
|
+
const locals = {};
|
|
237
|
+
let getLocals = keyName
|
|
238
|
+
? function (value, key) {
|
|
239
|
+
locals[keyName] = key;
|
|
240
|
+
locals[valueName] = value;
|
|
241
|
+
return locals;
|
|
242
|
+
}
|
|
243
|
+
: function (value) {
|
|
244
|
+
locals[valueName] = value;
|
|
245
|
+
return locals;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
function Option(selectValue, viewValue, label, group, disabled) {
|
|
249
|
+
this.selectValue = selectValue;
|
|
250
|
+
this.viewValue = viewValue;
|
|
251
|
+
this.label = label;
|
|
252
|
+
this.group = group;
|
|
253
|
+
this.disabled = disabled;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function getOptionValuesKeys(optionValues) {
|
|
257
|
+
let optionValuesKeys;
|
|
258
|
+
|
|
259
|
+
if (!keyName && isArrayLike(optionValues)) {
|
|
260
|
+
optionValuesKeys = optionValues;
|
|
261
|
+
} else {
|
|
262
|
+
// if object, extract keys, in enumeration order, unsorted
|
|
263
|
+
optionValuesKeys = [];
|
|
264
|
+
for (const itemKey in optionValues) {
|
|
265
|
+
if (
|
|
266
|
+
Object.prototype.hasOwnProperty.call(optionValues, itemKey) &&
|
|
267
|
+
itemKey.charAt(0) !== "$"
|
|
268
|
+
) {
|
|
269
|
+
optionValuesKeys.push(itemKey);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return optionValuesKeys;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
trackBy,
|
|
278
|
+
getTrackByValue,
|
|
279
|
+
getWatchables: $parse(valuesFn, (optionValues) => {
|
|
280
|
+
// Create a collection of things that we would like to watch (watchedArray)
|
|
281
|
+
// so that they can all be watched using a single $watchCollection
|
|
282
|
+
// that only runs the handler once if anything changes
|
|
283
|
+
const watchedArray = [];
|
|
284
|
+
optionValues = optionValues || [];
|
|
285
|
+
|
|
286
|
+
const optionValuesKeys = getOptionValuesKeys(optionValues);
|
|
287
|
+
const optionValuesLength = optionValuesKeys.length;
|
|
288
|
+
for (let index = 0; index < optionValuesLength; index++) {
|
|
289
|
+
const key =
|
|
290
|
+
optionValues === optionValuesKeys
|
|
291
|
+
? index
|
|
292
|
+
: optionValuesKeys[index];
|
|
293
|
+
const value = optionValues[key];
|
|
294
|
+
|
|
295
|
+
const locals = getLocals(value, key);
|
|
296
|
+
const selectValue = getTrackByValueFn(value, locals);
|
|
297
|
+
watchedArray.push(selectValue);
|
|
298
|
+
|
|
299
|
+
// Only need to watch the displayFn if there is a specific label expression
|
|
300
|
+
if (match[2] || match[1]) {
|
|
301
|
+
const label = displayFn(scope, locals);
|
|
302
|
+
watchedArray.push(label);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Only need to watch the disableWhenFn if there is a specific disable expression
|
|
306
|
+
if (match[4]) {
|
|
307
|
+
const disableWhen = disableWhenFn(scope, locals);
|
|
308
|
+
watchedArray.push(disableWhen);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return watchedArray;
|
|
312
|
+
}),
|
|
313
|
+
|
|
314
|
+
getOptions() {
|
|
315
|
+
const optionItems = [];
|
|
316
|
+
const selectValueMap = {};
|
|
317
|
+
|
|
318
|
+
// The option values were already computed in the `getWatchables` fn,
|
|
319
|
+
// which must have been called to trigger `getOptions`
|
|
320
|
+
const optionValues = valuesFn(scope) || [];
|
|
321
|
+
const optionValuesKeys = getOptionValuesKeys(optionValues);
|
|
322
|
+
const optionValuesLength = optionValuesKeys.length;
|
|
323
|
+
|
|
324
|
+
for (let index = 0; index < optionValuesLength; index++) {
|
|
325
|
+
const key =
|
|
326
|
+
optionValues === optionValuesKeys
|
|
327
|
+
? index
|
|
328
|
+
: optionValuesKeys[index];
|
|
329
|
+
const value = optionValues[key];
|
|
330
|
+
const locals = getLocals(value, key);
|
|
331
|
+
const viewValue = viewValueFn(scope, locals);
|
|
332
|
+
const selectValue = getTrackByValueFn(viewValue, locals);
|
|
333
|
+
const label = displayFn(scope, locals);
|
|
334
|
+
const group = groupByFn(scope, locals);
|
|
335
|
+
const disabled = disableWhenFn(scope, locals);
|
|
336
|
+
const optionItem = new Option(
|
|
337
|
+
selectValue,
|
|
338
|
+
viewValue,
|
|
339
|
+
label,
|
|
340
|
+
group,
|
|
341
|
+
disabled,
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
optionItems.push(optionItem);
|
|
345
|
+
selectValueMap[selectValue] = optionItem;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
items: optionItems,
|
|
350
|
+
selectValueMap,
|
|
351
|
+
getOptionFromViewValue(value) {
|
|
352
|
+
return selectValueMap[getTrackByValue(value)];
|
|
353
|
+
},
|
|
354
|
+
getViewValueFromOption(option) {
|
|
355
|
+
// If the viewValue could be an object that may be mutated by the application,
|
|
356
|
+
// we need to make a copy and not return the reference to the value on the option.
|
|
357
|
+
return trackBy
|
|
358
|
+
? structuredClone(option.viewValue)
|
|
359
|
+
: option.viewValue;
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Support: IE 9 only
|
|
367
|
+
// We can't just jqLite('<option>') since jqLite is not smart enough
|
|
368
|
+
// to create it in <select> and IE barfs otherwise.
|
|
369
|
+
const optionTemplate = window.document.createElement("option");
|
|
370
|
+
const optGroupTemplate = window.document.createElement("optgroup");
|
|
371
|
+
|
|
372
|
+
function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
|
|
373
|
+
const selectCtrl = ctrls[0];
|
|
374
|
+
const ngModelCtrl = ctrls[1];
|
|
375
|
+
const { multiple } = attr;
|
|
376
|
+
|
|
377
|
+
const children = selectElement.childNodes;
|
|
378
|
+
|
|
379
|
+
// The emptyOption allows the application developer to provide their own custom "empty"
|
|
380
|
+
// option when the viewValue does not match any of the option values.
|
|
381
|
+
for (let i = 0, ii = children.length; i < ii; i++) {
|
|
382
|
+
if (children[i].value === "") {
|
|
383
|
+
selectCtrl.hasEmptyOption = true;
|
|
384
|
+
selectCtrl.emptyOption = children[i];
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// The empty option will be compiled and rendered before we first generate the options
|
|
390
|
+
selectElement.empty();
|
|
391
|
+
|
|
392
|
+
const providedEmptyOption = !!selectCtrl.emptyOption;
|
|
393
|
+
|
|
394
|
+
const unknownOption = jqLite(optionTemplate.cloneNode(false));
|
|
395
|
+
unknownOption.val("?");
|
|
396
|
+
|
|
397
|
+
let options;
|
|
398
|
+
const ngOptions = parseOptionsExpression(
|
|
399
|
+
attr.ngOptions,
|
|
400
|
+
selectElement,
|
|
401
|
+
scope,
|
|
402
|
+
);
|
|
403
|
+
// This stores the newly created options before they are appended to the select.
|
|
404
|
+
// Since the contents are removed from the fragment when it is appended,
|
|
405
|
+
// we only need to create it once.
|
|
406
|
+
const listFragment = $document[0].createDocumentFragment();
|
|
407
|
+
|
|
408
|
+
// Overwrite the implementation. ngOptions doesn't use hashes
|
|
409
|
+
selectCtrl.generateUnknownOptionValue = () => "?";
|
|
410
|
+
|
|
411
|
+
// Update the controller methods for multiple selectable options
|
|
412
|
+
if (!multiple) {
|
|
413
|
+
selectCtrl.writeValue = function writeNgOptionsValue(value) {
|
|
414
|
+
// The options might not be defined yet when ngModel tries to render
|
|
415
|
+
if (!options) return;
|
|
416
|
+
|
|
417
|
+
const selectedOption =
|
|
418
|
+
selectElement[0].options[selectElement[0].selectedIndex];
|
|
419
|
+
const option = options.getOptionFromViewValue(value);
|
|
420
|
+
|
|
421
|
+
// Make sure to remove the selected attribute from the previously selected option
|
|
422
|
+
// Otherwise, screen readers might get confused
|
|
423
|
+
if (selectedOption) selectedOption.removeAttribute("selected");
|
|
424
|
+
|
|
425
|
+
if (option) {
|
|
426
|
+
// Don't update the option when it is already selected.
|
|
427
|
+
// For example, the browser will select the first option by default. In that case,
|
|
428
|
+
// most properties are set automatically - except the `selected` attribute, which we
|
|
429
|
+
// set always
|
|
430
|
+
|
|
431
|
+
if (selectElement[0].value !== option.selectValue) {
|
|
432
|
+
selectCtrl.removeUnknownOption();
|
|
433
|
+
|
|
434
|
+
selectElement[0].value = option.selectValue;
|
|
435
|
+
option.element.selected = true;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
option.element.setAttribute("selected", "selected");
|
|
439
|
+
} else {
|
|
440
|
+
selectCtrl.selectUnknownOrEmptyOption(value);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
selectCtrl.readValue = function readNgOptionsValue() {
|
|
445
|
+
const selectedOption = options.selectValueMap[selectElement.val()];
|
|
446
|
+
|
|
447
|
+
if (selectedOption && !selectedOption.disabled) {
|
|
448
|
+
selectCtrl.unselectEmptyOption();
|
|
449
|
+
selectCtrl.removeUnknownOption();
|
|
450
|
+
return options.getViewValueFromOption(selectedOption);
|
|
451
|
+
}
|
|
452
|
+
return null;
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
// If we are using `track by` then we must watch the tracked value on the model
|
|
456
|
+
// since ngModel only watches for object identity change
|
|
457
|
+
// FIXME: When a user selects an option, this watch will fire needlessly
|
|
458
|
+
if (ngOptions.trackBy) {
|
|
459
|
+
scope.$watch(
|
|
460
|
+
() => ngOptions.getTrackByValue(ngModelCtrl.$viewValue),
|
|
461
|
+
() => {
|
|
462
|
+
ngModelCtrl.$render();
|
|
463
|
+
},
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
} else {
|
|
467
|
+
selectCtrl.writeValue = function writeNgOptionsMultiple(values) {
|
|
468
|
+
// The options might not be defined yet when ngModel tries to render
|
|
469
|
+
if (!options) return;
|
|
470
|
+
|
|
471
|
+
// Only set `<option>.selected` if necessary, in order to prevent some browsers from
|
|
472
|
+
// scrolling to `<option>` elements that are outside the `<select>` element's viewport.
|
|
473
|
+
const selectedOptions =
|
|
474
|
+
(values && values.map(getAndUpdateSelectedOption)) || [];
|
|
475
|
+
|
|
476
|
+
options.items.forEach((option) => {
|
|
477
|
+
if (option.element.selected && !includes(selectedOptions, option)) {
|
|
478
|
+
option.element.selected = false;
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
selectCtrl.readValue = function readNgOptionsMultiple() {
|
|
484
|
+
const selectedValues = selectElement.val() || [];
|
|
485
|
+
const selections = [];
|
|
486
|
+
|
|
487
|
+
forEach(selectedValues, (value) => {
|
|
488
|
+
const option = options.selectValueMap[value];
|
|
489
|
+
if (option && !option.disabled)
|
|
490
|
+
selections.push(options.getViewValueFromOption(option));
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
return selections;
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
// If we are using `track by` then we must watch these tracked values on the model
|
|
497
|
+
// since ngModel only watches for object identity change
|
|
498
|
+
if (ngOptions.trackBy) {
|
|
499
|
+
scope.$watchCollection(
|
|
500
|
+
() => {
|
|
501
|
+
if (isArray(ngModelCtrl.$viewValue)) {
|
|
502
|
+
return ngModelCtrl.$viewValue.map((value) =>
|
|
503
|
+
ngOptions.getTrackByValue(value),
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
() => {
|
|
508
|
+
ngModelCtrl.$render();
|
|
509
|
+
},
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (providedEmptyOption) {
|
|
515
|
+
// compile the element since there might be bindings in it
|
|
516
|
+
$compile(selectCtrl.emptyOption)(scope);
|
|
517
|
+
|
|
518
|
+
selectElement.prepend(selectCtrl.emptyOption);
|
|
519
|
+
|
|
520
|
+
if (selectCtrl.emptyOption[0].nodeType === Node.COMMENT_NODE) {
|
|
521
|
+
// This means the empty option has currently no actual DOM node, probably because
|
|
522
|
+
// it has been modified by a transclusion directive.
|
|
523
|
+
selectCtrl.hasEmptyOption = false;
|
|
524
|
+
|
|
525
|
+
// Redefine the registerOption function, which will catch
|
|
526
|
+
// options that are added by ngIf etc. (rendering of the node is async because of
|
|
527
|
+
// lazy transclusion)
|
|
528
|
+
selectCtrl.registerOption = function (optionScope, optionEl) {
|
|
529
|
+
if (optionEl.val() === "") {
|
|
530
|
+
selectCtrl.hasEmptyOption = true;
|
|
531
|
+
selectCtrl.emptyOption = optionEl;
|
|
532
|
+
// This ensures the new empty option is selected if previously no option was selected
|
|
533
|
+
ngModelCtrl.$render();
|
|
534
|
+
|
|
535
|
+
optionEl.on("$destroy", () => {
|
|
536
|
+
const needsRerender = selectCtrl.$isEmptyOptionSelected();
|
|
537
|
+
|
|
538
|
+
selectCtrl.hasEmptyOption = false;
|
|
539
|
+
selectCtrl.emptyOption = undefined;
|
|
540
|
+
|
|
541
|
+
if (needsRerender) ngModelCtrl.$render();
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// We will re-render the option elements if the option values or labels change
|
|
549
|
+
scope.$watchCollection(ngOptions.getWatchables, updateOptions);
|
|
550
|
+
|
|
551
|
+
// ------------------------------------------------------------------ //
|
|
552
|
+
|
|
553
|
+
function addOptionElement(option, parent) {
|
|
554
|
+
const optionElement = optionTemplate.cloneNode(false);
|
|
555
|
+
parent.appendChild(optionElement);
|
|
556
|
+
updateOptionElement(option, optionElement);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function getAndUpdateSelectedOption(viewValue) {
|
|
560
|
+
const option = options.getOptionFromViewValue(viewValue);
|
|
561
|
+
const element = option && option.element;
|
|
562
|
+
|
|
563
|
+
if (element && !element.selected) element.selected = true;
|
|
564
|
+
|
|
565
|
+
return option;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function updateOptionElement(option, element) {
|
|
569
|
+
option.element = element;
|
|
570
|
+
element.disabled = option.disabled;
|
|
571
|
+
// Support: IE 11 only, Edge 12-13 only
|
|
572
|
+
// NOTE: The label must be set before the value, otherwise IE 11 & Edge create unresponsive
|
|
573
|
+
// selects in certain circumstances when multiple selects are next to each other and display
|
|
574
|
+
// the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
|
|
575
|
+
// See https://github.com/angular/angular.js/issues/11314 for more info.
|
|
576
|
+
// This is unfortunately untestable with unit / e2e tests
|
|
577
|
+
if (option.label !== element.label) {
|
|
578
|
+
element.label = option.label;
|
|
579
|
+
element.textContent = option.label;
|
|
580
|
+
}
|
|
581
|
+
element.value = option.selectValue;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function updateOptions() {
|
|
585
|
+
const previousValue = options && selectCtrl.readValue();
|
|
586
|
+
|
|
587
|
+
// We must remove all current options, but cannot simply set innerHTML = null
|
|
588
|
+
// since the providedEmptyOption might have an ngIf on it that inserts comments which we
|
|
589
|
+
// must preserve.
|
|
590
|
+
// Instead, iterate over the current option elements and remove them or their optgroup
|
|
591
|
+
// parents
|
|
592
|
+
if (options) {
|
|
593
|
+
for (let i = options.items.length - 1; i >= 0; i--) {
|
|
594
|
+
const option = options.items[i];
|
|
595
|
+
if (isDefined(option.group)) {
|
|
596
|
+
jqLiteRemove(option.element.parentNode);
|
|
597
|
+
} else {
|
|
598
|
+
jqLiteRemove(option.element);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
options = ngOptions.getOptions();
|
|
604
|
+
|
|
605
|
+
const groupElementMap = {};
|
|
606
|
+
|
|
607
|
+
options.items.forEach((option) => {
|
|
608
|
+
let groupElement;
|
|
609
|
+
|
|
610
|
+
if (isDefined(option.group)) {
|
|
611
|
+
// This option is to live in a group
|
|
612
|
+
// See if we have already created this group
|
|
613
|
+
groupElement = groupElementMap[option.group];
|
|
614
|
+
|
|
615
|
+
if (!groupElement) {
|
|
616
|
+
groupElement = optGroupTemplate.cloneNode(false);
|
|
617
|
+
listFragment.appendChild(groupElement);
|
|
618
|
+
|
|
619
|
+
// Update the label on the group element
|
|
620
|
+
// "null" is special cased because of Safari
|
|
621
|
+
groupElement.label =
|
|
622
|
+
option.group === null ? "null" : option.group;
|
|
623
|
+
|
|
624
|
+
// Store it for use later
|
|
625
|
+
groupElementMap[option.group] = groupElement;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
addOptionElement(option, groupElement);
|
|
629
|
+
} else {
|
|
630
|
+
// This option is not in a group
|
|
631
|
+
addOptionElement(option, listFragment);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
selectElement[0].appendChild(listFragment);
|
|
636
|
+
|
|
637
|
+
ngModelCtrl.$render();
|
|
638
|
+
|
|
639
|
+
// Check to see if the value has changed due to the update to the options
|
|
640
|
+
if (!ngModelCtrl.$isEmpty(previousValue)) {
|
|
641
|
+
const nextValue = selectCtrl.readValue();
|
|
642
|
+
const isNotPrimitive = ngOptions.trackBy || multiple;
|
|
643
|
+
if (
|
|
644
|
+
isNotPrimitive
|
|
645
|
+
? !equals(previousValue, nextValue)
|
|
646
|
+
: previousValue !== nextValue
|
|
647
|
+
) {
|
|
648
|
+
ngModelCtrl.$setViewValue(nextValue);
|
|
649
|
+
ngModelCtrl.$render();
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return {
|
|
656
|
+
restrict: "A",
|
|
657
|
+
terminal: true,
|
|
658
|
+
require: ["select", "ngModel"],
|
|
659
|
+
link: {
|
|
660
|
+
pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
|
|
661
|
+
// Deactivate the SelectController.register method to prevent
|
|
662
|
+
// option directives from accidentally registering themselves
|
|
663
|
+
// (and unwanted $destroy handlers etc.)
|
|
664
|
+
ctrls[0].registerOption = () => {};
|
|
665
|
+
},
|
|
666
|
+
post: ngOptionsPostLink,
|
|
667
|
+
},
|
|
668
|
+
};
|
|
669
|
+
},
|
|
670
|
+
];
|