UNPKG

neft

Version:

Universal Platform

424 lines (340 loc) 13.5 kB
# Route 'use strict' utils = require 'src/utils' assert = require 'src/assert' log = require 'src/log' Schema = require 'src/schema' Networking = require 'src/networking' Document = require 'src/document' Renderer = require 'src/renderer' Dict = require 'src/dict' log = log.scope 'App', 'Route' module.exports = (app) -> class Route if utils.isNode usedTemplates = [] else templates = Object.create null lastClientHTMLRoute = null ## *Document* Route.getTemplateView(*String* viewName) @getTemplateView = do -> if utils.isNode (name) -> scope = app: app, routes: new Dict tmpl = app.views[name].render null, scope usedTemplates.push tmpl tmpl else (name) -> scope = app: app, routes: new Dict templates[name] ?= app.views[name].render null, scope ## Route::constructor(*Object* options) Access it with: ```javascript module.exports = function(app) { var Route = app.Route; }; ``` Acceptable syntaxes: ```javascript *Route* app.Route(*String* method, *String* uri, *Object* options) *Route* app.Route(*String* methodWithUri, *Function* getData) *Route* app.Route(*String* methodWithUri, *Object* options) *Route* app.Route(*String* uri, *Function* getData) *Route* app.Route(*String* uri, *Object* options) *Route* app.Route(*String* method, *String* uri) *Route* app.Route(*String* uri) *Route* app.Route(*String* methodWithUri) ``` constructor: (method, uri, opts) -> if utils.isObject(method) # opts opts = method else if utils.isObject(uri) opts = uri else unless utils.isObject(opts) opts = {} if typeof method is 'string' and typeof uri isnt 'string' opts.uri = method else if typeof method is 'string' and typeof uri is 'string' opts.method ?= method opts.uri ?= uri if typeof uri is 'function' opts.getData ?= uri assert.isObject opts opts = utils.clone opts if typeof opts.uri is 'string' # support methodWithUri e.g. 'get /home' spaceIndex = opts.uri.indexOf ' ' if spaceIndex isnt -1 opts.method ?= opts.uri.slice 0, spaceIndex opts.uri = opts.uri.slice spaceIndex+1 opts.uri = new Networking.Uri opts.uri assert.instanceOf opts.uri, Networking.Uri opts.method ?= 'get' assert.isString opts.method opts.method = opts.method.toLowerCase() assert.ok utils.has(Networking.Request.METHODS, opts.method) , "Networking doesn't provide a `#{opts.method}` method" if opts.schema? if utils.isPlainObject(opts.schema) opts.schema = new Schema opts.schema assert.instanceOf opts.schema, Schema if opts.redirect? if typeof opts.redirect is 'string' opts.redirect = new Networking.Uri opts.redirect else assert.isFunction opts.redirect if utils.isObject(opts.toHTML) opts.toHTML = createToHTMLFromObject opts.toHTML for key, val of opts @[key] = val @__id__ = utils.uid() @app = app @name ||= getRouteName(@) app.networking.createHandler method: @method uri: @uri schema: @schema callback: utils.bindFunctionContext(handleRequest, @) getRouteName = (route) -> assert.instanceOf route, Route uri = route.uri._uri uri = uri.replace Networking.Uri.NAMES_RE, '' uri = uri.replace /\*/g, '' while uri.indexOf('//') isnt -1 uri = uri.replace /\/\//g, '/' uri = uri.replace /^\//, '' uri = uri.replace /\/$/, '' uri routesCache = Object.create null pendingRoutes = Object.create null factoryRoute = do -> createInstance = (route) -> r = Object.create route r.__hash__ = utils.uid() r.factory?() r (route) -> assert.instanceOf route, Route id = route.__id__ routesCache[id] ?= [] r = routesCache[id].pop() or createInstance(route) r = Object.create r r.request = r.response = null r.route = r r._dataPrepared = false r._destroyViewOnEnd = false r destroyRoute = (route) -> assert.instanceOf route, Route if lastClientHTMLRoute is route lastClientHTMLRoute = null route.response.onSend.disconnect onResponseSent, route pendingRoutes[route.__hash__] = false route.destroy?() if route._dataPrepared switch route.request.type when 'text' route.destroyText?() when 'json' route.destroyJSON?() when 'html' route.destroyHTML?() if route._destroyViewOnEnd route.response.data.destroy() routesCache[route.__id__].push Object.getPrototypeOf(route) resolveSyncGetDataFunc = (route) -> assert.instanceOf route, Route route.data = route.getData() resolveAsyncGetDataFuncCallback = (route, err, data) -> assert.instanceOf route, Route if err? if route.response.status is 200 route.response.status = 500 if route._dataPrepared and route.error is err return false route.error = err else if route._dataPrepared and route.data is data return false route.data = data true prepareRouteData = (route) -> assert.instanceOf route, Route {response} = route if route.error log.error "Error in route '#{route.uri}':\n#{route.error}" respData = response.data switch route.request.type when 'text' data = route.toText() when 'json' data = route.toJSON() when 'html' data = route.toHTML() if respData instanceof Document and route._destroyViewOnEnd respData.destroy() response.data = null if not (data instanceof Document) and response.data is respData data = renderViewFromConfig.call route, data route._dataPrepared = true if data? response.data = data else if response.data is respData response.data = '' onResponseSent = -> if utils.isNode or @request.type isnt 'html' destroyRoute @ if utils.isNode and utils.has(usedTemplates, @response.data) @response.data.destroy() utils.remove usedTemplates, @response.data return finishRequest = (route) -> assert.instanceOf route, Route if route.response.data instanceof Document Renderer.window.document.node = route.response.data.node if route.response.pending route.response.send() return callNextIfNeeded = (route, next) -> unless pendingRoutes[route.__hash__] if route.response.pending next() return true false handleRequest = (req, res, next) -> assert.instanceOf req, Networking.Request assert.instanceOf res, Networking.Response assert.isFunction next route = factoryRoute @ hash = route.__hash__ assert.notOk pendingRoutes[hash] if utils.isClient if lastClientHTMLRoute destroyRoute lastClientHTMLRoute lastClientHTMLRoute = route route.request = req route.response = res pendingRoutes[hash] = true res.onSend onResponseSent, route # init route.init?() unless pendingRoutes[hash] return next() # redirect {redirect} = route if typeof redirect is 'function' redirect = route.redirect() unless pendingRoutes[hash] return next() if typeof redirect is 'string' redirect = new Networking.Uri redirect if redirect instanceof Networking.Uri res.redirect redirect.toString(req.params) return # getData {getData} = route fakeAsync = false if typeof getData is 'function' if getData.length is 1 route.getData (err, data) -> fakeAsync = true if callNextIfNeeded(route, next) return unless resolveAsyncGetDataFuncCallback(route, err, data) return prepareRouteData route if callNextIfNeeded(route, next) return finishRequest route else resolveSyncGetDataFunc route if callNextIfNeeded(route, next) return prepareRouteData route if callNextIfNeeded(route, next) return finishRequest route else prepareRouteData route if callNextIfNeeded(route, next) return finishRequest route if not fakeAsync and callNextIfNeeded(route, next) return ## *String* Route::method = `'get'` ## *Networking.Uri* Route::uri ## *App* Route::app ## *App.Route* Route::route ## *String* Route::name ## *Schema* Route::schema ## *Any* Route::data ## *Any* Route::error ## *Function* Route::factory() ## *Function* Route::init() ## *Function* Route::getData([*Function* callback]) ## *Function* Route::destroy() ## *Function* Route::destroyJSON() ## *Function* Route::destroyText() ## *Function* Route::destroyHTML() ## *Networking.Uri* Route::redirect Can be also a function. May returns a *Networking.Uri*, any String or `undefined`. ## *Networking.Request* Route::request ## *Networking.Response* Route::response ## *Function* Route::next() next: -> assert.ok pendingRoutes[@__hash__] destroyRoute @ ## *Any* Route::toJSON() toJSON: -> if @response.status < 400 @data?.toJSON?() or @data else @error.toJSON?() or @error ## *String* Route::toText() toText: -> if @response.status < 400 @data+'' else @error+'' ## *Document* Route::toHTML() routeToString = -> "#{@method} #{@uri}" getDefaultRouteViewName = -> path = "views/#{@name}.xhtml" if app.views[path] return path renderViewFromConfig = (opts) -> viewName = opts?.view or getDefaultRouteViewName.call(@) or 'views/index.xhtml' tmplName = opts?.template or 'views/template.xhtml' useName = opts?.use or 'body' logtime = log.time 'Render' if viewName isnt tmplName if tmpl = app.views[tmplName] tmplView = Route.getTemplateView tmplName tmplView.use useName, null else log.warn "Template view '#{tmplName}' can't be found for route '#{routeToString.call(@)}'" if view = app.views[viewName] r = view.render null, @ else log.warn "View '#{tmplName}' can't be found for route '#{routeToString.call(@)}'" if tmplView if r? r = tmplView.use(useName, r) else r = tmplView if tmplView.context.routes.has(useName) tmplView.context.routes.pop useName tmplView.context.routes.set useName, @ @_destroyViewOnEnd = false else @_destroyViewOnEnd = true log.end logtime r createToHTMLFromObject = (opts) -> -> renderViewFromConfig.call @, opts toHTML: createToHTMLFromObject view: '' template: '' use: ''