Alien-GvaScript

 view release on metacpan or  search on metacpan

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

      var v = versionString.replace(/_.*|\./g, '');
      v = parseInt(v + '0'.times(4-v.length));
      return versionString.indexOf('_') > -1 ? v-1 : v;
    }
    if((typeof Prototype=='undefined') ||
       (typeof Element == 'undefined') ||
       (typeof Element.Methods=='undefined') ||
       (convertVersionString(Prototype.Version) <
        convertVersionString(GvaScript.REQUIRED_PROTOTYPE)))
       throw("GvaScript requires the Prototype JavaScript framework >= " +
        GvaScript.REQUIRED_PROTOTYPE);
  }
};

GvaScript.load();

//----------protoExtensions.js
//-----------------------------------------------------
// Some extensions to the prototype javascript framework
//-----------------------------------------------------

// fire value:change event when setValue method
// is used to change the value of a Form Element
Form.Element.Methods.setValue = Form.Element.Methods.setValue.wrap(
    function($p, element, value) {
        var oldvalue = $F(element);
        var _return = $p(element, value);
        element.fire('value:change', {oldvalue: oldvalue, newvalue: value});
        return _return;
    }
);
Element.addMethods();

// adds the method flash to SPAN, DIV, INPUT, BUTTON elements
// flashes an element by adding a classname for a brief moment of time
// options: {classname: // classname to add (default: flash)
//           duration:  // duration in ms to keep the classname (default: 100ms)}
var _element_list = ['DIV', 'INPUT',
                    'BUTTON', 'TEXTAREA', 'A',
                    'H1', 'H2', 'H3', 'H4', 'H5'];
// for the moment, SPAN not supported on WebKit (see prototype.js bug  in
// https://prototype.lighthouseapp.com/projects/8886/tickets/976-elementaddmethodsspan-fails-on-webkit)
if (!Prototype.Browser.WebKit) _element_list.push('SPAN');
Element.addMethods(_element_list, {
    flash: function(element, options) {
        if (element._IS_FLASHING) return;
        element = $(element);

        options = options || {};
        var duration  = options.duration  || 100;
        var classname = options.classname || 'flash';

        element._IS_FLASHING = true;

        var endFlash  = function() {
            this.removeClassName(classname);
            this._IS_FLASHING = false;
        };

        element.addClassName(classname);
        setTimeout(endFlash.bind(element), duration);
    }
});

// utilities for hash

// expands flat hash into a multi-level deep hash
// javascript version of Perl  CGI::Expand::expand_hash
Hash.expand = function(flat_hash) {
  var tree = {};

  // iterate on keys in the flat hash
  for (var k in flat_hash) {
    var parts = k.split(/\./);
    var loop = {tree: tree, key: "root"};

    // iterate on path parts within the key
    for (var i = 0 ; i < parts.length; i++) {
      var part = parts[i];

      // if no subtree yet, build it (Array or Object)
      if (!loop.tree[loop.key])
      loop.tree[loop.key] = part.match(/^\d+$/) ? [] : {};

      // walk down to subtree
      loop = {tree: loop.tree[loop.key], key:part};
    }
    // store value in leaf
    loop.tree[loop.key] = flat_hash[k];
  }

  return tree.root;
}

// collapses deep hash into a one level hash
Hash.flatten = function(deep_hash, prefix, tree) {
  tree = tree   || {};

  for (var i in deep_hash) {
    var v = deep_hash[i];
    var new_prefix = prefix? prefix + '.' + i : i;
    switch (typeof(v)) {
        case "function": continue; break;
        case "object"  : Hash.flatten(v, new_prefix, tree); break;
        case "string"  :
        case "number"  : tree["" + new_prefix + ""] = v; break;
        default        : break;
    }
  }
  return tree;
}

// utilities for string

Object.extend(String.prototype, {
  chomp: function() {
    return this.replace(/(\n|\r)+$/, '');
  }
});

Object.extend(Element, {

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

        GvaScript.eventsQueue.popEvent(target, event.type)
    }, 1000);

    return event_return;
  }
  else
    return null; // no handler found
};


//----------keyMap.js

//constructor
GvaScript.KeyMap = function (rules) {
    if (!(rules instanceof Object)) throw "KeyMap: invalid argument";
    this.rules = [rules];
    return this;
};


GvaScript.KeyMap.prototype = {
  destroy: function() {
    Event.stopObserving(this.elem, this.eventType, this.eventHandler);
  },

  eventHandler: function(event) {
    var keymap = this;

    // translate key code into key name
    event.keyName = GvaScript.KeyMap.KEYS.BUILTIN_NAMES[event.keyCode]
                 || String.fromCharCode(event.keyCode);

    // add Control|Shift|Alt modifiers
    event.keyModifiers = "";
    if (event.ctrlKey  && !this.options.ignoreCtrl)  event.keyModifiers += "C_";
    if (event.shiftKey && !this.options.ignoreShift) event.keyModifiers += "S_";
    if (event.altKey   && !this.options.ignoreAlt)   event.keyModifiers += "A_";

    // but cancel all modifiers if main key is Control|Shift|Alt
    if (event.keyName.search(/^(CTRL|SHIFT|ALT)$/) == 0)
      event.keyModifiers = "";

    // try to get the corresponding handler, and call it if found
    var handler = keymap._findInStack(event, keymap.rules);
    if (handler) {
      var toStop = handler.call(keymap, event);
      Event.detailedStop(event, toStop || this.options);
    }
  },

  observe: function(eventType, elem, options) {
    this.eventType = eventType || 'keydown';
    this.elem      = elem      || document;

    // "Shift" modifier usually does not make sense for keypress events
    if (eventType == 'keypress' && !options)
      options = {ignoreShift: true};

    this.options = Class.checkOptions(Event.stopNone, this.options || {});

    this.eventHandler = this.eventHandler.bindAsEventListener(this);
    Event.observe(this.elem, this.eventType, this.eventHandler);
  },

  _findInStack: function(event, stack) {
    for (var i = stack.length - 1; i >= 0; i--) {
      var rules = stack[i];

      // trick to differentiate between C_9 (digit) and C_09 (TAB)
      var keyCode = event.keyCode>9 ? event.keyCode : ("0"+event.keyCode);

      var handler = rules[event.keyModifiers + event.keyName]
                 || rules[event.keyModifiers + keyCode]
                 || this._regex_handler(event, rules.REGEX, true)
                 || this._regex_handler(event, rules.ANTIREGEX, false);
      if (handler)
        return handler;
    }
    return null;
  },

  _regex_handler: function(event, regex_rules, want_match) {
    if (!regex_rules) return null;
    for (var j = 0; j < regex_rules.length; j++) {
      var rule      = regex_rules[j];
      var modifiers = rule[0];
      var regex     = rule[1];
      var handler   = rule[2];

      var same_modifiers = modifiers == null
                        || modifiers == event.keyModifiers;

      // build regex if it was passed as a string
      if (typeof(regex) == "string")
        regex = new RegExp("^(" + regex + ")$");

      var match = same_modifiers && regex.test(event.keyName);
      if ((match && want_match) || (!match && !want_match))
        return handler;
    }
    return null;
  }
};

GvaScript.KeyMap.MapAllKeys = function(handler) {
    return {REGEX:[[null, /.*/, handler]]}
};


GvaScript.KeyMap.Prefix = function(rules) {

    // create a specific handler for the next character ...
    var one_time_handler = function (event) {
        this.rules.pop(); // cancel prefix
        var handler = this._findInStack(event, [rules]);
        if (handler) handler.call(this, event);
    }

    // ... and push that handler on top of the current rules
    return function(event) {
        this.rules.push(GvaScript.KeyMap.MapAllKeys(one_time_handler));

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

//-----------------------------------------------------

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;

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


    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',
                               this.classes.nodeOrLeaf);

  },

  firstSubNode: function (node) {
    node = node || this.rootElement;
    var parent = (node == this.rootElement) ? node
               : this.isLeaf(node)          ? null
               :                              this.content(node);
    return parent ? Element.navigateDom(parent.firstChild, 'nextSibling',
                                        this.classes.nodeOrLeaf)
                  : null;
  },

  lastSubNode: function (node) {
    node = node || this.rootElement;
    var parent = (node == this.rootElement) ? node
               : this.isLeaf(node)          ? null
               :                              this.content(node);
    return parent ? Element.navigateDom(parent.lastChild, 'previousSibling',
                                        this.classes.nodeOrLeaf)
                  : null;
  },

  lastVisibleSubnode: function(node) {
    node = node || this.rootElement;
    while(!this.isClosed(node)) {
      var lastSubNode = this.lastSubNode(node);
      if (!lastSubNode) break;
      node = lastSubNode;
    }
    return node;
  },

  // find next displayed node (i.e. skipping hidden nodes).
  nextDisplayedNode: function (node) {
    this._assertNodeOrLeaf(node, 'nextDisplayedNode: arg type');

    // case 1: node is opened and has a subtree : then return first subchild
    if (!this.isClosed(node)) {
      var firstSubNode = this.firstSubNode(node);
      if (firstSubNode) return firstSubNode;
    }

    // case 2: current node or one of its parents has a sibling
    while (node) {
      var sibling = this.nextSibling(node);

      if (sibling) {
        if (this.isVisible(sibling))
          return sibling;
        else
          node = sibling;
      }
      else
        node = this.parentNode(node);
    }

    // case 3: no next Node
    return null;
  },

  // find previous displayed node (i.e. skipping hidden nodes).
  previousDisplayedNode: function (node) {
    this._assertNodeOrLeaf(node, 'previousDisplayedNode: arg type');
    var node_init = node;

    while (node) {
      node = this.previousSibling(node);
      if (node && this.isVisible(node))
        return this.lastVisibleSubnode(node);
    }

    // if no previous sibling
    return this.parentNode(node_init);
  },

  enclosingNode:  function (elem) {
    return Element.navigateDom(
      $(elem), 'parentNode', this.classes.nodeOrLeaf,
      this.isRootElement.bind(this));
  },

  // flash the node
  flash: function (node) {
    var label = this.label(node);

    ASSERT(label, "node has no label");

    label.flash({duration: 200});
  },

  fireEvent: function(eventName, elem) {
    var args = [eventName];
    while (elem) {
      args.push(elem);
      elem = this.parentNode(elem);
    }
    args.push(this.rootElement);
    return GvaScript.fireEvent.apply(this, args);
  },

//-----------------------------------------------------
// Private methods
//-----------------------------------------------------
  // quick navigation initialization:
  // - exit navi_mode
  // - clear navi_word
  // - clear match result
  _clear_quick_navi: function() {
    if(this._quick_navi_mode !== false)
      window.clearTimeout(this._quick_navi_mode);

    this._quick_navi_mode  = false;  // quick_navi mode active (navi timer)
    this._quick_navi_word  = "";     // word to navigate to
    this.labels_array      = null;   // tree labels array
  },

  _assertNode: function(elem, msg) {
    ASSERT(elem && Element.hasAnyClass(elem, this.classes.node), msg);
  },

  _assertNodeOrLeaf: function(elem, msg) {
    ASSERT(elem && Element.hasAnyClass(elem, this.classes.nodeOrLeaf), msg);
  },

  _addHandlers: function() {
    Event.observe(
      this.rootElement,  "mouseover",
      this._treeMouseOverHandler.bindAsEventListener(this));

    Event.observe(
      this.rootElement,  "mouseout",
      this._treeMouseOutHandler.bindAsEventListener(this));

    Event.observe(
      // observing "mouseup" instead of "click", because "click"
      // on MSIE8 only fires when there is a tabindex
      this.rootElement,  "mouseup",
      this._treeClickHandler.bindAsEventListener(this));

    Event.observe(
      this.rootElement,  "dblclick",
      this._treeDblClickHandler.bindAsEventListener(this));
  },

  _removeHandlers: function() {
    this.rootElement.stopObserving();
    this.rootElement.unregister();
  },

//-----------------------------------------------------
// mouse handlers
//-----------------------------------------------------
  _treeClickHandler : function(event) {
    var target = Event.element(event);
    // IE: click on disabled input will fire the event
    // with event.srcElement null
    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);

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

  _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)
                    ||
                    // scroll count
                    this.options.scrollCount
                  );

  // prepare some stuff to be reused when binding to inputElements
  this.reuse = {
    onmouseover : this._listOverHandler.bindAsEventListener(this),
    onclick     : this._clickHandler.bindAsEventListener(this),
    ondblclick  : this._dblclickHandler.bindAsEventListener(this),
    navigationRules: {
      DOWN:      this._highlightDelta.bindAsEventListener(this, 1),
      UP:        this._highlightDelta.bindAsEventListener(this, -1),

      PAGE_DOWN: this._highlightDelta.bindAsEventListener(this, this.pageSize),
      PAGE_UP:   this._highlightDelta.bindAsEventListener(this, -this.pageSize),

      HOME:      this._jumpToIndex.bindAsEventListener(this, 0),
      END:       this._jumpToIndex.bindAsEventListener(this, 99999),

      RETURN:    this._returnHandler .bindAsEventListener(this),
      ESCAPE:    this._escapeHandler .bindAsEventListener(this)
    }
  };

  if(this.hasPaginator) {
    // next/prev page
    this.reuse.navigationRules.RIGHT
        = this._highlightDelta.bindAsEventListener(this, this.pageSize)
    this.reuse.navigationRules.LEFT
        = this._highlightDelta.bindAsEventListener(this, -this.pageSize);

    // first/last page
    this.reuse.navigationRules.C_HOME
        = this._jumpToPage.bindAsEventListener(this, 0);
    this.reuse.navigationRules.C_END
        = this._jumpToPage.bindAsEventListener(this, 99999);
  }
};


GvaScript.ChoiceList.prototype = {

//----------------------------------------------------------------------
// PUBLIC METHODS
//----------------------------------------------------------------------
  destroy: function() {
    // test that element still in DOM
    if(this.container) Event.stopObserving(this.container);
  },

  fillContainer: function(containerElem) {

    this.container = containerElem;
    this.container.choiceList = this;

    Element.update(this.container, this.htmlForChoices());

    // mouse events on choice items will bubble up to the container
    if(this.options.mouseovernavi) {
        Event.observe(this.container, "mouseover", this.reuse.onmouseover);
    }
    Event.observe(this.container, "click"    , this.reuse.onclick);
    Event.observe(this.container, "dblclick" , this.reuse.ondblclick);

    if (this.options.keymap) {
      this.keymap = this.options.keymap;
      this.keymap.rules.push(this.reuse.navigationRules);
    }
    else {
      this.keymap = new GvaScript.KeyMap(this.reuse.navigationRules);
      var target = this.container.tabIndex == undefined
                     ? document
                     : this.container;
      this.keymap.observe("keydown", target);
    }
    // POTENTIAL PROBLEM HERE : the keymap may stay active
    // even after the choiceList is deleted (may yield memory leaks and
    // inconsistent behaviour). But we have no "destructor", so how
    // can we unregister the keymap ?


    // highlight the initial value or the first choice
    this._highlightChoiceNum(this.currentHighlightedIndex || 0, true);
  },

  updateContainer: function(container, list) {
    this.choices = list;
    Element.update(this.container, this.htmlForChoices());
    this._highlightChoiceNum(0, true);
  },

  htmlForChoices: function(){ // creates the innerHTML
    var html = "";
    for (var i = 0; i < this.choices.length; i++) {
      var choice = this.choices[i];
      var label  =
        typeof choice == "string" ? choice : choice[this.options.labelField];

      var id = this.container.id ? this.container.id + "." : '';
      id += this.options.idForChoices + "." + i;
      html += this.choiceElementHTML(label, id);
    }
    return this.options.htmlWrapper(html);
  },

  choiceElementHTML: function(label, id) {
    return "<" + this.options.choiceItemTagName + " class='" 
               + this.classes.choiceItem +  "' id='" + id + "'>"
               + label + "</" + this.options.choiceItemTagName + ">";
  },

  fireEvent: GvaScript.fireEvent, // must be copied here for binding "this"


//----------------------------------------------------------------------
// PRIVATE METHODS
//----------------------------------------------------------------------


  //----------------------------------------------------------------------
  // conversion index <=> HTMLElement
  //----------------------------------------------------------------------

  _choiceElem: function(index) { // find DOM element from choice index
    var prefix = this.container.id ? this.container.id + "." : '';
    return $(prefix + this.options.idForChoices + "." + index);
  },

  _choiceIndex: function(elem) {
    return parseInt(elem.id.match(/\.(\d+)$/)[1], 10);
  },


  //----------------------------------------------------------------------
  // highlighting
  //----------------------------------------------------------------------

  _highlightChoiceNum: function(newIndex, autoScroll) {

    // do nothing if newIndex is invalid
    if (newIndex > this.choices.length - 1) return;

    Element.removeClassName(this._choiceElem(this.currentHighlightedIndex),
                            this.classes.choiceHighlight);
    this.currentHighlightedIndex = newIndex;
    var elem = this._choiceElem(newIndex);
    // not to throw an arrow when user is holding an UP/DN keys while
    // paginating
    if(! $(elem)) return;

    Element.addClassName(elem, this.classes.choiceHighlight);

    if (autoScroll)
      Element.autoScroll(elem, this.container, 30); // 30%

    this.fireEvent({type: "Highlight", index: newIndex}, elem, this.container);
  },

  // this method restricts navigation to the current page
  _jumpToIndex: function(event, nextIndex) {
    var autoScroll = event && event.keyName; // autoScroll only for key events

    this._highlightChoiceNum(
        Math.max(0, Math.min(this.choices.length-1, nextIndex)),
        autoScroll
    );

    if (event) Event.stop(event);
  },


  // TODO: jump to page numbers would be a nice addition

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

    classes          : {},        // see below for default classes
    maxHeight        : 200,       // pixels
    minWidth         : 200,       // pixels
    offsetX          : 0,         // pixels
    strict           : false,     // will complain on illegal values
    blankOK          : true,      // if strict, will also accept blanks
    colorIllegal     : "red",     // background color when illegal values
    scrollCount      : 5,
    multivalued      : false,
    multivalue_separator :  /[;,\s]\s*/,
    choiceItemTagName: "div",
    htmlWrapper      : function(html) {return html;},
    observed_scroll  : null,      // observe the scroll of a given element and
                                  // move the dropdown accordingly (useful in
                                  // case of scrolling windows)
    additional_params: null,      // additional parameters with optional default
                                  // values (only in the case where the
                                  // datasource is a URL)
    http_method      : 'get',     // method for Ajax requests
    dependentFields  : {},
    deltaTime_tolerance : 50,      // added msec. for imprecisions in setTimeout
    ignorePrefix : false,
    caseSensitive: false

  };

  // more options for array datasources
  if (typeof datasource == "object" && datasource instanceof Array) {
    defaultOptions.ignorePrefix  = false;  // if true, will always display
                                           // the full list
    defaultOptions.caseSensitive = true;
  }

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

  // autoSuggestDelay cannot be smaller than checkNewValueDelay
  this.options.autoSuggestDelay = Math.max(this.options.autoSuggestDelay,
                                           this.options.checkNewValDelay);

  var defaultClasses = {
    loading         : "AC_loading",
    dropdown        : "AC_dropdown",
    message         : "AC_message"
  };
  this.classes = Class.checkOptions(defaultClasses, this.options.classes);

  if (this.options.multivalued && this.options.strict) {
    throw new Error("options 'strict' and 'multivalue' are incompatible");
  }

  this.dropdownDiv = null;
  // array to store running ajax requests
  // of same autocompleter but for different input element
  this._runningAjax = [];

  this.setdatasource(datasource);

  // prepare an initial keymap; will be registered at first
  // focus() event; then a second set of keymap rules is pushed/popped
  // whenever the choice list is visible
  var basicHandler = this._keyDownHandler.bindAsEventListener(this);
  var detectedKeys = /^(BACKSPACE|DELETE|KP_.*|.)$/;
                   // catch any single char, plus some editing keys
  var basicMap     = { DOWN: this._ArrowDownHandler.bindAsEventListener(this),
                       REGEX: [[null, detectedKeys, basicHandler]] };
  this.keymap = new GvaScript.KeyMap(basicMap);

  // prepare some stuff to be reused when binding to inputElements
  this.reuse = {
    onblur  : this._blurHandler.bindAsEventListener(this),
    onclick : this._clickHandler.bindAsEventListener(this)
  };
}


GvaScript.AutoCompleter.prototype = {

//----------------------------------------------------------------------
// PUBLIC METHODS
//----------------------------------------------------------------------

  // autocomplete : called when the input element gets focus; binds
  // the autocompleter to the input element
  autocomplete: function(elem) {
    elem = $(elem);// in case we got an id instead of an element

    if (!elem) throw new Error("attempt to autocomplete a null element");

    // elem is readonly => no action
    if (elem.getAttribute('readonly') || elem.readOnly) return;

    // if already bound, no more work to do
    if (elem === this.inputElement) return;

    // bind to the element; if first time, also register the event handlers
    this.inputElement = elem;
    if (!elem._autocompleter) {
      elem._autocompleter = this;
      this.keymap.observe("keydown", elem, Event.stopNone);
      Element.observe(elem, "blur", this.reuse.onblur);
      Element.observe(elem, "click", this.reuse.onclick);

      // prevent browser builtin autocomplete behaviour
      elem.writeAttribute("autocomplete", "off");
    }

    // initialize time stamps
    this._timeLastCheck = this._timeLastKeyDown = 0;

    // more initialization, but only if we did not just come back from a
    // click on the dropdownDiv
    if (!this.dropdownDiv) {
      this.lastTypedValue = this.lastValue = "";
      this.choices = null;
      this.fireEvent("Bind", elem);
    }

    this._checkNewValue();
  },

  detach: function(elem) {
    elem._autocompleter = null;
    Element.stopObserving(elem, "blur", this.reuse.onblur);
    Element.stopObserving(elem, "click", this.reuse.onclick);
    Element.stopObserving(elem, "keydown", elem.onkeydown);
  },

  displayMessage : function(message) {
    this._removeDropdownDiv();
    if(_div = this._mkDropdownDiv()) {
      _div.innerHTML = message;
      Element.addClassName(_div, this.classes.message);
    }
  },

  // set additional params for autocompleters that have more than 1 param;
  // second param is the HTTP method (post or get)
  // DALNOTE 10.01.09 : pas de raison de faire le choix de la  méthode HTTP
  // dans  setAdditionalParams()! TOFIX. Apparemment, utilisé une seule fois
  // dans DMWeb (root\src\tab_composition\form.tt2:43)
  setAdditionalParams : function(params, method) {
    this.additional_params = params;
    if (method) this.options.http_method = method;
  },

  addAdditionalParam : function(param, value) {
    if (!this.additional_params)
      this.additional_params = {};
    this.additional_params[param] = value;
  },

  setdatasource : function(datasource) {

    // remember datasource in private property
    this._datasource = datasource;

    // register proper "updateChoices" function according to type of datasource
    var ds_type = typeof datasource;
    this._updateChoicesHandler
      = (ds_type == "string")   ? this._updateChoicesFromAjax
      : (ds_type == "function") ? this._updateChoicesFromCallback
      : (ds_type == "object" && datasource instanceof Array)
                                ? this._updateChoicesFromArray
      : (ds_type == "object" && datasource instanceof Object)
                                ? this._updateChoicesFromJSONP
      : undefined;
     if (!this._updateChoicesHandler)
      throw new Error("unexpected datasource type");
  },

  // 'fireEvent' function is copied from GvaScript.fireEvent, so that "this"
  // in that code gets properly bound to the current object
  fireEvent: GvaScript.fireEvent,

  // Set the element for the AC to look at to adapt its position. If elem is
  // null, stop observing the scroll.
  // DALNOTE 10.01.09 : pas certain de l'utilité de "set_observed_scroll"; si
  // l'élément est positionné correctement dans le DOM par rapport à son parent,
  // il devrait suivre le scroll automatiquement. N'est utilisé dans DMWeb que
  // par "avocat.js".
  set_observed_scroll : function(elem) {
    if (!elem) {
        Event.stopObserving(this.observed_scroll, 'scroll',
                            correct_dropdown_position);
        return;
    }

    this.observed_scroll = elem;
    this.currentScrollTop = elem.scrollTop;
    this.currentScrollLeft = elem.scrollLeft;
    var correct_dropdown_position = function() {
      if (this.dropdownDiv) {
        var dim = Element.getDimensions(this.inputElement);
        var pos = this.dropdownDiv.positionedOffset();
        pos.top  -= this.observed_scroll.scrollTop - this.currentScrollTop;
        pos.left -= this.observed_scroll.scrollLeft;
        this.dropdownDiv.style.top  = pos.top   + "px";
        this.dropdownDiv.style.left = pos.left  + "px";
      }
      this.currentScrollTop = this.observed_scroll.scrollTop;
      this.currentScrollLeft = this.observed_scroll.scrollLeft;
    }

    Event.observe(elem, 'scroll',
                  correct_dropdown_position.bindAsEventListener(this));
  },


//----------------------------------------------------------------------
// PRIVATE METHODS
//----------------------------------------------------------------------

  _updateChoicesFromAjax: function (val_to_complete, continuation) {

    // copies into local variables, needed for closures below (can't rely on
    // 'this' because 'this' may have changed when the ajax call comes back)
    var autocompleter = this;
    var inputElement  = this.inputElement;

    inputElement.style.backgroundColor = ""; // remove colorIllegal

    // abort prev ajax request on this input element
    if (this._runningAjax[inputElement.name])
      this._runningAjax[inputElement.name].transport.abort();

    Element.addClassName(inputElement, this.classes.loading);

    // encode value to complete 
    val_to_complete = val_to_complete.split("").map(function (c) {
      if (c.match(/[@\+\/]/)) {
        return encodeURIComponent(c);
      }
      else {
        return escape(c);
      }
    }).join("");
    
    var complete_url = this._datasource + val_to_complete;

    this._runningAjax[inputElement.name] = new Ajax.Request(
      complete_url,
      {asynchronous: true,
       method: this.options.http_method,
       parameters: this.additional_params, // for example {C_ETAT_AVOC : 'AC'}

       // DALNOTE 10.01.09: forcer du JSON dans le body du POST est spécifique
       // DMWeb; pour le cas général il faut pouvoir envoyer du
       // x-www-form-urlencoded ordinaire
       postBody: this.options.http_method == 'post'
                     ? Object.toJSON(this.additional_params)
                     : null,

       contentType: "text/javascript",
       evalJSON: 'force', // will evaluate even if header != 'application/json'
       onSuccess: function(xhr) {
          // aborted by the onblur handler
          if (xhr.transport.status == 0) return;

          autocompleter._runningAjax[inputElement.name] = null;

          if (xhr.responseJSON) continuation(xhr.responseJSON);

          // autocompleter input already blurred without _blurHandler being
          // called (autocompleter is strict and needs its choices to
          // be able to fire its final status

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
      else if (value.length < this.options.minimumChars) {
        var return_value = this.fireEvent({
          type: "IllegalValue", value: value
        }, this.inputElement);

        if(! return_value) {
          this.inputElement.style.backgroundColor = this.options.colorIllegal;
          this._updateDependentFields(this.inputElement, null);
        }
      }

      // otherwise get choices and then inspect status (maybe asynchronously)
      else  {
        this._updateChoices(this._fireFinalStatus.bind(this,
                                                       this.inputElement));
      }
    }

    this.fireEvent("Leave", this.inputElement);
    this.inputElement = null;
  },

  _fireFinalStatus: function (inputElement, choices) {
  // NOTE: takes inputElement and choices as arguments, because it might be
  // called asynchronously, after "this" has been detached from the input
  // element and the choices array, so we cannot call the object properties.

    var input_val = this._getValueToComplete(inputElement.value);

    var index = null;

    // inspect the choice list to automatically choose the appropriate candidate
    for (var i=0; i < choices.length; i++) {
        var val = this._valueFromChoiceItem(choices[i]);

        if (val == input_val) {
            index = i;
            break; // break the loop because this is the best choice
        }
        else if (val.toUpperCase() == input_val.toUpperCase()) {
            index = i;  // is a candidate, but we may find a better one
        }
    }

    // if automatic choice did not work, but we have only 1 choice, and this is
    // not blank on purpose, then force it into the field
    if (index === null && choices.length == 1
                       && (input_val || !this.options.blankOK ))
        index = 0;

    if (index !== null) {
        var choice = choices[index];
        var val = this._valueFromChoiceItem(choice);

        // put canonical value back into input field
        this._setValue(val, inputElement);

        // for backwards compatibility, we generate a "Complete" event, but
        // with a fake controller (because the real controller might be in a
        // diffent state).
        this.fireEvent({ type      : "Complete",
                         referrer  : "blur",    // input blur fired this event
                         index     : index,
                         choice    : choice,
                         controller: {choices: choices} }, inputElement);

        // update dependent fields
        this._updateDependentFields(inputElement, choice);

        // for new code : generate a "LegalValue" event
        this.fireEvent({ type       : "LegalValue",
                         value      : val,
                         choice     : choice,
                         controller : null  }, inputElement);

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

        if (!dep_fields) return;

        var form       = inputElement.form;
        var name_parts = inputElement.name.split(/\./);

        for (var k in dep_fields) {
            name_parts[name_parts.length - 1] = k;
            var related_name    = name_parts.join('.');
            var related_field   = form[related_name];
            var value_in_choice = dep_fields[k];
            if (related_field) {
                related_field.value
                    = (value_in_choice == "")        ? ""
                    : (choice === null)              ? "!!ILLEGAL_" + k + "!!"
                    : (typeof choice == "object")    ? 
                      (choice[value_in_choice]       ? choice[value_in_choice] : "")
                    : (typeof choice == "string")    ? choice
                    : "!!UNEXPECTED SOURCE FOR RELATED FIELD!!";
            }
        }
    },

  // if clicking in the 20px right border of the input element, will display
  // or hide the drowpdown div (like pressing ARROWDOWN or ESC)
  _clickHandler: function(event) {
    var x = event.offsetX || event.layerX; // MSIE || FIREFOX
    if (x > Element.getDimensions(this.inputElement).width - 20) {
        if ( this.dropdownDiv ) {
            this._removeDropdownDiv();
            Event.stop(event);
        }
        else
            this._ArrowDownHandler(event);
    }
  },

  _ArrowDownHandler: function(event) {
    var value = this._getValueToComplete();
    var valueLength = (value || "").length;
    if (valueLength < this.options.minimumChars)
      this.displayMessage("liste de choix à partir de "
                            + this.options.minimumChars + " caractères");
    else
      this._displayChoices();
    Event.stop(event);
  },



  _keyDownHandler: function(event) {

    // invalidate previous lists of choices because value may have changed
    this.choices = null;
    this._removeDropdownDiv();

    // cancel pending timeouts because we create a new one
    if (this._timeoutId) clearTimeout(this._timeoutId);

    this._timeLastKeyDown = (new Date()).getTime();
//     if (window.console) console.log('keyDown', this._timeLastKeyDown, event.keyCode);
    this._timeoutId = setTimeout(this._checkNewValue.bind(this),
                                 this.options.checkNewValDelay);

    // do NOT stop the event here : give back control so that the standard
    // browser behaviour can update the value; then come back through a
    // timeout to update the Autocompleter
  },



  _checkNewValue: function() {

    // abort if the timeout occurs after a blur (no input element)
    if (!this.inputElement) {
//       if (window.console) console.log('_checkNewValue ... no input elem');
      return;
    }

    // several calls to this function may be queued by setTimeout,
    // so we perform some checks to avoid doing the work twice
    if (this._timeLastCheck > this._timeLastKeyDown) {

//       if (window.console) console.log('_checkNewValue ... done already ',
//                   this._timeLastCheck, this._timeLastKeyDown);

      return; // the work was done already
    }

    var now = (new Date()).getTime();

    var deltaTime = now - this._timeLastKeyDown;
    if (deltaTime + this.options.deltaTime_tolerance
          <  this.options.checkNewValDelay) {

//       if (window.console) console.log('_checkNewValue ... too young ',
//                                       now, this._timeLastKeyDown);

      return; // too young, let olders do the work
    }


    this._timeLastCheck = now;
    var value = this._getValueToComplete();
//     if (window.console)
//         console.log('_checkNewValue ... real work [value = %o]  - [lastValue = %o] ',
//                              value, this.lastValue);
    this.lastValue = this.lastTypedValue = value;

    // create a list of choices if we have enough chars
    if (value.length >= this.options.minimumChars) {

        // first create a "continuation function"
        var continuation = function (choices) {

          // if, meanwhile, another keyDown occurred, then abort
          if (this._timeLastKeyDown > this._timeLastCheck) {
//             if (window.console)
//               console.log('after updateChoices .. abort because of keyDown',
//                           now, this._timeLastKeyDown);
            return;
          }

          this.choices = choices;
          if (choices && choices.length > 0) {
            this.inputElement.style.backgroundColor = ""; // remove colorIllegal
            if (this.options.autoSuggest)
              this._displayChoices();
          }
          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;
  },



  _displayChoices: function() {

    // if no choices are ready, can't display anything
    if (!this.choices) return;

    var toCompleteVal = this._getValueToComplete();

    if (this.choices.length > 0) {
      var ac = this;

      // create a choiceList
      var cl = this.choiceList = new GvaScript.ChoiceList(this.choices, {
        labelField        : this.options.labelField,
        scrollCount       : this.options.scrollCount,
        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]

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

        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();
                });
            }
        }

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

        });

        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');

        this.first.observe  ('click', this.getFirstPage.bind(this));
        this.last.observe   ('click', this.getLastPage.bind(this));
        this.back.observe   ('click', this.getPrevPage.bind(this));
        this.forward.observe('click', this.getNextPage.bind(this));
    }

    return {
        destroy: function() {
            this.first.stopObserving();
            this.last.stopObserving();
            this.back.stopObserving();
            this.forward.stopObserving();
        },
        initialize: function(url, options) {

            var defaults = {
                reset                : 'no',    // if yes, first call sends RESET=yes,
                                                // subsequent calls don't (useful for
                                                // resetting cache upon first request)
                step                 : 20,

                method               : 'post',  // POST so we get dispatched to *_PROCESS_FORM
                parameters           : $H({}),
                onSuccess            : Prototype.emptyFunction,

                lazy                 : false,   // false: load first page with Paginator initialization
                                                // true: donot load automatically, loadContent would
                                                // have to be called explicity
                timeoutAjax          : 15,
                errorMsg             : "Problème de connexion. Réessayer et si le problème persiste, contacter un administrateur."
            };
            this.options = Object.extend(defaults, options || {});
            this.options.errorMsg = "<h3 style='color: #183E6C'>" + this.options.errorMsg + "</h3>";

            this.links_container = $(this.options.links_container);
            this.list_container  = $(this.options.list_container);
            this.url             = url;

            // initialization of flags
            this.index         = 1;
            this.end_index     = 0;
            this.total         = 0;

            this._executing    = false; // loadContent one at a time

            // set the css for the paginator container
            this.links_container.addClassName(paginator_css);
            // and hide it
            this.links_container.hide();
            // add the pagination elements (next/prev links + text)
            _addPaginationElts.apply(this);

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

            // load content by XHR
            if(!this.options.lazy) this.loadContent();
        },

        hasPrevious: function() {
            return this.index != 1;
        },

        hasNext: function() {

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

            else
            return false;
        },

        /* Get the prev set of records from the current url */
        getPrevPage: function() {
            if(this._executing == false && this.hasPrevious()) {
                this.index -= this.options.step;
                this.loadContent();
                return true;
            }
            else
            return false;
        },

        getLastPage: function() {
            if(this._executing == false && this.hasNext()) {
                this.index = Math.floor(this.total/this.options.step)*this.options.step+1;
                this.loadContent();
                return true;
            }
            else
            return false;
        },

        getFirstPage: function() {
            if(this._executing == false && this.hasPrevious()) {
                this.index = 1;
                this.loadContent();
                return true;
            }
            else
            return false;
        },

        // Core function of the pagination object.
        // Get records from url that are in the specified range
        loadContent: function() {
            if(this._executing == true) return; // still handling a previous request
            else this._executing = true;

            // Add STEP and INDEX as url parameters
            var url = this.url;
            this.options.parameters.update({
                STEP: this.options.step,
                INDEX: this.index,
                RESET: this.options.reset
            });

            this.links_container.hide(); // hide 'em. (one click at a time)
            this.list_container.update(new Element('div', {'class': bcss+'-loading'}));

            new Ajax.Request(url, {
                evalJSON: 'force',  // force evaluation of response into responseJSON
                method: this.options.method,
                parameters: this.options.parameters,
                requestTimeout: this.options.timeoutAjax * 1000,
                onTimeout: function(req) {
                    this._executing = false;
                    this.list_container.update(this.options.errorMsg);
                }.bind(this),
                // on s'attend à avoir du JSON en retour
                onFailure: function(req) {
                    this._executing = false;
                    var answer = req.responseJSON;
                    var msg = answer.error.message || this.options.errorMsg;
                    this.list_container.update(msg);
                }.bind(this),
                onSuccess: function(req) {
                    this._executing = false;

                    var answer = req.responseJSON;
                    if(answer) {
                        var nb_displayed_records = this.options.onSuccess(answer);
                        this.total     = answer.total; // total number of records

                        this.end_index = Math.min(this.total, this.index+nb_displayed_records-1); // end index of records on current page

                        this.textElem.innerHTML = (this.total > 0)?
                            this.index + " &agrave; " + this.end_index + " de " + this.total: '0';
                        _toggleNavigatorsVisibility.apply(this);
                    }
                 }.bind(this)
            });
        }
    }
}());

//----------grid.js
// depends: custom-buttons.js
//          paginator.js
//          choiceList.js
GvaScript.Grid = Class.create();

Object.extend(GvaScript.Grid.prototype, function() {
    var bcss = CSSPREFIX();
    function _compileDTO(dto) {
        switch(typeof dto) {
            case 'object': return $H(dto).update({VUE: 'JSON'});
            case 'string': return $H(dto.toQueryParams()).update({VUE: 'JSON'});
            default: return {VUE: 'JSON'};
        }
    }
    function _compileCss(column) {
        switch (typeof column.css) {
            case 'object': return ' '+column.css.join(' ');
            case 'string': return ' '+column.css;
            default: return '';
        }
    }
    function _compileWidth(column) {
        switch (typeof column.width) {
        case 'number': return ' style="width: '+column.width+'px"';
            case 'string':
                if(isNaN(column.width)) return ' style="width: '+column.width+'"';
                else                    return ' style="width: '+column.width+'px"';
            default: return '';
        }
    }
    function _compileTitle(column) {
        switch (typeof column.title) {
            case 'string': return 'title= '+'"'+column.title+'"';
            default: return '';
        }
    }        
    function _evalCondition(column, grid) {
        if(typeof column.condition == 'undefined') return true;
        else
        if(typeof column.condition == 'function')  return column.condition(grid);
        else
        if(eval(column.condition))                 return true;
        else                                       return false;
    }
    function _getColumnValue(column, elt) {
        switch(typeof column.value) {
            case 'function' : if(val = column.value(elt)) return val; else return (column.default_value || '');
            case 'string'   : if(val = elt[column.value]) return val; else return (column.default_value || '');
            default: return '';
        }
    }

    return {
        destroy: function() {

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

                gridheight     : 'auto',  // available space
                recordheight   : 21,      // default record height in pixels
                requestTimeout : 15,
                method         : 'post',  // default XHR method
                errorMsg       : "Problème de connexion. Réessayer et si le problème persiste, contacter un administrateur.",
                onShow         : Prototype.emptyFunction,
                onPing         : Prototype.emptyFunction,
                onEmpty        : Prototype.emptyFunction,
                onCancel       : Prototype.emptyFunction
            }

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

            this.id                =  id;
            this.grid_container    = $(this.options.grid_container);
            this.toolbar_container = $(this.options.toolbar_container);
            this.columns           = this.options.columns;
            this.datasource        = datasource;
            // determine pagesize to send to paginator
            // size is preset
            if(typeof this.options.pagesize == 'number') {
                this.limit = this.options.pagesize;
            }
            // determine dynamically
            else {
                // set the height of the grid_container
                // height is preset
                if(typeof this.options.gridheight == 'number') {
                    this.grid_container.setStyle({height: this.options.gridheight+'px'});
                }
                // determine dynamically
                else {
                    var parentHeight = this.grid_container.up(0).getHeight();
                    var sibsHeights  = this.grid_container.siblings().collect(function(s) {return s.getHeight()});

                    var sibsHeight   = 0;
                    sibsHeights.each(function(h) {sibsHeight += h});
                    this.grid_container.setStyle({height: parentHeight-sibsHeight+'px'});
                }

                this.limit = Math.floor((this.grid_container.getHeight()-22)/this.options.recordheight);
            }

            this.grid_container.setStyle({width: this.grid_container.up(0).getWidth()+'px'});

            this.toolbar_container.addClassName(bcss+'-grid-toolbar');
            this.toolbar_container.update();

            this.paginatorbar_container = new Element('div', {'class': bcss+'-paginatorbar'});
            this.actionsbar_container   = new Element('div', {'class': bcss+'-grid-actionsbar'});
            this.toolbar_container.insert(this.paginatorbar_container);
            this.toolbar_container.insert(this.actionsbar_container);

            this.dto = _compileDTO(this.options.dto);
            this.paginator = new GvaScript.Paginator(
                this.datasource, {
                    list_container  : this.grid_container,
                    links_container : this.paginatorbar_container,

                    method      : this.options.method,
                    onSuccess   : this.receiveRequest.bind(this),
                    parameters  : this.dto,
                    step        : this.limit,
                    timeoutAjax : this.options.requestTimeout,
                    errorMsg    : this.options.errorMsg,
                    lazy        : true
                }
            );

            if(! (recycled = this.grid_container.choiceList) ) {
                this.choiceList = new GvaScript.ChoiceList([], {
                    paginator         : this.paginator,
                    mouseovernavi     : false,
                    classes           : {'choiceHighlight': "hilite"},
                    choiceItemTagName : "tr",
                    grabfocus         : false,
                    htmlWrapper       : this.gridWrapper.bind(this)

                });
                this.choiceList_initialized = false;
            }
            // recycle the previously created choiceList
            else {
                this.choiceList = recycled;
                this.choiceList.options.htmlWrapper = this.gridWrapper.bind(this);
                this.choiceList.options.paginator = this.paginator;
                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'
            });
        },

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

        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
            GvaScript.Form.init(this.formElt,
                                this.options.datatree,
                                this.options.dataprefix,
                                this.options.skipAutofocus);

            // declaring form as a widget
            this.formElt.store('widget', this);
            this.formElt.addClassName(CSSPREFIX()+'-widget');

            // register the instance
            GvaScript.Forms.register(this);

            // call onInit handler
            this.fire('Init');
        },

        // returns id of the form
        getId: function() {
            return this.formElt.identify();
        },

        // use to submit the for programatically



( run in 0.758 second using v1.01-cache-2.11-cpan-2398b32b56e )