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                        log.error(anchor);
126                        resetComponentResursive(anchor);
127                    }
128                }
129            }
130        } else {
131            log.error("No base component id given => cannot reset components state.");
132        }
133    }
134
135    /**
136     * Looks up the parent naming container for the component source of the action event, and reset components
137     * recursively within this container.
138     */
139    public void resetComponents(ActionEvent event) {
140        resetComponents((FacesEvent) event);
141    }
142
143    /**
144     * Looks up the parent naming container for the component source of the action event, and reset components
145     * recursively within this container.
146     *
147     * @since 6.0
148     */
149    public void resetComponents(AjaxBehaviorEvent event) {
150        resetComponents((FacesEvent) event);
151    }
152
153    protected void resetComponents(FacesEvent event) {
154        UIComponent component = event.getComponent();
155        if (component == null) {
156            return;
157        }
158        // take first anchor and force flush on every resettable component
159        UIComponent anchor = component.getNamingContainer();
160        if (anchor == null) {
161            resetComponentResursive(component);
162        } else {
163            resetComponentResursive(anchor);
164        }
165    }
166
167    /**
168     * Resets the given component.
169     * <p>
170     * Does not reset the component children.
171     */
172    public void resetComponent(UIComponent component) {
173        resetComponent(component, false);
174    }
175
176    /**
177     * Resets the given component and its children recursively.
178     */
179    public void resetComponentResursive(UIComponent parent) {
180        resetComponent(parent, true);
181    }
182
183    protected void resetComponent(UIComponent comp, boolean recursive) {
184        if (comp == null) {
185            return;
186        }
187        if (comp instanceof ResettableComponent) {
188            ((ResettableComponent) comp).resetCachedModel();
189        } else {
190            if (comp instanceof EditableValueHolder) {
191                // reset submitted value
192                ((EditableValueHolder) comp).setSubmittedValue(null);
193            }
194            if (comp instanceof ValueHolder) {
195                // reset local value, only if there's a value expression
196                // binding
197                ValueExpression ve = comp.getValueExpression("value");
198                if (ve != null) {
199                    ValueHolder vo = (ValueHolder) comp;
200                    vo.setValue(null);
201                    if (comp instanceof EditableValueHolder) {
202                        ((EditableValueHolder) comp).setLocalValueSet(false);
203                    }
204                }
205            }
206        }
207        if (recursive) {
208            List<UIComponent> children = comp.getChildren();
209            if (children != null && !children.isEmpty()) {
210                for (UIComponent child : children) {
211                    resetComponent(child, recursive);
212                }
213            }
214        }
215    }
216
217}