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