@angular-wave/angular.ts 0.0.11 → 0.0.13
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/dist/angular-ts.esm.js +1 -1
- package/dist/angular-ts.umd.js +1 -1
- package/package.json +4 -1
- package/src/exts/messages.md +30 -30
- package/src/index.js +1 -0
- package/src/public.js +2 -0
- package/src/router/adapter/directives/stateDirectives.js +695 -0
- package/src/router/adapter/directives/viewDirective.js +514 -0
- package/src/router/adapter/injectables.js +314 -0
- package/src/router/adapter/interface.js +1 -0
- package/src/router/adapter/locationServices.js +84 -0
- package/src/router/adapter/services.js +126 -0
- package/src/router/adapter/stateFilters.js +43 -0
- package/src/router/adapter/stateProvider.js +137 -0
- package/src/router/adapter/statebuilders/onEnterExitRetain.js +30 -0
- package/src/router/adapter/statebuilders/views.js +146 -0
- package/src/router/adapter/templateFactory.js +218 -0
- package/src/router/adapter/viewScroll.js +31 -0
- package/src/router/core/common/common.js +496 -0
- package/src/router/core/common/coreservices.js +15 -0
- package/src/router/core/common/glob.js +75 -0
- package/src/router/core/common/hof.js +194 -0
- package/src/router/core/common/predicates.js +44 -0
- package/src/router/core/common/queue.js +41 -0
- package/src/router/core/common/safeConsole.js +38 -0
- package/src/router/core/common/strings.js +141 -0
- package/src/router/core/common/trace.js +232 -0
- package/src/router/core/globals.js +29 -0
- package/src/router/core/hooks/coreResolvables.js +33 -0
- package/src/router/core/hooks/ignoredTransition.js +25 -0
- package/src/router/core/hooks/invalidTransition.js +14 -0
- package/src/router/core/hooks/lazyLoad.js +102 -0
- package/src/router/core/hooks/onEnterExitRetain.js +55 -0
- package/src/router/core/hooks/redirectTo.js +36 -0
- package/src/router/core/hooks/resolve.js +57 -0
- package/src/router/core/hooks/updateGlobals.js +30 -0
- package/src/router/core/hooks/url.js +25 -0
- package/src/router/core/hooks/views.js +39 -0
- package/src/router/core/interface.js +3 -0
- package/src/router/core/params/README.md +8 -0
- package/src/router/core/params/param.js +232 -0
- package/src/router/core/params/paramType.js +139 -0
- package/src/router/core/params/paramTypes.js +163 -0
- package/src/router/core/params/stateParams.js +35 -0
- package/src/router/core/path/pathNode.js +77 -0
- package/src/router/core/path/pathUtils.js +199 -0
- package/src/router/core/resolve/interface.js +10 -0
- package/src/router/core/resolve/resolvable.js +124 -0
- package/src/router/core/resolve/resolveContext.js +211 -0
- package/src/router/core/router.js +203 -0
- package/src/router/core/state/README.md +21 -0
- package/src/router/core/state/stateBuilder.js +332 -0
- package/src/router/core/state/stateMatcher.js +65 -0
- package/src/router/core/state/stateObject.js +117 -0
- package/src/router/core/state/stateQueueManager.js +89 -0
- package/src/router/core/state/stateRegistry.js +175 -0
- package/src/router/core/state/stateService.js +592 -0
- package/src/router/core/state/targetState.js +159 -0
- package/src/router/core/transition/hookBuilder.js +127 -0
- package/src/router/core/transition/hookRegistry.js +175 -0
- package/src/router/core/transition/interface.js +14 -0
- package/src/router/core/transition/rejectFactory.js +122 -0
- package/src/router/core/transition/transition.js +739 -0
- package/src/router/core/transition/transitionEventType.js +27 -0
- package/src/router/core/transition/transitionHook.js +199 -0
- package/src/router/core/transition/transitionService.js +311 -0
- package/src/router/core/url/interface.js +1 -0
- package/src/router/core/url/urlConfig.js +165 -0
- package/src/router/core/url/urlMatcher.js +548 -0
- package/src/router/core/url/urlMatcherFactory.js +123 -0
- package/src/router/core/url/urlRouter.js +115 -0
- package/src/router/core/url/urlRule.js +202 -0
- package/src/router/core/url/urlRules.js +348 -0
- package/src/router/core/url/urlService.js +268 -0
- package/src/router/core/view/interface.js +1 -0
- package/src/router/core/view/view.js +312 -0
- package/src/router/router.js +58 -0
- package/test/module-test.html +6 -2
- package/test/module-test.js +0 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { extend, isString } from "../../../core/utils";
|
|
2
|
+
import { is, pattern } from "../common/hof";
|
|
3
|
+
import { UrlRules } from "./urlRules";
|
|
4
|
+
import { UrlConfig } from "./urlConfig";
|
|
5
|
+
import { TargetState } from "../state/targetState";
|
|
6
|
+
/**
|
|
7
|
+
* API for URL management
|
|
8
|
+
*/
|
|
9
|
+
export class UrlService {
|
|
10
|
+
/** @internal */
|
|
11
|
+
constructor(/** @internal */ router) {
|
|
12
|
+
this.router = router;
|
|
13
|
+
/** @internal */ this.interceptDeferred = false;
|
|
14
|
+
/**
|
|
15
|
+
* The nested [[UrlRules]] API for managing URL rules and rewrites
|
|
16
|
+
*
|
|
17
|
+
* See: [[UrlRules]] for details
|
|
18
|
+
*/
|
|
19
|
+
this.rules = new UrlRules(this.router);
|
|
20
|
+
/**
|
|
21
|
+
* The nested [[UrlConfig]] API to configure the URL and retrieve URL information
|
|
22
|
+
*
|
|
23
|
+
* See: [[UrlConfig]] for details
|
|
24
|
+
*/
|
|
25
|
+
this.config = new UrlConfig(this.router);
|
|
26
|
+
// Delegate these calls to the current LocationServices implementation
|
|
27
|
+
/**
|
|
28
|
+
* Gets the current url, or updates the url
|
|
29
|
+
*
|
|
30
|
+
* ### Getting the current URL
|
|
31
|
+
*
|
|
32
|
+
* When no arguments are passed, returns the current URL.
|
|
33
|
+
* The URL is normalized using the internal [[path]]/[[search]]/[[hash]] values.
|
|
34
|
+
*
|
|
35
|
+
* For example, the URL may be stored in the hash ([[HashLocationServices]]) or
|
|
36
|
+
* have a base HREF prepended ([[PushStateLocationServices]]).
|
|
37
|
+
*
|
|
38
|
+
* The raw URL in the browser might be:
|
|
39
|
+
*
|
|
40
|
+
* ```
|
|
41
|
+
* http://mysite.com/somepath/index.html#/internal/path/123?param1=foo#anchor
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* or
|
|
45
|
+
*
|
|
46
|
+
* ```
|
|
47
|
+
* http://mysite.com/basepath/internal/path/123?param1=foo#anchor
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* then this method returns:
|
|
51
|
+
*
|
|
52
|
+
* ```
|
|
53
|
+
* /internal/path/123?param1=foo#anchor
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
*
|
|
57
|
+
* #### Example:
|
|
58
|
+
* ```js
|
|
59
|
+
* locationServices.url(); // "/some/path?query=value#anchor"
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* ### Updating the URL
|
|
63
|
+
*
|
|
64
|
+
* When `newurl` arguments is provided, changes the URL to reflect `newurl`
|
|
65
|
+
*
|
|
66
|
+
* #### Example:
|
|
67
|
+
* ```js
|
|
68
|
+
* locationServices.url("/some/path?query=value#anchor", true);
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @param newurl The new value for the URL.
|
|
72
|
+
* This url should reflect only the new internal [[path]], [[search]], and [[hash]] values.
|
|
73
|
+
* It should not include the protocol, site, port, or base path of an absolute HREF.
|
|
74
|
+
* @param replace When true, replaces the current history entry (instead of appending it) with this new url
|
|
75
|
+
* @param state The history's state object, i.e., pushState (if the LocationServices implementation supports it)
|
|
76
|
+
*
|
|
77
|
+
* @return the url (after potentially being processed)
|
|
78
|
+
*/
|
|
79
|
+
this.url = (newurl, replace, state) =>
|
|
80
|
+
this.router.locationService.url(newurl, replace, state);
|
|
81
|
+
/**
|
|
82
|
+
* Gets the path part of the current url
|
|
83
|
+
*
|
|
84
|
+
* If the current URL is `/some/path?query=value#anchor`, this returns `/some/path`
|
|
85
|
+
*
|
|
86
|
+
* @return the path portion of the url
|
|
87
|
+
*/
|
|
88
|
+
this.path = () => this.router.locationService.path();
|
|
89
|
+
/**
|
|
90
|
+
* Gets the search part of the current url as an object
|
|
91
|
+
*
|
|
92
|
+
* If the current URL is `/some/path?query=value#anchor`, this returns `{ query: 'value' }`
|
|
93
|
+
*
|
|
94
|
+
* @return the search (query) portion of the url, as an object
|
|
95
|
+
*/
|
|
96
|
+
this.search = () => this.router.locationService.search();
|
|
97
|
+
/**
|
|
98
|
+
* Gets the hash part of the current url
|
|
99
|
+
*
|
|
100
|
+
* If the current URL is `/some/path?query=value#anchor`, this returns `anchor`
|
|
101
|
+
*
|
|
102
|
+
* @return the hash (anchor) portion of the url
|
|
103
|
+
*/
|
|
104
|
+
this.hash = () => this.router.locationService.hash();
|
|
105
|
+
/**
|
|
106
|
+
* @internal
|
|
107
|
+
*
|
|
108
|
+
* Registers a low level url change handler
|
|
109
|
+
*
|
|
110
|
+
* Note: Because this is a low level handler, it's not recommended for general use.
|
|
111
|
+
*
|
|
112
|
+
* #### Example:
|
|
113
|
+
* ```js
|
|
114
|
+
* let deregisterFn = locationServices.onChange((evt) => console.log("url change", evt));
|
|
115
|
+
* ```
|
|
116
|
+
*
|
|
117
|
+
* @param callback a function that will be called when the url is changing
|
|
118
|
+
* @return a function that de-registers the callback
|
|
119
|
+
*/
|
|
120
|
+
this.onChange = (callback) =>
|
|
121
|
+
this.router.locationService.onChange(callback);
|
|
122
|
+
}
|
|
123
|
+
/** @internal */
|
|
124
|
+
dispose() {
|
|
125
|
+
this.listen(false);
|
|
126
|
+
this.rules.dispose();
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Gets the current URL parts
|
|
130
|
+
*
|
|
131
|
+
* This method returns the different parts of the current URL (the [[path]], [[search]], and [[hash]]) as a [[UrlParts]] object.
|
|
132
|
+
*/
|
|
133
|
+
parts() {
|
|
134
|
+
return { path: this.path(), search: this.search(), hash: this.hash() };
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Activates the best rule for the current URL
|
|
138
|
+
*
|
|
139
|
+
* Checks the current URL for a matching [[UrlRule]], then invokes that rule's handler.
|
|
140
|
+
* This method is called internally any time the URL has changed.
|
|
141
|
+
*
|
|
142
|
+
* This effectively activates the state (or redirect, etc) which matches the current URL.
|
|
143
|
+
*
|
|
144
|
+
* #### Example:
|
|
145
|
+
* ```js
|
|
146
|
+
* urlService.deferIntercept();
|
|
147
|
+
*
|
|
148
|
+
* fetch('/states.json').then(resp => resp.json()).then(data => {
|
|
149
|
+
* data.forEach(state => $stateRegistry.register(state));
|
|
150
|
+
* urlService.listen();
|
|
151
|
+
* // Find the matching URL and invoke the handler.
|
|
152
|
+
* urlService.sync();
|
|
153
|
+
* });
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
sync(evt) {
|
|
157
|
+
if (evt && evt.defaultPrevented) return;
|
|
158
|
+
const { urlService, stateService } = this.router;
|
|
159
|
+
const url = {
|
|
160
|
+
path: urlService.path(),
|
|
161
|
+
search: urlService.search(),
|
|
162
|
+
hash: urlService.hash(),
|
|
163
|
+
};
|
|
164
|
+
const best = this.match(url);
|
|
165
|
+
const applyResult = pattern([
|
|
166
|
+
[isString, (newurl) => urlService.url(newurl, true)],
|
|
167
|
+
[
|
|
168
|
+
TargetState.isDef,
|
|
169
|
+
(def) => stateService.go(def.state, def.params, def.options),
|
|
170
|
+
],
|
|
171
|
+
[
|
|
172
|
+
is(TargetState),
|
|
173
|
+
(target) =>
|
|
174
|
+
stateService.go(target.state(), target.params(), target.options()),
|
|
175
|
+
],
|
|
176
|
+
]);
|
|
177
|
+
applyResult(best && best.rule.handler(best.match, url, this.router));
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Starts or stops listening for URL changes
|
|
181
|
+
*
|
|
182
|
+
* Call this sometime after calling [[deferIntercept]] to start monitoring the url.
|
|
183
|
+
* This causes UI-Router to start listening for changes to the URL, if it wasn't already listening.
|
|
184
|
+
*
|
|
185
|
+
* If called with `false`, UI-Router will stop listening (call listen(true) to start listening again).
|
|
186
|
+
*
|
|
187
|
+
* #### Example:
|
|
188
|
+
* ```js
|
|
189
|
+
* urlService.deferIntercept();
|
|
190
|
+
*
|
|
191
|
+
* fetch('/states.json').then(resp => resp.json()).then(data => {
|
|
192
|
+
* data.forEach(state => $stateRegistry.register(state));
|
|
193
|
+
* // Start responding to URL changes
|
|
194
|
+
* urlService.listen();
|
|
195
|
+
* urlService.sync();
|
|
196
|
+
* });
|
|
197
|
+
* ```
|
|
198
|
+
*
|
|
199
|
+
* @param enabled `true` or `false` to start or stop listening to URL changes
|
|
200
|
+
*/
|
|
201
|
+
listen(enabled) {
|
|
202
|
+
if (enabled === false) {
|
|
203
|
+
this._stopListeningFn && this._stopListeningFn();
|
|
204
|
+
delete this._stopListeningFn;
|
|
205
|
+
} else {
|
|
206
|
+
return (this._stopListeningFn =
|
|
207
|
+
this._stopListeningFn ||
|
|
208
|
+
this.router.urlService.onChange((evt) => this.sync(evt)));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Disables monitoring of the URL.
|
|
213
|
+
*
|
|
214
|
+
* Call this method before UI-Router has bootstrapped.
|
|
215
|
+
* It will stop UI-Router from performing the initial url sync.
|
|
216
|
+
*
|
|
217
|
+
* This can be useful to perform some asynchronous initialization before the router starts.
|
|
218
|
+
* Once the initialization is complete, call [[listen]] to tell UI-Router to start watching and synchronizing the URL.
|
|
219
|
+
*
|
|
220
|
+
* #### Example:
|
|
221
|
+
* ```js
|
|
222
|
+
* // Prevent UI-Router from automatically intercepting URL changes when it starts;
|
|
223
|
+
* urlService.deferIntercept();
|
|
224
|
+
*
|
|
225
|
+
* fetch('/states.json').then(resp => resp.json()).then(data => {
|
|
226
|
+
* data.forEach(state => $stateRegistry.register(state));
|
|
227
|
+
* urlService.listen();
|
|
228
|
+
* urlService.sync();
|
|
229
|
+
* });
|
|
230
|
+
* ```
|
|
231
|
+
*
|
|
232
|
+
* @param defer Indicates whether to defer location change interception.
|
|
233
|
+
* Passing no parameter is equivalent to `true`.
|
|
234
|
+
*/
|
|
235
|
+
deferIntercept(defer) {
|
|
236
|
+
if (defer === undefined) defer = true;
|
|
237
|
+
this.interceptDeferred = defer;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Matches a URL
|
|
241
|
+
*
|
|
242
|
+
* Given a URL (as a [[UrlParts]] object), check all rules and determine the best matching rule.
|
|
243
|
+
* Return the result as a [[MatchResult]].
|
|
244
|
+
*/
|
|
245
|
+
match(url) {
|
|
246
|
+
url = extend({ path: "", search: {}, hash: "" }, url);
|
|
247
|
+
const rules = this.rules.rules();
|
|
248
|
+
// Checks a single rule. Returns { rule: rule, match: match, weight: weight } if it matched, or undefined
|
|
249
|
+
const checkRule = (rule) => {
|
|
250
|
+
const match = rule.match(url, this.router);
|
|
251
|
+
return match && { match, rule, weight: rule.matchPriority(match) };
|
|
252
|
+
};
|
|
253
|
+
// The rules are pre-sorted.
|
|
254
|
+
// - Find the first matching rule.
|
|
255
|
+
// - Find any other matching rule that sorted *exactly the same*, according to `.sort()`.
|
|
256
|
+
// - Choose the rule with the highest match weight.
|
|
257
|
+
let best;
|
|
258
|
+
for (let i = 0; i < rules.length; i++) {
|
|
259
|
+
// Stop when there is a 'best' rule and the next rule sorts differently than it.
|
|
260
|
+
if (best && best.rule._group !== rules[i]._group) break;
|
|
261
|
+
const current = checkRule(rules[i]);
|
|
262
|
+
// Pick the best MatchResult
|
|
263
|
+
best =
|
|
264
|
+
!best || (current && current.weight > best.weight) ? current : best;
|
|
265
|
+
}
|
|
266
|
+
return best;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import {
|
|
2
|
+
equals,
|
|
3
|
+
applyPairs,
|
|
4
|
+
removeFrom,
|
|
5
|
+
inArray,
|
|
6
|
+
find,
|
|
7
|
+
} from "../common/common";
|
|
8
|
+
import { curry, prop } from "../common/hof";
|
|
9
|
+
import { isString, isArray } from "../common/predicates";
|
|
10
|
+
import { trace } from "../common/trace";
|
|
11
|
+
/**
|
|
12
|
+
* The View service
|
|
13
|
+
*
|
|
14
|
+
* This service pairs existing `ui-view` components (which live in the DOM)
|
|
15
|
+
* with view configs (from the state declaration objects: [[StateDeclaration.views]]).
|
|
16
|
+
*
|
|
17
|
+
* - After a successful Transition, the views from the newly entered states are activated via [[activateViewConfig]].
|
|
18
|
+
* The views from exited states are deactivated via [[deactivateViewConfig]].
|
|
19
|
+
* (See: the [[registerActivateViews]] Transition Hook)
|
|
20
|
+
*
|
|
21
|
+
* - As `ui-view` components pop in and out of existence, they register themselves using [[registerUIView]].
|
|
22
|
+
*
|
|
23
|
+
* - When the [[sync]] function is called, the registered `ui-view`(s) ([[ActiveUIView]])
|
|
24
|
+
* are configured with the matching [[ViewConfig]](s)
|
|
25
|
+
*
|
|
26
|
+
*/
|
|
27
|
+
export class ViewService {
|
|
28
|
+
/**
|
|
29
|
+
* Normalizes a view's name from a state.views configuration block.
|
|
30
|
+
*
|
|
31
|
+
* This should be used by a framework implementation to calculate the values for
|
|
32
|
+
* [[_ViewDeclaration.$uiViewName]] and [[_ViewDeclaration.$uiViewContextAnchor]].
|
|
33
|
+
*
|
|
34
|
+
* @param context the context object (state declaration) that the view belongs to
|
|
35
|
+
* @param rawViewName the name of the view, as declared in the [[StateDeclaration.views]]
|
|
36
|
+
*
|
|
37
|
+
* @returns the normalized uiViewName and uiViewContextAnchor that the view targets
|
|
38
|
+
*/
|
|
39
|
+
static normalizeUIViewTarget(context, rawViewName = "") {
|
|
40
|
+
// TODO: Validate incoming view name with a regexp to allow:
|
|
41
|
+
// ex: "view.name@foo.bar" , "^.^.view.name" , "view.name@^.^" , "" ,
|
|
42
|
+
// "@" , "$default@^" , "!$default.$default" , "!foo.bar"
|
|
43
|
+
const viewAtContext = rawViewName.split("@");
|
|
44
|
+
let uiViewName = viewAtContext[0] || "$default"; // default to unnamed view
|
|
45
|
+
let uiViewContextAnchor = isString(viewAtContext[1])
|
|
46
|
+
? viewAtContext[1]
|
|
47
|
+
: "^"; // default to parent context
|
|
48
|
+
// Handle relative view-name sugar syntax.
|
|
49
|
+
// Matches rawViewName "^.^.^.foo.bar" into array: ["^.^.^.foo.bar", "^.^.^", "foo.bar"],
|
|
50
|
+
const relativeViewNameSugar = /^(\^(?:\.\^)*)\.(.*$)/.exec(uiViewName);
|
|
51
|
+
if (relativeViewNameSugar) {
|
|
52
|
+
// Clobbers existing contextAnchor (rawViewName validation will fix this)
|
|
53
|
+
uiViewContextAnchor = relativeViewNameSugar[1]; // set anchor to "^.^.^"
|
|
54
|
+
uiViewName = relativeViewNameSugar[2]; // set view-name to "foo.bar"
|
|
55
|
+
}
|
|
56
|
+
if (uiViewName.charAt(0) === "!") {
|
|
57
|
+
uiViewName = uiViewName.substr(1);
|
|
58
|
+
uiViewContextAnchor = ""; // target absolutely from root
|
|
59
|
+
}
|
|
60
|
+
// handle parent relative targeting "^.^.^"
|
|
61
|
+
const relativeMatch = /^(\^(?:\.\^)*)$/;
|
|
62
|
+
if (relativeMatch.exec(uiViewContextAnchor)) {
|
|
63
|
+
const anchorState = uiViewContextAnchor
|
|
64
|
+
.split(".")
|
|
65
|
+
.reduce((anchor, x) => anchor.parent, context);
|
|
66
|
+
uiViewContextAnchor = anchorState.name;
|
|
67
|
+
} else if (uiViewContextAnchor === ".") {
|
|
68
|
+
uiViewContextAnchor = context.name;
|
|
69
|
+
}
|
|
70
|
+
return { uiViewName, uiViewContextAnchor };
|
|
71
|
+
}
|
|
72
|
+
/** @internal */
|
|
73
|
+
constructor(/** @internal */ router) {
|
|
74
|
+
this.router = router;
|
|
75
|
+
/** @internal */ this._uiViews = [];
|
|
76
|
+
/** @internal */ this._viewConfigs = [];
|
|
77
|
+
/** @internal */ this._viewConfigFactories = {};
|
|
78
|
+
/** @internal */ this._listeners = [];
|
|
79
|
+
/** @internal */
|
|
80
|
+
this._pluginapi = {
|
|
81
|
+
_rootViewContext: this._rootViewContext.bind(this),
|
|
82
|
+
_viewConfigFactory: this._viewConfigFactory.bind(this),
|
|
83
|
+
_registeredUIView: (id) =>
|
|
84
|
+
find(this._uiViews, (view) => `${this.router.$id}.${view.id}` === id),
|
|
85
|
+
_registeredUIViews: () => this._uiViews,
|
|
86
|
+
_activeViewConfigs: () => this._viewConfigs,
|
|
87
|
+
_onSync: (listener) => {
|
|
88
|
+
this._listeners.push(listener);
|
|
89
|
+
return () => removeFrom(this._listeners, listener);
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** @internal */
|
|
94
|
+
_rootViewContext(context) {
|
|
95
|
+
return (this._rootContext = context || this._rootContext);
|
|
96
|
+
}
|
|
97
|
+
/** @internal */
|
|
98
|
+
_viewConfigFactory(viewType, factory) {
|
|
99
|
+
this._viewConfigFactories[viewType] = factory;
|
|
100
|
+
}
|
|
101
|
+
createViewConfig(path, decl) {
|
|
102
|
+
const cfgFactory = this._viewConfigFactories[decl.$type];
|
|
103
|
+
if (!cfgFactory)
|
|
104
|
+
throw new Error(
|
|
105
|
+
"ViewService: No view config factory registered for type " + decl.$type,
|
|
106
|
+
);
|
|
107
|
+
const cfgs = cfgFactory(path, decl);
|
|
108
|
+
return isArray(cfgs) ? cfgs : [cfgs];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Deactivates a ViewConfig.
|
|
112
|
+
*
|
|
113
|
+
* This function deactivates a `ViewConfig`.
|
|
114
|
+
* After calling [[sync]], it will un-pair from any `ui-view` with which it is currently paired.
|
|
115
|
+
*
|
|
116
|
+
* @param viewConfig The ViewConfig view to deregister.
|
|
117
|
+
*/
|
|
118
|
+
deactivateViewConfig(viewConfig) {
|
|
119
|
+
trace.traceViewServiceEvent("<- Removing", viewConfig);
|
|
120
|
+
removeFrom(this._viewConfigs, viewConfig);
|
|
121
|
+
}
|
|
122
|
+
activateViewConfig(viewConfig) {
|
|
123
|
+
trace.traceViewServiceEvent("-> Registering", viewConfig);
|
|
124
|
+
this._viewConfigs.push(viewConfig);
|
|
125
|
+
}
|
|
126
|
+
sync() {
|
|
127
|
+
const uiViewsByFqn = this._uiViews
|
|
128
|
+
.map((uiv) => [uiv.fqn, uiv])
|
|
129
|
+
.reduce(applyPairs, {});
|
|
130
|
+
// Return a weighted depth value for a uiView.
|
|
131
|
+
// The depth is the nesting depth of ui-views (based on FQN; times 10,000)
|
|
132
|
+
// plus the depth of the state that is populating the uiView
|
|
133
|
+
function uiViewDepth(uiView) {
|
|
134
|
+
const stateDepth = (context) =>
|
|
135
|
+
context && context.parent ? stateDepth(context.parent) + 1 : 1;
|
|
136
|
+
return (
|
|
137
|
+
uiView.fqn.split(".").length * 10000 +
|
|
138
|
+
stateDepth(uiView.creationContext)
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
// Return the ViewConfig's context's depth in the context tree.
|
|
142
|
+
function viewConfigDepth(config) {
|
|
143
|
+
let context = config.viewDecl.$context,
|
|
144
|
+
count = 0;
|
|
145
|
+
while (++count && context.parent) context = context.parent;
|
|
146
|
+
return count;
|
|
147
|
+
}
|
|
148
|
+
// Given a depth function, returns a compare function which can return either ascending or descending order
|
|
149
|
+
const depthCompare = curry(
|
|
150
|
+
(depthFn, posNeg, left, right) =>
|
|
151
|
+
posNeg * (depthFn(left) - depthFn(right)),
|
|
152
|
+
);
|
|
153
|
+
const matchingConfigPair = (uiView) => {
|
|
154
|
+
const matchingConfigs = this._viewConfigs.filter(
|
|
155
|
+
ViewService.matches(uiViewsByFqn, uiView),
|
|
156
|
+
);
|
|
157
|
+
if (matchingConfigs.length > 1) {
|
|
158
|
+
// This is OK. Child states can target a ui-view that the parent state also targets (the child wins)
|
|
159
|
+
// Sort by depth and return the match from the deepest child
|
|
160
|
+
// console.log(`Multiple matching view configs for ${uiView.fqn}`, matchingConfigs);
|
|
161
|
+
matchingConfigs.sort(depthCompare(viewConfigDepth, -1)); // descending
|
|
162
|
+
}
|
|
163
|
+
return { uiView, viewConfig: matchingConfigs[0] };
|
|
164
|
+
};
|
|
165
|
+
const configureUIView = (tuple) => {
|
|
166
|
+
// If a parent ui-view is reconfigured, it could destroy child ui-views.
|
|
167
|
+
// Before configuring a child ui-view, make sure it's still in the active uiViews array.
|
|
168
|
+
if (this._uiViews.indexOf(tuple.uiView) !== -1)
|
|
169
|
+
tuple.uiView.configUpdated(tuple.viewConfig);
|
|
170
|
+
};
|
|
171
|
+
// Sort views by FQN and state depth. Process uiviews nearest the root first.
|
|
172
|
+
const uiViewTuples = this._uiViews
|
|
173
|
+
.sort(depthCompare(uiViewDepth, 1))
|
|
174
|
+
.map(matchingConfigPair);
|
|
175
|
+
const matchedViewConfigs = uiViewTuples.map((tuple) => tuple.viewConfig);
|
|
176
|
+
const unmatchedConfigTuples = this._viewConfigs
|
|
177
|
+
.filter((config) => !inArray(matchedViewConfigs, config))
|
|
178
|
+
.map((viewConfig) => ({ uiView: undefined, viewConfig }));
|
|
179
|
+
uiViewTuples.forEach(configureUIView);
|
|
180
|
+
const allTuples = uiViewTuples.concat(unmatchedConfigTuples);
|
|
181
|
+
this._listeners.forEach((cb) => cb(allTuples));
|
|
182
|
+
trace.traceViewSync(allTuples);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Registers a `ui-view` component
|
|
186
|
+
*
|
|
187
|
+
* When a `ui-view` component is created, it uses this method to register itself.
|
|
188
|
+
* After registration the [[sync]] method is used to ensure all `ui-view` are configured with the proper [[ViewConfig]].
|
|
189
|
+
*
|
|
190
|
+
* Note: the `ui-view` component uses the `ViewConfig` to determine what view should be loaded inside the `ui-view`,
|
|
191
|
+
* and what the view's state context is.
|
|
192
|
+
*
|
|
193
|
+
* Note: There is no corresponding `deregisterUIView`.
|
|
194
|
+
* A `ui-view` should hang on to the return value of `registerUIView` and invoke it to deregister itself.
|
|
195
|
+
*
|
|
196
|
+
* @param uiView The metadata for a UIView
|
|
197
|
+
* @return a de-registration function used when the view is destroyed.
|
|
198
|
+
*/
|
|
199
|
+
registerUIView(uiView) {
|
|
200
|
+
trace.traceViewServiceUIViewEvent("-> Registering", uiView);
|
|
201
|
+
const uiViews = this._uiViews;
|
|
202
|
+
const fqnAndTypeMatches = (uiv) =>
|
|
203
|
+
uiv.fqn === uiView.fqn && uiv.$type === uiView.$type;
|
|
204
|
+
if (uiViews.filter(fqnAndTypeMatches).length)
|
|
205
|
+
trace.traceViewServiceUIViewEvent("!!!! duplicate uiView named:", uiView);
|
|
206
|
+
uiViews.push(uiView);
|
|
207
|
+
this.sync();
|
|
208
|
+
return () => {
|
|
209
|
+
const idx = uiViews.indexOf(uiView);
|
|
210
|
+
if (idx === -1) {
|
|
211
|
+
trace.traceViewServiceUIViewEvent(
|
|
212
|
+
"Tried removing non-registered uiView",
|
|
213
|
+
uiView,
|
|
214
|
+
);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
trace.traceViewServiceUIViewEvent("<- Deregistering", uiView);
|
|
218
|
+
removeFrom(uiViews)(uiView);
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Returns the list of views currently available on the page, by fully-qualified name.
|
|
223
|
+
*
|
|
224
|
+
* @return {Array} Returns an array of fully-qualified view names.
|
|
225
|
+
*/
|
|
226
|
+
available() {
|
|
227
|
+
return this._uiViews.map(prop("fqn"));
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Returns the list of views on the page containing loaded content.
|
|
231
|
+
*
|
|
232
|
+
* @return {Array} Returns an array of fully-qualified view names.
|
|
233
|
+
*/
|
|
234
|
+
active() {
|
|
235
|
+
return this._uiViews.filter(prop("$config")).map(prop("name"));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Given a ui-view and a ViewConfig, determines if they "match".
|
|
240
|
+
*
|
|
241
|
+
* A ui-view has a fully qualified name (fqn) and a context object. The fqn is built from its overall location in
|
|
242
|
+
* the DOM, describing its nesting relationship to any parent ui-view tags it is nested inside of.
|
|
243
|
+
*
|
|
244
|
+
* A ViewConfig has a target ui-view name and a context anchor. The ui-view name can be a simple name, or
|
|
245
|
+
* can be a segmented ui-view path, describing a portion of a ui-view fqn.
|
|
246
|
+
*
|
|
247
|
+
* In order for a ui-view to match ViewConfig, ui-view's $type must match the ViewConfig's $type
|
|
248
|
+
*
|
|
249
|
+
* If the ViewConfig's target ui-view name is a simple name (no dots), then a ui-view matches if:
|
|
250
|
+
* - the ui-view's name matches the ViewConfig's target name
|
|
251
|
+
* - the ui-view's context matches the ViewConfig's anchor
|
|
252
|
+
*
|
|
253
|
+
* If the ViewConfig's target ui-view name is a segmented name (with dots), then a ui-view matches if:
|
|
254
|
+
* - There exists a parent ui-view where:
|
|
255
|
+
* - the parent ui-view's name matches the first segment (index 0) of the ViewConfig's target name
|
|
256
|
+
* - the parent ui-view's context matches the ViewConfig's anchor
|
|
257
|
+
* - And the remaining segments (index 1..n) of the ViewConfig's target name match the tail of the ui-view's fqn
|
|
258
|
+
*
|
|
259
|
+
* Example:
|
|
260
|
+
*
|
|
261
|
+
* DOM:
|
|
262
|
+
* <ui-view> <!-- created in the root context (name: "") -->
|
|
263
|
+
* <ui-view name="foo"> <!-- created in the context named: "A" -->
|
|
264
|
+
* <ui-view> <!-- created in the context named: "A.B" -->
|
|
265
|
+
* <ui-view name="bar"> <!-- created in the context named: "A.B.C" -->
|
|
266
|
+
* </ui-view>
|
|
267
|
+
* </ui-view>
|
|
268
|
+
* </ui-view>
|
|
269
|
+
* </ui-view>
|
|
270
|
+
*
|
|
271
|
+
* uiViews: [
|
|
272
|
+
* { fqn: "$default", creationContext: { name: "" } },
|
|
273
|
+
* { fqn: "$default.foo", creationContext: { name: "A" } },
|
|
274
|
+
* { fqn: "$default.foo.$default", creationContext: { name: "A.B" } }
|
|
275
|
+
* { fqn: "$default.foo.$default.bar", creationContext: { name: "A.B.C" } }
|
|
276
|
+
* ]
|
|
277
|
+
*
|
|
278
|
+
* These four view configs all match the ui-view with the fqn: "$default.foo.$default.bar":
|
|
279
|
+
*
|
|
280
|
+
* - ViewConfig1: { uiViewName: "bar", uiViewContextAnchor: "A.B.C" }
|
|
281
|
+
* - ViewConfig2: { uiViewName: "$default.bar", uiViewContextAnchor: "A.B" }
|
|
282
|
+
* - ViewConfig3: { uiViewName: "foo.$default.bar", uiViewContextAnchor: "A" }
|
|
283
|
+
* - ViewConfig4: { uiViewName: "$default.foo.$default.bar", uiViewContextAnchor: "" }
|
|
284
|
+
*
|
|
285
|
+
* Using ViewConfig3 as an example, it matches the ui-view with fqn "$default.foo.$default.bar" because:
|
|
286
|
+
* - The ViewConfig's segmented target name is: [ "foo", "$default", "bar" ]
|
|
287
|
+
* - There exists a parent ui-view (which has fqn: "$default.foo") where:
|
|
288
|
+
* - the parent ui-view's name "foo" matches the first segment "foo" of the ViewConfig's target name
|
|
289
|
+
* - the parent ui-view's context "A" matches the ViewConfig's anchor context "A"
|
|
290
|
+
* - And the remaining segments [ "$default", "bar" ].join("."_ of the ViewConfig's target name match
|
|
291
|
+
* the tail of the ui-view's fqn "default.bar"
|
|
292
|
+
*
|
|
293
|
+
* @internal
|
|
294
|
+
*/
|
|
295
|
+
ViewService.matches = (uiViewsByFqn, uiView) => (viewConfig) => {
|
|
296
|
+
// Don't supply an ng1 ui-view with an ng2 ViewConfig, etc
|
|
297
|
+
if (uiView.$type !== viewConfig.viewDecl.$type) return false;
|
|
298
|
+
// Split names apart from both viewConfig and uiView into segments
|
|
299
|
+
const vc = viewConfig.viewDecl;
|
|
300
|
+
const vcSegments = vc.$uiViewName.split(".");
|
|
301
|
+
const uivSegments = uiView.fqn.split(".");
|
|
302
|
+
// Check if the tails of the segment arrays match. ex, these arrays' tails match:
|
|
303
|
+
// vc: ["foo", "bar"], uiv fqn: ["$default", "foo", "bar"]
|
|
304
|
+
if (!equals(vcSegments, uivSegments.slice(0 - vcSegments.length)))
|
|
305
|
+
return false;
|
|
306
|
+
// Now check if the fqn ending at the first segment of the viewConfig matches the context:
|
|
307
|
+
// ["$default", "foo"].join(".") == "$default.foo", does the ui-view $default.foo context match?
|
|
308
|
+
const negOffset = 1 - vcSegments.length || undefined;
|
|
309
|
+
const fqnToFirstSegment = uivSegments.slice(0, negOffset).join(".");
|
|
310
|
+
const uiViewContext = uiViewsByFqn[fqnToFirstSegment].creationContext;
|
|
311
|
+
return vc.$uiViewContextAnchor === (uiViewContext && uiViewContext.name);
|
|
312
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
$uiRouterProvider,
|
|
3
|
+
getProviderFor,
|
|
4
|
+
getStateProvider,
|
|
5
|
+
router,
|
|
6
|
+
runBlock,
|
|
7
|
+
watchDigests,
|
|
8
|
+
} from "./adapter/services";
|
|
9
|
+
import { TemplateFactory } from "./adapter/templateFactory";
|
|
10
|
+
import { trace } from "./core/common/trace";
|
|
11
|
+
|
|
12
|
+
export function initRouter() {
|
|
13
|
+
window.angular.module("ui.router.angular1", []);
|
|
14
|
+
const mod_init = window.angular.module("ui.router.init", ["ng"]);
|
|
15
|
+
const mod_util = window.angular.module("ui.router.util", ["ui.router.init"]);
|
|
16
|
+
const mod_state = window.angular.module("ui.router.state", [
|
|
17
|
+
"ui.router.util",
|
|
18
|
+
"ui.router.angular1",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const mod_main = window.angular.module("ui.router", [
|
|
22
|
+
"ui.router.init",
|
|
23
|
+
"ui.router.state",
|
|
24
|
+
"ui.router.angular1",
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
mod_init.provider("$uiRouter", $uiRouterProvider);
|
|
28
|
+
mod_util.provider("$urlService", getProviderFor("urlService"));
|
|
29
|
+
mod_util.provider("$urlMatcherFactory", [
|
|
30
|
+
"$uiRouterProvider",
|
|
31
|
+
function RouterProvide() {
|
|
32
|
+
return router.urlMatcherFactory;
|
|
33
|
+
},
|
|
34
|
+
]);
|
|
35
|
+
mod_util.provider("$templateFactory", function () {
|
|
36
|
+
return new TemplateFactory();
|
|
37
|
+
});
|
|
38
|
+
mod_state.provider("$stateRegistry", getProviderFor("stateRegistry"));
|
|
39
|
+
mod_state.provider("$uiRouterGlobals", getProviderFor("globals"));
|
|
40
|
+
mod_state.provider("$transitions", getProviderFor("transitionService"));
|
|
41
|
+
mod_state.provider("$state", ["$uiRouterProvider", getStateProvider]);
|
|
42
|
+
mod_state.factory("$stateParams", [
|
|
43
|
+
"$uiRouter",
|
|
44
|
+
function StateParamse($uiRouter) {
|
|
45
|
+
return $uiRouter.globals.params;
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
mod_main.factory("$view", function View() {
|
|
49
|
+
return router.viewService;
|
|
50
|
+
});
|
|
51
|
+
mod_main.service("$trace", function Trace() {
|
|
52
|
+
return trace;
|
|
53
|
+
});
|
|
54
|
+
mod_main.run(watchDigests);
|
|
55
|
+
mod_util.run(["$urlMatcherFactory", function MatcherFac() {}]);
|
|
56
|
+
mod_state.run(["$state", function State() {}]);
|
|
57
|
+
mod_init.run(runBlock);
|
|
58
|
+
}
|
package/test/module-test.html
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
|
-
<script
|
|
4
|
+
<script>
|
|
5
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
6
|
+
window.angular.module("test", ["ui.router"]);
|
|
7
|
+
});
|
|
8
|
+
</script>
|
|
5
9
|
<script type="module" src="../src/index.js"></script>
|
|
6
10
|
</head>
|
|
7
|
-
<body ng-app>
|
|
11
|
+
<body ng-app="test">
|
|
8
12
|
<div ng-init='list = [{ name: "x" }, { name: "y" }, { name: "z" }]'>
|
|
9
13
|
<label>Name:</label>
|
|
10
14
|
<input type="text" ng-model="yourName" placeholder="Enter a name here" />
|
|
File without changes
|