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}