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