001/*
002 * (C) Copyright 2011 Nuxeo SA (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 *     Anahide Tchertchian
016 */
017package org.nuxeo.ecm.platform.forms.layout.facelets.plugins;
018
019import java.io.Serializable;
020import java.util.ArrayList;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024
025import javax.faces.component.html.HtmlSelectManyCheckbox;
026import javax.faces.component.html.HtmlSelectManyListbox;
027import javax.faces.component.html.HtmlSelectManyMenu;
028import javax.faces.view.facelets.ComponentHandler;
029import javax.faces.view.facelets.CompositeFaceletHandler;
030import javax.faces.view.facelets.FaceletContext;
031import javax.faces.view.facelets.FaceletHandler;
032import javax.faces.view.facelets.TagAttributes;
033import javax.faces.view.facelets.TagConfig;
034
035import org.apache.commons.lang.ArrayUtils;
036import org.nuxeo.ecm.platform.forms.layout.api.BuiltinWidgetModes;
037import org.nuxeo.ecm.platform.forms.layout.api.Widget;
038import org.nuxeo.ecm.platform.forms.layout.api.WidgetSelectOption;
039import org.nuxeo.ecm.platform.forms.layout.api.WidgetSelectOptions;
040import org.nuxeo.ecm.platform.forms.layout.api.exceptions.WidgetException;
041import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetSelectOptionImpl;
042import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetSelectOptionsImpl;
043import org.nuxeo.ecm.platform.forms.layout.facelets.FaceletHandlerHelper;
044import org.nuxeo.ecm.platform.ui.web.component.UISelectItem;
045import org.nuxeo.ecm.platform.ui.web.component.UISelectItems;
046import org.nuxeo.ecm.platform.ui.web.tag.handler.LeafFaceletHandler;
047import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils;
048
049/**
050 * Helper class for options generation depending on the widget definition
051 *
052 * @since 5.4.2
053 */
054public abstract class AbstractSelectWidgetTypeHandler extends AbstractWidgetTypeHandler {
055
056    private static final long serialVersionUID = 1L;
057
058    protected enum SelectPropertyMappings {
059        selectOptions, var, itemLabel, resolveItemLabelTwice, itemLabelPrefix, itemLabelPrefixSeparator,
060        //
061        itemLabelSuffix, itemLabelSuffixSeparator, itemValue,
062        //
063        itemRendered, itemDisabled, itemEscaped, ordering, caseSensitive,
064        //
065        displayIdAndLabel, displayIdAndLabelSeparator, notDisplayDefaultOption,
066        //
067        localize, dbl10n;
068    }
069
070    // ease up override of behavior without impacting default options
071    // management
072    protected Map<String, Serializable> getOptionProperties(FaceletContext ctx, Widget widget,
073            WidgetSelectOption selectOption) {
074        Map<String, Serializable> props = new HashMap<>();
075        for (SelectPropertyMappings mapping : SelectPropertyMappings.values()) {
076            if (widget.getProperties().containsKey(mapping.name())) {
077                props.put(mapping.name(), widget.getProperty(mapping.name()));
078            }
079        }
080        return props;
081    }
082
083    protected String getOptionComponentType(WidgetSelectOption selectOption) {
084        if (selectOption instanceof WidgetSelectOptions) {
085            return UISelectItems.COMPONENT_TYPE;
086        } else {
087            return UISelectItem.COMPONENT_TYPE;
088        }
089    }
090
091    protected FaceletHandler getOptionFaceletHandler(FaceletContext ctx, FaceletHandlerHelper helper, Widget widget,
092            WidgetSelectOption selectOption, FaceletHandler nextHandler) {
093        String componentType = getOptionComponentType(selectOption);
094        TagAttributes attrs = helper.getTagAttributes(selectOption, getOptionProperties(ctx, widget, selectOption));
095        return helper.getHtmlComponentHandler(widget.getTagConfigId(), attrs, nextHandler, componentType, null);
096    }
097
098    // not impacted by custom behaviour by default
099    protected FaceletHandler getBareOptionFaceletHandler(FaceletContext ctx, FaceletHandlerHelper helper,
100            Widget widget, WidgetSelectOption selectOption, FaceletHandler nextHandler) {
101        String componentType = getOptionComponentType(selectOption);
102        TagAttributes attrs = helper.getTagAttributes(selectOption);
103        return helper.getHtmlComponentHandler(widget.getTagConfigId(), attrs, nextHandler, componentType, null);
104    }
105
106    /**
107     * Adds a default disabled "select a value" option if widget is not required.
108     *
109     * @since 6.0
110     */
111    protected FaceletHandler getFirstHandler(FaceletContext ctx, FaceletHandlerHelper helper, Widget widget,
112            FaceletHandler nextHandler) {
113        Object doNotDisplay = widget.getProperty(SelectPropertyMappings.notDisplayDefaultOption.name());
114        if (doNotDisplay != null) {
115            if (Boolean.TRUE.equals(doNotDisplay)) {
116                return null;
117            }
118            if (doNotDisplay instanceof String) {
119                Object res = ComponentTagUtils.resolveElExpression(ctx, (String) doNotDisplay);
120                if ((res instanceof Boolean && Boolean.TRUE.equals(res))
121                        || (res instanceof String && Boolean.parseBoolean((String) res))) {
122                    return null;
123                }
124            }
125        }
126        String bundleName = ctx.getFacesContext().getApplication().getMessageBundle();
127        String localizedExpression = String.format("#{%s['%s']}", bundleName, "label.vocabulary.selectValue");
128        WidgetSelectOption selectOption = new WidgetSelectOptionImpl("", "", localizedExpression, "", Boolean.FALSE,
129                Boolean.TRUE);
130        return getBareOptionFaceletHandler(ctx, helper, widget, selectOption, nextHandler);
131    }
132
133    /**
134     * Returns true if widget properties should generate a default tag handler for select options.
135     * <p>
136     * This default implementation requires the selectOptions widget property to be filled.
137     *
138     * @since 6.0
139     */
140    protected boolean shouldAddWidgetPropsHandler(Widget widget) {
141        if (widget.getProperties().containsKey(SelectPropertyMappings.selectOptions.name())) {
142            return true;
143        }
144        return false;
145    }
146
147    /**
148     * Computes select options from widget properties.
149     *
150     * @since 6.0
151     */
152    protected FaceletHandler getWidgetPropsHandler(FaceletContext ctx, FaceletHandlerHelper helper, Widget widget,
153            FaceletHandler nextHandler) {
154        if (shouldAddWidgetPropsHandler(widget)) {
155            WidgetSelectOptionsImpl selectOption = new WidgetSelectOptionsImpl(
156                    widget.getProperty(SelectPropertyMappings.selectOptions.name()),
157                    (String) widget.getProperty(SelectPropertyMappings.var.name()),
158                    (String) widget.getProperty(SelectPropertyMappings.itemLabel.name()),
159                    (String) widget.getProperty(SelectPropertyMappings.itemValue.name()),
160                    widget.getProperty(SelectPropertyMappings.itemDisabled.name()),
161                    widget.getProperty(SelectPropertyMappings.itemRendered.name()));
162            return getOptionFaceletHandler(ctx, helper, widget, selectOption, nextHandler);
163        }
164        return null;
165    }
166
167    protected FaceletHandler getOptionsFaceletHandler(FaceletContext ctx, FaceletHandlerHelper helper, Widget widget,
168            WidgetSelectOption[] selectOptions) {
169        FaceletHandler leaf = new LeafFaceletHandler();
170        List<FaceletHandler> selectItems = new ArrayList<FaceletHandler>();
171        FaceletHandler firstItem = getFirstHandler(ctx, helper, widget, leaf);
172        if (firstItem != null) {
173            selectItems.add(firstItem);
174        }
175        FaceletHandler widgetPropsHandler = getWidgetPropsHandler(ctx, helper, widget, leaf);
176        if (widgetPropsHandler != null) {
177            selectItems.add(widgetPropsHandler);
178        }
179        if (selectOptions != null && selectOptions.length > 0) {
180            for (WidgetSelectOption selectOption : selectOptions) {
181                if (selectOption == null) {
182                    continue;
183                }
184                FaceletHandler h = getBareOptionFaceletHandler(ctx, helper, widget, selectOption, leaf);
185                if (h != null) {
186                    selectItems.add(h);
187                }
188            }
189        }
190        return new CompositeFaceletHandler(selectItems.toArray(new FaceletHandler[0]));
191    }
192
193    protected FaceletHandler getOptionsFaceletHandler(FaceletContext ctx, FaceletHandlerHelper helper, Widget widget) {
194        return getOptionsFaceletHandler(ctx, helper, widget, widget.getSelectOptions());
195    }
196
197    /**
198     * Returns properties useful for select items, not to be reported on the select component.
199     */
200    protected List<String> getExcludedProperties() {
201        List<String> excludedProps = new ArrayList<>();
202        // BBB
203        excludedProps.add("cssStyle");
204        excludedProps.add("cssStyleClass");
205        for (SelectPropertyMappings mapping : SelectPropertyMappings.values()) {
206            excludedProps.add(mapping.name());
207        }
208        return excludedProps;
209    }
210
211    protected FaceletHandler getFaceletHandler(FaceletContext ctx, TagConfig tagConfig, Widget widget,
212            FaceletHandler[] subHandlers, String componentType) throws WidgetException {
213        return getFaceletHandler(ctx, tagConfig, widget, subHandlers, componentType, null);
214    }
215
216    protected FaceletHandler getComponentFaceletHandler(FaceletContext ctx, FaceletHandlerHelper helper, Widget widget,
217            FaceletHandler componentHandler) {
218        return componentHandler;
219    }
220
221    protected FaceletHandler getFaceletHandler(FaceletContext ctx, TagConfig tagConfig, Widget widget,
222            FaceletHandler[] subHandlers, String componentType, String rendererType) throws WidgetException {
223        FaceletHandlerHelper helper = new FaceletHandlerHelper(ctx, tagConfig);
224        String mode = widget.getMode();
225        String widgetId = widget.getId();
226        String widgetName = widget.getName();
227        String widgetTagConfigId = widget.getTagConfigId();
228        List<String> excludedProps = getExcludedProperties();
229        TagAttributes attributes = helper.getTagAttributes(widget, excludedProps, true);
230        // BBB for CSS style classes on directory select components
231        if (widget.getProperty("cssStyle") != null) {
232            attributes = FaceletHandlerHelper.addTagAttribute(attributes,
233                    helper.createAttribute("style", (String) widget.getProperty("cssStyle")));
234        }
235        if (widget.getProperty("cssStyleClass") != null) {
236            attributes = FaceletHandlerHelper.addTagAttribute(attributes,
237                    helper.createAttribute("styleClass", (String) widget.getProperty("cssStyleClass")));
238        }
239        if (!BuiltinWidgetModes.isLikePlainMode(mode)) {
240            attributes = FaceletHandlerHelper.addTagAttribute(attributes, helper.createAttribute("id", widgetId));
241        }
242        if (BuiltinWidgetModes.EDIT.equals(mode)) {
243            FaceletHandler optionsHandler = getOptionsFaceletHandler(ctx, helper, widget);
244            FaceletHandler[] nextHandlers = new FaceletHandler[] {};
245            nextHandlers = (FaceletHandler[]) ArrayUtils.add(nextHandlers, optionsHandler);
246            if (subHandlers != null) {
247                nextHandlers = (FaceletHandler[]) ArrayUtils.addAll(nextHandlers, subHandlers);
248            }
249            FaceletHandler leaf = getNextHandler(ctx, tagConfig, widget, nextHandlers, helper, true, true);
250            // maybe add convert handler for easier integration of select2
251            // widgets handling multiple values
252            if (HtmlSelectManyListbox.COMPONENT_TYPE.equals(componentType)
253                    || HtmlSelectManyCheckbox.COMPONENT_TYPE.equals(componentType)
254                    || HtmlSelectManyMenu.COMPONENT_TYPE.equals(componentType)) {
255                // add hint for value conversion to collection
256                attributes = FaceletHandlerHelper.addTagAttribute(attributes,
257                        helper.createAttribute("collectionType", ArrayList.class.getName()));
258            }
259
260            ComponentHandler input = helper.getHtmlComponentHandler(widgetTagConfigId, attributes, leaf, componentType,
261                    rendererType);
262            String msgId = helper.generateMessageId(widgetName);
263            ComponentHandler message = helper.getMessageComponentHandler(widgetTagConfigId, msgId, widgetId, null);
264            FaceletHandler[] handlers = { getComponentFaceletHandler(ctx, helper, widget, input), message };
265            return new CompositeFaceletHandler(handlers);
266        } else {
267            // TODO
268            return null;
269        }
270    }
271}