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}