UNPKG

20.5 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5var aureliaRouter = require('aurelia-router');
6var aureliaMetadata = require('aurelia-metadata');
7var aureliaPath = require('aurelia-path');
8var aureliaTemplating = require('aurelia-templating');
9var aureliaDependencyInjection = require('aurelia-dependency-injection');
10var aureliaBinding = require('aurelia-binding');
11var aureliaPal = require('aurelia-pal');
12var LogManager = require('aurelia-logging');
13
14/*! *****************************************************************************
15Copyright (c) Microsoft Corporation. All rights reserved.
16Licensed under the Apache License, Version 2.0 (the "License"); you may not use
17this file except in compliance with the License. You may obtain a copy of the
18License at http://www.apache.org/licenses/LICENSE-2.0
19
20THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
22WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
23MERCHANTABLITY OR NON-INFRINGEMENT.
24
25See the Apache Version 2.0 License for specific language governing permissions
26and limitations under the License.
27***************************************************************************** */
28/* global Reflect, Promise */
29
30var extendStatics = function(d, b) {
31 extendStatics = Object.setPrototypeOf ||
32 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
33 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
34 return extendStatics(d, b);
35};
36
37function __extends(d, b) {
38 extendStatics(d, b);
39 function __() { this.constructor = d; }
40 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
41}
42
43var EmptyLayoutViewModel = /** @class */ (function () {
44 function EmptyLayoutViewModel() {
45 }
46 return EmptyLayoutViewModel;
47}());
48/**
49 * Implementation of Aurelia Router ViewPort. Responsible for loading route, composing and swapping routes views
50 */
51var RouterView = /** @class */ (function () {
52 function RouterView(element, container, viewSlot, router, viewLocator, compositionTransaction, compositionEngine) {
53 this.element = element;
54 this.container = container;
55 this.viewSlot = viewSlot;
56 this.router = router;
57 this.viewLocator = viewLocator;
58 this.compositionTransaction = compositionTransaction;
59 this.compositionEngine = compositionEngine;
60 // add this <router-view/> to router view ports lookup based on name attribute
61 // when this router is the root router-view
62 // also trigger AppRouter registerViewPort extra flow
63 this.router.registerViewPort(this, this.element.getAttribute('name'));
64 // Each <router-view/> process its instruction as a composition transaction
65 // there are differences between intial composition and subsequent compositions
66 // also there are differences between root composition and child <router-view/> composition
67 // mark the first composition transaction with a property initialComposition to distinguish it
68 // when the root <router-view/> gets new instruction for the first time
69 if (!('initialComposition' in compositionTransaction)) {
70 compositionTransaction.initialComposition = true;
71 this.compositionTransactionNotifier = compositionTransaction.enlist();
72 }
73 }
74 /**@internal */
75 RouterView.inject = function () {
76 return [aureliaPal.DOM.Element, aureliaDependencyInjection.Container, aureliaTemplating.ViewSlot, aureliaRouter.Router, aureliaTemplating.ViewLocator, aureliaTemplating.CompositionTransaction, aureliaTemplating.CompositionEngine];
77 };
78 RouterView.prototype.created = function (owningView) {
79 this.owningView = owningView;
80 };
81 RouterView.prototype.bind = function (bindingContext, overrideContext) {
82 // router needs to get access to view model of current route parent
83 // doing it in generic way via viewModel property on container
84 this.container.viewModel = bindingContext;
85 this.overrideContext = overrideContext;
86 };
87 /**
88 * Implementation of `aurelia-router` ViewPort interface, responsible for templating related part in routing Pipeline
89 */
90 RouterView.prototype.process = function ($viewPortInstruction, waitToSwap) {
91 var _this = this;
92 // have strong typings without exposing it in public typings, this is to ensure maximum backward compat
93 var viewPortInstruction = $viewPortInstruction;
94 var component = viewPortInstruction.component;
95 var childContainer = component.childContainer;
96 var viewModel = component.viewModel;
97 var viewModelResource = component.viewModelResource;
98 var metadata = viewModelResource.metadata;
99 var config = component.router.currentInstruction.config;
100 var viewPortConfig = config.viewPorts ? (config.viewPorts[viewPortInstruction.name] || {}) : {};
101 childContainer.get(RouterViewLocator)._notify(this);
102 // layoutInstruction is our layout viewModel
103 var layoutInstruction = {
104 viewModel: viewPortConfig.layoutViewModel || config.layoutViewModel || this.layoutViewModel,
105 view: viewPortConfig.layoutView || config.layoutView || this.layoutView,
106 model: viewPortConfig.layoutModel || config.layoutModel || this.layoutModel,
107 router: viewPortInstruction.component.router,
108 childContainer: childContainer,
109 viewSlot: this.viewSlot
110 };
111 // viewport will be a thin wrapper around composition engine
112 // to process instruction/configuration from users
113 // preparing all information related to a composition process
114 // first by getting view strategy of a ViewPortComponent View
115 var viewStrategy = this.viewLocator.getViewStrategy(component.view || viewModel);
116 if (viewStrategy && component.view) {
117 viewStrategy.makeRelativeTo(aureliaMetadata.Origin.get(component.router.container.viewModel.constructor).moduleId);
118 }
119 // using metadata of a custom element view model to load appropriate view-factory instance
120 return metadata
121 .load(childContainer, viewModelResource.value, null, viewStrategy, true)
122 // for custom element, viewFactory typing is always ViewFactory
123 // for custom attribute, it will be HtmlBehaviorResource
124 .then(function (viewFactory) {
125 // if this is not the first time that this <router-view/> is composing its instruction
126 // try to capture ownership of the composition transaction
127 // child <router-view/> will not be able to capture, since root <router-view/> typically captures
128 // the ownership token
129 if (!_this.compositionTransactionNotifier) {
130 _this.compositionTransactionOwnershipToken = _this.compositionTransaction.tryCapture();
131 }
132 if (layoutInstruction.viewModel || layoutInstruction.view) {
133 viewPortInstruction.layoutInstruction = layoutInstruction;
134 }
135 var viewPortComponentBehaviorInstruction = aureliaTemplating.BehaviorInstruction.dynamic(_this.element, viewModel, viewFactory);
136 viewPortInstruction.controller = metadata.create(childContainer, viewPortComponentBehaviorInstruction);
137 if (waitToSwap) {
138 return null;
139 }
140 _this.swap(viewPortInstruction);
141 });
142 };
143 RouterView.prototype.swap = function ($viewPortInstruction) {
144 var _this = this;
145 // have strong typings without exposing it in public typings, this is to ensure maximum backward compat
146 var viewPortInstruction = $viewPortInstruction;
147 var viewPortController = viewPortInstruction.controller;
148 var layoutInstruction = viewPortInstruction.layoutInstruction;
149 var previousView = this.view;
150 // Final step of swapping a <router-view/> ViewPortComponent
151 var work = function () {
152 var swapStrategy = aureliaTemplating.SwapStrategies[_this.swapOrder] || aureliaTemplating.SwapStrategies.after;
153 var viewSlot = _this.viewSlot;
154 swapStrategy(viewSlot, previousView, function () { return Promise.resolve(viewSlot.add(_this.view)); }).then(function () {
155 _this._notify();
156 });
157 };
158 // Ensure all users setups have been completed
159 var ready = function (owningView_or_layoutView) {
160 viewPortController.automate(_this.overrideContext, owningView_or_layoutView);
161 var transactionOwnerShipToken = _this.compositionTransactionOwnershipToken;
162 // if this router-view is the root <router-view/> of a normal startup via aurelia.setRoot
163 // attemp to take control of the transaction
164 // if ownership can be taken
165 // wait for transaction to complete before swapping
166 if (transactionOwnerShipToken) {
167 return transactionOwnerShipToken
168 .waitForCompositionComplete()
169 .then(function () {
170 _this.compositionTransactionOwnershipToken = null;
171 return work();
172 });
173 }
174 // otherwise, just swap
175 return work();
176 };
177 // If there is layout instruction, new to compose layout before processing ViewPortComponent
178 // layout controller/view/view-model is composed using composition engine APIs
179 if (layoutInstruction) {
180 if (!layoutInstruction.viewModel) {
181 // createController chokes if there's no viewmodel, so create a dummy one
182 // but avoid using a POJO as it creates unwanted metadata in Object constructor
183 layoutInstruction.viewModel = new EmptyLayoutViewModel();
184 }
185 // using composition engine to create compose layout
186 return this.compositionEngine
187 // first create controller from layoutInstruction
188 // and treat it as CompositionContext
189 // then emulate slot projection with ViewPortComponent view
190 .createController(layoutInstruction)
191 .then(function (layoutController) {
192 var layoutView = layoutController.view;
193 aureliaTemplating.ShadowDOM.distributeView(viewPortController.view, layoutController.slots || layoutView.slots);
194 // when there is a layout
195 // view hierarchy is: <router-view/> owner view -> layout view -> ViewPortComponent view
196 layoutController.automate(aureliaBinding.createOverrideContext(layoutInstruction.viewModel), _this.owningView);
197 layoutView.children.push(viewPortController.view);
198 return layoutView || layoutController;
199 })
200 .then(function (newView) {
201 _this.view = newView;
202 return ready(newView);
203 });
204 }
205 // if there is no layout, then get ViewPortComponent view ready as view property
206 // and process controller/swapping
207 // when there is no layout
208 // view hierarchy is: <router-view/> owner view -> ViewPortComponent view
209 this.view = viewPortController.view;
210 return ready(this.owningView);
211 };
212 /**
213 * Notify composition transaction that this router has finished processing
214 * Happens when this <router-view/> is the root router-view
215 * @internal
216 */
217 RouterView.prototype._notify = function () {
218 var notifier = this.compositionTransactionNotifier;
219 if (notifier) {
220 notifier.done();
221 this.compositionTransactionNotifier = null;
222 }
223 };
224 /**
225 * @internal Actively avoid using decorator to reduce the amount of code generated
226 *
227 * There is no view to compose by default in a router view
228 * This custom element is responsible for composing its own view, based on current config
229 */
230 RouterView.$view = null;
231 /**
232 * @internal Actively avoid using decorator to reduce the amount of code generated
233 */
234 RouterView.$resource = {
235 name: 'router-view',
236 bindables: ['swapOrder', 'layoutView', 'layoutViewModel', 'layoutModel', 'inherit-binding-context']
237 };
238 return RouterView;
239}());
240/**
241* Locator which finds the nearest RouterView, relative to the current dependency injection container.
242*/
243var RouterViewLocator = /** @class */ (function () {
244 /**
245 * Creates an instance of the RouterViewLocator class.
246 */
247 function RouterViewLocator() {
248 var _this = this;
249 this.promise = new Promise(function (resolve) { return _this.resolve = resolve; });
250 }
251 /**
252 * Finds the nearest RouterView instance.
253 * @returns A promise that will be resolved with the located RouterView instance.
254 */
255 RouterViewLocator.prototype.findNearest = function () {
256 return this.promise;
257 };
258 /**@internal */
259 RouterViewLocator.prototype._notify = function (routerView) {
260 this.resolve(routerView);
261 };
262 return RouterViewLocator;
263}());
264
265/**@internal exported for unit testing */
266var EmptyClass = /** @class */ (function () {
267 function EmptyClass() {
268 }
269 return EmptyClass;
270}());
271aureliaTemplating.inlineView('<template></template>')(EmptyClass);
272/**
273 * Default implementation of `RouteLoader` used for loading component based on a route config
274 */
275var TemplatingRouteLoader = /** @class */ (function (_super) {
276 __extends(TemplatingRouteLoader, _super);
277 function TemplatingRouteLoader(compositionEngine) {
278 var _this = _super.call(this) || this;
279 _this.compositionEngine = compositionEngine;
280 return _this;
281 }
282 /**
283 * Resolve a view model from a RouteConfig
284 * Throws when there is neither "moduleId" nor "viewModel" property
285 * @internal
286 */
287 TemplatingRouteLoader.prototype.resolveViewModel = function (router, config) {
288 return new Promise(function (resolve, reject) {
289 var viewModel;
290 if ('moduleId' in config) {
291 var moduleId = config.moduleId;
292 if (moduleId === null) {
293 viewModel = EmptyClass;
294 }
295 else {
296 // this requires container of router has passes a certain point
297 // where a view model has been setup on the container
298 // it will fail in enhance scenario because no viewport has been registered
299 moduleId = aureliaPath.relativeToFile(moduleId, aureliaMetadata.Origin.get(router.container.viewModel.constructor).moduleId);
300 if (/\.html/i.test(moduleId)) {
301 viewModel = createDynamicClass(moduleId);
302 }
303 else {
304 viewModel = moduleId;
305 }
306 }
307 return resolve(viewModel);
308 }
309 // todo: add if ('viewModel' in config) to support static view model resolution
310 reject(new Error('Invalid route config. No "moduleId" found.'));
311 });
312 };
313 /**
314 * Create child container based on a router container
315 * Also ensures that child router are properly constructed in the newly created child container
316 * @internal
317 */
318 TemplatingRouteLoader.prototype.createChildContainer = function (router) {
319 var childContainer = router.container.createChild();
320 childContainer.registerSingleton(RouterViewLocator);
321 childContainer.getChildRouter = function () {
322 var childRouter;
323 childContainer.registerHandler(aureliaRouter.Router, function () { return childRouter || (childRouter = router.createChild(childContainer)); });
324 return childContainer.get(aureliaRouter.Router);
325 };
326 return childContainer;
327 };
328 /**
329 * Load corresponding component of a route config of a navigation instruction
330 */
331 TemplatingRouteLoader.prototype.loadRoute = function (router, config, _navInstruction) {
332 var _this = this;
333 return this
334 .resolveViewModel(router, config)
335 .then(function (viewModel) { return _this.compositionEngine.ensureViewModel({
336 viewModel: viewModel,
337 childContainer: _this.createChildContainer(router),
338 view: config.view || config.viewStrategy,
339 router: router
340 }); });
341 };
342 /**@internal */
343 TemplatingRouteLoader.inject = [aureliaTemplating.CompositionEngine];
344 return TemplatingRouteLoader;
345}(aureliaRouter.RouteLoader));
346/**@internal exported for unit testing */
347function createDynamicClass(moduleId) {
348 var name = /([^\/^\?]+)\.html/i.exec(moduleId)[1];
349 var DynamicClass = /** @class */ (function () {
350 function DynamicClass() {
351 }
352 DynamicClass.prototype.bind = function (bindingContext) {
353 this.$parent = bindingContext;
354 };
355 return DynamicClass;
356 }());
357 aureliaTemplating.customElement(name)(DynamicClass);
358 aureliaTemplating.useView(moduleId)(DynamicClass);
359 return DynamicClass;
360}
361
362var logger = LogManager.getLogger('route-href');
363/**
364 * Helper custom attribute to help associate an element with a route by name
365 */
366var RouteHref = /** @class */ (function () {
367 function RouteHref(router, element) {
368 this.router = router;
369 this.element = element;
370 this.attribute = 'href';
371 }
372 /*@internal */
373 RouteHref.inject = function () {
374 return [aureliaRouter.Router, aureliaPal.DOM.Element];
375 };
376 RouteHref.prototype.bind = function () {
377 this.isActive = true;
378 this.processChange();
379 };
380 RouteHref.prototype.unbind = function () {
381 this.isActive = false;
382 };
383 RouteHref.prototype.attributeChanged = function (value, previous) {
384 if (previous) {
385 this.element.removeAttribute(previous);
386 }
387 return this.processChange();
388 };
389 RouteHref.prototype.processChange = function () {
390 var _this = this;
391 return this.router
392 .ensureConfigured()
393 .then(function () {
394 if (!_this.isActive) {
395 // returning null to avoid Bluebird warning
396 return null;
397 }
398 var element = _this.element;
399 var href = _this.router.generate(_this.route, _this.params);
400 if (element.au.controller) {
401 element.au.controller.viewModel[_this.attribute] = href;
402 }
403 else {
404 element.setAttribute(_this.attribute, href);
405 }
406 // returning null to avoid Bluebird warning
407 return null;
408 })
409 .catch(function (reason) {
410 logger.error(reason);
411 });
412 };
413 /**
414 * @internal Actively avoid using decorator to reduce the amount of code generated
415 */
416 RouteHref.$resource = {
417 type: 'attribute',
418 name: 'route-href',
419 bindables: [
420 { name: 'route', changeHandler: 'processChange', primaryProperty: true },
421 { name: 'params', changeHandler: 'processChange' },
422 'attribute'
423 ] // type definition of Aurelia templating is wrong
424 };
425 return RouteHref;
426}());
427
428function configure(config) {
429 config
430 .singleton(aureliaRouter.RouteLoader, TemplatingRouteLoader)
431 .singleton(aureliaRouter.Router, aureliaRouter.AppRouter)
432 .globalResources(RouterView, RouteHref);
433 config.container.registerAlias(aureliaRouter.Router, aureliaRouter.AppRouter);
434}
435
436exports.RouteHref = RouteHref;
437exports.RouterView = RouterView;
438exports.TemplatingRouteLoader = TemplatingRouteLoader;
439exports.configure = configure;
440//# sourceMappingURL=aurelia-templating-router.js.map