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.focus2;
20  
21  import java.io.IOException;
22  import java.util.Iterator;
23  import java.util.Map;
24  
25  import javax.faces.component.ContextCallback;
26  import javax.faces.component.EditableValueHolder;
27  import javax.faces.component.UIComponent;
28  import javax.faces.component.UIForm;
29  import javax.faces.context.FacesContext;
30  import javax.faces.context.ResponseWriter;
31  import javax.faces.render.Renderer;
32  
33  import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
34  /**
35   * 
36   * @JSFRenderer
37   *   renderKitId = "HTML_BASIC" 
38   *   family = "javax.faces.Output"
39   *   type = "org.apache.myfaces.Focus2"
40   *
41   */
42  public class HtmlFocusRenderer extends Renderer
43  {
44  
45      protected void setSubmittedValue(UIComponent component, Object value)
46      {
47          ((EditableValueHolder) component).setSubmittedValue(value);
48      }
49  
50      public void decode(FacesContext context, UIComponent component)
51      {
52          super.decode(context, component);
53          RendererUtils.checkParamValidity(context, component, HtmlFocus.class);
54          HtmlFocus focus = (HtmlFocus) component;
55          focus.setSubmittedValue(getCurrentFocusedClientId(context, component));
56      }
57      
58      private Object getCurrentFocusedClientId(FacesContext context, UIComponent component) 
59      {
60          Map paramMap = context.getExternalContext().getRequestParameterMap();
61          String clientId = component.getClientId(context);
62          return paramMap.get(clientId);
63      }
64  
65      public void encodeEnd(FacesContext context, UIComponent component)
66              throws IOException
67      {
68          HtmlFocus focus = (HtmlFocus) component;
69  
70          String focusForId = getFocusForId(context, focus);
71  
72          ResponseWriter writer = context.getResponseWriter();
73  
74          writeForElementStore(context, component, focusForId);
75  
76          startScript(component, writer);
77          if (focusForId != null && focusForId.length() > 0)
78          {
79              writeSetFocusScript(component, focusForId, writer);
80          }
81          String focusAndSubmitOnEnter = focus.getFocusAndSubmitOnEnter();
82          if (focusAndSubmitOnEnter != null && focusAndSubmitOnEnter.length() > 0)
83          {
84              UIComponent targetComponent = focus
85                      .findComponent(focusAndSubmitOnEnter);
86              if (targetComponent == null)
87                  throw new IllegalStateException("Button component for id :"
88                          + focusAndSubmitOnEnter + " not found.");
89              writeSetDefaultButtonScript(component, targetComponent
90                      .getClientId(context), context);
91          }
92          writeUpdateFocusScript(context, component);
93          endScript(writer);
94  
95      }
96  
97      private void writeUpdateFocusScript(FacesContext context,
98              UIComponent component) throws IOException
99      {
100 
101         UIComponent formParent = getFormParent(component);
102 
103         if (formParent == null)
104             throw new IllegalStateException("parent form for component: "
105                     + component.getClientId(context) + " not found. ");
106 
107         String compClientId = component.getClientId(context);
108         String formParentClientId = formParent.getClientId(context);
109 
110         StringBuilder b = new StringBuilder();
111 
112         b.append("\nfunction csDomTrackActiveElement(evt) {\n");
113         b.append("  if (evt && evt.target) { \n");
114         b.append("    if (evt.target == document) {\n");
115         b.append("      document.activeElement = null;\n");
116         b.append("    } else {\n");
117         b.append("      if (evt.target.type != 'submit') {\n");
118         b.append("        document.activeElement = evt.target; ;\n");
119         b.append("      }\n");
120         b.append("    }\n");
121         b.append("  }\n");
122         b.append("}\n");
123         b.append("if (document.addEventListener) { \n");
124         b
125                 .append("document.addEventListener('focus',csDomTrackActiveElement,true);\n");
126         b.append("}\n");
127         b.append("  var csFocusParentForm =document.getElementById(\"").append(
128                 formParentClientId).append("\");\n");
129         b
130                 .append("  csFocusParentForm.csFocusSubmitSaver =csFocusParentForm.submit;\n");
131         b
132                 .append("  csFocusParentForm.csFocusOnsubmitSaver =csFocusParentForm.onsubmit;\n");
133 
134         b.append("  csFocusParentForm.submit=function() {\n");
135         transferId(compClientId, b);
136         b.append("    if(csFocusParentForm.csFocusSubmitSaver) ");
137         b.append("      return csFocusParentForm.csFocusSubmitSaver();\n");
138         b.append("  }\n");
139         b.append("  csFocusParentForm.onsubmit=function() {\n");
140         transferId(compClientId, b);
141         b.append("    if(csFocusParentForm.csFocusOnsubmitSaver) ");
142         b.append("      return csFocusParentForm.csFocusOnsubmitSaver();\n");
143         b.append("  }\n");
144 
145         context.getResponseWriter().write(b.toString());
146     }
147 
148     private void transferId(String compClientId, StringBuilder b)
149     {
150         b.append("  if(document.activeElement) {\n");
151         b
152                 .append("document.getElementById(\"")
153                 .append(compClientId)
154                 .append(
155                         "\").value=document.activeElement.id?document.activeElement.id:document.activeElement.name;\n");
156         b.append("  }\n");
157     }
158 
159     private UIComponent getFormParent(UIComponent component)
160     {
161         UIComponent parent = component;
162 
163         while (parent != null && !(parent instanceof UIForm))
164         {
165             parent = parent.getParent();
166         }
167 
168         return parent;
169     }
170 
171     private void writeForElementStore(FacesContext context,
172             UIComponent component, String currentFocusClientId)
173             throws IOException
174     {
175         ResponseWriter writer = context.getResponseWriter();
176         String clientId = component.getClientId(context);
177         writer.startElement("input", component);
178         writer.writeAttribute("type", "hidden", "type");
179         writer.writeAttribute("name", clientId, "clientId");
180         writer.writeAttribute("id", clientId, "clientId");
181         writer.writeAttribute("value", currentFocusClientId, "defaultFocusId");
182         writer.endElement("input");
183     }
184 
185     private void writeSetFocusScript(UIComponent component, String clientId,
186             ResponseWriter writer) throws IOException
187     {
188         writer.writeText("setTimeout(\"document.getElementById('" + clientId
189                 + "').focus()\", 100);", null);
190     }
191 
192     private void writeSetDefaultButtonScript(UIComponent component,
193             String clientId, FacesContext context) throws IOException
194     {
195 
196         String formClientId = getFormParent(component).getClientId(context);
197 
198         StringBuilder b = new StringBuilder(3000);
199 
200         b.append("  var csFocusKeyParentForm =document.getElementById(\"")
201                 .append(formClientId).append("\");\n");
202         b
203                 .append("\n\ncsFocusKeyParentForm.csFocusOnkeypressSaver =csFocusKeyParentForm.onkeypress;\n");
204 
205         b.append("csFocusKeyParentForm.onkeypress=function(evt) {\n");
206         b.append("  var retValue = csSubmitOnEnter(evt,'" + formClientId
207                 + "','" + clientId + "'); \n");
208         b.append("    if(csFocusKeyParentForm.csFocusOnkeypressSaver) {\n");
209         b
210                 .append("      retValue =csFocusKeyParentForm.csFocusOnkeypressSaver(evt) && retValue;\n");
211         b.append("    }\n");
212         b.append("  return retValue;\n");
213         b.append("}\n");
214         b.append("\n");
215 
216         b.append("function csGetKC(evt)\n");
217         b.append("{\n");
218         b.append("if(window.event)\n");
219         b.append("    return window.event.keyCode;\n");
220         b.append("  else if(evt)\n");
221         b.append("    return evt.which;\n");
222         b.append("  return -1;\n");
223         b.append("}\n\n");
224 
225         b.append("function csSubmitOnEnter(evt,formId,buttonId)\n");
226         b.append("{\n");
227         b.append("  if(window.event!=(void 0))\n");
228         b.append("    evt=window.event;\n");
229         b.append("  var fromElement;\n");
230         b.append("  if(evt.srcElement==undefined)\n");
231         b.append("    fromElement=evt.target;\n");
232         b.append("  else\n");
233         b.append("    fromElement=evt.srcElement;\n");
234         b.append("  if(!fromElement) return true;\n");
235         b.append("  if(fromElement.tagName.toUpperCase()=='A') return true;\n");
236         b.append("  if((fromElement.tagName.toUpperCase()=='INPUT')&&\n");
237         b.append("     (fromElement.type!='submit')&&\n");
238         b.append("     (fromElement.type!='reset'))\n");
239         b.append("  {\n");
240         b.append("    if(csGetKC(evt)==13)\n");
241         b.append("    {\n");
242         b.append("      if(buttonId!=(void 0))\n");
243         b.append("     {\n");
244         b.append("        document.getElementById(buttonId).click();\n");
245         b.append("      }\n");
246         b.append("     return false;\n");
247         b.append("    }\n");
248         b.append("  }\n");
249         b.append("  return true;\n");
250         b.append("}\n");
251 
252         context.getResponseWriter().writeText(b.toString(), null);
253     }
254 
255     private void endScript(ResponseWriter writer) throws IOException
256     {
257         writer.writeText("/*]]>*/", null);
258         writer.endElement("script");
259     }
260 
261     private void startScript(UIComponent component, ResponseWriter writer)
262             throws IOException
263     {
264         writer.startElement("script", component);
265         writer.writeAttribute("type", "text/javascript", null);
266         writer.writeText("/*<![CDATA[*/", null);
267     }
268 
269     /**
270      *
271      * @param context
272      * @param focus
273      * @return
274      */
275     private String getFocusForId(FacesContext context, HtmlFocus focus)
276     {
277 
278         //#1: override focus-id has been set
279         String forId = focus.getOverrideFocusId();
280         if (forId != null && forId.length() > 0)
281         {
282             UIComponent targetComponent = focus.findComponent(forId);
283 
284             if (targetComponent == null)
285             {
286                 throw new IllegalStateException("target-component for id :"
287                         + forId + " not found.");
288             }
289             return targetComponent.getClientId(context);
290         }
291 
292         //#2: error-messages have been queued
293         if (focus.isFocusOnError())
294         {
295             Iterator it = context.getMessages();
296             Iterator msgs = context.getClientIdsWithMessages();
297             if (msgs.hasNext())
298             {
299                 String clientId = (String) msgs.next();
300                 return clientId;
301             }
302         }
303 
304         //#3: a value has already been submitted, take this one
305         String clientId = (String) focus.getValue();
306         if (clientId != null && clientId.length() > 0)
307         {
308             boolean isPostBack = context.getRenderKit().getResponseStateManager().isPostback(context);
309             if(isPostBack) 
310             {
311                 final StringHolder nextClientId = new StringHolder();
312                 context.getViewRoot().invokeOnComponent(context, clientId,
313                         new ContextCallback()
314                         {
315     
316                             public void invokeContextCallback(FacesContext context,
317                                     UIComponent target)
318                             {
319                                 String nextClientIdString = getNextValueHolder(
320                                         context, target);
321                                 if (nextClientIdString != null)
322                                     nextClientId.string = nextClientIdString;
323                             }
324                         });
325     
326                 if (nextClientId.string != null && nextClientId.string.length() > 0)
327                     return nextClientId.string;
328             }
329             return clientId;
330         }
331 
332         //#4: focus on the first (rendered) editable value holder
333         if (focus.isFocusOnFirst())
334         {
335             UIComponent comp = getFormParent(focus);
336             String firstClientId = getNextValueHolder(context, comp);
337             if (firstClientId != null)
338                 return firstClientId;
339         }
340 
341         return clientId;
342     }
343 
344     private static class StringHolder
345     {
346         public String string;
347     }
348 
349     private static String getNextValueHolder(FacesContext context,
350             UIComponent comp)
351     {
352 
353         final StringHolder holder = new StringHolder();
354 
355         TreeVisitor.traverseTree(context,
356                 new TreeVisitor.TreeTraversalListener()
357                 {
358 
359                     private int count = 0;
360 
361                     public boolean traverse(FacesContext context, int level,
362                             UIComponent component)
363                     {
364                         if (count > 0)
365                         {
366                             if (!(component instanceof HtmlFocus)
367                                     && (component instanceof EditableValueHolder && component.isRendered())
368                             //&& (!(component instanceof Field) || (component instanceof Field && isVisible(component)))
369                             )
370                             {
371                                 holder.string = component.getClientId(context);
372                                 return false;
373                             }
374                         }
375 
376                         count++;
377                         return true;
378                     }
379 
380                     private boolean isVisible(UIComponent component)
381                     {
382                         //Field field = (Field) component;
383                         //return (("mutable".equals(field.getDisplay()) || "required"
384                         //       .equals(field.getDisplay()))
385                         //        && !field.isReadonly() && !field.isDisabled())
386                         //        && !"label".equals(field.getOnly());
387                         return true;
388                     }
389                 }, comp);
390 
391         return holder.string;
392     }
393 
394 }