/* * Copyright 2011 Eric F. Savage, code@efsavage.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 com.ajah.spring.mvc.servlet.tag; import java.io.IOException; import java.lang.reflect.Field; import java.util.Enumeration; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.Tag; import javax.validation.constraints.NotNull; import lombok.extern.java.Log; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import com.ajah.crypto.Password; import com.ajah.html.dtd.ButtonType; import com.ajah.html.dtd.FormMethod; import com.ajah.html.dtd.InputType; import com.ajah.html.element.Button; import com.ajah.html.element.Checkbox; import com.ajah.html.element.Div; import com.ajah.html.element.Form; import com.ajah.html.element.Input; import com.ajah.html.element.InputImpl; import com.ajah.html.element.Italic; import com.ajah.html.element.ListItem; import com.ajah.html.element.Option; import com.ajah.html.element.Script; import com.ajah.html.element.Select; import com.ajah.html.element.TextArea; import com.ajah.html.element.UnorderedList; import com.ajah.spring.mvc.form.AutoForm; import com.ajah.spring.mvc.form.AutoFormUtils; import com.ajah.spring.mvc.form.HtmlText; import com.ajah.spring.mvc.form.Icon; import com.ajah.spring.mvc.form.LongText; import com.ajah.spring.mvc.form.Submit; import com.ajah.spring.mvc.form.validation.Match; import com.ajah.util.AjahUtils; import com.ajah.util.Identifiable; import com.ajah.util.StringUtils; import com.ajah.util.data.format.EmailAddress; import com.ajah.util.reflect.IntrospectionUtils; /** * Renders an AutoForm. * * @author Eric F. Savage <code@efsavage.com> * */ @Log public class AutoFormTag extends SpringTag { /** * Finds a field by name. * * @param target * The name of the field to target. * @param allFields * The list of fields to search across. * @return Matching field, if found, otherwise null. */ private static Field findField(final String target, final Field[] allFields) { for (final Field field : allFields) { if (field.getName().equals(target)) { return field; } } return null; } private Object autoForm; private boolean compact = false; /** * {@inheritDoc} */ @Override public int doStartTag() throws JspException { if (this.autoForm == null) { setAutoForm(this.pageContext.getRequest().getAttribute("ajahAutoForm")); } AjahUtils.requireParam(this.autoForm, "autoForm"); try (JspWriter out = this.pageContext.getOut()) { final Div div = new Div().css("asm-auto"); final Enumeration<String> attributes = this.pageContext.getRequest().getAttributeNames(); while (attributes.hasMoreElements()) { final String attribute = attributes.nextElement(); if (attribute.startsWith("org.springframework.validation.BindingResult.")) { final BindingResult result = (BindingResult) this.pageContext.getRequest().getAttribute(attribute); log.fine("Found " + result.getErrorCount() + " global errors"); if (result.getErrorCount() > 0) { final Div alertBox = div.add(new Div().css("alert").css("alert-error")); final UnorderedList errs = alertBox.add(new UnorderedList().css("asm-err")); for (final ObjectError error : result.getAllErrors()) { errs.add(new ListItem(getMessage(error))); } } } } final Form form = new Form(FormMethod.POST).css("well").css("asm-auto"); if (!StringUtils.isBlank(getId())) { form.setId(getId()); } for (final Field field : this.autoForm.getClass().getFields()) { log.fine(field.getName() + " has a value of " + field.get(this.autoForm)); final Input<?> input = getInput(field, this.autoForm.getClass().getFields()); form.getInputs().add(input); } String submitText = null; Icon iconLeft = null; Icon iconRight = null; if (this.autoForm.getClass().isAnnotationPresent(Submit.class)) { final Submit submit = this.autoForm.getClass().getAnnotation(Submit.class); submitText = submit.value(); if (submit.iconLeft() != null && submit.iconLeft() != Icon.NONE) { iconLeft = submit.iconLeft(); } if (submit.iconRight() != null && submit.iconRight() != Icon.NONE) { iconRight = submit.iconRight(); } } if (StringUtils.isBlank(submitText)) { submitText = StringUtils.capitalize(StringUtils.splitCamelCase(this.autoForm.getClass().getSimpleName().replaceAll("Form$", ""))); } final Button submitButton = new Button().text(submitText).type(ButtonType.SUBMIT).css("btn").css("btn-primary"); if (iconLeft != null) { submitButton.addBeforeText(new Italic().css(iconLeft.getBootstrapClass())); } if (iconRight != null) { submitButton.add(new Italic().css(iconRight.getBootstrapClass())); } form.getInputs().add(submitButton); div.add(form); final Script script = new Script(); final StringBuffer code = new StringBuffer(); code.append("\n$(document).ready(function() {\n"); for (final Input<?> child : form.getInputs()) { if (child instanceof TextArea && ((TextArea) child).isHtml()) { this.pageContext.getRequest().setAttribute("jjScriptHtmlEditor", Boolean.TRUE); code.append("\t$(\".rich-text" + "\").cleditor({width:\"95%\", height:\"100%\", controls: \"style bold italic strikethrough | bullets numbering | outdent indent | rule image link unlink | removeformat source\"});\n"); break; } } code.append("\t$(\"#" + form.getInputs().get(0).getId() + "\").focus();\n"); code.append("});\n"); script.setText(code.toString()); div.add(script); div.render(out, isCompact() ? -1 : 0); } catch (final IOException e) { throw new JspException(e); } catch (final IllegalArgumentException e) { throw new JspException(e); } catch (final IllegalAccessException e) { throw new JspException(e); } return Tag.EVAL_PAGE; } /** * Returns the autoForm for this tag to render. * * @return The autoForm for this tag to render. May be null. */ public Object getAutoForm() { return this.autoForm; } private Input<?> getInput(final Field field, final Field[] allFields) throws IllegalArgumentException, IllegalAccessException { final String label = AutoFormUtils.getLabel(field); Input<?> input = null; log.fine(field.getType().toString()); // TODO handle type="email" and such http://diveintohtml5.org/forms.html if (field.getType().equals(String.class)) { if (field.isAnnotationPresent(HtmlText.class)) { // An HTML-enabled textarea input input = new TextArea(label, field.getName(), (String) field.get(this.autoForm), AutoFormUtils.getInputType(field), true); } else if (field.isAnnotationPresent(LongText.class)) { // A textarea input input = new TextArea(label, field.getName(), (String) field.get(this.autoForm), AutoFormUtils.getInputType(field), false); } else { // A normal text input input = new InputImpl(label, field.getName(), (String) field.get(this.autoForm), AutoFormUtils.getInputType(field)); } } else if (field.getType().equals(EmailAddress.class)) { // An email input input = new InputImpl(label, field.getName(), StringUtils.safeToString(field.get(this.autoForm)), AutoFormUtils.getInputType(field)); } else if (field.getType().isAssignableFrom(Password.class)) { // A password input input = new InputImpl(label, field.getName(), "", InputType.PASSWORD); } else if (field.getType().equals(Boolean.class) || field.getType().equals(boolean.class)) { // A checkbox input input = new Checkbox(label, field.getName(), "true", field.getBoolean(this.autoForm)); } else if (field.getType().equals(Long.class) || field.getType().equals(long.class) || field.getType().equals(Integer.class) || field.getType().equals(int.class)) { // An integer input input = new InputImpl(label, field.getName(), StringUtils.safeToString(field.get(this.autoForm)), AutoFormUtils.getInputType(field)); } else if (field.getType().equals(Double.class)) { // An floating point input input = new InputImpl(label, field.getName(), StringUtils.safeToString(field.get(this.autoForm)), AutoFormUtils.getInputType(field)); } else if (IntrospectionUtils.isIdentifiableEnum(field)) { final Select select = new Select(label, field.getName()); final Identifiable<?>[] options = (Identifiable<?>[]) field.getType().getEnumConstants(); for (final Identifiable<?> enumOption : options) { log.fine(enumOption.getId().toString() + " / " + enumOption.toString()); final Option option = new Option(enumOption.getId().toString(), AutoFormUtils.getLabel(field, enumOption)); select.add(option); } input = select; } else if (IntrospectionUtils.isToStringable(field) && IntrospectionUtils.isFromStringable(field)) { log.finest("label: " + label); input = new InputImpl(label, field.getName(), StringUtils.safeToString(field.get(this.autoForm)), AutoFormUtils.getInputType(field)); } else { throw new IllegalArgumentException(field.getType().getName() + " not supported"); } if (field.isAnnotationPresent(NotNull.class)) { log.fine("NotNull is present on " + field.getName()); input.css("required"); } else { log.fine("NotNull is NOT present on " + field.getName()); } if (field.isAnnotationPresent(Match.class)) { log.fine("Match is present on " + field.getName()); input.data("match", field.getAnnotation(Match.class).value()); final Field target = findField(field.getAnnotation(Match.class).value(), allFields); if (target == null) { throw new IllegalArgumentException("No field called " + field.getAnnotation(Match.class).value() + " found to match on"); } input.data("match-name", AutoFormUtils.getLabel(target)); } else { log.fine("Match is NOT present on " + field.getName()); } return input; } /** * Denotes if whitespace should be added for readability. * * @return true if whitespace should be removed (default), otherwise false */ public boolean isCompact() { return this.compact; } /** * Sets the autoForm for this tag to render. * * @param autoForm * The autoForm for this tag to render, required. */ public void setAutoForm(final Object autoForm) { AjahUtils.requireParam(autoForm, "autoForm"); if (autoForm.getClass().isAnnotationPresent(AutoForm.class)) { this.autoForm = autoForm; } else { throw new IllegalArgumentException(autoForm.getClass().getName() + " does not have " + AutoForm.class.getName() + " annotation"); } } /** * Denotes if whitespace should be added for readability. * * @param compact * true if whitespace should be removed, otherwise false */ public void setCompact(final boolean compact) { this.compact = compact; } }