@danielkalen/simplybind
Version:
Magically simple, framework-less one-way/two-way data binding for frontend/backend in ~5kb.
256 lines (227 loc) • 10.3 kB
JavaScript
import {Origin, protocol} from 'aurelia-metadata';
import {relativeToFile} from 'aurelia-path';
import {TemplateRegistryEntry} from 'aurelia-loader';
import {ViewEngine} from './view-engine';
import {ResourceLoadContext, ViewCompileInstruction} from './instructions';
import {DOM, PLATFORM} from 'aurelia-pal';
/**
* Implemented by classes that describe how a view factory should be loaded.
*/
interface ViewStrategy {
/**
* Loads a view factory.
* @param viewEngine The view engine to use during the load process.
* @param compileInstruction Additional instructions to use during compilation of the view.
* @param loadContext The loading context used for loading all resources and dependencies.
* @param target A class from which to extract metadata of additional resources to load.
* @return A promise for the view factory that is produced by this strategy.
*/
loadViewFactory(viewEngine: ViewEngine, compileInstruction: ViewCompileInstruction, loadContext?: ResourceLoadContext, target?: any): Promise<ViewFactory>;
}
/**
* Decorator: Indicates that the decorated class/object is a view strategy.
*/
export const viewStrategy: Function = protocol.create('aurelia:view-strategy', {
validate(target) {
if (!(typeof target.loadViewFactory === 'function')) {
return 'View strategies must implement: loadViewFactory(viewEngine: ViewEngine, compileInstruction: ViewCompileInstruction, loadContext?: ResourceLoadContext): Promise<ViewFactory>';
}
return true;
},
compose(target) {
if (!(typeof target.makeRelativeTo === 'function')) {
target.makeRelativeTo = PLATFORM.noop;
}
}
});
/**
* A view strategy that loads a view relative to its associated view-model.
*/
export class RelativeViewStrategy {
/**
* Creates an instance of RelativeViewStrategy.
* @param path The relative path to the view.
*/
constructor(path: string) {
this.path = path;
this.absolutePath = null;
}
/**
* Loads a view factory.
* @param viewEngine The view engine to use during the load process.
* @param compileInstruction Additional instructions to use during compilation of the view.
* @param loadContext The loading context used for loading all resources and dependencies.
* @param target A class from which to extract metadata of additional resources to load.
* @return A promise for the view factory that is produced by this strategy.
*/
loadViewFactory(viewEngine: ViewEngine, compileInstruction: ViewCompileInstruction, loadContext?: ResourceLoadContext, target?: any): Promise<ViewFactory> {
if (this.absolutePath === null && this.moduleId) {
this.absolutePath = relativeToFile(this.path, this.moduleId);
}
compileInstruction.associatedModuleId = this.moduleId;
return viewEngine.loadViewFactory(this.absolutePath || this.path, compileInstruction, loadContext, target);
}
/**
* Makes the view loaded by this strategy relative to the provided file path.
* @param file The path to load the view relative to.
*/
makeRelativeTo(file: string): void {
if (this.absolutePath === null) {
this.absolutePath = relativeToFile(this.path, file);
}
}
}
/**
* A view strategy based on naming conventions.
*/
export class ConventionalViewStrategy {
/**
* Creates an instance of ConventionalViewStrategy.
* @param viewLocator The view locator service for conventionally locating the view.
* @param origin The origin of the view model to conventionally load the view for.
*/
constructor(viewLocator: ViewLocator, origin: Origin) {
this.moduleId = origin.moduleId;
this.viewUrl = viewLocator.convertOriginToViewUrl(origin);
}
/**
* Loads a view factory.
* @param viewEngine The view engine to use during the load process.
* @param compileInstruction Additional instructions to use during compilation of the view.
* @param loadContext The loading context used for loading all resources and dependencies.
* @param target A class from which to extract metadata of additional resources to load.
* @return A promise for the view factory that is produced by this strategy.
*/
loadViewFactory(viewEngine: ViewEngine, compileInstruction: ViewCompileInstruction, loadContext?: ResourceLoadContext, target?: any): Promise<ViewFactory> {
compileInstruction.associatedModuleId = this.moduleId;
return viewEngine.loadViewFactory(this.viewUrl, compileInstruction, loadContext, target);
}
}
/**
* A view strategy that indicates that the component has no view that the templating engine needs to manage.
* Typically used when the component author wishes to take over fine-grained rendering control.
*/
export class NoViewStrategy {
/**
* Creates an instance of NoViewStrategy.
* @param dependencies A list of view resource dependencies of this view.
* @param dependencyBaseUrl The base url for the view dependencies.
*/
constructor(dependencies?: Array<string|Function|Object>, dependencyBaseUrl?: string) {
this.dependencies = dependencies || null;
this.dependencyBaseUrl = dependencyBaseUrl || '';
}
/**
* Loads a view factory.
* @param viewEngine The view engine to use during the load process.
* @param compileInstruction Additional instructions to use during compilation of the view.
* @param loadContext The loading context used for loading all resources and dependencies.
* @param target A class from which to extract metadata of additional resources to load.
* @return A promise for the view factory that is produced by this strategy.
*/
loadViewFactory(viewEngine: ViewEngine, compileInstruction: ViewCompileInstruction, loadContext?: ResourceLoadContext, target?: any): Promise<ViewFactory> {
let entry = this.entry;
let dependencies = this.dependencies;
if (entry && entry.factoryIsReady) {
return Promise.resolve(null);
}
this.entry = entry = new TemplateRegistryEntry(this.moduleId || this.dependencyBaseUrl);
// since we're not invoking the TemplateRegistryEntry template setter
// we need to create the dependencies Array manually and set it as loaded:
entry.dependencies = [];
entry.templateIsLoaded = true;
if (dependencies !== null) {
for (let i = 0, ii = dependencies.length; i < ii; ++i) {
let current = dependencies[i];
if (typeof current === 'string' || typeof current === 'function') {
entry.addDependency(current);
} else {
entry.addDependency(current.from, current.as);
}
}
}
compileInstruction.associatedModuleId = this.moduleId;
// loadViewFactory will resolve as 'null' because entry template is not set:
return viewEngine.loadViewFactory(entry, compileInstruction, loadContext, target);
}
}
/**
* A view strategy created directly from the template registry entry.
*/
export class TemplateRegistryViewStrategy {
/**
* Creates an instance of TemplateRegistryViewStrategy.
* @param moduleId The associated moduleId of the view to be loaded.
* @param entry The template registry entry used in loading the view factory.
*/
constructor(moduleId: string, entry: TemplateRegistryEntry) {
this.moduleId = moduleId;
this.entry = entry;
}
/**
* Loads a view factory.
* @param viewEngine The view engine to use during the load process.
* @param compileInstruction Additional instructions to use during compilation of the view.
* @param loadContext The loading context used for loading all resources and dependencies.
* @param target A class from which to extract metadata of additional resources to load.
* @return A promise for the view factory that is produced by this strategy.
*/
loadViewFactory(viewEngine: ViewEngine, compileInstruction: ViewCompileInstruction, loadContext?: ResourceLoadContext, target?: any): Promise<ViewFactory> {
let entry = this.entry;
if (entry.factoryIsReady) {
return Promise.resolve(entry.factory);
}
compileInstruction.associatedModuleId = this.moduleId;
return viewEngine.loadViewFactory(entry, compileInstruction, loadContext, target);
}
}
/**
* A view strategy that allows the component authore to inline the html for the view.
*/
export class InlineViewStrategy {
/**
* Creates an instance of InlineViewStrategy.
* @param markup The markup for the view. Be sure to include the wrapping template tag.
* @param dependencies A list of view resource dependencies of this view.
* @param dependencyBaseUrl The base url for the view dependencies.
*/
constructor(markup: string, dependencies?: Array<string|Function|Object>, dependencyBaseUrl?: string) {
this.markup = markup;
this.dependencies = dependencies || null;
this.dependencyBaseUrl = dependencyBaseUrl || '';
}
/**
* Loads a view factory.
* @param viewEngine The view engine to use during the load process.
* @param compileInstruction Additional instructions to use during compilation of the view.
* @param loadContext The loading context used for loading all resources and dependencies.
* @param target A class from which to extract metadata of additional resources to load.
* @return A promise for the view factory that is produced by this strategy.
*/
loadViewFactory(viewEngine: ViewEngine, compileInstruction: ViewCompileInstruction, loadContext?: ResourceLoadContext, target?: any): Promise<ViewFactory> {
let entry = this.entry;
let dependencies = this.dependencies;
if (entry && entry.factoryIsReady) {
return Promise.resolve(entry.factory);
}
this.entry = entry = new TemplateRegistryEntry(this.moduleId || this.dependencyBaseUrl);
entry.template = DOM.createTemplateFromMarkup(this.markup);
if (dependencies !== null) {
for (let i = 0, ii = dependencies.length; i < ii; ++i) {
let current = dependencies[i];
if (typeof current === 'string' || typeof current === 'function') {
entry.addDependency(current);
} else {
entry.addDependency(current.from, current.as);
}
}
}
compileInstruction.associatedModuleId = this.moduleId;
return viewEngine.loadViewFactory(entry, compileInstruction, loadContext, target);
}
}