Alien-GvaScript
view release on metacpan or search on metacpan
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
104: "KP_8",
105: "KP_9",
106: "KP_STAR",
107: "KP_PLUS",
109: "KP_MINUS",
110: "KP_DOT",
111: "KP_SLASH",
112: "F1",
113: "F2",
114: "F3",
115: "F4",
116: "F5",
117: "F6",
118: "F7",
119: "F8",
120: "F9",
121: "F10",
122: "F11",
123: "F12",
144: "NUM_LOCK",
145: "SCROLL_LOCK",
190: "DOT"
},
META_KEYS: {
8 : 'KEY_BACKSPACE',
9 : 'KEY_TAB',
13 : 'KEY_RETURN',
27 : 'KEY_ESC',
37 : 'KEY_LEFT',
38 : 'KEY_UP',
39 : 'KEY_RIGHT',
40 : 'KEY_DOWN',
46 : 'KEY_DELETE',
36 : 'KEY_HOME',
35 : 'KEY_END',
33 : 'KEY_PAGEUP',
34 : 'KEY_PAGEDOWN',
45 : 'KEY_INSERT',
112 : "F1",
113 : "F2",
114 : "F3",
115 : "F4",
116 : "F5",
117 : "F6",
118 : "F7",
119 : "F8",
120 : "F9",
121 : "F10",
122 : "F11",
123 : "F12"
}
}
//----------treeNavigator.js
//-----------------------------------------------------
// Constructor
//-----------------------------------------------------
GvaScript.TreeNavigator = function(elem, options) {
// fix bug of background images on dynamic divs in MSIE 6.0, see URLs
// http://www.bazon.net/mishoo/articles.epl?art_id=958
// http://misterpixel.blogspot.com/2006/09/forensic-analysis-of-ie6.html
try { document.execCommand("BackgroundImageCache",false,true); }
catch(e) {};
elem = $(elem); // in case we got an id instead of an element
options = options || {};
// default options
var defaultOptions = {
tabIndex : -1,
treeTabIndex : 0,
flashDuration : 200, // milliseconds
flashColor : "red",
selectDelay : 100, // milliseconds
selectOnButtonClick : true,
noPingOnFirstClick : false,
selectFirstNode : true,
createButtons : true,
scrollingContainer : elem.ownerDocument.documentElement,
autoScrollPercentage: 20,
classes : {},
keymap : null
};
this.options = Class.checkOptions(defaultOptions, options);
// values can be single class names or arrays of class names
var defaultClasses = {
node : "TN_node",
leaf : "TN_leaf",
label : "TN_label",
closed : "TN_closed",
content : "TN_content",
selected : "TN_selected",
mouse : "TN_mouse",
button : "TN_button",
showall : "TN_showall"
};
this.classes = Class.checkOptions(defaultClasses, this.options.classes);
this.classes.nodeOrLeaf = [this.classes.node, this.classes.leaf].flatten();
// connect to the root element
this.rootElement = elem;
// add buttons and tabIndex to labels
this.initSubTree(elem);
// tree-wide navigation handlers
this._addHandlers();
// tree-wide tabbing handlers
this._addTabbingBehaviour();
// initializing the keymap
var keyHandlers = {
DOWN: this._downHandler .bindAsEventListener(this),
UP: this._upHandler .bindAsEventListener(this),
LEFT: this._leftHandler .bindAsEventListener(this),
RIGHT: this._rightHandler .bindAsEventListener(this),
KP_PLUS: this._kpPlusHandler .bindAsEventListener(this),
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
var index = this.currentHighlightedIndex;
if (index != undefined) {
var elem = this._choiceElem(index);
var toStop = this.fireEvent({type : "Ping",
index: index}, elem, this.container);
Event.detailedStop(event, toStop || Event.stopAll);
}
},
_escapeHandler: function(event) {
var toStop = this.fireEvent("Cancel", this.container);
Event.detailedStop(event, toStop || Event.stopAll);
}
};
//----------autoCompleter.js
/**
TODO:
- messages : choose language
- multivalue :
- inconsistent variable names
- missing doc
- rajouter option "hierarchicalValues : true/false" (si true, pas besoin de
refaire un appel serveur quand l'utilisateur rajoute des lettres).
- sometimes arrowDown should force Ajax call even if < minChars
- choiceElementHTML
- cache choices. Modes are NOCACHE / CACHE_ON_BIND / CACHE_ON_SETUP
- dependentFields should also work with non-strict autocompleters
**/
//----------------------------------------------------------------------
// CONSTRUCTOR
//----------------------------------------------------------------------
GvaScript.AutoCompleter = function(datasource, options) {
var defaultOptions = {
minimumChars : 1,
labelField : "label",
valueField : "value",
autoSuggest : true, // will dropDown automatically on keypress
autoSuggestDelay : 100, // milliseconds, (OBSOLETE)
checkNewValDelay : 100, // milliseconds
typeAhead : true, // will fill the inputElement on highlight
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);
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
= (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
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));
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
// 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);
}
else {
var return_value = this.fireEvent({
type : "IllegalValue",
value : input_val,
controller : null
}, inputElement);
if(! return_value) {
inputElement.style.backgroundColor = this.options.colorIllegal;
this._updateDependentFields(inputElement, null);
}
}
},
_updateDependentFields: function(inputElement, choice) {
// "choice" might be
// - an object or nonempty string ==> update dependent fields
// - an empty string ==> clear dependent fields
// - 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);
},
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
// 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
( run in 1.515 second using v1.01-cache-2.11-cpan-0d23b851a93 )