Add view engine unit - handlebars - and response helpers

This commit is contained in:
garrettmills 2020-08-07 09:51:25 -05:00
parent 3e4f8f00f2
commit b5bde7d077
No known key found for this signature in database
GPG Key ID: 6ACD58D6ADACFC6E
14 changed files with 165 additions and 2 deletions

View File

@ -8,3 +8,4 @@ export { default as ModelsUnit } from '../../orm/src/ModelsUnit.ts'
export { default as HttpServerUnit } from '../../lib/src/unit/HttpServer.ts' export { default as HttpServerUnit } from '../../lib/src/unit/HttpServer.ts'
export { default as RoutingUnit } from '../../lib/src/unit/Routing.ts' export { default as RoutingUnit } from '../../lib/src/unit/Routing.ts'
export { default as ServicesUnit } from '../../lib/src/unit/Services.ts' export { default as ServicesUnit } from '../../lib/src/unit/Services.ts'
export { default as ViewEngineUnit } from '../../lib/src/unit/ViewEngine.ts'

View File

@ -0,0 +1,11 @@
import Controller from '../../../lib/src/http/Controller.ts'
import {Request} from '../../../lib/src/http/Request.ts'
import {view} from '../../../lib/src/http/response/helpers.ts'
export default class HomeController extends Controller {
get_home(request: Request) {
return view('home', { request })
}
}

View File

@ -0,0 +1,9 @@
import { RouterDefinition } from '../../../lib/src/http/type/RouterDefinition.ts'
export default {
prefix: '/',
middleware: [],
get: {
'/': 'controller::Home.get_home',
},
} as RouterDefinition

1
app/http/views/home.hbs Normal file
View File

@ -0,0 +1 @@
<h1>Welcome to Daton!</h1>

View File

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
{{#if title}}
<title>{{ title }} | Daton</title>
{{else}}
<title>Daton</title>
{{/if}}
</head>
<body>
{{{ body }}}
</body>
</html>

View File

View File

@ -9,6 +9,7 @@ import {
HttpServerUnit, HttpServerUnit,
RoutingUnit, RoutingUnit,
ServicesUnit, ServicesUnit,
ViewEngineUnit,
} from './bundle/daton_units.ts' } from './bundle/daton_units.ts'
export default [ export default [
@ -19,6 +20,7 @@ export default [
HttpKernelUnit, HttpKernelUnit,
MiddlewareUnit, MiddlewareUnit,
ControllerUnit, ControllerUnit,
ViewEngineUnit,
RoutesUnit, RoutesUnit,
RoutingUnit, RoutingUnit,
HttpServerUnit, HttpServerUnit,

View File

@ -1,2 +1,3 @@
export * from 'https://deno.land/std@0.53.0/http/server.ts' export * from 'https://deno.land/std@0.53.0/http/server.ts'
export * from 'https://deno.land/std@0.53.0/http/cookie.ts' export * from 'https://deno.land/std@0.53.0/http/cookie.ts'
export { Handlebars } from 'https://deno.land/x/handlebars/mod.ts'

View File

@ -0,0 +1,18 @@
import ResponseFactory from './ResponseFactory.ts'
import ViewEngine from '../../unit/ViewEngine.ts'
import {Request} from '../Request.ts'
export default class PartialViewResponseFactory extends ResponseFactory {
constructor(
public readonly view: string,
public readonly context?: any,
) {
super()
}
public async write(request: Request) {
const views: ViewEngine = this.make(ViewEngine)
request.response.body = await views.partial(this.view, this.context)
return request
}
}

View File

@ -0,0 +1,19 @@
import ResponseFactory from './ResponseFactory.ts'
import ViewEngine from '../../unit/ViewEngine.ts'
import {Request} from '../Request.ts'
export default class ViewResponseFactory extends ResponseFactory {
constructor(
public readonly view: string,
public readonly context?: any,
public readonly layout?: string,
) {
super()
}
public async write(request: Request) {
const views: ViewEngine = this.make(ViewEngine)
request.response.body = await views.render(this.view, this.context, this.layout)
return request
}
}

View File

@ -8,6 +8,8 @@ import TemporaryRedirectResponseFactory from './TemporaryRedirectResponseFactory
import {HTTPStatus} from '../../const/http.ts' import {HTTPStatus} from '../../const/http.ts'
import HTTPErrorResponseFactory from './HTTPErrorResponseFactory.ts' import HTTPErrorResponseFactory from './HTTPErrorResponseFactory.ts'
import HTTPError from '../../error/HTTPError.ts' import HTTPError from '../../error/HTTPError.ts'
import ViewResponseFactory from './ViewResponseFactory.ts'
import PartialViewResponseFactory from './PartialViewResponseFactory.ts'
export function json(value: any): JSONResponseFactory { export function json(value: any): JSONResponseFactory {
return make(JSONResponseFactory, value) return make(JSONResponseFactory, value)
@ -33,3 +35,11 @@ export function redirect(destination: string): TemporaryRedirectResponseFactory
export function http(status: HTTPStatus, message?: string): HTTPErrorResponseFactory { export function http(status: HTTPStatus, message?: string): HTTPErrorResponseFactory {
return make(HTTPErrorResponseFactory, new HTTPError(status, message)) return make(HTTPErrorResponseFactory, new HTTPError(status, message))
} }
export function view(view: string, context?: any, layout?: string): ViewResponseFactory {
return make(ViewResponseFactory, view, context, layout)
}
export function partial(view: string, context?: any): PartialViewResponseFactory {
return make(PartialViewResponseFactory, view, context)
}

View File

@ -5,8 +5,9 @@ import {container, make} from '../../../di/src/global.ts'
import {DependencyKey} from '../../../di/src/type/DependencyKey.ts' import {DependencyKey} from '../../../di/src/type/DependencyKey.ts'
import RunLevelErrorHandler from '../error/RunLevelErrorHandler.ts' import RunLevelErrorHandler from '../error/RunLevelErrorHandler.ts'
import {Status} from '../const/status.ts' import {Status} from '../const/status.ts'
import Instantiable from "../../../di/src/type/Instantiable.ts"; import Instantiable from '../../../di/src/type/Instantiable.ts'
import {Collection} from "../collection/Collection.ts"; import {Collection} from '../collection/Collection.ts'
import {path} from '../external/std.ts'
@Service() @Service()
export default class Application { export default class Application {
@ -66,4 +67,20 @@ export default class Application {
this.logger.verbose(e) this.logger.verbose(e)
} }
} }
get root() {
return path.resolve('.')
}
get app_root() {
return path.resolve('./app')
}
path(...parts: string[]) {
return path.resolve(this.root, ...parts)
}
app_path(...parts: string[]) {
return path.resolve(this.app_root, ...parts)
}
} }

View File

@ -0,0 +1,60 @@
import LifecycleUnit from '../lifecycle/Unit.ts'
import {Unit} from '../lifecycle/decorators.ts'
import {Handlebars} from '../external/http.ts'
import {Logging} from '../service/logging/Logging.ts'
import {fs} from '../external/std.ts'
@Unit()
export default class ViewEngine extends LifecycleUnit {
protected _handlebars!: Handlebars
// TODO include basic app info in view data
constructor(
protected readonly logger: Logging,
) {
super()
}
async up() {
this.logger.info(`Setting views base dir: ${this.app.app_path('http', 'views')}`)
this._handlebars = new Handlebars({
baseDir: this.app.app_path('http', 'views'),
extname: '.hbs',
layoutsDir: 'layouts',
partialsDir: 'partials',
defaultLayout: 'main',
helpers: undefined,
compilerOptions: undefined,
})
const main_layout_path = this.app.app_path('http', 'views', 'layouts', 'main.hbs')
if ( !(await fs.exists(main_layout_path)) ) {
this.logger.warn(`Unable to open main view layout file: ${main_layout_path}`)
this.logger.warn(`Unless you are using a custom layout, this could cause errors.`)
}
const partials_path = this.app.app_path('http', 'views', 'partials')
if ( !(await fs.exists(partials_path)) ) {
this.logger.warn(`Unable to open view partials directory: ${partials_path}`)
this.logger.warn(`This directory must exist for the view engine to function, even if it is empty.`)
}
}
get handlebars(): Handlebars {
return this._handlebars
}
async render(view: string, args?: any, layout?: string): Promise<string> {
this.logger.debug(`Rendering view: ${view}`)
return this.handlebars.renderView(view, args, layout)
}
async partial(view: string, args?: any) {
const parts = `${view}.hbs`.split(':')
const resolved = this.app.app_path('http', 'views', ...parts)
this.logger.debug(`Rendering partial: ${view} from ${resolved}`)
return this.handlebars.render(resolved, args)
}
}