问题
I would like to use an autocomplete with ajax. So my goal is to have:
When the user types something in the text field, some suggestions provided by the server appear (I have to find suggestions in a database)
When the user presses "enter", clicks somewhere else than in the autocomplete box, or when he/she selects a suggestion, the string in the textfield is sent to the server.
I first tried to use the autocomplete widget provided by lift but I faced three problems:
- it is meant to be an extended select, that is to say you can originally only submit suggested values.
- it is not meant to be used with ajax.
- it gets buggy when combined with
WiringUI
.
So, my question is: How can I combine jquery autocomplete and interact with the server in lift. I think I should use some callbacks but I don't master them.
Thanks in advance.
UPDATE Here is a first implementation I tried but the callback doesn't work:
private def update_source(current: String, limit: Int) = {
val results = if (current.length == 0) Nil else /* generate list of results */
new JsCmd{def toJsCmd = if(results.nonEmpty) results.mkString("[\"", "\", \"", "\"]") else "[]" }
}
def render = {
val id = "my-autocomplete"
val cb = SHtml.ajaxCall(JsRaw("request"), update_source(_, 4))
val script = Script(new JsCmd{
def toJsCmd = "$(function() {"+
"$(\"#"+id+"\").autocomplete({ "+
"autocomplete: on, "+
"source: function(request, response) {"+
"response("+cb._2.toJsCmd + ");" +
"}"+
"})});"
})
<head><script charset="utf-8"> {script} </script></head> ++
<span id={id}> {SHtml.ajaxText(init, s=>{ /*set cell to value s*/; Noop}) } </span>
}
So my idea was:
- to get the selected result via an
SHtml.ajaxText
field which would be wraped into an autocomplete field - to update the autocomplete suggestions using a javascript function
回答1:
Here's what you need to do.
1) Make sure you are using Lift 2.5-SNAPSHOT (this is doable in earlier versions, but it's more difficult)
2) In the snippet you use to render the page, use SHtml.ajaxCall (in particular, you probably want this version: https://github.com/lift/framework/blob/master/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala#L170) which will allow you to register a server side function that accepts your search term and return a JSON response containing the completions. You will also register some action to occur on the JSON response with the JsContext.
3) The ajaxCall above will return a JsExp object which will result in the ajax request when it's invoked. Embed it within a javascript function on the page using your snippet.
4) Wire them up with some client side JS.
Update - Some code to help you out. It can definitely be done more succinctly with Lift 2.5, but due to some inconsistencies in 2.4 I ended up rolling my own ajaxCall like function. S.fmapFunc registers the function on the server side and the function body makes a Lift ajax call from the client, then invokes the res function (which comes from jQuery autocomplete) on the JSON response.
My jQuery plugin to "activate" the text input
(function($) {
$.fn.initAssignment = function() {
return this.autocomplete({
autoFocus: true,
source: function(req, res) {
search(req.term, res);
},
select: function(event, ui) {
assign(ui.item.value, function(data){
eval(data);
});
event.preventDefault();
$(this).val("");
},
focus: function(event, ui) {
event.preventDefault();
}
});
}
})(jQuery);
My Scala code that results in the javascript search function:
def autoCompleteJs = JsRaw("""
function search(term, res) {
""" +
(S.fmapFunc(S.contextFuncBuilder(SFuncHolder({ terms: String =>
val _candidates =
if(terms != null && terms.trim() != "")
assigneeCandidates(terms)
else
Nil
JsonResponse(JArray(_candidates map { c => c.toJson }))
})))
({ name =>
"liftAjax.lift_ajaxHandler('" + name
})) +
"=' + encodeURIComponent(term), " +
"function(data){ res(data); }" +
", null, 'json');" +
"""
}
""")
Update 2 - To add the function above to your page, use a CssSelector transform similar to the one below. The >* means append to anything that already exists within the matched script element. I've got other functions I've defined on that page, and this adds the search function to them.
"script >*" #> autoCompleteJs
You can view source to verify that it exists on the page and can be called just like any other JS function.
回答2:
With the help of Dave Whittaker, here is the solution I came with.
I had to change some behaviors to get:
- the desired text (from autocomplete or not) in an ajaxText element
- the possibility to have multiple autocomplete forms on same page
- submit answer on ajaxText before blurring when something is selected in autocomplete suggestions.
Scala part
private def getSugggestions(current: String, limit: Int):List[String] = {
/* returns list of suggestions */
}
private def autoCompleteJs = AnonFunc("term, res",JsRaw(
(S.fmapFunc(S.contextFuncBuilder(SFuncHolder({ terms: String =>
val _candidates =
if(terms != null && terms.trim() != "")
getSugggestions(terms, 5)
else
Nil
JsonResponse(JArray(_candidates map { c => JString(c)/*.toJson*/ }))
})))
({ name =>
"liftAjax.lift_ajaxHandler('" + name
})) +
"=' + encodeURIComponent(term), " +
"function(data){ res(data); }" +
", null, 'json');"))
def xml = {
val id = "myId" //possibility to have multiple autocomplete fields on same page
Script(OnLoad(JsRaw("jQuery('#"+id+"').createAutocompleteField("+autoCompleteJs.toJsCmd+")"))) ++
SHtml.ajaxText(cell.get, s=>{ cell.set(s); SearchMenu.recomputeResults; Noop}, "id" -> id)
}
Script to insert into page header:
(function($) {
$.fn.createAutocompleteField = function(search) {
return this.autocomplete({
autoFocus: true,
source: function(req, res) {
search(req.term, res);
},
select: function(event, ui) {
$(this).val(ui.item.value);
$(this).blur();
},
focus: function(event, ui) {
event.preventDefault();
}
});
}
})(jQuery);
Note: I accepted Dave's answer, mine is just to provide a complete answer for my purpose
来源:https://stackoverflow.com/questions/10043179/lift-autocomplete-with-ajax-submission