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