View Javadoc
1   /*
2    * $Id$
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  package org.apache.struts.faces.renderer;
23  
24  
25  import java.io.IOException;
26  
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  import jakarta.faces.component.UIComponent;
31  import jakarta.faces.component.UIForm;
32  import jakarta.faces.component.UINamingContainer;
33  import jakarta.faces.component.UIParameter;
34  import jakarta.faces.context.FacesContext;
35  import jakarta.faces.context.ResponseWriter;
36  import jakarta.faces.event.ActionEvent;
37  
38  
39  /**
40   * <p><code>Renderer</code> implementation for the <code>commandLink</code>
41   * tag from the <em>Struts-Faces Integration Library</em>.</p>
42   *
43   * @version $Rev$ $Date$
44   */
45  
46  public class CommandLinkRenderer extends AbstractRenderer {
47  
48  
49      // -------------------------------------------------------- Static Variables
50  
51  
52      /**
53       * <p>Token for private names.</p>
54       */
55      private final static String TOKEN =
56          "org_apache_struts_faces_renderer_CommandLinkRenderer";
57  
58  
59      /**
60       * The {@code Log} instance for this class.
61       */
62      private final Logger log =
63          LoggerFactory.getLogger(CommandLinkRenderer.class);
64  
65  
66      // ---------------------------------------------------------- Public Methods
67  
68  
69      /**
70       * <p>Perform setup processing that will be required for decoding the
71       * incoming request.</p>
72       *
73       * @param context FacesContext for the request we are processing
74       * @param component UIComponent to be processed
75       *
76       * @exception NullPointerException if <code>context</code>
77       *  or <code>component</code> is null
78       */
79      public void decode(FacesContext context, UIComponent component) {
80  
81          // Implement spec requirements on NullPointerException
82          if ((context == null) || (component == null)) {
83              throw new NullPointerException();
84          }
85  
86          // Skip this component if it is not relevant
87          if (!component.isRendered() || isDisabled(component) ||
88              isReadOnly(component)) {
89              return;
90          }
91  
92          // Set up variables we will need
93          UIForm form = null;
94          UIComponent parent = component.getParent();
95          while (parent != null) {
96              if (parent instanceof UIForm) {
97                  form = (UIForm) parent;
98                  break;
99              }
100             parent = parent.getParent();
101         }
102         if (form == null) {
103             log.warn("CommandLinkComponent not nested inside UIForm, ignored");
104             return;
105         }
106 
107         // Was this the component that submitted this form?
108         String paramId = TOKEN;
109         String value = context.getExternalContext().getRequestParameterMap().get(paramId);
110         if ((value == null) || !value.equals(component.getClientId(context))) {
111             log.trace("decode({}) --> not active", component.getId());
112             return;
113         }
114 
115         // Queue an ActionEvent from this component
116         log.trace("decode({}) --> queueEvent()", component.getId());
117         component.queueEvent(new ActionEvent(component));
118 
119     }
120 
121 
122     private static String passThrough[] =
123     { "accesskey", "charset", "dir", "hreflang", "lang", "onblur",
124       /* "onclick", */ "ondblclick", "onfocus", "onkeydown",
125       "onkeypress", "onkeyup", "onmousedown", "onmousemove",
126       "onmouseout", "onmouseover", "onmouseup", "rel", "rev",
127       "style", "tabindex", "target", "title", "type" };
128 
129 
130     /**
131      * <p>Render the beginning of a hyperlink to submit this form.</p>
132      *
133      * @param context FacesContext for the request we are processing
134      * @param component UIComponent to be rendered
135      * @param writer ResponseWriter we are rendering to
136      *
137      * @exception IOException if an input/output error occurs while rendering
138      * @exception NullPointerException if <code>context</code>
139      *  or <code>component</code> is null
140      */
141     public void renderStart(FacesContext context, UIComponent component,
142                             ResponseWriter writer)
143         throws IOException {
144 
145         // Skip this component if it is not relevant
146         if (!component.isRendered() || isDisabled(component) ||
147             isReadOnly(component)) {
148             return;
149         }
150 
151         // Set up variables we will need
152         UIForm form = null;
153         UIComponent parent = component.getParent();
154         while (parent != null) {
155             if (parent instanceof UIForm) {
156                 form = (UIForm) parent;
157                 break;
158             }
159             parent = parent.getParent();
160         }
161         if (form == null) {
162             log.warn("CommandLinkComponent not nested inside UIForm, ignored");
163             return;
164         }
165         String formClientId = form.getClientId(context);
166 
167         // If this is the first nested command link inside this form,
168         // render a hidden variable to identify which link did the submit
169         String key = formClientId + UINamingContainer.getSeparatorChar(context) + TOKEN;
170         if (context.getExternalContext().getRequestMap().get(key) == null) {
171             writer.startElement("input", null);
172             writer.writeAttribute("name", TOKEN, null);
173             writer.writeAttribute("type", "hidden", null);
174             writer.writeAttribute("value", "", null);
175             writer.endElement("input");
176             context.getExternalContext().getRequestMap().put
177                 (key, Boolean.TRUE);
178         }
179 
180 
181         // Render the beginning of this hyperlink
182         writer.startElement("a", component);
183 
184     }
185 
186 
187     /**
188      * <p>Render the attributes of a hyperlink to submit this form.</p>
189      *
190      * @param context FacesContext for the request we are processing
191      * @param component UIComponent to be rendered
192      * @param writer ResponseWriter we are rendering to
193      *
194      * @exception IOException if an input/output error occurs while rendering
195      * @exception NullPointerException if <code>context</code>
196      *  or <code>component</code> is null
197      */
198     public void renderAttributes(FacesContext context, UIComponent component,
199                                  ResponseWriter writer)
200         throws IOException {
201 
202         // Skip this component if it is not relevant
203         if (!component.isRendered() || isDisabled(component) ||
204             isReadOnly(component)) {
205             return;
206         }
207 
208         // Set up variables we will need
209         UIComponent form = null;
210         UIComponent parent = component.getParent();
211         while (parent != null) {
212             if (parent instanceof UIForm ||
213                 "org.apache.myfaces.trinidad.Form".equals(parent.getFamily()) ||
214                 "oracle.adf.Form".equals(parent.getFamily())) {
215                 form = parent;
216                 break;
217             }
218             parent = parent.getParent();
219         }
220         if (form == null) {
221             log.warn("CommandLinkComponent not nested inside UIForm, ignored");
222             return;
223         }
224         String formClientId = form.getClientId(context);
225 
226         // Render the attributes of this hyperlink
227         if (component.getId() != null) {
228             writer.writeAttribute("id", component.getClientId(context), "id");
229         }
230         writer.writeAttribute("href", "#", null);
231         String styleClass = (String)
232             component.getAttributes().get("styleClass");
233         if (styleClass != null) {
234             writer.writeAttribute("class", styleClass, "styleClass");
235         }
236         renderPassThrough(context, component, writer, passThrough);
237 
238         // Render the JavaScript content of the "onclick" element
239         StringBuilder sb = new StringBuilder();
240         sb.append("document.forms['");
241         sb.append(formClientId);
242         sb.append("']['");
243         sb.append(TOKEN);
244         sb.append("'].value='");
245         sb.append(component.getClientId(context));
246         sb.append("';");
247         for (UIComponent kid : component.getChildren()) {
248             if (!(kid instanceof UIParameter)) {
249                 continue;
250             }
251             sb.append("document.forms['");
252             sb.append(formClientId);
253             sb.append("']['");
254             sb.append((String) kid.getAttributes().get("name"));
255             sb.append("'].value='");
256             sb.append((String) kid.getAttributes().get("value"));
257             sb.append("';");
258         }
259         sb.append("document.forms['");
260         sb.append(formClientId);
261         sb.append("'].submit(); return false;");
262         writer.writeAttribute("onclick", sb.toString(), null);
263 
264         // Render the component value as the hyperlink text
265         Object value = component.getAttributes().get("value");
266         if (value != null) {
267             if (value instanceof String) {
268                 writer.write((String) value);
269             } else {
270                 writer.write(value.toString());
271             }
272         }
273 
274     }
275 
276 
277     /**
278      * <p>Render the end of a hyperlink to submit this form.</p>
279      *
280      * @param context FacesContext for the request we are processing
281      * @param component UIComponent to be rendered
282      * @param writer ResponseWriter we are rendering to
283      *
284      * @exception IOException if an input/output error occurs while rendering
285      * @exception NullPointerException if <code>context</code>
286      *  or <code>component</code> is null
287      */
288     public void renderEnd(FacesContext context, UIComponent component,
289                           ResponseWriter writer)
290         throws IOException {
291 
292         // Skip this component if it is not relevant
293         if (!component.isRendered() || isDisabled(component) ||
294             isReadOnly(component)) {
295             return;
296         }
297 
298         // Render the beginning of this hyperlink
299         writer.endElement("a");
300 
301     }
302 }