name.martingeisse.wicket.component.select.SelectableElementsBehavior.java Source code

Java tutorial

Introduction

Here is the source code for name.martingeisse.wicket.component.select.SelectableElementsBehavior.java

Source

/**
 * Copyright (c) 2010 Martin Geisse
 *
 * This file is distributed under the terms of the MIT license.
 */

package name.martingeisse.wicket.component.select;

import java.util.ArrayList;
import java.util.List;
import name.martingeisse.wicket.component.contextmenu.ContextMenu;
import name.martingeisse.wicket.component.contextmenu.IContextMenuCallbackBuilder;
import name.martingeisse.wicket.util.WicketHeadUtil;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.attributes.CallbackParameter;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.request.IRequestParameters;
import org.apache.wicket.request.cycle.RequestCycle;
import org.json.simple.JSONValue;

/**
 * This behavior can be attached to a component to make sub-elements
 * of the component selectable. Selected elements are marked using
 * specific CSS classes and inline styles. Various types of
 * interactions, as well as provided Javascript functions, allow to
 * trigger server-side actions on the selected elements.
 * 
 * The behavior is configured by two main options:
 * - the elementSelector is a jQuery selector that finds the selectable
 *   elements within the context of the component to which this behavior
 *   is attached
 * - the valueExpression is evaluated to find the "value" of a selectable
 *   element (available in a variable called "element"). This value is
 *   then sent to the server. Such values must be JSON-encodable (they
 *   will be passed to JSON.stringify()).
 * 
 * This class supports a context menu, with the list of selected elements
 * as the anchor.
 * 
 * @param <T> the internal representation type of selectable elements
 */
public abstract class SelectableElementsBehavior<T> extends AbstractDefaultAjaxBehavior
        implements IContextMenuCallbackBuilder {

    /**
     * the CONTEXT_MENU_INTERACTION_PREFIX
     */
    private static final String CONTEXT_MENU_INTERACTION_PREFIX = "contextMenu.";

    /**
     * the elementSelector
     */
    private String elementSelector;

    /**
     * the valueExpression
     */
    private String valueExpression;

    /**
     * the contextMenu
     */
    private ContextMenu<List<T>> contextMenu;

    /**
     * Constructor.
     * @param elementSelector the jQuery selector for selectable elements
     * @param valueExpression an expression for the value of a selectable element
     */
    public SelectableElementsBehavior(final String elementSelector, final String valueExpression) {
        this.elementSelector = elementSelector;
        this.valueExpression = valueExpression;
    }

    /**
     * Constructor.
     * @param elementSelector the jQuery selector for selectable elements
     * @param valueExpression an expression for the value of a selectable element
     * @param contextMenu the context menu
     */
    public SelectableElementsBehavior(final String elementSelector, final String valueExpression,
            ContextMenu<List<T>> contextMenu) {
        this.elementSelector = elementSelector;
        this.valueExpression = valueExpression;
        this.contextMenu = contextMenu;
    }

    /**
     * Getter method for the elementSelector.
     * @return the elementSelector
     */
    public String getElementSelector() {
        return elementSelector;
    }

    /**
     * Setter method for the elementSelector.
     * @param elementSelector the elementSelector to set
     */
    public void setElementSelector(final String elementSelector) {
        this.elementSelector = elementSelector;
    }

    /**
     * Getter method for the valueExpression.
     * @return the valueExpression
     */
    public String getValueExpression() {
        return valueExpression;
    }

    /**
     * Setter method for the valueExpression.
     * @param valueExpression the valueExpression to set
     */
    public void setValueExpression(final String valueExpression) {
        this.valueExpression = valueExpression;
    }

    /* (non-Javadoc)
     * @see org.apache.wicket.behavior.Behavior#onConfigure(org.apache.wicket.Component)
     */
    @Override
    public void onConfigure(Component component) {
        super.onConfigure(component);
        contextMenu.onConfigure(component);
    }

    /* (non-Javadoc)
     * @see org.apache.wicket.ajax.AbstractDefaultAjaxBehavior#renderHead(org.apache.wicket.Component, org.apache.wicket.markup.head.IHeaderResponse)
     */
    @Override
    public void renderHead(final Component component, final IHeaderResponse response) {
        super.renderHead(component, response);
        WicketHeadUtil.includeClassJavascript(response, SelectableElementsBehavior.class);

        final CallbackParameter[] parameters = new CallbackParameter[] { CallbackParameter.explicit("interaction"),
                CallbackParameter.explicit("selectedValues"), CallbackParameter.explicit("data"), };

        final StringBuilder builder = new StringBuilder();
        builder.append("\t$('#").append(component.getMarkupId()).append("').selectableElements({\n");
        builder.append("\t\telementSelector: '" + elementSelector + "',\n");
        builder.append("\t\tvalueExtractor: function(element) {return " + valueExpression + ";},\n");
        builder.append("\t\tajaxCallback: ").append(getCallbackFunction(parameters).toString().replace("\n", " "))
                .append(",");
        builder.append("\t\tnotSelectedStyle: {'background-color': ''},\n");
        builder.append("\t\tselectedStyle: {'background-color': '#f00'},\n");
        builder.append("\t\tcontextMenuData: ");
        if (contextMenu != null) {
            ContextMenu.renderHead(response);
            contextMenu.buildCreateExpression(builder, "#" + component.getMarkupId(), this);
        } else {
            builder.append("null");
        }
        builder.append(",\n");
        builder.append("\t});\n");

        final AjaxRequestTarget target = component.getRequestCycle().find(AjaxRequestTarget.class);
        if (target == null) {
            response.render(OnDomReadyHeaderItem.forScript(builder.toString()));
        } else {
            target.appendJavaScript(builder);
        }

    }

    /* (non-Javadoc)
     * @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 IRequestParameters parameters = requestCycle.getRequest().getRequestParameters();
        final String interaction = parameters.getParameterValue("interaction").toString();
        if (interaction != null) {
            final Object selectedValues = getJsonParameter(parameters, "selectedValues", "[]");
            final Object data = getJsonParameter(parameters, "data", "null");
            if (selectedValues instanceof List) {
                List<T> convertedValues = convertSelectedValues((List<?>) selectedValues);
                if (interaction.startsWith(CONTEXT_MENU_INTERACTION_PREFIX)) {
                    if (contextMenu != null) {
                        String menuItemKey = interaction.substring(CONTEXT_MENU_INTERACTION_PREFIX.length());
                        contextMenu.notifySelected(menuItemKey, convertedValues, data);
                    }
                } else {
                    onInteraction(target, interaction, convertedValues);
                }
            }
        }
    }

    /**
     * 
     */
    private static Object getJsonParameter(IRequestParameters parameters, String name, String encodedDefaultValue) {
        String encodedValue = parameters.getParameterValue(name).toString();
        if (encodedValue == null) {
            encodedValue = encodedDefaultValue;
        }
        return JSONValue.parse(encodedValue);
    }

    /**
     * Converts a list of values from JSON to the internal value type T.
     * @param jsonValues the JSON values
     * @return the internal values
     */
    protected List<T> convertSelectedValues(List<?> jsonValues) {
        List<T> result = new ArrayList<T>();
        for (Object jsonValue : jsonValues) {
            result.add(convertSelectedValue(jsonValue));
        }
        return result;
    }

    /**
     * Converts a single value from JSON to the internal value type T.
     * @param jsonValue the JSON value
     * @return the internal value
     */
    protected abstract T convertSelectedValue(Object jsonValue);

    /**
     * This method is invoked when the client-side scripts notify the server about
     * a user interaction.
     * @param target the ART
     * @param interaction a string describing the interaction, e.g. "dblclick"
     * for a double-click
     * @param selectedValues the selected values
     */
    protected abstract void onInteraction(AjaxRequestTarget target, String interaction, List<T> selectedValues);

    /* (non-Javadoc)
     * @see name.martingeisse.webide.workbench.components.contextmenu.IContextMenuCallbackBuilder#buildContextMenuCallback(java.lang.StringBuilder)
     */
    @Override
    public void buildContextMenuCallback(StringBuilder builder) {
        builder.append("var interaction = '" + CONTEXT_MENU_INTERACTION_PREFIX + "' + key;");
        builder.append("\t$('#").append(getComponent().getMarkupId())
                .append("').selectableElementsAjax(interaction, data);\n");
    }

}