Alien-GvaScript
view release on metacpan or search on metacpan
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
//----------choiceList.js
//----------------------------------------------------------------------
// CONSTRUCTOR
//----------------------------------------------------------------------
GvaScript.ChoiceList = function(choices, options) {
if (! (choices instanceof Array) )
throw new Error("invalid choices argument : " + choices);
this.choices = choices;
var defaultOptions = {
labelField : "label",
classes : {}, // see below for default classes
idForChoices : "CL_choice",
keymap : null,
grabfocus : false,
mouseovernavi : true,
scrollCount : 5,
choiceItemTagName: "div",
htmlWrapper : function(html) {return html;},
paginator : null
};
this.options = Class.checkOptions(defaultOptions, options);
var defaultClasses = {
choiceItem : "CL_choiceItem",
choiceHighlight : "CL_highlight"
};
this.classes = Class.checkOptions(defaultClasses, this.options.classes);
// handy vars
this.hasPaginator = this.options.paginator != null;
this.pageSize = (
// the step size of the paginator if any
(this.hasPaginator && this.options.paginator.options.step)
||
// scroll count
this.options.scrollCount
);
// prepare some stuff to be reused when binding to inputElements
this.reuse = {
onmouseover : this._listOverHandler.bindAsEventListener(this),
onclick : this._clickHandler.bindAsEventListener(this),
ondblclick : this._dblclickHandler.bindAsEventListener(this),
navigationRules: {
DOWN: this._highlightDelta.bindAsEventListener(this, 1),
UP: this._highlightDelta.bindAsEventListener(this, -1),
PAGE_DOWN: this._highlightDelta.bindAsEventListener(this, this.pageSize),
PAGE_UP: this._highlightDelta.bindAsEventListener(this, -this.pageSize),
HOME: this._jumpToIndex.bindAsEventListener(this, 0),
END: this._jumpToIndex.bindAsEventListener(this, 99999),
RETURN: this._returnHandler .bindAsEventListener(this),
ESCAPE: this._escapeHandler .bindAsEventListener(this)
}
};
if(this.hasPaginator) {
// next/prev page
this.reuse.navigationRules.RIGHT
= this._highlightDelta.bindAsEventListener(this, this.pageSize)
this.reuse.navigationRules.LEFT
= this._highlightDelta.bindAsEventListener(this, -this.pageSize);
// first/last page
this.reuse.navigationRules.C_HOME
= this._jumpToPage.bindAsEventListener(this, 0);
this.reuse.navigationRules.C_END
= this._jumpToPage.bindAsEventListener(this, 99999);
}
};
GvaScript.ChoiceList.prototype = {
//----------------------------------------------------------------------
// PUBLIC METHODS
//----------------------------------------------------------------------
destroy: function() {
// test that element still in DOM
if(this.container) Event.stopObserving(this.container);
},
fillContainer: function(containerElem) {
this.container = containerElem;
this.container.choiceList = this;
Element.update(this.container, this.htmlForChoices());
// mouse events on choice items will bubble up to the container
if(this.options.mouseovernavi) {
Event.observe(this.container, "mouseover", this.reuse.onmouseover);
}
Event.observe(this.container, "click" , this.reuse.onclick);
Event.observe(this.container, "dblclick" , this.reuse.ondblclick);
if (this.options.keymap) {
this.keymap = this.options.keymap;
this.keymap.rules.push(this.reuse.navigationRules);
}
else {
this.keymap = new GvaScript.KeyMap(this.reuse.navigationRules);
var target = this.container.tabIndex == undefined
? document
: this.container;
this.keymap.observe("keydown", target);
}
// POTENTIAL PROBLEM HERE : the keymap may stay active
// even after the choiceList is deleted (may yield memory leaks and
// inconsistent behaviour). But we have no "destructor", so how
// can we unregister the keymap ?
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
//----------------------------------------------------------------------
_findChoiceItem: function(event) { // walk up DOM to find mouse target
var stop_condition = function(elem){return elem === this.container};
return Element.navigateDom(Event.element(event), "parentNode",
this.classes.choiceItem,
stop_condition);
},
_listOverHandler: function(event) {
var elem = this._findChoiceItem(event);
if (elem) {
this._highlightChoiceNum(this._choiceIndex(elem), false);
if (this.options.grabfocus)
this.container.focus();
Event.stop(event);
}
},
// no _listOutHandler needed
_dblclickHandler: function(event) {
var elem = this._findChoiceItem(event);
if (elem) {
var newIndex = this._choiceIndex(elem);
this._highlightChoiceNum(newIndex, false);
this._clickHandler(event);
}
},
_clickHandler: function(event) {
var elem = this._findChoiceItem(event);
if (elem) {
var newIndex = this._choiceIndex(elem);
// check if choice is selected
if (this.currentHighlightedIndex == newIndex) {
// selected -> fire ping event
var toStop = this.fireEvent({type : "Ping",
index: this._choiceIndex(elem)},
elem,
this.container);
Event.detailedStop(event, toStop || Event.stopAll);
}
else {
// not selected -> select
this._highlightChoiceNum(newIndex, false);
}
}
},
_returnHandler: function(event) {
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
lib/Alien/GvaScript/lib/GvaScript.js view on Meta::CPAN
// 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));
},
_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, "");
( run in 1.305 second using v1.01-cache-2.11-cpan-f5b5a18a01a )