001/* 002 * (C) Copyright 2007 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 * Nuxeo - initial API and implementation 018 * Sean Radford 019 * 020 * $Id: ComponentUtils.java 28924 2008-01-10 14:04:05Z sfermigier $ 021 */ 022 023package org.nuxeo.ecm.platform.ui.web.util; 024 025import java.io.File; 026import java.io.IOException; 027import java.io.Serializable; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.Collections; 031import java.util.List; 032import java.util.Locale; 033import java.util.Map; 034 035import javax.el.ValueExpression; 036import javax.faces.application.FacesMessage; 037import javax.faces.component.UIComponent; 038import javax.faces.component.UISelectItems; 039import javax.faces.component.UISelectMany; 040import javax.faces.context.ExternalContext; 041import javax.faces.context.FacesContext; 042import javax.faces.model.SelectItem; 043import javax.servlet.http.HttpServletRequest; 044import javax.servlet.http.HttpServletResponse; 045 046import org.apache.commons.lang3.StringUtils; 047import org.apache.commons.logging.Log; 048import org.apache.commons.logging.LogFactory; 049import org.nuxeo.common.utils.i18n.I18NUtils; 050import org.nuxeo.ecm.core.api.Blob; 051import org.nuxeo.ecm.core.api.Blobs; 052import org.nuxeo.ecm.core.api.DocumentModel; 053import org.nuxeo.ecm.core.io.download.DownloadService; 054import org.nuxeo.ecm.platform.ui.web.component.list.UIEditableList; 055import org.nuxeo.runtime.api.Framework; 056 057/** 058 * Generic component helper methods. 059 * 060 * @author <a href="mailto:[email protected]">Anahide Tchertchian</a> 061 */ 062public final class ComponentUtils { 063 064 public static final String WHITE_SPACE_CHARACTER = " "; 065 066 private static final Log log = LogFactory.getLog(ComponentUtils.class); 067 068 public static final String FORCE_NO_CACHE_ON_MSIE = "org.nuxeo.download.force.nocache.msie"; 069 070 // Utility class. 071 private ComponentUtils() { 072 } 073 074 /** 075 * Calls a component encodeBegin/encodeChildren/encodeEnd methods. 076 */ 077 public static void encodeComponent(FacesContext context, UIComponent component) throws IOException { 078 component.encodeBegin(context); 079 component.encodeChildren(context); 080 component.encodeEnd(context); 081 } 082 083 /** 084 * Helper method meant to be called in the component constructor. 085 * <p> 086 * When adding sub components dynamically, the tree fetching could be a problem so all possible sub components must 087 * be added. 088 * <p> 089 * Since 6.0, does not mark component as not rendered anymore, calls 090 * {@link #hookSubComponent(FacesContext, UIComponent, UIComponent, String)} directly. 091 * 092 * @param parent 093 * @param child 094 * @param facetName facet name to put the child in. 095 */ 096 public static void initiateSubComponent(UIComponent parent, String facetName, UIComponent child) { 097 parent.getFacets().put(facetName, child); 098 hookSubComponent(null, parent, child, facetName); 099 } 100 101 /** 102 * Add a sub component to a UI component. 103 * <p> 104 * Since 6.0, does not the set the component as rendered anymore. 105 * 106 * @param context 107 * @param parent 108 * @param child 109 * @param defaultChildId 110 * @return child comp 111 */ 112 public static UIComponent hookSubComponent(FacesContext context, UIComponent parent, UIComponent child, 113 String defaultChildId) { 114 // build a valid id using the parent id so that it's found everytime. 115 String childId = child.getId(); 116 if (defaultChildId != null) { 117 // override with default 118 childId = defaultChildId; 119 } 120 // make sure it's set 121 if (childId == null) { 122 childId = context.getViewRoot().createUniqueId(); 123 } 124 // reset client id 125 child.setId(childId); 126 child.setParent(parent); 127 return child; 128 } 129 130 /** 131 * Copies attributes and value expressions with given name from parent component to child component. 132 */ 133 public static void copyValues(UIComponent parent, UIComponent child, String[] valueNames) { 134 Map<String, Object> parentAttributes = parent.getAttributes(); 135 Map<String, Object> childAttributes = child.getAttributes(); 136 for (String name : valueNames) { 137 // attributes 138 if (parentAttributes.containsKey(name)) { 139 childAttributes.put(name, parentAttributes.get(name)); 140 } 141 // value expressions 142 ValueExpression ve = parent.getValueExpression(name); 143 if (ve != null) { 144 child.setValueExpression(name, ve); 145 } 146 } 147 } 148 149 public static void copyLinkValues(UIComponent parent, UIComponent child) { 150 String[] valueNames = { "accesskey", "charset", "coords", "dir", "disabled", "hreflang", "lang", "onblur", 151 "onclick", "ondblclick", "onfocus", "onkeydown", "onkeypress", "onkeyup", "onmousedown", "onmousemove", 152 "onmouseout", "onmouseover", "onmouseup", "rel", "rev", "shape", "style", "styleClass", "tabindex", 153 "target", "title", "type" }; 154 copyValues(parent, child, valueNames); 155 } 156 157 public static Object getAttributeValue(UIComponent component, String attributeName, Object defaultValue) { 158 return getAttributeValue(component, attributeName, Object.class, defaultValue, false); 159 } 160 161 /** 162 * @since 8.2 163 */ 164 public static <T> T getAttributeValue(UIComponent component, String name, Class<T> klass, T defaultValue, 165 boolean required) { 166 Object value = component.getAttributes().get(name); 167 if (value == null) { 168 value = defaultValue; 169 } 170 if (required && value == null) { 171 throw new IllegalArgumentException("Component attribute with name '" + name + "' cannot be null: " + value); 172 } 173 if (value == null || value.getClass().isAssignableFrom(klass)) { 174 return (T) value; 175 } 176 throw new IllegalArgumentException( 177 "Component attribute with name '" + name + "' is not a " + klass + ": " + value); 178 } 179 180 public static Object getAttributeOrExpressionValue(FacesContext context, UIComponent component, 181 String attributeName, Object defaultValue) { 182 Object value = component.getAttributes().get(attributeName); 183 if (value == null) { 184 ValueExpression schemaExpr = component.getValueExpression(attributeName); 185 value = schemaExpr.getValue(context.getELContext()); 186 } 187 if (value == null) { 188 value = defaultValue; 189 } 190 return value; 191 } 192 193 /** 194 * Downloads a blob and sends it to the requesting user, in the JSF current context. 195 * 196 * @param doc the document, if available 197 * @param xpath the blob's xpath or blobholder index, if available 198 * @param blob the blob, if already fetched 199 * @param filename the filename to use 200 * @param reason the download reason 201 * @since 7.3 202 */ 203 public static void download(DocumentModel doc, String xpath, Blob blob, String filename, String reason) { 204 download(doc, xpath, blob, filename, reason, Collections.emptyMap()); 205 } 206 207 /** 208 * Downloads a blob and sends it to the requesting user, in the JSF current context. 209 * 210 * @param doc the document, if available 211 * @param xpath the blob's xpath or blobholder index, if available 212 * @param blob the blob, if already fetched 213 * @param filename the filename to use 214 * @param reason the download reason 215 * @param extendedInfos an optional map of extended informations to log 216 * @since 7.3 217 */ 218 public static void download(DocumentModel doc, String xpath, Blob blob, String filename, String reason, 219 Map<String, Serializable> extendedInfos) { 220 FacesContext facesContext = FacesContext.getCurrentInstance(); 221 if (facesContext.getResponseComplete()) { 222 // nothing can be written, an error was probably already sent. don't bother 223 log.debug("Cannot send " + filename + ", response already complete"); 224 return; 225 } 226 if (facesContext.getPartialViewContext().isAjaxRequest()) { 227 // do not perform download in an ajax request 228 return; 229 } 230 ExternalContext externalContext = facesContext.getExternalContext(); 231 HttpServletRequest request = (HttpServletRequest) externalContext.getRequest(); 232 HttpServletResponse response = (HttpServletResponse) externalContext.getResponse(); 233 try { 234 DownloadService downloadService = Framework.getService(DownloadService.class); 235 downloadService.downloadBlob(request, response, doc, xpath, blob, filename, reason, extendedInfos); 236 } catch (IOException e) { 237 log.error("Error while downloading the file: " + filename, e); 238 } finally { 239 facesContext.responseComplete(); 240 } 241 } 242 243 public static String downloadFile(File file, String filename, String reason) throws IOException { 244 Blob blob = Blobs.createBlob(file); 245 download(null, null, blob, filename, reason); 246 return null; 247 } 248 249 /** 250 * @deprecated since 7.3, use {@link #downloadFile(Blob, String)} instead 251 */ 252 @Deprecated 253 public static String download(FacesContext faces, Blob blob, String filename) { 254 download(null, null, blob, filename, "download"); 255 return null; 256 } 257 258 /** 259 * @deprecated since 7.3, use {@link #downloadFile(File, String)} instead 260 */ 261 @Deprecated 262 public static String downloadFile(FacesContext faces, String filename, File file) throws IOException { 263 return downloadFile(file, filename, null); 264 } 265 266 protected static boolean forceNoCacheOnMSIE() { 267 // see NXP-7759 268 return Framework.isBooleanPropertyTrue(FORCE_NO_CACHE_ON_MSIE); 269 } 270 271 // hook translation passing faces context 272 273 public static String translate(FacesContext context, String messageId) { 274 return translate(context, messageId, (Object[]) null); 275 } 276 277 public static String translate(FacesContext context, String messageId, Object... params) { 278 String bundleName = context.getApplication().getMessageBundle(); 279 Locale locale = context.getViewRoot().getLocale(); 280 return I18NUtils.getMessageString(bundleName, messageId, evaluateParams(context, params), locale); 281 } 282 283 public static void addErrorMessage(FacesContext context, UIComponent component, String message) { 284 addErrorMessage(context, component, message, null); 285 } 286 287 public static void addErrorMessage(FacesContext context, UIComponent component, String message, Object[] params) { 288 String bundleName = context.getApplication().getMessageBundle(); 289 Locale locale = context.getViewRoot().getLocale(); 290 message = I18NUtils.getMessageString(bundleName, message, evaluateParams(context, params), locale); 291 FacesMessage msg = new FacesMessage(message); 292 msg.setSeverity(FacesMessage.SEVERITY_ERROR); 293 context.addMessage(component.getClientId(context), msg); 294 } 295 296 /** 297 * Evaluates parameters to pass to translation methods if they are value expressions. 298 * 299 * @since 5.7 300 */ 301 protected static Object[] evaluateParams(FacesContext context, Object[] params) { 302 if (params == null) { 303 return null; 304 } 305 Object[] res = new Object[params.length]; 306 for (int i = 0; i < params.length; i++) { 307 Object val = params[i]; 308 if (val instanceof String && ComponentTagUtils.isValueReference((String) val)) { 309 ValueExpression ve = context.getApplication().getExpressionFactory().createValueExpression( 310 context.getELContext(), (String) val, Object.class); 311 res[i] = ve.getValue(context.getELContext()); 312 } else { 313 res[i] = val; 314 } 315 } 316 return res; 317 } 318 319 /** 320 * Gets the base naming container from anchor. 321 * <p> 322 * Gets out of suggestion box as it's a naming container and we can't get components out of it with a relative path 323 * => take above first found container. 324 * 325 * @since 5.3.1 326 */ 327 public static UIComponent getBase(UIComponent anchor) { 328 // init base to given component in case there's no naming container for it 329 UIComponent base = anchor; 330 UIComponent container = anchor.getNamingContainer(); 331 if (container != null) { 332 UIComponent supContainer = container.getNamingContainer(); 333 if (supContainer != null) { 334 container = supContainer; 335 } 336 } 337 if (container != null) { 338 base = container; 339 } 340 if (log.isDebugEnabled()) { 341 log.debug(String.format("Resolved base '%s' for anchor '%s'", base.getId(), anchor.getId())); 342 } 343 return base; 344 } 345 346 /** 347 * Returns the component specified by the {@code componentId} parameter from the {@code base} component. 348 * <p> 349 * Does not throw any exception if the component is not found, returns {@code null} instead. 350 * 351 * @since 5.4 352 */ 353 @SuppressWarnings("unchecked") 354 public static <T> T getComponent(UIComponent base, String componentId, Class<T> expectedComponentClass) { 355 if (componentId == null) { 356 log.error("Cannot retrieve component with a null id"); 357 return null; 358 } 359 UIComponent component = ComponentRenderUtils.getComponent(base, componentId); 360 if (component == null) { 361 log.error("Could not find component with id: " + componentId); 362 } else { 363 try { 364 return (T) component; 365 } catch (ClassCastException e) { 366 log.error("Invalid component with id '" + componentId + "': " + component 367 + ", expected a component with interface " + expectedComponentClass); 368 } 369 } 370 return null; 371 } 372 373 static void clearTargetList(UIEditableList targetList) { 374 int rc = targetList.getRowCount(); 375 for (int i = 0; i < rc; i++) { 376 targetList.removeValue(0); 377 } 378 } 379 380 static void addToTargetList(UIEditableList targetList, SelectItem[] items) { 381 for (int i = 0; i < items.length; i++) { 382 targetList.addValue(items[i].getValue()); 383 } 384 } 385 386 /** 387 * Move items up inside the target select 388 */ 389 public static void shiftItemsUp(UISelectMany targetSelect, UISelectItems targetItems, 390 UIEditableList hiddenTargetList) { 391 String[] selected = (String[]) targetSelect.getSelectedValues(); 392 SelectItem[] all = (SelectItem[]) targetItems.getValue(); 393 if (selected == null) { 394 // nothing to do 395 return; 396 } 397 shiftUp(selected, all); 398 targetItems.setValue(all); 399 clearTargetList(hiddenTargetList); 400 addToTargetList(hiddenTargetList, all); 401 } 402 403 public static void shiftItemsDown(UISelectMany targetSelect, UISelectItems targetItems, 404 UIEditableList hiddenTargetList) { 405 String[] selected = (String[]) targetSelect.getSelectedValues(); 406 SelectItem[] all = (SelectItem[]) targetItems.getValue(); 407 if (selected == null) { 408 // nothing to do 409 return; 410 } 411 shiftDown(selected, all); 412 targetItems.setValue(all); 413 clearTargetList(hiddenTargetList); 414 addToTargetList(hiddenTargetList, all); 415 } 416 417 public static void shiftItemsFirst(UISelectMany targetSelect, UISelectItems targetItems, 418 UIEditableList hiddenTargetList) { 419 String[] selected = (String[]) targetSelect.getSelectedValues(); 420 SelectItem[] all = (SelectItem[]) targetItems.getValue(); 421 if (selected == null) { 422 // nothing to do 423 return; 424 } 425 all = shiftFirst(selected, all); 426 targetItems.setValue(all); 427 clearTargetList(hiddenTargetList); 428 addToTargetList(hiddenTargetList, all); 429 } 430 431 public static void shiftItemsLast(UISelectMany targetSelect, UISelectItems targetItems, 432 UIEditableList hiddenTargetList) { 433 String[] selected = (String[]) targetSelect.getSelectedValues(); 434 SelectItem[] all = (SelectItem[]) targetItems.getValue(); 435 if (selected == null) { 436 // nothing to do 437 return; 438 } 439 all = shiftLast(selected, all); 440 targetItems.setValue(all); 441 clearTargetList(hiddenTargetList); 442 addToTargetList(hiddenTargetList, all); 443 } 444 445 /** 446 * Make a new SelectItem[] with items whose ids belong to selected first, preserving inner ordering of selected and 447 * its complement in all. 448 * <p> 449 * Again this assumes that selected is an ordered sub-list of all 450 * </p> 451 * 452 * @param selected ids of selected items 453 * @param all 454 * @return 455 */ 456 static SelectItem[] shiftFirst(String[] selected, SelectItem[] all) { 457 SelectItem[] res = new SelectItem[all.length]; 458 int sl = selected.length; 459 int i = 0; 460 int j = sl; 461 for (SelectItem item : all) { 462 if (i < sl && item.getValue().toString().equals(selected[i])) { 463 res[i++] = item; 464 } else { 465 res[j++] = item; 466 } 467 } 468 return res; 469 } 470 471 /** 472 * Make a new SelectItem[] with items whose ids belong to selected last, preserving inner ordering of selected and 473 * its complement in all. 474 * <p> 475 * Again this assumes that selected is an ordered sub-list of all 476 * </p> 477 * 478 * @param selected ids of selected items 479 * @param all 480 * @return 481 */ 482 static SelectItem[] shiftLast(String[] selected, SelectItem[] all) { 483 SelectItem[] res = new SelectItem[all.length]; 484 int sl = selected.length; 485 int cut = all.length - sl; 486 int i = 0; 487 int j = 0; 488 for (SelectItem item : all) { 489 if (i < sl && item.getValue().toString().equals(selected[i])) { 490 res[cut + i++] = item; 491 } else { 492 res[j++] = item; 493 } 494 } 495 return res; 496 } 497 498 static void swap(Object[] ar, int i, int j) { 499 Object t = ar[i]; 500 ar[i] = ar[j]; 501 ar[j] = t; 502 } 503 504 static void shiftUp(String[] selected, SelectItem[] all) { 505 int pos = -1; 506 for (int i = 0; i < selected.length; i++) { 507 String s = selected[i]; 508 // "pos" is the index of previous "s" 509 int previous = pos; 510 while (!all[++pos].getValue().equals(s)) { 511 } 512 // now current "s" is at "pos" index 513 if (pos > previous + 1) { 514 swap(all, pos, --pos); 515 } 516 } 517 } 518 519 static void shiftDown(String[] selected, SelectItem[] all) { 520 int pos = all.length; 521 for (int i = selected.length - 1; i >= 0; i--) { 522 String s = selected[i]; 523 // "pos" is the index of previous "s" 524 int previous = pos; 525 while (!all[--pos].getValue().equals(s)) { 526 } 527 // now current "s" is at "pos" index 528 if (pos < previous - 1) { 529 swap(all, pos, ++pos); 530 } 531 } 532 } 533 534 /** 535 * Move items from components to others. 536 */ 537 public static void moveItems(UISelectMany sourceSelect, UISelectItems sourceItems, UISelectItems targetItems, 538 UIEditableList hiddenTargetList, boolean setTargetIds) { 539 String[] selected = (String[]) sourceSelect.getSelectedValues(); 540 if (selected == null) { 541 // nothing to do 542 return; 543 } 544 List<String> selectedList = Arrays.asList(selected); 545 546 SelectItem[] all = (SelectItem[]) sourceItems.getValue(); 547 List<SelectItem> toMove = new ArrayList<SelectItem>(); 548 List<SelectItem> toKeep = new ArrayList<SelectItem>(); 549 List<String> hiddenIds = new ArrayList<String>(); 550 if (all != null) { 551 for (SelectItem item : all) { 552 String itemId = item.getValue().toString(); 553 if (selectedList.contains(itemId)) { 554 toMove.add(item); 555 } else { 556 toKeep.add(item); 557 if (!setTargetIds) { 558 hiddenIds.add(itemId); 559 } 560 } 561 } 562 } 563 // reset left values 564 sourceItems.setValue(toKeep.toArray(new SelectItem[] {})); 565 sourceSelect.setSelectedValues(new Object[0]); 566 567 // change right values 568 List<SelectItem> newSelectItems = new ArrayList<SelectItem>(); 569 SelectItem[] oldSelectItems = (SelectItem[]) targetItems.getValue(); 570 if (oldSelectItems == null) { 571 newSelectItems.addAll(toMove); 572 } else { 573 newSelectItems.addAll(Arrays.asList(oldSelectItems)); 574 List<String> oldIds = new ArrayList<String>(); 575 for (SelectItem oldItem : oldSelectItems) { 576 String id = oldItem.getValue().toString(); 577 oldIds.add(id); 578 } 579 if (setTargetIds) { 580 hiddenIds.addAll(0, oldIds); 581 } 582 for (SelectItem toMoveItem : toMove) { 583 String id = toMoveItem.getValue().toString(); 584 if (!oldIds.contains(id)) { 585 newSelectItems.add(toMoveItem); 586 if (setTargetIds) { 587 hiddenIds.add(id); 588 } 589 } 590 } 591 } 592 targetItems.setValue(newSelectItems.toArray(new SelectItem[] {})); 593 594 // update hidden values 595 int numValues = hiddenTargetList.getRowCount(); 596 if (numValues > 0) { 597 for (int i = numValues - 1; i > -1; i--) { 598 hiddenTargetList.removeValue(i); 599 } 600 } 601 for (String newHiddenValue : hiddenIds) { 602 hiddenTargetList.addValue(newHiddenValue); 603 } 604 } 605 606 /** 607 * Move items from components to others. 608 */ 609 public static void moveAllItems(UISelectItems sourceItems, UISelectItems targetItems, 610 UIEditableList hiddenTargetList, boolean setTargetIds) { 611 SelectItem[] all = (SelectItem[]) sourceItems.getValue(); 612 List<SelectItem> toMove = new ArrayList<SelectItem>(); 613 List<SelectItem> toKeep = new ArrayList<SelectItem>(); 614 List<String> hiddenIds = new ArrayList<String>(); 615 if (all != null) { 616 for (SelectItem item : all) { 617 if (!item.isDisabled()) { 618 toMove.add(item); 619 } else { 620 toKeep.add(item); 621 } 622 } 623 } 624 // reset left values 625 sourceItems.setValue(toKeep.toArray(new SelectItem[] {})); 626 627 // change right values 628 List<SelectItem> newSelectItems = new ArrayList<SelectItem>(); 629 SelectItem[] oldSelectItems = (SelectItem[]) targetItems.getValue(); 630 if (oldSelectItems == null) { 631 newSelectItems.addAll(toMove); 632 } else { 633 newSelectItems.addAll(Arrays.asList(oldSelectItems)); 634 List<String> oldIds = new ArrayList<String>(); 635 for (SelectItem oldItem : oldSelectItems) { 636 String id = oldItem.getValue().toString(); 637 oldIds.add(id); 638 } 639 if (setTargetIds) { 640 hiddenIds.addAll(0, oldIds); 641 } 642 for (SelectItem toMoveItem : toMove) { 643 String id = toMoveItem.getValue().toString(); 644 if (!oldIds.contains(id)) { 645 newSelectItems.add(toMoveItem); 646 if (setTargetIds) { 647 hiddenIds.add(id); 648 } 649 } 650 } 651 } 652 targetItems.setValue(newSelectItems.toArray(new SelectItem[] {})); 653 654 // update hidden values 655 int numValues = hiddenTargetList.getRowCount(); 656 if (numValues > 0) { 657 for (int i = numValues - 1; i > -1; i--) { 658 hiddenTargetList.removeValue(i); 659 } 660 } 661 for (String newHiddenValue : hiddenIds) { 662 hiddenTargetList.addValue(newHiddenValue); 663 } 664 } 665 666 public static String verifyTarget(String toVerify, String defaultTarget) { 667 if (StringUtils.isBlank(toVerify)) { 668 return null; 669 } 670 FacesContext context = FacesContext.getCurrentInstance(); 671 boolean ajaxRequest = context.getPartialViewContext().isAjaxRequest(); 672 if (ajaxRequest) { 673 // ease up ajax re-rendering in case of js scripts parsing defer 674 return null; 675 } 676 return defaultTarget; 677 } 678 679 public static final String NUXEO_RESOURCE_RELOCATED = "NUXEO_RESOURCE_RELOCATED_MARKER"; 680 681 /** 682 * Marks given component as relocated, so that subsequent calls to {@link #isRelocated(UIComponent)} returns true. 683 * 684 * @since 8.1 685 */ 686 public static void setRelocated(UIComponent component) { 687 component.getAttributes().put(NUXEO_RESOURCE_RELOCATED, "true"); 688 } 689 690 /** 691 * Returns true if given component is marked as relocated. 692 * 693 * @see #setRelocated(UIComponent) 694 * @see #relocate(UIComponent, String, String) 695 * @since 8.1 696 */ 697 public static boolean isRelocated(UIComponent component) { 698 return component.getAttributes().containsKey(NUXEO_RESOURCE_RELOCATED); 699 } 700 701 /** 702 * Relocates given component, adding it to the view root resources for given target. 703 * <p> 704 * If given composite key is not null, current composite component client id is saved using this key on the 705 * component attributes, for later reuse. 706 * <p> 707 * Component is also marked as relocated so that subsequent calls to {@link #isRelocated(UIComponent)} returns true. 708 * 709 * @since 8.1 710 */ 711 public static void relocate(UIComponent component, String target, String compositeKey) { 712 FacesContext context = FacesContext.getCurrentInstance(); 713 if (compositeKey != null) { 714 // We're checking for a composite component here as if the resource 715 // is relocated, it may still require it's composite component context 716 // in order to properly render. Store it for later use by 717 // encodeBegin() and encodeEnd(). 718 UIComponent cc = UIComponent.getCurrentCompositeComponent(context); 719 if (cc != null) { 720 component.getAttributes().put(compositeKey, cc.getClientId(context)); 721 } 722 } 723 // avoid relocating resources that are not actually rendered 724 if (isRendered(component)) { 725 setRelocated(component); 726 context.getViewRoot().addComponentResource(context, component, target); 727 } 728 } 729 730 protected static boolean isRendered(UIComponent component) { 731 UIComponent comp = component; 732 while (comp.isRendered()) { 733 UIComponent parent = comp.getParent(); 734 if (parent == null) { 735 // reached root 736 return true; 737 } else { 738 comp = parent; 739 } 740 } 741 return false; 742 } 743 744}