1 | import { Container } from 'aurelia-dependency-injection';
|
2 | import { createOverrideContext, OverrideContext } from 'aurelia-binding';
|
3 | import {
|
4 | ViewSlot,
|
5 | ViewLocator,
|
6 | BehaviorInstruction,
|
7 | CompositionTransaction,
|
8 | CompositionEngine,
|
9 | ShadowDOM,
|
10 | SwapStrategies,
|
11 | ResourceDescription,
|
12 | HtmlBehaviorResource,
|
13 | CompositionTransactionNotifier,
|
14 | View,
|
15 | CompositionTransactionOwnershipToken,
|
16 | Controller,
|
17 | ViewFactory,
|
18 | CompositionContext,
|
19 | IStaticResourceConfig,
|
20 | IStaticViewConfig
|
21 | } from 'aurelia-templating';
|
22 | import {
|
23 | Router
|
24 | } from 'aurelia-router';
|
25 | import { Origin } from 'aurelia-metadata';
|
26 | import { DOM } from 'aurelia-pal';
|
27 | import { IRouterViewViewPortInstruction } from './interfaces';
|
28 |
|
29 | class EmptyLayoutViewModel {
|
30 |
|
31 | }
|
32 |
|
33 |
|
34 |
|
35 |
|
36 | export class RouterView {
|
37 |
|
38 |
|
39 | static inject() {
|
40 | return [DOM.Element, Container, ViewSlot, Router, ViewLocator, CompositionTransaction, CompositionEngine];
|
41 | }
|
42 |
|
43 | |
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | static $view: IStaticViewConfig = null;
|
50 | |
51 |
|
52 |
|
53 | static $resource: IStaticResourceConfig = {
|
54 | name: 'router-view',
|
55 | bindables: ['swapOrder', 'layoutView', 'layoutViewModel', 'layoutModel', 'inherit-binding-context'] as any
|
56 | };
|
57 |
|
58 | |
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 | swapOrder?: 'before' | 'after' | 'with';
|
68 |
|
69 | |
70 |
|
71 |
|
72 | layoutView?: any;
|
73 |
|
74 | |
75 |
|
76 |
|
77 |
|
78 | layoutViewModel?: any;
|
79 |
|
80 | |
81 |
|
82 |
|
83 | layoutModel?: any;
|
84 |
|
85 | |
86 |
|
87 |
|
88 | readonly element: Element;
|
89 |
|
90 | |
91 |
|
92 |
|
93 | readonly router: Router;
|
94 |
|
95 | |
96 |
|
97 |
|
98 | container: Container;
|
99 |
|
100 | |
101 |
|
102 |
|
103 |
|
104 | viewSlot: ViewSlot;
|
105 |
|
106 | |
107 |
|
108 |
|
109 |
|
110 | viewLocator: ViewLocator;
|
111 |
|
112 | |
113 |
|
114 |
|
115 |
|
116 | view: View;
|
117 |
|
118 | |
119 |
|
120 |
|
121 |
|
122 | owningView: View;
|
123 |
|
124 | |
125 |
|
126 |
|
127 |
|
128 | compositionTransaction: CompositionTransaction;
|
129 |
|
130 | |
131 |
|
132 |
|
133 |
|
134 | compositionEngine: CompositionEngine;
|
135 |
|
136 | |
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 | compositionTransactionNotifier: CompositionTransactionNotifier;
|
143 |
|
144 | |
145 |
|
146 |
|
147 | compositionTransactionOwnershipToken: CompositionTransactionOwnershipToken;
|
148 |
|
149 | |
150 |
|
151 |
|
152 | overrideContext: OverrideContext;
|
153 |
|
154 | constructor(
|
155 | element: Element,
|
156 | container: Container,
|
157 | viewSlot: ViewSlot,
|
158 | router: Router,
|
159 | viewLocator: ViewLocator,
|
160 | compositionTransaction: CompositionTransaction,
|
161 | compositionEngine: CompositionEngine
|
162 | ) {
|
163 | this.element = element;
|
164 | this.container = container;
|
165 | this.viewSlot = viewSlot;
|
166 | this.router = router;
|
167 | this.viewLocator = viewLocator;
|
168 | this.compositionTransaction = compositionTransaction;
|
169 | this.compositionEngine = compositionEngine;
|
170 |
|
171 |
|
172 |
|
173 | this.router.registerViewPort(this, this.element.getAttribute('name'));
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 | if (!('initialComposition' in compositionTransaction)) {
|
181 | compositionTransaction.initialComposition = true;
|
182 | this.compositionTransactionNotifier = compositionTransaction.enlist();
|
183 | }
|
184 | }
|
185 |
|
186 | created(owningView: View): void {
|
187 | this.owningView = owningView;
|
188 | }
|
189 |
|
190 | bind(bindingContext: any, overrideContext: OverrideContext): void {
|
191 |
|
192 |
|
193 | this.container.viewModel = bindingContext;
|
194 | this.overrideContext = overrideContext;
|
195 | }
|
196 |
|
197 | |
198 |
|
199 |
|
200 | process($viewPortInstruction: any, waitToSwap?: boolean): Promise<void> {
|
201 |
|
202 | const viewPortInstruction = $viewPortInstruction as IRouterViewViewPortInstruction;
|
203 | const component = viewPortInstruction.component;
|
204 | const childContainer = component.childContainer;
|
205 | const viewModel = component.viewModel;
|
206 | const viewModelResource = component.viewModelResource as unknown as ResourceDescription;
|
207 | const metadata = viewModelResource.metadata;
|
208 | const config = component.router.currentInstruction.config;
|
209 | const viewPortConfig = config.viewPorts ? (config.viewPorts[viewPortInstruction.name] || {}) : {};
|
210 |
|
211 | (childContainer.get(RouterViewLocator) as RouterViewLocator)._notify(this);
|
212 |
|
213 |
|
214 | const layoutInstruction = {
|
215 | viewModel: viewPortConfig.layoutViewModel || config.layoutViewModel || this.layoutViewModel,
|
216 | view: viewPortConfig.layoutView || config.layoutView || this.layoutView,
|
217 | model: viewPortConfig.layoutModel || config.layoutModel || this.layoutModel,
|
218 | router: viewPortInstruction.component.router,
|
219 | childContainer: childContainer,
|
220 | viewSlot: this.viewSlot
|
221 | };
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 | const viewStrategy = this.viewLocator.getViewStrategy(component.view || viewModel);
|
228 | if (viewStrategy && component.view) {
|
229 | viewStrategy.makeRelativeTo(Origin.get(component.router.container.viewModel.constructor).moduleId);
|
230 | }
|
231 |
|
232 |
|
233 | return metadata
|
234 | .load(childContainer, viewModelResource.value, null, viewStrategy, true)
|
235 |
|
236 |
|
237 | .then((viewFactory: ViewFactory | HtmlBehaviorResource) => {
|
238 |
|
239 |
|
240 |
|
241 |
|
242 | if (!this.compositionTransactionNotifier) {
|
243 | this.compositionTransactionOwnershipToken = this.compositionTransaction.tryCapture();
|
244 | }
|
245 |
|
246 | if (layoutInstruction.viewModel || layoutInstruction.view) {
|
247 | viewPortInstruction.layoutInstruction = layoutInstruction;
|
248 | }
|
249 |
|
250 | const viewPortComponentBehaviorInstruction = BehaviorInstruction.dynamic(
|
251 | this.element,
|
252 | viewModel,
|
253 | viewFactory as ViewFactory
|
254 | );
|
255 | viewPortInstruction.controller = metadata.create(childContainer, viewPortComponentBehaviorInstruction);
|
256 |
|
257 | if (waitToSwap) {
|
258 | return null;
|
259 | }
|
260 |
|
261 | this.swap(viewPortInstruction);
|
262 | });
|
263 | }
|
264 |
|
265 | swap($viewPortInstruction: any): void | Promise<void> {
|
266 |
|
267 | const viewPortInstruction: IRouterViewViewPortInstruction = $viewPortInstruction;
|
268 | const viewPortController = viewPortInstruction.controller;
|
269 | const layoutInstruction = viewPortInstruction.layoutInstruction;
|
270 | const previousView = this.view;
|
271 |
|
272 |
|
273 | const work = () => {
|
274 | const swapStrategy = SwapStrategies[this.swapOrder] || SwapStrategies.after;
|
275 | const viewSlot = this.viewSlot;
|
276 |
|
277 | swapStrategy(
|
278 | viewSlot,
|
279 | previousView,
|
280 | () => Promise.resolve(viewSlot.add(this.view))
|
281 | ).then(() => {
|
282 | this._notify();
|
283 | });
|
284 | };
|
285 |
|
286 |
|
287 | const ready = (owningView_or_layoutView: View) => {
|
288 | viewPortController.automate(this.overrideContext, owningView_or_layoutView);
|
289 | const transactionOwnerShipToken = this.compositionTransactionOwnershipToken;
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 | if (transactionOwnerShipToken) {
|
296 | return transactionOwnerShipToken
|
297 | .waitForCompositionComplete()
|
298 | .then(() => {
|
299 | this.compositionTransactionOwnershipToken = null;
|
300 | return work();
|
301 | });
|
302 | }
|
303 |
|
304 |
|
305 | return work();
|
306 | };
|
307 |
|
308 |
|
309 |
|
310 | if (layoutInstruction) {
|
311 | if (!layoutInstruction.viewModel) {
|
312 |
|
313 |
|
314 | layoutInstruction.viewModel = new EmptyLayoutViewModel();
|
315 | }
|
316 |
|
317 |
|
318 | return this.compositionEngine
|
319 |
|
320 |
|
321 |
|
322 | .createController(layoutInstruction as CompositionContext)
|
323 | .then((layoutController: Controller) => {
|
324 | const layoutView = layoutController.view;
|
325 | ShadowDOM.distributeView(viewPortController.view, layoutController.slots || layoutView.slots);
|
326 |
|
327 |
|
328 | layoutController.automate(createOverrideContext(layoutInstruction.viewModel), this.owningView);
|
329 | layoutView.children.push(viewPortController.view);
|
330 | return layoutView || layoutController;
|
331 | })
|
332 | .then((newView: View | Controller) => {
|
333 | this.view = newView as View;
|
334 | return ready(newView as View);
|
335 | });
|
336 | }
|
337 |
|
338 |
|
339 |
|
340 |
|
341 |
|
342 | this.view = viewPortController.view;
|
343 |
|
344 | return ready(this.owningView);
|
345 | }
|
346 |
|
347 | |
348 |
|
349 |
|
350 |
|
351 |
|
352 | _notify() {
|
353 | const notifier = this.compositionTransactionNotifier;
|
354 | if (notifier) {
|
355 | notifier.done();
|
356 | this.compositionTransactionNotifier = null;
|
357 | }
|
358 | }
|
359 | }
|
360 |
|
361 |
|
362 |
|
363 |
|
364 | export class RouterViewLocator {
|
365 |
|
366 |
|
367 | promise: Promise<any>;
|
368 |
|
369 |
|
370 | resolve: (val?: any) => void;
|
371 |
|
372 | |
373 |
|
374 |
|
375 | constructor() {
|
376 | this.promise = new Promise((resolve) => this.resolve = resolve);
|
377 | }
|
378 |
|
379 | |
380 |
|
381 |
|
382 |
|
383 | findNearest(): Promise<RouterView> {
|
384 | return this.promise;
|
385 | }
|
386 |
|
387 |
|
388 | _notify(routerView: RouterView): void {
|
389 | this.resolve(routerView);
|
390 | }
|
391 | }
|