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.trinidad.render;
20
21 import java.io.IOException;
22 import java.util.List;
23
24 import javax.faces.component.NamingContainer;
25 import javax.faces.component.UIComponent;
26 import javax.faces.component.UIForm;
27 import javax.faces.context.FacesContext;
28
29 import javax.faces.render.Renderer;
30
31 import org.apache.myfaces.trinidad.component.UIXForm;
32 import org.apache.myfaces.trinidad.logging.TrinidadLogger;
33 import org.apache.myfaces.trinidad.util.ComponentUtils;
34
35 /**
36 * Generic utilities for rendering.
37 *
38 */
39 public class RenderUtils
40 {
41 private RenderUtils()
42 {
43 }
44
45
46 /**
47 * Encodes a component and all of its children.
48 */
49 @SuppressWarnings("unchecked")
50 static public void encodeRecursive(FacesContext context,
51 UIComponent component)
52 throws IOException
53 {
54 if (component.isRendered())
55 {
56 component.encodeBegin(context);
57 if (component.getRendersChildren())
58 {
59 component.encodeChildren(context);
60 }
61 else
62 {
63 if (component.getChildCount() > 0)
64 {
65 for(UIComponent child : (List<UIComponent>)component.getChildren())
66 {
67 encodeRecursive(context, child);
68 }
69 }
70 }
71
72 component.encodeEnd(context);
73 }
74 }
75
76 /**
77 * Retrieves id of the form the component is contained in.
78 *
79 * @param context the faces context object
80 * @param component UIComponent whose container form id is to be retuirned
81 *
82 * @return String id of the form if one exists in component's hierarchy,
83 * otherwise null
84 */
85 public static String getFormId(
86 FacesContext context,
87 UIComponent component)
88 {
89 UIComponent form = null;
90 while (component != null)
91 {
92 if ((component instanceof UIForm) ||
93 (component instanceof UIXForm))
94 {
95 form = component;
96 break;
97 }
98
99 component = component.getParent();
100 }
101
102 if (form == null)
103 return null;
104
105 return form.getClientId(context);
106 }
107
108 /**
109 * Given a 'from' component and a relativeId,
110 * return the clientId for use at rendering time that will identify the
111 * id of the component you are referencing on the client.
112 * This is used for attributes like e.g. "for" and "chooseId".
113 *
114 * <p>
115 * e.g., given this hierarchy
116 * <br/>
117 * <f:subview id="aaa">
118 * <f:subview id="xxx"><br/>
119 <tr:chooseColor id="cp1" .../><br/>
120 <f:subview id="yyy"><br/>
121 <tr:inputColor id="sic1" chooseId="::cp1" .../><br/>
122 </f:subview><br/>
123 </f:subview>
124 </f:subview><br/>
125 </p>
126 <p>
127 * The 'from' component is the inputColor component.
128 * The 'relativeId' is "::cp1". ('::' pops up one naming container)
129 * The return value is 'aaa:xxx:cp1' when
130 * the clientId of the 'xxx' component is 'aaa:xxx'.
131 *
132 * </p>
133 * <p>
134 * It does not assume that the target component can be located, although it does
135 * check. If it can't be found, it returns the correct relativeId anyway.
136 * </p>
137 * <p>
138 * A relativeId starting with
139 * NamingContainer.SEPARATOR_CHAR (that is, ':') will be
140 * treated as absolute (after dropping that character).
141 * A relativeId with no colons means it is within the same naming container
142 * as the 'from' component (this is within the 'from' component if 'from'
143 * is a naming container).
144 * A relativeId starting with '::' pops out of the 'from' component's
145 * naming container. If the 'from' component is a naming container, then
146 * '::' will pop out of the 'from' component itself. A relativeId with ':::' pops up two naming containers, etc.
147 * ComponentUtils.findRelativeComponent finds and returns the component, whereas
148 * this method returns a relativeId that can be used during renderering
149 * so the component can be found in javascript on the client.
150 * </p>
151 * @param context
152 * @param from the component to search relative to
153 * @param scopedId the relative id path from the 'from' component to the
154 * component to find
155 * @return the clientId for the 'relative' component.
156 * @see ComponentUtils#findRelativeComponent
157 * @see javax.faces.component.UIComponent#findComponent
158
159 */
160 public static String getRelativeId(
161 FacesContext context,
162 UIComponent from,
163 String scopedId)
164 {
165 if (from == null)
166 return null;
167
168 if ((scopedId == null) || (scopedId.length() == 0))
169 return null;
170
171 // Figure out how many colons
172 int colonCount = _getColonCount(scopedId);
173
174 // colonCount == 0: fully relative
175 // colonCount == 1: absolute
176 // colonCount > 1: for each extra colon after 1, pop out of
177 // the naming container (to the view root, if naming containers run out)
178
179 if (colonCount == 1)
180 return scopedId.substring(1);
181 if (colonCount == 0 && !(from instanceof NamingContainer))
182 {
183 // we do it the fast way if there
184 // are no colons and the from isn't a NamingContainer.
185 // the reason is this use case hasn't changed between the previous
186 // logic and the current logic for finding the component, so it
187 // is already backward compatible, and therefore we don't have to
188 // call the findComponent code for backward compatibility.
189 return _getRelativeId(context, from, scopedId, colonCount);
190 }
191
192 //
193 // We need to make it backward compatible, and
194 // the only way is to use the findRelativeComponent code.
195 // This way we'll have a hint that the syntax is 'old' if
196 // it can't be found. Plus, findRelativeComponent code has
197 // backward compatibilty built in.
198 UIComponent component =
199 ComponentUtils.findRelativeComponent(from, scopedId);
200 if (component == null && from instanceof NamingContainer)
201 {
202 component = ComponentUtils.findRelativeComponent(from.getParent(), scopedId);
203 if (component != null)
204 {
205 _LOG.warning("DEPRECATED_RELATIVE_ID_SYNTAX",
206 new Object[] {scopedId, from});
207 }
208 }
209
210 // the component wasn't found, but go ahead and return something smart
211 if (component == null)
212 {
213 return _getRelativeId(context, from, scopedId, colonCount);
214 }
215 else
216 {
217 return component.getClientId(context);
218 }
219
220 }
221
222 /**
223 * Returns the clientId used by the renderer in place of the clientId used by the component.
224 * Certain renderers render their root element with a clientId that differs from the one
225 * used by the component.
226 * @param context FacesContent.
227 * @param component UIComponent.
228 * @return component clientId if the renderer clientId is null. Otherwise clientId used by
229 * renderer.
230 */
231 public static String getRendererClientId(
232 FacesContext context,
233 UIComponent component)
234 {
235 String clientId = component.getClientId(context);
236 String family = component.getFamily();
237 String rendererType = component.getRendererType();
238 if (rendererType != null)
239 {
240 Renderer renderer = context.getRenderKit().getRenderer(family, rendererType);
241 if (renderer instanceof CoreRenderer)
242 {
243 CoreRenderer coreRenderer = (CoreRenderer) renderer;
244 String rendererClientId = coreRenderer.getClientId(context, component);
245 return rendererClientId == null ? clientId : rendererClientId;
246 }
247 }
248 return clientId;
249 }
250
251 // This does NOT use findComponent
252 // ComponentUtils.findRelativeComponent finds the component, whereas
253 // this method returns a relativeId that can be used during renderering
254 // so the component can be found in javascript on the client.
255 // This code is faster because it doesn't have to find the component.
256 // It is used when the getRelativeId's findRelativeComponent cannot find
257 // the component. This way we can return the relativeId anyway.
258 private static String _getRelativeId(
259 FacesContext context,
260 UIComponent from,
261 String relativeId,
262 int colonCount)
263 {
264
265
266 if (colonCount == 1)
267 return relativeId.substring(1);
268 else if (colonCount > 1)
269 {
270 relativeId = relativeId.substring(colonCount);
271 }
272
273 // if the component is not a NamingContainer, then we need to
274 // get the component's naming container and set this as the 'from'.
275
276 if (!(from instanceof NamingContainer))
277 {
278 from = _getParentNamingContainer(from);
279 }
280 // pop out of the naming containers if there are multiple colons
281 // from will be null if there are no more naming containers
282 for (int j = 1; j < colonCount; j++)
283 {
284 from = _getParentNamingContainer(from);
285 }
286
287 // assumption is no one but the parent naming container modifies its
288 // client id
289 if (from == null)
290 return relativeId;
291 else
292 {
293 return (from.getClientId(context) +
294 NamingContainer.SEPARATOR_CHAR + relativeId);
295 }
296
297
298 }
299
300
301 // Given a component, get its naming container. If the component
302 // is a naming container, it will get its naming container.
303 // This is different than the one in ComponentUtils. This one
304 // returns null if there are no NamingContainers. The other one
305 // returns the ViewRoot.
306 private static UIComponent _getParentNamingContainer (
307 UIComponent from)
308 {
309
310 while (from != null && from.getParent() != null)
311 {
312 from = from.getParent();
313 if (from instanceof NamingContainer)
314 return from;
315 }
316
317 return null;
318 }
319
320 // Figure out how many colons
321 private static int _getColonCount(String relativeId)
322 {
323 int idLength = relativeId.length();
324 int colonCount = 0;
325 while (colonCount < idLength)
326 {
327 if (relativeId.charAt(colonCount) != NamingContainer.SEPARATOR_CHAR)
328 break;
329 colonCount++;
330 }
331 return colonCount;
332 }
333
334 static private final TrinidadLogger _LOG =
335 TrinidadLogger.createTrinidadLogger(RenderUtils.class);
336
337 }