View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.custom.inputHtml;
20  
21  import java.io.IOException;
22  import java.util.List;
23  import java.util.Map;
24  
25  import javax.faces.component.UIComponent;
26  import javax.faces.component.UIForm;
27  import javax.faces.component.UINamingContainer;
28  import javax.faces.context.FacesContext;
29  import javax.faces.context.ResponseWriter;
30  import javax.faces.convert.ConverterException;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFRenderer;
35  import org.apache.myfaces.component.UserRoleUtils;
36  import org.apache.myfaces.custom.tabbedpane.HtmlPanelTab;
37  import org.apache.myfaces.custom.tabbedpane.HtmlPanelTabbedPane;
38  import org.apache.myfaces.shared_tomahawk.renderkit.JSFAttr;
39  import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
40  import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
41  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRenderer;
42  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRendererUtils;
43  import org.apache.myfaces.shared_tomahawk.renderkit.html.util.HTMLEncoder;
44  
45  /**
46   * 
47   * @author Sylvain Vieujot (latest modification by $Author: skitching $)
48   * @version $Revision: 673833 $ $Date: 2008-07-03 16:58:05 -0500 (jue, 03 jul 2008) $
49   */
50  @JSFRenderer(
51     renderKitId = "HTML_BASIC", 
52     family = "javax.faces.Input",
53     type = "org.apache.myfaces.InputHtml")
54  public class InputHtmlRenderer extends HtmlRenderer{
55      // TODO : Enable multiple editors on one page
56      // TODO : Fix i18n (check the kupustart_form.js)
57      // TODO : Finish Disabled mode.
58      // TODO : Automatic Fallback for non kupu capable browsers (Safari, smartphones, non javascript, ...).
59      // TODO : Make Image & Link Library work.
60      // TODO : Allow disabeling of tag filtering
61      // TODO : Fix height while zoomed
62  
63      private static final Log log = LogFactory.getLog(HtmlRendererUtils.class);
64      
65      protected boolean isDisabled(FacesContext facesContext, UIComponent uiComponent) {
66          if( !UserRoleUtils.isEnabledOnUserRole(uiComponent) )
67              return false;
68  
69          return ((InputHtml)uiComponent).isDisabled();
70      }
71  
72      private static boolean useFallback(InputHtml editor){
73          // TODO : Handle fallback="auto"
74          return editor.getFallback().equals("true");
75      }
76  
77      public boolean getRendersChildren()
78      {
79          return true;
80      }
81  
82      public void encodeBegin(FacesContext context, UIComponent component)
83              throws IOException
84      {
85      }
86  
87      public void encodeChildren(FacesContext context, UIComponent component)
88              throws IOException
89      {
90      }
91  
92      public void encodeEnd(FacesContext context, UIComponent uiComponent) throws IOException {
93          RendererUtils.checkParamValidity(context, uiComponent, InputHtml.class);
94          
95          UIComponent compositeFacet = (UIComponent) uiComponent
96                  .getFacet(UIComponent.COMPOSITE_FACET_NAME);
97  
98          if (compositeFacet == null)
99          {
100             if (log.isErrorEnabled())
101             {
102                 log
103                         .error("facet UIComponent.COMPOSITE_FACET_NAME not found when rendering composite component "
104                                 + uiComponent.getClientId(context));
105             }
106             return;
107         }
108         InputHtml editor = (InputHtml) uiComponent;
109         if( HtmlRendererUtils.isDisplayValueOnly(editor) )
110             encodeDisplayValueOnly(context, editor);
111         else if( useFallback(editor) )
112             encodeEndFallBackMode(context, editor);
113         else{
114             // HACK
115             // As only one inputHtml is supported by page in this version,
116             // we need to make sure we don't encode hidden inputHtml
117             // TODO : Fix this by supporting multiple inputHtml per page.
118 
119             if( ! isVisible(editor) ){
120                 encodeHidden(context, editor);
121             }else if( ! hasThisPageAlreadyRenderedAnInputHtml( context ) ){
122                 compositeFacet.encodeAll(context);
123                 setThisPageAlreadyRenderedAnInputHtml( context );
124             }else{
125                 log.warn("Only one inputHtml can be displayed at the same time. The component will be rendered isung a textarea." +
126                          "\nConpoment : "+RendererUtils.getPathToComponent( editor ));
127                 encodeEndFallBackMode(context, editor);
128             }
129         }
130     }
131 
132     static private boolean hasThisPageAlreadyRenderedAnInputHtml(FacesContext context){
133             return context.getExternalContext().getRequestMap().containsKey( InputHtmlRenderer.class.getName() );
134     }
135 
136     static private void setThisPageAlreadyRenderedAnInputHtml(FacesContext context){
137         context.getExternalContext().getRequestMap().put(InputHtmlRenderer.class.getName(), Boolean.TRUE);
138     }
139 
140     /**
141      * Try to figure out if this component is visible to avoid rendering the code if not necessary.
142      */
143     private boolean isVisible(InputHtml editor){
144             for(UIComponent parent = editor.getParent(); parent != null ; parent = parent.getParent()){
145                 if( parent instanceof HtmlPanelTab ){
146                     HtmlPanelTab panelTab = (HtmlPanelTab) parent;
147                     HtmlPanelTabbedPane panelTabbedPane = null;
148                     for(UIComponent panelAncestor=panelTab.getParent(); panelAncestor!=null ; panelAncestor=panelAncestor.getParent()){
149                         if( panelAncestor instanceof HtmlPanelTabbedPane ){
150                             panelTabbedPane = (HtmlPanelTabbedPane)panelAncestor;
151                             break;
152                         }
153                     }
154 
155                     if( panelTabbedPane != null ){
156                     if( panelTabbedPane.isClientSide() ){
157                         parent = panelTabbedPane;
158                         continue;
159                     }
160 
161                     // Not client side tabbed pane.
162                     // We need to check if the current panel tab is bisible;
163                     int selectedIndex = panelTabbedPane.getSelectedIndex();
164                     List children = panelTabbedPane.getChildren();
165                     int tabIdx = 0;
166                     for (int i = 0, len = children.size(); i < len && tabIdx <= selectedIndex ; i++){
167                         UIComponent child = htmlTabbedPaneRenderer_getUIComponent((UIComponent)children.get(i));
168                         if (child instanceof HtmlPanelTab){
169                                 if( child == panelTab ){
170                                     if( ! child.isRendered() || tabIdx != selectedIndex){
171                                     return false;
172                                 }else{
173                                         // It's visible. Check at upper level.
174                                         parent = panelTabbedPane;
175                                     continue;
176                                 }
177                                 }
178                             tabIdx++;
179                         }
180                         }
181                     }else{
182                         log.debug("pannelTabbedPane == null for component "+RendererUtils.getPathToComponent(panelTab));
183                     }
184                 }
185             }
186 
187             return true;
188     }
189 
190     /**
191      * This is a copy of HtmlTabbedPaneRenderer.getUIComponent
192      */
193     private UIComponent htmlTabbedPaneRenderer_getUIComponent(UIComponent uiComponent)
194     {
195         /* todo: forms other than UIForm, e.g. Trinidad's UIForm */
196         if (uiComponent instanceof UIForm || uiComponent instanceof UINamingContainer)
197         {
198             List children = uiComponent.getChildren();
199             for (int i = 0, len = children.size(); i < len; i++)
200             {
201                 uiComponent = htmlTabbedPaneRenderer_getUIComponent((UIComponent)children.get(i));
202             }
203         }
204         return uiComponent;
205     }
206 
207     private void encodeHidden(FacesContext context, InputHtml editor) throws IOException {
208         String clientId = editor.getClientId(context);
209         // Use a hidden textarea
210         ResponseWriter writer = context.getResponseWriter();
211         writer.startElement(HTML.TEXTAREA_ELEM, editor);
212 
213         writer.writeAttribute(HTML.NAME_ATTR, clientId, null);
214         writer.writeAttribute(HTML.STYLE_ATTR, "display:none", null);
215 
216         String text = RendererUtils.getStringValue(context, editor);
217         writer.writeText(text, JSFAttr.VALUE_ATTR);
218 
219         writer.endElement(HTML.TEXTAREA_ELEM);
220     }
221 
222     private void encodeDisplayValueOnly(FacesContext context, InputHtml editor) throws IOException {
223         // Use only a textarea
224         ResponseWriter writer = context.getResponseWriter();
225         writer.startElement(HTML.SPAN_ELEM, editor);
226         HtmlRendererUtils.writeIdIfNecessary(writer, editor, context);
227 
228         HtmlRendererUtils.renderDisplayValueOnlyAttributes(editor, writer);
229 
230         String text = RendererUtils.getStringValue(context, editor);
231         writer.write( editor.getHtmlBody( text ) );
232 
233         writer.endElement(HTML.SPAN_ELEM);
234     }
235 
236     private void encodeEndFallBackMode(FacesContext context, InputHtml editor) throws IOException {
237         String clientId = editor.getClientId(context);
238         // Use only a textarea
239         ResponseWriter writer = context.getResponseWriter();
240         writer.startElement(HTML.TEXTAREA_ELEM, editor);
241 
242         writer.writeAttribute(HTML.NAME_ATTR, clientId, null);
243         HtmlRendererUtils.writeIdIfNecessary(writer, editor, context);
244 
245         if( editor.getStyle()!=null )
246             writer.writeAttribute(HTML.STYLE_ATTR, editor.getStyle(), null);
247         if( editor.getStyleClass()!=null )
248             writer.writeAttribute(HTML.CLASS_ATTR, editor.getStyleClass(), null);
249 
250         if (isDisabled(context, editor))
251             writer.writeAttribute(HTML.DISABLED_ATTR, Boolean.TRUE, null);
252 
253         String text = RendererUtils.getStringValue(context, editor);
254         writer.write( htmlToPlainText(text, editor) );
255 
256         writer.endElement(HTML.TEXTAREA_ELEM);
257     }
258 
259     private static String htmlToPlainText(String html, InputHtml editor){
260         return editor.getHtmlBody( html )
261                 .replaceAll("<br.*>","\n")
262                 .replaceAll("<.+?>", "");
263     }
264 
265     public void decode(FacesContext facesContext, UIComponent uiComponent) {
266         RendererUtils.checkParamValidity(facesContext, uiComponent, InputHtml.class);
267         InputHtml editor = (InputHtml) uiComponent;
268 
269         Map paramMap = facesContext.getExternalContext().getRequestParameterMap();
270         String clientId = uiComponent.getClientId(facesContext);
271 
272         if (paramMap.containsKey(clientId)) {
273             //request parameter found, set submittedValue
274             String submitedText = (String)paramMap.get(clientId);
275             String htmlText = useFallback(editor) ? HTMLEncoder.encode(submitedText, true, true) : submitedText;
276 
277             editor.setSubmittedValue( htmlText );
278         } else {
279             log.warn(HtmlRendererUtils.NON_SUBMITTED_VALUE_WARNING + " Component : "+
280                      RendererUtils.getPathToComponent( editor ));
281         }
282     }
283 
284     public Object getConvertedValue(FacesContext facesContext, UIComponent uiComponent, Object submittedValue) throws ConverterException {
285         RendererUtils.checkParamValidity(facesContext, uiComponent, InputHtml.class);
286         InputHtml editor = (InputHtml) uiComponent;
287         String submittedDocument = editor.getValueFromDocument((String)submittedValue);
288         return RendererUtils.getConvertedUIOutputValue(facesContext, editor, submittedDocument);
289     }
290 }