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