Skip to content

Instantly share code, notes, and snippets.

@Vaccano
Created July 28, 2016 22:56
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/1e862b9318f4f0a9a8e1176ff4fb727e to your computer and use it in GitHub Desktop.
Save Vaccano/1e862b9318f4f0a9a8e1176ff4fb727e to your computer and use it in GitHub Desktop.
Screen Caching
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;
//some cache name
let cacheName = config.moduleId + ";" + navigationInstruction.fragment;
//cache magic
viewModel = this.viewModelCache.getViewModel(cacheName);
if (viewModel) {
//if there is a viewmodel in cache, do not activate it
viewModel.activate = () => {
return true;
}
}
//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;
}
//swap view factory with custom one
Object.assign(this.cacheViewFactory, viewFactory);
let ins = BehaviorInstruction.dynamic(
this.element,
viewModel,
this.cacheViewFactory
);
ins["cachedName"] = "view;" + cacheName;
viewPortInstruction.controller = metadata.create(childContainer, ins);
if (waitToSwap) {
return;
}
this.swap(viewPortInstruction);
});
}
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;
}
}
}
import {Container, autoinject } from 'aurelia-framework';
import {ViewFactory, ViewResources, ViewCreateInstruction, View} from 'aurelia-templating';
import {ViewCache} from '../../../src/cache/screen-cache/view-cache';
export class CacheViewCreateInstruction implements ViewCreateInstruction {
public cachedName: string;
}
@autoinject
export class CachedViewFactory extends ViewFactory {
constructor(template: DocumentFragment, instructions: Object, resources: ViewResources, private viewCache: ViewCache) {
super(template, instructions, resources);
}
public create(container: Container, createInstruction?: CacheViewCreateInstruction, element?: Element): View {
let view;
if (createInstruction) {
//try and find view in cache
view = this.viewCache.getView(createInstruction.cachedName);
}
if (view) {
//return cached view
return view;
}
//create new view if not found in cache
view = super.create(container, createInstruction, element);
if (createInstruction) {
//add to cache
this.viewCache.setView(createInstruction.cachedName, view);
}
return view;
}
}
import {CachedRouteLoader} from '../src/cache/screen-cache/cached-route-loader';
import {RouteLoader} from 'aurelia-router';
import {Aurelia} from 'aurelia-framework';
// Other Imports needed
bootstrap((aurelia: Aurelia): void => {
aurelia.use
.standardConfiguration()
.developmentLogging());
// This is the relevant part. This replaces the loader with the cached loader.
aurelia.container.registerSingleton(RouteLoader, CachedRouteLoader);
// Other stuff for file.
export class SwapStrategies {
// animate the next view in before removing the current view;
before(viewSlot, previousView, callback) {
let promise = Promise.resolve(callback());
if (previousView !== undefined) {
return promise.then(() => viewSlot.remove(previousView, true));
}
return promise;
}
// animate the next view at the same time the current view is removed
with(viewSlot, previousView, callback) {
let promise = Promise.resolve(callback());
if (previousView !== undefined) {
return Promise.all([viewSlot.remove(previousView, true), promise]);
}
return promise;
}
// animate the next view in after the current view has been removed
after(viewSlot, previousView, callback) {
return Promise.resolve(viewSlot.removeAll(true)).then(callback);
}
}
import {IDictionary} from '../../../src/common/interfaces/dictionary-interface'
export class ViewCache {
views: IDictionary<any> = {};
getView(resourceKey: string): any {
return this.views[resourceKey];
}
setView(resourceKey: string, resource: any) {
this.views[resourceKey] = resource;
}
}
import {IDictionary} from '../../../src/common/interfaces/dictionary-interface'
export class ViewModelCache {
viewModels: IDictionary<any> = {};
getViewModel(resourceKey: string): any {
return this.viewModels[resourceKey];
}
setViewModel(resourceKey: string, resource: any) {
this.viewModels[resourceKey] = resource;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment