Alien-GvaScript

 view release on metacpan or  search on metacpan

lib/Alien/GvaScript/lib/GvaScript.js  view on Meta::CPAN

            }
        }

        if (separator.lastIndex === match.index) {
            separator.lastIndex++; // avoid an infinite loop
        }
    }

    if (lastLastIndex === str.length) {
        if (lastLength || !separator.test("")) {
            output.push("");
        }
    } else {
        output.push(str.slice(lastLastIndex));
    }

    return output.length > limit ? output.slice(0, limit) : output;
};

cbSplit._compliantExecNpcg = /()??/.exec("")[1] === undefined; // NPCG: nonparticipating capturing group
cbSplit._nativeSplit = String.prototype.split;

} // end `if (!cbSplit)`

// for convenience...
String.prototype.split = function (separator, limit) {
    return cbSplit(this, separator, limit);
};


/**
 * Event Delegation
 * Based on http://code.google.com/p/protolicious/source/browse/trunk/src/event.register.js
 * modified to support focus/blur event capturing
 * [http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html]
 *
 * Prototype core is supposed to have this in v 1.7 !
 * Naming might differ, Event.register -> Event.delegate but at least
 * will have the same syntax
 */
// wrap in an anonymous function to avoid any variable conflict
(function() {
  var rules = { };
  var exprSplit = function(expression) {
    var expressions = [];
    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
      expressions.push(m[1].strip());
    });
    return expressions;
  }
  var eventManager = function(o_id, event) {
    // IE sometimes fires some events
    // while reloading (after unregister)
    if(! rules[o_id]) return;

    var element = event.target;
    var eventType = (event.memo)? event.eventName : event.type;
    do {
      if (element.nodeType == 1) {
        element = Element.extend(element);
        for (var selector in rules[o_id][eventType]) {
          if (_match = matches(rules[o_id][eventType][selector]._selector, element)) {
            for (var i=0, handlers=rules[o_id][eventType][selector], l=handlers.length; i<l; ++i) {
              handlers[i].call(element, Object.extend(event, { _target: element, _match: _match }));
            }
          }
        }
      }
    } while (element = element.parentNode)
  }
  var matches = function(selectors, element) {
    for (var i=0, l=selectors.length; i<l; ++i) {
      if (Prototype.Selector.match(element, selectors[i])) return selectors[i];
    }
    return undefined;
  }

  Event.register = function(observer, selector, eventName, handler) {
    var use_capture = (eventName == 'focus' || eventName == 'blur');
    if(use_capture && Prototype.Browser.IE) {
        eventName = (eventName == 'focus')? 'focusin' : 'focusout';
    }
    var observer_id = observer.identify ? observer.identify() : 'document';

    // create entry in cache for rules per observer
    if(! rules[observer_id]) {
        rules[observer_id] = { };
    }

    // observe event only once on the same observer
    if(! rules[observer_id][eventName]) {
      rules[observer_id][eventName] = { };

      if(use_capture) {
        if(Prototype.Browser.IE)
        Event.observe(observer, eventName, eventManager.curry(observer_id));
        else
        observer.addEventListener(eventName, eventManager.curry(observer_id), true);
      }
      else
      Event.observe(observer, eventName, eventManager.curry(observer_id));
    }

    var _selector = [ ], expr = selector.strip();
    // instantiate Selector's
    exprSplit(selector).each(function(s) { _selector.push(s) })

    // store instantiated Selector for faster matching
    if (!rules[observer_id][eventName][expr]) {
      rules[observer_id][eventName][expr] = Object.extend([ ], { _selector: _selector });
    }

    // associate handler with expression
    rules[observer_id][eventName][expr].push(handler);
  }

  // unregistering an event on an elemment
  Event.unregister = function(elt, selector, eventName) {
    var _id = (typeof elt == 'string')? elt :
              (elt.identify)? elt.identify() : 'document';
    // unregister event identified by name and selector
    if (eventName) {
      rules[_id][eventName][selector] = null;
      delete rules[_id][eventName][selector];
    }
    else {
      for (var eventName in rules[_id]) {
        // unregister all events identified by selector
        if(selector) {
          rules[_id][eventName][selector] = null;
          delete rules[_id][eventName][selector];
        }
        // unregister all events
        else {
          rules[_id][eventName] = null;
          delete rules[_id][eventName];
        }
      }
    }
  },

  // unregister *all* events registered using
  // the Event.register method
  Event.unregisterAll = function() {
    for(var _id in rules) {
        Event.unregister(_id);
        delete rules[_id];
    }
  }

  Event.observe(window, 'unload', Event.unregisterAll);
  document.register = Event.register.curry(document);
  Element.addMethods({register: Event.register, unregister: Event.unregister});
})();

// based on:
// getJSON function by Juriy Zaytsev
// http://github.com/kangax/protolicious/tree/master/get_json.js
(function(){
  var id = 0, head = $$('head')[0];
  Prototype.getJSON = function(url, callback) {
    var script = document.createElement('script'), token = '__jsonp' + id;

    // callback should be a global function
    window[token] = callback;

    // url should have "?2" parameter which is to be replaced with a global callback name
    script.src = url.replace(/\?(&|$)/, '__jsonp' + id + '$1');

    // clean up on load: remove script tag, null script variable and delete global callback function
    script.onload = function() {
      script.remove();
      script = null;
      delete window[token];
    };
    head.appendChild(script);

    // callback name should be unique
    id++;
  }
})();

//----------event.js
// array holding fired events that are pending to be executed
// useful for avoiding accidental double firing of events
// events in queue are unique per eventType&eventTarget
GvaScript.eventsQueue = Class.create();
Object.extend(GvaScript.eventsQueue, {
    _queue: $A([]),
    hasEvent: function(target, name) {
        return (typeof this._queue.find(function(e) {

lib/Alien/GvaScript/lib/GvaScript.js  view on Meta::CPAN

        120: "F9",
        121: "F10",
        122: "F11",
        123: "F12",
        144: "NUM_LOCK",
        145: "SCROLL_LOCK",
        190: "DOT"
    },
    META_KEYS: {
        8   : 'KEY_BACKSPACE',
        9   : 'KEY_TAB',
        13  : 'KEY_RETURN',
        27  : 'KEY_ESC',
        37  : 'KEY_LEFT',
        38  : 'KEY_UP',
        39  : 'KEY_RIGHT',
        40  : 'KEY_DOWN',
        46  : 'KEY_DELETE',
        36  : 'KEY_HOME',
        35  : 'KEY_END',
        33  : 'KEY_PAGEUP',
        34  : 'KEY_PAGEDOWN',
        45  : 'KEY_INSERT',
        112 : "F1",
        113 : "F2",
        114 : "F3",
        115 : "F4",
        116 : "F5",
        117 : "F6",
        118 : "F7",
        119 : "F8",
        120 : "F9",
        121 : "F10",
        122 : "F11",
        123 : "F12"
    }
}

//----------treeNavigator.js
//-----------------------------------------------------
// Constructor
//-----------------------------------------------------

GvaScript.TreeNavigator = function(elem, options) {

  // fix bug of background images on dynamic divs in MSIE 6.0, see URLs
  // http://www.bazon.net/mishoo/articles.epl?art_id=958
  // http://misterpixel.blogspot.com/2006/09/forensic-analysis-of-ie6.html
  try { document.execCommand("BackgroundImageCache",false,true); }
  catch(e) {};

  elem = $(elem); // in case we got an id instead of an element
  options = options || {};

  // default options
  var defaultOptions = {
    tabIndex            : -1,
    treeTabIndex        :  0,
    flashDuration       : 200,     // milliseconds
    flashColor          : "red",
    selectDelay         : 100,     // milliseconds
    selectOnButtonClick : true,
    noPingOnFirstClick  : false,
    selectFirstNode     : true,
    createButtons       : true,
    scrollingContainer  : elem.ownerDocument.documentElement,
    autoScrollPercentage: 20,
    classes             : {},
    keymap              : null
  };

  this.options = Class.checkOptions(defaultOptions, options);

  // values can be single class names or arrays of class names
  var defaultClasses = {
    node     : "TN_node",
    leaf     : "TN_leaf",
    label    : "TN_label",
    closed   : "TN_closed",
    content  : "TN_content",
    selected : "TN_selected",
    mouse    : "TN_mouse",
    button   : "TN_button",
    showall  : "TN_showall"
  };
  this.classes = Class.checkOptions(defaultClasses, this.options.classes);
  this.classes.nodeOrLeaf = [this.classes.node, this.classes.leaf].flatten();

  // connect to the root element
  this.rootElement = elem;

  // add buttons and tabIndex to labels
  this.initSubTree(elem);

  // tree-wide navigation handlers
  this._addHandlers();
  // tree-wide tabbing handlers
  this._addTabbingBehaviour();

  // initializing the keymap
  var keyHandlers = {
    DOWN:       this._downHandler   .bindAsEventListener(this),
    UP:         this._upHandler     .bindAsEventListener(this),
    LEFT:       this._leftHandler   .bindAsEventListener(this),
    RIGHT:      this._rightHandler  .bindAsEventListener(this),
    KP_PLUS:    this._kpPlusHandler .bindAsEventListener(this),
    KP_MINUS:   this._kpMinusHandler.bindAsEventListener(this),
    KP_STAR:    this._kpStarHandler .bindAsEventListener(this),
    KP_SLASH:   this._kpSlashHandler.bindAsEventListener(this),
    C_R:        this._ctrl_R_handler.bindAsEventListener(this),
    RETURN:     this._ReturnHandler .bindAsEventListener(this),
    C_KP_STAR:  this._showAll       .bindAsEventListener(this, true),
    C_KP_SLASH: this._showAll       .bindAsEventListener(this, false),
    HOME:       this._homeHandler   .bindAsEventListener(this),
    END:        this._endHandler    .bindAsEventListener(this),

    C_PAGE_UP  : this._ctrlPgUpHandler  .bindAsEventListener(this),
    C_PAGE_DOWN: this._ctrlPgDownHandler.bindAsEventListener(this),

    REGEX      : [[ "", /^\w$/, this._charHandler.bindAsEventListener(this) ]]
  };

  if (this.options.tabIndex >= 0)
    keyHandlers["TAB"] = this._tabHandler.bindAsEventListener(this);

  // handlers for ctrl_1, ctrl_2, etc. to open the tree at that level
  var numHandler = this._chooseLevel.bindAsEventListener(this);
  $R(1, 9).each(function(num){keyHandlers["C_" + num] = numHandler});

  // tabIndex for the tree element
  elem.tabIndex = Math.max(elem.tabIndex, this.options.treeTabIndex);

  this._clear_quick_navi();

  if (options.keymap) {
    this.keymap = options.keymap;
    this.keymap.rules.push(keyHandlers);
  }
  else {
    this.keymap = new GvaScript.KeyMap(keyHandlers);

    // observe keyboard events on tree (preferred) or on document
    var target = (elem.tabIndex  < 0) ? document : elem;
    this.keymap.observe("keydown", target, Event.stopNone);
  }

  this.rootElement.store('widget', this);
  this.rootElement.addClassName(CSSPREFIX() + '-widget');

  // selecting the first node
  if (this.options.selectFirstNode) {
    this.select(this.firstSubNode());

    // if labels do not take focus but tree does, then set focus on the tree
    if (this.options.tabIndex < 0 && elem.tabIndex >= 0)
      elem.focus();
  }
}


GvaScript.TreeNavigator.prototype = {

//-----------------------------------------------------
// Public methods
//-----------------------------------------------------
  destroy: function() {
    this._removeHandlers();
  },

  initSubTree: function (tree_root) {
    tree_root = $(tree_root);
    // get the labels of the sub tree
    var labels = tree_root.select('.'+this.classes.label);

    // add tabIndex per label
    if (this.options.tabIndex >= 0) {
      _idx = this.options.tabIndex;
      labels.each(function(label) {
        label.tabIndex = _idx;
      });
    }

    // add tree navigation buttons per label
    if (this.options.createButtons) {
      var button = document.createElement("span");
      button.className = this.classes.button;

      labels.each(function(label) {
        label.parentNode.insertBefore(button.cloneNode(true), label);
      });
    }
  },

  isClosed: function (node) {
    return Element.hasAnyClass(node, this.classes.closed);
  },

  isVisible: function(elem) { // true if elem is not display:none
    return !(elem.offsetWidth == 0 && elem.offsetHeight == 0);
  },

  isLeaf: function(node) {
    return Element.hasAnyClass(node, this.classes.leaf);
  },

  isRootElement: function(elem) {
    return (elem === this.rootElement);
  },

  isLabel: function(elem) {
    if(elem.hasClassName(this.classes.label))
      return elem;
    else
      return Element.navigateDom(elem, 'parentNode', this.classes.label);
  },

  close: function (node) {
    if (this.isLeaf(node))
      return;
    Element.addClassName(node, this.classes.closed);
    this.fireEvent("Close", node, this.rootElement);
  },

  open: function (node) {
    if (this.isLeaf(node))
      return;

    Element.removeClassName(node, this.classes.closed);

    var node_content = this.content(node);
    // if inline content, adjust scrollbar to make visible (if necessary)
    // FIXME: only works for default scrollingContainer
    if (node_content) {

lib/Alien/GvaScript/lib/GvaScript.js  view on Meta::CPAN

    // ajax content -> go get it
    else {
      this.loadContent(node);
    }

    this.fireEvent("Open", node, this.rootElement);
  },

  toggle: function(node) {
    if (this.isClosed(node))
        this.open(node);
    else
        this.close(node);
  },

  openEnclosingNodes: function (elem) {
    var node = this.enclosingNode(elem);
    while (node) {
      if (this.isClosed(node))
        this.open(node);
      node = this.parentNode(node);
    }
  },

  openAtLevel: function(elem, level) {
    var method = this[(level > 1) ? "open" : "close"];
    var node = this.firstSubNode(elem);
    while (node) {
      method.call(this, node); // open or close
      this.openAtLevel(node, level-1);
      node = this.nextSibling(node);
    }
  },

  loadContent: function (node) {
    var url = node.getAttribute('tn:contenturl');
    // TODO : default URL generator at the tree level

    if (url) {
      var content = this.content(node);
      if (!content) {
        content = document.createElement('div');
        content.className = this.classes.content;
        var content_type = node.getAttribute('content_type');
        if (content_type) content.className += " " + content_type;
        content.innerHTML = "loading " + url;
        node.insertBefore(content, null); // null ==> insert at end of node
      }
      this.fireEvent("BeforeLoadContent", node, this.rootElement);

      var treeNavigator = this; // needed for closure below
      var callback = function() {
        treeNavigator.initSubTree(content);
        treeNavigator.fireEvent("AfterLoadContent", node, this.rootElement);
      };
      new Ajax.Updater(content, url, {onComplete: callback});
      return true;
    }
  },

  select: function (node) {
    var previousNode = this.selectedNode;

    // re-selecting the current node is a no-op
    if (node == previousNode) return;

    // deselect the previously selected node
    if (previousNode) {
        var label = this.label(previousNode);
        if (label) {
          Element.removeClassName(label, this.classes.selected);
        }
    }

    // select the new node
    this.selectedNode = node;
    if (node) {
      this._assertNodeOrLeaf(node, 'select node');
      var label = this.label(node);
      if (!label) {
        throw new Error("selected node has no label");
      }
      else {
        Element.addClassName(label, this.classes.selected);

        if (this.isVisible(label)) {
          // focus has not yet been given to label
          if(! label.hasAttribute('hasFocus'))
            label.focus();
        }
      }
    }

    // cancel if there was any select execution pending
    if (this._selectionTimeoutId) clearTimeout(this._selectionTimeoutId);

    // register code to call the selection handlers after some delay
    var callback = this._selectionTimeoutHandler.bind(this, previousNode);
    this._selectionTimeoutId =
      setTimeout(callback, this.options.selectDelay);
  },

  scrollTo: function(node, with_content) {
    if(!node) return;

    var container = this.options.scrollingContainer;
    if(typeof container == 'string') {
      container = $(container);
    }
    if(!container) return;

    // donot invoke scroll if scrolling is disabled
    // first test if scrolling is enabled on the scrolling container
    if(container.tagName.toLowerCase() == 'html') {
      // on document body
      if(document.body.style.overflow == 'hidden'
        || document.body.style.overflowY == 'hidden'
        || document.body.scroll == 'no') // IE
      return;
    }
    else {
      // on element
      if(container.style.overflow == 'hidden'
        || container.style.overflowY == 'hidden')
      return;
    }

    // test if the node in 'in view'
    _container_y_start = container.scrollTop;
    _container_y_end   = _container_y_start + container.clientHeight;
    _node_y  = Element.cumulativeOffset(node).top + (with_content? node.offsetHeight: 0);

    // calculate padding space between the selected node and
    // the edge of the scrollable container
    _perc = this.options.autoScrollPercentage || 0;
    _padding = container.clientHeight * _perc / 100;

    // calculate delta scroll to affect on scrollingContainer
    _delta = 0;

    // node is beneath scrolling area
    if(_node_y > _container_y_end - _padding) {
      _delta = _node_y - _container_y_end + _padding;
    }

    // node is above scrolling area
    if(_node_y < _container_y_start + _padding) {
      _delta = _container_y_start - _node_y - _padding;
    }

    if(_delta != 0) {
      // amount required to scroll to greater than available document height
      if(_delta > container.clientHeight - _padding) {
        // make label top
        var lbl_pos = Element.cumulativeOffset(this.label(node)).top;
        container.scrollTop = lbl_pos - _padding;
      }
      else
      container.scrollTop += parseInt(_delta)
    }
    return;
  },

  label: function(node) {
    this._assertNodeOrLeaf(node, 'label: arg type');
    return Element.navigateDom(node.firstChild, 'nextSibling',
                               this.classes.label);
  },

  content: function(node) {
    if (this.isLeaf(node)) return null;
    this._assertNode(node, 'content: arg type');
    return Element.navigateDom(node.lastChild, 'previousSibling',
                               this.classes.content);
  },

  parentNode: function (node) {
    this._assertNodeOrLeaf(node, 'parentNode: arg type');
    return Element.navigateDom(
      node.parentNode, 'parentNode', this.classes.node,
      this.isRootElement.bind(this));
  },

  nextSibling: function (node) {
    this._assertNodeOrLeaf(node, 'nextSibling: arg type');
    return Element.navigateDom(node.nextSibling, 'nextSibling',
                               this.classes.nodeOrLeaf);

  },

  previousSibling: function (node) {
    this._assertNodeOrLeaf(node, 'previousSibling: arg type');
    return Element.navigateDom(node.previousSibling, 'previousSibling',

lib/Alien/GvaScript/lib/GvaScript.js  view on Meta::CPAN

    if(target.nodeType != 1) return;

    // ignore right mousedown
    if(!Event.isLeftClick(event)) return;

    // button clicked
    if(target.hasClassName(this.classes.button)) {
        // as not to fire blur_handler
        // on treeNode
        Event.stop(event);
        return this._buttonClicked(target.parentNode);
    }

    // label (or one of its childElements) clicked
    if(label = this.isLabel(target)) {
        return this._labelClicked(label.parentNode, event);
    }
  },

  _treeDblClickHandler : function(event) {
    var target = Event.element(event);
    if(target.nodeType != 1) return;

    // only consider doubleclicks on labels
    if(!(label = this.isLabel(target))) return;

    var event_stop_mode;

    // should_ping_on_dblclick was just set within _labelClicked
    if (this.should_ping_on_dblclick) {
      event_stop_mode = this.fireEvent("Ping", label.parentNode, this.rootElement);
    }

    // stop the event unless the ping_handler decided otherwise
    Event.detailedStop(event, event_stop_mode || Event.stopAll);
  },

  _treeMouseOverHandler: function(event) {
    var target = Event.element(event);
    if(target.nodeType != 1) return;

    if(label = this.isLabel(target)) {
      Element.addClassName(label, this.classes.mouse);
      Event.stop(event);
    }
  },

  _treeMouseOutHandler: function(event) {
    var target = Event.element(event);
    if(target.nodeType != 1) return;

    if(label = this.isLabel(target)) {
      Element.removeClassName(label, this.classes.mouse);
      Event.stop(event);
    }
  },

  _buttonClicked : function(node) {
    var method = this.isClosed(node) ? this.open : this.close;
    method.call(this, node);
    if (this.options.selectOnButtonClick) {
        window.setTimeout(function() {
            this.select(node);
        }.bind(this), 0);
    }
  },

  _labelClicked : function(node, event) {
    // situation before the mousedown
    var is_selected    = (this.selectedNode == node);
    var is_first_click = !is_selected;

    // select node if it wasn't
    if (!is_selected) this.select(node);

    // should ping : depends on options.noPingOnFirstClick
    var should_ping = (!is_first_click) || !this.options.noPingOnFirstClick;

    // do the ping if necessary
    var event_stop_mode;
    if (should_ping)
    event_stop_mode = this.fireEvent("Ping", node, this.rootElement);

    // avoid a second ping from the dblclick handler
    this.should_ping_on_dblclick = !should_ping;

    // stop the event unless the ping_handler decided otherwise
    Event.detailedStop(event, event_stop_mode || Event.stopAll);
  },

//-----------------------------------------------------
// Keyboard handlers
//-----------------------------------------------------
  _addTabbingBehaviour: function() {
    if (this.options.tabIndex < 0) return; // no tabbing

    var treeNavigator = this; // handlers will be closures on this

    // focus handler
    var focus_handler = function(e) {
      var label = e._target;
      label.writeAttribute('hasFocus', 'hasFocus');

      var node  = Element.navigateDom(label, 'parentNode',
                                      treeNavigator.classes.nodeOrLeaf);

      // not yet been selected
      if(node && !label.hasClassName(treeNavigator.classes.selected)) {
        treeNavigator.select  (node);
      }
    };

    // blur handler
    var blur_handler = function(e) {
      var label = e._target;
      label.removeAttribute('hasFocus');

      // deselect the previously selected node
      treeNavigator.select(null);
    };

    // focus and blur do not bubble
    // workaround per browser
    focus_handler = focus_handler.bindAsEventListener(this);
    blur_handler  = blur_handler.bindAsEventListener(this);

    this.rootElement.register('.'+this.classes.label, 'focus', focus_handler);
    this.rootElement.register('.'+this.classes.label, 'blur',  blur_handler );
  },


//-----------------------------------------------------
// timeout handler for firing Select/Deselect events
//-----------------------------------------------------

  _selectionTimeoutHandler: function(previousNode) {
      this._selectionTimeoutId = null;

      var newNode = this.selectedNode;

      // fire events
      if (previousNode != newNode) {
        if (previousNode) {
          this.fireEvent("Deselect", previousNode, this.rootElement);
        }
        if (newNode) {
          this.fireEvent("Select", newNode, this.rootElement);
        }
      }
  },


//-----------------------------------------------------
// Key handlers
//-----------------------------------------------------
  _charHandler: function (event) {
    var selectedNode = this.selectedNode;
    if(! selectedNode) return;

    // stop firefox quick search if enabled
    // via "accessibility.typeaheadfind" => 'true'
    Event.stop(event);

    this._quick_navi_word += event.keyName; // always uppercase
    var is_quick_navi_mode = (this._quick_navi_mode !== false);

    // drop the previous timer
    if(is_quick_navi_mode) {
      window.clearTimeout(this._quick_navi_mode);
    }
    // initialize labels_array on start of quick-search
    // (mandate of dynamic trees)
    else {
        this.labels_array = this.rootElement.select('.'+this.classes.label);
    }

    // activate a new timer
    this._quick_navi_mode = window.setTimeout(function() {
      this._clear_quick_navi();
    }.bind(this), 800);

    var selectedLabel = this.label(selectedNode);
    var selectedIndex = this.labels_array.indexOf(selectedLabel);
    // partitions the labels array into 2 arrays
    // 1: preceeding labels & selectedNode if not in quick_navi_mode
    // 2: following labels  & selectedNode if in quick_navi_mode
    var labels = this.labels_array.partition(function(l, index) {
        // quick-navi mode
        if(is_quick_navi_mode) return index < selectedIndex;
        else                   return index <= selectedIndex;
    });

    // returns first label found to start with word.
    var find_match = function(labels, word) {
        var match = labels.find(function(label) {
            return label.innerHTML.stripTags()          // in case label contains HTML elements
                    .replace(/\r?\n/g, '')              // clear line breaks
                    .replace(/\ \ /g, '')               // clear white-spaces
                    .toUpperCase().startsWith(word);
        });
        return match;
    }

    // first look ahead then look back
    var matching_label  =  find_match(labels[1], this._quick_navi_word)
                        || find_match(labels[0], this._quick_navi_word);

    // found a match -> make it visible and select it
    if(matching_label) {
      this.openEnclosingNodes(matching_label);

      var znode = this.enclosingNode(matching_label);
      this.scrollTo(znode);
      this.select  (znode);
    }
    // no match -> flash the selected label
    else {
      this.label(this.selectedNode).flash();
    }
  },

  _downHandler: function (event) {
    var selectedNode = this.selectedNode;
    if (selectedNode) {
      var nextNode = this.nextDisplayedNode(selectedNode);
      if (nextNode) {
        this.scrollTo(nextNode);
        this.select  (nextNode);
      }
      else this.flash(selectedNode);

      Event.stop(event);
    }
    // otherwise: do nothing and let default behaviour happen
  },

  _upHandler: function (event) {
    var selectedNode = this.selectedNode;
    if (selectedNode) {
      var prevNode = this.previousDisplayedNode(selectedNode);
      if (prevNode) {
        this.scrollTo(prevNode);
        this.select  (prevNode);
      }
      else this.flash(selectedNode);

      Event.stop(event);
    }
    // otherwise: do nothing and let default behaviour happen
  },

  _leftHandler: function (event) {
    var selectedNode = this.selectedNode;
    if (selectedNode) {
      if (!this.isLeaf(selectedNode) && !this.isClosed(selectedNode)) {
        this.close(selectedNode);
      }
      else {
        var zparent = this.parentNode(selectedNode);
        if (zparent) {
          this.scrollTo(zparent);
          this.select  (zparent);
        }
        else
          this.flash(selectedNode);
      }
      Event.stop(event);
    }
  },

  _rightHandler: function (event) {
    var selectedNode = this.selectedNode;
    if (selectedNode) {
      if (this.isLeaf(selectedNode)) return;
      if (this.isClosed(selectedNode))
        this.open(selectedNode);
      else {
        var subNode = this.firstSubNode(selectedNode);
        if (subNode) {
          this.scrollTo(subNode);
          this.select  (subNode);
        }
        else
          this.flash(selectedNode);
      }
      Event.stop(event);
    }
  },

  _tabHandler: function (event) {
    var selectedNode = this.selectedNode;
    if (selectedNode && this.isClosed(selectedNode)) {
      this.open(selectedNode);
      var label = this.label(selectedNode);
      Event.stop(event);
    }
  },

  _kpPlusHandler: function (event) {
    var selectedNode = this.selectedNode;
    if (selectedNode && this.isClosed(selectedNode)) {
      this.open(selectedNode);
      Event.stop(event);
    }
  },

  _kpMinusHandler: function (event) {
    var selectedNode = this.selectedNode;
    if (selectedNode && !this.isClosed(selectedNode)) {
      this.close(selectedNode);
      Event.stop(event);
    }
  },

  _kpStarHandler: function (event) {
    var selectedNode = this.selectedNode;
    if (selectedNode) {
      var nodes = Element.getElementsByClassNames(
        selectedNode,
        this.classes.node
      );
      nodes.unshift(selectedNode);
      nodes.each(function(node) {this.open(node)}, this);
      Event.stop(event);
    }
  },

  _kpSlashHandler: function (event) {
    var selectedNode = this.selectedNode;
    if (selectedNode) {
      var nodes = Element.getElementsByClassNames(
        selectedNode,
        this.classes.node
      );
      nodes.unshift(selectedNode);
      nodes.each(function(node) {this.close(node)}, this);
      Event.stop(event);
    }
  },

  _ctrl_R_handler: function (event) {
    var selectedNode = this.selectedNode;
    if (selectedNode) {
      if (this.loadContent(selectedNode))
        Event.stop(event);
    }
  },

  _ReturnHandler: function (event) {
    var selectedNode = this.selectedNode;
    if (selectedNode) {
      var toStop = this.fireEvent("Ping", selectedNode, this.rootElement);
      Event.detailedStop(event, toStop || Event.stopAll);
    }
  },

  _homeHandler: function (event) {
    if (this.selectedNode) {
      var znode = this.firstSubNode();
      this.scrollTo(znode);
      this.select  (znode);
      Event.stop(event);
    }
  },

  _endHandler: function (event) {
    if (this.selectedNode) {
      var znode = this.lastVisibleSubnode();
      this.scrollTo(znode);
      this.select  (znode);
      Event.stop(event);
    }
  },

  _ctrlPgUpHandler: function (event) {
    var node = this.enclosingNode(Event.element(event));
    if (node) {
      this.scrollTo(node);
      this.select  (node);
      Event.stop(event);
    }
  },

  _ctrlPgDownHandler: function (event) {
    var node = this.enclosingNode(Event.element(event));
    if (node) {
      node = this.nextDisplayedNode(node);
      if (node) {
        this.scrollTo(node);
        this.select  (node);
        Event.stop(event);
      }
    }
  },

  _chooseLevel: function(event) {
    var level = event.keyCode - "0".charCodeAt(0);
    this.openAtLevel(this.rootElement, level);

    // stop the default Ctrl-num event
    // FF: jump to tab#num
    // IE: Ctrl-5 Select-All
    Event.stop(event);
  },

  _showAll: function(event, toggle) {
    var method = toggle ? Element.addClassName : Element.removeClassName;
    method(this.rootElement, this.classes.showall);
  }

};

//----------choiceList.js

//----------------------------------------------------------------------
// CONSTRUCTOR
//----------------------------------------------------------------------

GvaScript.ChoiceList = function(choices, options) {
  if (! (choices instanceof Array) )
    throw new Error("invalid choices argument : " + choices);
  this.choices = choices;

  var defaultOptions = {
    labelField       : "label",
    classes          : {},        // see below for default classes
    idForChoices     : "CL_choice",
    keymap           : null,
    grabfocus        : false,
    mouseovernavi    : true,
    scrollCount      : 5,
    choiceItemTagName: "div",
    htmlWrapper      : function(html) {return html;},
    paginator        : null
  };


  this.options = Class.checkOptions(defaultOptions, options);

  var defaultClasses = {
    choiceItem      : "CL_choiceItem",
    choiceHighlight : "CL_highlight"
  };
  this.classes = Class.checkOptions(defaultClasses, this.options.classes);

  // handy vars
  this.hasPaginator = this.options.paginator != null;
  this.pageSize = (
                    // the step size of the paginator if any
                    (this.hasPaginator && this.options.paginator.options.step)

lib/Alien/GvaScript/lib/GvaScript.js  view on Meta::CPAN

  _highlightDelta: function(event, deltax, deltay) {
    var currentIndex = this.currentHighlightedIndex;
    var nextIndex    = currentIndex + deltax;

    // first try to flip a page
    // if first page -> go top of list
    if (nextIndex < 0) {
        if(this.hasPaginator) {
            if(this.options.paginator.getPrevPage()) return;
        }
        nextIndex = 0;
    }

    if (nextIndex >= this.choices.length) {
        if(this.hasPaginator) {
            if(this.options.paginator.getNextPage()) return;
        }
        nextIndex = this.choices.length -1;
    }

    // we're still on the same page
    this._jumpToIndex(event, nextIndex);
  },

  //----------------------------------------------------------------------
  // navigation
  //----------------------------------------------------------------------

  _findChoiceItem: function(event) { // walk up DOM to find mouse target
    var stop_condition = function(elem){return elem === this.container};
    return Element.navigateDom(Event.element(event), "parentNode",
                               this.classes.choiceItem,
                               stop_condition);
  },

  _listOverHandler: function(event) {
    var elem = this._findChoiceItem(event);
    if (elem) {
      this._highlightChoiceNum(this._choiceIndex(elem), false);
      if (this.options.grabfocus)
        this.container.focus();
      Event.stop(event);
    }
  },

  // no _listOutHandler needed

  _dblclickHandler: function(event) {
    var elem = this._findChoiceItem(event);
    if (elem) {
      var newIndex = this._choiceIndex(elem);
      this._highlightChoiceNum(newIndex, false);
      this._clickHandler(event);
    }
  },

  _clickHandler: function(event) {
    var elem = this._findChoiceItem(event);
    if (elem) {
      var newIndex = this._choiceIndex(elem);
      // check if choice is selected
      if (this.currentHighlightedIndex == newIndex) {
        // selected -> fire ping event
        var toStop = this.fireEvent({type : "Ping",
                                    index: this._choiceIndex(elem)},
                                    elem,
                                    this.container);
        Event.detailedStop(event, toStop || Event.stopAll);
      }
      else {
        // not selected -> select
        this._highlightChoiceNum(newIndex, false);
      }
    }
  },

  _returnHandler: function(event) {
    var index = this.currentHighlightedIndex;
    if (index != undefined) {
      var elem = this._choiceElem(index);
      var toStop = this.fireEvent({type : "Ping",
                                   index: index}, elem, this.container);
      Event.detailedStop(event, toStop || Event.stopAll);
    }
  },

  _escapeHandler: function(event) {
    var toStop = this.fireEvent("Cancel", this.container);
    Event.detailedStop(event, toStop || Event.stopAll);
  }

};


//----------autoCompleter.js
/**

TODO:
  - messages : choose language

  - multivalue :
     - inconsistent variable names
     - missing doc

  - rajouter option "hierarchicalValues : true/false" (si true, pas besoin de
    refaire un appel serveur quand l'utilisateur rajoute des lettres).

  - sometimes arrowDown should force Ajax call even if < minChars

  - choiceElementHTML

  - cache choices. Modes are NOCACHE / CACHE_ON_BIND / CACHE_ON_SETUP

  - dependentFields should also work with non-strict autocompleters

**/

//----------------------------------------------------------------------
// CONSTRUCTOR
//----------------------------------------------------------------------

GvaScript.AutoCompleter = function(datasource, options) {

  var defaultOptions = {
    minimumChars     : 1,
    labelField       : "label",
    valueField       : "value",
    autoSuggest      : true,      // will dropDown automatically on keypress
    autoSuggestDelay : 100,       // milliseconds, (OBSOLETE)
    checkNewValDelay : 100,       // milliseconds
    typeAhead        : true,      // will fill the inputElement on highlight

lib/Alien/GvaScript/lib/GvaScript.js  view on Meta::CPAN

      });
  },

  _updateChoicesFromCallback : function(val_to_complete, continuation) {
     continuation(this._datasource(val_to_complete));
  },

  _updateChoicesFromJSONP : function(val_to_complete, continuation) {
      if(val_to_complete) {
        var _url = this._datasource.json_url.replace(/\?1/, val_to_complete).replace(/\?2/, '?');
        var that = this;

        Element.addClassName(that.inputElement, that.classes.loading);
        Prototype.getJSON(_url, function(data) {
          var _data_list = data;

          if(that._datasource.json_list)
          that._datasource.json_list.split('/').each(function(p) {
            _data_list = _data_list[p];
          });
          Element.removeClassName(that.inputElement, that.classes.loading);

          continuation(_data_list);
        });
      }
  },

  _updateChoicesFromArray : function(val_to_complete, continuation) {
    if (this.options.ignorePrefix) {
      // store the index of the initial value
      if (val_to_complete) {
        this._idx_to_hilite = (val_to_complete == ''? 0 : -1);
        $A(this._datasource).each(function(choice, index) {
          switch(typeof choice) {
            case "object" : value = choice[this.options.valueField]; break;
            case "number" : value = choice.toString(10); break;
            case "string" : value = choice; break;
            default: throw new Error("unexpected type of value");
          }
          if(value.toLowerCase().startsWith(val_to_complete.toLowerCase())) {
            this._idx_to_hilite = index;
            throw $break;
          }
        }, this);
      }
      continuation(this._datasource);
    }
    else {
      var regex = new RegExp("^" + RegExp.escape(val_to_complete),
                             this.options.caseSensitive ? "" : "i");
      var matchPrefix = function(choice) {
        var value;
        switch(typeof choice) {
          case "object" : value = choice[this.options.valueField]; break;
          case "number" : value = choice.toString(10); break;
          case "string" : value = choice; break;
          default: throw new Error("unexpected type of value");
        }
        return value.search(regex) > -1;
      };
      continuation(this._datasource.select(matchPrefix.bind(this)));
    }
  },


  _updateChoices : function (continuation) {
    var value = this._getValueToComplete();

//     if (window.console) console.log('updateChoices', value);

    this._updateChoicesHandler(value, continuation);
  },


  // does the reverse of "autocomplete()"
  // doesnot fire if input blurred from click on choice list
  _blurHandler: function(event) {

    // remove choice list
    if (this.dropdownDiv)  this._removeDropdownDiv();

    // xhr is still active: waiting for response from server
    if (_xhr = this._runningAjax[this.inputElement.name]) {

      // if autocompleter is strict, need to wait for xhr to
      // finish before calling the _blurHandler to fire the
      // autocompleter's finalState
      if (this.options.strict) {
        _xhr['blurAfterSuccess'] = true;
        return;
      }

      _xhr.transport.abort();
      _xhr = null;
      Element.removeClassName(this.inputElement, this.classes.loading);
    }

    // if strict mode, inform client about the final status
    if (this.options.strict) {
      var value = this._getValueToComplete();

      // if value has changed, invalidate previous list of choices
      if (value != this.lastValue) {
        this.choices = null;
      }

      // if blank and blankOK, this is a legal value
      if (!value && this.options.blankOK) {
        this._updateDependentFields(this.inputElement, "");
        this.fireEvent({ type       : "LegalValue",
                         value      : "",
                         choice     : null,
                         controller : null  }, this.inputElement);
      }

      // if choices are known, just inspect status
      else if (this.choices) {
        this._fireFinalStatus(this.inputElement, this.choices);
      }

      // if not enough chars to get valid choices, this is illegal

lib/Alien/GvaScript/lib/GvaScript.js  view on Meta::CPAN

          }
          else if (this.options.strict && (!this.options.blankOK)) {
            this.inputElement.style.backgroundColor = this.options.colorIllegal;
          }
        };

        // now call updateChoices (which then will call the continuation)
        this._updateChoices(continuation.bind(this));
      }
  },


  // return the value to be completed
  // TODO : for multivalued, should return the value under the cursor,
  // instead returning sytematically the last value
  _getValueToComplete : function(value) {
     // NOTE: the explicit value as argument is only used from
     //_fireFinalStatus(), when we can no longer rely on
     // this.inputElement.value
    value = value || this.inputElement.value;
    if (this.options.multivalued) {
      var vals = value.split(this.options.multivalue_separator);
      value    = vals[vals.length-1];
    }
    return value;
  },

  _setValue : function(value, inputElement) {
        // NOTE: the explicit inputElement as argument is only used from
        // _fireFinalStatus(), when we can no longer rely on this.inputElement

    // default inputElement is the one bound to this autocompleter
    if (!inputElement) inputElement = this.inputElement;

    // if multivalued, the completed value replaces the last one in the list
    if (this.options.multivalued) {
      var _sep = inputElement.value.match(this.options.multivalue_separator);
      if (_sep) {
        var vals = inputElement.value.split(this.options.multivalue_separator);
        vals[vals.length-1] = value;
        value = vals.join(_sep[0]); // join all vals with first separator found
      }
    }

    // setting value in input field
    inputElement.value = this.lastValue = value;
  },



  _typeAhead : function () {
    var curLen     = this.lastTypedValue.length;
    var index      = this.choiceList.currentHighlightedIndex;
    var suggestion = this._valueFromChoice(index);
    var newLen     = suggestion.length;
    this._setValue(suggestion);

    if (this.inputElement.createTextRange){ // MSIE
      var range = this.inputElement.createTextRange();
      range.moveStart("character", curLen); // no need to moveEnd
      range.select(); // will call focus();
    }
    else if (this.inputElement.setSelectionRange){ // Mozilla
      this.inputElement.setSelectionRange(curLen, newLen);
    }
  },



//----------------------------------------------------------------------
// methods for the dropdown list of choices
//----------------------------------------------------------------------

  _mkDropdownDiv : function() {
    this._removeDropdownDiv();

    // the autocompleter has been blurred ->
    // do not display the div
    if(!this.inputElement) return null;

    // if observed element for scroll, reposition
    var movedUpBy   = 0;
    var movedLeftBy = 0;
    if (this.observed_scroll) {
        movedUpBy   = this.observed_scroll.scrollTop;
        movedLeftBy = this.observed_scroll.scrollLeft;
    }

    // create div
    var div = new Element('div');
    div.className = this.classes.dropdown;

    // positioning
    var coords = Position.cumulativeOffset(this.inputElement);
    var dim     = Element.getDimensions(this.inputElement);
    div.style.left      = coords[0] + this.options.offsetX - movedLeftBy + "px";
    div.style.top       = coords[1] + dim.height -movedUpBy + "px";
    div.style.maxHeight = this.options.maxHeight + "px";
    div.style.minWidth  = this.options.minWidth + "px";
    div.style.zIndex    = 32767; //Seems to be the highest valid value

    // insert into DOM
    document.body.appendChild(div);

    // simulate minWidth on old MSIE (must be AFTER appendChild())
    // maxHeight cannot be simulated untill displayChoices
    if (navigator.userAgent.match(/\bMSIE [456]\b/)) {
      div.style.width  = this.options.minWidth + "px";
    }

    // mouseenter and mouseleave events to control
    // whether autocompleter has been blurred
    var elem = this.inputElement;
    div.observe('mouseenter', function(e) {
      Element.stopObserving(elem, "blur", this.reuse.onblur);
    }.bind(this));
    div.observe('mouseleave', function(e) {
      Element.observe(elem, "blur", this.reuse.onblur);
    }.bind(this));

    return this.dropdownDiv = div;

lib/Alien/GvaScript/lib/GvaScript.js  view on Meta::CPAN

        choiceItemTagName : this.options.choiceItemTagName,
        htmlWrapper       : this.options.htmlWrapper
      });
      cl.currentHighlightedIndex = ac._idx_to_hilite;

      // TODO: explain and publish method "choiceElementHTML", or redesign
      // and make it a private method
      if ( this.choiceElementHTML ) {
        cl.choiceElementHTML = this.choiceElementHTML;
      }

      cl.onHighlight = function(event) {
        if (ac.options.typeAhead)
          ac._typeAhead();
        ac.fireEvent(event, ac.inputElement);
      };
      cl.onPing = function(event) {
        ac._completeFromChoiceElem(event.target);
      };
      cl.onCancel = function(event) {
        ac._removeDropdownDiv();
      };

      // append div to DOM
      var choices_div = this._mkDropdownDiv();
      // fill div now so that the keymap gets initialized
      cl.fillContainer(choices_div);
      // set height of div for IE6 (no suppport for maxHeight!)
      if (navigator.userAgent.match(/\bMSIE [456]\b/)) {
        choices_div.style.height =
          (choices_div.scrollHeight > this.options.maxHeight)?
            this.options.maxHeight + 'px' :
            'auto';
      }

      // determine if there is a space to dislay
      // the choices list under the input
      // if not, display above.
      // onscreen height needed for displaying the choices list
      var _h_needed = Element.viewportOffset(this.inputElement)[1]
                      + this.inputElement.offsetHeight
                      + choices_div.offsetHeight;
      var _h_avail  = document.viewport.getHeight();
      // move choices list on top of the input element
      if(_h_needed >= _h_avail) {
        var div_top = choices_div.offsetTop
                      - choices_div.offsetHeight
                      - this.inputElement.offsetHeight;
        if (div_top >= 0)
          choices_div.style.top = div_top + 'px';
      }

      // catch keypress on TAB while choiceList has focus
      cl.keymap.rules[0].TAB = cl.keymap.rules[0].S_TAB = function(event) {
        var index = cl.currentHighlightedIndex;
        if (index != undefined) {

          var elem = cl._choiceElem(index);

          // generate a "Ping" on the choiceList, like if user had
          // pressed RETURN to select the current highlighted item
          cl.fireEvent({type : "Ping",
                        index: index}, elem, cl.container);

          // NO Event.stop() here, because the navigator should
          // do the tabbing (pass focus to next/previous element)
        }
      };

      // more key handlers when the suggestion list is displayed
      this.keymap.rules.push(cl.keymap.rules[0]);

    }
    else
      this.displayMessage("pas de suggestion");
  },

  _removeDropdownDiv: function() {
    // remove the dropdownDiv that was added previously by _mkDropdownDiv();
    // that div contained either a menu of choices or a message to the user
    if (this.dropdownDiv) {
      // remove mouseenter and mouseleave observers
      this.dropdownDiv.stopObserving();
      Element.remove(this.dropdownDiv);
      this.dropdownDiv = null;
    }

    // if applicable, also remove rules previously pushed by _displayChoices
    if (this.keymap.rules.length > 1)
      this.keymap.rules.pop();
  },

  _valueFromChoice: function(index) {
    if (!this.choices) return null;
    var choice = this.choices[index];
    return (choice !== null) ? this._valueFromChoiceItem(choice) : null;
  },

  _valueFromChoiceItem: function(choice) {
    return (typeof choice == "string") ? choice
                                       : choice[this.options.valueField];
  },



  //triggered by the onPing event on the choicelist, i.e. when the user selects
  //one of the choices in the list
  _completeFromChoiceElem: function(elem) {
    // identify the selected line and handle it
    var num = parseInt(elem.id.match(/\.(\d+)$/)[1], 10);

    // add the value to the input element
    var value = this._valueFromChoice(num);
    if (value !== null) {
      this._setValue(value)
      this._removeDropdownDiv();
      
      // ADDED LEMOINEJ 26.09.13
      this._timeLastCheck = this._timeLastKeyDown = 0;      
      this._checkNewValue();

      if (!this.options.multivalued) {
        this.inputElement.select();
      }

      this._updateDependentFields(this.inputElement, this.choices[num]);

      // fire events: "Complete" for backwards compatibility, "LegalValue"
      // for regular use
      var eventNames =  ["Complete", "LegalValue"];
      // for loop : can't use .each() from prototype.js because it alters "this"
      for (var i = 0; i < eventNames.length; i++) {
        this.fireEvent({
          type      : eventNames[i],
          referrer  : "select",    // choice selection fired this event
          index     : num,
          choice    : this.choices[num],
          controller: {choices: this.choices}
          }, elem, this.inputElement);
      }
    }
  }
}



//----------customButtons.js
// depends: keyMap.js

GvaScript.CustomButtons = {};

GvaScript.CustomButtons.Button = Class.create();
// Renders Buttons in the following HTML structure
// <span class="gva-btn-container">
//         <span class="left"/>
//         <span class="center">
//                 <button class="btn" style="width: auto;" id="btn_1227001526005">
//                         Créer
//                 </button>
//         </span>
//         <span class="right"/>
// </span>
Object.extend(GvaScript.CustomButtons.Button.prototype, function() {
    var bcss = CSSPREFIX();

    function _extendCss(button_options) {
        // basic class
        var button_css = bcss+'-btn-container';

        // extended classes
        switch (typeof button_options.css) {
            case 'object': button_css += (' ' + button_options.css.join(' ')); break;
            case 'string': button_css += (' ' + button_options.css); break;
            default: break;
        }
        button_options.button_css = button_css;
    }
    var _button_template = new Template(
          '<span class="#{button_css}" id="#{id}">'
        + '<span class="left"></span>'
        + '<span class="center">'
            + '<button type="#{type}" style="width:#{width}" '
            + ' class="btn">#{label}'
            + '</button>'
        + '</span>'
        + '<span class="right"></span>'
        + '</span>'
    );
    function _render(button_options) {
        _extendCss(button_options);
        return _button_template.evaluate(button_options);
    }
    function _evalCondition(button_condition) {
        if(typeof button_condition == 'function') return button_condition();
        else
        if(eval(button_condition)) return true;
        else                       return false;
    }
    return {
        destroy: function() {
            // test that element still in DOM
            if(this.btnElt) this.btnElt.stopObserving('click');
        },
        initialize: function(container, options) {
            var defaults = {
                id: 'btn_' + (new Date()).getTime(),
                callback: Prototype.emptyFunction,
                condition: true,
                width: 'auto',
                type: 'button',
                label: 'GVA_SCRIPT_BUTTON'
            };
            this.options = Object.extend(defaults, options || {});

            if(_evalCondition(this.options.condition)) {
                try {
                    this.container = $(container);
                    this.container.insert(_render(this.options));
                    this.btnContainer = $(this.options.id); // the outer <span/>

                    this.btnElt = this.btnContainer.down('.btn'); // the <button/>

                    // setting inline style on the button container
                    if(typeof this.options.style != 'undefined') {
                        this.btnContainer.setStyle(this.options.style);
                    }

                    // setting tabindex on button if any
                    if(typeof this.options.tabindex != 'undefined') {
                        this.btnElt.writeAttribute('tabindex', this.options.tabindex);
                    }

                    this.btnElt.observe('click', this.options.callback.bind(this.btnElt));
                } catch (e) {}
            }
        }
    }
}());

GvaScript.CustomButtons.ButtonNavigation = Class.create();
Object.extend(GvaScript.CustomButtons.ButtonNavigation.prototype, function() {
        // private members
        var bcss = CSSPREFIX();

        function _leftHandler(event) {
            var selectedBtn = this.selectedBtn;
            if (selectedBtn) {
                var nextBtn = this.previousBtn(selectedBtn);

                if (nextBtn) this.select(nextBtn);
                else         selectedBtn.flash();

                Event.stop(event);
            }
        }
        function _rightHandler(event) {
            var selectedBtn = this.selectedBtn;
            if (selectedBtn) {
                var prevBtn = this.nextBtn(selectedBtn);

                if (prevBtn) this.select(prevBtn);
                else         selectedBtn.flash();

                Event.stop(event);
            }
        }
        function _tabHandler(event) {
            if (this.options.preventListBlur)
                if (this.isLast(this.selectedBtn))
                    Event.stop(event);
        }
        function _shiftTabHandler(event) {
            if (this.options.preventListBlur)
                if (this.isFirst(this.selectedBtn))
                    Event.stop(event);
        }
        function _homeHandler(event) {
            if (this.selectedBtn) {
                this.select(this.firstBtn());
                Event.stop(event);
            }
        }
        function _endHandler(event) {
            if (this.selectedBtn) {
                this.select(this.lastBtn());
                Event.stop(event);
            }
        }
        function _addHandlers() {
            this.buttons.each(function(btnContainer) {
                var btn;
                // if the button is a GvaScript.CustomButtons.BUTTON, then the actual <button> element
                // will be embedded and selectable via .btn classname:
                // <span class="gva-btn-container">
                //         <span class="left"/>
                //         <span class="center">
                //                 <button accesskey="r" class="btn" style="width: auto;" id="btn_1226916357164">
                //                         Rechercher dans Calvin
                //                 </button>
                //         </span>
                //         <span class="right"/>
                // </span>
                // this will be cleaner when all application buttons are transformed into
                // GvaScript.CustomButtons.Button instances
                if(btnContainer.tagName.search(/^(INPUT|BUTTON)$/i) > -1) btn = btnContainer;
                else {
                    btn = btnContainer.down('.btn');
                    btn.visible        = function() {return btnContainer.visible();}
                    // support focus function on span.buttonContainer
                    btnContainer.focus = function() {btn.focus();}
                }

                if(typeof btn == 'undefined') return;

            }, this);

            this.container.register('button.btn', 'focus', function(e) {
                this.select.call(this, e._target.up('.'+bcss+'-btn-container'));
            }.bind(this));
            this.container.register('button.btn', 'blur', function(e) {
                this.select.call(this, null);
            }.bind(this));
        }

        // public members
        return {
            destroy: function() {
                // test that element still in DOM
                if(this.container) this.container.unregister();
                this.keymap.destroy();
            },
            initialize: function(container, options) {
                var defaults = {
                    preventListBlur     : false,
                    flashDuration       : 100,     // milliseconds
                    flashClassName      : 'flash',
                    keymap              : null,
                    selectFirstBtn      : true,
                    className           : bcss+'-button'
                };
                this.options   = Object.extend(defaults, options || {});
                this.container = $(container);

                // initializing the keymap
                var keyHandlers = {
                    LEFT:       _leftHandler     .bindAsEventListener(this),
                    RIGHT:      _rightHandler    .bindAsEventListener(this),
                    TAB:        _tabHandler      .bindAsEventListener(this),
                    S_TAB:      _shiftTabHandler .bindAsEventListener(this),
                    HOME:       _homeHandler     .bindAsEventListener(this),
                    END:        _endHandler      .bindAsEventListener(this)
                };
                this.keymap = new GvaScript.KeyMap(keyHandlers);
                this.keymap.observe("keydown", container, {
                    preventDefault:false,
                    stopPropagation:false
                });

                // get all buttons of designated className regardless of their
                // visibility jump over hidden ones when navigating
                this.buttons = this.container.select('.'+this.options.className);
                _addHandlers.call(this);

                if (this.options.selectFirstBtn) {
                    if(firstButObj = this.firstBtn()) {
                        this.select(firstButObj);
                    }
                    // set the focus on the container anyways so that the focus
                    // gets trasferred successfully to windows with empty
                    // actionsbar
                    else {
                        this.container.writeAttribute('tabindex', 0);
                        this.container.focus();
                    }
                }
            },
            select: function (btn) {
                var previousBtn = this.selectedBtn || null;
                if (previousBtn === btn) return; // selection already handled

                // blur the previously selected button
                if (previousBtn) {
                    previousBtn.removeClassName('btn-focus');
                }
                this.selectedBtn = btn;
                if (btn) {
                    btn.addClassName('btn-focus');
                    try {
                        if(btn.tagName.search(/^(INPUT|BUTTON)$/i) > -1)
                            btn.focus();
                        else
                            btn.down('.btn').focus();
                    } catch (err) {}
                }
            },
            // returns the next visible button
            // null if none exists
            nextBtn: function (btn) {
                var _idx = this.buttons.indexOf(btn);
                var _nextBtn = null;

                do    _nextBtn = this.buttons[++_idx]
                while(_nextBtn && !(_nextBtn.visible()));

                return _nextBtn;
            },
            // returns the previous visible button
            // null if none exists
            previousBtn: function (btn) {
                var _idx = this.buttons.indexOf(btn);
                var _prevBtn = null;

                do    _prevBtn = this.buttons[--_idx]
                while(_prevBtn && !(_prevBtn.visible()));

                return _prevBtn;
            },
            isFirst: function(btn) { return btn == this.firstBtn() },
            isLast:  function(btn) { return btn == this.lastBtn() },
            // return first visible button
            firstBtn: function() {
                return this.buttons.find(function(e) {
                    return e.visible();
                });
            },
            // return last visible button
            lastBtn: function() {
                return this.buttons.reverse(false).find(function(e) {
                    return e.visible();
                });
            }
        }
}());


GvaScript.CustomButtons.ActionsBar = Class.create();
Object.extend(GvaScript.CustomButtons.ActionsBar.prototype, {
    initialize: function(container, options) {
        var bcss = CSSPREFIX();
        var defaults = {
            actions: [],
            selectfirst: false
        }
        this.container = $(container);
        this.container.update('');
        this.options = Object.extend(defaults, options || {});
        this.container.addClassName(bcss+'-actionsbar');

        this.options.actions.each(function(action_props, index) {
            action_props.id = action_props.id || this.container.id + '_btn_' + index;
            // renders a <button> element and appends it to container
            new GvaScript.CustomButtons.Button(this.container, action_props);
        }, this);

        this.buttonNavigation = new GvaScript.CustomButtons.ButtonNavigation(this.container, {
            selectFirstBtn: this.options.selectfirst,
            className: bcss+'-btn-container'
        });

        this.container.store('widget', this);
        this.container.addClassName(bcss+'-widget');
    },
    destroy: function() {
        this.buttonNavigation.destroy();
    }
});

document.register('.'+CSSPREFIX()+'-btn-container', 'mouseover', function(e) {
    e._target.addClassName('btn-hover');
});
document.register('.'+CSSPREFIX()+'-btn-container', 'mouseout', function(e) {
    e._target.removeClassName('btn-hover');
});

//----------paginator.js
GvaScript.Paginator = Class.create();

Object.extend(GvaScript.Paginator.prototype, function() {
    var bcss = CSSPREFIX();
    var paginator_css = bcss + '-paginatorbar';
    var pagination_buttons = "<div class='last' title='Dernière page'></div>"
             + "<div class='forward' title='Page suivante'></div>"
             + "<div class='text'></div>"
             + "<div class='back' title='Page précédente'></div>"
             + "<div class='first' title='Première page'></div>";


    function _toggleNavigatorsVisibility() {
        if(this.hasPrevious()) {
            this.back.removeClassName('inactive');
            this.first.removeClassName('inactive');
        }
        else {
            this.back.addClassName('inactive');
            this.first.addClassName('inactive');
        }
        if(this.hasNext()) {
            this.forward.removeClassName('inactive');
            this.last.removeClassName('inactive');
        }
        else {
            this.forward.addClassName('inactive');
            this.last.addClassName('inactive');
        }
        this.links_container.show();
    }
    /* Create pagination controls and append them to the placeholder 'PG:frame' */
    function _addPaginationElts() {
        // append the pagination buttons
        this.links_container.insert(pagination_buttons);

        this.first    = this.links_container.down('.first');
        this.last     = this.links_container.down('.last');
        this.forward  = this.links_container.down('.forward');
        this.back     = this.links_container.down('.back');
        this.textElem = this.links_container.down('.text');

lib/Alien/GvaScript/lib/GvaScript.js  view on Meta::CPAN

                this.choiceList_initialized = true;
            }

           this.choiceList.onCancel = this.options.onCancel;
           this.choiceList.onPing   = this.pingWrapper.bind(this);

           this.paginator.loadContent();

           this.grid_container.addClassName(bcss+'-widget');
           this.grid_container.store('widget', this);

           GvaScript.Grids.register(this);
        },

        getId: function() {
            return this.id;
        },

        clearResult: function(msg) {
            this.grid_container.update(msg || '');
        },

        clearToolbar: function() {
            this.toolbar_container.update('');
        },

        clearActionButtons: function() {
            this.actionsbar_container.update('');
        },

        clear: function(msg) {
            this.clearResult(msg);
            this.clearToolbar();
        },

        pingWrapper: function(event) {
            this.options.onPing(this.records[event.index]);
        },

        addActionButtons: function() {
            // first clear the actionbuttons container
            this.clearActionButtons();

            // append the action buttons
            var actions = this.options.actions.each(function(action_props, index) {
                // evaluation button condition in the 'this' context
                prop_condition = action_props.condition;
                switch(typeof prop_condition) {
                    case 'undefined' : action_props.condition = true; break;
                    case 'function'  : action_props.condition = prop_condition(this); break;
                    default          : action_props.condition = eval(prop_condition); break;
                }
                action_props.id = action_props.id || this.getId() + "_btn_" + index;

                // renders a <button> element and appends it to container
                new GvaScript.CustomButtons.Button(this.actionsbar_container, action_props);
            }, this);

            // activate the navigation over the action buttons
            this.actionButtons = new GvaScript.CustomButtons.ButtonNavigation(this.actionsbar_container, {
                selectFirstBtn: false,
                className: bcss+'-btn-container'
            });
        },

        // wrapping the recordset in a table with column headers
        gridWrapper: function(html) {
           return '<table class="'+bcss+'-grid '+this.options.css+'">' +
                    '<thead><tr>' +
                        '<th class="grid-marker">&nbsp;</th>' +
                        (this.columns.collect(function(e) {
                            if(_evalCondition(e, this))
                            return '<th class="grid-header'+_compileCss(e)+'"'+_compileWidth(e)+_compileTitle(e)+'>'+e.label+'</th>'
                            else return '';
                        }, this).join('')) +
                    '</tr></thead>' +
                    '<tbody>'+html+'</tbody>'+
                '</table>';
        },

        // called by the paginator
        receiveRequest: function(response_json) {
            this.records = response_json.liste;
            this.total   = response_json.total;
            this.rights  = response_json.rights || {can_create: 1};

            var list_records = $A(this.records).collect(function(e, index) {
                        return  '<td class="grid-marker">&nbsp;</td>' +
                                this.columns.collect(function(c) {
                                    if(_evalCondition(c, this))
                                    return  '<td class="grid-cell index_'+(index%2)+_compileCss(c)+'" valign="top">' +
                                            _getColumnValue(c, e) +
                                            '</td>';
                                    else return '';
                                }, this).join('');
            }, this);

            // TODO not elegant !
            if(this.choiceList_initialized) {
                this.choiceList.updateContainer(this.grid_container, list_records);
            }
            else {
                this.choiceList.choices = list_records;
                this.choiceList.fillContainer(this.grid_container);
                this.choiceList_initialized = true;
            }

            if(this.options.grabfocus) {
                try {this.grid_container.focus();}
                catch(e) {}
            }

            if(typeof this.actionButtons == 'undefined')
                this.addActionButtons();

            if(!(this.total > 0)) this.options.onEmpty.apply(this);

            (this.options.onShow || Prototype.emptyFunction).call();

            return this.records.length;
        }

lib/Alien/GvaScript/lib/GvaScript.js  view on Meta::CPAN


  - check prototype.js serialize on multivalues
*/

GvaScript.Form = Class.create();

GvaScript.Form.Methods = {

  to_hash: function(form) {
    form = $(form);

    return form.serialize({hash:true});
  },

  to_tree: function(form) {
    form = $(form);

    return Hash.expand(GvaScript.Form.to_hash(form));
  },

  fill_from_tree : (function() {

    var doc = document; // local variable is faster than global 'document'

    // IMPLEMENTATION NOTE : Form.Element.setValue() is quite similar,
    // but our treatment of arrays is different, so we have to reimplement
    var _fill_from_value = function(form, elem, val, is_init) {

      // force val into an array
      if (!(val instanceof Array)) val = [val];


      var old_value = null; // needed for value:change custom event
      var new_value = null;

      switch (elem.type) {

        case "text" :
        case "textarea" :
        case "hidden" :
          old_value  = elem.value;
          elem.value = new_value = val.join(",");
        break;

        case "checkbox" :
        case "radio":
          var elem_val  = elem.value;
          old_value = elem.checked ? elem_val : null;

          // hand-crafted loop through val array (because val.include() is too slow)
          elem.checked = false;
          for (var count = val.length; count--;) {
            if (val[count] == elem_val) {
                elem.checked = true;
                break;
            }
          }
          new_value = elem.checked ? elem_val : null;
        break;

        case "select-one" :
        case "select-multiple" :
          var options = elem.options;
          var old_values = [],
              new_values = [];
          for (var i=0, len=options.length; i<len; i++) {
            var opt = options[i];
            var opt_value = opt.value || opt.text;
            if (opt.selected) old_values.push(opt_value);
            // hand-crafted loop through val array (because val.include() is too slow
            opt.selected = false;
            for (var count = val.length; count--;) {
              if (val[count] == opt_value) {
                new_values.push(opt_value);
                opt.selected = true;
                break;
              }
            }
          }
          old_value = old_values.join(",");
          new_value = new_values.join(",");
        break;

        default:
          // if no element type, might be a node list
          var elem_length = elem.length;
          if (elem_length !== undefined) {
            for (var i=0; i < elem_length; i++) {
              _fill_from_value(form, elem.item(i), val, is_init);
            }
          }
          else
            throw new Error("unexpected elem type : " + elem.type);
        break;
      } // end switch

      // if initializing form
      //   and form has an init handler registered to its inputs
      //   and elem has a new_value set
      // => fire the custom 'value:init' event
      if (is_init) {
        if (form.has_init_registered)
          if (new_value)
            Element.fire(elem, 'value:init', {newvalue: new_value}); 
      }
      else {
        if (new_value != old_value)
          Element.fire(elem, 'value:change', {oldvalue: old_value, newvalue: new_value});
      }
    }

    var _fill_from_array = function (form, field_prefix, array, is_init) {
      for (var i=0, len=array.length; i < len; i++) {
        var new_prefix = field_prefix + "." + i;

        // if form has a corresponding named element, fill it
        var elem = form[new_prefix];
        if (elem) {
          _fill_from_value(form, elem, array[i], is_init);
          continue;
        }

        // otherwise try to walk down to a repetition block

        // try to find an existing repetition block
        elem = doc.getElementById(new_prefix);  // TODO : check: is elem in form ?

        // no repetition block found, try to instanciate one
        if (!elem) {
          var placeholder = doc.getElementById(field_prefix + ".placeholder");
          if (placeholder && placeholder.repeat) {
            GvaScript.Repeat.add(placeholder, i + 1 - placeholder.repeat.count);
            elem = doc.getElementById(new_prefix);
          }
        }

lib/Alien/GvaScript/lib/GvaScript.js  view on Meta::CPAN

        // mremlawi: sometimes multi-value fields are filled without
        // passing by the repeat moduleearly
        // -> no id's on repeatable blocks are set but need to recurse anyway
//         if (elem)
        GvaScript.Form.fill_from_tree(form, new_prefix, array[i], is_init);
      }
    }

    function fill_from_tree(form, field_prefix, tree, is_init)  {
      if (Object.isString(form)) form = $(form);

      for (var key in tree) {
        if (!tree.hasOwnProperty(key)) continue;

        var val = tree[key];
        var new_prefix = field_prefix ? field_prefix+'.'+key : key;

        switch (typeof(val)) {
          case "boolean" :
              val = val ? "true" : "";
              // NO break here

          case "string":
          case "number":
              var elem = form[new_prefix];
              if (elem)
                _fill_from_value(form, elem, val, is_init);
              break;

          case "object":
              if (val instanceof Array) {
                var elem = form[new_prefix];
                // value is an array but to be filled
                // in one form element =>
                // join array into one value using multival separator
                if (elem)
                  _fill_from_value(
                    form, elem, val.join(GvaScript.Forms.multival_sep), is_init
                  );
                else
                  _fill_from_array(form, new_prefix, val, is_init);
              }
              else
                this.fill_from_tree(form, new_prefix, val, is_init);
              break;

          case "function":
          case "undefined":
              // do nothing
        }
      }
    }
    return fill_from_tree;
  })(),

  autofocus: function(container) {

    if (Object.isString(container)) 
      container = document.getElementById(container);

    // replace prototype's down selector
    // as it performs slowly on IE6
    var _find_autofocus = function(p_node) {
      var _kids = p_node.childNodes;

      for(var _idx = 0, len = _kids.length; _idx < len; ) {
        _kid = _kids[_idx ++];

        if(_kid.nodeType == 1) {
          if(Element.hasAttribute(_kid, 'autofocus')) {
            return _kid;
          }
          else {
            var _look_in_descendants = _find_autofocus(_kid);
            if(_look_in_descendants) return _look_in_descendants;
          }
        }
      }
    }

    if(container) {
      //slow on IE6
      //var target = container.down('[autofocus]');
      var target = _find_autofocus(container);
      // TODO : check if target is visible
      if (target) try {target.activate()}
                  catch(e){}
    }
  },

  /**
    * wrapper around Element.register method.
    * method wrapped for special handling of form inputs
    * 'change' and 'init' events
    *
    * all handlers will receive 'event' object as a first argument.
    * 'change' handler will also receive input's oldvalue/newvalue as
    * second and third arguments respectively.
    * 'init' handler will also receive input's newvalue as a
    * second argument.
    *
    * @param {string} query : css selector to match elements
    *                         to watch
    * @param {string} eventname : standard event name that can be triggered
    *                             by form inputs + the custom 'init' event
    *                             that is triggerd on form initialization
    * @param {Function} handler : function to execute.
    *
    * @return undefined
    */
  register: function(form, query, eventname, handler) {
      form = $(form);

      switch(eventname) {
        // change event doesnot bubble in IE
        // rely on blur event to check for change
        // and fire value:change event
        case 'change':
          form.register(query, 'focus', function(event) {
              var elt = event._target;
              elt.store('value', elt.getValue());
          });

          form.register(query, 'blur', function(event) {
              var elt      = event._target;
              var oldvalue = elt.retrieve('value');
              var newvalue = elt.getValue();

              if(oldvalue != newvalue) {
                  elt.fire('value:change', {
                      oldvalue : oldvalue,
                      newvalue : newvalue,
                      handler  : handler
                  });
                  elt.store('value', newvalue);
              }
          });
        break;

        // value:init fired by GvaScript.Form.fill_from_tree method
        // used in formElt initialization
        case 'init':
          // set a flag here in order to fire the 
          // value:init custom event while initializing
          // the form 
          form.has_init_registered = true;

          form.register(query, 'value:init', function(event) {
              handler(event, event.memo.newvalue);
          });
        break;

        default:
          form.register(query, eventname, handler);
        break;
      }
  },

  /**
    * wrapper around Element.unregister method.
    * method wrapped for special handling of form inputs
    * 'change' and 'init' events
    *
    * remove handler attached to eventname for inputs that match query
    *
    * @param {string} query : css selector to remove handlers from
    * @param {string} eventname : eventname to stop observing
    * @param {Funtion} handler : handler to stop firing oneventname
    *                            NOTE: should be identical to what was used in
    *                            register method.
    *                            {optional} : if not specified, will remove all
    *                            handlers attached to eventname for indicated selector
    * @return undefined
    */
  unregister: function(form, query, eventname, handler) {
    form = $(form);

    switch(eventname) {
      case 'change' :
        form.unregister(query, 'focus', handler);
        form.unregister(query, 'blur',  handler);
      break;
      default :
        form.unregister(query, eventname, handler);
      break;
    }
  }
}

Object.extend(GvaScript.Form.prototype, function() {
    // private method to initialize and add actions
    // to form's actions bar
    function _addActionButtons(form) {
        var _actionsbar = $H(form.options.actionsbar);
        if(_actions_container = _actionsbar.get('container')) {
            _actions_container = $(_actions_container);
            _actions_list = _actionsbar.get('actions') || [];

            form.actionsbar = new GvaScript.CustomButtons.ActionsBar(_actions_container, {
                selectfirst: _actionsbar.get('selectfirst') ,
                actions: _actions_list
            });
        }
    }

    return {
        formElt: null,
        actionsbar: null,
        initialize: function(formElt, options) {
            this.formElt = $(formElt);

            var defaults = {
                datatree: {},                               // data object to init form with
                dataprefix: '',                             // data prefix used on form elements


                actionsbar: {},                             // form actions
                registry: [],                               // list of [elements_selector, event_name, event_handler]

                skipAutofocus : false,

                onInit           : Prototype.emptyFunction,  // called after form initialization

                onRepeatBlockRemove : Prototype.emptyFunction,  // called when a repeatable block gets removed
                onRepeatBlockAdd    : Prototype.emptyFunction,  // called when a repeatable block gets added

                onChange         : Prototype.emptyFunction,  // called if any input/textarea value change
                onBeforeSubmit   : Prototype.emptyFunction,  // called right after form.submit
                onSubmit         : Prototype.emptyFunction,  // form submit handler
                onBeforeDestroy  : Prototype.emptyFunction   // called right before form.destroy
            }

            this.options = Object.extend(defaults, options || {});

            // attaching submitMethod to form.onsubmit event
            this.formElt.observe('submit', function() {
                // submit method only called if
                // onBeforeSubmit handler doesnot return false
                if ( this.fire('BeforeSubmit') ) return this.fire('Submit');
            }.bind(this));

            // initializing watchers
            $A(this.options.registry).each(function(w) {
                this.register(w[0], w[1], w[2]);
            }, this);

            var that = this;
            // workaround as change event doesnot bubble in IE
            this.formElt.observe('value:change', function(event) {
                if(event.memo.handler) {
                    event.memo.handler(event,
                                       event.memo.newvalue,
                                       event.memo.oldvalue
                    );
                    // fire the onChange event passing the event
                    // object as an arguement
                    that.fire('Change', event);
                }
                else {
                    if(Prototype.Browser.IE) {
                        var evt = document.createEventObject();
                        event.target.fireEvent('onblur', evt)
                    }
                    else {
                        var evt = document.createEvent("HTMLEvents");
                        evt.initEvent('blur', true, true); // event type,bubbling,cancelable
                        event.target.dispatchEvent(evt);
                    }
                }
            });

            // initializing form actions
            _addActionButtons(this);

            // registering change event to support the onChange event
            this.register('input,textarea','change', Prototype.emptyFunction);

            // initializing for with data



( run in 1.021 second using v1.01-cache-2.11-cpan-119454b85a5 )