added a more generic version of he toggler (jli.js), not tested yet...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2014-10-08 04:52:41 +04:00
parent 2fcb376f13
commit 55fcab441a
3 changed files with 262 additions and 35 deletions

View File

@ -15,38 +15,67 @@ define(function(require){ var module = {}
// Actions are an extension to the JavaScript object model tailored for
// a set of specific tasks.
//
// The action system consists of these parts:
//
// 1) documentation generation and introspection
// XXX not all helpers are defined at this point...
// Goals:
// - provide a unified mechanism to define and manage user API's for
// use in UI-hooks, keyboard mappings, scripting, ...
// - a means to generate configuration UI's
// - a means to generate documentation
//
//
// 2) event-like callbacks for actions
// The main entities:
//
// MyActions.on('action', function(){ ... })
// MyActions.on('action.post', function(){ ... })
// 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)
//
// MyActions.on('action.pre', function(){ ... })
// Action
// - a method, created by Action(..),
// - calls all the shadowed 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 full shadow.
// - returns the action set (for call chaining),
// - can consist of two parts: the first is called before the
// shadowed action (pre-callback) and the second after (post-callback).
// - 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.
//
//
// 3) a mechanism to extend already defined actions
//
// The action system provides three components:
//
// 1) Documentation generation and introspection (MetaActions)
//
// <action-set>.getDoc()
// <action-set>.getDoc(<action-name>[, ..])
// -> dict of action-name, doc
//
// <action-set>.actions
// -> list of action names
//
//
// 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, here is a direct comparison:
//
//
// // Native...
// var X = {
// m: function(){ console.log('m') }
// }
// var O = {
// m: function(){
// console.log('pre')
// B.__proto__.m.call(this)
// console.log('post')
// }
// }
// O.__proto__ = X
//
// mechanisms (Action, Actions)
//
// // Actions...
// var X = Actions({
@ -61,15 +90,10 @@ define(function(require){ var module = {}
// }]
// })
//
//
// Comparing to the native system:
// + no need to chain overloaded calls by hand (automatic)
// +/- more restrictive -- no way to prevent original actions from
// running, i.e. no way to shadow.
// +/- hidden the internals (.__proto__ assignment)
// - more structural code (returning a callback vs. B.__proto__.m.call)
// NOTE: that the Actions(..) call and lists containing functions
// is not added complexity as they are mainly used for docs.
// 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.
//
//
//
@ -246,6 +270,7 @@ module.MetaActions = {
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...
@ -262,9 +287,14 @@ module.MetaActions = {
return res
},
// Collect all the handlers from the inheritance chain and arrange
// them up-down, first defined to last...
// 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(..)
getHandlers: function(name){
var handlers = []
var cur = this

View File

@ -231,6 +231,189 @@ function createCSSClassToggler(elem, class_list, callback_a, callback_b){
}
// Make a generic toggler function/method...
//
// state_accessor signature:
//
// Get current state:
// state_accessor()
// -> <current-state>
//
// Set new state:
// state_accessor(<new-state>)
// -> <new-state>
//
// NOTE: for single state toggling, 'none' will get passed to
// state_accessor to indicate an "empty" state...
//
function makeToggler(state_accessor, states, callback_a, callback_b){
// normalize states...
states = typeof(states) == typeof('str') ? ['none', states] : states
// normalize the callbacks...
if(callback_b == null){
var callback_pre = null
var callback_post = callback_a
} else {
var callback_pre = callback_a
var callback_post = callback_b
}
var bool_action = (states.length == 2 && states[0] == 'none')
var func = function(a, b){
// so as to be able to distinguish between a method and a root
// context in a simple manner...
'use strict'
// parse arguments...
if(b == null){
var action = a == 'next' ? null : a
// XXX is this correct???
var e = this
} else {
var e = a
var action = b == 'next' ? null : b
}
// option number...
if(typeof(action) == typeof(1)){
// range check...
if(action < 0 || action >= states.length){
return null
}
if(bool_action){
action = action == 0 ? 'off' : 'on'
} else {
action = states[action]
}
}
// we need to get the current state...
if(action == null || action == '?' || action == '!'){
// get current state...
var cur = state_accessor.call(e)
// just asking for info...
if(action == '?'){
return bool_action ? (cur == 'none' ? 'off' : 'on') : cur
}
// force reload of current state...
if(action == '!'){
action = bool_action ? (cur == 'none' ? 'off' : 'on') : cur
}
// invalid action...
} else if((bool_action && ['on', 'off'].indexOf(action) == -1)
|| (!bool_action && states.indexOf(action) == -1)){
return null
}
var state = bool_action ? states[1] : action
// get the right class...
if(action == null){
var i = states.indexOf(cur)+1
i = i == -1 ? 0 : i
i = i == states.length ? 0 : i
state = states[i]
if(bool_action){
action = state == 'none' ? 'off' : 'on'
} else {
action = state
}
}
// NOTE: the callbacks are passed the same this as the calling
// function, this will enable them to act as metods correctly
// pre callback...
if(callback_pre != null){
if(callback_pre.apply(this, [action, e].concat(args)) === false){
//return
return func('?')
}
}
// update the element...
state_accessor.call(e, state)
// post callback...
if(callback_post != null){
callback_post.apply(this, [action, e].concat(args))
}
return action
}
func.states = states
if(bool_action){
func.doc = 'With no arguments this will toggle between "on" and '+
'"off".\n'+
'If either "on" or "off" are given then this will switch '+
'to that mode.\n'+
'If "?" is given, this will return either "on" or "off" '+
'depending on the current state.'
}else{
func.doc = 'With no arguments this will toggle between '+
states +' in cycle.\n' +
'if any of the state names or its number is given then that '+
'state is switched on.'+
'If "?" is given, this will return the current state.'
}
return func
}
// XXX this should be drop-in compatible with createCSSClassToggler(..)
// test and replace...
function makeCSSClassToggler(elem, classes, callback_a, callback_b){
// normalize the states...
classes = typeof(classes) == typeof('str') ? ['none', classes] : classes
// remove the dot from class names...
// NOTE: this is here because I've made the error of including a
// leading "." almost every time I use this after I forget
// the UI...
classes = classes
.map(function(e){
return e.split(' ')
.map(function(c){
c = c.trim()
return c[0] == '.' ? c.slice(1) : c
}).join(' ')
})
return makeToggler(
function(state){
var e = $(this == null ? elem : this)
// get the state...
if(state == null){
var cur = 'none'
for(var i=0; i < classes.length; i++){
// XXX make this faster by getting the class list once
// and checking in that rather than doing a
// .hasClass(..) per iteration...
if(e.hasClass(classes[i])){
cur = classes[i]
break
}
}
return cur
// set the state...
} else {
e.removeClass(classes.join(' '))
if(state != 'none' && action != 'off'){
e.addClass(state)
}
}
},
classes,
callback_a,
callback_b)
}
/*
// show a jQuary opject in viewer overlay...
@ -1121,6 +1304,7 @@ Array.prototype.compact = function(){
// like .length but for sparse arrays will return the element count...
// XXX make this a prop...
Array.prototype.len = function(){
return this.compact().length
}

View File

@ -11,7 +11,20 @@ console.log('>>> viewer')
/*********************************************************************/
//
// XXX Tasks to accomplish here:
// - life-cycle actions/events
// - setup
// - reset
// - "features" and the mechanism to turn them on or off (action-sets)
//
//
var Client = {
}
var Viewer = {
}