fiftyfive.wicket.util.Shortcuts.java Source code

Java tutorial

Introduction

Here is the source code for fiftyfive.wicket.util.Shortcuts.java

Source

/**
 * Copyright 2013 55 Minutes (http://www.55minutes.com)
 *
 * Licensed 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 fiftyfive.wicket.util;

import java.io.Serializable;
import java.util.Collection;
import java.util.Set;

import fiftyfive.util.ReflectUtils;
import fiftyfive.wicket.basic.LabelWithPlaceholder;
import fiftyfive.wicket.css.CssClassModifier;
import fiftyfive.wicket.css.InternetExplorerCss;

import org.apache.wicket.Application;
import org.apache.wicket.Component;

import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.behavior.Behavior;

import org.apache.wicket.markup.html.IHeaderResponse;

import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;

import org.apache.wicket.request.Response;
import org.apache.wicket.request.resource.PackageResourceReference;

import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.lang.Classes;
import org.apache.wicket.util.string.Strings;

/**
 * Helper methods that greatly reduce the amount of boilerplate code needed
 * for common Wicket tasks. Consider adding this
 * in the Java file of your wicket page or component:
 * <pre class="example">
 * import static fiftyfive.wicket.util.Shortcuts.*;</pre>
 *
 * @author Matt Brictson
 */
public class Shortcuts {
    private static final Behavior EMPTY_BEHAVIOR = new Behavior() {
    };

    /**
     * Shortcut for creating a PropertyModel. Equivalent to:
     * <pre class="example">
     * new PropertyModel(bean, propertyExpr)</pre>
     * 
     * @see PropertyModel
     */
    public static PropertyModel prop(Object bean, String propertyExpr) {
        return new PropertyModel(bean, propertyExpr);
    }

    /**
     * Creates a model that loads itself by exeucting the specified method
     * on a bean. The return value of that method will be cached as the value
     * of the model. When the model is detached, the cache will be discarded.
     * This is useful for connecting your Wicket page to your back-end:
     * <pre class="example">
     * public PersonDetailPage(PageParameters params)
     * {
     *     super(params);
     *     setDefaultModel(loadedModel(this, "loadPerson"));
     * }
     * private Person loadPerson()
     * {
     *     return personService.loadPerson(getPageParameters().get("id"));
     * }</pre>
     * 
     * @throws IllegalArgumentException if the loadMethod does not exist, or
     *                                  takes more than zero arguments
     */
    public static LoadableDetachableModel loadedModel(final Object bean, final String loadMethod) {
        // assert that method exists
        ReflectUtils.getZeroArgMethod(bean.getClass(), loadMethod);

        return new LoadableDetachableModel() {
            @Override
            protected Object load() {
                return ReflectUtils.invokeZeroArgMethod(bean, loadMethod);
            }
        };
    }

    /**
     * Shortcut for creating a LabelWithPlaceholder with an implied model.
     * Equivalent to:
     * <pre class="example">
     * new LabelWithPlaceholder("name")</pre>
     * 
     * @see LabelWithPlaceholder
     */
    public static LabelWithPlaceholder label(String id) {
        return new LabelWithPlaceholder(id);
    }

    /**
     * Shortcut for creating a LabelWithPlaceholder with a PropertyModel.
     * Equivalent to:
     * <pre class="example">
     * new LabelWithPlaceholder("name", new PropertyModel(person, "fullName"))</pre>
     * 
     * @see LabelWithPlaceholder
     */
    public static LabelWithPlaceholder label(String id, Object bean, String propertyExpr) {
        return label(id, prop(bean, propertyExpr));
    }

    /**
     * Shortcut for creating a LabelWithPlaceholder with a hardcoded value or a
     * custom model. Equivalent to:
     * <pre class="example">
     * new LabelWithPlaceholder("name", "Hardcoded string value")</pre>
     * 
     * @see LabelWithPlaceholder
     */
    public static LabelWithPlaceholder label(String id, Serializable valueOrIModel) {
        IModel model = valueOrIModel != null ? new Model(valueOrIModel) : null;
        if (valueOrIModel instanceof IModel) {
            model = (IModel) valueOrIModel;
        }
        return new LabelWithPlaceholder(id, model);
    }

    /**
     * Appends the specified text after the
     * closing tag of the component it decorates. The text will <b>not</b> be
     * escaped to be safe HTML, so take care to escape the string first if
     * necessary.
     * <p>
     * This shortcut is very useful for doing comma separated lists.
     * For example:
     * <pre class="example">
     * add(new ListView("list", myList) {
     *     &#064;Override protected void populateItem(ListItem item)
     *     {
     *         if(item.getIndex() &lt; getList().size() - 1)
     *         {
     *             item.add(afterTag(", "));
     *         }
     *     }
     * });</pre>
     */
    public static Behavior afterTag(final String textToAppend) {
        return new Behavior() {
            @Override
            public void afterRender(Component component) {
                Response response = component.getResponse();
                response.write(textToAppend);
            }
        };
    }

    /**
     * Adds a CSS class to the component it decorates.
     * Equivalent to:
     * <pre class="example">
     * new AttributeAppender("class", true, cssClass, " ");</pre>
     * 
     * @since 2.0
     */
    public static Behavior cssClass(IModel<String> cssClass) {
        if (null == cssClass) {
            return EMPTY_BEHAVIOR;
        }
        return new AttributeAppender("class", cssClass) {
            @Override
            protected String newValue(String current, String append) {
                if (null == current && null == append)
                    return null;
                return super.newValue(current, append);
            }
        }.setSeparator(" ");
    }

    /**
     * Adds a CSS class to the component it decorates.
     * Equivalent to:
     * <pre class="example">
     * new AttributeAppender("class", true, new Model(cssClass), " ");</pre>
     */
    public static Behavior cssClass(String cssClass) {
        return cssClass(new Model(cssClass));
    }

    /**
     * Adds {@code classIfTrue} to the HTML {@code class} attribute of the component
     * if the {@code toggle} value is {@code true}. If the {@code toggle} value is {@code false},
     * ensure that the class is removed (in case it was already present in the markup).
     * <p>
     * For example, let's say your HTML file contains the following markup:
     * <pre class="example">
     * &lt;span wicket:id=&quot;message&quot; class=&quot;severe&quot;&gt;blah blah&lt;/span&gt;</pre>
     * You've added a behavior like so:
     * <pre class="example">
     * add(new MyMessageComponent("message")
     *     .add(toggledCssClass("severe", isSevereModel)));</pre>
     * If the {@code isSevereModel} model returns {@code true}, the component will render as:
     * <pre class="example">
     * &lt;span class=&quot;severe&quot;&gt;...</pre>
     * If {@code false}, then:
     * <pre class="example">
     * &lt;span class=&quot;&quot;&gt;...</pre>
     * 
     * @since 2.0.4
     */
    public static Behavior toggledCssClass(String classIfTrue, IModel<Boolean> toggle) {
        return toggledCssClass(classIfTrue, null, toggle);
    }

    /**
     * Adds {@code classIfTrue} to the HTML {@code class} attribute of the component
     * if the {@code toggle} value is {@code true}. If the {@code toggle} value is {@code false},
     * adds {@code classIfFalse} to the HTML instead. In both cases, if these classes already
     * exist in the markup, they will first be removed.
     * <p>
     * For example, let's say your HTML file contains the following markup:
     * <pre class="example">
     * &lt;span wicket:id=&quot;message&quot; class=&quot;high-priority&quot;&gt;blah blah&lt;/span&gt;</pre>
     * You've added a behavior like so:
     * <pre class="example">
     * add(new MyMessageComponent("message")
     *     .add(toggledCssClass("high-priority", "low-priority", isHighPriorityModel)));</pre>
     * If the {@code isHighPriorityModel} model returns {@code true}, the component will render as:
     * <pre class="example">
     * &lt;span class=&quot;high-priority&quot;&gt;...</pre>
     * If {@code false}, then:
     * <pre class="example">
     * &lt;span class=&quot;low-priority&quot;&gt;...</pre>
     * 
     * @since 2.0.4
     */
    public static Behavior toggledCssClass(final String classIfTrue, final String classIfFalse,
            final IModel<Boolean> toggle) {
        if (null == toggle) {
            return EMPTY_BEHAVIOR;
        }
        return new CssClassModifier() {
            @Override
            protected void modifyClasses(Component component, Set<String> cssClasses) {
                // Note that we treat null as false
                Boolean b = toggle.getObject();
                if (b != null && b) {
                    if (classIfFalse != null)
                        cssClasses.remove(classIfFalse);
                    if (classIfTrue != null)
                        cssClasses.add(classIfTrue);
                } else {
                    if (classIfTrue != null)
                        cssClasses.remove(classIfTrue);
                    if (classIfFalse != null)
                        cssClasses.add(classIfFalse);
                }
            }
        };
    }

    /**
     * Creates a header contributor that adds a &lt;link&gt; to a CSS file with
     * the same name as the specified class. For example:
     * <pre class="example">
     * add(cssResource(MyPanel.class));</pre>
     * <p>
     * will add a &lt;link&gt; to the &lt;head&gt; for {@code MyPanel.css},
     * found in the same classpath location as {@code MyPanel.class}.
     * <p>
     * This is equivalent to overriding {@code renderHead()} with:
     * <pre class="example">
     * response.renderCSSReference(
     *     new PackageResourceReference(MyPanel.class, "MyPanel.css")
     * );</pre>
     * 
     * @since 2.0
     */
    public static Behavior cssResource(Class<?> cls) {
        Args.notNull(cls, "cls");
        return cssResource(cls, Classes.simpleName(cls) + ".css");
    }

    /**
     * Creates a header contributor that adds a &lt;link&gt; to a CSS file with
     * the specified name, relative to the current application class.
     * For example:
     * <pre class="example">
     * add(cssResource("screen.css"));</pre>
     * <p>
     * will add a &lt;link&gt; to the &lt;head&gt; for {@code screen.css},
     * found in the same classpath location as your wicket application class.
     * <p>
     * This is equivalent to overriding {@code renderHead()} with:
     * <pre class="example">
     * response.renderCSSReference(
     *     new PackageResourceReference(Application.get().getClass(), "screen.css")
     * );</pre>
     * 
     * @since 2.0
     */
    public static Behavior cssResource(String filename) {
        return cssResource(filename, null);
    }

    /**
     * Creates a header contributor that adds a &lt;link&gt; to a CSS file with
     * the specified name and media; the name is resolved relative to the current
     * application class.
     * For example:
     * <pre class="example">
     * add(cssResource("application.css", "screen"));</pre>
     * <p>
     * will add a {@code <link media="screen">} to the &lt;head&gt; for {@code application.css},
     * found in the same classpath location as your wicket application class.
     * <p>
     * This is equivalent to overriding {@code renderHead()} with:
     * <pre class="example">
     * response.renderCSSReference(
     *     new PackageResourceReference(Application.get().getClass(), "application.css"),
     *     "screen"
     * );</pre>
     * 
     * @since 3.0
     */
    public static Behavior cssResource(String filename, String media) {
        return cssResource(Application.get().getClass(), filename, media);
    }

    /**
     * Creates a header contributor that adds a &lt;link&gt; to a CSS file with
     * the specified name, which resolved relative to the given class.
     * For example:
     * <pre class="example">
     * add(cssResource(BasePage.class, "application.css"));</pre>
     * <p>
     * will add a &lt;link&gt; to the &lt;head&gt; for {@code application.css},
     * found in the same classpath location as BasePage.
     * <p>
     * This is equivalent to overriding {@code renderHead()} with:
     * <pre class="example">
     * response.renderCSSReference(
     *     new PackageResourceReference(BasePage.class, "application.css")
     * );</pre>
     * 
     * @since 2.0
     */
    public static Behavior cssResource(Class<?> scope, String filename) {
        return cssResource(scope, filename, null);
    }

    /**
     * Creates a header contributor that adds a &lt;link&gt; to a CSS file with
     * the specified name and media; the name is resolved relative to the given class.
     * For example:
     * <pre class="example">
     * add(cssResource(BasePage.class, "application.css", "screen"));</pre>
     * <p>
     * will add a {@code <link media="screen">} to the &lt;head&gt; for {@code application.css},
     * found in the same classpath location as BasePage.
     * <p>
     * This is equivalent to overriding {@code renderHead()} with:
     * <pre class="example">
     * response.renderCSSReference(
     *     new PackageResourceReference(BasePage.class, "application.css"),
     *     "screen"
     * );</pre>
     * 
     * @since 3.0
     */
    public static Behavior cssResource(final Class<?> scope, final String filename, final String media) {
        Args.notNull(scope, "scope");
        Args.notNull(filename, "filename");
        return new Behavior() {
            @Override
            public void renderHead(Component comp, IHeaderResponse response) {
                response.renderCSSReference(new PackageResourceReference(scope, filename), media);
            }
        };
    }

    /**
     * Creates a header contributor that adds a &lt;link&gt; to a print
     * stylesheet CSS file with the specified name, relative to the current
     * application class.
     * For example:
     * <pre class="example">
     * add(cssPrintResource("print.css"));</pre>
     * <p>
     * will add a &lt;link&gt; to the &lt;head&gt; for {@code print.css},
     * found in the same classpath location as your wicket application class.
     * The &lt;link&gt; will have a print media type.
     * <p>
     * This is equivalent to overriding {@code renderHead()} with:
     * <pre class="example">
     * response.renderCSSReference(
     *     new PackageResourceReference(Application.get().getClass(), filename),
     *     "print"
     * );</pre>
     * 
     * @since 2.0
     */
    public static Behavior cssPrintResource(final String filename) {
        return cssResource(filename, "print");
    }

    /**
     * Creates a header contributor that adds a &lt;link&gt; to an
     * Internet Explorer conditional stylesheet CSS file with
     * the specified name, relative to the current application class.
     * The stylesheet will apply based on the IE condition argument.
     * For example:
     * <pre class="example">
     * add(cssConditionalResource("IE 7", "ie-7.css"));</pre>
     * <p>
     * will add a &lt;link&gt; to the &lt;head&gt; for {@code ie-7.css},
     * found in the same classpath location as your wicket application class.
     * The stylesheet will only be loaded in IE 7 browsers.
     * <p>
     * This is equivalent to:
     * <pre class="example">
     * InternetExplorerCss.getConditionalHeaderContribution(
     *     "IE 7"
     *     new PackageResourceReference(
     *         Application.get().getClass(), "ie-7.css")
     *     )
     * );</pre>
     * 
     * @since 2.0
     */
    public static Behavior cssConditionalResource(String cond, String filename) {
        Args.notNull(cond, "cond");
        Args.notNull(filename, "filename");
        return InternetExplorerCss.getConditionalHeaderContribution(cond,
                new PackageResourceReference(Application.get().getClass(), filename));
    }

    /**
     * Return true if the object is null, zero-length (if array or collection)
     * false (if a boolean) or blank (if a string). Examples:
     * <table>
     *   <tr><th>Object</th><th>Empty?</th></tr>
     *   <tr><td><code>"foo"</code></td><td><code>false</code></td></tr>
     *   <tr><td><code>""</code></td><td><code>true</code></td></tr>
     *   <tr><td><code>"   "</code></td><td><code>true</code></td></tr>
     *   <tr><td><code>null</code></td><td><code>true</code></td></tr>
     *   <tr><td><code>Collections.EMPTY_LIST</code></td><td><code>true</code></td></tr>
     *   <tr><td><code>Collections.singletonList("foo")</code></td><td><code>false</code></td></tr>
     *   <tr><td><code>new String[0]</code></td><td><code>true</code></td></tr>
     *   <tr><td><code>new String[] { "foo" }</code></td><td><code>false</code></td></tr>
     *   <tr><td><code>true</code></td><td><code>false</code></td></tr>
     *   <tr><td><code>false</code></td><td><code>true</code></td></tr>
     * </table>
     */
    public static boolean empty(Object obj) {
        boolean empty = false;
        if (null == obj) {
            empty = true;
        } else if (obj instanceof Boolean) {
            empty = !(Boolean) obj;
        } else if (obj instanceof Collection) {
            empty = ((Collection) obj).size() == 0;
        } else if (obj instanceof String) {
            empty = Strings.isEmpty((String) obj);
        } else if (obj.getClass().isArray()) {
            empty = ((Object[]) obj).length == 0;
        }
        return empty;
    }

    /**
     * This class cannot be instantiated or subclassed.
     */
    private Shortcuts() {
    }
}