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}