Alien-GvaScript
view release on metacpan or search on metacpan
src/autoCompleter.js view on Meta::CPAN
/**
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
//----------------------------------------------------------------------
src/autoCompleter.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));
}
},
( run in 1.932 second using v1.01-cache-2.11-cpan-119454b85a5 )