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("×"); 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}