Skip to content

Instantly share code, notes, and snippets.

@Vaccano
Last active March 9, 2017 17:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Vaccano/293703df43cc0bb045cecffe7cef51e0 to your computer and use it in GitHub Desktop.
Save Vaccano/293703df43cc0bb045cecffe7cef51e0 to your computer and use it in GitHub Desktop.
Aurelia Cached Routing
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;
});
}
}
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