chocolate
Version:
A full stack Node.js web framework built using Coffeescript
359 lines (288 loc) • 17.3 kB
text/coffeescript
_ = require '../../general/chocodash'
Chocokup = require '../../general/chocokup'
# Interface
# defaults: {} or ->
# use: {} or ->
# locks: [] or ->
# check: ->
# steps: ->
# render : ->
Interface = _.prototype
constructor: (defaults, use, service) ->
if not service? then service = use ; use = undefined
if not service? then service = defaults ; defaults = undefined
service = render:service if typeof service is 'function'
service.defaults = defaults if service? and defaults?
service.use = use if service? and use?
if service?
if service.defaults?
if typeof service.defaults is 'function' then = service.defaults
else = _.defaults , service.defaults
if service.locks?
if typeof service.locks is 'function' then = service.locks
else = ( ?= []).concat service.locks
if service.use?
if typeof service.use is 'function' then = service.use
else = _.defaults , service.use
= service.check if service.check?
= service.steps if service.steps?
= service.action if service.action? # 'action' is synonym to 'render'
@[name] = item for name, item of service when name not in ['defaults', 'use', 'locks', 'check', 'steps'] # service, actor, document, update...
?= undefined
= do ->
files_stack = []
if Error.prepareStackTrace?
oldPST = Error.prepareStackTrace
Error.prepareStackTrace = (err, stack) -> stack
stack = (new Error).stack
Error.prepareStackTrace = oldPST
if typeof stack is 'string'
files_stack = stack.split '\n'
else
for line in stack then files_stack.push line.getFileName()
else
stack = (new Error).stack
files_stack = stack.toString().split('\n')
found = no
for line in files_stack
if line.indexOf('/chocodash.') >= 0 then found = yes
else if line.indexOf('\\chocodash.') >= 0 then found = yes
else if found then return line
'global'
return
bind: (actor, document, ) ->
unless ? and ?
= actor
= document
switch _.type
when _.Type.Function then
when _.Type.String then (html) => $().html html; return
review: (bin, reaction) ->
reaction.certified ?= yes
if ? then .review bin, reaction
if reaction.certified
self = {bin, props:bin, space:bin?.__?.space, document:, 'interface':@, actor:}
check =
# `defaults` ensure default values are set in the bin/props passed to the interface
defaults: (object, defaults) =>
if typeof defaults is 'function' then defaults = defaults.call self, object
set = (o, d) ->
for own dk,dv of d
if (_.isBasicObject(o[dk]) or o[dk] instanceof Interface.Web.Global) and (_.isBasicObject(dv) or dv instanceof Interface.Web.Global) then set o[dk], dv
else o[dk] = dv if not o[dk]?
o
set object, defaults
# `use` ensure required values are set to a specific value in the bin/props passed to the interface
use: (object, required) =>
if typeof required is 'function' then required = required.call self, object
set = (o, d) ->
for own dk,dv of d
if (_.isBasicObject(o[dk]) or o[dk] instanceof Interface.Web.Global) and (_.isBasicObject(dv) or dv instanceof Interface.Web.Global) then set o[dk], dv
else o[dk] = dv
o
set object, required
# `locks` ensure keys are provided for every present lock (a lock is a uid or an object id and key fields)
locks: (keys, locks) =>
return yes unless locks?
if typeof locks is 'function' then locks = locks.call self
for lock in locks then return no unless (lock.key ? lock) of keys
yes
# `values` ensure values at the right scale, in the right range...
values: (bin, controller) => controller.call self, bin
check.defaults bin, if ?
check.use bin, if ?
if reaction.certified then reaction.certified = check.locks bin.__?.session?.keys, if ?
if reaction.certified then reaction.certified = check.values bin, if ?
return
submit: (bin = {}) ->
publisher = new _.Publisher
reaction = new Interface.Reaction
_.flow self:@, (run) ->
getSelf = (end) ->
respond = (o) -> reaction.props = reaction.bin = o ; end()
respond.later = end.later
transmit = (actor, service, bin = {}) ->
if typeof service isnt 'string'
interface_ = actor ; bin = service ; service = ''
else
interface_ = actor[service]
interface_.submit(_.extend , bin).subscribe (reaction) => reaction.bin
respond.later
{ bin, props:bin, space:bin?.__?.space, document:, 'interface':@, actor:, reaction, respond, transmit }
run (end) ->
bin, reaction
if reaction.certified and ?.steps
self = getSelf.call , end
result = .steps.call self, bin
end.with result
run (end) ->
if reaction.certified and ?
self = getSelf.call this, end
result = .call self, bin
end.with result
run (end) ->
if reaction.certified
if ?
self = getSelf.call this, end
result = .call self, bin
reaction.props = reaction.bin = result unless reaction.bin? or result is end.later
else
redirect = if ? then .redirect else
if redirect?
self = getSelf.call this, end
reaction.redirect = if typeof redirect is 'function' then redirect.call(self) else redirect
end.with result
run ->
publisher.notify reaction
publisher
observe: (render) ->
new _.Observer =>
.signal?.value() ; .subscribe ({bin}) -> render if typeof bin.render is 'function' then bin.render() else bin
Interface.Reaction = _.prototype
constructor: (, ) -> = ; return
Interface.Remote = _.prototype inherit:Interface, use: ->
= (bin = {}) ->
if '__' of bin then _.super @, bin
else .submit , bin
Interface.Web = _.prototype inherit:Interface, use: ->
get_declare_kups = (kups) ->
declare_kups = []
declare_path = {}
for kup in kups
path = "this.locals"
for step in kup.scope
path += ".#{step}"
unless declare_path[path]?
declare_path[path] = "#{path} = #{path} ? #{path} : {}"
declare_kups.push declare_path[path]
declare_kups.push "this.locals#{if kup.scope.length > 0 then '.' + kup.scope.join('.') else ''}.#{kup.name} = _kup_#{kup.id}"
declare_kups
= 'App'
= (bin, reaction) ->
_.super Interface.Web::review, @, bin, reaction
if reaction.certified
reaction.props = reaction.bin = ''
return if reaction.kups is false
scope = []
checked = []
checked_kups = {}
check_interfaces = (bin) ->
local_kups = []
for name, service of bin
if service instanceof Interface.Web
if service.defaults? and service not in checked
checked.push service
defaults = service.defaults
self = {bin, props:bin, space:bin?.__?.space, document:, 'interface':@, actor:}
if typeof defaults is 'function' then defaults = defaults.call self, bin
scope_ = scope
scope = []
kups = checked_kups[service] = check_interfaces.call this, defaults
scope = scope_
else
if service.use? and service not in checked
checked.push service
use = service.use
self = {bin, props:bin, space:bin?.__?.space, document:, 'interface':@, actor:}
if typeof use is 'function' then use = use.call self, bin
scope_ = scope
scope = []
kups = checked_kups[service] = check_interfaces.call this, use
scope = scope_
else
kups = checked_kups[service] ? []
declare_kups = get_declare_kups kups
service_id = _.Uuid().replace /\-/g, '_'
service_kup = new Function 'args', """
var interface = this.interface, bin = this.bin, props = this.props, keys = this.keys, actor = this.actor, space = this.space, module_path = this.module_path, local_ids = this.local_ids, __hasProp = {}.hasOwnProperty, Interface = this.params.Interface;
try {this.interface = bin#{if scope.length > 0 then '.' + scope.join('.') else ''}.#{name};}
catch (error) { try {this.interface = bin.#{name};} catch (error) {}; };
this.actor = this.interface != null ? (this.interface.actor != null ? this.interface.actor : actor) : actor;
this.keys = [];
this.props = this.bin = {__:bin.__};
this.space = this.bin != null && this.bin.__ != null ? this.bin.__.space : {};
this.module_path = '#{service.module_path}';
this.local_ids = {};
bin_cp = function(b_, _b) {
var done = false, k, v;
for (k in _b) {
if (!hasProp.call(_b, k) || (k === '__')) continue;
if (((v = _b[k]) != null ? v.constructor : void 0) === {}.constructor) { b_[k] = {}; if (!(done = bin_cp(b_[k], v))) { delete b_[k]; } }
else if ((v instanceof Interface.Web) || (v instanceof Interface.Web.Global)) { b_[k] = v; done = true; }
}
return done;
};
bin_cp(this.bin, bin);
if (args != null) {for (k in args) {if (__hasProp.call(args, k)) { this.bin[k] = args[k]; this.keys.push(k); }}}
reaction = {kups:false};
if (this.interface != null)
this.interface.review(this.bin, reaction);
if (reaction.certified) {
#{declare_kups.join ';\n'};
with (this.locals) {(#{(service.render?.overriden ? service.render).toString()}).call(this, this.bin);}
}
this.bin = bin; this.props = props; this.keys = keys; this.interface = interface; this.actor = actor; this.space = space; this.module_path = module_path; this.local_ids = local_ids;
return reaction.certified;
"""
reaction.kups ?= {}
reaction.kups["_kup_#{service_id}"] ?= service_kup
local_kups.push {name, scope:[].concat(scope), id:service_id}
else
if name isnt '__' and (_.isBasicObject(service) or service instanceof Interface.Web.Global)
scope.push name
checked.push service
local_kups = local_kups.concat check_interfaces.call this, service
scope.pop()
return local_kups
checked.push bin
reaction.local_kups = check_interfaces.call this, bin
= (bin) ->
unless ?.overriden
render_code = ? ->
chocokup_code = null
= (bin) ->
bin ?= {}
kups = .kups
delete .kups
local_kups = .local_kups
delete .local_kups
declare_kups = get_declare_kups local_kups
chocokup_code = if declare_kups.length > 0 then new Function 'args', """
this.self.keys = [];
this.module_path = this.self.module_path = '#{@interface.module_path}';
this.local_ids = {};
if (args != null) {for (k in args) {if ({}.hasOwnProperty.call(args, k)) { this.self.bin[k] = args[k]; this.self.keys.push(k); }}}
#{declare_kups.join ';\n'};
with (this.locals) {return (#{render_code.toString()}).apply(this.self, arguments);}
"""
else new Function 'args', """
this.module_path = this.self.module_path = '#{@interface.module_path}';
this.local_ids = {};
return (#{render_code.toString()}).apply(this.self, arguments);
"""
transmit = (actor, service, bin_ = {}) ->
if typeof service isnt 'string'
interface_ = actor ; bin_ = service ; service = ''
else
interface_ = actor[service]
interface_.call options, _.extend bin, bin_
if ? then = transmit
options = {bin, props:bin, space:bin?.__?.space, document:, Interface, 'self':@, actor:, kups, transmit}
options.theme = bin.theme if bin.theme?
options.with_coffee = bin.with_coffee if bin.with_coffee?
options.manifest = bin.manifest if bin.manifest?
.props = .bin = switch .type
when 'Panel'then new Chocokup.Panel options, chocokup_code
else new Chocokup[.type] bin?.name ? '', options, chocokup_code
.overriden = chocokup_code ? render_code
if typeof bin is 'function' then callback = bin ; bin = {}
result = _.super @, bin
if callback? then result.subscribe (reaction) -> callback reaction.bin.render()
result
Interface.Web.Global = _.prototype
constructor: (o) -> @[k] = v for own k,v of o
Interface.Web.App = Interface.Web
Interface.Web.Document = _.prototype inherit:Interface.Web, use: -> = 'Document'
Interface.Web.Panel = Interface.Web.Html = _.prototype inherit:Interface.Web, use: -> = 'Panel'
_module = window ? module
if _module.exports? then _module.exports = Interface else window.Locco ?= {} ; window.Locco.Interface = Interface