org.wicketstuff.objectautocomplete.ObjectAutoCompleteBehavior.java Source code

Java tutorial

Introduction

Here is the source code for org.wicketstuff.objectautocomplete.ObjectAutoCompleteBehavior.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.wicketstuff.objectautocomplete;

import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AbstractAutoCompleteBehavior;
import org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteSettings;
import org.apache.wicket.extensions.ajax.markup.html.autocomplete.IAutoCompleteRenderer;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.request.IRequestCycle;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.http.WebResponse;
import org.apache.wicket.request.resource.JavaScriptResourceReference;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.resource.CoreLibrariesContributor;

import java.io.Serializable;
import java.util.Iterator;

/**
 * Behaviour for object auto completion using a slightly modified variant of {@see
 * org.apache.wicket.extensions.ajax.markup.html.autocomplete.AbstractAutoCompleteBehavior}
 *
 * An (hidden) element is required to store the object id which has been selected.
 *
 * The type parameter is the type of the object to be rendered (not it's id)
 *
 * @author roland
 * @since May 18, 2008
 */
public class ObjectAutoCompleteBehavior<O> extends AbstractAutoCompleteBehavior {
    private static final long serialVersionUID = 1L;
    private static final ResourceReference OBJECTAUTOCOMPLETE_JS = new JavaScriptResourceReference(
            ObjectAutoCompleteBehavior.class, "wicketstuff-objectautocomplete.js");
    // Our version of 'wicket-autocomplete.js', with the patch from WICKET-1651
    private static final ResourceReference AUTOCOMPLETE_OBJECTIFIED_JS = new JavaScriptResourceReference(
            ObjectAutoCompleteBehavior.class, "wicketstuff-dropdown-list.js");

    // Element holding the object id as value
    private final Component objectElement;

    private final ObjectAutoCompleteCancelListener cancelListener;
    private final AutoCompletionChoicesProvider<O> choicesProvider;

    // one of this renderer must be set with the response renderer taking precedence
    private final IAutoCompleteRenderer<O> renderer;
    private final ObjectAutoCompleteResponseRenderer<O> responseRenderer;

    // =====================================================================================================
    // Specific configuration options:

    // tag name which indicates the possible choices (typically this is a "li")
    private final String choiceTagName;

    // alignment of menu
    private final ObjectAutoCompleteBuilder.Alignment alignment;

    // width of drop down
    private long width = 0;

    // delay for how long to wait for the update
    private final long delay;

    // whether search should be triggered on paste event
    private final boolean searchOnPaste;

    <I extends Serializable> ObjectAutoCompleteBehavior(Component pObjectElement,
            ObjectAutoCompleteBuilder<O, I> pBuilder) {
        renderer = pBuilder.autoCompleteRenderer;
        settings = new AutoCompleteSettings().setMaxHeightInPx(pBuilder.maxHeightInPx)
                .setPreselect(pBuilder.preselect).setShowListOnEmptyInput(pBuilder.showListOnEmptyInput);
        choiceTagName = pBuilder.choiceTagName;
        width = pBuilder.width;
        alignment = pBuilder.alignement;

        objectElement = pObjectElement;
        responseRenderer = pBuilder.autoCompleteResponseRenderer;
        cancelListener = pBuilder.cancelListener;
        choicesProvider = pBuilder.choicesProvider;
        searchOnPaste = pBuilder.searchOnPaste;
        delay = pBuilder.delay;
    }

    /**
     * Temporarily solution until patch from WICKET-1651 is applied. Note, that we avoid a call to
     * super to avoid the initialization in the direct parent class, but we have to copy over all
     * other code from the parent,
     *
     * @param response
     *            response to write to
     */
    @Override
    public void renderHead(Component c, IHeaderResponse response) {
        abstractDefaultAjaxBehaviour_renderHead(c, response);
        initHead(response);
    }

    @Override
    protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
        super.updateAjaxAttributes(attributes);
        attributes.setWicketAjaxResponse(false);
        attributes.setDataType("text");
    }

    @Override
    protected void onRequest(final String input, RequestCycle requestCycle) {
        IRequestHandler target = new IRequestHandler() {
            public void respond(IRequestCycle requestCycle) {
                WebResponse response = (WebResponse) requestCycle.getResponse();

                // Determine encoding
                final String encoding = Application.get().getRequestCycleSettings().getResponseRequestEncoding();
                response.setContentType("text/xml; charset=" + encoding);

                // Make sure it is not cached by a
                response.disableCaching();

                Iterator<O> comps = getChoices(input);
                if (responseRenderer != null) {
                    // there is a dedicated renderer configured
                    responseRenderer.onRequest(comps, response, input);
                } else {
                    renderer.renderHeader(response);
                    int renderedObjects = 0;
                    while (comps.hasNext()) {
                        final O comp = comps.next();
                        renderer.render(comp, response, input);
                        renderedObjects++;
                    }
                    renderer.renderFooter(response, renderedObjects);
                }
            }

            public void detach(IRequestCycle requestCycle) {
            }

        };
        requestCycle.scheduleRequestHandlerAfterCurrent(target);
    }

    // Copied over from AbstractDefaultAjaxBehaviour.renderHead() until patch
    // in WICKET-1651 gets applied
    private void abstractDefaultAjaxBehaviour_renderHead(Component component, IHeaderResponse response) {
        CoreLibrariesContributor.contributeAjax(component.getApplication(), response);
    }

    /**
     * Initialize response with our own java script
     *
     * @param response
     *            response to write to
     */
    protected void initHead(IHeaderResponse response) {
        response.render(JavaScriptHeaderItem.forReference(AUTOCOMPLETE_OBJECTIFIED_JS));
        response.render(JavaScriptHeaderItem.forReference(OBJECTAUTOCOMPLETE_JS));
        final String id = getComponent().getMarkupId();
        String initJS = String.format("new Wicketstuff.ObjectAutoComplete('%s','%s','%s',%s);", id,
                objectElement.getMarkupId(), getCallbackUrl(), getSettings());
        response.render(OnDomReadyHeaderItem.forScript(initJS));
    }

    @Override
    protected void onComponentTag(ComponentTag tag) {
        super.onComponentTag(tag);
        if (cancelListener != null) {
            final String keypress = "if (event) { var kc=Wicket.Event.keyCode(event); if (kc==27) {"
                    + "Wicket.Ajax.get({'u': '" + getCallbackUrl() + "&cancel=true&force=true'});" + "return false;"
                    + "} else if (kc==13) " + "return false; " + "else return true;}";
            tag.put("onkeypress", keypress);

            //         final String onblur = "Wicket.Ajax.get({'u': '" + getCallbackUrl() + "&cancel=true' }); return false;";
            //         tag.put("onblur", onblur);
        }
    }

    @Override
    protected void respond(AjaxRequestTarget target) {
        RequestCycle requestCycle = RequestCycle.get();
        boolean cancel = requestCycle.getRequest().getRequestParameters().getParameterValue("cancel").toBoolean();
        boolean force = requestCycle.getRequest().getRequestParameters().getParameterValue("force").toBoolean();

        if (cancelListener != null && cancel) {
            cancelListener.searchCanceled(target, force);
        } else {
            super.respond(target);
        }
    }

    protected Iterator<O> getChoices(String input) {
        return choicesProvider.getChoices(input);
    }

    // Create settings
    private CharSequence getSettings() {
        StringBuilder builder = new StringBuilder(constructSettingsJS());
        if (choiceTagName != null || alignment != null || width != 0) {
            // remove trailing "}"
            builder.setLength(builder.length() - 1);
            if (choiceTagName != null) {
                builder.append(",choiceTagName: '").append(choiceTagName.toUpperCase()).append("'");
            }
            if (alignment != null) {
                builder.append(",align: '")
                        .append(alignment == ObjectAutoCompleteBuilder.Alignment.LEFT ? "left" : "right")
                        .append("'");
            }
            if (width != 0) {
                builder.append(",width: ").append(width);
            }
            if (delay != 0) {
                builder.append(",delay: ").append(delay);
            }
            if (searchOnPaste) {
                builder.append(",searchOnPaste: true");
            }
            builder.append("}");
        }
        return builder;
    }
}