001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/sections/tags/sakai_2-4-1/sections-app/src/java/org/sakaiproject/tool/section/jsf/DivMessagesRenderer.java $
003: * $Id: DivMessagesRenderer.java 20002 2006-12-22 19:52:45Z jholtzman@berkeley.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2005, 2006 The Regents of the University of California and The Regents of the University of Michigan
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.tool.section.jsf;
021:
022: import java.io.IOException;
023: import java.util.ArrayList;
024: import java.util.Collection;
025: import java.util.Iterator;
026: import java.util.List;
027:
028: import javax.faces.application.FacesMessage;
029: import javax.faces.component.UIComponent;
030: import javax.faces.component.UIMessages;
031: import javax.faces.context.FacesContext;
032:
033: import org.apache.commons.logging.Log;
034: import org.apache.commons.logging.LogFactory;
035:
036: /**
037: * Work around JSF 1.1's inadequate Messages renderer, which has the following problems:
038: *
039: * - Contrary to the documentation, an HTML list is not used to render a list. Instead,
040: * the message texts are written out one after another.
041: *
042: * - There's no way to define styles or classes for the containing HTML element.
043: *
044: * - Each message is put into a "span" element rather than a "div". This means that
045: * important formatting options are not available to page designers.
046: *
047: * Of these, the worst problem seems the use of "span" instead of "div". Not many friendly
048: * UIs will present messages to the user in a bulleted or numbered list, and since
049: * each row only has one cell, the "table" capabilities aren't much different than one
050: * would get from "div"s. So this overriding renderer uses "div" instead.
051: *
052: * TODO Implement "table" and "list", and the trimmings ("detail", "title", etc.) to
053: * make this a full replacement.
054: *
055: * TODO Possibly add a TLD to make styling the container possible.
056: *
057: * To replace the JSF renderer with no further fuss, just paste this into faces-config.xml :
058: *
059: * <render-kit>
060: * <renderer>
061: * <component-family>javax.faces.Messages</component-family>
062: * <renderer-type>javax.faces.Messages</renderer-type>
063: * <renderer-class>org.sakaiproject.tool.section.jsf.DivMessagesRenderer</renderer-class>
064: * </renderer>
065: * </render-kit>
066: *
067: * Notes added by JH below:
068: *
069: * This messages renderer will check in two places for faces messages: in the
070: * faces context, and in the session-scoped messagesBean.
071: *
072: */
073: public class DivMessagesRenderer extends DivMessageRendererBase {
074: private static final Log logger = LogFactory
075: .getLog(DivMessagesRenderer.class);
076:
077: public void encodeEnd(FacesContext context, UIComponent component)
078: throws IOException {
079: if (!component.isRendered()) {
080: return;
081: }
082:
083: List allMessages = combineMessages(context, component);
084: if (allMessages.size() == 0) {
085: return;
086: }
087:
088: for (Iterator messages = allMessages.iterator(); messages
089: .hasNext();) {
090: FacesMessage message = (FacesMessage) messages.next();
091: renderMessage(context, component, message);
092: }
093: }
094:
095: /**
096: * Combine the messages from the redirect-safe messages bean and those
097: * stored in the faces context. If there are messages associated with
098: * individual components, but no global messages, then add a global message
099: * pointing the user to check for component messages ('See messages below'
100: * for instance).
101: *
102: * @param context
103: * @param component
104: * @return
105: */
106: private List combineMessages(FacesContext context,
107: UIComponent component) {
108: List redirectSafeMessages = ((MessagingBean) JsfUtil
109: .resolveVariable("messagingBean"))
110: .getMessagesAndClear();
111: List allMessages = new ArrayList(redirectSafeMessages);
112: boolean globalOnly = ((UIMessages) component).isGlobalOnly();
113:
114: Iterator allFacesMessages = context.getMessages();
115: Iterator globalFacesMessages = context.getMessages(null);
116:
117: Collection componentBoundMessages = getComponentBoundMessages(
118: allFacesMessages, globalFacesMessages);
119: Iterator facesMessages;
120: if (globalOnly) {
121: facesMessages = globalFacesMessages;
122: } else {
123: facesMessages = allFacesMessages;
124: }
125:
126: // If this is a global only component, and there are no global messages,
127: // and there are no redirect-safe messages (which are always global), and
128: // there are component-bound messages, then add a global message telling
129: // the user to look for the component-bound messages.
130: if (globalOnly && redirectSafeMessages.size() == 0
131: && !globalFacesMessages.hasNext()
132: && componentBoundMessages.size() != 0) {
133: FacesMessage seeBelowMessage = new FacesMessage(
134: FacesMessage.SEVERITY_WARN,
135: JsfUtil
136: .getLocalizedMessage("validation_messages_present"),
137: null);
138: allMessages.add(seeBelowMessage);
139: }
140:
141: // We've already iterated over the facesMessage iterator, so we need to get them again so we can iterate again.
142: // This is ugly... is there a better way to do this?
143: if (globalOnly) {
144: facesMessages = context.getMessages(null);
145: } else {
146: facesMessages = context.getMessages();
147: }
148:
149: for (Iterator msgs = facesMessages; facesMessages.hasNext();) {
150: allMessages.add(facesMessages.next());
151: }
152: return allMessages;
153: }
154:
155: /**
156: * Finds the non-global messages
157: *
158: * @param allFacesMessages
159: * @param globalFacesMessages
160: * @return
161: */
162: private Collection getComponentBoundMessages(
163: Iterator allFacesMessages, Iterator globalFacesMessages) {
164: List allFacesMessagesList = new ArrayList();
165: for (Iterator msgs = allFacesMessages; msgs.hasNext();) {
166: allFacesMessagesList.add(msgs.next());
167: }
168:
169: List globalFacesMessagesList = new ArrayList();
170: for (Iterator msgs = globalFacesMessages; msgs.hasNext();) {
171: globalFacesMessagesList.add(msgs.next());
172: }
173:
174: allFacesMessagesList.removeAll(globalFacesMessagesList);
175: if (logger.isDebugEnabled())
176: logger.debug(allFacesMessagesList.size()
177: + " component bound messages");
178: return allFacesMessagesList;
179: }
180: }
|