|
Posted on 2007-02-01 22:39 Zou Ang 阅读(3929) 评论(7) 编辑 收藏 所属分类:
经过一个星期的煎熬,终于把基于Ajax的输入提示功能实现了。太痛苦了,写Javascript的感觉就跟用NotePad来写代码一样,没有智能提示、弱类型、难调试……总之是太折磨人了。 本来自己写了一个比较简单的,但是由于我的页面上需要多个输入框,还要可以动态增加输入框,要把传回来的结果set入多个输入框,由于是使用的Struts标签库,<html:text>还没有id属性,让这个问题复杂了不少。 需求是这样的: 有一个页面,需要录入货品信息,货品有id,编号,名称,单位,单价,规格等属性,每个货品信息在表格中有一行,一行中有多个输入框,用于输入货品信息。在输入货品编号的时候,应该访问后台的商品信息库,寻找以前是否有输入过该编号的货品,如果有,把编号返回。支持用鼠标点击,键盘回车等方法选择货品,选择后应该把货品信息显示到各个输入框中供用户修改。如果该货品在商品信息库中标记为敏感商品,要作出提示。一个编号可以有多个货品。 改了3天代码,终于决定破釜沉舟,删掉重做。修改了《Ajax in Action》中的代码,Ajax in Action中的代码有些地方有错误,不仔细检查一遍还真不太容易发现。书中后台使用C#,前台是使用Rico来向某个url传参数的形式进行Ajax通信。返回的response类似: <ajax-response>
<response type='object' id='field1_updater'>
<matches>
<text>XXX</text>
<value>XXX</value>
</matches>
</response>
</ajax-response>不过我不想写JSP或者Servlet,所以用了DWR,直接用spring中的BO把结果传回来:  cargobaseService.getByNumberAndCompany(this.lastRequestString,this.company,function(ajaxResponse) {
//
});cargobaseService是使用DWR创建的Javascript对象: dwr.xml: <dwr>
<allow>
<create creator="spring" javascript = "cargobaseService">
<param name="beanName" value="cargobaseService"/>
</create>
<convert match="com.gdnfha.atcs.cargobase.model.*" converter="bean"></convert>
</allow>
</dwr>
 返回为下面对象的数组  var cargoModel = {
cargoCompany: XXX,
cargoCurrency: XXX,
cargoDestination: XXX,
cargoId: XXX,
cargoImpose: XXX,
cargoName: XXX,
cargoNumber: XXX,
cargoSize: XXX,
cargoUnit: XXX,
cargoUnitPrice: XXX,
sensitive: true|false
} Javascript代码如下: TextSuggest = Class.create();

 TextSuggest.prototype = {
//构造函数
 initialize: function(anId,company, url, options) {
this.id = anId;
this.company = company;
var browser = navigator.userAgent.toLowerCase();
this.isIE = browser.indexOf("msie") != -1;
this.isOpera = browser.indexOf("opera")!= -1;
this.textInput = $(this.id);
this.suggestions = new Array();
this.setOptions(options);
this.injectSuggestBehavior();
},
//设置参数
 setOptions: function(options) {
 this.options = {
suggestDivClassName: 'suggestDiv',
suggestionClassName: 'suggestion',
matchClassName : 'match',
matchTextWidth : true,
selectionColor : '#b1c09c',
matchAnywhere : false,
ignoreCase : false,
count : 10
 }.extend(options || {});
},
//注入输入提示行为
 injectSuggestBehavior: function() {

if ( this.isIE )
this.textInput.autocomplete = "off";
//创建控制器
var keyEventHandler = new TextSuggestKeyHandler(this);
//主要是为了避免在按回车的时候把表单提交
new Insertion.After( this.textInput,
'<input type="text" id="'+this.id+'_preventtsubmit'+'" style="display:none"/>' );
new Insertion.After( this.textInput,
'<input type="hidden" name="'+this.id+'_hidden'+'" id="'+this.id+'_hidden'+'"/>' );
//创建div层
this.createSuggestionsDiv();
},
//处理输入信息
 handleTextInput: function() {
var previousRequest = this.lastRequestString;
this.lastRequestString = this.textInput.value;
if ( this.lastRequestString == "" )
this.hideSuggestions();
 else if ( this.lastRequestString != previousRequest ) {
//访问数据源
this.sendRequestForSuggestions();
}
},
//选择框上移
 moveSelectionUp: function() {
 if ( this.selectedIndex > 0 ) {
this.updateSelection(this.selectedIndex - 1);
}
},
//选择框下移
 moveSelectionDown: function() {
 if ( this.selectedIndex < (this.suggestions.length - 1) ) {
this.updateSelection(this.selectedIndex + 1);
}
},
//更新当前选择信息
 updateSelection: function(n) {
var span = $( this.id + "_" + this.selectedIndex );
 if ( span ) {
//消除以前的样式
span.style.backgroundColor = "";
}
this.selectedIndex = n;
var span = $( this.id + "_" + this.selectedIndex );
 if ( span ) {
//更新新样式
span.style.backgroundColor = this.options.selectionColor;
}
},
//发送请求
 sendRequestForSuggestions: function() {
 if ( this.handlingRequest ) {
this.pendingRequest = true;
return;
}

this.handlingRequest = true;
this.callDWRAjaxEngine();
},

//使用DWR访问后台
 callDWRAjaxEngine: function() {
//保存当前对象指针
var tempThis = this;
 cargobaseService.getByNumberAndCompany(this.lastRequestString,this.company,function(ajaxResponse) {
tempThis.suggestions = ajaxResponse;
 if ( tempThis.suggestions.length == 0 ) {
tempThis.hideSuggestions();
$( tempThis.id + "_hidden" ).value = "";
 }else {
tempThis.updateSuggestionsDiv();
tempThis.showSuggestions();
tempThis.updateSelection(0);
}
tempThis.handlingRequest = false;
 if ( tempThis.pendingRequest ) {
tempThis.pendingRequest = false;
tempThis.lastRequestString = this.textInput.value;
tempThis.sendRequestForSuggestions();
}
});
},
//显示信息
 setInputFromSelection: function() {
var index = this.id.split("_");
var trId = "cargoTr_" + index[1];
var trElement = $(trId);
var cellNodes = trElement.childNodes;
var suggestion = this.suggestions[ this.selectedIndex ];
 for(var i = 0; i < cellNodes.length; i++) {
var cargo = cellNodes[i].firstChild;
 if(cargo.name == "cargoName") {
cargo.value = suggestion.cargoName;
}
 if(cargo.name == "cargoSize") {
cargo.value = suggestion.cargoSize;
}
 if(cargo.name == "cargoUnit") {
cargo.value == suggestion.cargoUnit;
}
 if(cargo.name == "cargoDestination") {
cargo.value = suggestion.cargoDestination;
}
 if(cargo.name == "cargoUnitPrice") {
cargo.value = suggestion.cargoUnitPrice;
}
}
this.textInput.value = suggestion.cargoNumber;
//敏感提示
 if(suggestion.sensitive) {
var warnStr = "注意!\n编号:"+suggestion.cargoNumber
+"\n名称:" + suggestion.cargoName
+"\n为敏感商品!";
alert(warnStr);
}
this.hideSuggestions();
},
//显示层
 showSuggestions: function() {
var divStyle = this.suggestionsDiv.style;
if ( divStyle.display == '' )
return;
this.positionSuggestionsDiv();
divStyle.display = '';
},
//定位层
 positionSuggestionsDiv: function() {
var textPos = RicoUtil.toDocumentPosition(this.textInput);
var divStyle = this.suggestionsDiv.style;
divStyle.top = (textPos.y + this.textInput.offsetHeight) + "px";
divStyle.left = textPos.x + "px";

if ( this.options.matchTextWidth )
divStyle.width = (this.textInput.offsetWidth- this.padding()) + "px";
},
//计算间隔
 padding: function() {
 try {
var styleFunc = RicoUtil.getElementsComputedStyle;
var lPad = styleFunc( this.suggestionsDiv, "paddingLeft", "padding-left" );
var rPad = styleFunc( this.suggestionsDiv, "paddingRight", "padding-right" );
var lBorder = styleFunc( this.suggestionsDiv, "borderLeftWidth", "border-left-width" );
var rBorder = styleFunc( this.suggestionsDiv, "borderRightWidth", "border-right-width" );

lPad = isNaN(lPad) ? 0 : lPad;
rPad = isNaN(rPad) ? 0 : rPad;
lBorder = isNaN(lBorder) ? 0 : lBorder;
rBorder = isNaN(rBorder) ? 0 : rBorder;

return parseInt(lPad) + parseInt(rPad) + parseInt(lBorder) + parseInt(rBorder);
 }catch (e) {
return 0;
}
},
//隐藏层
 hideSuggestions: function() {
this.suggestionsDiv.style.display = 'none';
},
//创建层
 createSuggestionsDiv: function() {
this.suggestionsDiv = document.createElement("div");
this.suggestionsDiv.className = this.options.suggestDivClassName;

var divStyle = this.suggestionsDiv.style;
divStyle.position = 'absolute';
divStyle.zIndex = 101;
divStyle.display = "none";

this.textInput.parentNode.appendChild(this.suggestionsDiv);
},
//更新层
 updateSuggestionsDiv: function() {
this.suggestionsDiv.innerHTML = "";
var suggestLines = this.createSuggestionSpans();
for ( var i = 0 ; i < suggestLines.length ; i++ )
this.suggestionsDiv.appendChild(suggestLines[i]);
},
//创建层中的选项span
 createSuggestionSpans: function() {
var regExpFlags = "";
if ( this.options.ignoreCase )
regExpFlags = 'i';
var startRegExp = "^";
if ( this.options.matchAnywhere )
startRegExp = '';
//正则表达式匹配
var regExp = new RegExp( startRegExp + this.lastRequestString, regExpFlags );
var suggestionSpans = new Array();
for ( var i = 0 ; i < this.suggestions.length ; i++ )
suggestionSpans.push( this.createSuggestionSpan( i, regExp ) )

return suggestionSpans;
},
//创建单个选项span
 createSuggestionSpan: function( n, regExp ) {
var suggestion = this.suggestions[n];

var suggestionSpan = document.createElement("span");
suggestionSpan.className = this.options.suggestionClassName;
suggestionSpan.style.width = '100%';
suggestionSpan.style.display = 'block';
suggestionSpan.id = this.id + "_" + n;
suggestionSpan.onmouseover = this.mouseoverHandler.bindAsEventListener(this);
suggestionSpan.onclick = this.itemClickHandler.bindAsEventListener(this);
var textValues = this.splitTextValues( suggestion.cargoNumber+"",
this.lastRequestString.length,
regExp );
var textMatchSpan = document.createElement("span");
textMatchSpan.id = this.id + "_match_" + n;
textMatchSpan.className = this.options.matchClassName;
textMatchSpan.onmouseover = this.mouseoverHandler.bindAsEventListener(this);
textMatchSpan.onclick = this.itemClickHandler.bindAsEventListener(this);

textMatchSpan.appendChild( document.createTextNode(textValues.mid) );

suggestionSpan.appendChild( document.createTextNode( textValues.start ) );
suggestionSpan.appendChild( textMatchSpan );
suggestionSpan.appendChild( document.createTextNode( textValues.end ) );

return suggestionSpan;
},
//鼠标经过处理
 mouseoverHandler: function(e) {
var src = e.srcElement ? e.srcElement : e.target;
var index = parseInt(src.id.substring(src.id.lastIndexOf('_')+1));
this.updateSelection(index);
},
//鼠标点击处理
 itemClickHandler: function(e) {
this.mouseoverHandler(e);
//原书没有下面一句,也就是说鼠标点击不会把数据set入输入框!
this.setInputFromSelection();
this.hideSuggestions();
},
//分拆字符串
 splitTextValues: function( text, len, regExp ) {
var startPos = text.search(regExp);
var matchText = text.substring( startPos, startPos + len );
var startText = startPos == 0 ? "" : text.substring(0, startPos);
var endText = text.substring( startPos + len );
 return { start: startText, mid: matchText, end: endText };
},

 getElementContent: function(element) {
return element.firstChild.data;
}
}
//控制器类
TextSuggestKeyHandler = Class.create();

 TextSuggestKeyHandler.prototype = {
//构造方法
 initialize: function( textSuggest ) {
//TextSuggest类的引用
this.textSuggest = textSuggest;
//输入框的引用
this.input = this.textSuggest.textInput;
//为输入框增加事件响应机制
this.addKeyHandling();
},

 addKeyHandling: function() {
this.input.onkeyup = this.keyupHandler.bindAsEventListener(this);
this.input.onkeydown = this.keydownHandler.bindAsEventListener(this);
this.input.onblur = this.onblurHandler.bindAsEventListener(this);
//原书有错,原文是this.isOpera,但是TextSuggestKeyHandler没有isOpera属性
if ( this.textSuggest.isOpera )
this.input.onkeypress = this.keyupHandler.bindAsEventListener(this);
},
//按键按下事件响应
 keydownHandler: function(e) {
var upArrow = 38;
var downArrow = 40;

 if ( e.keyCode == upArrow ) {
this.textSuggest.moveSelectionUp();
setTimeout( this.moveCaretToEnd.bind(this), 1 );
}
 else if ( e.keyCode == downArrow ) {
this.textSuggest.moveSelectionDown();
}
},
//放开按键事件响应
 keyupHandler: function(e) {
if ( this.input.length == 0 && !this.isOpera )
this.textSuggest.hideSuggestions();

if ( !this.handledSpecialKeys(e) )
this.textSuggest.handleTextInput();
},
//处理特殊按键
 handledSpecialKeys: function(e) {
var enterKey = 13;
var upArrow = 38;
var downArrow = 40;
 if ( e.keyCode == upArrow || e.keyCode == downArrow ) {
return true;
}
 else if ( e.keyCode == enterKey ) {
//回车则set入数据
this.textSuggest.setInputFromSelection();
return true;
}

return false;
},
//不太明白这个方法干啥用的
 moveCaretToEnd: function() {
var pos = this.input.value.length;
 if (this.input.setSelectionRange) {
this.input.setSelectionRange(pos,pos);
}
 else if(this.input.createTextRange) {
var m = this.input.createTextRange();
m.moveStart('character',pos);
m.collapse();
m.select();
}
},
//失去焦点事件响应
 onblurHandler: function(e) {
if ( this.textSuggest.suggestionsDiv.style.display == '' )
//如果当前输入是显示的,那么点击其他地方应该把选择值注入输入框
this.textSuggest.setInputFromSelection();
this.textSuggest.hideSuggestions();
}

};

 有几个地方是需要特别注意的: 用下面得方法访问后台,并把结果放到当前对象得suggestions数组中: //使用DWR访问后台
 callDWRAjaxEngine: function() {
//保存当前对象指针
var tempThis = this;
 cargobaseService.getByNumberAndCompany(this.lastRequestString,this.company,function(ajaxResponse) {
tempThis.suggestions = ajaxResponse;
 if ( tempThis.suggestions.length == 0 ) {
tempThis.hideSuggestions();
$( tempThis.id + "_hidden" ).value = "";
 }else {
tempThis.updateSuggestionsDiv();
tempThis.showSuggestions();
tempThis.updateSelection(0);
}
tempThis.handlingRequest = false;
 if ( tempThis.pendingRequest ) {
tempThis.pendingRequest = false;
tempThis.lastRequestString = this.textInput.value;
tempThis.sendRequestForSuggestions();
}
});
},这个方法中一定要用tempThis保存当前对象的引用,原文中直接用this的话会产生object Error //显示信息
 setInputFromSelection: function() {
var index = this.id.split("_");
var trId = "cargoTr_" + index[1];
var trElement = $(trId);
var cellNodes = trElement.childNodes;
var suggestion = this.suggestions[ this.selectedIndex ];
 for(var i = 0; i < cellNodes.length; i++) {
var cargo = cellNodes[i].firstChild;
 if(cargo.name == "cargoName") {
cargo.value = suggestion.cargoName;
}
 if(cargo.name == "cargoSize") {
cargo.value = suggestion.cargoSize;
}
 if(cargo.name == "cargoUnit") {
cargo.value == suggestion.cargoUnit;
}
 if(cargo.name == "cargoDestination") {
cargo.value = suggestion.cargoDestination;
}
 if(cargo.name == "cargoUnitPrice") {
cargo.value = suggestion.cargoUnitPrice;
}
}
this.textInput.value = suggestion.cargoNumber;
//敏感提示
 if(suggestion.sensitive) {
var warnStr = "注意!\n编号:"+suggestion.cargoNumber
+"\n名称:" + suggestion.cargoName
+"\n为敏感商品!";
alert(warnStr);
}
this.hideSuggestions();
},使用这个方法把结果set到输入框中 其他几个地方比较麻烦的就是原书的代码有错了,我是自己手打进去的才发现 页面上使用下面方法为输入框增加ajax的自动提示功能:
 function prepareTypeAhead() {
var companyArray = document.getElementsByName("bill.clientName");
var company = companyArray[0].value;
var cargoArray = document.getElementsByName("cargoNumber");
 var suggestOptions = {};
 for(var i = 0; i < cargoArray.length; i++) {
cargoArray[i].id = "cargo_" + i;
suggest = createTextSuggest("cargo_"+i,company,suggestOptions);
}
}
 function createTextSuggest(id,company,suggestOptions) {
//为输入框增加输入提示功能
suggest = new TextSuggest(id,company,suggestOptions);
return suggest;
}可以自己配置选项:  setOptions: function(options) {
 this.options = {
//层样式
suggestDivClassName: 'suggestDiv',
//选项样式
suggestionClassName: 'suggestion',
//匹配样式
matchClassName : 'match',
//是否匹配输入框宽度
matchTextWidth : true,
//选项颜色
selectionColor : '#b1c09c',
//是否从头匹配
matchAnywhere : false,
//是否忽略大小写
ignoreCase : false,
//显示数目,暂时没用上
count : 10
 }.extend(options || {});
},至此就差不多了,其实全都是Ajax in Action上的代码,可是他的代码写得太繁复,很难看明白,而且书上的解释也不太清楚(我这么觉得),可能是由于我不熟悉javascript得缘故吧,还是有很大得差距,要努力!基本按照上面的代码,把获取输入的方式和设置结果的方式重写一下就可以重用了,还很容易配置,真是很精致的类啊
评论
# re: Javascript噩梦-Ajax实现输入提示的调整与配置 回复 更多评论
2007-02-01 22:45 by
本来也不是什么很难的东西,可是写着写着就头大,删了又写,写了又删,太烦人了
# re: Javascript噩梦-Ajax实现输入提示的调整与配置 回复 更多评论
2007-02-02 08:37 by
<html:text>还没有id属性???styleId是啥
# re: Javascript噩梦-Ajax实现输入提示的调整与配置[未登录] 回复 更多评论
2007-02-02 08:45 by
@啊啊啊啊
多谢指导!一直不知道呵呵……谢谢啊
# re: Javascript噩梦-Ajax实现输入提示的调整与配置[未登录] 回复 更多评论
2007-02-02 13:46 by
不明白这种功夫有多大的意义,用dwr一切都可以解决,做事要动脑子阿。
# re: Javascript噩梦-Ajax实现输入提示的调整与配置 回复 更多评论
2007-02-05 11:46 by
呵呵,楼上的,DWR确实是牛
# re: Javascript噩梦-Ajax实现输入提示的调整与配置 回复 更多评论
2007-02-06 23:35 by
老弟,你写这个东西就叫烦人啊,我每个星期都要写几千行的JS代码,那我不早就跳楼算了。还有像“啊啊啊啊 ”说的一样,struts中把styleId属性做为html标签的id属性使用,这个在reference中就可以查到的了。
# re: Javascript噩梦-Ajax实现输入提示的调整与配置 回复 更多评论
2007-02-07 07:58 by
@errorfun
呵呵,不常写嘛,别见怪~
|