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