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.components; 014 015import org.apache.tapestry5.*; 016import org.apache.tapestry5.annotations.Environmental; 017import org.apache.tapestry5.annotations.HeartbeatDeferred; 018import org.apache.tapestry5.annotations.Parameter; 019import org.apache.tapestry5.annotations.SupportsInformalParameters; 020import org.apache.tapestry5.dom.Element; 021import org.apache.tapestry5.ioc.annotations.Inject; 022import org.apache.tapestry5.ioc.internal.util.InternalUtils; 023 024/** 025 * Generates a <label> element for a particular field. It writes the CSS class "control-label". 026 * 027 * A Label will render its body, if it has one. However, in most cases it will not have a body, and will render its 028 * {@linkplain org.apache.tapestry5.Field#getLabel() field's label} as its body. Remember, however, that it is the 029 * field label that will be used in any error messages. The Label component allows for client- and server-side 030 * validation error decorations. 031 * 032 * @tapestrydoc 033 */ 034@SupportsInformalParameters 035public class Label 036{ 037 /** 038 * The for parameter is used to identify the {@link Field} linked to this label (it is named this way because it 039 * results in the for attribute of the label element). 040 */ 041 @Parameter(name = "for", required = true, allowNull = false, defaultPrefix = BindingConstants.COMPONENT) 042 private Field field; 043 044 @Environmental 045 private ValidationDecorator decorator; 046 047 @Inject 048 private ComponentResources resources; 049 050 /** 051 * If true, then the body of the label element (in the template) is ignored. This is used when a designer places a 052 * value inside the <label> element for WYSIWYG purposes, but it should be replaced with a different 053 * (probably, localized) value at runtime. The default is false, so a body will be used if present and the field's 054 * label will only be used if the body is empty or blank. 055 */ 056 @Parameter 057 private boolean ignoreBody; 058 059 private Element labelElement; 060 061 boolean beginRender(MarkupWriter writer) 062 { 063 decorator.beforeLabel(field); 064 065 labelElement = writer.element("label", "class", "control-label"); 066 067 resources.renderInformalParameters(writer); 068 069 // Since we don't know if the field has rendered yet, we need to defer writing the for and id 070 // attributes until we know the field has rendered (and set its clientId property). That's 071 // exactly what Heartbeat is for. 072 073 updateAttributes(); 074 075 return !ignoreBody; 076 } 077 078 @HeartbeatDeferred 079 private void updateAttributes() 080 { 081 String fieldId = field.getClientId(); 082 083 if (fieldId == null) 084 { 085 throw new IllegalStateException("The field has returned a null client-side ID"); 086 } 087 088 labelElement.forceAttributes("for", fieldId); 089 decorator.insideLabel(field, labelElement); 090 } 091 092 void afterRender(MarkupWriter writer) 093 { 094 // If the Label element has a body that renders some non-blank output, that takes precedence 095 // over the label string provided by the field. 096 097 boolean bodyIsBlank = InternalUtils.isBlank(labelElement.getChildMarkup()); 098 099 if (bodyIsBlank) 100 writer.write(field.getLabel()); 101 102 writer.end(); // label 103 104 decorator.afterLabel(field); 105 } 106}