001/* 002 * (C) Copyright 2007 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nuxeo - initial API and implementation 016 * 017 * $Id: EditableListBean.java 25566 2007-10-01 14:01:21Z atchertchian $ 018 */ 019 020package org.nuxeo.ecm.platform.ui.web.component.list; 021 022import java.util.ArrayList; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026 027import javax.faces.component.UIComponent; 028import javax.faces.context.ExternalContext; 029import javax.faces.context.FacesContext; 030import javax.faces.event.ActionEvent; 031import javax.faces.event.AjaxBehaviorEvent; 032import javax.faces.event.FacesEvent; 033 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.nuxeo.ecm.platform.ui.web.model.EditableModel; 037import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils; 038 039/** 040 * Bean used to interact with {@link UIEditableList} component. 041 * <p> 042 * Used to add/remove items from a list. 043 * <p> 044 * Optionally used to work around some unwanted behaviour in data tables. 045 * 046 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> 047 */ 048public class EditableListBean { 049 050 private static final Log log = LogFactory.getLog(EditableListBean.class); 051 052 public static final String FOR_PARAMETER_NAME = "for"; 053 054 public static final String INDEX_PARAMETER_NAME = "index"; 055 056 public static final String TYPE_PARAMETER_NAME = "type"; 057 058 public static final String NUMBER_PARAMETER_NAME = "number"; 059 060 protected UIComponent binding; 061 062 // dont make it static so that jsf can call it 063 public UIComponent getBinding() { 064 return binding; 065 } 066 067 // dont make it static so that jsf can call it 068 public void setBinding(UIComponent binding) { 069 this.binding = binding; 070 } 071 072 // don't make it static so that jsf can call it 073 public void performAction(String listComponentId, String index, String type) { 074 if (binding == null) { 075 log.error("Component binding not set, cannot perform action"); 076 return; 077 } 078 Map<String, String> requestMap = new HashMap<String, String>(); 079 requestMap.put(FOR_PARAMETER_NAME, listComponentId); 080 requestMap.put(INDEX_PARAMETER_NAME, index); 081 requestMap.put(TYPE_PARAMETER_NAME, type); 082 performAction(binding, requestMap); 083 } 084 085 public void performAction(ActionEvent event) { 086 performAction((FacesEvent) event); 087 } 088 089 /** 090 * @since 6.0 091 */ 092 public void performAction(AjaxBehaviorEvent event) { 093 performAction((FacesEvent) event); 094 } 095 096 protected void performAction(FacesEvent event) { 097 UIComponent component = event.getComponent(); 098 if (component == null) { 099 return; 100 } 101 FacesContext context = FacesContext.getCurrentInstance(); 102 ExternalContext eContext = context.getExternalContext(); 103 Map<String, String> requestMap = eContext.getRequestParameterMap(); 104 performAction(component, requestMap); 105 } 106 107 /** 108 * Resets all {@link UIEditableList} components cached model in first container found thanks to given event 109 * 110 * @since 5.3.1 111 * @deprecated since 5.6: the component resets its cache correctly after update now so forcing the reset is now 112 * useless 113 */ 114 @Deprecated 115 public void resetAllListsCachedModels(ActionEvent event) { 116 UIComponent component = event.getComponent(); 117 if (component == null) { 118 return; 119 } 120 // take first anchor and force flush on every list component 121 UIComponent anchor = ComponentUtils.getBase(component); 122 resetListCachedModels(anchor); 123 } 124 125 /** 126 * @deprecated since 5.6: the component resets its cache correctly after update now so forcing the reset is now 127 * useless 128 */ 129 @Deprecated 130 protected void resetListCachedModels(UIComponent parent) { 131 if (parent == null) { 132 return; 133 } 134 if (parent instanceof UIEditableList) { 135 ((UIEditableList) parent).resetCachedModel(); 136 } 137 List<UIComponent> children = parent.getChildren(); 138 if (children != null && !children.isEmpty()) { 139 for (UIComponent child : children) { 140 resetListCachedModels(child); 141 } 142 } 143 } 144 145 protected static void performAction(UIComponent binding, Map<String, String> requestMap) { 146 UIEditableList editableComp = getEditableListComponent(binding, requestMap); 147 if (editableComp == null) { 148 return; 149 } 150 EditableListModificationType type = getModificationType(requestMap); 151 if (type == null) { 152 return; 153 } 154 Integer index; 155 Integer number; 156 157 EditableModel model = editableComp.getEditableModel(); 158 Object template = editableComp.getTemplate(); 159 switch (type) { 160 case ADD: 161 number = getNumber(requestMap); 162 if (number == null) { 163 // perform add only once 164 model.addValue(template); 165 } else { 166 for (int i = 0; i < number; i++) { 167 model.addTemplateValue(); 168 } 169 } 170 break; 171 case INSERT: 172 index = getIndex(requestMap); 173 if (index == null) { 174 return; 175 } 176 number = getNumber(requestMap); 177 if (number == null) { 178 // perform insert only once 179 model.insertValue(index, template); 180 } else { 181 for (int i = 0; i < number; i++) { 182 model.insertTemplateValue(index); 183 } 184 } 185 break; 186 case REMOVE: 187 index = getIndex(requestMap); 188 if (index == null) { 189 return; 190 } 191 model.removeValue(index); 192 break; 193 case MOVEUP: 194 index = getIndex(requestMap); 195 if (index == null) { 196 return; 197 } 198 model.moveValue(index, index - 1); 199 break; 200 case MOVEDOWN: 201 index = getIndex(requestMap); 202 if (index == null) { 203 return; 204 } 205 model.moveValue(index, index + 1); 206 break; 207 } 208 } 209 210 protected static String getParameterValue(Map<String, String> requestMap, String parameterName) { 211 String string = requestMap.get(parameterName); 212 if (string == null || string.length() == 0) { 213 return null; 214 } else { 215 return string; 216 } 217 } 218 219 protected static UIEditableList getEditableListComponent(UIComponent component, Map<String, String> requestMap) { 220 UIEditableList listComponent = null; 221 String forString = getParameterValue(requestMap, FOR_PARAMETER_NAME); 222 if (forString == null) { 223 log.error(String.format("Could not find '%s' parameter in the request map", FOR_PARAMETER_NAME)); 224 } else { 225 UIComponent forComponent = component.findComponent(forString); 226 if (forComponent == null) { 227 log.error("Could not find component with id: " + forString); 228 } else if (!(forComponent instanceof UIEditableList)) { 229 log.error(String.format("Invalid component with id %s: %s, expected a " + "component with class %s", 230 forString, forComponent, UIEditableList.class)); 231 } else { 232 listComponent = (UIEditableList) forComponent; 233 } 234 } 235 return listComponent; 236 } 237 238 protected static EditableListModificationType getModificationType(Map<String, String> requestMap) { 239 EditableListModificationType type = null; 240 String typeString = getParameterValue(requestMap, TYPE_PARAMETER_NAME); 241 if (typeString == null) { 242 log.error(String.format("Could not find '%s' parameter in the request map", TYPE_PARAMETER_NAME)); 243 } else { 244 try { 245 type = EditableListModificationType.valueOfString(typeString); 246 } catch (IllegalArgumentException err) { 247 log.error(String.format("Illegal value for '%s' attribute: %s, " + "should be one of %s", 248 TYPE_PARAMETER_NAME, typeString, EditableListModificationType.values())); 249 } 250 } 251 return type; 252 } 253 254 protected static Integer getIndex(Map<String, String> requestMap) { 255 Integer index = null; 256 String indexString = getParameterValue(requestMap, INDEX_PARAMETER_NAME); 257 if (indexString == null) { 258 log.error(String.format("Could not find '%s' parameter in the request map", INDEX_PARAMETER_NAME)); 259 } else { 260 try { 261 index = Integer.valueOf(indexString); 262 } catch (NumberFormatException e) { 263 log.error(String.format("Illegal value for '%s' attribute: %s, " + "should be integer", 264 INDEX_PARAMETER_NAME, indexString)); 265 } 266 } 267 return index; 268 } 269 270 protected static Integer getNumber(Map<String, String> requestMap) { 271 Integer number = null; 272 String numberString = getParameterValue(requestMap, NUMBER_PARAMETER_NAME); 273 if (numberString != null) { 274 try { 275 number = Integer.valueOf(numberString); 276 } catch (NumberFormatException e) { 277 log.error(String.format("Illegal value for '%s' attribute: %s, " + "should be integer", 278 NUMBER_PARAMETER_NAME, numberString)); 279 } 280 } 281 return number; 282 } 283 284 /** 285 * Dummy list of one item, used to wrap a table within another table. 286 * <p> 287 * A table resets its saved state when decoding, which is a problem when saving a file temporarily: as it will not 288 * be submitted again in the request, the new value will be lost. The table is not reset when embedded in another 289 * table, so we can use this list as value of the embedding table as a work around. 290 * 291 * @return dummy list of one item 292 */ 293 // don't make it static so that jsf can call it 294 public List<Object> getDummyList() { 295 List<Object> dummy = new ArrayList<Object>(1); 296 dummy.add("dummy"); 297 return dummy; 298 } 299 300}