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