001/* 002 * (C) Copyright 2013 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Thomas Roger 018 */ 019 020package org.nuxeo.ecm.platform.ui.web.component.message; 021 022import java.io.IOException; 023import java.util.Iterator; 024 025import javax.faces.application.FacesMessage; 026import javax.faces.component.UIComponent; 027import javax.faces.component.UIMessages; 028import javax.faces.context.FacesContext; 029import javax.faces.context.ResponseWriter; 030import javax.faces.event.AbortProcessingException; 031import javax.faces.event.ComponentSystemEvent; 032import javax.faces.event.ComponentSystemEventListener; 033import javax.faces.event.ListenerFor; 034import javax.faces.event.PostAddToViewEvent; 035 036import org.apache.commons.lang.StringEscapeUtils; 037import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils; 038import org.nuxeo.runtime.api.Framework; 039 040import com.sun.faces.renderkit.html_basic.MessagesRenderer; 041 042/** 043 * Handles rendering of {@link javax.faces.application.FacesMessage} through jQuery Ambiance plugin. 044 * 045 * @since 5.7.3 046 */ 047@ListenerFor(systemEventClass = PostAddToViewEvent.class) 048public class NXMessagesRenderer extends MessagesRenderer implements ComponentSystemEventListener { 049 050 public static final String RENDERER_TYPE = "javax.faces.NXMessages"; 051 052 @Override 053 public void encodeEnd(FacesContext context, UIComponent component) throws IOException { 054 rendererParamsNotNull(context, component); 055 056 if (!shouldEncode(component)) { 057 return; 058 } 059 060 // If id is user specified, we must render 061 boolean mustRender = shouldWriteIdAttribute(component); 062 063 UIMessages messages = (UIMessages) component; 064 ResponseWriter writer = context.getResponseWriter(); 065 assert(writer != null); 066 067 String clientId = ((UIMessages) component).getFor(); 068 // if no clientId was included 069 if (clientId == null) { 070 // and the author explicitly only wants global messages 071 if (messages.isGlobalOnly()) { 072 // make it so only global messages get displayed. 073 clientId = ""; 074 } 075 } 076 077 // "for" attribute optional for Messages 078 Iterator<?> messageIter = getMessageIter(context, clientId, component); 079 080 assert(messageIter != null); 081 082 if (!messageIter.hasNext()) { 083 if (mustRender) { 084 // no message to render, but must render anyway 085 // but if we're writing the dev stage messages, 086 // only write it if messages exist 087 if ("javax_faces_developmentstage_messages".equals(component.getId())) { 088 return; 089 } 090 writer.startElement("div", component); 091 writeIdAttributeIfNecessary(context, writer, component); 092 writer.endElement("div"); 093 } // otherwise, return without rendering 094 return; 095 } 096 097 boolean showDetail = messages.isShowDetail(); 098 099 while (messageIter.hasNext()) { 100 FacesMessage curMessage = (FacesMessage) messageIter.next(); 101 if (curMessage.isRendered() && !messages.isRedisplay()) { 102 continue; 103 } 104 curMessage.rendered(); 105 106 // make sure we have a non-null value for summary and 107 // detail. 108 String summary = (null != (summary = curMessage.getSummary())) ? summary : ""; 109 // Default to summary if we have no detail 110 String detail = (null != (detail = curMessage.getDetail())) ? detail : summary; 111 112 String severityStyleClass = null; 113 String errorType = "default"; 114 long timeout = 5; 115 if (curMessage.getSeverity() == FacesMessage.SEVERITY_INFO) { 116 severityStyleClass = (String) component.getAttributes().get("infoClass"); 117 errorType = "info"; 118 } else if (curMessage.getSeverity() == FacesMessage.SEVERITY_WARN) { 119 severityStyleClass = (String) component.getAttributes().get("warnClass"); 120 errorType = "warn"; 121 } else if (curMessage.getSeverity() == FacesMessage.SEVERITY_ERROR) { 122 severityStyleClass = (String) component.getAttributes().get("errorClass"); 123 errorType = "error"; 124 timeout = 0; 125 } else if (curMessage.getSeverity() == FacesMessage.SEVERITY_FATAL) { 126 severityStyleClass = (String) component.getAttributes().get("fatalClass"); 127 errorType = "fatal"; 128 timeout = 0; 129 } 130 131 // ensure message stays visible when running tests 132 if (Framework.getProperty("org.nuxeo.ecm.tester.name") != null) { 133 timeout = 0; 134 } 135 136 writer.startElement("script", messages); 137 writer.writeAttribute("type", "text/javascript", null); 138 String message = ""; 139 if (showDetail) { 140 message = StringEscapeUtils.escapeJavaScript(detail); 141 } 142 String scriptContent = new StringBuilder().append("jQuery(document).ready(function() {\n") 143 .append(" jQuery.ambiance({\n") 144 .append(" message: \"") 145 .append(message) 146 .append("\",\n") 147 .append(" title: \"") 148 .append(StringEscapeUtils.escapeJavaScript(summary)) 149 .append("\",\n") 150 .append(" type: \"") 151 .append(errorType) 152 .append("\",\n") 153 .append(" className: \"") 154 .append(severityStyleClass) 155 .append("\",\n") 156 .append(" timeout: \"") 157 .append(timeout) 158 .append("\"") 159 .append(" })\n") 160 .append("});\n") 161 .toString(); 162 writer.writeText(scriptContent, null); 163 writer.endElement("script"); 164 } 165 } 166 167 /* 168 * When this method is called, we know that there is a component with a script renderer somewhere in the view. We 169 * need to make it so that when an element with a name given by the value of the optional "target" component 170 * attribute is encountered, this component can be called upon to render itself. This method will add the component 171 * (associated with this Renderer) to a facet in the view only if a "target" component attribute is set. 172 */ 173 public void processEvent(ComponentSystemEvent event) throws AbortProcessingException { 174 UIComponent component = event.getComponent(); 175 if (ComponentUtils.isRelocated(component)) { 176 return; 177 } 178 String target = verifyTarget((String) component.getAttributes().get("target")); 179 if (target != null) { 180 ComponentUtils.relocate(component, target, null); 181 } 182 } 183 184 protected String verifyTarget(String toVerify) { 185 return ComponentUtils.verifyTarget(toVerify, toVerify); 186 } 187 188}