001/*
002 * (C) Copyright 2013 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 *     Anahide Tchertchian
018 */
019package org.nuxeo.ecm.platform.ui.web.component;
020
021import java.util.List;
022
023import javax.el.ValueExpression;
024import javax.faces.component.EditableValueHolder;
025import javax.faces.component.UIComponent;
026import javax.faces.component.ValueHolder;
027import javax.faces.event.ActionEvent;
028import javax.faces.event.AjaxBehaviorEvent;
029import javax.faces.event.FacesEvent;
030
031import org.apache.commons.lang.StringUtils;
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.nuxeo.ecm.platform.ui.web.util.ComponentRenderUtils;
038
039/**
040 * Managed bean, request-scoped, that resets the components value for ajax interactions.
041 * <p>
042 * Resets are done using the following rule:
043 * <ul>
044 * <li>if the component implements {@link ResettableComponent} interface, its method
045 * {@link ResettableComponent#resetCachedModel()} is called</li>
046 * <li>if the component implements {@link EditableValueHolder}, its submitted value is reset, and its local value is
047 * reset only if it holds a value binding for the "value" attribute</li>
048 * <li>if the component implements {@link ValueHolder}, its its local value is reset only if it holds a value binding
049 * for the "value" attribute.</li>
050 * </ul>
051 *
052 * @since 5.7
053 */
054@Name("jsfResetActions")
055@Scope(ScopeType.EVENT)
056public class JSFResetActionsBean {
057
058    private static final Log log = LogFactory.getLog(JSFResetActionsBean.class);
059
060    /**
061     * Base component id for reset actions.
062     *
063     * @since 5.9.1
064     */
065    protected String baseComponentId;
066
067    /**
068     * Returns the base component id, if {@link #setBaseComponentId(String)} was previously called in the same request,
069     * or null.
070     *
071     * @since 5.9.1
072     */
073    public String getBaseComponentId() {
074        return baseComponentId;
075    }
076
077    /**
078     * Sets the base component id so that {@link #resetComponentsFor(ActionEvent)} can look it up in the hierarchy of
079     * components, and reset component states recursively from it.
080     *
081     * @since 5.9.1
082     * @see #resetComponentsFor(ActionEvent)
083     */
084    public void setBaseComponentId(String baseComponentId) {
085        this.baseComponentId = baseComponentId;
086    }
087
088    /**
089     * Looks up the parent naming container for the component source of the action event, and reset components
090     * recursively within this container.
091     *
092     * @since 5.9.1
093     */
094    public void resetComponentsFor(ActionEvent event) {
095        resetComponentsFor((FacesEvent) event);
096    }
097
098    /**
099     * Looks up the parent naming container for the corresponding ajax behavior event, and reset components recursively
100     * within this container.
101     *
102     * @since 8.1
103     */
104    public void resetComponentsFor(AjaxBehaviorEvent event) {
105        resetComponentsFor((FacesEvent) event);
106    }
107
108    protected void resetComponentsFor(FacesEvent event) {
109        UIComponent component = event.getComponent();
110        if (component == null) {
111            return;
112        }
113        String baseCompId = getStringAttribute(component, "target", false);
114        if (baseCompId == null) {
115            // compat
116            baseCompId = getBaseComponentId();
117        }
118        if (baseCompId != null) {
119            String[] split = baseCompId.split("\\s");
120            if (split != null) {
121                for (String item : split) {
122                    if (!StringUtils.isBlank(item)) {
123                        UIComponent anchor = ComponentRenderUtils.getComponent(component, item);
124                        log.error(anchor);
125                        resetComponentResursive(anchor);
126                    }
127                }
128            }
129        } else {
130            log.error("No base component id given => cannot reset components state.");
131        }
132    }
133
134    protected String getStringAttribute(UIComponent component, String name, boolean required) {
135        Object value = component.getAttributes().get(name);
136        if (required && value == null) {
137            throw new IllegalArgumentException("Component attribute with name '" + name + "' cannot be null: " + value);
138        }
139        if (value == null || value instanceof String) {
140            return (String) value;
141        }
142        throw new IllegalArgumentException("Component attribute with name '" + name + "' is not a String: " + value);
143    }
144
145    /**
146     * Looks up the parent naming container for the component source of the action event, and reset components
147     * recursively within this container.
148     */
149    public void resetComponents(ActionEvent event) {
150        resetComponents((FacesEvent) event);
151    }
152
153    /**
154     * Looks up the parent naming container for the component source of the action event, and reset components
155     * recursively within this container.
156     *
157     * @since 6.0
158     */
159    public void resetComponents(AjaxBehaviorEvent event) {
160        resetComponents((FacesEvent) event);
161    }
162
163    protected void resetComponents(FacesEvent event) {
164        UIComponent component = event.getComponent();
165        if (component == null) {
166            return;
167        }
168        // take first anchor and force flush on every resettable component
169        UIComponent anchor = component.getNamingContainer();
170        if (anchor == null) {
171            resetComponentResursive(component);
172        } else {
173            resetComponentResursive(anchor);
174        }
175    }
176
177    /**
178     * Resets the given component.
179     * <p>
180     * Does not reset the component children.
181     */
182    public void resetComponent(UIComponent component) {
183        resetComponent(component, false);
184    }
185
186    /**
187     * Resets the given component and its children recursively.
188     */
189    public void resetComponentResursive(UIComponent parent) {
190        resetComponent(parent, true);
191    }
192
193    protected void resetComponent(UIComponent comp, boolean recursive) {
194        if (comp == null) {
195            return;
196        }
197        if (comp instanceof ResettableComponent) {
198            ((ResettableComponent) comp).resetCachedModel();
199        } else {
200            if (comp instanceof EditableValueHolder) {
201                // reset submitted value
202                ((EditableValueHolder) comp).setSubmittedValue(null);
203            }
204            if (comp instanceof ValueHolder) {
205                // reset local value, only if there's a value expression
206                // binding
207                ValueExpression ve = comp.getValueExpression("value");
208                if (ve != null) {
209                    ValueHolder vo = (ValueHolder) comp;
210                    vo.setValue(null);
211                    if (comp instanceof EditableValueHolder) {
212                        ((EditableValueHolder) comp).setLocalValueSet(false);
213                    }
214                }
215            }
216        }
217        if (recursive) {
218            List<UIComponent> children = comp.getChildren();
219            if (children != null && !children.isEmpty()) {
220                for (UIComponent child : children) {
221                    resetComponent(child, recursive);
222                }
223            }
224        }
225    }
226
227}