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}