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