001/* 002 * (C) Copyright 2010 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> 016 * <a href="mailto:gr@nuxeo.com">Georges Racinet</a> 017 */ 018package org.nuxeo.ecm.platform.ui.web.util; 019 020import java.io.Serializable; 021 022import javax.faces.component.EditableValueHolder; 023import javax.faces.component.UIComponent; 024import javax.faces.component.UISelectItems; 025import javax.faces.component.UISelectMany; 026import javax.faces.component.ValueHolder; 027import javax.faces.event.ActionEvent; 028import javax.faces.event.AjaxBehaviorEvent; 029import javax.faces.event.FacesEvent; 030import javax.faces.model.SelectItem; 031 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.jboss.seam.ScopeType; 035import org.jboss.seam.annotations.Name; 036import org.jboss.seam.annotations.Scope; 037import org.jboss.seam.annotations.web.RequestParameter; 038import org.nuxeo.ecm.platform.ui.web.component.list.UIEditableList; 039 040/** 041 * Helper for selection actions, useful when performing ajax calls on a "liste shuttle" widget for instance, or to 042 * retrieve the selected value on a JSF component and set it on another. 043 */ 044@Name("selectionActions") 045@Scope(ScopeType.EVENT) 046public class SelectionActionsBean implements Serializable { 047 048 private static final long serialVersionUID = 1L; 049 050 private static final Log log = LogFactory.getLog(SelectionActionsBean.class); 051 052 public enum ShiftType { 053 FIRST, UP, DOWN, LAST 054 } 055 056 @RequestParameter 057 protected String leftSelect; 058 059 @RequestParameter 060 protected String leftItems; 061 062 @RequestParameter 063 protected String rightSelect; 064 065 @RequestParameter 066 protected String rightItems; 067 068 @RequestParameter 069 protected String submittedList; 070 071 /** 072 * Id of the input selector 073 * <p> 074 * Component must be an instance of {@link ValueHolder} 075 */ 076 @RequestParameter 077 protected String selectorId; 078 079 /** 080 * Id of the value holder that will receive the selected value. 081 * <p> 082 * Component must be an instance of {@link ValueHolder} 083 */ 084 @RequestParameter 085 protected String valueHolderId; 086 087 /** 088 * Lookup level request parameter (defaults to 1, means search is done in r first parent naming container) 089 * 090 * @since 5.6 091 */ 092 @RequestParameter 093 protected Integer lookupLevel; 094 095 /** 096 * Lookup level field (defaults to 1, means search is done in first parent naming container) 097 * <p> 098 * Useful as fallback when posting a button where request parameter {@link #lookupLevel} cannot be set. 099 * 100 * @since 5.6 101 */ 102 protected String lookupLevelValue; 103 104 /** 105 * @since 5.6 106 */ 107 public String getLookupLevelValue() { 108 return lookupLevelValue; 109 } 110 111 /** 112 * @since 5.6 113 */ 114 public void setLookupLevelValue(String lookupLevelValue) { 115 this.lookupLevelValue = lookupLevelValue; 116 } 117 118 /** 119 * @since 5.6 120 */ 121 protected int computeLookupLevel() { 122 if (lookupLevel != null) { 123 return lookupLevel.intValue(); 124 } 125 String setValue = getLookupLevelValue(); 126 if (setValue != null) { 127 return Integer.valueOf(setValue).intValue(); 128 } 129 return 1; 130 } 131 132 /** 133 * Value held temporarily by this bean to be set on JSF components. 134 * 135 * @since 5.5 136 */ 137 @RequestParameter 138 protected String selectedValue; 139 140 public String getSelectedValue() { 141 return selectedValue; 142 } 143 144 public void setSelectedValue(String selectedValue) { 145 this.selectedValue = selectedValue; 146 } 147 148 /** 149 * Value component id held temporarily by this bean to be retrieved from the JSF component tree. 150 * <p> 151 * this is an alternative to {@link #valueHolderId} request parameter usage, to make it possible to set this value 152 * easily from command buttons (as only command links do take request parameters into account). 153 * 154 * @since 5.6 155 */ 156 protected String selectedValueHolder; 157 158 public String getSelectedValueHolder() { 159 return selectedValueHolder; 160 } 161 162 public void setSelectedValueHolder(String selectedValueHolder) { 163 this.selectedValueHolder = selectedValueHolder; 164 } 165 166 public SelectItem[] getEmptySelection() { 167 return new SelectItem[0]; 168 } 169 170 protected boolean checkRightComponents() { 171 String logPrefix = "Check right components: "; 172 if (rightSelect == null) { 173 log.error(logPrefix + "No select component name"); 174 return false; 175 } 176 if (rightItems == null) { 177 log.error(logPrefix + "No items component name"); 178 return false; 179 } 180 return true; 181 } 182 183 protected boolean checkLeftComponents() { 184 String logPrefix = "Check left components: "; 185 if (leftSelect == null) { 186 log.error(logPrefix + "No select component name"); 187 return false; 188 } 189 if (leftItems == null) { 190 log.error(logPrefix + "No items component name"); 191 return false; 192 } 193 return true; 194 } 195 196 protected boolean checkSubmittedList() { 197 String logPrefix = "Check submitted list: "; 198 if (submittedList == null) { 199 log.error(logPrefix + "No component name"); 200 return false; 201 } 202 return true; 203 } 204 205 public void shiftSelected(ShiftType stype, ActionEvent event) { 206 if (!checkRightComponents() || !checkSubmittedList()) { 207 return; 208 } 209 UIComponent eventComp = event.getComponent(); 210 UIComponent rightItemsComp = eventComp.findComponent(rightItems); 211 UIComponent rightSelectComp = eventComp.findComponent(rightSelect); 212 UIComponent hiddenTargetListComp = eventComp.findComponent(submittedList); 213 if (rightSelectComp instanceof UISelectMany && rightItemsComp instanceof UISelectItems 214 && hiddenTargetListComp instanceof UIEditableList) { 215 UISelectItems targetItems = (UISelectItems) rightItemsComp; 216 UISelectMany targetComp = (UISelectMany) rightSelectComp; 217 UIEditableList hiddenTargetList = (UIEditableList) hiddenTargetListComp; 218 switch (stype) { 219 case UP: 220 ComponentUtils.shiftItemsUp(targetComp, targetItems, hiddenTargetList); 221 break; 222 case DOWN: 223 ComponentUtils.shiftItemsDown(targetComp, targetItems, hiddenTargetList); 224 break; 225 case FIRST: 226 ComponentUtils.shiftItemsFirst(targetComp, targetItems, hiddenTargetList); 227 break; 228 case LAST: 229 ComponentUtils.shiftItemsLast(targetComp, targetItems, hiddenTargetList); 230 break; 231 } 232 } 233 } 234 235 public void shiftSelectedUp(ActionEvent event) { 236 shiftSelected(ShiftType.UP, event); 237 } 238 239 public void shiftSelectedDown(ActionEvent event) { 240 shiftSelected(ShiftType.DOWN, event); 241 } 242 243 public void shiftSelectedFirst(ActionEvent event) { 244 shiftSelected(ShiftType.FIRST, event); 245 } 246 247 public void shiftSelectedLast(ActionEvent event) { 248 shiftSelected(ShiftType.LAST, event); 249 } 250 251 public void addToSelection(ActionEvent event) { 252 if (!checkLeftComponents() || !checkRightComponents() || !checkSubmittedList()) { 253 return; 254 } 255 UIComponent eventComp = event.getComponent(); 256 UIComponent leftSelectComp = eventComp.findComponent(leftSelect); 257 UIComponent leftItemsComp = eventComp.findComponent(leftItems); 258 UIComponent rightItemsComp = eventComp.findComponent(rightItems); 259 UIComponent hiddenTargetListComp = eventComp.findComponent(submittedList); 260 if (leftSelectComp instanceof UISelectMany && leftItemsComp instanceof UISelectItems 261 && rightItemsComp instanceof UISelectItems && hiddenTargetListComp instanceof UIEditableList) { 262 UISelectMany sourceSelect = (UISelectMany) leftSelectComp; 263 UISelectItems sourceItems = (UISelectItems) leftItemsComp; 264 UISelectItems targetItems = (UISelectItems) rightItemsComp; 265 UIEditableList hiddenTargetList = (UIEditableList) hiddenTargetListComp; 266 ComponentUtils.moveItems(sourceSelect, sourceItems, targetItems, hiddenTargetList, true); 267 } 268 } 269 270 public void removeFromSelection(ActionEvent event) { 271 if (!checkLeftComponents() || !checkRightComponents() || !checkSubmittedList()) { 272 return; 273 } 274 UIComponent eventComp = event.getComponent(); 275 UIComponent leftItemsComp = eventComp.findComponent(leftItems); 276 UIComponent rightSelectComp = eventComp.findComponent(rightSelect); 277 UIComponent rightItemsComp = eventComp.findComponent(rightItems); 278 UIComponent hiddenTargetListComp = eventComp.findComponent(submittedList); 279 if (leftItemsComp instanceof UISelectItems && rightSelectComp instanceof UISelectMany 280 && rightItemsComp instanceof UISelectItems && hiddenTargetListComp instanceof UIEditableList) { 281 UISelectItems leftItems = (UISelectItems) leftItemsComp; 282 UISelectMany rightSelect = (UISelectMany) rightSelectComp; 283 UISelectItems rightItems = (UISelectItems) rightItemsComp; 284 UIEditableList hiddenTargetList = (UIEditableList) hiddenTargetListComp; 285 ComponentUtils.moveItems(rightSelect, rightItems, leftItems, hiddenTargetList, false); 286 } 287 } 288 289 public void addAllToSelection(ActionEvent event) { 290 if (!checkLeftComponents() || !checkRightComponents() || !checkSubmittedList()) { 291 return; 292 } 293 UIComponent eventComp = event.getComponent(); 294 UIComponent leftItemsComp = eventComp.findComponent(leftItems); 295 UIComponent rightItemsComp = eventComp.findComponent(rightItems); 296 UIComponent hiddenTargetListComp = eventComp.findComponent(submittedList); 297 if (leftItemsComp instanceof UISelectItems && rightItemsComp instanceof UISelectItems 298 && hiddenTargetListComp instanceof UIEditableList) { 299 UISelectItems sourceItems = (UISelectItems) leftItemsComp; 300 UISelectItems targetItems = (UISelectItems) rightItemsComp; 301 UIEditableList hiddenTargetList = (UIEditableList) hiddenTargetListComp; 302 ComponentUtils.moveAllItems(sourceItems, targetItems, hiddenTargetList, true); 303 } 304 } 305 306 public UISelectMany getSourceSelectComponent(ActionEvent event) { 307 if (leftSelect == null) { 308 log.warn("Unable to find leftSelect component. Param 'leftSelect' not sent in request"); 309 return null; 310 } 311 UIComponent eventComp = event.getComponent(); 312 UIComponent leftSelectComp = eventComp.findComponent(leftSelect); 313 314 if (leftSelectComp instanceof UISelectMany) { 315 return (UISelectMany) leftSelectComp; 316 } 317 return null; 318 } 319 320 public UISelectItems getSourceSelectItems(ActionEvent event) { 321 if (leftItems == null) { 322 log.warn("Unable to find leftItems component. Param 'leftItems' not sent in request"); 323 return null; 324 } 325 UIComponent eventComp = event.getComponent(); 326 UIComponent leftItemsComp = eventComp.findComponent(leftItems); 327 328 if (leftItemsComp instanceof UISelectItems) { 329 return (UISelectItems) leftItemsComp; 330 } 331 return null; 332 } 333 334 public void removeAllFromSelection(ActionEvent event) { 335 if (!checkLeftComponents() || !checkRightComponents() || !checkSubmittedList()) { 336 return; 337 } 338 UIComponent eventComp = event.getComponent(); 339 UIComponent leftItemsComp = eventComp.findComponent(leftItems); 340 UIComponent rightItemsComp = eventComp.findComponent(rightItems); 341 UIComponent hiddenTargetListComp = eventComp.findComponent(submittedList); 342 if (leftItemsComp instanceof UISelectItems && rightItemsComp instanceof UISelectItems 343 && hiddenTargetListComp instanceof UIEditableList) { 344 UISelectItems leftItems = (UISelectItems) leftItemsComp; 345 UISelectItems rightItems = (UISelectItems) rightItemsComp; 346 UIEditableList hiddenTargetList = (UIEditableList) hiddenTargetListComp; 347 ComponentUtils.moveAllItems(rightItems, leftItems, hiddenTargetList, false); 348 } 349 } 350 351 /** 352 * Adds selection retrieved from a selector to another component 353 * <p> 354 * Must pass request parameters "selectorId" holding the id of component holding the value to pass to the other 355 * component, and "valueHolderId" holding the other component id. 356 * 357 * @since 5.5 358 * @param event 359 * @deprecated since 6.0: use {@link #onSelection(AjaxBehaviorEvent)} instead. 360 */ 361 @Deprecated 362 public void onSelection(ActionEvent event) { 363 log.warn(String.format("The method #onSelection(ActionEvent) on component " 364 + "'selectionActions' at '%s' is deprecated, please " + "use #onSelection(AjaxBehaviorEvent) instead", 365 this.getClass().getName())); 366 onSelection((FacesEvent) event); 367 } 368 369 /** 370 * @since 6.0 371 */ 372 public void onSelection(AjaxBehaviorEvent event) { 373 onSelection((FacesEvent) event); 374 } 375 376 protected void onSelection(FacesEvent event) { 377 UIComponent component = event.getComponent(); 378 Object value = retrieveSourceComponentValue(component, selectorId); 379 UIComponent base = ComponentUtils.getBase(component); 380 ValueHolder valueHolderComp = ComponentUtils.getComponent(base, valueHolderId, ValueHolder.class); 381 setTargetComponentValue(valueHolderComp, value); 382 } 383 384 /** 385 * Adds value retrieved from {@link #getSelectedValue()} to a component 386 * <p> 387 * Must pass request parameters "valueHolderId" holding the id of the bound component, and call 388 * {@link #setSelectedValue(String)} prior to this call. 389 * <p> 390 * As an alternative, must call {@link #setSelectedValueHolder(String)} with the id of the bound component, and call 391 * {@link #setSelectedValue(String)} prior to this call (this makes it possible to use the same logic in command 392 * buttons that do not make it possible to pass request parameters). 393 * 394 * @deprecated since 6.0: use {@link #onClick(AjaxBehaviorEvent)} instead. 395 * @since 5.5 396 * @param event 397 */ 398 @Deprecated 399 public void onClick(ActionEvent event) { 400 log.warn(String.format("The method #onClick(ActionEvent) on component " 401 + "'selectionActions' at '%s' is deprecated, please " + "use #onClick(AjaxBehaviorEvent) instead", 402 this.getClass().getName())); 403 onClick((FacesEvent) event); 404 } 405 406 public void onClick(AjaxBehaviorEvent event) { 407 onClick((FacesEvent) event); 408 } 409 410 protected void onClick(FacesEvent event) { 411 UIComponent component = event.getComponent(); 412 if (component == null) { 413 return; 414 } 415 EditableValueHolder hiddenSelector = null; 416 UIComponent base = retrieveBase(component, computeLookupLevel()); 417 if (valueHolderId != null) { 418 hiddenSelector = ComponentUtils.getComponent(base, valueHolderId, EditableValueHolder.class); 419 } 420 if (hiddenSelector == null) { 421 String selectedValueHolder = getSelectedValueHolder(); 422 if (selectedValueHolder != null) { 423 hiddenSelector = ComponentUtils.getComponent(base, selectedValueHolder, EditableValueHolder.class); 424 } 425 } 426 if (hiddenSelector != null) { 427 String selectedValue = getSelectedValue(); 428 setTargetComponentValue(hiddenSelector, selectedValue); 429 } 430 } 431 432 protected UIComponent retrieveBase(UIComponent anchor, int lookupLevel) { 433 UIComponent base = ComponentUtils.getBase(anchor); 434 if (lookupLevel > 1) { 435 for (int i = 0; i < (lookupLevel - 1); i++) { 436 base = ComponentUtils.getBase(base); 437 } 438 } 439 return base; 440 } 441 442 /** 443 * Retrieves a value from another component and sets it on the target component. 444 * <p> 445 * Source component id must be passed in the event component attributes with id "sourceComponentId". 446 * <p> 447 * Target component id must be passed in the event component attributes with id "targetComponentId". If target 448 * component is an {@link EditableValueHolder}, its submitted value is set. Otherwise, its local value is set. 449 * 450 * @since 6.0 451 * @param event 452 */ 453 public void setValueFromComponent(AjaxBehaviorEvent event) { 454 setValueFromComponent((FacesEvent) event); 455 } 456 457 /** 458 * @see #setValueFromComponent(ActionEvent) 459 * @since 6.0 460 */ 461 public void setValueFromComponent(ActionEvent event) { 462 setValueFromComponent((FacesEvent) event); 463 } 464 465 protected void setValueFromComponent(FacesEvent event) { 466 UIComponent anchor = event.getComponent(); 467 String sourceCompId = getStringAttribute(anchor, "sourceComponentId", true); 468 Object value = retrieveSourceComponentValue(anchor, sourceCompId); 469 String targetCompId = getStringAttribute(anchor, "targetComponentId", true); 470 ValueHolder targetComp = ComponentUtils.getComponent(anchor, targetCompId, ValueHolder.class); 471 setTargetComponentValue(targetComp, value); 472 } 473 474 /** 475 * Retrieves a value passed as an attribute with id "selectedValue" on the event component attributes and sets it on 476 * the target component. 477 * <p> 478 * Target component id must be passed in the event component attributes with id "targetComponentId". If target 479 * component is an {@link EditableValueHolder}, its submitted value is set. Otherwise, its local value is set. 480 * 481 * @since 6.0 482 * @param event 483 */ 484 public void setStaticValue(AjaxBehaviorEvent event) { 485 setStaticValue((FacesEvent) event); 486 } 487 488 /** 489 * @see #setStaticValue(ActionEvent) 490 * @since 6.0 491 */ 492 public void setStaticValue(ActionEvent event) { 493 setStaticValue((FacesEvent) event); 494 } 495 496 protected void setStaticValue(FacesEvent event) { 497 UIComponent anchor = event.getComponent(); 498 Object value = anchor.getAttributes().get("selectedValue"); 499 String targetCompId = getStringAttribute(anchor, "targetComponentId", true); 500 ValueHolder targetComp = ComponentUtils.getComponent(anchor, targetCompId, ValueHolder.class); 501 setTargetComponentValue(targetComp, value); 502 } 503 504 protected String getStringAttribute(UIComponent component, String name, boolean required) { 505 Object value = component.getAttributes().get(name); 506 if (required && value == null) { 507 throw new IllegalArgumentException(String.format("Component attribute with name '%s' cannot be null: %s", 508 name, value)); 509 } 510 if (value == null || value instanceof String) { 511 return (String) value; 512 } 513 throw new IllegalArgumentException(String.format("Component attribute with name '%s' is not a String: %s", 514 name, value)); 515 } 516 517 protected Object retrieveSourceComponentValue(UIComponent base, String targetId) { 518 ValueHolder selectComp = ComponentUtils.getComponent(base, targetId, ValueHolder.class); 519 if (selectComp != null) { 520 Object value; 521 if (selectComp instanceof EditableValueHolder) { 522 value = ((EditableValueHolder) selectComp).getSubmittedValue(); 523 if (value == null) { 524 value = selectComp.getValue(); 525 } 526 } else { 527 value = selectComp.getValue(); 528 } 529 return value; 530 } 531 return null; 532 } 533 534 protected void setTargetComponentValue(ValueHolder target, Object value) { 535 if (target != null) { 536 if (target instanceof EditableValueHolder) { 537 ((EditableValueHolder) target).setSubmittedValue(value); 538 } else { 539 target.setValue(value); 540 } 541 } 542 } 543 544}