/** * Copyright 2014 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) { * @Override protected void populateItem(ListItem item) * { * if(item.getIndex() < 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"> * <span wicket:id="message" class="severe">blah blah</span></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"> * <span class="severe">...</pre> * If {@code false}, then: * <pre class="example"> * <span class="">...</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"> * <span wicket:id="message" class="high-priority">blah blah</span></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"> * <span class="high-priority">...</pre> * If {@code false}, then: * <pre class="example"> * <span class="low-priority">...</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 <link> 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 <link> to the <head> 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 <link> 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 <link> to the <head> 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 <link> 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 <head> 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 <link> 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 <link> to the <head> 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 <link> 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 <head> 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 <link> 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 <link> to the <head> for {@code print.css}, * found in the same classpath location as your wicket application class. * The <link> 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 <link> 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 <link> to the <head> 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() { } }