/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
// www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation; version 3 of the License.
//
// This community edition is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see http://www.gnu.org/licenses/.
//
/////////////////////////////////////////////////////////////////////////////
package org.projectforge.web.wicket.autocompletion;
import java.util.ArrayList;
import java.util.List;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.extensions.ajax.markup.html.autocomplete.IAutoCompleteRenderer;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptReferenceHeaderItem;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.handler.TextRequestHandler;
import org.apache.wicket.util.string.Strings;
import org.projectforge.web.core.JsonBuilder;
import org.projectforge.web.wicket.WicketRenderHeadUtils;
public abstract class PFAutoCompleteBehavior<T> extends AbstractDefaultAjaxBehavior
{
private static final long serialVersionUID = -6532710378025987377L;
protected PFAutoCompleteSettings settings;
protected IAutoCompleteRenderer<String> renderer;
public PFAutoCompleteBehavior(final IAutoCompleteRenderer<String> renderer, final PFAutoCompleteSettings settings)
{
this.renderer = renderer;
this.settings = settings;
}
/**
* @see org.apache.wicket.ajax.AbstractDefaultAjaxBehavior#renderHead(org.apache.wicket.Component,
* org.apache.wicket.markup.html.IHeaderResponse)
*/
@Override
public void renderHead(final Component component, final IHeaderResponse response)
{
super.renderHead(component, response);
WicketRenderHeadUtils.renderMainJavaScriptIncludes(response);
response.render(JavaScriptReferenceHeaderItem.forUrl("scripts/jquery.wicket-autocomplete.js"));
renderAutocompleteHead(response);
}
/**
* Render autocomplete init javascript and other head contributions
*
* @param response
*/
private void renderAutocompleteHead(final IHeaderResponse response)
{
final String id = getComponent().getMarkupId();
String indicatorId = findIndicatorId();
if (Strings.isEmpty(indicatorId)) {
indicatorId = "null";
} else {
indicatorId = "'" + indicatorId + "'";
}
final StringBuffer buf = new StringBuffer();
buf.append("var favorite" + id + " = ");
final List<T> favorites = getFavorites();
final MyJsonBuilder builder = new MyJsonBuilder();
if (favorites != null) {
buf.append(builder.append(favorites).getAsString());
} else {
buf.append(builder.append(getRecentUserInputs()).getAsString());
}
buf.append(";").append("var z = $(\"#").append(id).append("\");\n").append("z.autocomplete(\"").append(getCallbackUrl()).append("\",{");
boolean first = true;
for (final String setting : getSettingsJS()) {
if (first == true)
first = false;
else buf.append(", ");
buf.append(setting);
}
if (first == true)
first = false;
else buf.append(", ");
buf.append("favoriteEntries:favorite" + id);
buf.append("});");
if (settings.isHasFocus() == true) {
buf.append("\nz.focus();");
}
final String initJS = buf.toString();
// String initJS = String.format("new Wicket.AutoComplete('%s','%s',%s,%s);", id, getCallbackUrl(), constructSettingsJS(), indicatorId);
response.render(OnDomReadyHeaderItem.forScript(initJS));
}
protected final List<String> getSettingsJS()
{
final List<String> result = new ArrayList<String>();
addSetting(result, "matchContains", settings.isMatchContains());
addSetting(result, "minChars", settings.getMinChars());
addSetting(result, "delay", settings.getDelay());
addSetting(result, "matchCase", settings.isMatchCase());
addSetting(result, "matchSubset", settings.isMatchSubset());
addSetting(result, "cacheLength", settings.getCacheLength());
addSetting(result, "mustMatch", settings.isMustMatch());
addSetting(result, "selectFirst", settings.isSelectFirst());
addSetting(result, "selectOnly", settings.isSelectOnly());
addSetting(result, "maxItemsToShow", settings.getMaxItemsToShow());
addSetting(result, "autoFill", settings.isAutoFill());
addSetting(result, "autoSubmit", settings.isAutoSubmit());
addSetting(result, "scroll", settings.isScroll());
addSetting(result, "scrollHeight", settings.getScrollHeight());
addSetting(result, "width", settings.getWidth());
addSetting(result, "deletableItem", settings.isDeletableItem());
if (settings.isLabelValue() == true) {
addSetting(result, "labelValue", settings.isLabelValue());
}
return result;
}
private final void addSetting(final List<String> result, final String name, final Boolean value)
{
if (value == null) {
return;
}
result.add(name + ":" + ((value == true) ? "1" : "0"));
}
private final void addSetting(final List<String> result, final String name, final Integer value)
{
if (value == null) {
return;
}
result.add(name + ":" + value);
}
/**
* @see org.apache.wicket.ajax.AbstractDefaultAjaxBehavior#onBind()
*/
@Override
protected void onBind()
{
// add empty AbstractDefaultAjaxBehavior to the component, to force
// rendering wicket-ajax.js reference if no other ajax behavior is on
// page
getComponent().add(new AbstractDefaultAjaxBehavior() {
private static final long serialVersionUID = 1L;
@Override
protected void respond(final AjaxRequestTarget target)
{
}
});
}
/**
* @see org.apache.wicket.ajax.AbstractDefaultAjaxBehavior#respond(org.apache.wicket.ajax.AjaxRequestTarget)
*/
@Override
protected void respond(final AjaxRequestTarget target)
{
final RequestCycle requestCycle = RequestCycle.get();
final org.apache.wicket.util.string.StringValue val = requestCycle.getRequest().getQueryParameters().getParameterValue("q");
onRequest(val != null ? val.toString() : null, requestCycle);
}
protected final void onRequest(final String val, final RequestCycle requestCycle)
{
// final PageParameters pageParameters = new PageParameters(requestCycle.getRequest().getParameterMap());
final List<T> choices = getChoices(val);
final MyJsonBuilder builder = new MyJsonBuilder();
final String json = builder.append(choices).getAsString();
requestCycle.scheduleRequestHandlerAfterCurrent(new TextRequestHandler("application/json", "utf-8", json));
/*
* IRequestTarget target = new IRequestTarget() {
*
* public void respond(RequestCycle requestCycle) {
*
* WebResponse r = (WebResponse) requestCycle.getResponse(); // Determine encoding final String encoding =
* Application.get().getRequestCycleSettings().getResponseRequestEncoding(); r.setCharacterEncoding(encoding);
* r.setContentType("application/json"); // Make sure it is not cached by a r.setHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT");
* r.setHeader("Cache-Control", "no-cache, must-revalidate"); r.setHeader("Pragma", "no-cache");
*
* final List<T> choices = getChoices(val); renderer.renderHeader(r); renderer.render(JsonBuilder.buildRows(false, choices), r, val);
* renderer.renderFooter(r); }
*
* public void detach(RequestCycle requestCycle) { } }; requestCycle.setRequestTarget(target);
*/
}
/**
* Callback method that should return an iterator over all possible choice objects. These objects will be passed to the renderer to
* generate output. Usually it is enough to return an iterator over strings.
*
* @param input current input
* @return iterator over all possible choice objects
*/
protected abstract List<T> getChoices(String input);
/**
* Callback method that should return a list of all possible default choice objects to show, if the user double clicks the empty input
* field. These objects will be passed to the renderer to generate output. Usually it is enough to return an iterator over strings.
*/
protected abstract List<T> getFavorites();
/**
* Callback method that should return a list of all recent user inputs in the text input field. They will be shown, if the user double
* clicks the empty input field. These objects will be passed to the renderer to generate output. Usually it is enough to return an
* iterator over strings. <br/>
* Please note: Please, use only getFavorites() OR getRecentUserInputs()!
*/
protected abstract List<String> getRecentUserInputs();
/**
* Used for formatting the values.
*/
protected abstract String formatValue(T value);
/**
* Used for formatting the labels if labelValue is set to true.
* @return null at default (if not overload).
*/
protected String formatLabel(final T value)
{
return null;
}
private class MyJsonBuilder extends JsonBuilder
{
private MyJsonBuilder()
{
setEscapeHtml(true);
}
@SuppressWarnings("unchecked")
@Override
protected String formatValue(final Object obj)
{
if (obj instanceof String) {
return obj.toString();
} else {
return PFAutoCompleteBehavior.this.formatValue((T) obj);
}
}
@SuppressWarnings("unchecked")
@Override
protected Object transform(final Object obj)
{
if (settings.isLabelValue() == true) {
final Object[] oa = new Object[2];
if (obj instanceof String) {
oa[0] = obj;
oa[1] = obj;
} else {
oa[0] = PFAutoCompleteBehavior.this.formatLabel((T) obj);
oa[1] = PFAutoCompleteBehavior.this.formatValue((T) obj);
}
return oa;
} else {
return obj;
}
}
};
}