garrettmills
463076d182
All checks were successful
continuous-integration/drone/push Build is passing
165 lines
5.4 KiB
TypeScript
165 lines
5.4 KiB
TypeScript
import {AppClass} from '../lifecycle/AppClass'
|
|
import {Config} from '../service/Config'
|
|
import {Container} from '../di'
|
|
import {ErrorWithContext, hasOwnProperty, Maybe, UniversalPath} from '../util'
|
|
import {Routing} from '../service/Routing'
|
|
import {RequestLocalStorage} from '../http/RequestLocalStorage'
|
|
import {Request} from '../http/lifecycle/Request'
|
|
|
|
/**
|
|
* Abstract base class for rendering views via different view engines.
|
|
*/
|
|
export abstract class ViewEngine extends AppClass {
|
|
protected readonly config: Config
|
|
|
|
protected readonly routing: Routing
|
|
|
|
protected readonly request: RequestLocalStorage
|
|
|
|
protected readonly debug: boolean
|
|
|
|
protected readonly namespaces: {[key: string]: UniversalPath} = {}
|
|
|
|
protected readonly globals: {[key: string]: (req: Maybe<Request>) => any} = {}
|
|
|
|
constructor() {
|
|
super()
|
|
this.config = Container.getContainer().make(Config)
|
|
this.routing = Container.getContainer().make(Routing)
|
|
this.request = Container.getContainer().make(RequestLocalStorage)
|
|
this.debug = (this.config.get('server.mode', 'production') === 'development'
|
|
|| this.config.get('server.debug', false))
|
|
}
|
|
|
|
/**
|
|
* Get the UniversalPath to the base directory where views are loaded from.
|
|
*/
|
|
public get path(): UniversalPath {
|
|
return this.app().appPath(...['resources', 'views']) // FIXME allow configuring
|
|
}
|
|
|
|
/**
|
|
* Given a template string and a set of variables for the view, render the string to HTML and return it.
|
|
* @param templateString
|
|
* @param locals
|
|
*/
|
|
public abstract renderString(templateString: string, locals: {[key: string]: any}): string | Promise<string>
|
|
|
|
/**
|
|
* Given the canonical name of a template file, render the file using the provided variables.
|
|
* @param templateName
|
|
* @param locals
|
|
*/
|
|
public abstract renderByName(templateName: string, locals: {[key: string]: any}): string | Promise<string>
|
|
|
|
/**
|
|
* Get the file extension of template files of this engine.
|
|
* @example `.pug`
|
|
*/
|
|
public abstract getFileExtension(): string
|
|
|
|
/**
|
|
* Get the global variables that should be passed to every view rendered.
|
|
* @protected
|
|
*/
|
|
protected getGlobals(): {[key: string]: any} {
|
|
const globals: {[key: string]: any} = {
|
|
app: this.app(),
|
|
config: (key: string, fallback?: any) => this.config.get(key, fallback),
|
|
asset: (...parts: string[]) => this.routing.getAssetPath(...parts).toRemote,
|
|
vendor: (namespace: string, ...parts: string[]) => this.routing.getVendorPath(namespace, ...parts).toRemote,
|
|
named: (name: string) => this.routing.getNamedPath(name).toRemote,
|
|
route: (...parts: string[]) => this.routing.getAppUrl().concat(...parts).toRemote,
|
|
hasRoute: (name: string) => this.routing.hasNamedRoute(name),
|
|
}
|
|
|
|
const req = this.request.get()
|
|
if ( req ) {
|
|
globals.request = () => req
|
|
}
|
|
|
|
for ( const key in this.globals ) {
|
|
if ( !hasOwnProperty(this.globals, key) ) {
|
|
continue
|
|
}
|
|
|
|
globals[key] = this.globals[key](req)
|
|
}
|
|
|
|
return globals
|
|
}
|
|
|
|
/**
|
|
* Register a new factory that produces a global available in the templates by default.
|
|
* @param name
|
|
* @param factory
|
|
*/
|
|
public registerGlobalFactory(name: string, factory: (req: Maybe<Request>) => any): this {
|
|
this.globals[name] = factory
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Register a path as a root for rendering views prefixed with the given namespace.
|
|
* @param namespace
|
|
* @param basePath
|
|
*/
|
|
public registerNamespace(namespace: string, basePath: UniversalPath): this {
|
|
if ( namespace.startsWith('@') ) {
|
|
namespace = namespace.substr(1)
|
|
}
|
|
|
|
this.namespaces[namespace] = basePath
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Given the name of a template, get a UniversalPath pointing to its file.
|
|
* @param templateName
|
|
*/
|
|
public resolveName(templateName: string): UniversalPath {
|
|
let path = this.path
|
|
if ( templateName.startsWith('@') ) {
|
|
const [namespace, ...parts] = templateName.split(':')
|
|
path = this.namespaces[namespace.substr(1)]
|
|
|
|
if ( !path ) {
|
|
throw new ErrorWithContext('Invalid template namespace: ' + namespace, {
|
|
namespace,
|
|
templateName,
|
|
})
|
|
}
|
|
|
|
templateName = parts.join(':')
|
|
}
|
|
|
|
if ( !templateName.endsWith(this.getFileExtension()) ) {
|
|
templateName += this.getFileExtension()
|
|
}
|
|
|
|
return path.concat(...templateName.split(':'))
|
|
}
|
|
|
|
/**
|
|
* Given the name of a template, get a UniversalPath to the root of the tree where
|
|
* that template resides.
|
|
* @param templateName
|
|
*/
|
|
public resolveBasePath(templateName: string): UniversalPath {
|
|
let path = this.path
|
|
if ( templateName.startsWith('@') ) {
|
|
const [namespace] = templateName.split(':')
|
|
path = this.namespaces[namespace.substr(1)]
|
|
|
|
if ( !path ) {
|
|
throw new ErrorWithContext('Invalid template namespace: ' + namespace, {
|
|
namespace,
|
|
templateName,
|
|
})
|
|
}
|
|
}
|
|
|
|
return path
|
|
}
|
|
}
|