From 77b0759ac731e1a4e3029162961e64ba8059277e Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Fri, 5 Dec 2014 00:10:01 +0300 Subject: [PATCH] split out the feature mechanics... Signed-off-by: Alex A. Naanou --- ui (gen4)/lib/features.js | 311 ++++++++++++++++++++++++++++++++++++ ui (gen4)/ui.js | 2 +- ui (gen4)/viewer.js | 323 +++----------------------------------- 3 files changed, 330 insertions(+), 306 deletions(-) create mode 100755 ui (gen4)/lib/features.js diff --git a/ui (gen4)/lib/features.js b/ui (gen4)/lib/features.js new file mode 100755 index 00000000..3925661f --- /dev/null +++ b/ui (gen4)/lib/features.js @@ -0,0 +1,311 @@ +/********************************************************************** +* +* +* +**********************************************************************/ + +define(function(require){ var module = {} +console.log('>>> features') + +//var DEBUG = DEBUG != null ? DEBUG : true + + +/*********************************************************************/ +// +// 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. +// .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 +// .handlers - feature event handlers (list | null) +// +// +// +// .handlers format: +// [ +// [ , ], +// ... +// ] +// +// NOTE: both and must be compatible with +// Action.on(..) +// +// 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... + // XXX should this use inheritance??? + if(this.config != null + || (this.actions != null + && this.actions.config != null)){ + var config = this.config || this.actions.config + + if(actions.config == null){ + actions.config = {} + } + Object.keys(config).forEach(function(n){ + // keep existing keys... + if(actions.config[n] === undefined){ + actions.config[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??? +var Feature = +module.Feature = +function Feature(feature_set, obj){ + if(obj == null){ + obj = feature_set + // XXX is this a good default??? + feature_set = Features + } + + obj.__proto__ = FeatureProto + + if(feature_set){ + feature_set[obj.tag] = obj + } + + return obj +} +Feature.prototype = FeatureProto +Feature.prototype.constructor = Feature + + +// XXX experimental... +// ...not sure if the global feature set is a good idea... +// XXX if this works out might be a good idea to organize everything as +// a feature... including the Client and Viewer +// ...needs more thought... +var FeatureSet = +module.FeatureSet = { + buildFeatureList: function(obj, lst){ + lst = lst.constructor !== Array ? [lst] : lst + + var that = this + + // sort features via priority... + lst = lst.sort(function(a, b){ + a = that[a] == null ? 0 : that[a].getPriority() + b = that[b] == null ? 0 : that[b].getPriority() + return b - a + }) + + // sort features via dependencies... + var unapplicable = [] + var conflicts = {} + lst = lst.filter(function(n, i){ + var e = that[n] + if(e == null){ + return true + } + + // check applicability... + if(!e.isApplicable(obj)){ + unapplicable.push(n) + return false + } + + // no dependencies... + if(e.depends == null ){ + return true + } + + // keep only conflicting... + var deps = e.depends.filter(function(dep){ + dep = lst.indexOf(dep) + return dep == -1 || dep > i + }) + + // no conflicts... + if(deps.length == 0){ + return true + } + + // can't fix... + conflicts[n] = deps + + return false + }) + + // skip duplicate exclusive features... + var exclusive = [] + var excluded = [] + lst = lst.filter(function(n){ + var e = that[n] + if(e == null || 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 + + if(!res){ + excluded.push(n) + } + + return res + }) + + return { + features: lst, + excluded: excluded, + conflicts: conflicts, + unapplicable: unapplicable, + } + }, + + setup: function(obj, lst){ + 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){ + var c = features.conflicts + + // build a report... + var report = [] + Object.keys(c).forEach(function(k){ + report.push(k + ': must setup after:\n ' + c[k].join(', ')) + }) + throw 'Feature dependency error:\n ' + report.join('\n ') + } + + // report excluded features... + if(features.excluded.length > 0){ + console.warn('Excluded features due to exclusivity conflict:', + features.excluded.join(', ')) + } + + // report unapplicable features... + if(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){ + console.log('Setting up feature:', n) + setup.call(that[n], 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) + } + }) + }, +} + + +//--------------------------------------------------------------------- + +var Features = +module.Features = Object.create(FeatureSet) + + + +/********************************************************************** +* vim:set ts=4 sw=4 : */ +return module }) diff --git a/ui (gen4)/ui.js b/ui (gen4)/ui.js index fff1cfd5..94142f81 100755 --- a/ui (gen4)/ui.js +++ b/ui (gen4)/ui.js @@ -210,7 +210,7 @@ $(function(){ a.setEmptyMsg('Loading...') - viewer.Features.setup(a, [ + viewer.ImageGridFeatures.setup(a, [ // features... 'ui-partial-ribbons', 'ui-ribbon-align-to-order', diff --git a/ui (gen4)/viewer.js b/ui (gen4)/viewer.js index c5eae256..e7152152 100755 --- a/ui (gen4)/viewer.js +++ b/ui (gen4)/viewer.js @@ -10,6 +10,7 @@ console.log('>>> viewer') //var DEBUG = DEBUG != null ? DEBUG : true var actions = require('lib/actions') +var features = require('lib/features') var data = require('data') var images = require('images') @@ -1136,297 +1137,9 @@ actions.Actions(Client, { /*********************************************************************/ -// -// 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. -// .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 -// .handlers - feature event handlers (list | null) -// -// -// -// .handlers format: -// [ -// [ , ], -// ... -// ] -// -// NOTE: both and must be compatible with -// Action.on(..) -// -// 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... - // XXX should this use inheritance??? - if(this.config != null - || (this.actions != null - && this.actions.config != null)){ - var config = this.config || this.actions.config - - if(actions.config == null){ - actions.config = {} - } - Object.keys(config).forEach(function(n){ - // keep existing keys... - if(actions.config[n] === undefined){ - actions.config[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??? -var Feature = -module.Feature = -function Feature(feature_set, obj){ - if(obj == null){ - obj = feature_set - // XXX is this a good default??? - feature_set = Features - } - - obj.__proto__ = FeatureProto - - if(feature_set){ - feature_set[obj.tag] = obj - } - - return obj -} -Feature.prototype = FeatureProto -Feature.prototype.constructor = Feature - - -// XXX experimental... -// ...not sure if the global feature set is a good idea... -// XXX if this works out might be a good idea to organize everything as -// a feature... including the Client and Viewer -// ...needs more thought... -var FeatureSet = -module.FeatureSet = { - buildFeatureList: function(obj, lst){ - lst = lst.constructor !== Array ? [lst] : lst - - var that = this - - // sort features via priority... - lst = lst.sort(function(a, b){ - a = that[a] == null ? 0 : that[a].getPriority() - b = that[b] == null ? 0 : that[b].getPriority() - return b - a - }) - - // sort features via dependencies... - var unapplicable = [] - var conflicts = {} - lst = lst.filter(function(n, i){ - var e = that[n] - if(e == null){ - return true - } - - // check applicability... - if(!e.isApplicable(obj)){ - unapplicable.push(n) - return false - } - - // no dependencies... - if(e.depends == null ){ - return true - } - - // keep only conflicting... - var deps = e.depends.filter(function(dep){ - dep = lst.indexOf(dep) - return dep == -1 || dep > i - }) - - // no conflicts... - if(deps.length == 0){ - return true - } - - // can't fix... - conflicts[n] = deps - - return false - }) - - // skip duplicate exclusive features... - var exclusive = [] - var excluded = [] - lst = lst.filter(function(n){ - var e = that[n] - if(e == null || 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 - - if(!res){ - excluded.push(n) - } - - return res - }) - - return { - features: lst, - excluded: excluded, - conflicts: conflicts, - unapplicable: unapplicable, - } - }, - - setup: function(obj, lst){ - 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){ - var c = features.conflicts - - // build a report... - var report = [] - Object.keys(c).forEach(function(k){ - report.push(k + ': must setup after:\n ' + c[k].join(', ')) - }) - throw 'Feature dependency error:\n ' + report.join('\n ') - } - - // report excluded features... - if(features.excluded.length > 0){ - console.warn('Excluded features due to exclusivity conflict:', - features.excluded.join(', ')) - } - - // report unapplicable features... - if(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){ - console.log('Setting up feature:', n) - setup.call(that[n], 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) - } - }) - }, -} - - -var Features = -module.Features = Object.create(FeatureSet) +var ImageGridFeatures = +module.ImageGridFeatures = Object.create(features.FeatureSet) //--------------------------------------------------------------------- @@ -1551,7 +1264,7 @@ var PartialRibbonsActions = actions.Actions({ // XXX The two should be completely independent.... (???) // XXX need to test and tweak with actual images... var PartialRibbons = -module.PartialRibbons = Feature({ +module.PartialRibbons = features.Feature(ImageGridFeatures, { title: 'Partial Ribbons', doc: 'Maintains partially loaded ribbons, this enables very lage ' +'image sets to be hadled eficiently.', @@ -1696,7 +1409,7 @@ function updateImageProportions(){ // // var SingleImageView = -module.SingleImageView = Feature({ +module.SingleImageView = features.Feature(ImageGridFeatures, { title: '', doc: '', @@ -1738,7 +1451,7 @@ module.SingleImageView = Feature({ // XXX should .alignByOrder(..) be a feature-specific action or global // as it is now??? var AlignRibbonsToImageOrder = -module.AlignRibbonsToImageOrder = Feature({ +module.AlignRibbonsToImageOrder = features.Feature(ImageGridFeatures, { title: '', doc: '', @@ -1754,7 +1467,7 @@ module.AlignRibbonsToImageOrder = Feature({ //--------------------------------------------------------------------- var AlignRibbonsToFirstImage = -module.AlignRibbonsToFirstImage = Feature({ +module.AlignRibbonsToFirstImage = features.Feature(ImageGridFeatures, { title: '', doc: '', @@ -1771,7 +1484,7 @@ module.AlignRibbonsToFirstImage = Feature({ // XXX at this point this does not support target lists... var ShiftAnimation = -module.ShiftAnimation = Feature({ +module.ShiftAnimation = features.Feature(ImageGridFeatures, { title: '', doc: '', @@ -1856,7 +1569,7 @@ function didAdvance(indicator){ } var BoundsIndicators = -module.BoundsIndicators = Feature({ +module.BoundsIndicators = features.Feature(ImageGridFeatures, { title: '', doc: '', @@ -2012,7 +1725,7 @@ var CurrentImageIndicatorActions = actions.Actions({ }) var CurrentImageIndicator = -module.CurrentImageIndicator = Feature({ +module.CurrentImageIndicator = features.Feature(ImageGridFeatures, { title: '', doc: '', @@ -2108,7 +1821,7 @@ module.CurrentImageIndicator = Feature({ // XXX this depends on CurrentImageIndicator... var CurrentImageIndicatorHideOnFastScreenNav = -module.CurrentImageIndicatorHideOnFastScreenNav = Feature({ +module.CurrentImageIndicatorHideOnFastScreenNav = features.Feature(ImageGridFeatures, { title: '', doc: '', @@ -2172,7 +1885,7 @@ module.CurrentImageIndicatorHideOnFastScreenNav = Feature({ // XXX this depends on CurrentImageIndicator... var CurrentImageIndicatorHideOnScreenNav = -module.CurrentImageIndicatorHideOnScreenNav = Feature({ +module.CurrentImageIndicatorHideOnScreenNav = features.Feature(ImageGridFeatures, { title: '', doc: '', @@ -2210,7 +1923,7 @@ module.CurrentImageIndicatorHideOnScreenNav = Feature({ // XXX var ImageStateIndicator = -module.ImageStateIndicator = Feature({ +module.ImageStateIndicator = features.Feature(ImageGridFeatures, { title: '', doc: '', @@ -2223,7 +1936,7 @@ module.ImageStateIndicator = Feature({ // XXX var GlobalStateIndicator = -module.GlobalStateIndicator = Feature({ +module.GlobalStateIndicator = features.Feature(ImageGridFeatures, { title: '', doc: '', @@ -2245,7 +1958,7 @@ module.GlobalStateIndicator = Feature({ // XXX need to get the minimal size and not the width as results will // depend on viewer format... var AutoSingleImage = -module.AutoSingleImage = Feature({ +module.AutoSingleImage = features.Feature(ImageGridFeatures, { title: '', doc: '', @@ -2381,7 +2094,7 @@ var ImageMarkActions = actions.Actions({ var ImageMarks = -module.ImageMarks = Feature({ +module.ImageMarks = features.Feature(ImageGridFeatures, { title: '', doc: '', @@ -2437,7 +2150,7 @@ var ImageBookmarkActions = actions.Actions({ var ImageBookmarks = -module.ImageBookmarks = Feature({ +module.ImageBookmarks = features.Feature(ImageGridFeatures, { title: '', doc: '', @@ -2459,7 +2172,7 @@ module.ImageBookmarks = Feature({ //--------------------------------------------------------------------- var FileSystemLoader = -module.FileSystemLoader = Feature({ +module.FileSystemLoader = features.Feature(ImageGridFeatures, { title: '', doc: '',