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

    break;
  case "object" :
    event = arguments[0];
    break;
  default:
    throw new Error("invalid first argument to fireEvent()");
  }

  var propName = "on" + event.type;
  var handler;
  var target   = arguments[1]; // first element where the event is triggered
  var currentTarget;           // where the handler is found

  // event already fired and executing
  if(GvaScript.eventsQueue.hasEvent(target, event.type)) return;

  // try to find the handler, first in the HTML elements, then in "this"
  for (var i = 1, len = arguments.length; i < len; i++) {
    var elem = arguments[i];
    if (handler = elem.getAttribute(propName)) {
      currentTarget = elem;
      break;
    }
  }
  if (currentTarget === undefined)
    if (handler = this[propName])
      currentTarget = this;

  if (handler) {
    // build context and copy into event structure
    var controller = this;
    if (!event.target)        event.target        = target;
    if (!event.srcElement)    event.srcElement    = target;
    if (!event.currentTarget) event.currentTarget = currentTarget;
    if (!event.controller)    event.controller    = controller;

    // add the event to the queue, it's about to be fired
    GvaScript.eventsQueue.pushEvent(target, event.type);

    var event_return = null; // return value of event execution
    if (typeof(handler) == "string") {
      // string will be eval-ed in a closure context where 'this', 'event',
      // 'target' and 'controller' are defined.
      var eval_handler = function(){return eval( handler ) };
      handler = eval_handler.call(currentTarget); // target bound to 'this'
    }

    if (handler instanceof Function) {
      // now call the eval-ed or pre-bound handler
      event_return = handler(event);
    }
    else {
      // whatever was returned by the string evaluation
      event_return = handler;
    }

    // event executed, pop from the queue
    // keep a safety margin of 1sec before allowing
    // the same event on the same element to be refired
    // TODO: is 1sec reasonable
    window.setTimeout(function() {
        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 || {});

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        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;

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

};


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

TODO:
  - messages : choose language

  - multivalue :
     - inconsistent variable names
     - missing doc

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

  - sometimes arrowDown should force Ajax call even if < minChars

  - choiceElementHTML

  - cache choices. Modes are NOCACHE / CACHE_ON_BIND / CACHE_ON_SETUP

  - dependentFields should also work with non-strict autocompleters

**/

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

GvaScript.AutoCompleter = function(datasource, options) {

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

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

        //   - null                         ==> put "ILLEGAL_***"
        var attr       = inputElement.getAttribute('ac:dependentFields');
        var dep_fields = attr ? eval("("+attr+")")
                              : this.options.dependentFields;
        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));
      }
  },


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

                this.index += this.options.step;
                this.loadContent();
                return true;
            }
            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() {
            // do not destroy if not initialized !
            if(GvaScript.Grids.unregister(this.id)) {
                if(this.choiceList)    this.choiceList.destroy();
                if(this.actionButtons) this.actionButtons.destroy();
            }
        },
        initialize: function(id, datasource, options) {
            var defaults = {
                css            : '',
                dto            : {},
                columns        : [],
                actions        : [],
                grabfocus      : true,
                pagesize       : 'auto',  // fill available grid height
                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]);
        },



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