📄 combobox.js
字号:
/* 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.require("dojo.io.*");dojo.require("dojo.lfx.*");dojo.require("dojo.html.*");dojo.require("dojo.html.display");dojo.require("dojo.html.layout");dojo.require("dojo.html.iframe");dojo.require("dojo.string");dojo.require("dojo.widget.html.stabile");dojo.require("dojo.widget.PopupContainer");dojo.widget.incrementalComboBoxDataProvider = function(/*String*/ url, /*Number*/ limit, /*Number*/ timeout){ this.searchUrl = url; this.inFlight = false; this.activeRequest = null; this.allowCache = false; this.cache = {}; this.init = function(/*Widget*/ cbox){ this.searchUrl = cbox.dataUrl; }; this.addToCache = function(/*String*/ keyword, /*Array*/ data){ if(this.allowCache){ this.cache[keyword] = data; } }; this.startSearch = function(/*String*/ searchStr, /*String*/ type, /*Boolean*/ ignoreLimit){ if(this.inFlight){ // FIXME: implement backoff! } var tss = encodeURIComponent(searchStr); var realUrl = dojo.string.substituteParams(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(/*Array*/ dataPairs, /*Number*/ limit, /*Number*/ 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 = timeout || 500; this.searchLimit = limit || 30; this.searchType = "STARTSTRING"; // may also be "STARTWORD" or "SUBSTRING" this.caseSensitive = false; // for caching optimizations this._lastSearch = ""; this._lastSearchResults = null; this.init = function(/*Widget*/ cbox, /*DomNode*/ 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 text = opts[x].textContent || opts[x].innerText || opts[x].innerHTML; var keyValArr = [String(text), String(opts[x].value)]; data.push(keyValArr); if(opts[x].selected){ cbox.setAllValues(keyValArr[0], keyValArr[1]); } } this.setData(data); } } }; this.getData = function(/*String*/ 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(/*String*/ searchStr, /*String*/ type, /*Boolean*/ ignoreLimit){ // FIXME: need to add timeout handling here!! this._preformSearch(searchStr, type, ignoreLimit); }; this._preformSearch = function(/*String*/ searchStr, /*String*/ type, /*Boolean*/ 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"){ 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(/*Array*/ resultsDataPairs){ }; this.addData = function(/*Array*/ pairs){ // FIXME: incredibly naive and slow! this.data = this.data.concat(pairs); }; this.setData = function(/*Array*/ pdata){ // populate this.data and initialize lookup structures this.data = pdata; }; if(dataPairs){ this.setData(dataPairs); }};dojo.widget.defineWidget( "dojo.widget.ComboBox", dojo.widget.HtmlWidget, { // Applies to any renderer isContainer: false, forceValidOption: false, searchType: "stringstart", dataProvider: null, startSearch: function(/*String*/ searchString){}, selectNextResult: function(){}, selectPrevResult: function(){}, setSelectedResult: function(){}, // HTML specific stuff autoComplete: true, name: "", // clone in the name from the DOM node textInputNode: null, comboBoxValue: null, comboBoxSelectionValue: null, optionsListWrapper: null, optionsListNode: null, downArrowNode: null, searchTimer: null, searchDelay: 100, dataUrl: "", fadeTime: 200, disabled: false, // maxListLength limits list to X visible rows, scroll on rest maxListLength: 8, // mode can also be "remote" for JSON-returning live search or "html" for // dumber live search mode: "local", selectedResult: null, _highlighted_option: null, _prev_key_backspace: false, _prev_key_esc: false, _gotFocus: false, _mouseover_list: false, dataProviderClass: "dojo.widget.ComboBoxDataProvider", buttonSrc: dojo.uri.dojoUri("src/widget/templates/images/combo_box_arrow.png"), //the old implementation has builtin fade toggle, so we mimic it here dropdownToggle: "fade", templatePath: dojo.uri.dojoUri("src/widget/templates/ComboBox.html"), templateCssPath: dojo.uri.dojoUri("src/widget/templates/ComboBox.css"), setValue: function(/*String*/ value){ this.comboBoxValue.value = value; if (this.textInputNode.value != value){ // prevent mucking up of selection this.textInputNode.value = value; // only change state and value if a new value is set dojo.widget.html.stabile.setState(this.widgetId, this.getState(), true); this.onValueChanged(value); } }, // for user to override onValueChanged: function(){ }, getValue: function(){ return this.comboBoxValue.value; }, getState: function(){ return {value: this.getValue()}; }, setState: function(/*Object*/ state){ this.setValue(state.value); }, enable:function(){ this.disabled=false; this.isEnabled = true; this.textInputNode.removeAttribute("disabled"); }, disable: function(){ this.disabled = true; this.isEnabled = false; this.textInputNode.setAttribute("disabled",true); }, getCaretPos: function(/*DomNode*/ element){ // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22 if(dojo.lang.isNumber(element.selectionStart)){ // FIXME: this is totally borked on Moz < 1.3. Any recourse? return element.selectionStart; }else if(dojo.render.html.ie){ // in the case of a mouse click in a popup being handled, // then the document.selection is not the textarea, but the popup // var r = document.selection.createRange(); // hack to get IE 6 to play nice. What a POS browser. var tr = document.selection.createRange().duplicate(); var ntr = element.createTextRange(); tr.move("character",0); ntr.move("character",0); try { // If control doesnt have focus, you get an exception. // Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes). // There appears to be no workaround for this - googled for quite a while. ntr.setEndPoint("EndToEnd", tr); return String(ntr.text).replace(/\r/g,"").length; } catch (e){ return 0; // If focus has shifted, 0 is fine for caret pos. } } }, setCaretPos: function(/*DomNode*/ element, /*Number*/ location){ location = parseInt(location); this.setSelectedRange(element, location, location); }, setSelectedRange: function(/*DomNode*/ element, /*Number*/ start, /*Number*/ end){ if(!end){ end = element.value.length; } // NOTE: Strange - should be able to put caret at start of text? // Mozilla // parts borrowed from http://www.faqts.com/knowledge_base/view.phtml/aid/13562/fid/130 if(element.setSelectionRange){ element.focus(); element.setSelectionRange(start, end); }else if(element.createTextRange){ // IE var range = element.createTextRange(); with(range){ collapse(true); moveEnd('character', end); moveStart('character', start); select(); } }else{ //otherwise try the event-creation hack (our own invention) // do we need these? element.value = element.value; element.blur(); element.focus(); // figure out how far back to go var dist = parseInt(element.value.length)-end; var tchar = String.fromCharCode(37); var tcc = tchar.charCodeAt(0); for(var x = 0; x < dist; x++){ var te = document.createEvent("KeyEvents"); te.initKeyEvent("keypress", true, true, null, false, false, false, false, tcc, tcc); element.dispatchEvent(te); } } }, // does the keyboard related stuff _handleKeyEvents: function(/*Event*/ evt){ if(evt.ctrlKey || evt.altKey || !evt.key){ return; } // reset these this._prev_key_backspace = false; this._prev_key_esc = false; var k = dojo.event.browser.keys; var doSearch = true; switch(evt.key){ case k.KEY_DOWN_ARROW: if(!this.popupWidget.isShowingNow){ this.startSearchFromInput(); } this.highlightNextOption(); dojo.event.browser.stopEvent(evt); return; case k.KEY_UP_ARROW: this.highlightPrevOption(); dojo.event.browser.stopEvent(evt); return; case k.KEY_TAB: // using linux alike tab for autocomplete if(!this.autoComplete && this.popupWidget.isShowingNow && this._highlighted_option){ dojo.event.browser.stopEvent(evt); this.selectOption({ 'target': this._highlighted_option, 'noHide': false}); // put caret last this.setSelectedRange(this.textInputNode, this.textInputNode.value.length, null); }else{ this.selectOption(); return; } break; case k.KEY_ENTER: // prevent submitting form if we press enter with list open if(this.popupWidget.isShowingNow){ dojo.event.browser.stopEvent(evt); } if(this.autoComplete){ this.selectOption(); return; } // fallthrough case " ": if(this.popupWidget.isShowingNow && this._highlighted_option){ dojo.event.browser.stopEvent(evt);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -