    001// Copyright 2006-2013 The Apache Software Foundation
    002//
    003// Licensed under the Apache License, Version 2.0 (the "License");
    004// you may not use this file except in compliance with the License.
    005// You may obtain a copy of the License at
    006//
    007//     http://www.apache.org/licenses/LICENSE-2.0
    008//
    009// Unless required by applicable law or agreed to in writing, software
    010// distributed under the License is distributed on an "AS IS" BASIS,
    011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012// See the License for the specific language governing permissions and
    013// limitations under the License.
    014
    015package org.apache.tapestry5.corelib.components;
    016
    017import org.apache.tapestry5.BindingConstants;
    018import org.apache.tapestry5.MarkupWriter;
    019import org.apache.tapestry5.ValidationTracker;
    020import org.apache.tapestry5.annotations.Environmental;
    021import org.apache.tapestry5.annotations.Import;
    022import org.apache.tapestry5.annotations.Parameter;
    023import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    024
    025import java.util.List;
    026import java.util.Set;
    027
    028/**
    029 * Standard validation error presenter. Must be enclosed by a
    030 * {@link org.apache.tapestry5.corelib.components.Form} component. If errors are present, renders a
    031 * {@code <div>} element around a banner message and around an unnumbered list of
    032 * error messages. Renders nothing if the {@link org.apache.tapestry5.ValidationTracker} shows no
    033 * errors.
    034 *
    035 * @tapestrydoc
    036 * @see Form
    037 */
    038@Import(module = "bootstrap/alert")
    039public class Errors
    040{
    041    /**
    042     * The banner message displayed above the errors. The default value is "You must correct the
    043     * following errors before
    044     * you may continue.".
    045     */
    046    @Parameter("message:core-default-error-banner")
    047    private String banner;
    048
    049    /**
    050     * If true, then only errors global to the form (unassociated with any specific field) are
    051     * presented. By default all errors (associated with fields, or not) are presented; with unassociated
    052     * errors presented first.
    053     *
    054     * @since 5.4
    055     */
    056    @Parameter
    057    private boolean globalOnly;
    058
    059    /**
    060     * The CSS class for the div element rendered by the component.
    061     */
    062    @Parameter(name = "class", defaultPrefix = BindingConstants.LITERAL, value = "alert alert-danger")
    063    private String className;
    064
    065    // Allow null so we can generate a better error message if missing
    066    @Environmental(false)
    067    private ValidationTracker tracker;
    068
    069    boolean beginRender(MarkupWriter writer)
    070    {
    071        if (tracker == null)
    072            throw new RuntimeException("The Errors component must be enclosed by a Form component.");
    073
    074        if (!tracker.getHasErrors())
    075        {
    076            return false;
    077        }
    078
    079        List<String> errors =
    080                globalOnly ? tracker.getUnassociatedErrors() : tracker.getErrors();
    081
    082        if (errors.isEmpty())
    083        {
    084            return false;
    085        }
    086
    087        Set<String> previousErrors = CollectionFactory.newSet();
    088
    089        writer.element("div", "class", "alert-dismissable " + className);
    090        writer.element("button",
    091                "type", "button",
    092                "class", "close",
    093                "data-dismiss", "alert");
    094        writer.writeRaw("&times;");
    095        writer.end();
    096
    097        writer.element("h4");
    098        writer.writeRaw(banner);
    099        writer.end();
    100
    101        writer.element("ul");
    102
    103        for (String message : errors)
    104        {
    105            if (previousErrors.contains(message))
    106            {
    107                continue;
    108            }
    109
    110            writer.element("li");
    111            writer.write(message);
    112            writer.end();
    113
    114            previousErrors.add(message);
    115        }
    116
    117        writer.end(); // ul
    118
    119        writer.end(); // div
    120
    121        return false;
    122    }
    123}