Ajax学习--输入前提示
// -------------------- mContentLoader.js
/**/ /*
* Ajax的辅助对象,执行所有与Ajax处理相关的工作
*
*/
var net = new Object();
net.READY_STATE_UNINITIALIZED = 0 ;
net.READY_STATE_LOADING = 1 ;
net.READY_STATE_LOADED = 2 ;
net.READY_STATE_INTERACTIVE = 3 ;
net.READY_STATE_COMPLETE = 4 ;
/**/ /*
* 构造函数
* component指定这个辅助所提供的对象,并假定component有一个ajaxUpdate()方法处理响应,一个handleError()方法处理错误
* url指定这个辅助从服务器端获取数据时调用的url
* mothd指定HTTP请求方法,有效值有GET和POST
* requestParams以key=value格式的字符串组形势指定一组传递给请求的请求参数
*/
net.ContentLoader = function ( component, url, method, requestParams ) {
this.component = component;
this.url = url;
this.requestParams = requestParams;
this.method = method;
}
net.ContentLoader.prototype = {
getTransport: function() {
var transport;
if ( window.XMLHttpRequest )
transport = new XMLHttpRequest();
else if ( window.ActiveXObject ) {
try {
transport = new ActiveXObject('Msxml2.XMLHTTP');
}
catch(err) {
transport = new ActiveXObject('Microsoft.XMLHTTP');
}
}
return transport;
},
sendRequest: function() {
//if ( window.netscape && window.netscape.security.PrivilegeManager.enablePrivilege)
// netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead');
var requestParams = []
for ( var i = 0 ; i < arguments.length ; i++ )
requestParams.push(arguments[i]);
var oThis = this;
var request = this.getTransport();
request.open( this.method, this.url, true );
request.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded');
request.onreadystatechange = function() { oThis.handleAjaxResponse(request) };
request.send( this.queryString(requestParams) );
},
queryString: function(args) {
var requestParams = [];
for ( var i = 0 ; i < this.requestParams.length ; i++ )
requestParams.push(this.requestParams[i]);
for ( var j = 0 ; j < args.length ; j++ )
requestParams.push(args[j]);
var queryString = "";
if ( requestParams && requestParams.length > 0 ) {
for ( var i = 0 ; i < requestParams.length ; i++ )
queryString += requestParams[i] + '&';
queryString = queryString.substring(0, queryString.length-1);
}
return queryString;
},
handleAjaxResponse: function(request) {
if ( request.readyState == net.READY_STATE_COMPLETE ) {
if ( this.isSuccess(request) )
this.component.ajaxUpdate(request);
else
this.component.handleError(request);
}
},
isSuccess: function(request) {
return request.status == 0
|| (request.status >= 200 && request.status < 300);
}
} ;
// -------------------- mTextSuggest.js
/**/ /*
* 输入前提示组件,可重用在大部分需要这些功能的应用中,为此考虑一下特征
* 1.不修改HTML标记,只简单修改head部分来注入组件行为
* 2.组件能够使用不同的URL,并为所有配置选项提供合理的默认值
* 3.组件不引入任何全局变量,防止全局变量混乱
* 4.组件使用开源框架,以减少编码工作量,并提高方案的质量和健壮性
*/
/**/ /*
************************************创建TextSuggest***********************************************
*/
TextSuggest = Class.create();
TextSuggest.prototype = {
/**//*
* 构造函数
* andId:要附加提示行为的文本字段的ID
* url:处理请求的服务器URL
* options:options对象为组件的每一个配置参数提供一个属性
*/
initialize: function(anId, url, options) {
this.id = anId;
this.textInput = $(this.id);
//检测浏览器类型
var browser = navigator.userAgent.toLowerCase();
this.isIE = browser.indexOf("msie") != -1;
this.isOpera = browser.indexOf("opera")!= -1;
this.suggestions = [];
this.setOptions(options);
this.initAjax(url);
this.injectSuggestBehavior();
},
/**//*
* 为options每个属性都指定一个默认值
* 然后,使用在构造时传入的options对象调用Prototype库的extend()方法,来覆盖默认值
* 最后结果是一个合并的options对象,包含默认值和指定的覆盖值
*/
setOptions: function(options) {
this.options = {
suggestDivClassName: 'suggestDiv', //指定用来保存提示的生成div元素的css类名
suggestionClassName: 'suggestion', //指定用来保存每一条提示的生成span元素的css类名
matchClassName : 'match', //指定用来保存匹配用户输入内容的提示部分的span元素的css类名
matchTextWidth : true, //用来指示为提示生成的div是否需要将自身大小设置为与它附加到的文本字段的宽度匹配
selectionColor : '#b1c09c', //为选中的提示的背景颜色指定一个十六进制值
matchAnywhere : false, //指定匹配是只查找字符串的开始位置,还是查找任意位置
ignoreCase : false, //指定匹配是否是区别大小写的
count : 10 //显示最大提示数量
}.extend(options || {});
},
/**//*
************************************支持Ajax***********************************************
*/
//-----------------------文本提示--发送Ajax请求
/**//*
* 发送请求限制
* 如果没有正在处理的请求,就调用callRicoAjaxEngine()来发送请求,反之不发送
* 通过设置一个内部布尔属性this.pendingRequest实现,当Ajax请求发送时值为true,当返回的请求被处理后值为false
* 用来基于服务器的处理速度限制事件的发送速度
*/
sendRequestForSuggestions: function() {
if ( this.handlingRequest ) {
this.pendingRequest = true;
return;
}
this.handlingRequest = true;
this.callRicoAjaxEngine();
},
/**//*
* 使用Rico提供的一个出色的Ajax处理函数
* ajaxEngine这个API支持为请求注册逻辑名,和注册知道如何处理Ajax响应的对象
* ajaxEngine.registerRequest()为一个(可能很长或很怪的)URL注册逻辑名,在发送请求时可以使用这个逻辑名
* ajaxEngine.registerAjaxObject()用来注册一个Ajax处理对象
*/
initAjax: function(url) {
ajaxEngine.registerRequest( this.id + '_request', url );
ajaxEngine.registerAjaxObject( this.id + '_updater', this );
},
/**//*
* 发送请求
* 先将对象的内部状态和options对象的特定属性存入数组callParms
* 再将外部请求参数存入数组callParms
* 通过ajaxEngine.sendRequest.apply( ajaxEngine, callParms )发送
* apply()方法见最后注释
*/
callRicoAjaxEngine: function() {
var callParms = [];
callParms.push( this.id + '_request');
callParms.push( 'id=' + this.id);
callParms.push( 'count=' + this.options.count);
callParms.push( 'query=' + this.lastRequestString);
callParms.push( 'match_anywhere=' + this.options.matchAnywhere);
callParms.push( 'ignore_case=' + this.options.ignoreCase);
var additionalParms = this.options.requestParameters || [];
for( var i=0 ; i < additionalParms.length ; i++ )
callParms.push(additionalParms[i]);
ajaxEngine.sendRequest.apply( ajaxEngine, callParms );
},
//-----------------------文本提示--处理Ajax请求
/**//*
* 处理请求
* 224 先通过createSuggestions()方法将响应解析成提示在内存中的表现形式,保存在suggestions属性中
* 226 如果没找到提示,将探出框隐藏,并清空隐藏字段中的内部值
* 230 如果找到提示,创建下拉框的UI元素,使用提示组装,并显示给用户
* 236 最后将this.handlingRequest重新设置为false,表示响应处理完成
*/
ajaxUpdate: function( ajaxResponse ) {
this.createSuggestions( ajaxResponse );
if ( this.suggestions.length == 0 ) {
this.hideSuggestions();
$( this.id + "_hidden" ).value = "";
}
else {
this.updateSuggestionsDiv();
this.showSuggestions();
this.updateSelection(0);
}
this.handlingRequest = false;
if ( this.pendingRequest ) {
this.pendingRequest = false;
this.lastRequestString = this.textInput.value;
this.sendRequestForSuggestions();
}
},
createSuggestions: function(ajaxResponse) {
this.suggestions = [];
var entries = ajaxResponse.getElementsByTagName('entry');
for ( var i = 0 ; i < entries.length ; i++ ) {
var strText = this.getElementContent(entries[i].getElementsByTagName('text')[0]);
var strValue = this.getElementContent(entries[i].getElementsByTagName('value')[0]);
this.suggestions.push( { text: strText, value: strValue } );
}
},
/**//*
************************************事件处理***********************************************
*/
//行为注入
injectSuggestBehavior: function() {
if ( this.isIE )
this.textInput.autocomplete = "off"; //关闭IE自动完成
var keyEventHandler = new TextSuggestKeyHandler(this); //创建控制器
//Insertion.After由Prototype库提供,添加一个不可见文本字段来防止回车键提交表单
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'+'"/>' );
this.createSuggestionsDiv(); //创建UI
},
//TextSuggest的选择处理方法
moveSelectionUp: function() {
//selectedIndex: select对象中当前被选option的下标
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;
}
},
//文本输入处理函数
handleTextInput: function() {
var previousRequest = this.lastRequestString; //上次请求的值
this.lastRequestString = this.textInput.value; //现在请求的值
if ( this.lastRequestString == "" )
this.hideSuggestions();
else if ( this.lastRequestString != previousRequest ) {
this.sendRequestForSuggestions(); //数据的Ajax请求
}
},
setInputFromSelection: function() {
var hiddenInput = $( this.id + "_hidden" );
var suggestion = this.suggestions[ this.selectedIndex ];
this.textInput.value = suggestion.text; //更新可见的值
hiddenInput.value = suggestion.value; //更新隐藏的值
this.hideSuggestions();
},
/**//*
************************************提示的弹出框界面***********************************************
*/
//创建DIV
createSuggestionsDiv: function() {
this.suggestionsDiv = document.createElement("div"); //创建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); //插入到文档中
},
//定位弹出框
positionSuggestionsDiv: function() {
//通过Rico提供的toDocumentPosition()方法来计算文本字段的绝对位置
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;
}
},
//创建弹出框的内容
updateSuggestionsDiv: function() {
this.suggestionsDiv.innerHTML = ""; //除去以前的内容
var suggestLines = this.createSuggestionSpans(); //创建新内容
for ( var i = 0 ; i < suggestLines.length ; i++ )
this.suggestionsDiv.appendChild(suggestLines[i]);
},
//创建提示列表的条目
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 = [];
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.text,
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;
},
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 };
},
//列表条目的鼠标事件处理函数
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);
this.hideSuggestions();
this.textInput.focus();
},
//显示和隐藏弹出框
showSuggestions: function() {
var divStyle = this.suggestionsDiv.style;
if ( divStyle.display == '' )
return;
this.positionSuggestionsDiv();
divStyle.display = '';
},
hideSuggestions: function() {
this.suggestionsDiv.style.display = 'none';
},
getElementContent: function(element) {
return element.firstChild.data;
}
}
// ---------------控制器对象,用来担任事件的代理
TextSuggestKeyHandler = Class.create();
TextSuggestKeyHandler.prototype = {
/**//*
* 构造方法
* 控制器保存提示组件的引用和HTML标单的输入字段,通过this.addKeyHandling()为输入字段添加处理函数
*/
initialize: function( textSuggest ) {
this.textSuggest = textSuggest;
this.input = this.textSuggest.textInput;
this.addKeyHandling();
},
//bindAsEventListener()是prototype库提供的一个闭包机制,该机制允许处理函数调用控制器的方法
addKeyHandling: function() {
this.input.onkeyup = this.keyupHandler.bindAsEventListener(this);
this.input.onkeydown = this.keydownHandler.bindAsEventListener(this);
this.input.onblur = this.onblurHandler.bindAsEventListener(this);
if ( this.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 ) {
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();
}
} ;