vitro/webapp/web/src/widget/ComboBox.js

236 lines
6.3 KiB
JavaScript

/*
Copyright (c) 2004-2006, The Dojo Foundation
All Rights Reserved.
Licensed under the Academic Free License version 2.1 or above OR the
modified BSD license. For more information on Dojo licensing, see:
http://dojotoolkit.org/community/licensing.shtml
*/
dojo.provide("dojo.widget.ComboBox");
dojo.require("dojo.widget.*");
dojo.require("dojo.event.*");
dojo.widget.incrementalComboBoxDataProvider = function(url, limit, timeout){
this.searchUrl = url;
this.inFlight = false;
this.activeRequest = null;
this.allowCache = false;
this.cache = {};
this.init = function(cbox){
this.searchUrl = cbox.dataUrl;
}
this.addToCache = function(keyword, data){
if(this.allowCache){
this.cache[keyword] = data;
}
}
this.startSearch = function(searchStr, type, ignoreLimit){
if(this.inFlight){
// FIXME: implement backoff!
}
var tss = encodeURIComponent(searchStr);
var realUrl = dojo.string.paramString(this.searchUrl, {"searchString": tss});
var _this = this;
var request = dojo.io.bind({
url: realUrl,
method: "get",
mimetype: "text/json",
load: function(type, data, evt){
_this.inFlight = false;
if(!dojo.lang.isArray(data)){
var arrData = [];
for(var key in data){
arrData.push([data[key], key]);
}
data = arrData;
}
_this.addToCache(searchStr, data);
_this.provideSearchResults(data);
}
});
this.inFlight = true;
}
}
dojo.widget.ComboBoxDataProvider = function(dataPairs, limit, timeout){
// NOTE: this data provider is designed as a naive reference
// implementation, and as such it is written more for readability than
// speed. A deployable data provider would implement lookups, search
// caching (and invalidation), and a significantly less naive data
// structure for storage of items.
this.data = [];
this.searchTimeout = 500;
this.searchLimit = 30;
this.searchType = "STARTSTRING"; // may also be "STARTWORD" or "SUBSTRING"
this.caseSensitive = false;
// for caching optimizations
this._lastSearch = "";
this._lastSearchResults = null;
this.init = function(cbox, node){
if(!dojo.string.isBlank(cbox.dataUrl)){
this.getData(cbox.dataUrl);
}else{
// check to see if we can populate the list from <option> elements
if((node)&&(node.nodeName.toLowerCase() == "select")){
// NOTE: we're not handling <optgroup> here yet
var opts = node.getElementsByTagName("option");
var ol = opts.length;
var data = [];
for(var x=0; x<ol; x++){
var keyValArr = [new String(opts[x].innerHTML), new String(opts[x].value)];
data.push(keyValArr);
if(opts[x].selected){
cbox.setAllValues(keyValArr[0], keyValArr[1]);
}
}
this.setData(data);
}
}
}
this.getData = function(url){
dojo.io.bind({
url: url,
load: dojo.lang.hitch(this, function(type, data, evt){
if(!dojo.lang.isArray(data)){
var arrData = [];
for(var key in data){
arrData.push([data[key], key]);
}
data = arrData;
}
this.setData(data);
}),
mimetype: "text/json"
});
}
this.startSearch = function(searchStr, type, ignoreLimit){
// FIXME: need to add timeout handling here!!
this._preformSearch(searchStr, type, ignoreLimit);
}
this._preformSearch = function(searchStr, type, ignoreLimit){
//
// NOTE: this search is LINEAR, which means that it exhibits perhaps
// the worst possible speed characteristics of any search type. It's
// written this way to outline the responsibilities and interfaces for
// a search.
//
var st = type||this.searchType;
// FIXME: this is just an example search, which means that we implement
// only a linear search without any of the attendant (useful!) optimizations
var ret = [];
if(!this.caseSensitive){
searchStr = searchStr.toLowerCase();
}
for(var x=0; x<this.data.length; x++){
if((!ignoreLimit)&&(ret.length >= this.searchLimit)){
break;
}
// FIXME: we should avoid copies if possible!
var dataLabel = new String((!this.caseSensitive) ? this.data[x][0].toLowerCase() : this.data[x][0]);
if(dataLabel.length < searchStr.length){
// this won't ever be a good search, will it? What if we start
// to support regex search?
continue;
}
if(st == "STARTSTRING"){
// jum.debug(dataLabel.substr(0, searchStr.length))
// jum.debug(searchStr);
if(searchStr == dataLabel.substr(0, searchStr.length)){
ret.push(this.data[x]);
}
}else if(st == "SUBSTRING"){
// this one is a gimmie
if(dataLabel.indexOf(searchStr) >= 0){
ret.push(this.data[x]);
}
}else if(st == "STARTWORD"){
// do a substring search and then attempt to determine if the
// preceeding char was the beginning of the string or a
// whitespace char.
var idx = dataLabel.indexOf(searchStr);
if(idx == 0){
// implicit match
ret.push(this.data[x]);
}
if(idx <= 0){
// if we didn't match or implicily matched, march onward
continue;
}
// otherwise, we have to go figure out if the match was at the
// start of a word...
// this code is taken almost directy from nWidgets
var matches = false;
while(idx!=-1){
// make sure the match either starts whole string, or
// follows a space, or follows some punctuation
if(" ,/(".indexOf(dataLabel.charAt(idx-1)) != -1){
// FIXME: what about tab chars?
matches = true; break;
}
idx = dataLabel.indexOf(searchStr, idx+1);
}
if(!matches){
continue;
}else{
ret.push(this.data[x]);
}
}
}
this.provideSearchResults(ret);
}
this.provideSearchResults = function(resultsDataPairs){
}
this.addData = function(pairs){
// FIXME: incredibly naive and slow!
this.data = this.data.concat(pairs);
}
this.setData = function(pdata){
// populate this.data and initialize lookup structures
this.data = pdata;
}
if(dataPairs){
this.setData(dataPairs);
}
}
dojo.declare(
"dojo.widget.ComboBox",
null,
{
widgetType: "ComboBox",
isContainer: false,
forceValidOption: false,
searchType: "stringstart",
dataProvider: null,
startSearch: function(searchString){},
openResultList: function(results){},
clearResultList: function(){},
hideResultList: function(){},
selectNextResult: function(){},
selectPrevResult: function(){},
setSelectedResult: function(){}
}
);
// render-specific includes
dojo.requireAfterIf("html", "dojo.widget.html.ComboBox");