    001// Licensed under the Apache License, Version 2.0 (the "License");
    002// you may not use this file except in compliance with the License.
    003// You may obtain a copy of the License at
    004//
    005//     http://www.apache.org/licenses/LICENSE-2.0
    006//
    007// Unless required by applicable law or agreed to in writing, software
    008// distributed under the License is distributed on an "AS IS" BASIS,
    009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    010// See the License for the specific language governing permissions and
    011// limitations under the License.
    012
    013package org.apache.tapestry5.corelib.mixins;
    014
    015import org.apache.tapestry5.Field;
    016import org.apache.tapestry5.MarkupWriter;
    017import org.apache.tapestry5.SymbolConstants;
    018import org.apache.tapestry5.ValidationDecorator;
    019import org.apache.tapestry5.annotations.Environmental;
    020import org.apache.tapestry5.annotations.HeartbeatDeferred;
    021import org.apache.tapestry5.annotations.InjectContainer;
    022import org.apache.tapestry5.dom.Element;
    023import org.apache.tapestry5.ioc.annotations.Inject;
    024import org.apache.tapestry5.ioc.annotations.Symbol;
    025import org.apache.tapestry5.services.javascript.JavaScriptSupport;
    026
    027/**
    028 * Applied to a {@link org.apache.tapestry5.Field}, this provides the outer layers of markup to correctly
    029 * render text fields, selects, and textareas using Bootstrap:
    030 * an outer {@code <div class="field-group">} containing a {@code <label class="control-label">} and the field itself.
    031 * Actually, the class attribute of the div is defined by the
    032 * {@link SymbolConstants#FORM_GROUP_WRAPPER_CSS_CLASS} and
    033 * the class attribute of label is defined by the {@link SymbolConstants#FORM_GROUP_LABEL_CSS_CLASS}.
    034 * <code>field-group</code> and <code>control-label</code> are the default values.
    035 * As with the {@link org.apache.tapestry5.corelib.components.Label} component, the {@code for} attribute is set (after the field itself
    036 * renders).
    037 *
    038 *
    039 * You can also use the {@link SymbolConstants#FORM_GROUP_FORM_FIELD_WRAPPER_ELEMENT_NAME} symbol
    040 * to optionally wrap the input field in an element and {@link SymbolConstants#FORM_GROUP_FORM_FIELD_WRAPPER_ELEMENT_CSS_CLASS}
    041 * to give it a CSS class. This is useful for Bootstrap form-horizontal forms.
    042 * Setting {@link SymbolConstants#FORM_GROUP_FORM_FIELD_WRAPPER_ELEMENT_NAME} to <code>div</code>,
    043 * {@link SymbolConstants#FORM_GROUP_FORM_FIELD_WRAPPER_ELEMENT_CSS_CLASS} to <code>col-sm-10</code>
    044 * and {@link SymbolConstants#FORM_GROUP_LABEL_CSS_CLASS} to <code>col-sm-2</code>
    045 * will generate labels 2 columns wide and form fields 10 columns wide.
    046 *
    047 *
    048 * This component is not appropriate for radio buttons or checkboxes as they use a different class on the outermost element
    049 * ("radio" or "checkbox") and next the element inside the {@code <label>}.
    050 *
    051 *
    052 * @tapestrydoc
    053 * @since 5.4
    054 * @see SymbolConstants#FORM_GROUP_WRAPPER_CSS_CLASS
    055 * @see SymbolConstants#FORM_GROUP_FORM_FIELD_WRAPPER_ELEMENT_NAME
    056 * @see SymbolConstants#FORM_GROUP_FORM_FIELD_WRAPPER_ELEMENT_CSS_CLASS
    057 * @see SymbolConstants#FORM_GROUP_LABEL_CSS_CLASS
    058 * @see SymbolConstants#FORM_FIELD_CSS_CLASS
    059 */
    060public class FormGroup
    061{
    062    @InjectContainer
    063    private Field field;
    064
    065    @Inject
    066    @Symbol(SymbolConstants.FORM_GROUP_LABEL_CSS_CLASS)
    067    private String labelCssClass;
    068
    069    @Inject
    070    @Symbol(SymbolConstants.FORM_GROUP_WRAPPER_CSS_CLASS)
    071    private String divCssClass;
    072
    073    @Inject
    074    @Symbol(SymbolConstants.FORM_GROUP_FORM_FIELD_WRAPPER_ELEMENT_NAME)
    075    private String fieldWrapperElementName;
    076
    077    @Inject
    078    @Symbol(SymbolConstants.FORM_GROUP_FORM_FIELD_WRAPPER_ELEMENT_CSS_CLASS)
    079    private String fieldWrapperElementCssClass;
    080
    081    private Element label;
    082
    083    private Element fieldWrapper;
    084
    085    @Environmental
    086    private ValidationDecorator decorator;
    087
    088    @Inject
    089    private JavaScriptSupport javaScriptSupport;
    090
    091    void beginRender(MarkupWriter writer)
    092    {
    093        writer.element("div", "class",
    094                !("form-group".equals(divCssClass)) ? ("form-group" + " " + divCssClass) : divCssClass);
    095
    096        decorator.beforeLabel(field);
    097
    098        label = writer.element("label", "class", labelCssClass);
    099        writer.end();
    100
    101        fillInLabelAttributes();
    102
    103        decorator.afterLabel(field);
    104
    105        if (fieldWrapperElementName.length() > 0) {
    106            fieldWrapper = writer.element(fieldWrapperElementName);
    107            if (fieldWrapperElementCssClass.length() > 0) {
    108                fieldWrapper.attribute("class", fieldWrapperElementCssClass);
    109            }
    110        }
    111
    112    }
    113
    114    @HeartbeatDeferred
    115    void fillInLabelAttributes()
    116    {
    117        label.attribute("for", field.getClientId());
    118        label.text(field.getLabel());
    119    }
    120
    121    void afterRender(MarkupWriter writer)
    122    {
    123        if (fieldWrapper != null)
    124        {
    125            writer.end(); // field wrapper
    126        }
    127
    128        // TAP5-2662
    129        final Element inputElement = writer.getDocument().getElementById(field.getClientId());
    130        if (inputElement != null)
    131        {
    132            final String clientId = field.getClientId();
    133            final String labelId = javaScriptSupport.allocateClientId(clientId + "-label");
    134            label.attribute("id", labelId);
    135            inputElement.attribute("aria-labelledby", labelId);
    136        }
    137
    138        writer.end(); // div.form-group
    139    }
    140}