mirror of
https://github.com/flynx/pWiki.git
synced 2025-10-29 10:00:08 +00:00
1445 lines
40 KiB
JavaScript
1445 lines
40 KiB
JavaScript
|
|
/**********************************************************************
|
||
|
|
*
|
||
|
|
*
|
||
|
|
*
|
||
|
|
**********************************************************************/
|
||
|
|
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)(
|
||
|
|
function(require){ var module={} // makes module AMD/node compatible...
|
||
|
|
/*********************************************************************/
|
||
|
|
|
||
|
|
var object = require('ig-object')
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*********************************************************************/
|
||
|
|
|
||
|
|
var args2array = function(a){ return [].slice.call(a) }
|
||
|
|
|
||
|
|
var UNDEFINED =
|
||
|
|
module.UNDEFINED = ['undefined placeholder']
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*********************************************************************/
|
||
|
|
// Actions
|
||
|
|
//
|
||
|
|
// Actions are an extension to the JavaScript object model tailored for
|
||
|
|
// a set of specific tasks.
|
||
|
|
//
|
||
|
|
// Goals:
|
||
|
|
// - provide a unified mechanism to define and manage user API's for
|
||
|
|
// use in UI-hooks, keyboard mappings, scripting, ... etc.
|
||
|
|
// - a means to generate configuration UI's
|
||
|
|
// - a means to generate documentation
|
||
|
|
//
|
||
|
|
//
|
||
|
|
// The main entities:
|
||
|
|
//
|
||
|
|
// Action set
|
||
|
|
// - an object containing a number of actions,
|
||
|
|
// - optionally, directly or indirectly inherited from MetaActions
|
||
|
|
// and/or other action sets,
|
||
|
|
// - the action handlers are bound relative to it (._action_handlers)
|
||
|
|
//
|
||
|
|
// Action
|
||
|
|
//
|
||
|
|
// + pre + pre + + post + post +
|
||
|
|
// Action event handler: o-------x o-------x
|
||
|
|
// v ^
|
||
|
|
// Actions o-------x o-------x
|
||
|
|
// v ^
|
||
|
|
// Root Action o---|---x
|
||
|
|
//
|
||
|
|
// - a method, created by Action(..),
|
||
|
|
// - calls all the shadowed/overloaded actions in the inheritance
|
||
|
|
// chain in sequence implicitly,
|
||
|
|
// NOTE: there is no way to prevent an action in the chain from
|
||
|
|
// running, this is by design, i.e. no way to fully shadow.
|
||
|
|
// - actions that do not shadow anything are called root actions.
|
||
|
|
// - returns the action set by default (for call chaining),
|
||
|
|
// - the base/root action can return any value.
|
||
|
|
// NOTE: if undefined is returned, it will be replaced by the
|
||
|
|
// action context/action set.
|
||
|
|
// NOTE: there is no distinction between root and other actions
|
||
|
|
// other than that root action's return values are not
|
||
|
|
// ignored.
|
||
|
|
// - can consist of two parts: the first is called before the
|
||
|
|
// shadowed action (pre-callback) and the second after (post-callback).
|
||
|
|
// - post-callback has access to the return value and can modify it
|
||
|
|
// but not replace it.
|
||
|
|
// - can be bound to, a-la an event, calling the handlers when it is
|
||
|
|
// called,
|
||
|
|
//
|
||
|
|
// Action (event) handler
|
||
|
|
// - a function,
|
||
|
|
// - can be bound to run before and/or after the action itself,
|
||
|
|
// - is local to an action set it was bound via,
|
||
|
|
// - when an action is triggered from an action set, all the pre
|
||
|
|
// handlers in its inheritance chain will be called before the
|
||
|
|
// respective actions they are bound to and all the post handlers
|
||
|
|
// are called directly after.
|
||
|
|
// - pre handlers are passed the same arguments the original actions
|
||
|
|
// got when it was called.
|
||
|
|
// - post action handlers will get the root action result as first
|
||
|
|
// argument succeeded by the action arguments.
|
||
|
|
//
|
||
|
|
//
|
||
|
|
//
|
||
|
|
// The action system main protocols:
|
||
|
|
//
|
||
|
|
// 1) Documentation generation and introspection (MetaActions)
|
||
|
|
//
|
||
|
|
// <action>.toString()
|
||
|
|
// -> code of original action function
|
||
|
|
//
|
||
|
|
// <action-set>.getDoc()
|
||
|
|
// <action-set>.getDoc(<action-name>[, ..])
|
||
|
|
// -> dict of action-name, doc
|
||
|
|
//
|
||
|
|
// <action-set>.a.getHandlerDocStr(<action-name>)
|
||
|
|
// -> formated string of action handlers
|
||
|
|
//
|
||
|
|
// <action-set>.actions
|
||
|
|
// -> list of action names
|
||
|
|
//
|
||
|
|
// <action-set>.length
|
||
|
|
// -> number of actions
|
||
|
|
//
|
||
|
|
//
|
||
|
|
// 2) Event-like callbacks for actions (MetaActions, Action)
|
||
|
|
//
|
||
|
|
// <action-set>.on('action', function(){ ... })
|
||
|
|
// <action-set>.on('action.post', function(){ ... })
|
||
|
|
//
|
||
|
|
// <action-set>.on('action.pre', function(){ ... })
|
||
|
|
//
|
||
|
|
//
|
||
|
|
// 3) A mechanism to define and extend already defined actions
|
||
|
|
// This replaces / complements the standard JavaScript overloading
|
||
|
|
// mechanisms (Action, Actions)
|
||
|
|
//
|
||
|
|
// // Actions...
|
||
|
|
// var X = Actions({
|
||
|
|
// m: [function(){ console.log('m') }]
|
||
|
|
// })
|
||
|
|
// var O = Actions(X, {
|
||
|
|
// m: [function(){
|
||
|
|
// console.log('pre')
|
||
|
|
// return function(res){
|
||
|
|
// console.log('post')
|
||
|
|
// }
|
||
|
|
// }]
|
||
|
|
// })
|
||
|
|
//
|
||
|
|
// NOTE: what is done here is similar to calling O.__proto__.m.call(..)
|
||
|
|
// but is implicit, and not dependant on the original containing
|
||
|
|
// object name/reference ('O'), thus enabling an action to be
|
||
|
|
// referenced and called from any object and still chain correctly.
|
||
|
|
//
|
||
|
|
//
|
||
|
|
//
|
||
|
|
// Secondary action protocols:
|
||
|
|
//
|
||
|
|
// 1) A mechanism to manually call the pre/post stages of an action
|
||
|
|
//
|
||
|
|
// Pre phase...
|
||
|
|
// <action>.pre(<context>)
|
||
|
|
// <action>.pre(<context>, [<arg>, ..])
|
||
|
|
// -> <call-data>
|
||
|
|
//
|
||
|
|
// Post phase...
|
||
|
|
// <action>.post(<context>, <call-data>)
|
||
|
|
// -> <result>
|
||
|
|
//
|
||
|
|
// This is internally used to implement the action call as well as the
|
||
|
|
// chaining callbacks (see below).
|
||
|
|
//
|
||
|
|
// All action protocol details apply.
|
||
|
|
//
|
||
|
|
// NOTE: there is not reliable way to call the post phase without first
|
||
|
|
// calling the pre phase due to how the pre phase is defined (i.e.
|
||
|
|
// pre phase functions can return post phase functions).
|
||
|
|
//
|
||
|
|
//
|
||
|
|
// 2) A mechanism to chain/wrap actions or an action and a function.
|
||
|
|
// This enables us to call a callback or another action (inner) between
|
||
|
|
// the root action's (outer) pre and post stages.
|
||
|
|
//
|
||
|
|
// Outer action o-------x o-------x
|
||
|
|
// v ^
|
||
|
|
// Inner action/callback o---|---x
|
||
|
|
//
|
||
|
|
// A trivial example:
|
||
|
|
//
|
||
|
|
// actionSet.someAction.chainApply(actionsSet,
|
||
|
|
// function(){
|
||
|
|
// // this gets run between someAction's pre and post
|
||
|
|
// // stages...
|
||
|
|
// },
|
||
|
|
// args)
|
||
|
|
//
|
||
|
|
// This is intended to implement protocols where a single action is
|
||
|
|
// intended to act as a hook point (outer) and multiple different
|
||
|
|
// implementations (inner) within a single action set can be used as
|
||
|
|
// entry points.
|
||
|
|
//
|
||
|
|
// // Protocol root action (outer) definition...
|
||
|
|
// protocolAction: [function(){}],
|
||
|
|
//
|
||
|
|
// // Implementation actions (inner)...
|
||
|
|
// implementationAction1: [function(){
|
||
|
|
// return this.protocolAction.chainApply(this, function(){
|
||
|
|
// // ...
|
||
|
|
// }, ..)
|
||
|
|
// }]
|
||
|
|
//
|
||
|
|
// implementationAction2: [function(){
|
||
|
|
// return this.protocolAction.chainApply(this, function(){
|
||
|
|
// // ...
|
||
|
|
// }, ..)
|
||
|
|
// }]
|
||
|
|
//
|
||
|
|
// Now calling any of the 'implementation' actions will execute code
|
||
|
|
// in the following order:
|
||
|
|
// 1) pre phase of protocol action (outer)
|
||
|
|
// 2) implementation action (inner)
|
||
|
|
// 3) post phase of protocol action (outer)
|
||
|
|
//
|
||
|
|
// NOTE: this will not affect to protocol/signature of the outer action
|
||
|
|
// in any way.
|
||
|
|
// NOTE: both the inner and outer actions will get passed the same
|
||
|
|
// arguments.
|
||
|
|
// NOTE: another use-case is testing/debugging actions.
|
||
|
|
// NOTE: this is effectively the inside-out of normal action overloading.
|
||
|
|
// NOTE: there is intentionally no shorthand for this feature, to avoid
|
||
|
|
// confusion and to discourage the use of this feature unless
|
||
|
|
// really necessary.
|
||
|
|
//
|
||
|
|
//
|
||
|
|
// 3) .__call__ action / handler
|
||
|
|
// This action if defined is called for every action called. It behaves
|
||
|
|
// like any other action but with a fixed signature, it always receives
|
||
|
|
// the action name as first argument and a list of action arguments as
|
||
|
|
// the second arguments, and as normal a result on the post phase.
|
||
|
|
//
|
||
|
|
// NOTE: it is not necessary to define the actual action, binding to a
|
||
|
|
// handler will also work.
|
||
|
|
// NOTE: one should not call actions directly from within a __call__
|
||
|
|
// handler as that will result in infinite recursion.
|
||
|
|
// XXX need a way to prevent this...
|
||
|
|
// NOTE: one should use this with extreme care as this will introduce
|
||
|
|
// an overhead on all the actions if not done carefully.
|
||
|
|
//
|
||
|
|
//
|
||
|
|
//
|
||
|
|
/*********************************************************************/
|
||
|
|
// helpers...
|
||
|
|
|
||
|
|
var normalizeTabs = function(str){
|
||
|
|
str = str.split(/\n/g)
|
||
|
|
|
||
|
|
// get min number of leading tabs...
|
||
|
|
var i = str.length == 2 && /^\t/.test(str[1]) ?
|
||
|
|
str[1].split(/^(\t+)/)[1].length - 1
|
||
|
|
: Math.min.apply(null, str
|
||
|
|
// skip first line...
|
||
|
|
.slice(1)
|
||
|
|
// skip empty strings...
|
||
|
|
.filter(function(l){ return l.trim() != '' })
|
||
|
|
// count leading tabs...
|
||
|
|
.map(function(l){
|
||
|
|
return /^\t+/.test(l) ?
|
||
|
|
l.split(/^(\t+)/)[1].length
|
||
|
|
: 0}))
|
||
|
|
|
||
|
|
return (str[0] +'\n'
|
||
|
|
+ str
|
||
|
|
.slice(1)
|
||
|
|
// trim leading tabs...
|
||
|
|
.map(function(l){ return l.slice(i) }).join('\n')
|
||
|
|
// replace tabs...
|
||
|
|
.replace(/\t/g, ' '))
|
||
|
|
// remove leading and trailing whitespace...
|
||
|
|
.trim()
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
var doWithRootAction =
|
||
|
|
module.doWithRootAction =
|
||
|
|
function(func){
|
||
|
|
return function(){
|
||
|
|
var args = args2array(arguments)
|
||
|
|
var handlers = (this.getHandlerList
|
||
|
|
|| MetaActions.getHandlerList)
|
||
|
|
.apply(this, args)
|
||
|
|
|
||
|
|
return func.apply(this, [handlers.pop()].concat(args))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*********************************************************************/
|
||
|
|
|
||
|
|
// Construct an action object...
|
||
|
|
//
|
||
|
|
// Action function format:
|
||
|
|
//
|
||
|
|
// // pre event code...
|
||
|
|
// function(..){
|
||
|
|
// ... // pre code
|
||
|
|
// }
|
||
|
|
//
|
||
|
|
// // pre/post event code...
|
||
|
|
// function(..){
|
||
|
|
// ... // pre code
|
||
|
|
// return function(<return>, ..){
|
||
|
|
// ... // post code
|
||
|
|
// }
|
||
|
|
// }
|
||
|
|
//
|
||
|
|
//
|
||
|
|
// An action is essentially a method with several additional features:
|
||
|
|
//
|
||
|
|
// - actions are split into two stages:
|
||
|
|
// pre: the code of the method is executed before the action
|
||
|
|
// event is fired
|
||
|
|
// post: if the action returns a callback function it will be
|
||
|
|
// executed after the event is fired
|
||
|
|
// NOTE: the signature if the post stage is the same as the
|
||
|
|
// action's with the added return value as first argument
|
||
|
|
// (the rest og the arguments are shifted by 1).
|
||
|
|
//
|
||
|
|
// - actions automatically call the shadowed action, the pre stage is
|
||
|
|
// executed down-up while the post stage is run in reverse order,
|
||
|
|
// i.e. the pre is going down and the post is going up.
|
||
|
|
//
|
||
|
|
// - actions provide an event-like mechanism to register handlers or
|
||
|
|
// callbacks. These callbacks are local to a specific object and will
|
||
|
|
// be fired on action event/call starting from the current action
|
||
|
|
// caller and down the inheritance chain, i.e. all event handlers
|
||
|
|
// registered from the current object and up to the base action set
|
||
|
|
// will be fired.
|
||
|
|
//
|
||
|
|
// - an action will return the deepest (root) action's return, if that
|
||
|
|
// return is undefined, then the action set is returned instead.
|
||
|
|
//
|
||
|
|
// - action arguments are "threaded" through the action chain down and
|
||
|
|
// root action return value and arguments are threaded back up the
|
||
|
|
// action chain.
|
||
|
|
//
|
||
|
|
// NOTE: actions once defined do not depend on the inheritance hierarchy,
|
||
|
|
// other than the .getHandlerList(..) method. If this method is not
|
||
|
|
// found in the inheritance chain (i.e. the link to MetaActions)
|
||
|
|
// was severed, then the default will be used:
|
||
|
|
// MetaActions.getHandlerList(..)
|
||
|
|
// This makes it possible to redefine the method if needed but
|
||
|
|
// prevents the system from breaking when an action set gets
|
||
|
|
// disconnected from MetaActions. This can be useful, for example,
|
||
|
|
// to remove .on(..) / .off(..) handler functionality.
|
||
|
|
// XXX is this correct??
|
||
|
|
// NOTE: by default an action will return 'this', i.e. the action set
|
||
|
|
// object the action was called from.
|
||
|
|
//
|
||
|
|
// XXX add more metadata/docs:
|
||
|
|
// .section
|
||
|
|
// .category
|
||
|
|
// ...
|
||
|
|
// XXX might be a good idea to add an option to return the full results...
|
||
|
|
var Action =
|
||
|
|
module.Action =
|
||
|
|
function Action(name, doc, ldoc, func){
|
||
|
|
// we got called without a 'new'...
|
||
|
|
if(this == null || this.constructor !== Action){
|
||
|
|
// XXX using something like .apply(.., arguemnts) would be more
|
||
|
|
// generel but have no time to figure out how to pass it
|
||
|
|
// to new without the later complaining...
|
||
|
|
return new Action(name, doc, ldoc, func)
|
||
|
|
}
|
||
|
|
|
||
|
|
// prevent action overloading...
|
||
|
|
if(this[name] != null){
|
||
|
|
throw 'action "'+name+'" already exists.'
|
||
|
|
}
|
||
|
|
|
||
|
|
// create the actual instance we will be returning...
|
||
|
|
//var meth = function(){
|
||
|
|
// return meth.chainApply(this, null, arguments) }
|
||
|
|
var meth = function(){
|
||
|
|
return meth.chainApply(this, null, arguments) }
|
||
|
|
meth.__proto__ = this.__proto__
|
||
|
|
|
||
|
|
// populate the action attributes...
|
||
|
|
//meth.name = name
|
||
|
|
Object.defineProperty(meth, 'name', {
|
||
|
|
value: name,
|
||
|
|
})
|
||
|
|
meth.doc = doc
|
||
|
|
meth.long_doc = ldoc
|
||
|
|
|
||
|
|
meth.func = func
|
||
|
|
|
||
|
|
// make introspection be a bit better...
|
||
|
|
meth.toString = func.toString.bind(func)
|
||
|
|
|
||
|
|
return meth
|
||
|
|
}
|
||
|
|
// this will make action instances behave like real functions...
|
||
|
|
Action.prototype.__proto__ = Function
|
||
|
|
|
||
|
|
// The pre/post stage runners...
|
||
|
|
//
|
||
|
|
// .pre(context, args)
|
||
|
|
// -> data
|
||
|
|
//
|
||
|
|
// .post(context, data)
|
||
|
|
// -> result
|
||
|
|
//
|
||
|
|
//
|
||
|
|
// NOTE: All the defaults should be handled by the pre stage, post will
|
||
|
|
// process data assuming that it is correct.
|
||
|
|
//
|
||
|
|
// XXX revise the structure....
|
||
|
|
// ...is it a better idea to define action methods in an object
|
||
|
|
// and assign that???
|
||
|
|
Action.prototype.pre = function(context, args){
|
||
|
|
args = args || []
|
||
|
|
|
||
|
|
var res = context
|
||
|
|
var outer = this.name
|
||
|
|
|
||
|
|
// get the handler list...
|
||
|
|
var getHandlers = context.getHandlers || MetaActions.getHandlers
|
||
|
|
var handlers = getHandlers.call(context, outer)
|
||
|
|
|
||
|
|
// special case: see if we need to handle the call without handlers...
|
||
|
|
var preActionHandler = context.preActionHandler || MetaActions.preActionHandler
|
||
|
|
if(preActionHandler){
|
||
|
|
// XXX signature needs work...
|
||
|
|
var res = preActionHandler.call(context, outer, handlers, args)
|
||
|
|
if(res !== undefined){
|
||
|
|
return res
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
var call_wrapper = outer != '__call__' ?
|
||
|
|
getHandlers.call(context, '__call__')
|
||
|
|
: []
|
||
|
|
|
||
|
|
// wrapper handlers: pre phase...
|
||
|
|
call_wrapper = call_wrapper
|
||
|
|
.map(function(a){
|
||
|
|
if(a.pre){
|
||
|
|
res = a.pre.call(context, outer, args)
|
||
|
|
|
||
|
|
// if a handler returns a function register is as a post
|
||
|
|
// handler...
|
||
|
|
if(res
|
||
|
|
&& res !== context
|
||
|
|
&& res instanceof Function){
|
||
|
|
a.post = res
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return a
|
||
|
|
})
|
||
|
|
|
||
|
|
// handlers: pre phase...
|
||
|
|
handlers
|
||
|
|
// NOTE: current action will get included and called by the code
|
||
|
|
// above and below, so no need to explicitly call func...
|
||
|
|
// NOTE: pre handlers are called FIFO, i.e. the last defined first...
|
||
|
|
.map(function(a){
|
||
|
|
if(a.pre){
|
||
|
|
res = a.pre.apply(context, args)
|
||
|
|
|
||
|
|
// if a handler returns a function register is as a post
|
||
|
|
// handler...
|
||
|
|
if(res
|
||
|
|
&& res !== context
|
||
|
|
&& res instanceof Function){
|
||
|
|
a.post = res
|
||
|
|
|
||
|
|
// reset the result...
|
||
|
|
// NOTE: this is the only difference between this
|
||
|
|
// and wrapper stages...
|
||
|
|
res = context
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return a
|
||
|
|
})
|
||
|
|
|
||
|
|
// return context if nothing specific is returned...
|
||
|
|
res = res === undefined ? context
|
||
|
|
: res === UNDEFINED ? undefined
|
||
|
|
: res
|
||
|
|
|
||
|
|
return {
|
||
|
|
arguments: args,
|
||
|
|
|
||
|
|
wrapper: call_wrapper,
|
||
|
|
handlers: handlers,
|
||
|
|
|
||
|
|
result: res,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Action.prototype.post = function(context, data){
|
||
|
|
var res = data.result
|
||
|
|
|
||
|
|
var args = data.arguments || []
|
||
|
|
// the post handlers get the result as the first argument...
|
||
|
|
args.splice(0, 0, res)
|
||
|
|
|
||
|
|
var outer = this.name
|
||
|
|
|
||
|
|
// handlers: post phase...
|
||
|
|
data.handlers && data.handlers
|
||
|
|
// NOTE: post handlers are called LIFO -- last defined last...
|
||
|
|
.reverse()
|
||
|
|
.forEach(function(a){
|
||
|
|
a.post
|
||
|
|
&& a.post.apply(context, args)
|
||
|
|
})
|
||
|
|
|
||
|
|
// wrapper handlers: post phase...
|
||
|
|
data.wrapper && data.wrapper
|
||
|
|
// NOTE: post handlers are called LIFO -- last defined last...
|
||
|
|
.reverse()
|
||
|
|
.forEach(function(a){
|
||
|
|
a.post
|
||
|
|
&& a.post.call(context, res, outer, args.slice(1))
|
||
|
|
})
|
||
|
|
|
||
|
|
return res
|
||
|
|
}
|
||
|
|
|
||
|
|
// Chaining...
|
||
|
|
Action.prototype.chainApply = function(context, inner, args){
|
||
|
|
args = [].slice.call(args || [])
|
||
|
|
var res = context
|
||
|
|
var outer = this.name
|
||
|
|
|
||
|
|
var data = this.pre(context, args)
|
||
|
|
|
||
|
|
// call the inner action/function if preset....
|
||
|
|
if(inner){
|
||
|
|
//res = inner instanceof Function ?
|
||
|
|
inner instanceof Function ?
|
||
|
|
inner.call(context, args)
|
||
|
|
: inner instanceof Array && inner.length > 0 ?
|
||
|
|
context[inner.pop()].chainCall(context, inner, args)
|
||
|
|
: typeof(inner) == typeof('str') ?
|
||
|
|
context[inner].chainCall(context, null, args)
|
||
|
|
: null
|
||
|
|
}
|
||
|
|
|
||
|
|
return this.post(context, data)
|
||
|
|
}
|
||
|
|
Action.prototype.chainCall = function(context, inner){
|
||
|
|
return this.chainApply(context, inner, args2array(arguments).slice(2))
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
//---------------------------------------------------------------------
|
||
|
|
|
||
|
|
// A base action-set object...
|
||
|
|
//
|
||
|
|
// This will define a set of action-set specific methods and helpers.
|
||
|
|
//
|
||
|
|
// XXX .off(...) needs more work...
|
||
|
|
// XXX need a mechanism to publish specific attrs...
|
||
|
|
var MetaActions =
|
||
|
|
module.MetaActions = {
|
||
|
|
// List actions...
|
||
|
|
//
|
||
|
|
get actions(){
|
||
|
|
var res = []
|
||
|
|
for(var k in this){
|
||
|
|
// avoid recursion, skip props...
|
||
|
|
var cur = this
|
||
|
|
var prop = Object.getOwnPropertyDescriptor(cur, k)
|
||
|
|
while(!prop && cur.__proto__ != null){
|
||
|
|
var cur = cur.__proto__
|
||
|
|
var prop = Object.getOwnPropertyDescriptor(cur, k)
|
||
|
|
}
|
||
|
|
if(prop.get != null){
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
//if(k == 'actions' || k == 'length'){
|
||
|
|
// continue
|
||
|
|
//}
|
||
|
|
// get only actions...
|
||
|
|
if(this[k] instanceof Action){
|
||
|
|
res.push(k)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return res
|
||
|
|
},
|
||
|
|
|
||
|
|
// Number of defined actions...
|
||
|
|
//
|
||
|
|
get length(){
|
||
|
|
return this.actions.length
|
||
|
|
},
|
||
|
|
|
||
|
|
// Get action documentation...
|
||
|
|
//
|
||
|
|
getDoc: function(actions){
|
||
|
|
var res = {}
|
||
|
|
var that = this
|
||
|
|
actions = actions == null ? this.actions
|
||
|
|
: arguments.length > 1 ? args2array(arguments)
|
||
|
|
: typeof(actions) == typeof('str') ? [actions]
|
||
|
|
: actions
|
||
|
|
|
||
|
|
// get the first defined set of docs in the inheritance chain...
|
||
|
|
actions.forEach(function(n){
|
||
|
|
var cur = that
|
||
|
|
res[n] = []
|
||
|
|
// go up the proto chain...
|
||
|
|
while(cur.__proto__ != null){
|
||
|
|
if(cur[n] != null && cur[n].doc != null){
|
||
|
|
res[n] = [ cur[n].doc, cur[n].long_doc, cur[n].name ]
|
||
|
|
break
|
||
|
|
}
|
||
|
|
cur = cur.__proto__
|
||
|
|
}
|
||
|
|
})
|
||
|
|
return res
|
||
|
|
},
|
||
|
|
|
||
|
|
getPath: function(actions){
|
||
|
|
var res = {}
|
||
|
|
var that = this
|
||
|
|
actions = actions == null ? this.actions
|
||
|
|
: arguments.length > 1 ? args2array(arguments)
|
||
|
|
: typeof(actions) == typeof('str') ? [actions]
|
||
|
|
: actions
|
||
|
|
|
||
|
|
// get the first defined set of docs in the inheritance chain...
|
||
|
|
actions.forEach(function(n){
|
||
|
|
var cur = that
|
||
|
|
// go up the proto chain...
|
||
|
|
while(cur.__proto__ != null){
|
||
|
|
if(cur[n] != null && cur[n].doc != null){
|
||
|
|
var doc = cur[n].doc
|
||
|
|
var long_doc = cur[n].long_doc
|
||
|
|
break
|
||
|
|
}
|
||
|
|
cur = cur.__proto__
|
||
|
|
}
|
||
|
|
|
||
|
|
res[(doc && doc.replace(/[\\\/]$/, '/'+n)) || n] = [n, doc, long_doc]
|
||
|
|
})
|
||
|
|
return res
|
||
|
|
},
|
||
|
|
|
||
|
|
|
||
|
|
// Get action handlers from the inheritance chain...
|
||
|
|
//
|
||
|
|
// NOTE: this collects both the event handlers (in order of hierarchy,
|
||
|
|
// then order of definition) and actions (in order of hierarchy)
|
||
|
|
// NOTE: this is the correct order for 'pre' calling, but is the
|
||
|
|
// reverse of how the 'post' handlers must be called.
|
||
|
|
//
|
||
|
|
// For more docs on handler sequencing and definition see: .on(..)
|
||
|
|
getHandlerList: function(name){
|
||
|
|
var handlers = []
|
||
|
|
var cur = this
|
||
|
|
while(cur.__proto__ != null){
|
||
|
|
// get action "event" handlers...
|
||
|
|
if(cur.hasOwnProperty('_action_handlers')
|
||
|
|
&& name in cur._action_handlers){
|
||
|
|
handlers.splice.apply(handlers,
|
||
|
|
[handlers.length, 0].concat(cur._action_handlers[name]))
|
||
|
|
}
|
||
|
|
|
||
|
|
// get the overloading action...
|
||
|
|
// NOTE: this will get all the handlers including the root
|
||
|
|
// and the current handlers...
|
||
|
|
// NOTE: this will ignore "shadows" that are not actions...
|
||
|
|
if(cur.hasOwnProperty(name) && cur[name] instanceof Action){
|
||
|
|
handlers.push(cur[name].func)
|
||
|
|
}
|
||
|
|
|
||
|
|
cur = cur.__proto__
|
||
|
|
}
|
||
|
|
return handlers
|
||
|
|
},
|
||
|
|
|
||
|
|
// Get structured action handler definitions...
|
||
|
|
//
|
||
|
|
// Format:
|
||
|
|
// [
|
||
|
|
// {
|
||
|
|
// // NOTE: only one handler per level can be present, either
|
||
|
|
// // .pre or .post, this does not mean that one can
|
||
|
|
// // not define both, just that they are stored separately
|
||
|
|
// pre|post: <handler>,
|
||
|
|
// },
|
||
|
|
// ...
|
||
|
|
// ]
|
||
|
|
//
|
||
|
|
// XXX need to get parent action or definition context for each level...
|
||
|
|
// XXX is this simpler to use than the original .getHandlerList(..)
|
||
|
|
// XXX rename this....
|
||
|
|
getHandlers: function(name){
|
||
|
|
return (this.getHandlerList || MetaActions.getHandlerList).call(this, name)
|
||
|
|
.map(function(a){
|
||
|
|
var res = {
|
||
|
|
// action doc...
|
||
|
|
// XXX
|
||
|
|
}
|
||
|
|
|
||
|
|
if(!a.post_handler){
|
||
|
|
res.pre = a
|
||
|
|
|
||
|
|
} else {
|
||
|
|
res.post = a.post_handler
|
||
|
|
}
|
||
|
|
|
||
|
|
return res
|
||
|
|
})
|
||
|
|
},
|
||
|
|
|
||
|
|
// Handler for cases when we need to avoid the pre/post handlers...
|
||
|
|
//
|
||
|
|
// Returns:
|
||
|
|
// - undefined - handle the action normally.
|
||
|
|
// - object - bypass action handlers.
|
||
|
|
//
|
||
|
|
// NOTE: the object result must be compatible with Action.pre(..)
|
||
|
|
// return value...
|
||
|
|
// NOTE: this is mostly a stub, here for documentation reasons...
|
||
|
|
//preActionHandler: doWithRootAction(
|
||
|
|
// function(action, name, handlers, args){ return null }),
|
||
|
|
|
||
|
|
|
||
|
|
// Register an action callback...
|
||
|
|
//
|
||
|
|
// Register a post action callback
|
||
|
|
// .on('action', [<tag>, ]<function>)
|
||
|
|
// .on('action.post', [<tag>, ]<function>)
|
||
|
|
// -> <action-set>
|
||
|
|
//
|
||
|
|
// Register a pre action callback
|
||
|
|
// .on('action.pre', [<tag>, ]<function>)
|
||
|
|
// -> <action-set>
|
||
|
|
//
|
||
|
|
// Modes:
|
||
|
|
// 'pre' - the handler is fired before the action is triggered,
|
||
|
|
// and if the handler returns a deferred or a function
|
||
|
|
// then that will get resolved, called resp. after
|
||
|
|
// the action is done.
|
||
|
|
// 'post' - the handler is fired after the action is finished.
|
||
|
|
// this is the default.
|
||
|
|
//
|
||
|
|
// Handler Arguments:
|
||
|
|
// 'pre' - the handler will get the same arguments as the main
|
||
|
|
// action when called.
|
||
|
|
// 'post' - the handler will get the action return value followed
|
||
|
|
// by action arguments.
|
||
|
|
//
|
||
|
|
// The optional tag marks the handler to enable group removal via
|
||
|
|
// .off(..)
|
||
|
|
//
|
||
|
|
// NOTE: 'post' mode is the default.
|
||
|
|
//
|
||
|
|
// XXX should we have multiple tags per handler???
|
||
|
|
on: function(actions, b, c){
|
||
|
|
var handler = typeof(c) == 'function' ? c : b
|
||
|
|
var tag = typeof(c) == 'function' ? b : c
|
||
|
|
|
||
|
|
// XXX make this split by whitespace...
|
||
|
|
actions = typeof(actions) == 'string' ? actions.split(/ +/) : actions
|
||
|
|
|
||
|
|
var that = this
|
||
|
|
actions.forEach(function(action){
|
||
|
|
// prepare the handler...
|
||
|
|
var mode = action.split('.')
|
||
|
|
action = mode[0]
|
||
|
|
mode = mode[1]
|
||
|
|
|
||
|
|
// keep the original handler for future use...
|
||
|
|
var a_handler = handler
|
||
|
|
|
||
|
|
// a post handler (default)...
|
||
|
|
if(mode == null || mode == 'post'){
|
||
|
|
var old_handler = a_handler
|
||
|
|
a_handler = function(){ return old_handler }
|
||
|
|
a_handler.post_handler = old_handler
|
||
|
|
// NOTE: this is set so as to identify the handler
|
||
|
|
// for removal via. .off(..)
|
||
|
|
a_handler.orig_handler = old_handler.orig_handler || old_handler
|
||
|
|
|
||
|
|
// not pre mode...
|
||
|
|
} else if(mode != 'pre') {
|
||
|
|
// XXX
|
||
|
|
throw 'Unknown action mode: '+action+'.'+mode
|
||
|
|
}
|
||
|
|
|
||
|
|
a_handler.tag = tag
|
||
|
|
|
||
|
|
// register handlers locally only...
|
||
|
|
if(!that.hasOwnProperty('_action_handlers')){
|
||
|
|
that._action_handlers = {}
|
||
|
|
}
|
||
|
|
if(!(action in that._action_handlers)){
|
||
|
|
that._action_handlers[action] = []
|
||
|
|
}
|
||
|
|
// register a handler only once...
|
||
|
|
if(that._action_handlers[action].indexOf(a_handler) < 0){
|
||
|
|
// NOTE: last registered is first...
|
||
|
|
that._action_handlers[action].splice(0, 0, a_handler)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
return this
|
||
|
|
},
|
||
|
|
|
||
|
|
// Remove an action callback...
|
||
|
|
//
|
||
|
|
// Remove all handlers from action:
|
||
|
|
// .off('action')
|
||
|
|
// .off('action', '*')
|
||
|
|
// .off('action', 'all')
|
||
|
|
// -> <action-set>
|
||
|
|
//
|
||
|
|
// Remove specific handler from action:
|
||
|
|
// .off('action', <handler>)
|
||
|
|
// -> <action-set>
|
||
|
|
//
|
||
|
|
// Remove handlers from action by tag:
|
||
|
|
// .off('action', <tag>)
|
||
|
|
// -> <action-set>
|
||
|
|
//
|
||
|
|
// NOTE: the handler passed to .off(..) for removal must be the same
|
||
|
|
// as the handler passed to .on(..) / .one(..)
|
||
|
|
off: function(actions, handler){
|
||
|
|
if(this.hasOwnProperty('_action_handlers')){
|
||
|
|
|
||
|
|
actions = actions == '*' ? Object.keys(this._action_handlers)
|
||
|
|
: typeof(actions) == 'string' ? actions.split(' ')
|
||
|
|
: actions
|
||
|
|
|
||
|
|
var that = this
|
||
|
|
actions.forEach(function(action){
|
||
|
|
var mode = action.split('.')
|
||
|
|
action = mode[0]
|
||
|
|
mode = mode[1]
|
||
|
|
|
||
|
|
// get the handlers...
|
||
|
|
var h = that._action_handlers[action] || []
|
||
|
|
|
||
|
|
// remove explicit handler...
|
||
|
|
if(typeof(handler) == 'function'){
|
||
|
|
var i = -1
|
||
|
|
if(mode == null || mode == 'post'){
|
||
|
|
// XXX find via e.orig_handler == handler && e.mode == 'post'
|
||
|
|
h.forEach(function(e, j){
|
||
|
|
// NOTE: we will only get the first match...
|
||
|
|
if(e.orig_handler === handler && i == -1){
|
||
|
|
i = j
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
} else if(mode == 'pre'){
|
||
|
|
i = h.indexOf(handler)
|
||
|
|
}
|
||
|
|
|
||
|
|
// NOTE: unknown modes are skipped...
|
||
|
|
if(i >= 0){
|
||
|
|
h.splice(i, 1)
|
||
|
|
}
|
||
|
|
|
||
|
|
// remove all handlers...
|
||
|
|
} else if(handler == null || handler == 'all' || handler == '*'){
|
||
|
|
h.splice(0, h.length)
|
||
|
|
|
||
|
|
// remove handlers by tag...
|
||
|
|
} else {
|
||
|
|
// filter out everything that mathches a tag in-place...
|
||
|
|
h.splice.apply(h,
|
||
|
|
[0, h.length]
|
||
|
|
.concat(h.filter(function(e){
|
||
|
|
return e.tag != handler })))
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
return this
|
||
|
|
},
|
||
|
|
|
||
|
|
// Register an action callback that will only fire once per event...
|
||
|
|
//
|
||
|
|
// This is signature compatible with .on(..)
|
||
|
|
one: function(actions, b, c){
|
||
|
|
var _handler = typeof(c) == 'function' ? c : b
|
||
|
|
var tag = typeof(c) == 'function' ? b : c
|
||
|
|
|
||
|
|
actions = typeof(actions) == 'string' ? actions.split(' *') : actions
|
||
|
|
|
||
|
|
var that = this
|
||
|
|
actions.forEach(function(action){
|
||
|
|
// NOTE: we are using both 'that' below and 'this', so as
|
||
|
|
// to separate the call context and the bind context,
|
||
|
|
// .off(..) must be called at the bind context while
|
||
|
|
// the actual action is called from the call context
|
||
|
|
// NOTE: we are not using the closure _handler below to
|
||
|
|
// keep the code introspectable, and direct the user
|
||
|
|
// to the original function.
|
||
|
|
var handler = function(){
|
||
|
|
// remove handler...
|
||
|
|
that.off(action, handler.orig_handler)
|
||
|
|
|
||
|
|
// call the actual supplied handler function...
|
||
|
|
return handler.orig_handler.apply(this, arguments)
|
||
|
|
}
|
||
|
|
handler.orig_handler = _handler
|
||
|
|
that.on(action, tag, handler)
|
||
|
|
})
|
||
|
|
|
||
|
|
return this
|
||
|
|
},
|
||
|
|
|
||
|
|
// Apply/call a function/action "inside" an action...
|
||
|
|
//
|
||
|
|
// .chainApply(outer, inner)
|
||
|
|
// .chainApply(outer, inner, arguments)
|
||
|
|
// -> result
|
||
|
|
//
|
||
|
|
// .chainCall(outer, inner)
|
||
|
|
// .chainCall(outer, inner, ..)
|
||
|
|
// -> result
|
||
|
|
//
|
||
|
|
//
|
||
|
|
// The inner action call is completely nested as base of the outer
|
||
|
|
// action.
|
||
|
|
//
|
||
|
|
// Outer action o-------x o-------x
|
||
|
|
// v ^
|
||
|
|
// Inner action o---|---x
|
||
|
|
//
|
||
|
|
// The given arguments are passed as-is to both the outer and inner
|
||
|
|
// actions.
|
||
|
|
// The base inner action return value is passed to the outer action
|
||
|
|
// .post handlers.
|
||
|
|
//
|
||
|
|
// NOTE: these call the action's .chainApply(..) and .chainCall(..)
|
||
|
|
// methods, thus is not compatible with non-action methods...
|
||
|
|
// NOTE: .chianCall('action', ..) is equivalent to .action.chianCall(..)
|
||
|
|
chainApply: function(outer, inner, args){
|
||
|
|
return this[outer].chainApply(this, inner, args) },
|
||
|
|
chainCall: function(outer, inner){
|
||
|
|
return this[outer].chainApply(this, inner, args2array(arguments).slice(2)) },
|
||
|
|
|
||
|
|
// Get mixin object in inheritance chain...
|
||
|
|
//
|
||
|
|
// NOTE: if pre is true this will return the chain item before the
|
||
|
|
// mixin, this is useful, for example, to remove mixins, see
|
||
|
|
// .mixout(..) for an example...
|
||
|
|
getMixin: function(from, pre){
|
||
|
|
var cur = this
|
||
|
|
var proto = this.__proto__
|
||
|
|
while(proto != null){
|
||
|
|
// we have a hit...
|
||
|
|
if(proto.hasOwnProperty('__mixin_source')
|
||
|
|
&& proto.__mixin_source === from){
|
||
|
|
return pre ? cur : proto
|
||
|
|
}
|
||
|
|
// go to next item in chain...
|
||
|
|
cur = proto
|
||
|
|
proto = cur.__proto__
|
||
|
|
}
|
||
|
|
return null
|
||
|
|
},
|
||
|
|
|
||
|
|
// Mixin a set of actions into this...
|
||
|
|
//
|
||
|
|
// NOTE: if 'all' is set then mixin all the actions available,
|
||
|
|
// otherwise only mixin local actions...
|
||
|
|
// NOTE: this will override existing own attributes.
|
||
|
|
//
|
||
|
|
// XXX should we include functions by default????
|
||
|
|
inlineMixin: function(from, all, descriptors, all_attr_types){
|
||
|
|
// defaults...
|
||
|
|
descriptors = descriptors || true
|
||
|
|
all_attr_types = all_attr_types || false
|
||
|
|
|
||
|
|
if(all){
|
||
|
|
var keys = []
|
||
|
|
for(var k in from){
|
||
|
|
keys.push(k)
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
var keys = Object.keys(from)
|
||
|
|
}
|
||
|
|
|
||
|
|
var that = this
|
||
|
|
keys.forEach(function(k){
|
||
|
|
/*
|
||
|
|
// XXX is this the right way to go???
|
||
|
|
// check if we are not overwriting anything...
|
||
|
|
if(that.hasOwnProperty(k)){
|
||
|
|
console.warn('WARNING:', that,'already has attribute', k, '- skipping...')
|
||
|
|
return
|
||
|
|
}
|
||
|
|
*/
|
||
|
|
|
||
|
|
// properties....
|
||
|
|
var prop = Object.getOwnPropertyDescriptor(from, k)
|
||
|
|
if(descriptors && prop.get != null){
|
||
|
|
// NOTE: so as to be able to delete this on mixout...
|
||
|
|
prop.configurable = true
|
||
|
|
Object.defineProperty(that, k, prop)
|
||
|
|
|
||
|
|
|
||
|
|
// actions and other attributes...
|
||
|
|
} else {
|
||
|
|
var attr = from[k]
|
||
|
|
if(all_attr_types
|
||
|
|
//|| attr instanceof Function
|
||
|
|
|| attr instanceof Action){
|
||
|
|
that[k] = attr
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
return this
|
||
|
|
},
|
||
|
|
|
||
|
|
// Same as .inlineMixin(..) but isolates a mixin in a seporate object
|
||
|
|
// in the inheritance chain...
|
||
|
|
//
|
||
|
|
mixin: function(from, all, descriptors, all_attr_types){
|
||
|
|
var proto = Object.create(this.__proto__)
|
||
|
|
|
||
|
|
// mixinto an empty object
|
||
|
|
proto.inlineMixin(from, all, descriptors, all_attr_types)
|
||
|
|
|
||
|
|
// mark the mixin for simpler removal...
|
||
|
|
proto.__mixin_source = from
|
||
|
|
|
||
|
|
this.__proto__ = proto
|
||
|
|
|
||
|
|
return this
|
||
|
|
},
|
||
|
|
|
||
|
|
// Mixin a set of local actions into an object...
|
||
|
|
//
|
||
|
|
// XXX this will not work on non-actions...
|
||
|
|
mixinTo: function(to, all, descriptors, all_attr_types){
|
||
|
|
return this.mixin.call(to, this, all, descriptors, all_attr_types)
|
||
|
|
},
|
||
|
|
|
||
|
|
|
||
|
|
// Remove mixed in actions from this...
|
||
|
|
//
|
||
|
|
// NOTE: this will only remove local actions, inherited actions will
|
||
|
|
// not be affected...
|
||
|
|
// NOTE: this will not affect event handlers, they should be removed
|
||
|
|
// manually if needed...
|
||
|
|
inlineMixout: function(from, all, descriptors, all_attr_types){
|
||
|
|
// defaults...
|
||
|
|
descriptors = descriptors || true
|
||
|
|
all_attr_types = all_attr_types || false
|
||
|
|
|
||
|
|
if(all){
|
||
|
|
var keys = []
|
||
|
|
for(var k in from){
|
||
|
|
keys.push(k)
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
var keys = Object.keys(from)
|
||
|
|
}
|
||
|
|
|
||
|
|
var locals = Object.keys(this)
|
||
|
|
var that = this
|
||
|
|
keys.forEach(function(k){
|
||
|
|
var prop = Object.getOwnPropertyDescriptor(from, k)
|
||
|
|
|
||
|
|
// descriptor...
|
||
|
|
if(descriptors && prop.get != null){
|
||
|
|
if(prop.get === Object.getOwnPropertyDescriptor(that, k).get){
|
||
|
|
delete that[k]
|
||
|
|
}
|
||
|
|
|
||
|
|
// actions and other attrs...
|
||
|
|
} else {
|
||
|
|
var attr = from[k]
|
||
|
|
if((all_attr_types || attr instanceof Action)
|
||
|
|
// remove only local attrs...
|
||
|
|
&& locals.indexOf(k) >= 0){
|
||
|
|
delete that[k]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
return this
|
||
|
|
},
|
||
|
|
|
||
|
|
// This is similare in effect but different in mechanics to .inlineMixout(..)
|
||
|
|
//
|
||
|
|
// This will find and remove a mixin object from the inheritance chian.
|
||
|
|
//
|
||
|
|
// NOTE: this will remove only the first occurance of a mixin.
|
||
|
|
mixout: function(from){
|
||
|
|
var o = this.getMixin(from, true)
|
||
|
|
|
||
|
|
// pop the mixin off the chain...
|
||
|
|
if(o != null){
|
||
|
|
o.__proto__ = o.__proto__.__proto__
|
||
|
|
}
|
||
|
|
|
||
|
|
return this
|
||
|
|
},
|
||
|
|
|
||
|
|
// Remove a set of local mixed in actions from object...
|
||
|
|
//
|
||
|
|
mixoutFrom: function(to, all, descriptors, all_attr_types){
|
||
|
|
return this.mixout.call(to, this, all, descriptors, all_attr_types)
|
||
|
|
},
|
||
|
|
|
||
|
|
// Create a child object...
|
||
|
|
//
|
||
|
|
// NOTE: this will create a .config in the instance that inherits from
|
||
|
|
// this.config...
|
||
|
|
// NOTE: this will not copy/clone any data.
|
||
|
|
//
|
||
|
|
// XXX is this correct???
|
||
|
|
// XXX should this be an action???
|
||
|
|
clone: function(full){
|
||
|
|
var o = Object.create(this)
|
||
|
|
if(this.config){
|
||
|
|
if(full){
|
||
|
|
o.config = JSON.parse(JSON.stringify(this.config))
|
||
|
|
} else {
|
||
|
|
o.config = Object.create(this.config)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return o
|
||
|
|
},
|
||
|
|
|
||
|
|
// doc generators...
|
||
|
|
//
|
||
|
|
// XXX would be nice to make these prop of the action itself but I
|
||
|
|
// do not see a way to do this properly yet -- we can't get to
|
||
|
|
// the action context from the action dynamically...
|
||
|
|
getHandlerDocStr: function(name){
|
||
|
|
var lst = this.getHandlers(name)
|
||
|
|
var str = ''
|
||
|
|
|
||
|
|
var handler = function(p){
|
||
|
|
if(lst.length == 0){
|
||
|
|
//str += p + '---'
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// indicate root action...
|
||
|
|
p = lst.length == 1 ? p+'| ' : p+' '
|
||
|
|
|
||
|
|
var cur = lst.shift()
|
||
|
|
|
||
|
|
if(cur.pre){
|
||
|
|
str += p
|
||
|
|
+ normalizeTabs(cur.pre.toString()).replace(/\n/g, p)
|
||
|
|
+ p
|
||
|
|
}
|
||
|
|
|
||
|
|
handler(p + ' |')
|
||
|
|
|
||
|
|
str += p
|
||
|
|
|
||
|
|
if(cur.post){
|
||
|
|
str += p + p
|
||
|
|
+ normalizeTabs(cur.post.toString()).replace(/\n/g, p)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
handler('\n|')
|
||
|
|
|
||
|
|
return str
|
||
|
|
},
|
||
|
|
getHandlerDocHTML: function(name){
|
||
|
|
var lst = this.getHandlers(name)
|
||
|
|
var res = $('<div class="action">')
|
||
|
|
|
||
|
|
var handler = function(p){
|
||
|
|
if(lst.length == 0){
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
var cur = lst.shift()
|
||
|
|
p = $('<div class="level">')
|
||
|
|
.appendTo(p)
|
||
|
|
|
||
|
|
if(cur.pre){
|
||
|
|
p.append($('<pre>').html(
|
||
|
|
normalizeTabs(cur.pre.toString())
|
||
|
|
.replace(/return/g, '<b>return</b>')))
|
||
|
|
}
|
||
|
|
|
||
|
|
handler(p)
|
||
|
|
|
||
|
|
if(cur.post){
|
||
|
|
p.append($('<pre>').html(
|
||
|
|
normalizeTabs(cur.post.toString())))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
handler(res)
|
||
|
|
|
||
|
|
return res
|
||
|
|
},
|
||
|
|
|
||
|
|
|
||
|
|
// This will create a .config in instances...
|
||
|
|
// NOTE: this makes Actions compatible with lib/object.js...
|
||
|
|
__init__: function(){
|
||
|
|
if(this.__proto__.config && !Object.hasOwnProperty(this, 'config')){
|
||
|
|
this.config = Object.create(this.__proto__.config)
|
||
|
|
}
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
var ActionSet =
|
||
|
|
module.ActionSet =
|
||
|
|
object.makeConstructor('ActionSet', MetaActions)
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// An action set...
|
||
|
|
//
|
||
|
|
// Actions(<object>)
|
||
|
|
// Actions(<prototype>, <object>)
|
||
|
|
// -> actions
|
||
|
|
//
|
||
|
|
// This will pre-process an object to setup the action mechanics.
|
||
|
|
//
|
||
|
|
// If the 'this' and prototype both contain a .config attribute then this
|
||
|
|
// will make set <actions>.config.__proto__ = <prototype>.config
|
||
|
|
//
|
||
|
|
//
|
||
|
|
// The action format:
|
||
|
|
// {
|
||
|
|
// // full format...
|
||
|
|
// <name> : [
|
||
|
|
// <doc>,
|
||
|
|
// <long-doc>,
|
||
|
|
// <function>
|
||
|
|
// ],
|
||
|
|
//
|
||
|
|
// // short doc only...
|
||
|
|
// <name> : [
|
||
|
|
// <doc>,
|
||
|
|
// <function>
|
||
|
|
// ],
|
||
|
|
//
|
||
|
|
// // only the code...
|
||
|
|
// <name> : [
|
||
|
|
// <function>
|
||
|
|
// ],
|
||
|
|
// ...
|
||
|
|
// }
|
||
|
|
//
|
||
|
|
//
|
||
|
|
// NOTE: the action function is always last.
|
||
|
|
// NOTE: if <prototype> is not given, MetaActions will be used as default.
|
||
|
|
//
|
||
|
|
// For more documentation see: Action(..).
|
||
|
|
//
|
||
|
|
// XXX add doc, ldoc, tags and save them to each action...
|
||
|
|
// XXX is .config processing correct here???
|
||
|
|
var Actions =
|
||
|
|
module.Actions =
|
||
|
|
function Actions(a, b){
|
||
|
|
var obj = b == null ? a : b
|
||
|
|
var proto = b == null ? b : a
|
||
|
|
obj = obj || new ActionSet()
|
||
|
|
|
||
|
|
// NOTE: this is intentionally done only for own attributes...
|
||
|
|
Object.keys(obj).forEach(function(k){
|
||
|
|
// NOTE: we are not getting the attrs directly (vars = obj[k])
|
||
|
|
// as that will trigger the getters on an object that is
|
||
|
|
// not in a consistent state...
|
||
|
|
// NOTE: this will skip all the getters and setters, they will
|
||
|
|
// be included as-is...
|
||
|
|
var arg = Object.getOwnPropertyDescriptor(obj, k).value
|
||
|
|
|
||
|
|
// skip non-arrays...
|
||
|
|
if(arg == null
|
||
|
|
// XXX node?: for some magical reason when running this
|
||
|
|
// from node console instanceof tests fail...
|
||
|
|
//|| !(arg instanceof Array)
|
||
|
|
|| arg.constructor.name != 'Array'
|
||
|
|
// and arrays the last element of which is not a function...
|
||
|
|
|| typeof(arg[arg.length-1]) != 'function'){
|
||
|
|
//|| !(arg[arg.length-1] instanceof Function)){
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
var func = arg.pop()
|
||
|
|
|
||
|
|
// create a new action...
|
||
|
|
obj[k] = new Action(k, arg[0], arg[1], func)
|
||
|
|
})
|
||
|
|
|
||
|
|
if(proto != null){
|
||
|
|
obj.__proto__ = proto
|
||
|
|
|
||
|
|
// XXX is this the right way to go???
|
||
|
|
if(obj.config != null && proto.config != null){
|
||
|
|
obj.config.__proto__ = proto.config
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return obj
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*********************************************************************/
|
||
|
|
|
||
|
|
var mix =
|
||
|
|
module.mix =
|
||
|
|
function(){
|
||
|
|
var args = [].slice.call(arguments)
|
||
|
|
var res = {}
|
||
|
|
|
||
|
|
// special case: if MetaActions is in the args then inherit the root
|
||
|
|
// object from it...
|
||
|
|
if(args.indexOf(MetaActions) >= 0){
|
||
|
|
args.splice(args.indexOf(MetaActions), 1)
|
||
|
|
res.__proto__ = MetaActions
|
||
|
|
}
|
||
|
|
|
||
|
|
var mixin = MetaActions.inlineMixin
|
||
|
|
|
||
|
|
args.forEach(function(p){
|
||
|
|
res = Object.create(mixin.call(res, p))
|
||
|
|
|
||
|
|
// merge config...
|
||
|
|
if(p.config){
|
||
|
|
var config = res.config = res.config || Object.create({})
|
||
|
|
|
||
|
|
Object.keys(p.config).forEach(function(k){
|
||
|
|
res.config.__proto__[k] = JSON.parse(JSON.stringify(p.config[k]))
|
||
|
|
})
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
return res
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*********************************************************************/
|
||
|
|
|
||
|
|
var test =
|
||
|
|
module.test =
|
||
|
|
function test(){
|
||
|
|
// NOTE: this is needed only to add action methods to TestActions...
|
||
|
|
var BaseActions = new ActionSet()
|
||
|
|
|
||
|
|
var TestActions =
|
||
|
|
module.TestActions =
|
||
|
|
Actions(BaseActions, {
|
||
|
|
testActionGen1: ['baisc test action...',
|
||
|
|
'some extra info',
|
||
|
|
function(){
|
||
|
|
console.log(' test 1!')
|
||
|
|
return function(){
|
||
|
|
console.log(' test 2!')
|
||
|
|
}
|
||
|
|
}],
|
||
|
|
|
||
|
|
testActionGen2: ['baisc 2\'nd gen test action...',
|
||
|
|
// no extra info...
|
||
|
|
function(){
|
||
|
|
console.log(' test gen 2!')
|
||
|
|
this.testActionGen1()
|
||
|
|
}],
|
||
|
|
})
|
||
|
|
|
||
|
|
var TestActions2 =
|
||
|
|
module.TestActions2 =
|
||
|
|
Actions(TestActions, {
|
||
|
|
// NOTE: this looks like an action and feels like an action but
|
||
|
|
// actually this is a callback as an action with this name
|
||
|
|
// already exists...
|
||
|
|
testActionGen1: [
|
||
|
|
function(){
|
||
|
|
console.log(' pre callback!')
|
||
|
|
return function(){
|
||
|
|
console.log(' post callback!')
|
||
|
|
}
|
||
|
|
}],
|
||
|
|
|
||
|
|
testAction2: ['this is an action',
|
||
|
|
function(){
|
||
|
|
console.log('testAction2 args:', arguments)
|
||
|
|
}],
|
||
|
|
|
||
|
|
})
|
||
|
|
|
||
|
|
// XXX the main question here is that there is no way to know if a
|
||
|
|
// particular action is going to be a root action or an action
|
||
|
|
// callback because we do not know if the action in the parent
|
||
|
|
// will be available at mix time or not, and the two models
|
||
|
|
// are different...
|
||
|
|
// XXX one way to do this is to make all code a callback and
|
||
|
|
// just use the root as an event trigger...
|
||
|
|
//
|
||
|
|
// ...but this effectively means we are implementing
|
||
|
|
// inheritance ourselves as the traditional name resolution
|
||
|
|
// will no longer be used, and as in the case we implement
|
||
|
|
// MRO why not go the whole way and implement multiple
|
||
|
|
// inheritance in the first place...
|
||
|
|
//
|
||
|
|
// ...let's try and avoid this...
|
||
|
|
/*
|
||
|
|
var TestActionMixin =
|
||
|
|
module.TestActionMixin =
|
||
|
|
ActionMixin({
|
||
|
|
// XXX
|
||
|
|
})
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
console.log('TestActions.testActionGen1()')
|
||
|
|
TestActions.testActionGen1()
|
||
|
|
console.log('TestActions.testActionGen2()')
|
||
|
|
TestActions.testActionGen2()
|
||
|
|
|
||
|
|
|
||
|
|
// both of these should cet a callback...
|
||
|
|
console.log('TestActions2.testActionGen1()')
|
||
|
|
TestActions2.testActionGen1()
|
||
|
|
console.log('TestActions2.testActionGen2()')
|
||
|
|
TestActions2.testActionGen2()
|
||
|
|
|
||
|
|
// and an event-like handler...
|
||
|
|
TestActions2.on('testActionGen1.post',
|
||
|
|
function(){ console.log(' post handler! (first defined)') })
|
||
|
|
TestActions2.on('testActionGen1',
|
||
|
|
function(){ console.log(' post handler! (last defined)') })
|
||
|
|
|
||
|
|
console.log('TestActions2.testActionGen1()')
|
||
|
|
TestActions2.testActionGen1()
|
||
|
|
|
||
|
|
TestActions2.on('testActionGen2.pre',
|
||
|
|
function(){ console.log(' pre handler! (first defined)') })
|
||
|
|
TestActions2.on('testActionGen2.pre',
|
||
|
|
function(){ console.log(' pre handler! (last defined)') })
|
||
|
|
|
||
|
|
console.log('TestActions2.testActionGen2()')
|
||
|
|
TestActions2.testActionGen2()
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/**********************************************************************
|
||
|
|
* vim:set ts=4 sw=4 : */
|
||
|
|
return module })
|