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}