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