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}