org.jbpm.formModeler.core.processing.formRendering.FormRenderingFormatter.java Source code

Java tutorial

Introduction

Here is the source code for org.jbpm.formModeler.core.processing.formRendering.FormRenderingFormatter.java

Source

/**
 * Copyright (C) 2012 JBoss Inc
 *
 * 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 org.jbpm.formModeler.core.processing.formRendering;

import org.jbpm.formModeler.core.processing.formProcessing.NamespaceManager;
import org.slf4j.Logger;
import org.jbpm.formModeler.api.model.DataHolder;
import org.jbpm.formModeler.core.config.FormManager;
import org.jbpm.formModeler.core.FieldHandlersManager;
import org.jbpm.formModeler.core.FormCoreServices;
import org.jbpm.formModeler.core.processing.FormProcessingServices;
import org.jbpm.formModeler.service.annotation.config.Config;
import org.jbpm.formModeler.service.bb.mvc.taglib.formatter.Formatter;
import org.jbpm.formModeler.service.bb.mvc.components.handling.MessagesComponentHandler;
import org.jbpm.formModeler.service.bb.mvc.taglib.formatter.FormatterException;
import org.jbpm.formModeler.api.model.Field;
import org.jbpm.formModeler.api.model.Form;
import org.jbpm.formModeler.api.model.FormDisplayInfo;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.jbpm.formModeler.core.processing.FieldHandler;
import org.jbpm.formModeler.core.processing.FormProcessor;
import org.jbpm.formModeler.core.processing.FormStatusData;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.math.BigInteger;
import java.util.*;

/**
 * Renders a form.
 */
@Named("FormRenderingFormatter")
public class FormRenderingFormatter extends Formatter {

    public static final String ATTR_FIELD = "_ddm_currentField";
    public static final String ATTR_NAMESPACE = "_ddm_currentNamespace";
    public static final String ATTR_VALUE = "_ddm_currentValue";
    public static final String ATTR_INPUT_VALUE = "_ddm_currentInputValue";
    public static final String ATTR_NAME = "_ddm_currentName";
    public static final String ATTR_FIELD_IS_WRONG = "_ddm_currentFieldIsWrong";
    public static final String ATTR_FORM_RENDER_MODE = "_ddm_current_renderMode";
    public static final String ATTR_FIELD_IS_READONLY = "_ddm_fieldIsReadonly";
    public static final String FIELD_CONTAINER_STYLE = "padding-top: 3px; padding-right:3px;";

    private Logger log = LoggerFactory.getLogger(FormRenderingFormatter.class);

    @Inject
    private FormErrorMessageBuilder formErrorMessageBuilder;

    @Inject
    protected FieldI18nResourceObtainer fieldI18nResourceObtainer;

    @Inject
    protected NamespaceManager namespaceManager;

    @Inject
    @Config("/formModeler/defaultFormErrors.jsp")
    private String errorsPage;

    private String[] formModes = new String[] { Form.RENDER_MODE_FORM, Form.RENDER_MODE_WYSIWYG_FORM };
    private String[] displayModes = new String[] { Form.RENDER_MODE_DISPLAY, Form.RENDER_MODE_WYSIWYG_DISPLAY };

    protected transient Form formToPaint;
    protected transient String namespace;
    protected transient String renderMode;
    protected transient Boolean isReadonly = Boolean.FALSE;
    protected transient FormStatusData formStatusData;

    public FormManager getFormManager() {
        return FormCoreServices.lookup().getFormManager();
    }

    public FormProcessor getFormProcessor() {
        return FormProcessingServices.lookup().getFormProcessor();
    }

    public FieldHandlersManager getFieldHandlersManager() {
        return FormProcessingServices.lookup().getFieldHandlersManager();
    }

    public void service(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
            throws FormatterException {
        Object formObject = getParameter("form");

        if (formObject != null)
            formToPaint = (Form) formObject;
        else {
            log.error("Form not found");
            return;
        }

        renderMode = (String) getParameter("renderMode"); //Default is form
        String labelMode = (String) getParameter("labelMode"); //Default is before;
        String reusingStatus = (String) getParameter("reuseStatus"); //Default is true;
        String forceLabelModeParam = (String) getParameter("forceLabelMode"); //Default is false
        String displayModeParam = (String) getParameter("displayMode");
        String subForm = (String) getParameter("isSubForm");
        String multiple = (String) getParameter("isMultiple");

        Boolean readonly = (Boolean) getParameter("isReadonly");

        if (readonly != null)
            isReadonly = readonly;

        boolean isSubForm = subForm != null && Boolean.valueOf(subForm).booleanValue();
        boolean isMultiple = multiple != null && Boolean.valueOf(multiple).booleanValue();

        namespace = (String) getParameter("namespace");

        if (StringUtils.isEmpty(namespace)) {
            log.warn(
                    "Empty namespace is no longer permitted. Will use a default namespace value, for backwards compatibility",
                    new Exception());
            namespace = FormProcessor.DEFAULT_NAMESPACE;
        } else if (!Character.isJavaIdentifierStart(namespace.charAt(0))) {
            log.warn("Namespace " + namespace
                    + " starts with an illegal character. It may cause unexpected behaviour of form under IE.");
        }
        Object formValues = getParameter("formValues");
        if (formValues != null) {
            getFormProcessor().clear(formToPaint, namespace);
            getFormProcessor().read(formToPaint, namespace, (Map) formValues);
        }
        // Default render mode is FORM
        renderMode = renderMode == null ? Form.RENDER_MODE_FORM : renderMode;

        //Default label mode depends on render mode
        labelMode = labelMode == null ? Form.LABEL_MODE_BEFORE : labelMode;
        //if (Form.RENDER_MODE_DISPLAY.equals(renderMode)) {
        //    labelMode = Form.LABEL_MODE_HIDDEN;
        //}

        boolean reuseStatus = reusingStatus == null || Boolean.valueOf(reusingStatus).booleanValue();
        boolean forceLabelMode = forceLabelModeParam != null && Boolean.valueOf(forceLabelModeParam).booleanValue();
        namespace = namespace == null ? "" : namespace;

        try {
            if (log.isDebugEnabled()) {
                log.debug("Printing form " + formToPaint.getId() + ". Mode: " + renderMode + ".");
            }
            String formLabelMode = formToPaint.getLabelMode();
            if (formLabelMode != null && !"".equals(formLabelMode)
                    && !Form.LABEL_MODE_UNDEFINED.equals(formLabelMode)) {
                if (!forceLabelMode)
                    labelMode = formLabelMode;
            }

            if (formValues != null) {
                getFormProcessor().clear(formToPaint, namespace);
                formStatusData = getFormProcessor().read(formToPaint, namespace, (Map) formValues);
            } else {
                formStatusData = getFormProcessor().read(formToPaint, namespace);
            }

            String displayMode = formToPaint.getDisplayMode();
            if (displayModeParam != null)
                displayMode = displayModeParam;
            FormDisplayInfo displayInfo = null;
            if (displayMode != null) {
                for (Iterator it = formToPaint.getFormDisplayInfos().iterator(); it.hasNext();) {
                    FormDisplayInfo info = (FormDisplayInfo) it.next();
                    if (info.getDisplayMode().equals(displayMode)) {
                        displayInfo = info;
                        break;
                    }
                }
            }

            if (log.isDebugEnabled())
                log.debug("About to display form " + formToPaint.getId() + " in namespace " + namespace);
            display(formToPaint, namespace, displayMode, displayInfo, renderMode, labelMode, isSubForm, isMultiple);

        } catch (Exception e) {
            log.error("Error:", e);
            throw new FormatterException("Error", e);
        }
    }

    protected void setFormFieldErrors(String namespace, Form form) {
        MessagesComponentHandler messagesComponentHandler = MessagesComponentHandler.lookup();
        if (namespace != null && form != null) {
            try {
                messagesComponentHandler.getErrorsToDisplay()
                        .addAll(formErrorMessageBuilder.getWrongFormErrors(namespace, form));
            } catch (Exception e) {
                log.error("Error getting error messages for object " + form.getId() + ": ", e);
            }
        }
    }

    protected void display(Form form, String namespace, String displayMode, FormDisplayInfo displayInfo,
            String renderMode, String labelMode, boolean isSubForm, boolean isMultiple) {

        if (!isSubForm || (isSubForm && isMultiple)) {
            setFormFieldErrors(namespace, form);
            includePage(errorsPage);
        }
        if (displayMode == null) {
            defaultDisplay(form, namespace, renderMode, labelMode, Form.DISPLAY_MODE_DEFAULT);
        } else {
            if (Form.DISPLAY_MODE_DEFAULT.equals(displayMode)) {
                defaultDisplay(form, namespace, renderMode, labelMode, Form.DISPLAY_MODE_DEFAULT);
            } else if (Form.DISPLAY_MODE_ALIGNED.equals(displayMode)) {
                defaultDisplay(form, namespace, renderMode, labelMode, Form.DISPLAY_MODE_ALIGNED);
            } else if (Form.DISPLAY_MODE_NONE.equals(displayMode)) {
                defaultDisplay(form, namespace, renderMode, labelMode, Form.DISPLAY_MODE_NONE);
            } else if (Form.DISPLAY_MODE_TEMPLATE.equals(displayMode)) {
                templateDisplay(form, namespace, renderMode);
            } else {
                log.error("Unsupported display mode.");
            }
        }
    }

    public void afterRendering(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
            throws FormatterException {
        super.afterRendering(httpServletRequest, httpServletResponse);

        // If form was used just to show something, clear the form status.
        if (Form.RENDER_MODE_DISPLAY.equals(renderMode) || Form.RENDER_MODE_TEMPLATE_EDIT.equals(renderMode)) {
            if (formToPaint != null) {
                getFormProcessor().clear(formToPaint, namespace);
            }
        }
    }

    protected void templateDisplay(final Form form, final String namespace, final String renderMode) {
        List renderingInstructions = FormProcessingServices.lookup().getFormTemplateHelper()
                .getRenderingInstructions(form.getFormTemplate());
        FormRenderer renderer = new FormRenderer() {
            public void writeToOut(String text) {
                FormRenderingFormatter.this.writeToOut(text);
            }

            public void renderField(String fieldName) {
                Field field = form.getField(fieldName);
                if (field != null) {
                    //setAttribute("field", field.getForm().getFormFields().iterator().next());
                    setAttribute("field", field);
                    setAttribute("hideMotionButtons", Boolean.TRUE);
                    FormRenderingFormatter.this.renderFragment("beforeFieldInTemplateMode");
                    FormRenderingFormatter.this.renderField(field, namespace, renderMode);
                    FormRenderingFormatter.this.renderFragment("afterFieldInTemplateMode");
                } else {
                    setAttribute("fieldName", fieldName);
                    setAttribute("hideMotionButtons", Boolean.TRUE);
                    FormRenderingFormatter.this.renderFragment("beforeFieldInTemplateMode");
                    FormRenderingFormatter.this.writeToOut(Form.TEMPLATE_FIELD + "{" + fieldName + "}");
                    FormRenderingFormatter.this.renderFragment("afterFieldInTemplateMode");
                }
            }

            public void renderLabel(String fieldName) {
                Field field = form.getField(fieldName);
                if (field != null) {
                    FormRenderingFormatter.this.renderFragment("beforeLabelInTemplateMode");
                    //FormRenderingFormatter.this.renderLabel(
                    //        field.getForm().getFormFields().iterator().next(),
                    //        namespace,
                    //        renderMode
                    //);
                    FormRenderingFormatter.this.renderLabel(field, namespace, renderMode);
                    FormRenderingFormatter.this.renderFragment("afterLabelInTemplateMode");
                } else {
                    FormRenderingFormatter.this.renderFragment("beforeLabelInTemplateMode");
                    FormRenderingFormatter.this.writeToOut(Form.TEMPLATE_LABEL + "{" + fieldName + "}");
                    FormRenderingFormatter.this.renderFragment("afterLabelInTemplateMode");
                }
            }
        };
        for (int i = 0; i < renderingInstructions.size(); i++) {
            TemplateRenderingInstruction instruction = (TemplateRenderingInstruction) renderingInstructions.get(i);
            instruction.doRender(renderer);
        }
        displayFooter(form);
    }

    protected void renderField(Field field, String namespace, String renderMode) {
        beforeRenderField(field, namespace, renderMode);
        boolean fieldHasErrors = formStatusData.getWrongFields().contains(field.getFieldName());
        String renderPage = "";
        FieldHandler fieldHandler = getFieldHandlersManager().getHandler(field.getFieldType());

        if (Arrays.asList(formModes).contains(renderMode)) {
            renderPage = fieldHandler.getPageToIncludeForRendering();
        } else if (Arrays.asList(displayModes).contains(renderMode)) {
            renderPage = fieldHandler.getPageToIncludeForDisplaying();
        }
        if (!"".equals(renderPage)) {
            Boolean fieldIsRequired = field.getFieldRequired();
            boolean fieldRequired = fieldIsRequired != null && fieldIsRequired.booleanValue();
            if (fieldHasErrors)
                renderFragment("beforeWrongField");
            if (fieldRequired)
                renderFragment("beforeRequiredField");
            Object value = formStatusData.getCurrentValue(field.getFieldName());
            boolean isStringType = String.class.getName().equals(field.getFieldType().getFieldClass());
            if (value == null && isStringType) {
                Map currentInputValues = formStatusData.getCurrentInputValues();
                String[] values = null;
                if (currentInputValues != null) {
                    values = ((String[]) currentInputValues.get(namespace + FormProcessor.NAMESPACE_SEPARATOR
                            + field.getForm().getId() + FormProcessor.NAMESPACE_SEPARATOR + field.getFieldName()));
                }
                value = values != null && values.length > 0 ? values[0] : value;
            }

            setRenderingAttributes(field, namespace, value, formStatusData, fieldHasErrors);
            // If disabled and/or readonly parameters were received from a subformformatter, pass them on to the included
            // fields (only relevant when they're set to true)
            if (isReadonly)
                setAttribute(ATTR_FIELD_IS_READONLY, isReadonly);
            includePage(renderPage);
            if (fieldRequired)
                renderFragment("afterRequiredField");
            if (fieldHasErrors)
                renderFragment("afterWrongField");
        } else {
            if (Form.RENDER_MODE_TEMPLATE_EDIT.equals(renderMode)) {
                writeToOut(Form.TEMPLATE_FIELD + "{" + field.getFieldName() + "}");
            } else
                log.warn("Invalid render mode " + renderMode);
        }
        afterRenderField(field, namespace, renderMode);
    }

    protected void beforeRenderField(Field field, String namespace, String renderMode) {
        String uid = namespaceManager.generateSquashedInputName(namespace, field);
        String fieldTypeCss = field.getFieldType().getCssStyle();
        String fieldCss = field.getCssStyle();
        Object overridenValue = getFormProcessor().getAttribute(field.getForm(), namespace,
                field.getFieldName() + ".cssStyle");
        String css = fieldTypeCss;
        if (!StringUtils.isEmpty(fieldCss)) {
            css = fieldCss;
        }
        if (overridenValue != null) {
            css = (String) overridenValue;
        }
        css = StringUtils.defaultString(css);
        css = StringUtils.remove(css, ' ');
        String styleToWrite = FIELD_CONTAINER_STYLE;
        StringTokenizer strtk = new StringTokenizer(css, ";");
        while (strtk.hasMoreTokens()) {
            String tk = strtk.nextToken();
            if ("display:none".equals(tk)) {
                styleToWrite = tk;
                break;
            }
        }
        writeToOut("<div style=\"" + styleToWrite + "\" id=\"" + uid + "_container\">");
    }

    protected void afterRenderField(Field field, String namespace, String renderMode) {
        writeToOut("</div>");
    }

    protected void renderLabel(Field field, String namespace, String renderMode) {
        beforeRenderLabel(field, namespace, renderMode);
        String inputId = namespaceManager.generateSquashedInputName(namespace, field);
        String labelCssStyle = null;
        String labelCssClass = null;
        try {
            labelCssStyle = field.getLabelCSSStyle();
            labelCssClass = field.getLabelCSSClass();
            //Check if label style was overriden by formulas.
            Object style = getFormProcessor().getAttribute(field.getForm(), namespace,
                    field.getFieldName() + ".labelCSSStyle");
            if (style != null)
                labelCssStyle = style.toString();

        } catch (Exception e) {
            log.error("Error: ", e);
        }

        if (Form.RENDER_MODE_TEMPLATE_EDIT.equals(renderMode)) {
            writeToOut(Form.TEMPLATE_LABEL + "{" + field.getFieldName() + "}");
        } else {
            boolean fieldHasErrors = formStatusData.getWrongFields().contains(field.getFieldName());
            String label = fieldI18nResourceObtainer.getFieldLabel(field);
            Boolean fieldIsRequired = field.getFieldRequired();
            boolean fieldRequired = fieldIsRequired != null && fieldIsRequired.booleanValue()
                    && !Form.RENDER_MODE_DISPLAY.equals(fieldIsRequired);

            String labelValue = StringEscapeUtils.escapeHtml4(StringUtils.defaultString(label));

            writeToOut("<span id=\"" + inputId + "_label\"");
            writeToOut(" class='dynInputStyle " + StringUtils.defaultString(labelCssClass) + "' ");
            if (labelCssStyle != null)
                writeToOut(" style='" + labelCssStyle + "' ");
            writeToOut(" >");

            if (fieldHasErrors)
                writeToOut("<span class=\"skn-error\">");
            if (!StringUtils.isEmpty(inputId) && !StringUtils.isEmpty(labelValue)
                    && !Form.RENDER_MODE_DISPLAY.equals(renderMode))
                writeToOut("<label for=\"" + StringEscapeUtils.escapeHtml4(inputId) + "\">");
            if (fieldRequired)
                writeToOut("*");
            writeToOut(labelValue);
            if (!StringUtils.isEmpty(inputId) && !StringUtils.isEmpty(labelValue)
                    && !Form.RENDER_MODE_DISPLAY.equals(renderMode))
                writeToOut("</label>");
            if (fieldRequired)
                renderFragment("afterRequiredLabel");
            if (fieldHasErrors)
                writeToOut("</span>");

            writeToOut("</span>");

        }
        afterRenderLabel(field, namespace, renderMode);
    }

    protected void beforeRenderLabel(Field field, String namespace, String renderMode) {
        String uid = namespaceManager.generateSquashedInputName(namespace, field);

        String fieldCss = field.getLabelCSSStyle();
        Object overridenValue = getFormProcessor().getAttribute(field.getForm(), namespace,
                field.getFieldName() + ".labelCSSStyle");
        String css = fieldCss;
        if (overridenValue != null) {
            css = (String) overridenValue;
        }
        css = StringUtils.defaultString(css);
        css = StringUtils.remove(css, ' ');
        String styleToWrite = FIELD_CONTAINER_STYLE;
        StringTokenizer strtk = new StringTokenizer(css, ";");
        while (strtk.hasMoreTokens()) {
            String tk = strtk.nextToken();
            if ("display:none".equals(tk)) {
                styleToWrite = tk;
                break;
            }
        }
        writeToOut("<div style=\"" + styleToWrite + "\" id=\"" + uid + "_label_container\">");
    }

    protected void afterRenderLabel(Field field, String namespace, String renderMode) {
        writeToOut("</div>");
    }

    /**
     * Default display. One field after each other
     *
     * @param form
     * @param renderMode
     */
    protected void defaultDisplay(Form form, String namespace, String renderMode, String labelMode, String mode) {
        Set<Field> fields = form.getFormFields();
        List<Field> sortedFields = new ArrayList(fields);
        Collections.sort(sortedFields, new Field.Comparator());
        FormStatusData formStatusData = getFormProcessor().read(form, namespace);

        setAttribute("width", deduceWidthForForm(form, renderMode, labelMode, mode));
        renderFragment("outputStart");
        renderFragment("formHeader");
        /*Calculate colspans*/
        List colspans = new ArrayList();
        List fieldGroups = new ArrayList();
        fieldGroups.add(new ArrayList());
        for (Field field : sortedFields) {
            List currentList = (List) fieldGroups.get(fieldGroups.size() - 1);
            if (!Boolean.TRUE.equals(field.getGroupWithPrevious())) {
                fieldGroups.add(currentList = new ArrayList());
            }
            currentList.add(field);
        }
        for (int i = 0; i < fieldGroups.size(); i++) {
            List list = (List) fieldGroups.get(i);
            if (!list.isEmpty())
                colspans.add(new BigInteger(String.valueOf(list.size())));
        }

        BigInteger mcm = calculateMCM(colspans);
        BigInteger max = calculateMax(colspans);

        /*Render fields with colspans*/
        List groupList = new ArrayList();

        boolean first = true;

        for (int i = 0; i < sortedFields.size(); i++) {
            Field field = sortedFields.get(i);
            groupList.add(field);
            if (i < sortedFields.size() - 1) {
                Field nextField = sortedFields.get(i + 1);
                if (nextField.getGroupWithPrevious() != null && nextField.getGroupWithPrevious().booleanValue()) {
                    continue;
                }
            }
            if (i > 0 && Form.DISPLAY_MODE_NONE.equals(mode)) {
                renderFragment("outputEnd");
                setAttribute("width", deduceWidthForForm(form, renderMode, labelMode, mode));
                renderFragment("outputStart");
            }
            defaultDisplayGroup(form, groupList, mcm.intValue(), max.intValue(), renderMode, labelMode,
                    formStatusData, mode, namespace, i, first);
            groupList.clear();
            first = false;
        }

        displayFooter(form);
        renderFragment("outputEnd");
    }

    protected void displayFooter(Form form) {
        String displayMode = form.getDisplayMode();
        if (Form.RENDER_MODE_FORM.equals(renderMode)) {
            String formRefresherFieldName = namespaceManager.generateFieldNamesPace(namespace, form,
                    ":initialFormRefresher");
            setAttribute("name", formRefresherFieldName);
            setAttribute("uid", formRefresherFieldName);
            if (Form.DISPLAY_MODE_TEMPLATE.equals(displayMode)) {
                includePage("/formModeler/defaultFormFooter.jsp");
            } else {
                renderFragment("formFooter");
            }
        }
    }

    /**
     * Deduce width for a form.
     * @return Deduced width for a form.
     */
    protected String deduceWidthForForm(Form form, String renderMode, String labelMode, String mode) {
        if (Form.DISPLAY_MODE_TEMPLATE.equals(mode))
            return null; //In these modes, it doesn't matter
        if (Form.RENDER_MODE_DISPLAY.equals(renderMode)) { //Showing data
            if (Form.DISPLAY_MODE_NONE.equals(mode)) {
                return "";
            } else {
                return "100%";
            }
        } else { //Entering data
            return "1%";
        }
    }

    protected void defaultDisplayGroup(Form form, List groupMembers, int maxCols, int maxMembers, String renderMode,
            String labelMode, FormStatusData formStatusData, String mode, String namespace, int position,
            boolean first) {
        int fieldColspan = maxCols / groupMembers.size();
        int fieldWidth = (100 * fieldColspan) / maxCols;
        if (Form.DISPLAY_MODE_ALIGNED.equals(mode)) {
            fieldColspan = maxCols / maxMembers;
        }
        if (Form.DISPLAY_MODE_NONE.equals(mode)) {
            fieldColspan = 1;
        }
        setAttribute("groupPosition", position);
        setAttribute("field", groupMembers.get(0));
        setAttribute("colspan", maxCols);
        setAttribute("isFirst", first);
        renderFragment("groupStart");
        for (int i = 0; i < groupMembers.size(); i++) {
            if (i == groupMembers.size() - 1 && Form.DISPLAY_MODE_ALIGNED.equals(mode)) {
                fieldColspan = maxCols - i * maxCols / maxMembers;
                fieldWidth = (100 * fieldColspan) / maxCols;
            }
            renderInputElement((Field) groupMembers.get(i), fieldColspan, fieldWidth, namespace, renderMode,
                    labelMode, i);
        }
        setAttribute("field", groupMembers.get(groupMembers.size() - 1));
        setAttribute("colspan", maxCols);
        renderFragment("groupEnd");
    }

    protected void renderInputElement(Field field, int fieldColspan, int fieldWidth, String namespace,
            String renderMode, String labelMode, int index) {
        setAttribute("field", field);
        setAttribute("colspan", fieldColspan);
        setAttribute("width", fieldWidth);
        setAttribute("index", index);
        boolean labelInSameLine = Form.LABEL_MODE_LEFT.equals(labelMode) || Form.LABEL_MODE_RIGHT.equals(labelMode);
        renderFragment("beforeInputElement");

        // TODO: improve alignment for checkbox label
        if ((field.getFieldType().getCode().equals("CheckBox")
                || field.getFieldType().getCode().equals("CheckBoxPrimitiveBoolean"))
                && !Form.LABEL_MODE_LEFT.equals(labelMode)) {
            labelMode = Form.LABEL_MODE_AFTER;
            labelInSameLine = true;
        }

        if (Form.LABEL_MODE_BEFORE.equals(labelMode) || Form.LABEL_MODE_LEFT.equals(labelMode)) {
            setAttribute("colspan", fieldColspan);
            setAttribute("width", fieldWidth);

            setBindingAttributes(field);

            renderFragment("beforeLabel");

            renderLabel(field, namespace, renderMode);
            renderFragment("afterLabel");
            if (!labelInSameLine)
                renderFragment("lineBetweenLabelAndField");
        }
        setAttribute("field", field);
        setAttribute("colspan", fieldColspan);
        setAttribute("width", fieldWidth);
        renderFragment("beforeField");
        renderField(field, namespace, renderMode);
        setAttribute("field", field);
        renderFragment("afterField");

        if (Form.LABEL_MODE_AFTER.equals(labelMode) || Form.LABEL_MODE_RIGHT.equals(labelMode)) {
            if (!labelInSameLine)
                renderFragment("lineBetweenLabelAndField");
            setAttribute("colspan", fieldColspan);
            setAttribute("width", fieldWidth);

            setBindingAttributes(field);

            renderFragment("beforeLabel");
            renderLabel(field, namespace, renderMode);
            renderFragment("afterLabel");
        }
        setAttribute("field", field);
        renderFragment("afterInputElement");
    }

    private void setBindingAttributes(Field field) {

        DataHolder inputHolder = formToPaint.getDataHolderFromInputExpression(field.getInputBinding());
        boolean hasInputBinding = !StringUtils.isEmpty(field.getInputBinding());

        String color = "#444444";

        setAttribute("hasInputBinding", hasInputBinding);
        if (hasInputBinding) {
            setAttribute("inputBindingColor", inputHolder != null ? inputHolder.getRenderColor() : color);
        }

        DataHolder outputHolder = formToPaint.getDataHolderFromOutputExpression(field.getOutputBinding());
        boolean hasOutputBinding = !StringUtils.isEmpty(field.getOutputBinding());

        if (hasOutputBinding && hasInputBinding) {
            if (inputHolder != null && inputHolder.equals(outputHolder))
                hasOutputBinding = false;
            else if (inputHolder == null && outputHolder == null)
                hasOutputBinding = false;
        }

        setAttribute("hasOutputBinding", hasOutputBinding);
        if (hasOutputBinding) {
            setAttribute("outputBindingColor", outputHolder != null ? outputHolder.getRenderColor() : color);
        }
    }

    protected BigInteger calculateMCM(List colspans) {
        if (colspans == null || colspans.isEmpty()) {
            return new BigInteger("1");
        } else if (colspans.size() == 1) {
            return (BigInteger) colspans.get(0);
        } else if (colspans.size() == 2) {
            BigInteger b1 = (BigInteger) colspans.get(0);
            BigInteger b2 = (BigInteger) colspans.get(1);
            return b1.multiply(b2).divide(b1.gcd(b2));
        } else { //Size > 2
            int halfLength = colspans.size() / 2;
            List firstHalf = colspans.subList(0, halfLength);
            List secondHalf = colspans.subList(halfLength, colspans.size());
            BigInteger b1 = calculateMCM(firstHalf);
            BigInteger b2 = calculateMCM(secondHalf);
            return b1.multiply(b2).divide(b1.gcd(b2));
        }
    }

    protected BigInteger calculateMax(List colspans) {
        BigInteger max = new BigInteger("0");
        for (int i = 0; i < colspans.size(); i++) {
            BigInteger number = (BigInteger) colspans.get(i);
            max = max.compareTo(number) < 0 ? number : max;
        }
        return max;
    }

    protected void setRenderingAttributes(Field field, String namespace, Object value,
            FormStatusData formStatusData, boolean isWrongField) {
        String fieldName = namespaceManager.generateFieldNamesPace(namespace, field);
        setAttribute(ATTR_FIELD, field);
        setAttribute(ATTR_VALUE, value);
        setAttribute(ATTR_INPUT_VALUE, formStatusData.getCurrentInputValue(fieldName));
        setAttribute(ATTR_FIELD_IS_WRONG, isWrongField);
        setAttribute(ATTR_NAMESPACE, namespace);
        setAttribute(ATTR_NAME, fieldName);
        setAttribute(ATTR_FORM_RENDER_MODE, renderMode);
    }
}