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 java.util.List; 018import java.util.Set; 019 020import org.apache.tapestry5.BindingConstants; 021import org.apache.tapestry5.MarkupWriter; 022import org.apache.tapestry5.SymbolConstants; 023import org.apache.tapestry5.ValidationTracker; 024import org.apache.tapestry5.annotations.Environmental; 025import org.apache.tapestry5.annotations.Parameter; 026import org.apache.tapestry5.commons.util.CollectionFactory; 027import org.apache.tapestry5.ioc.annotations.Inject; 028import org.apache.tapestry5.ioc.annotations.Symbol; 029import org.apache.tapestry5.services.ComponentOverride; 030import org.apache.tapestry5.services.javascript.JavaScriptSupport; 031 032/** 033 * Standard validation error presenter. Must be enclosed by a 034 * {@link org.apache.tapestry5.corelib.components.Form} component. If errors are present, renders a 035 * {@code <div>} element around a banner message and around an unnumbered list of 036 * error messages. Renders nothing if the {@link org.apache.tapestry5.ValidationTracker} shows no 037 * errors. 038 * 039 * @tapestrydoc 040 * @see Form 041 */ 042//@Import(module = "bootstrap/alert") 043public class Errors 044{ 045 /** 046 * The banner message displayed above the errors. The default value is "You must correct the 047 * following errors before 048 * you may continue.". 049 */ 050 @Parameter("message:core-default-error-banner") 051 private String banner; 052 053 /** 054 * If true, then only errors global to the form (unassociated with any specific field) are 055 * presented. By default all errors (associated with fields, or not) are presented; with unassociated 056 * errors presented first. 057 * 058 * @since 5.4 059 */ 060 @Parameter 061 private boolean globalOnly; 062 063 /** 064 * The CSS class for the div element rendered by the component. 065 */ 066 @Parameter(name = "class", defaultPrefix = BindingConstants.LITERAL, value = "symbol:" + SymbolConstants.ERRORS_DEFAULT_CLASS_PARAMETER_VALUE) 067 private String className; 068 069 // Allow null so we can generate a better error message if missing 070 @Environmental(false) 071 private ValidationTracker tracker; 072 073 @Inject 074 @Symbol(SymbolConstants.ERRORS_BASE_CSS_CLASS) 075 private String baseCssClass; 076 077 @Inject 078 @Symbol(SymbolConstants.ERRORS_CLOSE_BUTTON_CSS_CLASS) 079 private String closeButtonCssClass; 080 081 @Inject 082 private JavaScriptSupport javaScriptSupport; 083 084 boolean beginRender(MarkupWriter writer) 085 { 086 if (tracker == null) 087 throw new RuntimeException("The Errors component must be enclosed by a Form component."); 088 089 if (!tracker.getHasErrors()) 090 { 091 return false; 092 } 093 094 List<String> errors = 095 globalOnly ? tracker.getUnassociatedErrors() : tracker.getErrors(); 096 097 if (errors.isEmpty()) 098 { 099 return false; 100 } 101 102 setUpJavaScript(); 103 104 Set<String> previousErrors = CollectionFactory.newSet(); 105 106 writer.element("div", "class", baseCssClass + " " + className, "role", "alert"); 107 writer.element("button", 108 "type", "button", 109 "class", closeButtonCssClass, 110 "data-dismiss", "alert"); 111 writer.writeRaw("×"); 112 writer.end(); 113 114 writer.element("h4"); 115 writer.writeRaw(banner); 116 writer.end(); 117 118 writer.element("ul"); 119 120 for (String message : errors) 121 { 122 if (previousErrors.contains(message)) 123 { 124 continue; 125 } 126 127 writer.element("li"); 128 writer.write(message); 129 writer.end(); 130 131 previousErrors.add(message); 132 } 133 134 writer.end(); // ul 135 136 writer.end(); // div 137 138 return false; 139 } 140 141 /** 142 * Hook to set up JavaScript, specifically for handling the close button. 143 * Method intended to be overriden when Bootstrap isn't being used. 144 * This implementation calls <code>javaScriptSupport.require("bootstrap/alert");</code>. 145 * @since 5.5 146 * @see ComponentOverride 147 */ 148 protected void setUpJavaScript() { 149 javaScriptSupport.require("bootstrap/alert"); 150 } 151 152}