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