view release on metacpan or search on metacpan
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
/*-------------------------------------------------------------------------*
* GvaScript - Javascript framework born in Geneva.
*
* Authors: Laurent Dami <laurent.d...@etat.ge.ch>
* Mona Remlawi
* Jean-Christophe Durand
* Sebastien Cuendet
* LICENSE
* This library is free software, you can redistribute it and/or modify
* it under the same terms as Perl's artistic license.
*
*--------------------------------------------------------------------------*/
var GvaScript = {
Version: '1.45',
REQUIRED_PROTOTYPE: '1.7',
load: function() {
function convertVersionString(versionString) {
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 = {};
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
// start at elem, walk nav_property until find any of wanted_classes
navigateDom: function (elem, navigation_property,
wanted_classes, stop_condition) {
while (elem){
if (stop_condition && stop_condition(elem)) break;
if (elem.nodeType == 1 &&
Element.hasAnyClass(elem, wanted_classes))
return $(elem);
// else walk to next element
elem = elem[navigation_property];
}
return null;
},
autoScroll: function(elem, container, percentage) {
percentage = percentage || 20; // default
container = container || elem.offsetParent;
var offset = elem.offsetTop;
var firstElementChild = container.firstElementChild
|| $(container).firstDescendant();
if (firstElementChild) {
var first_child_offset = firstElementChild.offsetTop;
if (first_child_offset == container.offsetTop)
offset -= first_child_offset;
}
var min = offset - (container.clientHeight * (100-percentage)/100);
var max = offset - (container.clientHeight * percentage/100);
if (container.scrollTop < min) container.scrollTop = min;
else if (container.scrollTop > max) container.scrollTop = max;
},
outerHTML: function(elem) {
var tag = elem.tagName;
if (!tag)
return elem; // not an element node
if (elem.outerHTML)
return elem.outerHTML; // has builtin implementation
else {
var attrs = elem.attributes;
var str = "<" + tag;
for (var i = 0; i < attrs.length; i++) {
var val = attrs[i].value;
var delim = val.indexOf('"') > -1 ? "'" : '"';
str += " " + attrs[i].name + "=" + delim + val + delim;
}
return str + ">" + elem.innerHTML + "</" + tag + ">";
}
}
});
Class.checkOptions = function(defaultOptions, ctorOptions) {
ctorOptions = ctorOptions || {}; // options passed to the class constructor
for (var property in ctorOptions) {
if (defaultOptions[property] === undefined)
throw new Error("unexpected option: " + property);
}
return Object.extend(Object.clone(defaultOptions), ctorOptions);
};
Object.extend(Event, {
detailedStop: function(event, toStop) {
if (toStop.preventDefault) {
if (event.preventDefault) event.preventDefault();
else event.returnValue = false;
}
if (toStop.stopPropagation) {
if (event.stopPropagation) event.stopPropagation();
else event.cancelBubble = true;
}
},
stopAll: {stopPropagation: true, preventDefault: true},
stopNone: {stopPropagation: false, preventDefault: false}
});
function ASSERT (cond, msg) {
if (!cond)
throw new Error("Violated assertion: " + msg);
}
// detects if a global CSS_PREFIX has been set
// if yes, use it to prefix the css classes
// default to gva
function CSSPREFIX () {
if(typeof CSS_PREFIX != 'undefined') {
return (CSS_PREFIX)? CSS_PREFIX : 'gva';
}
return 'gva';
}
/**
*
* Cross-Browser Split 1.0.1
* (c) Steven Levithan <stevenlevithan.com>; MIT License
* in order to fix a bug with String.prototype.split(RegExp) and Internet Explorer
* [http://blog.stevenlevithan.com/archives/cross-browser-split]
* An ECMA-compliant, uniform cross-browser split method
*
* */
var cbSplit;
// avoid running twice, which would break `cbSplit._nativeSplit`'s reference to the native `split`
if (!cbSplit) {
cbSplit = function (str, separator, limit) {
// if `separator` is not a regex, use the native `split`
if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
return cbSplit._nativeSplit.call(str, separator, limit);
}
var output = [],
lastLastIndex = 0,
flags = (separator.ignoreCase ? "i" : "") +
(separator.multiline ? "m" : "") +
(separator.sticky ? "y" : ""),
separator = RegExp(separator.source, flags + "g"), // make `global` and avoid `lastIndex` issues by working with a copy
separator2, match, lastIndex, lastLength;
str = str + ""; // type conversion
if (!cbSplit._compliantExecNpcg) {
separator2 = RegExp("^" + separator.source + "$(?!\\s)", flags); // doesn't need /g or /y, but they don't hurt
}
/* behavior for `limit`: if it's...
- `undefined`: no limit.
- `NaN` or zero: return an empty array.
- a positive number: use `Math.floor(limit)`.
- a negative number: no limit.
- other: type-convert, then use the above rules. */
if (limit === undefined || +limit < 0) {
limit = Infinity;
} else {
limit = Math.floor(+limit);
if (!limit) {
return [];
}
}
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
var id = 0, head = $$('head')[0];
Prototype.getJSON = function(url, callback) {
var script = document.createElement('script'), token = '__jsonp' + id;
// callback should be a global function
window[token] = callback;
// url should have "?2" parameter which is to be replaced with a global callback name
script.src = url.replace(/\?(&|$)/, '__jsonp' + id + '$1');
// clean up on load: remove script tag, null script variable and delete global callback function
script.onload = function() {
script.remove();
script = null;
delete window[token];
};
head.appendChild(script);
// callback name should be unique
id++;
}
})();
//----------event.js
// array holding fired events that are pending to be executed
// useful for avoiding accidental double firing of events
// events in queue are unique per eventType&eventTarget
GvaScript.eventsQueue = Class.create();
Object.extend(GvaScript.eventsQueue, {
_queue: $A([]),
hasEvent: function(target, name) {
return (typeof this._queue.find(function(e) {
return (e.target == target && e.name == name);
}) == 'object');
},
pushEvent: function(target, name) {
this._queue.push({target: target, name: name});
},
popEvent: function(target, name) {
this._queue = this._queue.reject(function(e) {
return (e.target == target && e.name == name);
});
}
});
// fireEvent : should be COPIED into controller objects, so that
// 'this' is properly bound to the controller
GvaScript.fireEvent = function(/* type, elem1, elem2, ... */) {
var event;
switch (typeof arguments[0]) {
case "string" :
event = {type: arguments[0]};
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 || {});
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);
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
node = this.parentNode(node);
}
},
openAtLevel: function(elem, level) {
var method = this[(level > 1) ? "open" : "close"];
var node = this.firstSubNode(elem);
while (node) {
method.call(this, node); // open or close
this.openAtLevel(node, level-1);
node = this.nextSibling(node);
}
},
loadContent: function (node) {
var url = node.getAttribute('tn:contenturl');
// TODO : default URL generator at the tree level
if (url) {
var content = this.content(node);
if (!content) {
content = document.createElement('div');
content.className = this.classes.content;
var content_type = node.getAttribute('content_type');
if (content_type) content.className += " " + content_type;
content.innerHTML = "loading " + url;
node.insertBefore(content, null); // null ==> insert at end of node
}
this.fireEvent("BeforeLoadContent", node, this.rootElement);
var treeNavigator = this; // needed for closure below
var callback = function() {
treeNavigator.initSubTree(content);
treeNavigator.fireEvent("AfterLoadContent", node, this.rootElement);
};
new Ajax.Updater(content, url, {onComplete: callback});
return true;
}
},
select: function (node) {
var previousNode = this.selectedNode;
// re-selecting the current node is a no-op
if (node == previousNode) return;
// deselect the previously selected node
if (previousNode) {
var label = this.label(previousNode);
if (label) {
Element.removeClassName(label, this.classes.selected);
}
}
// select the new node
this.selectedNode = node;
if (node) {
this._assertNodeOrLeaf(node, 'select node');
var label = this.label(node);
if (!label) {
throw new Error("selected node has no label");
}
else {
Element.addClassName(label, this.classes.selected);
if (this.isVisible(label)) {
// focus has not yet been given to label
if(! label.hasAttribute('hasFocus'))
label.focus();
}
}
}
// cancel if there was any select execution pending
if (this._selectionTimeoutId) clearTimeout(this._selectionTimeoutId);
// register code to call the selection handlers after some delay
var callback = this._selectionTimeoutHandler.bind(this, previousNode);
this._selectionTimeoutId =
setTimeout(callback, this.options.selectDelay);
},
scrollTo: function(node, with_content) {
if(!node) return;
var container = this.options.scrollingContainer;
if(typeof container == 'string') {
container = $(container);
}
if(!container) return;
// donot invoke scroll if scrolling is disabled
// first test if scrolling is enabled on the scrolling container
if(container.tagName.toLowerCase() == 'html') {
// on document body
if(document.body.style.overflow == 'hidden'
|| document.body.style.overflowY == 'hidden'
|| document.body.scroll == 'no') // IE
return;
}
else {
// on element
if(container.style.overflow == 'hidden'
|| container.style.overflowY == 'hidden')
return;
}
// test if the node in 'in view'
_container_y_start = container.scrollTop;
_container_y_end = _container_y_start + container.clientHeight;
_node_y = Element.cumulativeOffset(node).top + (with_content? node.offsetHeight: 0);
// calculate padding space between the selected node and
// the edge of the scrollable container
_perc = this.options.autoScrollPercentage || 0;
_padding = container.clientHeight * _perc / 100;
// calculate delta scroll to affect on scrollingContainer
_delta = 0;
// node is beneath scrolling area
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
this.select (znode);
Event.stop(event);
}
},
_endHandler: function (event) {
if (this.selectedNode) {
var znode = this.lastVisibleSubnode();
this.scrollTo(znode);
this.select (znode);
Event.stop(event);
}
},
_ctrlPgUpHandler: function (event) {
var node = this.enclosingNode(Event.element(event));
if (node) {
this.scrollTo(node);
this.select (node);
Event.stop(event);
}
},
_ctrlPgDownHandler: function (event) {
var node = this.enclosingNode(Event.element(event));
if (node) {
node = this.nextDisplayedNode(node);
if (node) {
this.scrollTo(node);
this.select (node);
Event.stop(event);
}
}
},
_chooseLevel: function(event) {
var level = event.keyCode - "0".charCodeAt(0);
this.openAtLevel(this.rootElement, level);
// stop the default Ctrl-num event
// FF: jump to tab#num
// IE: Ctrl-5 Select-All
Event.stop(event);
},
_showAll: function(event, toggle) {
var method = toggle ? Element.addClassName : Element.removeClassName;
method(this.rootElement, this.classes.showall);
}
};
//----------choiceList.js
//----------------------------------------------------------------------
// CONSTRUCTOR
//----------------------------------------------------------------------
GvaScript.ChoiceList = function(choices, options) {
if (! (choices instanceof Array) )
throw new Error("invalid choices argument : " + choices);
this.choices = choices;
var defaultOptions = {
labelField : "label",
classes : {}, // see below for default classes
idForChoices : "CL_choice",
keymap : null,
grabfocus : false,
mouseovernavi : true,
scrollCount : 5,
choiceItemTagName: "div",
htmlWrapper : function(html) {return html;},
paginator : null
};
this.options = Class.checkOptions(defaultOptions, options);
var defaultClasses = {
choiceItem : "CL_choiceItem",
choiceHighlight : "CL_highlight"
};
this.classes = Class.checkOptions(defaultClasses, this.options.classes);
// handy vars
this.hasPaginator = this.options.paginator != null;
this.pageSize = (
// the step size of the paginator if any
(this.hasPaginator && this.options.paginator.options.step)
||
// 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);
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
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
_jumpToPage: function(event, pageIndex) {
if(pageIndex <=1) return this.options.paginator.getFirstPage();
if(pageIndex == 99999) return this.options.paginator.getLastPage();
if (event) Event.stop(event);
},
// would navigate through pages if index goes out of bound
_highlightDelta: function(event, deltax, deltay) {
var currentIndex = this.currentHighlightedIndex;
var nextIndex = currentIndex + deltax;
// first try to flip a page
// if first page -> go top of list
if (nextIndex < 0) {
if(this.hasPaginator) {
if(this.options.paginator.getPrevPage()) return;
}
nextIndex = 0;
}
if (nextIndex >= this.choices.length) {
if(this.hasPaginator) {
if(this.options.paginator.getNextPage()) return;
}
nextIndex = this.choices.length -1;
}
// we're still on the same page
this._jumpToIndex(event, nextIndex);
},
//----------------------------------------------------------------------
// navigation
//----------------------------------------------------------------------
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
// 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
//----------------------------------------------------------------------
// 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
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
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
if (xhr['blurAfterSuccess']) autocompleter._blurHandler();
},
onFailure: function(xhr) {
autocompleter._runningAjax[inputElement.name] = null;
autocompleter.displayMessage("pas de réponse du serveur");
},
onComplete: function(xhr) {
Element.removeClassName(inputElement,
autocompleter.classes.loading);
}
});
},
_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) {
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
// repetition block gets an event
placeholder.fireEvent("Add", insertion_block);
// deal with nested repeated sections
this._init_repeat_elements(insertion_block, repeat.path);
// restore initial path
repeat.path = path_ini;
}
return repeat.count;
},
remove: function(repetition_block, live_update) {
// default behavior to live update all blocks below
// the removed block
if(typeof live_update == 'undefined') live_update = true;
// find element, placeholder and repeat info
var elem = $(repetition_block);
elem.id.match(/(.*)\.(\d+)$/);
var repeat_name = RegExp.$1;
var remove_ix = RegExp.$2;
var placeholder = this._find_placeholder(repeat_name);
var max = placeholder.repeat.count;
// fire onRemove event
// remove block from DOM
var block = $(repeat_name + "." + remove_ix);
placeholder.fireEvent("Remove", block);
block.remove();
// if live_update
if(live_update) {
// update repeat.count as first block has been deleted
placeholder.repeat.count -= 1;
// remove all blocks below
var _start_ix = remove_ix;
while(++_start_ix < max) {
block = $(repeat_name + "." + _start_ix);
block.remove();
placeholder.repeat.count -= 1;
}
// re-add the blocks above so that they will be renumbered
var n_add = max - remove_ix - 1;
if (n_add > 0) this.add(placeholder, n_add);
}
},
//-----------------------------------------------------
// Private methods
//-----------------------------------------------------
_find_placeholder: function(name) {
if (typeof name == "string" && !name.match(/.placeholder$/))
name += ".placeholder";
var placeholder = $(name);
if (!placeholder) throw new Error("no such element: " + name);
return placeholder;
},
_init_repeat_elements: function(elem, path) {
elem = $(elem);
if (elem) {
var elements = this._find_repeat_elements(elem);
for (var i = 0; i < elements.length; i++) {
this._init_repeat_element(elements[i], path);
}
}
},
_find_repeat_elements: function(elem) {
var result = [];
// navigate DOM, do not recurse under "repeat" nodes
for (var child = elem.firstChild; child; child = child.nextSibling) {
var has_repeat = child.nodeType == 1 && child.getAttribute('repeat');
result.push(has_repeat ? child : this._find_repeat_elements(child));
}
return result.flatten();
},
_init_repeat_element: function(element, path) {
element = $(element);
path = path || element.getAttribute('repeat-prefix');
// number of initial repetition blocks
var n_blocks = element.getAttribute('repeat-start');
if (n_blocks == undefined) n_blocks = 1;
// hash to hold all properties of the repeat element
var repeat = {};
repeat.name = element.getAttribute('repeat');
repeat.min = element.getAttribute('repeat-min') || 0;
repeat.max = element.getAttribute('repeat-max') || 99;
repeat.count = 0;
repeat.path = (path ? path + "." : "") + repeat.name;
// create a new element (placeholder for new insertion blocks)
var placeholder_tag = element.tagName.match(/^(TR|TD|TBODY|THEAD|TH)$/i)
? element.tagName
: 'SPAN';
var placeholder = document.createElement(placeholder_tag);
placeholder.id = repeat.path + ".placeholder";
placeholder.fireEvent = GvaScript.fireEvent;
element.parentNode.insertBefore(placeholder, element);
// take this elem out of the DOM and into a string ...
{
// a) force the id that will be needed in the template)
element.id = "#{" + repeat.name + ".path}";
// b) remove "repeat*" attributes (don't want them in the template)
var attrs = element.attributes;
var repeat_attrs = [];
for (var i = 0; i < attrs.length; i++) {
var name = attrs[i].name;
if (name.match(/^repeat/i)) repeat_attrs.push(name);
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
var old_value = null; // needed for value:change custom event
var new_value = null;
switch (elem.type) {
case "text" :
case "textarea" :
case "hidden" :
old_value = elem.value;
elem.value = new_value = val.join(",");
break;
case "checkbox" :
case "radio":
var elem_val = elem.value;
old_value = elem.checked ? elem_val : null;
// hand-crafted loop through val array (because val.include() is too slow)
elem.checked = false;
for (var count = val.length; count--;) {
if (val[count] == elem_val) {
elem.checked = true;
break;
}
}
new_value = elem.checked ? elem_val : null;
break;
case "select-one" :
case "select-multiple" :
var options = elem.options;
var old_values = [],
new_values = [];
for (var i=0, len=options.length; i<len; i++) {
var opt = options[i];
var opt_value = opt.value || opt.text;
if (opt.selected) old_values.push(opt_value);
// hand-crafted loop through val array (because val.include() is too slow
opt.selected = false;
for (var count = val.length; count--;) {
if (val[count] == opt_value) {
new_values.push(opt_value);
opt.selected = true;
break;
}
}
}
old_value = old_values.join(",");
new_value = new_values.join(",");
break;
default:
// if no element type, might be a node list
var elem_length = elem.length;
if (elem_length !== undefined) {
for (var i=0; i < elem_length; i++) {
_fill_from_value(form, elem.item(i), val, is_init);
}
}
else
throw new Error("unexpected elem type : " + elem.type);
break;
} // end switch
// if initializing form
// and form has an init handler registered to its inputs
// and elem has a new_value set
// => fire the custom 'value:init' event
if (is_init) {
if (form.has_init_registered)
if (new_value)
Element.fire(elem, 'value:init', {newvalue: new_value});
}
else {
if (new_value != old_value)
Element.fire(elem, 'value:change', {oldvalue: old_value, newvalue: new_value});
}
}
var _fill_from_array = function (form, field_prefix, array, is_init) {
for (var i=0, len=array.length; i < len; i++) {
var new_prefix = field_prefix + "." + i;
// if form has a corresponding named element, fill it
var elem = form[new_prefix];
if (elem) {
_fill_from_value(form, elem, array[i], is_init);
continue;
}
// otherwise try to walk down to a repetition block
// try to find an existing repetition block
elem = doc.getElementById(new_prefix); // TODO : check: is elem in form ?
// no repetition block found, try to instanciate one
if (!elem) {
var placeholder = doc.getElementById(field_prefix + ".placeholder");
if (placeholder && placeholder.repeat) {
GvaScript.Repeat.add(placeholder, i + 1 - placeholder.repeat.count);
elem = doc.getElementById(new_prefix);
}
}
// recurse to the repetition block
// mremlawi: sometimes multi-value fields are filled without
// passing by the repeat moduleearly
// -> no id's on repeatable blocks are set but need to recurse anyway
// if (elem)
GvaScript.Form.fill_from_tree(form, new_prefix, array[i], is_init);
}
}
function fill_from_tree(form, field_prefix, tree, is_init) {
if (Object.isString(form)) form = $(form);
for (var key in tree) {
if (!tree.hasOwnProperty(key)) continue;
var val = tree[key];
var new_prefix = field_prefix ? field_prefix+'.'+key : key;