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