/*
* (C) Copyright 2010 Nuxeo SA (http://nuxeo.com/) and contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* Contributors:
* <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
* <a href="mailto:gr@nuxeo.com">Georges Racinet</a>
*/
package org.nuxeo.ecm.platform.ui.web.util;
import java.io.Serializable;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UISelectItems;
import javax.faces.component.UISelectMany;
import javax.faces.component.ValueHolder;
import javax.faces.event.ActionEvent;
import javax.faces.event.AjaxBehaviorEvent;
import javax.faces.event.FacesEvent;
import javax.faces.model.SelectItem;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.web.RequestParameter;
import org.nuxeo.ecm.core.api.ClientException;
import org.nuxeo.ecm.platform.ui.web.component.list.UIEditableList;
/**
* Helper for selection actions, useful when performing ajax calls on a "liste
* shuttle" widget for instance, or to retrieve the selected value on a JSF
* component and set it on another.
*/
@Name("selectionActions")
@Scope(ScopeType.EVENT)
public class SelectionActionsBean implements Serializable {
private static final long serialVersionUID = 1L;
private static final Log log = LogFactory.getLog(SelectionActionsBean.class);
public enum ShiftType {
FIRST, UP, DOWN, LAST
}
@RequestParameter
protected String leftSelect;
@RequestParameter
protected String leftItems;
@RequestParameter
protected String rightSelect;
@RequestParameter
protected String rightItems;
@RequestParameter
protected String submittedList;
/**
* Id of the input selector
* <p>
* Component must be an instance of {@link ValueHolder}
*/
@RequestParameter
protected String selectorId;
/**
* Id of the value holder that will receive the selected value.
* <p>
* Component must be an instance of {@link ValueHolder}
*/
@RequestParameter
protected String valueHolderId;
/**
* Lookup level request parameter (defaults to 1, means search is done in r
* first parent naming container)
*
* @since 5.6
*/
@RequestParameter
protected Integer lookupLevel;
/**
* Lookup level field (defaults to 1, means search is done in first parent
* naming container)
* <p>
* Useful as fallback when posting a button where request parameter
* {@link #lookupLevel} cannot be set.
*
* @since 5.6
*/
protected String lookupLevelValue;
/**
* @since 5.6
*/
public String getLookupLevelValue() {
return lookupLevelValue;
}
/**
* @since 5.6
*/
public void setLookupLevelValue(String lookupLevelValue) {
this.lookupLevelValue = lookupLevelValue;
}
/**
* @since 5.6
*/
protected int computeLookupLevel() {
if (lookupLevel != null) {
return lookupLevel.intValue();
}
String setValue = getLookupLevelValue();
if (setValue != null) {
return Integer.valueOf(setValue).intValue();
}
return 1;
}
/**
* Value held temporarily by this bean to be set on JSF components.
*
* @since 5.5
*/
@RequestParameter
protected String selectedValue;
public String getSelectedValue() {
return selectedValue;
}
public void setSelectedValue(String selectedValue) {
this.selectedValue = selectedValue;
}
/**
* Value component id held temporarily by this bean to be retrieved from
* the JSF component tree.
* <p>
* this is an alternative to {@link #valueHolderId} request parameter
* usage, to make it possible to set this value easily from command buttons
* (as only command links do take request parameters into account).
*
* @since 5.6
*/
protected String selectedValueHolder;
public String getSelectedValueHolder() {
return selectedValueHolder;
}
public void setSelectedValueHolder(String selectedValueHolder) {
this.selectedValueHolder = selectedValueHolder;
}
public SelectItem[] getEmptySelection() {
return new SelectItem[0];
}
protected boolean checkRightComponents() {
String logPrefix = "Check right components: ";
if (rightSelect == null) {
log.error(logPrefix + "No select component name");
return false;
}
if (rightItems == null) {
log.error(logPrefix + "No items component name");
return false;
}
return true;
}
protected boolean checkLeftComponents() {
String logPrefix = "Check left components: ";
if (leftSelect == null) {
log.error(logPrefix + "No select component name");
return false;
}
if (leftItems == null) {
log.error(logPrefix + "No items component name");
return false;
}
return true;
}
protected boolean checkSubmittedList() {
String logPrefix = "Check submitted list: ";
if (submittedList == null) {
log.error(logPrefix + "No component name");
return false;
}
return true;
}
public void shiftSelected(ShiftType stype, ActionEvent event) {
if (!checkRightComponents() || !checkSubmittedList()) {
return;
}
UIComponent eventComp = event.getComponent();
UIComponent rightItemsComp = eventComp.findComponent(rightItems);
UIComponent rightSelectComp = eventComp.findComponent(rightSelect);
UIComponent hiddenTargetListComp = eventComp.findComponent(submittedList);
if (rightSelectComp instanceof UISelectMany
&& rightItemsComp instanceof UISelectItems
&& hiddenTargetListComp instanceof UIEditableList) {
UISelectItems targetItems = (UISelectItems) rightItemsComp;
UISelectMany targetComp = (UISelectMany) rightSelectComp;
UIEditableList hiddenTargetList = (UIEditableList) hiddenTargetListComp;
switch (stype) {
case UP:
ComponentUtils.shiftItemsUp(targetComp, targetItems,
hiddenTargetList);
break;
case DOWN:
ComponentUtils.shiftItemsDown(targetComp, targetItems,
hiddenTargetList);
break;
case FIRST:
ComponentUtils.shiftItemsFirst(targetComp, targetItems,
hiddenTargetList);
break;
case LAST:
ComponentUtils.shiftItemsLast(targetComp, targetItems,
hiddenTargetList);
break;
}
}
}
public void shiftSelectedUp(ActionEvent event) throws ClientException {
shiftSelected(ShiftType.UP, event);
}
public void shiftSelectedDown(ActionEvent event) throws ClientException {
shiftSelected(ShiftType.DOWN, event);
}
public void shiftSelectedFirst(ActionEvent event) throws ClientException {
shiftSelected(ShiftType.FIRST, event);
}
public void shiftSelectedLast(ActionEvent event) throws ClientException {
shiftSelected(ShiftType.LAST, event);
}
public void addToSelection(ActionEvent event) throws ClientException {
if (!checkLeftComponents() || !checkRightComponents()
|| !checkSubmittedList()) {
return;
}
UIComponent eventComp = event.getComponent();
UIComponent leftSelectComp = eventComp.findComponent(leftSelect);
UIComponent leftItemsComp = eventComp.findComponent(leftItems);
UIComponent rightItemsComp = eventComp.findComponent(rightItems);
UIComponent hiddenTargetListComp = eventComp.findComponent(submittedList);
if (leftSelectComp instanceof UISelectMany
&& leftItemsComp instanceof UISelectItems
&& rightItemsComp instanceof UISelectItems
&& hiddenTargetListComp instanceof UIEditableList) {
UISelectMany sourceSelect = (UISelectMany) leftSelectComp;
UISelectItems sourceItems = (UISelectItems) leftItemsComp;
UISelectItems targetItems = (UISelectItems) rightItemsComp;
UIEditableList hiddenTargetList = (UIEditableList) hiddenTargetListComp;
ComponentUtils.moveItems(sourceSelect, sourceItems, targetItems,
hiddenTargetList, true);
}
}
public void removeFromSelection(ActionEvent event) throws ClientException {
if (!checkLeftComponents() || !checkRightComponents()
|| !checkSubmittedList()) {
return;
}
UIComponent eventComp = event.getComponent();
UIComponent leftItemsComp = eventComp.findComponent(leftItems);
UIComponent rightSelectComp = eventComp.findComponent(rightSelect);
UIComponent rightItemsComp = eventComp.findComponent(rightItems);
UIComponent hiddenTargetListComp = eventComp.findComponent(submittedList);
if (leftItemsComp instanceof UISelectItems
&& rightSelectComp instanceof UISelectMany
&& rightItemsComp instanceof UISelectItems
&& hiddenTargetListComp instanceof UIEditableList) {
UISelectItems leftItems = (UISelectItems) leftItemsComp;
UISelectMany rightSelect = (UISelectMany) rightSelectComp;
UISelectItems rightItems = (UISelectItems) rightItemsComp;
UIEditableList hiddenTargetList = (UIEditableList) hiddenTargetListComp;
ComponentUtils.moveItems(rightSelect, rightItems, leftItems,
hiddenTargetList, false);
}
}
public void addAllToSelection(ActionEvent event) {
if (!checkLeftComponents() || !checkRightComponents()
|| !checkSubmittedList()) {
return;
}
UIComponent eventComp = event.getComponent();
UIComponent leftItemsComp = eventComp.findComponent(leftItems);
UIComponent rightItemsComp = eventComp.findComponent(rightItems);
UIComponent hiddenTargetListComp = eventComp.findComponent(submittedList);
if (leftItemsComp instanceof UISelectItems
&& rightItemsComp instanceof UISelectItems
&& hiddenTargetListComp instanceof UIEditableList) {
UISelectItems sourceItems = (UISelectItems) leftItemsComp;
UISelectItems targetItems = (UISelectItems) rightItemsComp;
UIEditableList hiddenTargetList = (UIEditableList) hiddenTargetListComp;
ComponentUtils.moveAllItems(sourceItems, targetItems,
hiddenTargetList, true);
}
}
public UISelectMany getSourceSelectComponent(ActionEvent event) {
if (leftSelect == null) {
log.warn("Unable to find leftSelect component. Param 'leftSelect' not sent in request");
return null;
}
UIComponent eventComp = event.getComponent();
UIComponent leftSelectComp = eventComp.findComponent(leftSelect);
if (leftSelectComp instanceof UISelectMany) {
return (UISelectMany) leftSelectComp;
}
return null;
}
public UISelectItems getSourceSelectItems(ActionEvent event) {
if (leftItems == null) {
log.warn("Unable to find leftItems component. Param 'leftItems' not sent in request");
return null;
}
UIComponent eventComp = event.getComponent();
UIComponent leftItemsComp = eventComp.findComponent(leftItems);
if (leftItemsComp instanceof UISelectItems) {
return (UISelectItems) leftItemsComp;
}
return null;
}
public void removeAllFromSelection(ActionEvent event)
throws ClientException {
if (!checkLeftComponents() || !checkRightComponents()
|| !checkSubmittedList()) {
return;
}
UIComponent eventComp = event.getComponent();
UIComponent leftItemsComp = eventComp.findComponent(leftItems);
UIComponent rightItemsComp = eventComp.findComponent(rightItems);
UIComponent hiddenTargetListComp = eventComp.findComponent(submittedList);
if (leftItemsComp instanceof UISelectItems
&& rightItemsComp instanceof UISelectItems
&& hiddenTargetListComp instanceof UIEditableList) {
UISelectItems leftItems = (UISelectItems) leftItemsComp;
UISelectItems rightItems = (UISelectItems) rightItemsComp;
UIEditableList hiddenTargetList = (UIEditableList) hiddenTargetListComp;
ComponentUtils.moveAllItems(rightItems, leftItems,
hiddenTargetList, false);
}
}
/**
* Adds selection retrieved from a selector to another component
* <p>
* Must pass request parameters "selectorId" holding the id of component
* holding the value to pass to the other component, and "valueHolderId"
* holding the other component id.
*
* @since 5.5
* @param event
* @deprecated since 6.0: use
* {@link #onSelection(AjaxBehaviorEvent)} instead.
*/
@Deprecated
public void onSelection(ActionEvent event) {
log.warn(String.format(
"The method #onSelection(ActionEvent) on component "
+ "'selectionActions' at '%s' is deprecated, please "
+ "use #onSelection(AjaxBehaviorEvent) instead",
this.getClass().getName()));
onSelection((FacesEvent) event);
}
/**
* @since 6.0
*/
public void onSelection(AjaxBehaviorEvent event) {
onSelection((FacesEvent) event);
}
protected void onSelection(FacesEvent event) {
UIComponent component = event.getComponent();
Object value = retrieveSourceComponentValue(component, selectorId);
UIComponent base = ComponentUtils.getBase(component);
ValueHolder valueHolderComp = ComponentUtils.getComponent(base,
valueHolderId, ValueHolder.class);
setTargetComponentValue(valueHolderComp, value);
}
/**
* Adds value retrieved from {@link #getSelectedValue()} to a component
* <p>
* Must pass request parameters "valueHolderId" holding the id of the bound
* component, and call {@link #setSelectedValue(String)} prior to this
* call.
* <p>
* As an alternative, must call {@link #setSelectedValueHolder(String)}
* with the id of the bound component, and call
* {@link #setSelectedValue(String)} prior to this call (this makes it
* possible to use the same logic in command buttons that do not make it
* possible to pass request parameters).
*
* @deprecated since 6.0: use {@link #onClick(AjaxBehaviorEvent)}
* instead.
* @since 5.5
* @param event
*/
@Deprecated
public void onClick(ActionEvent event) {
log.warn(String.format("The method #onClick(ActionEvent) on component "
+ "'selectionActions' at '%s' is deprecated, please "
+ "use #onClick(AjaxBehaviorEvent) instead",
this.getClass().getName()));
onClick((FacesEvent) event);
}
public void onClick(AjaxBehaviorEvent event) {
onClick((FacesEvent) event);
}
protected void onClick(FacesEvent event) {
UIComponent component = event.getComponent();
if (component == null) {
return;
}
EditableValueHolder hiddenSelector = null;
UIComponent base = retrieveBase(component, computeLookupLevel());
if (valueHolderId != null) {
hiddenSelector = ComponentUtils.getComponent(base, valueHolderId,
EditableValueHolder.class);
}
if (hiddenSelector == null) {
String selectedValueHolder = getSelectedValueHolder();
if (selectedValueHolder != null) {
hiddenSelector = ComponentUtils.getComponent(base,
selectedValueHolder, EditableValueHolder.class);
}
}
if (hiddenSelector != null) {
String selectedValue = getSelectedValue();
setTargetComponentValue(hiddenSelector, selectedValue);
}
}
protected UIComponent retrieveBase(UIComponent anchor, int lookupLevel) {
UIComponent base = ComponentUtils.getBase(anchor);
if (lookupLevel > 1) {
for (int i = 0; i < (lookupLevel - 1); i++) {
base = ComponentUtils.getBase(base);
}
}
return base;
}
/**
* Retrieves a value from another component and sets it on the target
* component.
* <p>
* Source component id must be passed in the event component attributes
* with id "sourceComponentId".
* <p>
* Target component id must be passed in the event component attributes
* with id "targetComponentId". If target component is an
* {@link EditableValueHolder}, its submitted value is set. Otherwise, its
* local value is set.
*
* @since 6.0
* @param event
*/
public void setValueFromComponent(AjaxBehaviorEvent event) {
setValueFromComponent((FacesEvent) event);
}
/**
* @see #setValueFromComponent(ActionEvent)
* @since 6.0
*/
public void setValueFromComponent(ActionEvent event) {
setValueFromComponent((FacesEvent) event);
}
protected void setValueFromComponent(FacesEvent event) {
UIComponent anchor = event.getComponent();
String sourceCompId = getStringAttribute(anchor, "sourceComponentId",
true);
Object value = retrieveSourceComponentValue(anchor, sourceCompId);
String targetCompId = getStringAttribute(anchor, "targetComponentId",
true);
ValueHolder targetComp = ComponentUtils.getComponent(anchor,
targetCompId, ValueHolder.class);
setTargetComponentValue(targetComp, value);
}
/**
* Retrieves a value passed as an attribute with id "selectedValue" on the
* event component attributes and sets it on the target component.
* <p>
* Target component id must be passed in the event component attributes
* with id "targetComponentId". If target component is an
* {@link EditableValueHolder}, its submitted value is set. Otherwise, its
* local value is set.
*
* @since 6.0
* @param event
*/
public void setStaticValue(AjaxBehaviorEvent event) {
setStaticValue((FacesEvent) event);
}
/**
* @see #setStaticValue(ActionEvent)
* @since 6.0
*/
public void setStaticValue(ActionEvent event) {
setStaticValue((FacesEvent) event);
}
protected void setStaticValue(FacesEvent event) {
UIComponent anchor = event.getComponent();
Object value = anchor.getAttributes().get("selectedValue");
String targetCompId = getStringAttribute(anchor, "targetComponentId",
true);
ValueHolder targetComp = ComponentUtils.getComponent(anchor,
targetCompId, ValueHolder.class);
setTargetComponentValue(targetComp, value);
}
protected String getStringAttribute(UIComponent component, String name,
boolean required) {
Object value = component.getAttributes().get(name);
if (required && value == null) {
throw new IllegalArgumentException(String.format(
"Component attribute with name '%s' cannot be null: %s",
name, value));
}
if (value == null || value instanceof String) {
return (String) value;
}
throw new IllegalArgumentException(String.format(
"Component attribute with name '%s' is not a String: %s", name,
value));
}
protected Object retrieveSourceComponentValue(UIComponent base,
String targetId) {
ValueHolder selectComp = ComponentUtils.getComponent(base, targetId,
ValueHolder.class);
if (selectComp != null) {
Object value;
if (selectComp instanceof EditableValueHolder) {
value = ((EditableValueHolder) selectComp).getSubmittedValue();
if (value == null) {
value = selectComp.getValue();
}
} else {
value = selectComp.getValue();
}
return value;
}
return null;
}
protected void setTargetComponentValue(ValueHolder target, Object value) {
if (target != null) {
if (target instanceof EditableValueHolder) {
((EditableValueHolder) target).setSubmittedValue(value);
} else {
target.setValue(value);
}
}
}
}