var
Autocomplete
=
function
(el, options){
this
.el
=
$(el);
this
.id
=
this
.el.identify();
this
.el.setAttribute(
'
autocomplete
'
,
'
off
'
);
this
.suggestions
=
[];
this
.data
=
[];
this
.badQueries
=
[];
this
.selectedIndex
=
-
1
;
this
.currentValue
=
this
.el.value;
this
.intervalId
=
0
;
this
.cachedResponse
=
[];
this
.instanceId
=
null
;
this
.onChangeInterval
=
null
;
this
.ignoreValueChange
=
false
;
this
.serviceUrl
=
options.serviceUrl;
this
.numbers
=
[];
//
条数
this
.options
=
{
autoSubmit:
false
,
minChars:
1
,
maxHeight:
300
,
deferRequestBy:
0
,
width:
0
,
showNumber:
true
,
//
是否显示条数
container:
null
};
if
(options){ Object.extend(
this
.options, options); }
if
(Autocomplete.isDomLoaded){
this
.initialize();
}
else
{
Event.observe(document,
'
dom:loaded
'
,
this
.initialize.bind(
this
),
false
);
}
};
Autocomplete.instances
=
[];
Autocomplete.isDomLoaded
=
false
;
Autocomplete.getInstance
=
function
(id){
var
instances
=
Autocomplete.instances;
var
i
=
instances.length;
while
(i
--
){
if
(instances[i].id
===
id){
return
instances[i]; }}
};
Autocomplete.highlight
=
function
(value, re){
return
value.replace(re,
function
(match){
return
'
<strong>
'
+
match
+
'
<\/strong>
'
});
};
Autocomplete.prototype
=
{
killerFn:
null
,
initialize:
function
() {
var
me
=
this
;
this
.killerFn
=
function
(e) {
if
(
!
$(Event.element(e)).up(
'
.autocomplete
'
)) {
me.killSuggestions();
me.disableKillerFn();
}
} .bindAsEventListener(
this
);
if
(
!
this
.options.width) {
this
.options.width
=
this
.el.getWidth(); }
var
div
=
new
Element(
'
div
'
, { style:
'
position:absolute;
'
});
div.update(
'
<div class="autocomplete-w1"><div class="autocomplete-w2"><div class="autocomplete" id="Autocomplete_
'
+
this
.id
+
'
" style="display:none; width:
'
+
this
.options.width
+
'
px;"></div></div></div>
'
);
this
.options.container
=
$(
this
.options.container);
if
(
this
.options.container) {
this
.options.container.appendChild(div);
this
.fixPosition
=
function
() { };
}
else
{
document.body.appendChild(div);
}
this
.mainContainerId
=
div.identify();
this
.container
=
$(
'
Autocomplete_
'
+
this
.id);
this
.fixPosition();
Event.observe(
this
.el, window.opera
?
'
keypress
'
:
'
keydown
'
,
this
.onKeyPress.bind(
this
));
Event.observe(
this
.el,
'
keyup
'
,
this
.onKeyUp.bind(
this
));
Event.observe(
this
.el,
'
blur
'
,
this
.enableKillerFn.bind(
this
));
Event.observe(
this
.el,
'
focus
'
,
this
.fixPosition.bind(
this
));
this
.container.setStyle({ maxHeight:
this
.options.maxHeight
+
'
px
'
});
this
.instanceId
=
Autocomplete.instances.push(
this
)
-
1
;
},
fixPosition:
function
() {
var
offset
=
this
.el.cumulativeOffset();
$(
this
.mainContainerId).setStyle({ top: (offset.top
+
this
.el.getHeight())
+
'
px
'
, left: offset.left
+
'
px
'
});
},
enableKillerFn:
function
() {
Event.observe(document.body,
'
click
'
,
this
.killerFn);
},
disableKillerFn:
function
() {
Event.stopObserving(document.body,
'
click
'
,
this
.killerFn);
},
killSuggestions:
function
() {
this
.stopKillSuggestions();
this
.intervalId
=
window.setInterval(
function
() {
this
.hide();
this
.stopKillSuggestions(); } .bind(
this
),
300
);
},
stopKillSuggestions:
function
() {
window.clearInterval(
this
.intervalId);
},
onKeyPress:
function
(e) {
if
(
!
this
.enabled) {
return
; }
switch
(e.keyCode) {
case
Event.KEY_ESC:
this
.el.value
=
this
.currentValue;
this
.hide();
break
;
case
Event.KEY_TAB:
case
Event.KEY_RETURN:
if
(
this
.selectedIndex
===
-
1
) {
this
.hide();
return
;
}
this
.select(
this
.selectedIndex);
if
(e.keyCode
===
Event.KEY_TAB) {
return
; }
break
;
case
Event.KEY_UP:
this
.moveUp();
break
;
case
Event.KEY_DOWN:
this
.moveDown();
break
;
default
:
return
;
}
Event.stop(e);
},
onKeyUp:
function
(e) {
switch
(e.keyCode) {
case
Event.KEY_UP:
case
Event.KEY_DOWN:
return
;
}
clearInterval(
this
.onChangeInterval);
if
(
this
.currentValue
!==
this
.el.value) {
if
(
this
.options.deferRequestBy
>
0
) {
//
Defer lookup in case when value changes very quickly:
this
.onChangeInterval
=
setInterval((
function
() {
this
.onValueChange();
}).bind(
this
),
this
.options.deferRequestBy);
}
else
{
this
.onValueChange();
}
}
},
onValueChange:
function
() {
clearInterval(
this
.onChangeInterval);
this
.currentValue
=
this
.el.value;
this
.selectedIndex
=
-
1
;
if
(
this
.ignoreValueChange) {
this
.ignoreValueChange
=
false
;
return
;
}
if
(
this
.currentValue
===
''
||
this
.currentValue.length
<
this
.options.minChars) {
this
.hide();
}
else
{
this
.getSuggestions();
}
},
getSuggestions:
function
() {
var
cr
=
this
.cachedResponse[
this
.currentValue];
if
(cr
&&
Object.isArray(cr.suggestions)) {
this
.suggestions
=
cr.suggestions;
this
.data
=
cr.data;
this
.numbers
=
cr.numbers;
this
.suggest();
}
else
if
(
!
this
.isBadQuery(
this
.currentValue)) {
new
Ajax.Request(
this
.serviceUrl, {
parameters: { query:
this
.currentValue },
onComplete:
this
.processResponse.bind(
this
),
method:
'
get
'
});
}
},
isBadQuery:
function
(q) {
var
i
=
this
.badQueries.length;
while
(i
--
) {
if
(q.indexOf(
this
.badQueries[i])
===
0
) {
return
true
; }
}
return
false
;
},
hide:
function
() {
this
.enabled
=
false
;
this
.selectedIndex
=
-
1
;
this
.container.hide();
},
suggest:
function
() {
if
(
this
.suggestions.length
===
0
) {
this
.hide();
return
;
}
var
content
=
[];
var
re
=
new
RegExp(
'
\\b
'
+
this
.currentValue.match(
/
[\u4e00-\u9fa5a-zA-Z0-9]+
/
g).join(
'
|\\b
'
),
'
gi
'
);
var
numbersContent
=
''
;
this
.suggestions.each(
function
(value, i) {
if
(Object.isArray(
this
.numbers)
&&
this
.options.showNumber){
numbersContent
=
'
<span class="number">约
'
+
this
.numbers[i]
+
'
条</span>
'
;
}
content.push((
this
.selectedIndex
===
i
?
'
<div class="selected"
'
:
'
<div
'
),
'
title="
'
, value,
'
" onclick="Autocomplete.instances[
'
,
this
.instanceId,
'
].select(
'
, i,
'
);" onmouseover="Autocomplete.instances[
'
,
this
.instanceId,
'
].activate(
'
, i,
'
);">
'
, Autocomplete.highlight(value, re), numbersContent,
'
</div>
'
);
} .bind(
this
));
this
.enabled
=
true
;
this
.container.update(content.join(
''
)).show();
},
processResponse:
function
(xhr) {
var
response;
try
{
response
=
xhr.responseText.evalJSON();
if
(
!
Object.isArray(response.data)) { response.data
=
[]; }
}
catch
(err) {
return
; }
this
.cachedResponse[response.query]
=
response;
if
(response.suggestions.length
===
0
) {
this
.badQueries.push(response.query); }
if
(response.query
===
this
.currentValue) {
this
.suggestions
=
response.suggestions;
this
.data
=
response.data;
this
.numbers
=
response.numbers;
this
.suggest();
}
},
activate:
function
(index) {
var
divs
=
this
.container.childNodes;
var
activeItem;
//
Clear previous selection:
if
(
this
.selectedIndex
!==
-
1
&&
divs.length
>
this
.selectedIndex) {
divs[
this
.selectedIndex].className
=
''
;
}
this
.selectedIndex
=
index;
if
(
this
.selectedIndex
!==
-
1
&&
divs.length
>
this
.selectedIndex) {
activeItem
=
divs[
this
.selectedIndex]
activeItem.className
=
'
selected
'
;
}
return
activeItem;
},
deactivate:
function
(div, index) {
div.className
=
''
;
if
(
this
.selectedIndex
===
index) {
this
.selectedIndex
=
-
1
; }
},
select:
function
(i) {
var
selectedValue
=
this
.suggestions[i];
if
(selectedValue) {
this
.el.value
=
selectedValue;
if
(
this
.options.autoSubmit
&&
this
.el.form) {
this
.el.form.submit();
}
this
.ignoreValueChange
=
true
;
this
.hide();
this
.onSelect(i);
}
},
moveUp:
function
() {
if
(
this
.selectedIndex
===
-
1
) {
return
; }
if
(
this
.selectedIndex
===
0
) {
this
.container.childNodes[
0
].className
=
''
;
this
.selectedIndex
=
-
1
;
this
.el.value
=
this
.currentValue;
return
;
}
this
.adjustScroll(
this
.selectedIndex
-
1
);
},
moveDown:
function
() {
if
(
this
.selectedIndex
===
(
this
.suggestions.length
-
1
)) {
return
; }
this
.adjustScroll(
this
.selectedIndex
+
1
);
},
adjustScroll:
function
(i) {
var
container
=
this
.container;
var
activeItem
=
this
.activate(i);
var
offsetTop
=
activeItem.offsetTop;
var
upperBound
=
container.scrollTop;
var
lowerBound
=
upperBound
+
this
.options.maxHeight
-
25
;
if
(offsetTop
<
upperBound) {
container.scrollTop
=
offsetTop;
}
else
if
(offsetTop
>
lowerBound) {
container.scrollTop
=
offsetTop
-
this
.options.maxHeight
+
25
;
}
this
.el.value
=
this
.suggestions[i];
},
onSelect:
function
(i) {
(
this
.options.onSelect
||
Prototype.emptyFunction)(
this
.suggestions[i],
this
.data[i]);
}
};
Event.observe(document,
'
dom:loaded
'
,
function
(){ Autocomplete.isDomLoaded
=
true
; },
false