| /* |
| Copyright (c) 2004-2005, 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.html.ComboBox"); |
| dojo.require("dojo.widget.ComboBox"); |
| dojo.require("dojo.widget.*"); |
| dojo.require("dojo.io.*"); |
| dojo.require("dojo.fx.*"); |
| dojo.require("dojo.dom"); |
| dojo.require("dojo.html"); |
| dojo.require("dojo.string"); |
| dojo.require("dojo.widget.html.stabile"); |
| |
| dojo.widget.html.ComboBox = function(){ |
| dojo.widget.ComboBox.call(this); |
| dojo.widget.HtmlWidget.call(this); |
| |
| this.autoComplete = true; |
| this.formInputName = ""; |
| this.name = ""; // clone in the name from the DOM node |
| this.textInputNode = null; |
| this.comboBoxValue = null; |
| this.comboBoxSelectionValue = null; |
| this.optionsListWrapper = null; |
| this.optionsListNode = null; |
| this.downArrowNode = null; |
| this.cbTableNode = null; |
| this.searchTimer = null; |
| this.searchDelay = 100; |
| this.dataUrl = ""; |
| // mode can also be "remote" for JSON-returning live search or "html" for |
| // dumber live search |
| this.mode = "local"; |
| this.selectedResult = null; |
| this._highlighted_option = null; |
| this._prev_key_backspace = false; |
| this._prev_key_esc = false; |
| this._result_list_open = false; |
| } |
| |
| dojo.inherits(dojo.widget.html.ComboBox, dojo.widget.HtmlWidget); |
| |
| // copied from superclass since we can't really over-ride via prototype |
| dojo.lang.extend(dojo.widget.html.ComboBox, dojo.widget.ComboBox.defaults); |
| |
| dojo.lang.extend(dojo.widget.html.ComboBox, { |
| |
| |
| templatePath: dojo.uri.dojoUri("src/widget/templates/HtmlComboBox.html"), |
| templateCssPath: dojo.uri.dojoUri("src/widget/templates/HtmlComboBox.css"), |
| |
| setValue: function(value) { |
| this.comboBoxValue.value = this.textInputNode.value = value; |
| dojo.widget.html.stabile.setState(this.widgetId, this.getState(), true); |
| }, |
| |
| getValue: function() { |
| return this.comboBoxValue.value; |
| }, |
| |
| getState: function() { |
| return {value: this.getValue()}; |
| }, |
| |
| setState: function(state) { |
| this.setValue(state.value); |
| }, |
| |
| |
| getCaretPos: function(element){ |
| // FIXME: we need to figure this out for Konq/Safari! |
| if(dojo.render.html.mozilla){ |
| // 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 = r.duplicate(); |
| var tr = document.selection.createRange().duplicate(); |
| // var ntr = document.selection.createRange().duplicate(); |
| var ntr = element.createTextRange(); |
| // FIXME: this seems to work but I'm getting some execptions on reverse-tab |
| tr.move("character",0); |
| ntr.move("character",0); |
| /* |
| try{ |
| ntr.moveToElementText(element); |
| }catch(e){ dojo.debug(e); } |
| */ |
| ntr.setEndPoint("EndToEnd", tr); |
| return String(ntr.text).replace(/\r/g,"").length; |
| } |
| }, |
| |
| setCaretPos: function(element, location){ |
| location = parseInt(location); |
| this.setSelectedRange(element, location, location); |
| }, |
| |
| setSelectedRange: function(element, start, end){ |
| if(!end){ end = element.value.length; } |
| // 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); |
| twe.dispatchEvent(te); |
| } |
| } |
| }, |
| |
| killEvent: function(evt){ |
| evt.preventDefault(); |
| evt.stopPropagation(); |
| }, |
| |
| onKeyDown: function(evt){ |
| }, |
| |
| setSelectedValue: function(value){ |
| // FIXME, not sure what to do here! |
| this.comboBoxSelectionValue.value = value; |
| this.hideResultList(); |
| }, |
| |
| highlightNextOption: function(){ |
| if(this._highlighted_option){ |
| dojo.html.removeClass(this._highlighted_option, "cbItemHighlight"); |
| } |
| if((!this._highlighted_option)||(!this._highlighted_option.nextSibling)){ |
| this._highlighted_option = this.optionsListNode.firstChild; |
| }else{ |
| this._highlighted_option = this._highlighted_option.nextSibling; |
| } |
| dojo.html.addClass(this._highlighted_option, "cbItemHighlight"); |
| }, |
| |
| highlightPrevOption: function(){ |
| if(this._highlighted_option){ |
| dojo.html.removeClass(this._highlighted_option, "cbItemHighlight"); |
| } |
| if((!this._highlighted_option)||(!this._highlighted_option.previousSibling)){ |
| this._highlighted_option = this.optionsListNode.lastChild; |
| }else{ |
| this._highlighted_option = this._highlighted_option.previousSibling; |
| } |
| dojo.html.addClass(this._highlighted_option, "cbItemHighlight"); |
| }, |
| |
| onKeyUp: function(evt){ |
| if(evt.keyCode == 27){ // esc is 27 |
| this.hideResultList(); |
| if(this._prev_key_esc){ |
| this.textInputNode.blur(); |
| this.selectedResult = null; |
| } |
| this._prev_key_esc = true; |
| return; |
| }else if((evt.keyCode == 32)||(evt.keyCode == 13)){ // space is 32, enter is 13. |
| /* |
| // Cancel the enter key event bubble to avoid submitting the form. |
| if (evt.keyCode == 13) { |
| // FIXME: the does not cancel the form submission. |
| this.killEvent(evt); |
| } |
| */ |
| // If the list is open select the option with the event. |
| if(this._result_list_open){ |
| evt = { target: this._highlighted_option }; |
| this.selectOption(evt); |
| }else{ |
| // Otherwise select the option with out the event. |
| this.selectOption(); |
| } |
| return; |
| }else if(evt.keyCode == 40){ // down is 40 |
| if(!this._result_list_open){ |
| this.startSearchFromInput(); |
| } |
| this.highlightNextOption(); |
| return; |
| }else if(evt.keyCode == 38){ // up is 38 |
| this.highlightPrevOption(); |
| return; |
| }else{ |
| this.setValue(this.textInputNode.value); |
| } |
| |
| // backspace is 8 |
| this._prev_key_backspace = (evt.keyCode == 8) ? true : false; |
| this._prev_key_esc = false; |
| |
| if(this.searchTimer){ |
| clearTimeout(this.searchTimer); |
| } |
| if((this._prev_key_backspace)&&(!this.textInputNode.value.length)){ |
| this.hideResultList(); |
| }else{ |
| this.searchTimer = setTimeout(dojo.lang.hitch(this, this.startSearchFromInput), this.searchDelay); |
| } |
| }, |
| |
| fillInTemplate: function(args, frag){ |
| // FIXME: need to get/assign DOM node names for form participation here. |
| this.comboBoxValue.name = this.name; |
| this.comboBoxSelectionValue.name = this.name+"_selected"; |
| |
| // NOTE: this doesn't copy style info inherited from classes; |
| // it's just primitive support for direct style setting |
| var sourceNodeStyle = this.getFragNodeRef(frag).style; |
| if ( sourceNodeStyle ){ |
| this.domNode.style.cssText = sourceNodeStyle.cssText; |
| } |
| |
| // FIXME: add logic |
| this.dataProvider = new dojo.widget.ComboBoxDataProvider(); |
| |
| if(!dojo.string.isBlank(this.dataUrl)){ |
| if("local" == this.mode){ |
| var _this = this; |
| dojo.io.bind({ |
| url: this.dataUrl, |
| load: function(type, data, evt){ |
| if(type=="load"){ |
| _this.dataProvider.setData(data); |
| } |
| }, |
| mimetype: "text/javascript" |
| }); |
| }else if("remote" == this.mode){ |
| this.dataProvider = new dojo.widget.incrementalComboBoxDataProvider(this.dataUrl); |
| } |
| }else{ |
| // check to see if we can populate the list from <option> elements |
| var node = frag["dojo:"+this.widgetType.toLowerCase()]["nodeRef"]; |
| 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++){ |
| data.push([new String(opts[x].innerHTML), new String(opts[x].value)]); |
| } |
| this.dataProvider.setData(data); |
| } |
| } |
| |
| // Prevent IE bleed-through problem |
| this.bgIframe = new dojo.html.BackgroundIframe(); |
| if(this.bgIframe.iframe){ |
| this.optionsListWrapper.appendChild(this.bgIframe.iframe); |
| } |
| }, |
| |
| openResultList: function(results){ |
| this.clearResultList(); |
| if(!results.length){ |
| this.hideResultList(); |
| }else{ |
| this.showResultList(); |
| } |
| if( (this.autoComplete)&& |
| (results.length)&& |
| (!this._prev_key_backspace)&& |
| (this.textInputNode.value.length > 0)){ |
| var cpos = this.getCaretPos(this.textInputNode); |
| // only try to extend if we added the last charachter at the end of the input |
| if((cpos+1) >= this.textInputNode.value.length){ |
| this.textInputNode.value = results[0][0]; |
| // build a new range that has the distance from the earlier |
| // caret position to the end of the first string selected |
| this.setSelectedRange(this.textInputNode, cpos, this.textInputNode.value.length); |
| } |
| } |
| |
| var even = true; |
| while(results.length){ |
| var tr = results.shift(); |
| if(tr){ |
| var td = document.createElement("div"); |
| td.appendChild(document.createTextNode(tr[0])); |
| td.setAttribute("resultName", tr[0]); |
| td.setAttribute("resultValue", tr[1]); |
| td.className = "cbItem "+((even) ? "cbItemEven" : "cbItemOdd"); |
| even = (!even); |
| this.optionsListNode.appendChild(td); |
| } |
| } |
| |
| dojo.event.kwConnect({ |
| once: true, |
| srcObj: dojo.html.body(), |
| srcFunc: "onclick", |
| adviceObj: this, |
| adviceFunc: "hideResultList" |
| }); |
| |
| // prevent IE bleed through |
| dojo.lang.setTimeout(this, "showBackgroundIframe", 100); |
| }, |
| |
| showBackgroundIframe: function(){ |
| var w = dojo.style.getOuterWidth(this.optionsListNode); |
| var h = dojo.style.getOuterHeight(this.optionsListNode); |
| if ( isNaN(w) || isNaN(h) ){ |
| // need more time to calculate size |
| dojo.lang.setTimeout(this, "showBackgroundIframe", 100); |
| return; |
| } |
| this.bgIframe.show([0,0,w,h]); |
| this.bgIframe.setZIndex(1); |
| }, |
| |
| selectOption: function(evt){ |
| if(!evt){ |
| evt = { target: this._highlighted_option }; |
| } |
| |
| if(!dojo.dom.isDescendantOf(evt.target, this.optionsListNode)){ |
| return; |
| } |
| |
| var tgt = evt.target; |
| while((tgt.nodeType!=1)||(!tgt.getAttribute("resultName"))){ |
| tgt = tgt.parentNode; |
| if(tgt === dojo.html.body()){ |
| return false; |
| } |
| } |
| |
| this.textInputNode.value = tgt.getAttribute("resultName"); |
| this.selectedResult = [tgt.getAttribute("resultName"), tgt.getAttribute("resultValue")]; |
| this.setValue(tgt.getAttribute("resultName")); |
| this.comboBoxSelectionValue.value = tgt.getAttribute("resultValue"); |
| this.hideResultList(); |
| }, |
| |
| clearResultList: function(){ |
| var oln = this.optionsListNode; |
| while(oln.firstChild){ |
| oln.removeChild(oln.firstChild); |
| } |
| }, |
| |
| hideResultList: function(){ |
| dojo.fx.fadeHide(this.optionsListNode, 200); |
| dojo.event.disconnect(dojo.html.body(), "onclick", this, "hideResultList"); |
| this._result_list_open = false; |
| this.bgIframe.hide(); |
| return; |
| }, |
| |
| showResultList: function(){ |
| if(this._result_list_open){ return; } |
| with(this.optionsListNode.style){ |
| display = ""; |
| // visibility = "hidden"; |
| height = ""; |
| width = dojo.html.getInnerWidth(this.downArrowNode)+dojo.html.getInnerWidth(this.textInputNode)+"px"; |
| if(dojo.render.html.khtml){ |
| marginTop = dojo.html.totalOffsetTop(this.optionsListNode.parentNode)+"px"; |
| } |
| } |
| dojo.html.setOpacity(this.optionsListNode, 0); |
| dojo.fx.fadeIn(this.optionsListNode, 200); |
| this._result_list_open = true; |
| }, |
| |
| handleArrowClick: function(){ |
| if(this._result_list_open){ |
| this.hideResultList(); |
| }else{ |
| this.startSearchFromInput(); |
| } |
| }, |
| |
| startSearchFromInput: function(){ |
| this.startSearch(this.textInputNode.value); |
| }, |
| |
| postCreate: function(){ |
| dojo.event.connect(this, "startSearch", this.dataProvider, "startSearch"); |
| dojo.event.connect(this.dataProvider, "provideSearchResults", this, "openResultList"); |
| var s = dojo.widget.html.stabile.getState(this.widgetId); |
| if (s) { |
| this.setState(s); |
| } |
| } |
| |
| }); |