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