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