Wicket の Autocomplete といえば、jQuery UI を利用したものが慣れ親しんでものであった。
最近よく使われる Bootstrap デザインを適用しても使えないわけではない、
CSSスタイルシートを合わせて書いていけば良いのだが、
Bootstrap と jQuery UI を併用が嫌で jQuery UI をやめたくなる。
datepicker も Bootstrap向けが存在するわけだし、
Draggable / Drop これさえ Bootstrap でできれば、
jQuery UI を完全にやめれるのではないか?
前置きはこのくらいで、本題!
typeahead.js/jquery_typeahead.md at master · twitter/typeahead.js · GitHub
twitter/typeahead.js が Bootstrap デザインと相性が良いようだ。
まず、twitter/Typeahead.js のリモートからマッチ候補を受信する書き方を把握しておく。。
JSソースとしては、
<input type="text" id="typeahead"> に対して
$('input#typeahead').typeahead({
highlight: true,
minLength: 1
}, {
displayKey: 'display',
limit: 7,
source : new Bloodhound({
datumTokenizer: function(datum){
return Bloodhound.tokenizers.whitespace(datum.display);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote:{
wildcard: '%QUERY',
url: 'http://xxxxxxxxxxxxx?query=%QUERY',
transform: function(response){
return $.map(response, function(item){
return { display:item.value, id:item.id };
});
},
}
})
}).on('typeahead:select', function(ev, item){
console.log('選択された id = ' + item.id);
console.log('選択された 表示文字列 = ' + item.display);
}).on('change', function(ev){
console.log($(this).val());
});
このようなJSソースをリソースとして用意するのではなく、
Wicket TextField の振るまい Behavior として
AJAX通信→ マッチ候補リスト表示させるようにするのです。
CSSは、以下に適当なものがあったので、
https://github.com/bassjobsen/typeahead.js-bootstrap-css
これを参考に以下のように用意します。
span.twitter-typeahead .tt-menu,
span.twitter-typeahead .tt-dropdown-menu {
cursor: pointer;
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
display: none;
float: left;
min-width: 100%;
padding: 5px 0;
margin: 2px 0 0;
list-style: none;
font-size: 14px;
text-align: left;
background-color: #ffffff;
border: 1px solid #cccccc;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 4px;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
background-clip: padding-box;
}
span.twitter-typeahead .tt-suggestion {
display: block;
padding: 3px 20px;
clear: both;
font-weight: normal;
line-height: 1.42857143;
color: #333333;
white-space: nowrap;
}
span.twitter-typeahead .tt-suggestion.tt-cursor,
span.twitter-typeahead .tt-suggestion:hover,
span.twitter-typeahead .tt-suggestion:focus {
color: #ffffff;
text-decoration: none;
outline: 0;
background-color: #337ab7;
}
.input-group.input-group-lg span.twitter-typeahead .form-control {
height: 46px;
padding: 10px 16px;
font-size: 18px;
line-height: 1.3333333;
border-radius: 6px;
}
.input-group.input-group-sm span.twitter-typeahead .form-control {
height: 30px;
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px;
}
span.twitter-typeahead {
width: 100%;
}
.input-group span.twitter-typeahead {
display: block !important;
height: 34px;
}
.input-group span.twitter-typeahead .tt-menu,
.input-group span.twitter-typeahead .tt-dropdown-menu {
top: 32px !important;
}
.input-group span.twitter-typeahead:not(:first-child):not(:last-child) .form-control {
border-radius: 0;
}
.input-group span.twitter-typeahead:first-child .form-control {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.input-group span.twitter-typeahead:last-child .form-control {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.input-group.input-group-sm span.twitter-typeahead {
height: 30px;
}
.input-group.input-group-sm span.twitter-typeahead .tt-menu,
.input-group.input-group-sm span.twitter-typeahead .tt-dropdown-menu {
top: 30px !important;
}
.input-group.input-group-lg span.twitter-typeahead {
height: 46px;
}
.input-group.input-group-lg span.twitter-typeahead .tt-menu,
.input-group.input-group-lg span.twitter-typeahead .tt-dropdown-menu {
top: 46px !important;
}
Wicket の TextField を継承
キー入力イベント後のAJAX通信で返す JSON は、Goole gson で生成しています。
import java.util.List;
import java.util.Optional;
import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.IRequestParameters;
import org.apache.wicket.request.handler.TextRequestHandler;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
Twitter Boostrap Typeahead TextField.
public abstract class AjaxTypeahead<T extends Typeahead> extends TextField<String>{
private AbstractDefaultAjaxBehavior queryAjaxBehavior;
private List<Typeahead> choicelist;
public AjaxTypeahead(String id, IModel<String> model){
super(id, model);
Gson gson = new GsonBuilder().serializeNulls().create();
queryAjaxBehavior = new AbstractDefaultAjaxBehavior(){
@Override
protected void respond(AjaxRequestTarget target){
IRequestParameters p = getRequestCycle().getRequest().getQueryParameters();
Optional.ofNullable(p.getParameterValue("query").toString()).ifPresent(s->{
choicelist = getChoices(s.trim());
getComponent().getRequestCycle()
.replaceAllRequestHandlers(new TextRequestHandler("application/json", "UTF-8", gson.toJson(choicelist)));
});
Optional.ofNullable(p.getParameterValue("selected").toString()).ifPresent(s->{
String id = p.getParameterValue("id").toString();
for(Typeahead t:choicelist){
if (t.id.equals(id)){
onSelect(target, t);
break;
}
}
});
Optional.ofNullable(p.getParameterValue("change").toString()).ifPresent(s->{
String display = Optional.ofNullable(p.getParameterValue("display").toString())
.orElse("").trim();
onChange(target, display);
});
getRequestCycle().getResponse().close();
}
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.setDataType("json");
attributes.setWicketAjaxResponse(false);
}
};
add(queryAjaxBehavior);
}
@Override
protected void onAfterRender(){
super.onAfterRender();
getResponse().write("<script type=\"text/javascript\">");
getResponse().write(" $(\"#" + this.getMarkupId(true) + "\").typeahead({highlight:true,minLength:1},{");
getResponse().write("displayKey: 'display',");
getResponse().write("limit: 7,");
getResponse().write("source : new Bloodhound({");
getResponse().write("datumTokenizer: function(datum){");
getResponse().write("return Bloodhound.tokenizers.whitespace(datum.display);");
getResponse().write("},");
getResponse().write("queryTokenizer: Bloodhound.tokenizers.whitespace,");
getResponse().write("remote:{");
getResponse().write("wildcard: '%QUERY',");
getResponse().write("url: '" + queryAjaxBehavior.getCallbackUrl() + "&query=%QUERY',");
getResponse().write("transform: function(r){");
getResponse().write("return $.map(r, function(t){");
getResponse().write("return { display:t.value, id:t.id };");
getResponse().write("});}}})");
getResponse().write("}).on('typeahead:select', function(ev, item){");
getResponse().write("var url = '" + queryAjaxBehavior.getCallbackUrl() + "' + '&selected=&id=' + item.id + '&display=' + item.display;");
getResponse().write("Wicket.Ajax.get({ u: url });");
getResponse().write("}).on('change', function(ev){");
getResponse().write("var url = '" + queryAjaxBehavior.getCallbackUrl() + "' + '&change=&display=' + $(this).val();");
getResponse().write("Wicket.Ajax.get({ u: url });");
getResponse().write("});");
getResponse().write("</script>");
}
入力文字→候補リスト
protected abstract List<Typeahead> getChoices(String input);
選択イベント捕捉.
@param target
@param id
@param dislay
protected void onSelect(AjaxRequestTarget target, Typeahead typeahead){
}
変更イベント捕捉
protected void onChange(AjaxRequestTarget target, String dislay){
}
}
表示用の素材、Typeahead は、id と プルダウン表示及び入力文字列をSerializableである。
この Typeahead を継承するかそのまま使用する。
import java.io.Serializable;
twitter/Typeahead.js element.
public class Typeahead implements Serializable{
public String id;
public String value;
public Typeahead(){}
public void setValue(String value){
this.value = value;
}
public void setId(String id){
this.id = id;
}
}
使用する例: Example
AjaxTypeahead<Typeahead> simpleField = new AjaxTypeahead<>("name", new Model<>()){
@Override
protected List<Typeahead> getChoices(String input){
if (Strings.isEmpty(input)) return Collections.emptyList();
TODO
return list;
}
@Override
protected void onSelect(AjaxRequestTarget target, String id, String dislay){
TODO
}
@Override
protected void onChange(AjaxRequestTarget target, String dislay){
TODO
}
};
queue(simpleField);
あくまでも、TextField の継承であり、
フォーム送信で受け取る Wicket の Model オブジェクトは、String である。