Last active
March 9, 2017 17:47
-
-
Save Vaccano/293703df43cc0bb045cecffe7cef51e0 to your computer and use it in GitHub Desktop.
Aurelia Cached Routing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {autoinject} from 'aurelia-dependency-injection'; | |
import {CompositionEngine, CompositionContext, ViewEngine} from 'aurelia-templating'; | |
import {RouteLoader, Router} from 'aurelia-router'; | |
import {relativeToFile} from 'aurelia-path'; | |
import {Origin} from 'aurelia-metadata'; | |
import {ViewModelCache} from '../../../src/cache/screen-cache/view-model-cache'; | |
import {ViewCache} from '../../../src/cache/screen-cache/view-cache'; | |
@autoinject | |
export class CachedRouteLoader extends RouteLoader { | |
constructor(compositionEngine: CompositionEngine, viewModelCache: ViewModelCache, viewEngine: ViewEngine, viewCache: ViewCache) { | |
super(); | |
this.compositionEngine = compositionEngine; | |
this.viewModelCache = viewModelCache; | |
this.viewEngine = viewEngine; | |
this.viewCache = viewCache; | |
} | |
private compositionEngine: CompositionEngine; | |
private viewModelCache: ViewModelCache; | |
private viewEngine: ViewEngine; | |
private viewCache: ViewCache; | |
loadRoute(router, config, navigationInstruction) { | |
let childContainer = router.container.createChild(); | |
let instruction = { | |
viewModel: relativeToFile(config.moduleId, Origin.get(router.container.viewModel.constructor).moduleId), | |
childContainer: childContainer, | |
view: config.view || config.viewStrategy, | |
router: router, | |
container: undefined, | |
bindingContext: undefined, | |
viewResources: undefined, | |
viewSlot: undefined | |
}; | |
childContainer.getChildRouter = function () { | |
let childRouter; | |
childContainer.registerHandler(Router, c => { | |
return childRouter || (childRouter = router.createChild(childContainer)); | |
}); | |
return childContainer.get(Router); | |
}; | |
return this.viewEngine.importViewModelResource(instruction.viewModel, undefined).then(viewModelResource => { | |
childContainer.autoRegister(viewModelResource["value"]); | |
let viewModel = null; | |
let cacheName = config.moduleId; //+ ";" + navigationInstruction.fragment; | |
viewModel = this.viewModelCache.getViewModel(cacheName); | |
//viewmodel not found in cache, let aurelia create one | |
if (!viewModel) | |
viewModel = childContainer.get(viewModelResource["value"]); | |
//cache if needed | |
this.viewModelCache.setViewModel(cacheName, viewModel); | |
instruction.viewModel = childContainer.viewModel = viewModel; | |
instruction["viewModelResource"] = viewModelResource; | |
return instruction; | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Container, inject, autoinject } from 'aurelia-framework'; | |
import {ViewSlot, ViewLocator, customElement, noView, BehaviorInstruction, bindable, ViewFactory, ViewResources, View, ViewCreateInstruction} from 'aurelia-templating'; | |
import {Router} from 'aurelia-router'; | |
import {Origin} from 'aurelia-metadata'; | |
import {DOM} from 'aurelia-pal'; | |
import {ViewCache} from '../../../src/cache/screen-cache/view-cache'; | |
import {SwapStrategies} from '../../../src/cache/screen-cache/swap-strategies'; | |
import {CachedViewFactory} from '../../../src/cache/screen-cache/cached-view-factory'; | |
const swapStrategies = new SwapStrategies(); | |
@customElement('cached-router-view') | |
@noView | |
@inject(DOM.Element, Container, ViewSlot, Router, ViewLocator, ViewCache, CachedViewFactory) | |
export class CachedRouterView { | |
@bindable swapOrder; | |
public owningView: any; | |
public overrideContext: any; | |
public view: any; | |
constructor(public element, public container, public viewSlot, public router, public viewLocator, private viewCache, private cacheViewFactory) { | |
this.router.registerViewPort(this, this.element.getAttribute('name')); | |
} | |
created(owningView) { | |
this.owningView = owningView; | |
} | |
bind(bindingContext, overrideContext) { | |
this.container.viewModel = bindingContext; | |
this.overrideContext = overrideContext; | |
} | |
process(viewPortInstruction, waitToSwap) { | |
let component = viewPortInstruction.component; | |
let childContainer = component.childContainer; | |
let viewModel = component.viewModel; | |
let viewModelResource = component.viewModelResource; | |
let metadata = viewModelResource.metadata; | |
let viewStrategy = this.viewLocator.getViewStrategy(component.view || viewModel); | |
if (viewStrategy) { | |
viewStrategy.makeRelativeTo(Origin.get(component.router.container.viewModel.constructor).moduleId); | |
} | |
return metadata.load(childContainer, viewModelResource.value, null, viewStrategy, true).then(viewFactory => { | |
//set cache name | |
let cacheName: string = viewPortInstruction.moduleId; | |
//if (!this.includes(cacheName, '/index')) { | |
// cacheName = cacheName + ';' + viewPortInstruction.lifecycleArgs[2].fragment; | |
//} | |
cacheName = 'view;' + cacheName; | |
//swap view factory with custom one | |
Object.assign(this.cacheViewFactory, viewFactory); | |
let ins = BehaviorInstruction.dynamic( | |
this.element, | |
viewModel, | |
this.cacheViewFactory | |
); | |
ins['cachedName'] = cacheName; | |
// Save off if the the view is cached already or not. | |
let isViewCached = this.viewCache.viewExists(cacheName); | |
// This will create or load the view using our custom cached-view-factory. | |
viewPortInstruction.controller = metadata.create(childContainer, ins); | |
// Make sure that we only call the lifecycle methods when we are really ready. | |
let controller = viewPortInstruction.controller; | |
this.disableStartupMethodsForController(controller, !isViewCached); | |
this.disableStartupMethodsForControllerChildern(controller, !isViewCached); | |
if (waitToSwap) { | |
return; | |
} | |
this.swap(viewPortInstruction); | |
}); | |
} | |
// Disables all the startup methods for a controller's childern. | |
disableStartupMethodsForControllerChildern(controller: any, isFirstRun: boolean) { | |
if (controller.view) { | |
let customControllers = this.getCustomControllersForView(controller.view); | |
for (let customController of customControllers) { | |
this.disableStartupMethodsForController(customController, isFirstRun); | |
// If this customController has a view, then we need to check it's children too | |
if (customController.view) | |
this.disableStartupMethodsForControllerChildern(customController, isFirstRun); | |
} | |
} | |
} | |
disableStartupMethodsForController(controller: any, isFirstRun: boolean) { | |
if (controller.behavior) { | |
// If this is the first run, then save off the inital values and tell Aurelia that we don't | |
// want detached and unbind callbacks. | |
if (isFirstRun) { | |
controller.behavior.handlesUnbindBackup = controller.behavior.handlesUnbind; | |
controller.behavior.handlesDetachedBackup = controller.behavior.handlesDetached; | |
controller.behavior.handlesBindBackup = controller.behavior.handlesBind; | |
controller.behavior.handlesAttachedBackup = controller.behavior.handlesAttached; | |
controller.behavior.handlesUnbind = false; | |
controller.behavior.handlesDetached = false; | |
} | |
// Once we have run once, we don't want to bind or attached again. | |
// The idea is that if we did, it would always be a new instance (so it would be a first run). | |
else { | |
controller.behavior.handlesBind = false; | |
controller.behavior.handlesAttached = false; | |
} | |
} | |
} | |
getCustomControllersForView(view: any): any[] { | |
let elementNames: string[] = Object.keys(view.resources.elements) | |
.map(key => { | |
let el = view.resources.elements[key]; | |
if (el && el.target) | |
return el.target.name; | |
return null; | |
}); | |
let attributeNames: string[] = Object.keys(view.resources.attributes) | |
.map(key => { | |
let attr = view.resources.attributes[key]; | |
if (attr && attr.target) | |
return attr.target.name; | |
return null; | |
}); | |
let customControllers = view.controllers.filter(controller => { | |
if (controller && controller.viewModel && controller.viewModel.constructor) { | |
let viewModelName = controller.viewModel.constructor.name; | |
// If the view model name is a match then this is our code and we need to account for it. | |
if (elementNames.indexOf(viewModelName) !== -1) { | |
return true; | |
} | |
else if (attributeNames.indexOf(viewModelName) !== -1) { | |
return true; | |
} | |
} | |
return false; | |
}); | |
return customControllers; | |
} | |
swap(viewPortInstruction) { | |
let previousView = this.view; | |
let viewSlot = this.viewSlot; | |
let swapStrategy = this.swapOrder in swapStrategies | |
? swapStrategies[this.swapOrder] | |
: swapStrategies.after; | |
swapStrategy(viewSlot, previousView, () => { | |
viewPortInstruction.controller.automate(this.overrideContext, this.owningView); | |
return viewSlot.add(viewPortInstruction.controller.view); | |
}); | |
this.view = viewPortInstruction.controller.view; | |
} | |
includes(source: string, search: string, start: number = 0): boolean { | |
'use strict'; | |
if (typeof start !== 'number') { | |
start = 0; | |
} | |
if (start + search.length > source.length) { | |
return false; | |
} else { | |
return source.indexOf(search, start) !== -1; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment