mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-29 18:30:09 +00:00
moved away from local object/actions/features, not yet sure if it's a good move...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
5dfd470170
commit
51bd6284d4
File diff suppressed because it is too large
Load Diff
@ -1,702 +0,0 @@
|
|||||||
/**********************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
**********************************************************************/
|
|
||||||
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
|
||||||
(function(require){ var module={} // make module AMD/node compatible...
|
|
||||||
/*********************************************************************/
|
|
||||||
|
|
||||||
// XXX
|
|
||||||
// XXX replace this with ig-features
|
|
||||||
// XXX
|
|
||||||
|
|
||||||
var args2array = require('lib/util').args2array
|
|
||||||
|
|
||||||
var object = require('lib/object')
|
|
||||||
var actions = module.actions = require('lib/actions')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*********************************************************************/
|
|
||||||
//
|
|
||||||
// Feature attributes:
|
|
||||||
// .tag - feature tag (string)
|
|
||||||
// this is used to identify the feature, its event handlers
|
|
||||||
// and DOM elements.
|
|
||||||
//
|
|
||||||
// .title - feature name (string | null)
|
|
||||||
// .doc - feature description (string | null)
|
|
||||||
//
|
|
||||||
// .priority - feature priority
|
|
||||||
// can be:
|
|
||||||
// - 'high' (99) | 'medium' (0) | 'low' (-99)
|
|
||||||
// - number
|
|
||||||
// - null (0, default)
|
|
||||||
// features with higher priority will be setup first,
|
|
||||||
// features with the same priority will be run in order of
|
|
||||||
// occurrence.
|
|
||||||
// .suggested - list of optional suggested features, these are not
|
|
||||||
// required but setup if available.
|
|
||||||
// This is useful for defining meta features but without
|
|
||||||
// making each sub-feature a strict dependency.
|
|
||||||
// .depends - feature dependencies -- tags of features that must setup
|
|
||||||
// before the feature (list | null)
|
|
||||||
// .exclusive - feature exclusivity tags (list | null)
|
|
||||||
// an exclusivity group enforces that only one feature in
|
|
||||||
// it will be run, i.e. the first / highest priority.
|
|
||||||
//
|
|
||||||
// .actions - action object containing feature actions (ActionSet | null)
|
|
||||||
// this will be mixed into the base object on .setup()
|
|
||||||
// and mixed out on .remove()
|
|
||||||
// .config - feature configuration, will be merged with base
|
|
||||||
// object's .config
|
|
||||||
// NOTE: the final .config is an empty object with
|
|
||||||
// .__proto__ set to the merged configuration
|
|
||||||
// data...
|
|
||||||
// .handlers - feature event handlers (list | null)
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// .handlers format:
|
|
||||||
// [
|
|
||||||
// [ <event-spec>, <handler-function> ],
|
|
||||||
// ...
|
|
||||||
// ]
|
|
||||||
//
|
|
||||||
// NOTE: both <event-spec> and <handler-function> must be compatible with
|
|
||||||
// Action.on(..)
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Feature applicability:
|
|
||||||
// If feature.isApplicable(..) returns false then the feature will not be
|
|
||||||
// considered on setup...
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// XXX this could install the handlers in two locations:
|
|
||||||
// - mixin if available...
|
|
||||||
// - base object (currently implemented)
|
|
||||||
// should the first be done?
|
|
||||||
var FeatureProto =
|
|
||||||
module.FeatureProto = {
|
|
||||||
tag: null,
|
|
||||||
|
|
||||||
isApplicable: function(actions){
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
|
|
||||||
getPriority: function(){
|
|
||||||
var res = this.priority || 0
|
|
||||||
return res == 'high' ? 99
|
|
||||||
: res == 'low' ? -99
|
|
||||||
: res == 'medium' ? 0
|
|
||||||
: res
|
|
||||||
},
|
|
||||||
|
|
||||||
setup: function(actions){
|
|
||||||
var that = this
|
|
||||||
|
|
||||||
// mixin actions...
|
|
||||||
if(this.actions != null){
|
|
||||||
actions.mixin(this.actions)
|
|
||||||
}
|
|
||||||
|
|
||||||
// install handlers...
|
|
||||||
if(this.handlers != null){
|
|
||||||
this.handlers.forEach(function(h){
|
|
||||||
actions.on(h[0], that.tag, h[1])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// merge config...
|
|
||||||
// NOTE: this will merge the actual config in .config.__proto__
|
|
||||||
// keeping the .config clean for the user to lay with...
|
|
||||||
if(this.config != null
|
|
||||||
|| (this.actions != null
|
|
||||||
&& this.actions.config != null)){
|
|
||||||
var config = this.config = this.config || this.actions.config
|
|
||||||
|
|
||||||
if(actions.config == null){
|
|
||||||
actions.config = Object.create({})
|
|
||||||
}
|
|
||||||
Object.keys(config).forEach(function(n){
|
|
||||||
// NOTE: this will overwrite existing values...
|
|
||||||
actions.config.__proto__[n] = config[n]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// custom setup...
|
|
||||||
// XXX is this the correct way???
|
|
||||||
if(this.hasOwnProperty('setup') && this.setup !== FeatureProto.setup){
|
|
||||||
this.setup(actions)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
},
|
|
||||||
remove: function(actions){
|
|
||||||
if(this.actions != null){
|
|
||||||
actions.mixout(this.actions)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.handlers != null){
|
|
||||||
actions.off('*', this.tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.hasOwnProperty('remove') && this.setup !== FeatureProto.remove){
|
|
||||||
this.remove(actions)
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove feature DOM elements...
|
|
||||||
actions.ribbons.viewer.find('.' + this.tag).remove()
|
|
||||||
|
|
||||||
return this
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// XXX is hard-coded default feature-set a good way to go???
|
|
||||||
//
|
|
||||||
// Feature(obj)
|
|
||||||
// -> feature
|
|
||||||
//
|
|
||||||
// Feature(feature-set, obj)
|
|
||||||
// -> feature
|
|
||||||
//
|
|
||||||
// Feature(tag, obj)
|
|
||||||
// -> feature
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Feature(tag, actions)
|
|
||||||
// -> feature
|
|
||||||
//
|
|
||||||
// Feature(feature-set, tag, actions)
|
|
||||||
// -> feature
|
|
||||||
//
|
|
||||||
var Feature =
|
|
||||||
module.Feature =
|
|
||||||
function Feature(feature_set, tag, obj){
|
|
||||||
if(arguments.length == 2){
|
|
||||||
// Feature(<tag>, <obj>)
|
|
||||||
if(typeof(feature_set) == typeof('str')){
|
|
||||||
obj = tag
|
|
||||||
tag = feature_set
|
|
||||||
feature_set = Features
|
|
||||||
|
|
||||||
// Feature(<feature-set>, <obj>)
|
|
||||||
} else {
|
|
||||||
obj = tag
|
|
||||||
tag = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Feature(<obj>)
|
|
||||||
} else if(arguments.length == 1){
|
|
||||||
obj = feature_set
|
|
||||||
feature_set = Features
|
|
||||||
}
|
|
||||||
|
|
||||||
if(tag != null && obj.tag != null && obj.tag != tag){
|
|
||||||
throw 'Error: tag and obj.tag mismatch, either use one or both must match.'
|
|
||||||
}
|
|
||||||
|
|
||||||
// action...
|
|
||||||
if(obj instanceof actions.Action){
|
|
||||||
if(tag == null){
|
|
||||||
throw 'Error: need a tag to make a feature out of an action'
|
|
||||||
}
|
|
||||||
var f = {
|
|
||||||
tag: tag,
|
|
||||||
actions: obj,
|
|
||||||
}
|
|
||||||
obj = f
|
|
||||||
|
|
||||||
// meta-feature...
|
|
||||||
} else if(obj.constructor === Array){
|
|
||||||
if(tag == null){
|
|
||||||
throw 'Error: need a tag to make a meta-feature'
|
|
||||||
}
|
|
||||||
var f = {
|
|
||||||
tag: tag,
|
|
||||||
suggested: obj,
|
|
||||||
}
|
|
||||||
obj = f
|
|
||||||
|
|
||||||
// feature...
|
|
||||||
} else {
|
|
||||||
obj.__proto__ = FeatureProto
|
|
||||||
}
|
|
||||||
|
|
||||||
if(feature_set){
|
|
||||||
feature_set[obj.tag] = obj
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
Feature.prototype = FeatureProto
|
|
||||||
Feature.prototype.constructor = Feature
|
|
||||||
|
|
||||||
|
|
||||||
var FeatureSetProto = {
|
|
||||||
__feature__: Feature,
|
|
||||||
__actions__: actions.Actions,
|
|
||||||
|
|
||||||
// if true, .setup(..) will report things it's doing...
|
|
||||||
__verbose__: null,
|
|
||||||
|
|
||||||
// List of registered features...
|
|
||||||
get features(){
|
|
||||||
var that = this
|
|
||||||
return Object.keys(this)
|
|
||||||
.filter(function(e){
|
|
||||||
return e != 'features'
|
|
||||||
&& that[e] instanceof Feature })
|
|
||||||
},
|
|
||||||
|
|
||||||
// Build list of features...
|
|
||||||
//
|
|
||||||
// Build list of all features for an empty object...
|
|
||||||
// .buildFeatureList()
|
|
||||||
// .buildFeatureList({})
|
|
||||||
// .buildFeatureList({}, '*')
|
|
||||||
// -> data
|
|
||||||
//
|
|
||||||
// Build a list of features for a specific root feature and object...
|
|
||||||
// .buildFeatureList(object, feature)
|
|
||||||
// -> data
|
|
||||||
//
|
|
||||||
// Build a list of features for a specific set of root features and object...
|
|
||||||
// .buildFeatureList(object, [feature, ..])
|
|
||||||
// -> data
|
|
||||||
// NOTE: to disable a feature and all of it's dependants prefix
|
|
||||||
// it's tag with '-' in the list.
|
|
||||||
// e.g. including 'some-feature' will include the feature
|
|
||||||
// and its dependants while '-some-feature' will remove
|
|
||||||
// it and it's dependants.
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// This will build from user input a loadable list of features taking
|
|
||||||
// into account feature dependencies, priorities and suggestions.
|
|
||||||
//
|
|
||||||
// Roughly this is done in this order starting with the given features:
|
|
||||||
// - include all dependencies (recursively)
|
|
||||||
// - include all suggested features (recursively)
|
|
||||||
// - sort features by priority
|
|
||||||
// - sort features by dependency
|
|
||||||
// - check for feature applicability
|
|
||||||
// - remove non-applicable features and all dependants (recursively)
|
|
||||||
// - remove disabled features and all dependants (recursively)
|
|
||||||
// - check and resolve exclusivity conflicts (XXX needs revision)
|
|
||||||
// - check for missing features and dependencies
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Return format:
|
|
||||||
// {
|
|
||||||
// // list of input features...
|
|
||||||
// input: [ .. ],
|
|
||||||
//
|
|
||||||
// // features in correct load order...
|
|
||||||
// features: [ .. ],
|
|
||||||
//
|
|
||||||
// // features disabled explicitly and their dependants...
|
|
||||||
// disabled: [ .. ],
|
|
||||||
// // unapplicable features and their dependants...
|
|
||||||
// unapplicable: [ .. ],
|
|
||||||
//
|
|
||||||
// // features removed due to exclusivity conflict...
|
|
||||||
// excluded: [ .. ],
|
|
||||||
//
|
|
||||||
// missing: {
|
|
||||||
// // features explicitly given by user but missing...
|
|
||||||
// USER: [ .. ],
|
|
||||||
// // missing <feature> dependencies...
|
|
||||||
// <feature>: [ .. ],
|
|
||||||
// ...
|
|
||||||
// },
|
|
||||||
// conflicts: {
|
|
||||||
// XXX
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// NOTE: obj (action set) here is used only for applicability testing...
|
|
||||||
// NOTE: some feature applicability checks (.isApplicable(..)) may
|
|
||||||
// require a real action set, thus for correct operation one
|
|
||||||
// should be provided.
|
|
||||||
// NOTE: all feature sorting is done maintaining relative feature order
|
|
||||||
// when possible...
|
|
||||||
// NOTE: meta-features are not included in the list as they do not
|
|
||||||
// need to be setup.
|
|
||||||
// ...this is because they are not Feature objects.
|
|
||||||
//
|
|
||||||
// XXX should meta-features be MetaFeature objects???
|
|
||||||
// XXX not sure about handling excluded features (see inside)...
|
|
||||||
// XXX add dependency loops to .conflicts...
|
|
||||||
// XXX might be a good idea to check dependency loops on feature
|
|
||||||
// construction, too... (???)
|
|
||||||
buildFeatureList: function(obj, lst){
|
|
||||||
var that = this
|
|
||||||
obj = obj || {}
|
|
||||||
|
|
||||||
lst = (lst == null || lst == '*') ? this.features : lst
|
|
||||||
lst = lst.constructor !== Array ? [lst] : lst
|
|
||||||
|
|
||||||
var input = lst.slice()
|
|
||||||
var disabled = []
|
|
||||||
var excluded = []
|
|
||||||
var unapplicable = []
|
|
||||||
var missing = {}
|
|
||||||
var conflicts = {}
|
|
||||||
|
|
||||||
|
|
||||||
// reverse dependency cache...
|
|
||||||
var dependants = {}
|
|
||||||
|
|
||||||
// build dependency list...
|
|
||||||
var _buildDepList = function(n, seen){
|
|
||||||
seen = seen || []
|
|
||||||
return seen.indexOf(n) >= 0 ? []
|
|
||||||
: seen.push(n) && dependants[n] ? []
|
|
||||||
.concat.apply(
|
|
||||||
dependants[n],
|
|
||||||
dependants[n]
|
|
||||||
.map(function(n){ return _buildDepList(n, seen) }))
|
|
||||||
: []
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// missing stage 1: check if all user included features exist...
|
|
||||||
// NOTE: we'll ignore missing disabled features too...
|
|
||||||
lst.forEach(function(n){
|
|
||||||
if(!that[n] && n[0] != '-'){
|
|
||||||
var m = missing['USER'] = missing['USER'] || []
|
|
||||||
m.push(n)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// include all dependencies...
|
|
||||||
//
|
|
||||||
// NOTE: this should never fall into an infinite loop as we do
|
|
||||||
// not include feature already seen...
|
|
||||||
// ...unless there is an infinite number of features, but
|
|
||||||
// I'll try to avoid that big a feature creep.
|
|
||||||
// XXX should we check for dependency loops here???
|
|
||||||
// ...this would have been simple if this was a recursion
|
|
||||||
// (just check if cur is in path), but here it is not
|
|
||||||
// trivial...
|
|
||||||
for(var i=0; i < lst.length; i++){
|
|
||||||
var k = lst[i]
|
|
||||||
|
|
||||||
// skip disabled or missing features....
|
|
||||||
if(k[0] == '-' || !that[k]){
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var deps = that[k].depends || []
|
|
||||||
var refs = that[k].suggested || []
|
|
||||||
|
|
||||||
deps.forEach(function(n){
|
|
||||||
// expand lst with dependencies....
|
|
||||||
lst.indexOf(n) < 0 && lst.push(n)
|
|
||||||
|
|
||||||
// build reverse dependency index...
|
|
||||||
var d = dependants[n] = dependants[n] || []
|
|
||||||
d.indexOf(k) < 0 && d.push(k)
|
|
||||||
})
|
|
||||||
|
|
||||||
// expand lst with suggenstions....
|
|
||||||
refs.forEach(function(n){
|
|
||||||
lst.indexOf(n) < 0 && lst.push(n)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort features by priority or position...
|
|
||||||
lst = lst
|
|
||||||
// remove undefined and non-features...
|
|
||||||
.filter(function(n){
|
|
||||||
// feature disabled -> record and skip...
|
|
||||||
if(n[0] == '-'){
|
|
||||||
disabled.push(n.slice(1))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
var f = that[n]
|
|
||||||
// feature not defined or is not a feature...
|
|
||||||
if(f == null || !(f instanceof Feature)){
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// check applicability...
|
|
||||||
if(f.isApplicable && !f.isApplicable.call(that, obj)){
|
|
||||||
unapplicable.push(n)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
// remove disabled...
|
|
||||||
.filter(function(e){ return disabled.indexOf(e) < 0 })
|
|
||||||
// build the sort table: [ <priority>, <index>, <elem> ]
|
|
||||||
.map(function(e, i){ return [ that[e].getPriority(), i, e ] })
|
|
||||||
// do the sort...
|
|
||||||
// NOTE: JS compares lists as strings so we have to compare
|
|
||||||
// the list manually...
|
|
||||||
.sort(function(a, b){ return a[0] - b[0] || a[1] - b[1] })
|
|
||||||
// cleanup -- drop the sort table...
|
|
||||||
.map(function(e){ return e[2] })
|
|
||||||
|
|
||||||
// remove dependants on not applicable and on disabled...
|
|
||||||
var _unapplicable = unapplicable.slice()
|
|
||||||
var _disabled = disabled.slice()
|
|
||||||
// build the full lists of features to remove...
|
|
||||||
_unapplicable
|
|
||||||
.forEach(function(n){ _unapplicable = _unapplicable.concat(_buildDepList(n)) })
|
|
||||||
_disabled
|
|
||||||
.forEach(function(n){ _disabled = _disabled.concat(_buildDepList(n)) })
|
|
||||||
// clear...
|
|
||||||
// NOTE: in case of intersection disabled has priority...
|
|
||||||
lst = lst
|
|
||||||
.filter(function(n){
|
|
||||||
return _disabled.indexOf(n) >= 0 ?
|
|
||||||
disabled.push(n) && false
|
|
||||||
: _unapplicable.indexOf(n) >= 0 ?
|
|
||||||
unapplicable.push(n) && false
|
|
||||||
: true })
|
|
||||||
|
|
||||||
// missing stage 2: dependencies...
|
|
||||||
lst.forEach(function(k){
|
|
||||||
(that[k].depends || []).forEach(function(d){
|
|
||||||
// NOTE: we do not need to check disabled or unapplicable
|
|
||||||
// here as if the feature depended on dropped feature
|
|
||||||
// it would have been already dropped too...
|
|
||||||
if(!that[k]){
|
|
||||||
var m = missing[k] = missing[k] || []
|
|
||||||
m.push(d)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// check exclusive -> excluded...
|
|
||||||
//
|
|
||||||
// NOTE: this is the right spot for this, just after priority
|
|
||||||
// sorting and clearing but before dependency sorting.
|
|
||||||
//
|
|
||||||
// XXX do we need to clear dependencies pulled by excluded features???
|
|
||||||
// ways to go:
|
|
||||||
// - drop excluded and continue (current state)
|
|
||||||
// - disable excluded, add to original input and rebuild
|
|
||||||
// - err and let the user decide
|
|
||||||
var _exclusive = []
|
|
||||||
lst = lst.filter(function(n){
|
|
||||||
var e = that[n]
|
|
||||||
|
|
||||||
// keep non-exclusive stuff...
|
|
||||||
if(!e || e.exclusive == null){
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// count the number of exclusive features already present...
|
|
||||||
var res = e.exclusive
|
|
||||||
.filter(function(n){
|
|
||||||
if(_exclusive.indexOf(n) < 0){
|
|
||||||
_exclusive.push(n)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
.length == 0
|
|
||||||
|
|
||||||
!res
|
|
||||||
&& excluded.push(n)
|
|
||||||
// warn the user...
|
|
||||||
// XXX not sure if this is the right place for this...
|
|
||||||
&& console.warn(
|
|
||||||
'Excluding unaplicable:', n, '(reccomended to exclude manually)')
|
|
||||||
|
|
||||||
return res
|
|
||||||
})
|
|
||||||
|
|
||||||
// sort by dependency...
|
|
||||||
var l = lst.length
|
|
||||||
// get maximum possible length...
|
|
||||||
// ...the worst case length appears to be (for full reversal):
|
|
||||||
// S(2*(n-1) + 1)
|
|
||||||
// S = n => n > 0 ? 2*(n-1)+1 + S(n-1) : 0
|
|
||||||
// S = n => n > 0 ? 2*n-1 + S(n-1) : 0
|
|
||||||
//
|
|
||||||
// 2 * S(n) - n
|
|
||||||
// S = n => n > 0 ? n + S(n-1) : 0
|
|
||||||
// f = n => 2 * S(n) - n
|
|
||||||
//
|
|
||||||
// N^2 + C
|
|
||||||
// S = n => n * n
|
|
||||||
//
|
|
||||||
// NOTE: this is the brute force way to check if we have a
|
|
||||||
// dependency loop, need something faster...
|
|
||||||
//
|
|
||||||
// XXX is O(n^2) good enough worst case here?
|
|
||||||
// ...at this point I think it is acceptable as we'll not
|
|
||||||
// expect dependency graphs too saturated, and the average
|
|
||||||
// complexity is far better...
|
|
||||||
var max = l * l
|
|
||||||
|
|
||||||
for(var i=0; i < lst.length; i++){
|
|
||||||
var k = lst[i]
|
|
||||||
var depends = that[k].depends || []
|
|
||||||
|
|
||||||
// list of dependencies to move...
|
|
||||||
var move = []
|
|
||||||
|
|
||||||
lst
|
|
||||||
.slice(0, i)
|
|
||||||
.forEach(function(n, j){
|
|
||||||
// if n is a dependency of k, prepare to move...
|
|
||||||
if(depends.indexOf(n) >= 0){
|
|
||||||
delete lst[j]
|
|
||||||
move.push(n)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// move the dependencies after k...
|
|
||||||
// NOTE: this will keep the order within the dependencies...
|
|
||||||
move.length > 0
|
|
||||||
&& lst.splice.apply(lst, [i+1, 0].concat(move))
|
|
||||||
|
|
||||||
// check for cyclic dependencies...
|
|
||||||
// XXX loop signs:
|
|
||||||
// - the tail length stops changing -- we stop progressing to list end
|
|
||||||
// - the loop is packed
|
|
||||||
// - each element includes a set of dependencies
|
|
||||||
// - this set is of the same length when at a specific element
|
|
||||||
// - we only shift the same set of N elements over N iterations
|
|
||||||
// - ...
|
|
||||||
if(lst.length >= max){
|
|
||||||
// XXX get the actual cycle...
|
|
||||||
console.error('Feature cyclic dependency...')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanup after sort...
|
|
||||||
lst = lst
|
|
||||||
// remove undefined and non-features...
|
|
||||||
.filter(function(e){
|
|
||||||
return that[e] != null && that[e] instanceof Feature })
|
|
||||||
.reverse()
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
|
||||||
input: input,
|
|
||||||
|
|
||||||
features: lst,
|
|
||||||
|
|
||||||
disabled: disabled,
|
|
||||||
unapplicable: unapplicable,
|
|
||||||
excluded: excluded,
|
|
||||||
|
|
||||||
missing: missing,
|
|
||||||
conflicts: conflicts,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// .setup(<actions>, [<feature>, ...])
|
|
||||||
// -> <actions>
|
|
||||||
//
|
|
||||||
// .setup([<feature>, ...])
|
|
||||||
// -> <actions>
|
|
||||||
//
|
|
||||||
setup: function(obj, lst){
|
|
||||||
// if no explicit object is given, just the list...
|
|
||||||
if(lst == null){
|
|
||||||
lst = obj
|
|
||||||
obj = null
|
|
||||||
}
|
|
||||||
|
|
||||||
obj = obj || (this.__actions__ || actions.Actions)()
|
|
||||||
|
|
||||||
lst = lst.constructor !== Array ? [lst] : lst
|
|
||||||
var features = this.buildFeatureList(obj, lst)
|
|
||||||
lst = features.features
|
|
||||||
|
|
||||||
// check for conflicts...
|
|
||||||
if(Object.keys(features.conflicts).length != 0
|
|
||||||
|| Object.keys(features.missing).length != 0){
|
|
||||||
var m = features.missing
|
|
||||||
var c = features.conflicts
|
|
||||||
|
|
||||||
// build a report...
|
|
||||||
var report = []
|
|
||||||
|
|
||||||
// missing deps...
|
|
||||||
Object.keys(m).forEach(function(k){
|
|
||||||
report.push(k + ': missing but required by:\n ' + m[k].join(', '))
|
|
||||||
})
|
|
||||||
report.push('\n')
|
|
||||||
|
|
||||||
// conflicts...
|
|
||||||
Object.keys(c).forEach(function(k){
|
|
||||||
report.push(k + ': must setup after:\n ' + c[k].join(', '))
|
|
||||||
})
|
|
||||||
|
|
||||||
// break...
|
|
||||||
throw 'Feature dependency error:\n ' + report.join('\n ')
|
|
||||||
}
|
|
||||||
|
|
||||||
// report excluded features...
|
|
||||||
if(this.__verbose__ && features.excluded.length > 0){
|
|
||||||
console.warn('Excluded features due to exclusivity conflict:',
|
|
||||||
features.excluded.join(', '))
|
|
||||||
}
|
|
||||||
|
|
||||||
// report unapplicable features...
|
|
||||||
if(this.__verbose__ && features.unapplicable.length > 0){
|
|
||||||
console.log('Features not applicable in current context:',
|
|
||||||
features.unapplicable.join(', '))
|
|
||||||
}
|
|
||||||
|
|
||||||
// do the setup...
|
|
||||||
var that = this
|
|
||||||
var setup = FeatureProto.setup
|
|
||||||
lst.forEach(function(n){
|
|
||||||
// setup...
|
|
||||||
if(that[n] != null){
|
|
||||||
this.__verbose__ && console.log('Setting up feature:', n)
|
|
||||||
setup.call(that[n], obj)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// XXX should we extend this if it already was in the object???
|
|
||||||
obj.features = features
|
|
||||||
|
|
||||||
return obj
|
|
||||||
},
|
|
||||||
remove: function(obj, lst){
|
|
||||||
lst = lst.constructor !== Array ? [lst] : lst
|
|
||||||
var that = this
|
|
||||||
lst.forEach(function(n){
|
|
||||||
if(that[n] != null){
|
|
||||||
console.log('Removing feature:', n)
|
|
||||||
that[n].remove(obj)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
// shorthand for: Feature(<feature-set>, ...)
|
|
||||||
// XXX should this return this?
|
|
||||||
Feature: function(){
|
|
||||||
return this.__feature__.apply(null, [this].concat(args2array(arguments)))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var FeatureSet =
|
|
||||||
module.FeatureSet = object.makeConstructor('FeatureSet', FeatureSetProto)
|
|
||||||
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------
|
|
||||||
|
|
||||||
var Features =
|
|
||||||
module.Features = new FeatureSet()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************
|
|
||||||
* vim:set ts=4 sw=4 : */ return module })
|
|
||||||
@ -1,220 +0,0 @@
|
|||||||
/**********************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
**********************************************************************/
|
|
||||||
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
|
|
||||||
(function(require){ var module={} // make module AMD/node compatible...
|
|
||||||
/*********************************************************************/
|
|
||||||
|
|
||||||
return require('../node_modules/ig-object/object')
|
|
||||||
|
|
||||||
// XXX
|
|
||||||
// XXX replace this with ig-object
|
|
||||||
// XXX
|
|
||||||
|
|
||||||
|
|
||||||
/*********************************************************************/
|
|
||||||
|
|
||||||
|
|
||||||
// Make a JavaScrip object constructor...
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Make a constructor with an object prototype...
|
|
||||||
// makeConstructor(<name>, <proto>)
|
|
||||||
// -> constructor
|
|
||||||
//
|
|
||||||
// Make a constructor with an init function prototype...
|
|
||||||
// makeConstructor(<name>, <init-func>)
|
|
||||||
// -> constructor
|
|
||||||
//
|
|
||||||
// Make a constructor with a prototype (object/function) and a class
|
|
||||||
// prototype...
|
|
||||||
// makeConstructor(<name>, <proto>, <class-proto>)
|
|
||||||
// makeConstructor(<name>, <init-func>, <class-proto>)
|
|
||||||
// -> constructor
|
|
||||||
// NOTE: the <class-proto> defines a set of class methods and
|
|
||||||
// attributes.
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// The resulting constructor can produce objects in one of these ways:
|
|
||||||
//
|
|
||||||
// Basic constructor use...
|
|
||||||
// constructor()
|
|
||||||
// new constructor
|
|
||||||
// new constructor()
|
|
||||||
// -> instance
|
|
||||||
//
|
|
||||||
// Pass arguments to the constructor...
|
|
||||||
// constructor(<arg>[, ...])
|
|
||||||
// new constructor(<arg>[, ...])
|
|
||||||
// -> instance
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// All produced objects are instances of the constructor
|
|
||||||
// instance instanceof constructor
|
|
||||||
// -> true
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Init protocol:
|
|
||||||
// 1) the base instance object is prepared (.__proto__ is set)
|
|
||||||
// 2) if <init-func> is present, then it is called with instance as
|
|
||||||
// context and passed the constructor arguments
|
|
||||||
// 3) if <proto>.__init__(..) is present, it is called with the instance
|
|
||||||
// as context and passed the constructor arguments.
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Inheritance:
|
|
||||||
// A simple way to build C -> B -> A chain would be:
|
|
||||||
//
|
|
||||||
// var A = makeConstructor('A', {})
|
|
||||||
//
|
|
||||||
// // NOTE: the prototype is an instance and not a constructor,
|
|
||||||
// // this is obvious if one considers that in JS there are
|
|
||||||
// // no classes and inheritance is done via object prototypes
|
|
||||||
// // but this might be a gotcha to people coming from the
|
|
||||||
// // class-object world.
|
|
||||||
// var B = makeConstructor('B', A())
|
|
||||||
//
|
|
||||||
// var C = makeConstructor('C', B())
|
|
||||||
//
|
|
||||||
// var c = C()
|
|
||||||
//
|
|
||||||
// c instanceof C // -> true
|
|
||||||
// c instanceof B // -> true
|
|
||||||
// c instanceof A // -> true
|
|
||||||
//
|
|
||||||
// A.prototype.x = 123
|
|
||||||
//
|
|
||||||
// c.x // -> 123
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Motivation:
|
|
||||||
// The general motivation here is to standardise the constructor protocol
|
|
||||||
// and make a single simple way to go with minimal variation. This is due
|
|
||||||
// to the JavaScript base protocol though quite simple, being too flexible
|
|
||||||
// making it very involved to produce objects in a consistent manner by
|
|
||||||
// hand, especially in long running projects, in turn spreading all the
|
|
||||||
// refactoring over multiple sites and styles.
|
|
||||||
//
|
|
||||||
// This removes part of the flexibility and in return gives us:
|
|
||||||
// - single, well defined protocol
|
|
||||||
// - one single spot where all the "magic" happens
|
|
||||||
// - full support for existing JavaScript ways of doing things
|
|
||||||
// - easy refactoring without touching the client code
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// NOTE: this sets the proto's .constructor attribute, this rendering it
|
|
||||||
// not reusable, to use the same prototype for multiple objects clone
|
|
||||||
// it via. Object.create(..) or copy it...
|
|
||||||
//
|
|
||||||
// XXX might be a good idea to be able to make an instance without
|
|
||||||
// initializing it...
|
|
||||||
// ...mainly for inheritance.
|
|
||||||
// ...would also be helpful in this case to call all the
|
|
||||||
// constructors in the chain
|
|
||||||
var makeConstructor =
|
|
||||||
module.makeConstructor =
|
|
||||||
function makeConstructor(name, a, b){
|
|
||||||
var proto = b == null ? a : b
|
|
||||||
var cls_proto = b == null ? b : a
|
|
||||||
|
|
||||||
var _constructor = function Constructor(){
|
|
||||||
/*
|
|
||||||
// XXX BUG: if the constructor is called from it's instance this will
|
|
||||||
// return the instance and not a new object...
|
|
||||||
// in case this is called as a function (without new)...
|
|
||||||
if(this.constructor !== _constructor){
|
|
||||||
// NOTE: the following does the job of the 'new' operator but
|
|
||||||
// with one advantage, we can now pass arbitrary args
|
|
||||||
// in...
|
|
||||||
// This is equivalent to:
|
|
||||||
// return new _constructor(json)
|
|
||||||
var obj = {}
|
|
||||||
obj.__proto__ = _constructor.prototype
|
|
||||||
// XXX for some reason this does not resolve from .__proto__
|
|
||||||
obj.constructor = _constructor
|
|
||||||
//obj.__proto__.constructor = _constructor
|
|
||||||
|
|
||||||
} else {
|
|
||||||
var obj = this
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// NOTE: the following does the job of the 'new' operator but
|
|
||||||
// with one advantage, we can now pass arbitrary args
|
|
||||||
// in...
|
|
||||||
// This is equivalent to:
|
|
||||||
// return new _constructor(json)
|
|
||||||
var obj = {}
|
|
||||||
obj.__proto__ = _constructor.prototype
|
|
||||||
// XXX for some reason this does not resolve from .__proto__
|
|
||||||
// XXX this also is a regular attr and not a prop...
|
|
||||||
//obj.constructor = _constructor
|
|
||||||
Object.defineProperty(obj, 'constructor', {
|
|
||||||
value: _constructor,
|
|
||||||
enumerable: false,
|
|
||||||
})
|
|
||||||
//obj.__proto__.constructor = _constructor
|
|
||||||
|
|
||||||
// explicit init...
|
|
||||||
if(proto instanceof Function){
|
|
||||||
proto.apply(obj, arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
// load initial state...
|
|
||||||
if(obj.__init__ != null){
|
|
||||||
obj.__init__.apply(obj, arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
/* XXX for some reason this works for the _constructor but all
|
|
||||||
* instances get the wrong name resolved...
|
|
||||||
Object.defineProperty(_constructor, 'name', {
|
|
||||||
value: name,
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
|
|
||||||
// just in case the browser refuses to change the name, we'll make it
|
|
||||||
// a different offer ;)
|
|
||||||
if(_constructor.name == 'Constructor'){
|
|
||||||
// skip for chrome app...
|
|
||||||
//&& !(window.chrome && chrome.runtime && chrome.runtime.id)){
|
|
||||||
eval('_constructor = '+ _constructor
|
|
||||||
.toString()
|
|
||||||
.replace(/Constructor/g, name))
|
|
||||||
}
|
|
||||||
|
|
||||||
_constructor.__proto__ = cls_proto
|
|
||||||
_constructor.prototype = proto
|
|
||||||
_constructor.prototype.constructor = _constructor
|
|
||||||
|
|
||||||
return _constructor
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// super equivalent...
|
|
||||||
//
|
|
||||||
// superMethod(<class>, <method-name>).call(this, ...)
|
|
||||||
// -> <result>
|
|
||||||
//
|
|
||||||
// This will return a next method in inheritance chain after <class> by
|
|
||||||
// its name (<method-name>).
|
|
||||||
// In the normal use-case <class> is the current class and <method-name>
|
|
||||||
// is the name of the current method.
|
|
||||||
var superMethod =
|
|
||||||
module.superMethod =
|
|
||||||
function superMethod(cls, meth){
|
|
||||||
return cls.prototype.__proto__[meth]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************
|
|
||||||
* vim:set ts=4 sw=4 : */ return module })
|
|
||||||
@ -15,6 +15,36 @@ if((typeof(process) != 'undefined' ? process : {}).__nwjs){
|
|||||||
.addPath(path.dirname(process.execPath) + '/node_modules/')
|
.addPath(path.dirname(process.execPath) + '/node_modules/')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//*
|
||||||
|
// Setup modules loaded from npm...
|
||||||
|
//
|
||||||
|
// XXX for some reason this breaks in browser if run after the if below...
|
||||||
|
// XXX not sure if this strategy is correct...
|
||||||
|
// ...most likely this is not actually a good idea, need to think of
|
||||||
|
// a way of organizing things without so much manual hoop jumping...
|
||||||
|
var requirejs_cfg = {
|
||||||
|
paths: {
|
||||||
|
// XXX one approach to avoid at least this section is to copy the
|
||||||
|
// modules to lib/*, this way we'll need the map section below
|
||||||
|
// only... (without automation this also sounds bad)
|
||||||
|
'lib/object': './node_modules/ig-object/object',
|
||||||
|
'lib/actions': './node_modules/ig-features/actions',
|
||||||
|
'lib/features': './node_modules/ig-features/features',
|
||||||
|
},
|
||||||
|
map: {
|
||||||
|
'*': {
|
||||||
|
// back-refs
|
||||||
|
// ...these enable the npm modules reference each other in
|
||||||
|
// a cross-platform manner....
|
||||||
|
'ig-object': 'lib/object',
|
||||||
|
'ig-actions': 'lib/actions',
|
||||||
|
'ig-features': 'lib/features',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// config the browser version of requirejs...
|
||||||
|
requirejs.config(requirejs_cfg)
|
||||||
|
//*/
|
||||||
|
|
||||||
// Setup requirejs if we are in node/nw...
|
// Setup requirejs if we are in node/nw...
|
||||||
//
|
//
|
||||||
@ -26,18 +56,27 @@ if((typeof(process) != 'undefined' ? process : {}).__nwjs){
|
|||||||
// XXX setting nodeRequire on existing requirejs will change how
|
// XXX setting nodeRequire on existing requirejs will change how
|
||||||
// everything is loaded...
|
// everything is loaded...
|
||||||
if(typeof(process) != 'undefined'){
|
if(typeof(process) != 'undefined'){
|
||||||
var requirejs =
|
requirejs =
|
||||||
global.requirejs =
|
global.requirejs =
|
||||||
window.requirejs =
|
window.requirejs =
|
||||||
|
// XXX for some reason we can't just use the browser requirejs
|
||||||
|
// even if we pass it nodeRequire, it still can't pass the
|
||||||
|
// node stuff to node...
|
||||||
require('requirejs')
|
require('requirejs')
|
||||||
|
|
||||||
var nodeRequire =
|
// config the node version of requirejs...
|
||||||
|
requirejs.config(requirejs_cfg)
|
||||||
|
|
||||||
|
nodeRequire =
|
||||||
global.nodeRequire =
|
global.nodeRequire =
|
||||||
window.nodeRequire =
|
window.nodeRequire =
|
||||||
require
|
require
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*********************************************************************/
|
/*********************************************************************/
|
||||||
(typeof(define)[0]=='u'?function(f){module.exports=f(require)}:define)(
|
(typeof(define)[0]=='u'?function(f){module.exports=f(require)}:define)(
|
||||||
function(require){ var module={} // makes module AMD/node compatible...
|
function(require){ var module={} // makes module AMD/node compatible...
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user