From 9c2d886b75a7fd5175c889d838595ab92aa365b3 Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Sat, 20 Jun 2015 20:48:45 +0300 Subject: [PATCH] split out the toggler constructor... Signed-off-by: Alex A. Naanou --- ui (gen4)/index.html | 1 + ui (gen4)/lib/toggler.js | 301 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 302 insertions(+) create mode 100755 ui (gen4)/lib/toggler.js diff --git a/ui (gen4)/index.html b/ui (gen4)/index.html index 26e40693..dd66923b 100755 --- a/ui (gen4)/index.html +++ b/ui (gen4)/index.html @@ -171,6 +171,7 @@ require('nw.gui').Window.get().showDevTools() + diff --git a/ui (gen4)/lib/toggler.js b/ui (gen4)/lib/toggler.js new file mode 100755 index 00000000..dd927c7f --- /dev/null +++ b/ui (gen4)/lib/toggler.js @@ -0,0 +1,301 @@ +/********************************************************************** +* +**********************************************************************/ + + + +/*********************************************************************/ + +// This will create a function that will cycle through a class_list on elem +// calling the optional callbacks before and/or after. +// If class_list is given as a string, then this will create a toggler that +// will turn the given class on the element on and off. +// +// Elem is a jquery compatible object; default use-case: a css selector. +// +// This will return a function with the folowing signature: +// +// func() -> +// func() -> +// func(, ) -> +// +// +// In the first form this just toggles the state. +// +// In forms 2 and 3, if class_list is a string, the can be : +// - : 0 for 'off' and 1 for 'on' (see below) +// - 'on' : switch mode on -- add class +// - 'off' : switch mode off -- remove class +// - '!' : reload current state, same as toggler(toggler('?')) +// - '?' : return current state ('on'|'off') +// +// In forms 2 and 3, if class_list is a list of strings, the can be: +// - : explicitly set the state to index in class_list +// - : explicitly set a class from the list +// - '!' : reload current state, same as toggler(toggler('?')) +// - '?' : return current state ('on'|'off') +// +// +// In the third form the is a jquery-compatible object. +// +// In all forms this will return the current state string or null if the +// action argument given is invalid. +// +// NOTE: action '?' is handled internally and not passed to the callbacks. +// NOTE: there is a special action 'next', passing it will have the same +// effect as not passing any action -- we will change to the next +// state. +// NOTE: if it is needed to apply this to an explicit target but with +// no explicit action, just pass 'next' as the second argument. +// NOTE: a special class name 'none' means no class is set, if it is present +// in the class_list then that state will be with all other state +// classes removed. +// NOTE: must be an exact match to a string given in class_list +// NOTE: of only one callback is given then it will be called after the +// class change... +// a way around this is to pass an empty function as callback_b +// NOTE: leading dots in class names in class_list are optional. +// this is due to several times I've repeated the same mistake of +// forgetting to write the classes without leading dots, the class +// list is not normalized... +// NOTE: the toggler can be passed a non-jquery object, but then only an +// explicit state is supported as the second argument, the reason +// being that we can not determain the current state without a propper +// .hasClass(..) test... +// +// +// This also takes one or two callbacks. If only one is given then it is +// called after (post) the change is made. If two are given then the first +// is called before the change and the second after the change. +// +// The callbacks are passed two arguments: +// - : the state we are going in +// - : the target element or the element passed to the +// toggler +// +// +// The callback function will have 'this' set to the same value as the +// toggler itself, e.g. if the toggler is called as a method, the +// callback's 'this' will reference it's parent object. +// +// NOTE: the pre-callback will get the "intent" action, i.e. the state the +// we are changing into but the changes are not yet made. +// NOTE: if the pre-callback explicitly returns false, then the change will +// not be made. +// +// XXX revize/update this doc for Toggler(..) + + + +// Make a generic toggler function/method... +// +// state_accessor signature: +// +// Get current state: +// state_accessor() +// -> +// +// Set new state: +// state_accessor() +// -> +// +// NOTE: for single state toggling, 'none' will get passed to +// state_accessor to indicate an "empty" state... +// NOTE: if elem is a function it will be called in the same context as +// the toggler and is expected to return the element. +// +// XXX technically we do not need both elem and state_accessor here, the +// later is enough, but as strict mode is not stable enough (sometimes +// works and sometimes does not), we can not reliably pass the element +// via 'this'. +function Toggler(elem, 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') + + // NOTE: this needs to be strict so as to be able to distinguish + // between a method and a root context in a simple manner... + var func = function(a, b){ + // XXX for some magical reason this does not work... + 'use strict' + + // parse arguments... + if(b == null){ + var action = a == 'next' ? null : a + // XXX is this correct??? + //var e = this + var e = elem + } else { + var e = a + var action = b == 'next' ? null : b + } + + e = e instanceof Function ? e.call(this) : e + + // XXX is this correct??? + var args = args2array(arguments).slice(2) + + // 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[action == 'off' ? 0 : 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.' + } + + func.__proto__ = Toggler.prototype + func.constructor = Toggler + + return func +} +Toggler.prototype.__proto__ = Function.prototype + + +// XXX this should be drop-in compatible with createCSSClassToggler(..) +// test and replace... +function CSSClassToggler(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(' ') + }) + + var toggler = Toggler( + elem, + function(state){ + 'use strict' + 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' && state != 'off'){ + e.addClass(state) + } + } + }, + classes, + callback_a, + callback_b) + + toggler.__proto__ = CSSClassToggler.prototype + toggler.constructor = CSSClassToggler + + return toggler +} + +CSSClassToggler.prototype.__proto__ = Toggler.prototype + + + +/********************************************************************** +* vim:set ts=4 sw=4 : */