001/*
002 * (C) Copyright 2006-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 *     George Lefter
016 *
017 * $Id$
018 */
019
020package org.nuxeo.ecm.platform.ui.web.directory;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.List;
026import java.util.Map;
027
028import javax.faces.component.UICommand;
029import javax.faces.component.UIGraphic;
030import javax.faces.component.html.HtmlInputHidden;
031import javax.faces.context.FacesContext;
032import javax.faces.context.ResponseWriter;
033
034import org.apache.commons.lang.StringUtils;
035import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils;
036
037/**
038 * @author <a href="mailto:glefter@nuxeo.com">George Lefter</a>
039 */
040public class ChainSelectMany extends ChainSelectBase {
041
042    public static final String ADD_BUTTON = "addButton";
043
044    private static final String REMOVE_BUTTON = "removeButton";
045
046    /* a hidden input set if the value of the component is empty */
047    private static final String EMPTY_VALUE_MARKER = "emptyValueMarker";
048
049    /* a hidden input to hold the remove entry id */
050    private static final String REMOVE_HIDDEN = "removeHidden";
051
052    /* a hidden input set if the user click the add button */
053    private static final String ADD_HIDDEN = "addHidden";
054
055    public ChainSelectMany() {
056        FacesContext context = FacesContext.getCurrentInstance();
057
058        HtmlInputHidden emptyValueMarker = new HtmlInputHidden();
059        emptyValueMarker.setId("emptyValueMarker");
060        emptyValueMarker.setValue("true");
061        getFacets().put(EMPTY_VALUE_MARKER, emptyValueMarker);
062
063        UICommand addButton = (UICommand) context.getApplication().createComponent("org.ajax4jsf.ajax.CommandButton");
064        addButton.getAttributes().put("id", "addButton");
065        addButton.getAttributes().put("value", "add");
066        getFacets().put(ADD_BUTTON, addButton);
067
068        HtmlInputHidden addHidden = new HtmlInputHidden();
069        addHidden.setId("addHidden");
070        getFacets().put(ADD_HIDDEN, addHidden);
071
072        UICommand removeButton = (UICommand) context.getApplication().createComponent("org.ajax4jsf.ajax.CommandLink");
073        UIGraphic image = new UIGraphic();
074        image.setValue("/icons/delete.png");
075        removeButton.getAttributes().put("id", "removeButton");
076        removeButton.getChildren().add(image);
077        getFacets().put(REMOVE_BUTTON, removeButton);
078
079        HtmlInputHidden removeHidden = new HtmlInputHidden();
080        removeHidden.setId("removeHidden");
081        getFacets().put(REMOVE_HIDDEN, removeHidden);
082    }
083
084    @Override
085    public void decode(FacesContext context) {
086        if (getDisplayValueOnly()) {
087            return;
088        }
089        decodeSelection(context);
090        decodeValue(context);
091        setValid(true);
092    }
093
094    private void decodeValue(FacesContext context) {
095        String emptyValueMarkerClientId = getFacet(EMPTY_VALUE_MARKER).getClientId(context);
096        String removeEntryClientId = getFacet(REMOVE_HIDDEN).getClientId(context);
097        String addEntryClientId = getFacet(ADD_HIDDEN).getClientId(context);
098        Map<String, String> map = context.getExternalContext().getRequestParameterMap();
099
100        String removeEntryId = map.get(removeEntryClientId);
101        boolean addButtonClicked = "true".equals(map.get(addEntryClientId));
102        String allValues = map.get(emptyValueMarkerClientId);
103
104        String[] oldValue = StringUtils.split(allValues, ",");
105
106        List<String> valueList = new ArrayList<String>(Arrays.asList(oldValue));
107
108        if (addButtonClicked) {
109            String[] selection = getSelection();
110            if (validateEntry(context, selection)) {
111                valueList.add(StringUtils.join(selection, getKeySeparator()));
112            }
113        }
114
115        if (!StringUtils.isEmpty(removeEntryId)) {
116            valueList.remove(removeEntryId);
117        }
118
119        String[] newValue = valueList.toArray(new String[valueList.size()]);
120        setSubmittedValue(newValue);
121    }
122
123    @Override
124    public String[] getSelection() {
125        String clientId = getClientId(FacesContext.getCurrentInstance());
126        String[] selection = selectionMap.get(clientId);
127        if (selection == null) {
128            selection = new String[0];
129            selectionMap.put(clientId, selection);
130        }
131        return selection;
132    }
133
134    @Override
135    public void encodeBegin(FacesContext context) throws IOException {
136        if (getDisplayValueOnly()) {
137            encodeReadOnly(context);
138        } else {
139            encodeReadWrite(context);
140        }
141    }
142
143    public void encodeReadWrite(FacesContext context) throws IOException {
144        ResponseWriter writer = context.getResponseWriter();
145
146        getChildren().clear();
147        writer.startElement("div", this);
148        writer.writeAttribute("id", getClientId(context), "id");
149        String style = getStyle();
150        if (style != null) {
151            writer.writeAttribute("style", style, "style");
152        }
153        String styleClass = getStyleClass();
154        if (styleClass != null) {
155            writer.writeAttribute("class", styleClass, "class");
156        }
157
158        String[] selectedKeys = getSelection();
159        for (int level = 0; level < getDepth(); level++) {
160            encodeListbox(context, level, selectedKeys);
161        }
162
163        encodeAddButton(context);
164        encodeValue(context);
165        writer.endElement("div");
166    }
167
168    public void encodeReadOnly(FacesContext context) throws IOException {
169        ResponseWriter writer = context.getResponseWriter();
170        String[] values = (String[]) getSubmittedValue();
171        if (values == null) {
172            values = (String[]) getValue();
173        }
174        if (values != null) {
175            writer.startElement("div", this);
176            for (String value : values) {
177                String[] keys = StringUtils.split(value, getKeySeparator());
178                List<DirectoryEntry> nodes = resolveKeys(keys);
179                List<String> labels = new ArrayList<String>();
180                for (DirectoryEntry node : nodes) {
181                    String itemValue = node.getId();
182                    String itemLabel = node.getLabel();
183                    itemLabel = computeItemLabel(context, itemValue, itemLabel);
184                    labels.add(itemLabel);
185                }
186                String concatenatedLabel = StringUtils.join(labels.iterator(), getKeySeparator());
187
188                writer.startElement("div", this);
189                writer.write(concatenatedLabel);
190                writer.endElement("div");
191            }
192            writer.endElement("div");
193        }
194    }
195
196    private void encodeAddButton(FacesContext context) throws IOException {
197        UICommand addButton = (UICommand) getFacet(ADD_BUTTON);
198        HtmlInputHidden addHidden = (HtmlInputHidden) getFacet(ADD_HIDDEN);
199        String addJs = String.format("document.getElementById('%s').value = '%s'", addHidden.getClientId(context),
200                "true");
201        addButton.getAttributes().put("onclick", addJs);
202        addHidden.setValue("");
203
204        String reRender = getReRender();
205        if (reRender == null) {
206            reRender = getId();
207        }
208        addButton.getAttributes().put("reRender", reRender);
209        ComponentUtils.encodeComponent(context, addButton);
210        ComponentUtils.encodeComponent(context, addHidden);
211    }
212
213    @Override
214    public boolean getRendersChildren() {
215        return true;
216    }
217
218    @Override
219    public void encodeChildren(FacesContext context) throws IOException {
220    }
221
222    private void encodeValue(FacesContext context) throws IOException {
223        ResponseWriter writer = context.getResponseWriter();
224
225        String[] values = (String[]) getSubmittedValue();
226        if (values == null) {
227            values = (String[]) getValue();
228        }
229
230        String compValue = StringUtils.join(values, ",");
231        compValue = compValue == null ? "" : compValue;
232        HtmlInputHidden emptyValueMarker = (HtmlInputHidden) getFacet(EMPTY_VALUE_MARKER);
233        emptyValueMarker.setValue(compValue);
234        ComponentUtils.encodeComponent(context, emptyValueMarker);
235
236        if (values != null) {
237            HtmlInputHidden removeHidden = (HtmlInputHidden) getFacet(REMOVE_HIDDEN);
238            removeHidden.setValue("");
239            ComponentUtils.encodeComponent(context, removeHidden);
240            UICommand removeButton = (UICommand) getFacet(REMOVE_BUTTON);
241            String reRender = getReRender();
242            if (reRender == null) {
243                reRender = getId();
244            }
245            removeButton.getAttributes().put("reRender", reRender);
246            writer.startElement("div", this);
247            for (String value : values) {
248                String[] keys = StringUtils.split(value, getKeySeparator());
249                List<DirectoryEntry> nodes = resolveKeys(keys);
250                List<String> labels = new ArrayList<String>();
251                for (DirectoryEntry node : nodes) {
252                    String itemValue = node.getId();
253                    String itemLabel = node.getLabel();
254                    itemLabel = computeItemLabel(context, itemValue, itemLabel);
255                    labels.add(itemLabel);
256                }
257                String concatenatedLabel = StringUtils.join(labels.iterator(), getKeySeparator());
258
259                writer.startElement("div", this);
260                String removeJs = String.format("document.getElementById('%s').value = '%s'",
261                        removeHidden.getClientId(context), value);
262                removeButton.getAttributes().put("onclick", removeJs);
263                ComponentUtils.encodeComponent(context, removeButton);
264                writer.write(concatenatedLabel);
265                writer.endElement("div");
266            }
267            writer.endElement("div");
268        }
269    }
270
271    /*
272     * the number of visible components
273     */
274    public int getCurrentDepth() {
275        return getDepth();
276    }
277
278    @Override
279    public String getFamily() {
280        return "nxdirectory.ChainSelectMany";
281    }
282
283    @Override
284    public String getRendererType() {
285        return null;
286    }
287
288    @Override
289    public Object saveState(FacesContext context) {
290        Object[] values = new Object[1];
291        values[0] = super.saveState(context);
292        return values;
293    }
294
295    @Override
296    public void restoreState(FacesContext context, Object state) {
297        Object[] values = (Object[]) state;
298        super.restoreState(context, values[0]);
299    }
300
301}