UNPKG

13.7 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.StateBuilder = exports.resolvablesBuilder = void 0;
4var common_1 = require("../common/common");
5var predicates_1 = require("../common/predicates");
6var strings_1 = require("../common/strings");
7var hof_1 = require("../common/hof");
8var resolvable_1 = require("../resolve/resolvable");
9var coreservices_1 = require("../common/coreservices");
10var parseUrl = function (url) {
11 if (!predicates_1.isString(url))
12 return false;
13 var root = url.charAt(0) === '^';
14 return { val: root ? url.substring(1) : url, root: root };
15};
16function nameBuilder(state) {
17 return state.name;
18}
19function selfBuilder(state) {
20 state.self.$$state = function () { return state; };
21 return state.self;
22}
23function dataBuilder(state) {
24 if (state.parent && state.parent.data) {
25 state.data = state.self.data = common_1.inherit(state.parent.data, state.data);
26 }
27 return state.data;
28}
29var getUrlBuilder = function ($urlMatcherFactoryProvider, root) {
30 return function urlBuilder(stateObject) {
31 var stateDec = stateObject.self;
32 // For future states, i.e., states whose name ends with `.**`,
33 // match anything that starts with the url prefix
34 if (stateDec && stateDec.url && stateDec.name && stateDec.name.match(/\.\*\*$/)) {
35 var newStateDec = {};
36 common_1.copy(stateDec, newStateDec);
37 newStateDec.url += '{remainder:any}'; // match any path (.*)
38 stateDec = newStateDec;
39 }
40 var parent = stateObject.parent;
41 var parsed = parseUrl(stateDec.url);
42 var url = !parsed ? stateDec.url : $urlMatcherFactoryProvider.compile(parsed.val, { state: stateDec });
43 if (!url)
44 return null;
45 if (!$urlMatcherFactoryProvider.isMatcher(url))
46 throw new Error("Invalid url '" + url + "' in state '" + stateObject + "'");
47 return parsed && parsed.root ? url : ((parent && parent.navigable) || root()).url.append(url);
48 };
49};
50var getNavigableBuilder = function (isRoot) {
51 return function navigableBuilder(state) {
52 return !isRoot(state) && state.url ? state : state.parent ? state.parent.navigable : null;
53 };
54};
55var getParamsBuilder = function (paramFactory) {
56 return function paramsBuilder(state) {
57 var makeConfigParam = function (config, id) { return paramFactory.fromConfig(id, null, state.self); };
58 var urlParams = (state.url && state.url.parameters({ inherit: false })) || [];
59 var nonUrlParams = common_1.values(common_1.mapObj(common_1.omit(state.params || {}, urlParams.map(hof_1.prop('id'))), makeConfigParam));
60 return urlParams
61 .concat(nonUrlParams)
62 .map(function (p) { return [p.id, p]; })
63 .reduce(common_1.applyPairs, {});
64 };
65};
66function pathBuilder(state) {
67 return state.parent ? state.parent.path.concat(state) : /*root*/ [state];
68}
69function includesBuilder(state) {
70 var includes = state.parent ? common_1.extend({}, state.parent.includes) : {};
71 includes[state.name] = true;
72 return includes;
73}
74/**
75 * This is a [[StateBuilder.builder]] function for the `resolve:` block on a [[StateDeclaration]].
76 *
77 * When the [[StateBuilder]] builds a [[StateObject]] object from a raw [[StateDeclaration]], this builder
78 * validates the `resolve` property and converts it to a [[Resolvable]] array.
79 *
80 * resolve: input value can be:
81 *
82 * {
83 * // analyzed but not injected
84 * myFooResolve: function() { return "myFooData"; },
85 *
86 * // function.toString() parsed, "DependencyName" dep as string (not min-safe)
87 * myBarResolve: function(DependencyName) { return DependencyName.fetchSomethingAsPromise() },
88 *
89 * // Array split; "DependencyName" dep as string
90 * myBazResolve: [ "DependencyName", function(dep) { return dep.fetchSomethingAsPromise() },
91 *
92 * // Array split; DependencyType dep as token (compared using ===)
93 * myQuxResolve: [ DependencyType, function(dep) { return dep.fetchSometingAsPromise() },
94 *
95 * // val.$inject used as deps
96 * // where:
97 * // corgeResolve.$inject = ["DependencyName"];
98 * // function corgeResolve(dep) { dep.fetchSometingAsPromise() }
99 * // then "DependencyName" dep as string
100 * myCorgeResolve: corgeResolve,
101 *
102 * // inject service by name
103 * // When a string is found, desugar creating a resolve that injects the named service
104 * myGraultResolve: "SomeService"
105 * }
106 *
107 * or:
108 *
109 * [
110 * new Resolvable("myFooResolve", function() { return "myFooData" }),
111 * new Resolvable("myBarResolve", function(dep) { return dep.fetchSomethingAsPromise() }, [ "DependencyName" ]),
112 * { provide: "myBazResolve", useFactory: function(dep) { dep.fetchSomethingAsPromise() }, deps: [ "DependencyName" ] }
113 * ]
114 */
115function resolvablesBuilder(state) {
116 /** convert resolve: {} and resolvePolicy: {} objects to an array of tuples */
117 var objects2Tuples = function (resolveObj, resolvePolicies) {
118 return Object.keys(resolveObj || {}).map(function (token) { return ({
119 token: token,
120 val: resolveObj[token],
121 deps: undefined,
122 policy: resolvePolicies[token],
123 }); });
124 };
125 /** fetch DI annotations from a function or ng1-style array */
126 var annotate = function (fn) {
127 var $injector = coreservices_1.services.$injector;
128 // ng1 doesn't have an $injector until runtime.
129 // If the $injector doesn't exist, use "deferred" literal as a
130 // marker indicating they should be annotated when runtime starts
131 return fn['$inject'] || ($injector && $injector.annotate(fn, $injector.strictDi)) || 'deferred';
132 };
133 /** true if the object has both `token` and `resolveFn`, and is probably a [[ResolveLiteral]] */
134 var isResolveLiteral = function (obj) { return !!(obj.token && obj.resolveFn); };
135 /** true if the object looks like a provide literal, or a ng2 Provider */
136 var isLikeNg2Provider = function (obj) {
137 return !!((obj.provide || obj.token) && (obj.useValue || obj.useFactory || obj.useExisting || obj.useClass));
138 };
139 /** true if the object looks like a tuple from obj2Tuples */
140 var isTupleFromObj = function (obj) {
141 return !!(obj && obj.val && (predicates_1.isString(obj.val) || predicates_1.isArray(obj.val) || predicates_1.isFunction(obj.val)));
142 };
143 /** extracts the token from a Provider or provide literal */
144 var getToken = function (p) { return p.provide || p.token; };
145 // prettier-ignore: Given a literal resolve or provider object, returns a Resolvable
146 var literal2Resolvable = hof_1.pattern([
147 [hof_1.prop('resolveFn'), function (p) { return new resolvable_1.Resolvable(getToken(p), p.resolveFn, p.deps, p.policy); }],
148 [hof_1.prop('useFactory'), function (p) { return new resolvable_1.Resolvable(getToken(p), p.useFactory, p.deps || p.dependencies, p.policy); }],
149 [hof_1.prop('useClass'), function (p) { return new resolvable_1.Resolvable(getToken(p), function () { return new p.useClass(); }, [], p.policy); }],
150 [hof_1.prop('useValue'), function (p) { return new resolvable_1.Resolvable(getToken(p), function () { return p.useValue; }, [], p.policy, p.useValue); }],
151 [hof_1.prop('useExisting'), function (p) { return new resolvable_1.Resolvable(getToken(p), common_1.identity, [p.useExisting], p.policy); }],
152 ]);
153 // prettier-ignore
154 var tuple2Resolvable = hof_1.pattern([
155 [hof_1.pipe(hof_1.prop('val'), predicates_1.isString), function (tuple) { return new resolvable_1.Resolvable(tuple.token, common_1.identity, [tuple.val], tuple.policy); }],
156 [hof_1.pipe(hof_1.prop('val'), predicates_1.isArray), function (tuple) { return new resolvable_1.Resolvable(tuple.token, common_1.tail(tuple.val), tuple.val.slice(0, -1), tuple.policy); }],
157 [hof_1.pipe(hof_1.prop('val'), predicates_1.isFunction), function (tuple) { return new resolvable_1.Resolvable(tuple.token, tuple.val, annotate(tuple.val), tuple.policy); }],
158 ]);
159 // prettier-ignore
160 var item2Resolvable = hof_1.pattern([
161 [hof_1.is(resolvable_1.Resolvable), function (r) { return r; }],
162 [isResolveLiteral, literal2Resolvable],
163 [isLikeNg2Provider, literal2Resolvable],
164 [isTupleFromObj, tuple2Resolvable],
165 [hof_1.val(true), function (obj) { throw new Error('Invalid resolve value: ' + strings_1.stringify(obj)); },],
166 ]);
167 // If resolveBlock is already an array, use it as-is.
168 // Otherwise, assume it's an object and convert to an Array of tuples
169 var decl = state.resolve;
170 var items = predicates_1.isArray(decl) ? decl : objects2Tuples(decl, state.resolvePolicy || {});
171 return items.map(item2Resolvable);
172}
173exports.resolvablesBuilder = resolvablesBuilder;
174/**
175 * A internal global service
176 *
177 * StateBuilder is a factory for the internal [[StateObject]] objects.
178 *
179 * When you register a state with the [[StateRegistry]], you register a plain old javascript object which
180 * conforms to the [[StateDeclaration]] interface. This factory takes that object and builds the corresponding
181 * [[StateObject]] object, which has an API and is used internally.
182 *
183 * Custom properties or API may be added to the internal [[StateObject]] object by registering a decorator function
184 * using the [[builder]] method.
185 */
186var StateBuilder = /** @class */ (function () {
187 function StateBuilder(matcher, urlMatcherFactory) {
188 this.matcher = matcher;
189 var self = this;
190 var root = function () { return matcher.find(''); };
191 var isRoot = function (state) { return state.name === ''; };
192 function parentBuilder(state) {
193 if (isRoot(state))
194 return null;
195 return matcher.find(self.parentName(state)) || root();
196 }
197 this.builders = {
198 name: [nameBuilder],
199 self: [selfBuilder],
200 parent: [parentBuilder],
201 data: [dataBuilder],
202 // Build a URLMatcher if necessary, either via a relative or absolute URL
203 url: [getUrlBuilder(urlMatcherFactory, root)],
204 // Keep track of the closest ancestor state that has a URL (i.e. is navigable)
205 navigable: [getNavigableBuilder(isRoot)],
206 params: [getParamsBuilder(urlMatcherFactory.paramFactory)],
207 // Each framework-specific ui-router implementation should define its own `views` builder
208 // e.g., src/ng1/statebuilders/views.ts
209 views: [],
210 // Keep a full path from the root down to this state as this is needed for state activation.
211 path: [pathBuilder],
212 // Speed up $state.includes() as it's used a lot
213 includes: [includesBuilder],
214 resolvables: [resolvablesBuilder],
215 };
216 }
217 StateBuilder.prototype.builder = function (name, fn) {
218 var builders = this.builders;
219 var array = builders[name] || [];
220 // Backwards compat: if only one builder exists, return it, else return whole arary.
221 if (predicates_1.isString(name) && !predicates_1.isDefined(fn))
222 return array.length > 1 ? array : array[0];
223 if (!predicates_1.isString(name) || !predicates_1.isFunction(fn))
224 return;
225 builders[name] = array;
226 builders[name].push(fn);
227 return function () { return builders[name].splice(builders[name].indexOf(fn, 1)) && null; };
228 };
229 /**
230 * Builds all of the properties on an essentially blank State object, returning a State object which has all its
231 * properties and API built.
232 *
233 * @param state an uninitialized State object
234 * @returns the built State object
235 */
236 StateBuilder.prototype.build = function (state) {
237 var _a = this, matcher = _a.matcher, builders = _a.builders;
238 var parent = this.parentName(state);
239 if (parent && !matcher.find(parent, undefined, false)) {
240 return null;
241 }
242 for (var key in builders) {
243 if (!builders.hasOwnProperty(key))
244 continue;
245 var chain = builders[key].reduce(function (parentFn, step) { return function (_state) { return step(_state, parentFn); }; }, common_1.noop);
246 state[key] = chain(state);
247 }
248 return state;
249 };
250 StateBuilder.prototype.parentName = function (state) {
251 // name = 'foo.bar.baz.**'
252 var name = state.name || '';
253 // segments = ['foo', 'bar', 'baz', '.**']
254 var segments = name.split('.');
255 // segments = ['foo', 'bar', 'baz']
256 var lastSegment = segments.pop();
257 // segments = ['foo', 'bar'] (ignore .** segment for future states)
258 if (lastSegment === '**')
259 segments.pop();
260 if (segments.length) {
261 if (state.parent) {
262 throw new Error("States that specify the 'parent:' property should not have a '.' in their name (" + name + ")");
263 }
264 // 'foo.bar'
265 return segments.join('.');
266 }
267 if (!state.parent)
268 return '';
269 return predicates_1.isString(state.parent) ? state.parent : state.parent.name;
270 };
271 StateBuilder.prototype.name = function (state) {
272 var name = state.name;
273 if (name.indexOf('.') !== -1 || !state.parent)
274 return name;
275 var parentName = predicates_1.isString(state.parent) ? state.parent : state.parent.name;
276 return parentName ? parentName + '.' + name : name;
277 };
278 return StateBuilder;
279}());
280exports.StateBuilder = StateBuilder;
281//# sourceMappingURL=stateBuilder.js.map
\No newline at end of file