com.flexive.faces.renderer.FxSelectRenderer.java Source code

Java tutorial

Introduction

Here is the source code for com.flexive.faces.renderer.FxSelectRenderer.java

Source

/***************************************************************
 *  This file is part of the [fleXive](R) framework.
 *
 *  Copyright (c) 1999-2014
 *  UCS - unique computing solutions gmbh (http://www.ucs.at)
 *  All rights reserved
 *
 *  The [fleXive](R) project is free software; you can redistribute
 *  it and/or modify it under the terms of the GNU Lesser General Public
 *  License version 2.1 or higher as published by the Free Software Foundation.
 *
 *  The GNU Lesser General Public License can be found at
 *  http://www.gnu.org/licenses/lgpl.html.
 *  A copy is found in the textfile LGPL.txt and important notices to the
 *  license from the author are found in LICENSE.txt distributed with
 *  these libraries.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  For further information about UCS - unique computing solutions gmbh,
 *  please see the company website: http://www.ucs.at
 *
 *  For further information about [fleXive](R), please see the
 *  project website: http://www.flexive.org
 *
 *
 *  This copyright notice MUST APPEAR in all copies of the file!
 ***************************************************************/
package com.flexive.faces.renderer;

import com.flexive.faces.FxJsfComponentUtils;
import com.flexive.faces.javascript.FxJavascriptUtils;
import com.flexive.faces.model.FxJSFSelectItem;
import com.flexive.shared.*;
import com.flexive.shared.scripting.FxScriptInfo;
import com.flexive.shared.structure.FxEnvironment;
import com.flexive.shared.structure.FxSelectList;
import com.flexive.shared.structure.FxSelectListItem;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.el.ValueExpression;
import javax.faces.component.*;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.model.SelectItem;
import javax.faces.model.SelectItemGroup;
import javax.faces.render.Renderer;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * A special renderer for SelectOneListbox and SelectManyListbox based on MenuRenderer from the JSF RI 1.2
 * Changed behaviour: if readonly or disabled on the text is rendered and colors are taken from
 * the SelectItem's if their value class extends ObjectWithColor or a style is provided in FxJSFSelectItem's
 *
 * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 * @see com.flexive.shared.ObjectWithColor
 */
public class FxSelectRenderer extends Renderer {

    private static final Log LOG = LogFactory.getLog(FxSelectRenderer.class);

    /**
     * constant if no value is assigned
     */
    public static final Object NO_VALUE = "";

    /**
     * Scripting support (debugging)
     */
    private static final boolean ALLOW_SCRIPTING = false;
    private static final String readOnlyScript = "renderSelectReadOnly.groovy";
    private static final String editScript = "renderSelectEdit.groovy";

    /**
     * {@inheritDoc}
     */
    @Override
    public String convertClientId(FacesContext context, String clientId) {
        return clientId;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean getRendersChildren() {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void decode(FacesContext context, UIComponent component) {
        if (!isEditable(component)) {
            //dont decode a readonly or disabled listbox
            return;
        }
        String clientId = component.getClientId(context);
        if (component instanceof UISelectMany) {
            Map<String, String[]> requestParameterValuesMap = context.getExternalContext()
                    .getRequestParameterValuesMap();
            if (requestParameterValuesMap.containsKey(clientId)) {
                String newValues[] = requestParameterValuesMap.get(clientId);
                ((UIInput) component).setSubmittedValue(newValues);
            } else {
                // Use the empty array, not null, to distinguish
                // between an deselected UISelectMany and a disabled one
                ((UIInput) component).setSubmittedValue(new String[0]);
            }
        } else {
            Map<String, String> requestParameterMap = context.getExternalContext().getRequestParameterMap();
            if (requestParameterMap.containsKey(clientId)) {
                String newValue = requestParameterMap.get(clientId);
                ((UIInput) component).setSubmittedValue(newValue);
            } else {
                // there is no value, but this is different from a null value.
                ((UIInput) component).setSubmittedValue(NO_VALUE);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
        //do nothing
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
        if (isEditable(component)) {
            if (ALLOW_SCRIPTING) {
                if (CacheAdmin.getEnvironment().scriptExists(editScript)) {
                    try {
                        LOG.info("Rendering using script " + editScript);
                        FxScriptInfo scriptInfo = CacheAdmin.getEnvironment().getScript(editScript);
                        GroovyShell shell = new GroovyShell();
                        Script script = shell
                                .parse(EJBLookup.getScriptingEngine().loadScriptCode(scriptInfo.getId()));
                        script.setProperty("context", context);
                        script.setProperty("component", component);
                        script.run();
                        LOG.info("Finished " + editScript);
                    } catch (Exception e) {
                        LOG.error("Error executing render script " + editScript + ": " + e.getMessage(), e);
                        renderSelect(context, component);
                    }
                    return;
                }
            }
            renderSelect(context, component);
        } else {
            if (ALLOW_SCRIPTING) {
                if (CacheAdmin.getEnvironment().scriptExists(readOnlyScript)) {
                    try {
                        LOG.info("Rendering using script " + readOnlyScript);
                        FxScriptInfo scriptInfo = CacheAdmin.getEnvironment().getScript(readOnlyScript);
                        GroovyShell shell = new GroovyShell();
                        Script script = shell
                                .parse(EJBLookup.getScriptingEngine().loadScriptCode(scriptInfo.getId()));
                        script.setProperty("context", context);
                        script.setProperty("component", component);
                        script.run();
                        LOG.info("Finished " + readOnlyScript);
                    } catch (Exception e) {
                        LOG.error("Error executing render script " + readOnlyScript + ": " + e.getMessage(), e);
                        renderText(context, component);
                    }
                    return;
                }
            }
            renderText(context, component);
        }
    }

    /**
     * Convert a SelectOne value to the correct value class
     *
     * @param context     faces context
     * @param uiSelectOne the select one component
     * @param newValue    the value to convert
     * @return converted value
     * @throws ConverterException on errors
     */
    public Object convertSelectOneValue(FacesContext context, UISelectOne uiSelectOne, String newValue)
            throws ConverterException {
        if (NO_VALUE.equals(newValue) || newValue == null)
            return null;
        return FxJsfComponentUtils.getConvertedValue(context, uiSelectOne, newValue);
    }

    /**
     * Convert SelectManys value to the correct value classes
     *
     * @param context      faces context
     * @param uiSelectMany the select many component
     * @param newValues    the new values to convert
     * @return converted values
     * @throws ConverterException on errors
     */
    public Object convertSelectManyValue(FacesContext context, UISelectMany uiSelectMany, String[] newValues)
            throws ConverterException {
        // if we have no local value, try to get the valueExpression.
        ValueExpression valueExpression = uiSelectMany.getValueExpression("value");

        Object result = newValues; // default case, set local value
        boolean throwException = false;

        // If we have a ValueExpression
        if (null != valueExpression) {
            Class modelType = valueExpression.getType(context.getELContext());
            // Does the valueExpression resolve properly to something with
            // a type?
            if (modelType != null)
                result = convertSelectManyValuesForModel(context, uiSelectMany, modelType, newValues);
            // If it could not be converted, as a fall back try the type of
            // the valueExpression's current value covering some edge cases such
            // as where the current value came from a Map.
            if (result == null) {
                Object value = valueExpression.getValue(context.getELContext());
                if (value != null)
                    result = convertSelectManyValuesForModel(context, uiSelectMany, value.getClass(), newValues);
            }
            if (result == null)
                throwException = true;
        } else {
            // No ValueExpression, just use Object array.
            result = convertSelectManyValues(context, uiSelectMany, Object[].class, newValues);
        }
        if (throwException) {
            StringBuffer values = new StringBuffer();
            if (null != newValues) {
                for (int i = 0; i < newValues.length; i++) {
                    if (i == 0)
                        values.append(newValues[i]);
                    else
                        values.append(' ').append(newValues[i]);
                }
            }
            throw new ConverterException("Error converting expression [" + valueExpression.getExpressionString()
                    + "] of " + String.valueOf(values));
        }
        return result;
    }

    /*
    * Converts the provided string array and places them into the correct provided model type.
    */
    protected Object convertSelectManyValuesForModel(FacesContext context, UISelectMany uiSelectMany,
            Class modelType, String[] newValues) {
        Object result = null;
        if (modelType.isArray())
            result = convertSelectManyValues(context, uiSelectMany, modelType, newValues);
        else if (List.class.isAssignableFrom(modelType)) {
            Object[] values = (Object[]) convertSelectManyValues(context, uiSelectMany, Object[].class, newValues);
            // perform a manual copy as the Array returned from
            // Arrays.asList() isn't mutable.  It seems a waste
            // to also call Collections.addAll(Arrays.asList())
            List<Object> l = new ArrayList<Object>(values.length);
            //noinspection ManualArrayToCollectionCopy
            for (Object v : values)
                l.add(v);
            result = l;
        }
        return result;
    }

    /**
     * Convert select many values to given array class
     *
     * @param context      faces context
     * @param uiSelectMany select many component
     * @param arrayClass   the array class
     * @param newValues    new values to convert
     * @return converted values
     * @throws ConverterException on errors
     */
    protected Object convertSelectManyValues(FacesContext context, UISelectMany uiSelectMany, Class arrayClass,
            String[] newValues) throws ConverterException {

        Object result;
        Converter converter;
        int len = (null != newValues ? newValues.length : 0);

        Class elementType = arrayClass.getComponentType();

        // Optimization: If the elementType is String, we don't need
        // conversion.  Just return newValues.
        if (elementType.equals(String.class))
            return newValues;

        try {
            result = Array.newInstance(elementType, len);
        } catch (Exception e) {
            throw new ConverterException(e);
        }

        // bail out now if we have no new values, returning our
        // oh-so-useful zero-length array.
        if (null == newValues)
            return result;

        // obtain a converter.

        // attached converter takes priority
        if (null == (converter = uiSelectMany.getConverter())) {
            // Otherwise, look for a by-type converter
            if (null == (converter = FxJsfComponentUtils.getConverterForClass(elementType, context))) {
                // if that fails, and the attached values are of Object type,
                // we don't need conversion.
                if (elementType.equals(Object.class))
                    return newValues;
                StringBuffer valueStr = new StringBuffer();
                for (int i = 0; i < len; i++) {
                    if (i == 0)
                        valueStr.append(newValues[i]);
                    else
                        valueStr.append(' ').append(newValues[i]);
                }
                throw new ConverterException("Could not get a converter for " + String.valueOf(valueStr));
            }
        }

        if (elementType.isPrimitive()) {
            for (int i = 0; i < len; i++) {
                if (elementType.equals(Boolean.TYPE)) {
                    Array.setBoolean(result, i,
                            ((Boolean) converter.getAsObject(context, uiSelectMany, newValues[i])));
                } else if (elementType.equals(Byte.TYPE)) {
                    Array.setByte(result, i, ((Byte) converter.getAsObject(context, uiSelectMany, newValues[i])));
                } else if (elementType.equals(Double.TYPE)) {
                    Array.setDouble(result, i,
                            ((Double) converter.getAsObject(context, uiSelectMany, newValues[i])));
                } else if (elementType.equals(Float.TYPE)) {
                    Array.setFloat(result, i, ((Float) converter.getAsObject(context, uiSelectMany, newValues[i])));
                } else if (elementType.equals(Integer.TYPE)) {
                    Array.setInt(result, i, ((Integer) converter.getAsObject(context, uiSelectMany, newValues[i])));
                } else if (elementType.equals(Character.TYPE)) {
                    Array.setChar(result, i,
                            ((Character) converter.getAsObject(context, uiSelectMany, newValues[i])));
                } else if (elementType.equals(Short.TYPE)) {
                    Array.setShort(result, i, ((Short) converter.getAsObject(context, uiSelectMany, newValues[i])));
                } else if (elementType.equals(Long.TYPE)) {
                    Array.setLong(result, i, ((Long) converter.getAsObject(context, uiSelectMany, newValues[i])));
                }
            }
        } else {
            for (int i = 0; i < len; i++)
                Array.set(result, i, converter.getAsObject(context, uiSelectMany, newValues[i]));
        }
        return result;
    }

    /**
     * Should the component be rendered as editable?
     * Checks the disabled and reaonly attributes.
     *
     * @param component the component to check
     * @return editable
     */
    protected boolean isEditable(UIComponent component) {
        return !(Boolean.valueOf(String.valueOf(component.getAttributes().get("disabled")))
                || Boolean.valueOf(String.valueOf(component.getAttributes().get("readonly"))));
    }

    /**
     * Render the component as static text (when readonly or disabled)
     *
     * @param context   faces context
     * @param component the component
     * @throws IOException on errors
     */
    protected void renderText(FacesContext context, UIComponent component) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        Object[] items = FxJsfComponentUtils.getCurrentSelectedValues(component);
        List<SelectItem> selectedItems = FxJsfComponentUtils.getSelectItems(context, component);
        for (Object item : items) {
            writer.startElement("div", component);
            for (SelectItem currItem : selectedItems) {
                if (currItem.getValue().equals(item)) {
                    item = currItem;
                    break;
                } else if (currItem instanceof FxJSFSelectItem && item instanceof Long
                        && currItem.getValue() instanceof SelectableObject
                        && ((SelectableObject) currItem.getValue()).getId() == (Long) item) {
                    item = currItem;
                    break;
                }
            }

            String color = null;
            if (item instanceof FxJSFSelectItem) {
                writer.writeAttribute("style", ((FxJSFSelectItem) item).getStyle(), "style");
            } else if (item instanceof ObjectWithColor) {
                ObjectWithColor oc = (ObjectWithColor) item;
                if (!StringUtils.isEmpty(oc.getColor()))
                    color = oc.getColor();
            } else if (item instanceof SelectItem) {
                if (((SelectItem) item).getValue() instanceof ObjectWithColor) {
                    ObjectWithColor oc = (ObjectWithColor) ((SelectItem) item).getValue();
                    if (!StringUtils.isEmpty(oc.getColor()))
                        color = oc.getColor();
                }
            }
            if (color != null) {
                //if no styleClass is explicitly set, apply contrast background color if too light
                if (null == component.getAttributes().get("styleClass") && FxFormatUtils.lackOfContrast(color))
                    color += ";background-color:" + FxFormatUtils.CONTRAST_BACKGROUND_COLOR;
                writer.writeAttribute("style", "color:" + color, "style");
            }

            if (item instanceof SelectableObjectWithLabel) {
                if (item instanceof FxJSFSelectItem && ((FxJSFSelectItem) item).isFxSelectListItem()) {
                    //we have an FxSelectListItem -> check if cascaded
                    final FxEnvironment env = CacheAdmin.getEnvironment();
                    final Long itemId = (Long) ((FxJSFSelectItem) item).getValue();
                    FxSelectList list = env.getSelectListItem(itemId).getList();

                    writer.write(list.getItem(itemId).getLabelBreadcrumbPath());
                } else
                    writer.write(((SelectableObjectWithLabel) item).getLabel().getBestTranslation());

            } else if (item instanceof SelectableObjectWithName)
                writer.write(((SelectableObjectWithName) item).getName());
            else if (item instanceof SelectItem)
                writer.write(((SelectItem) item).getLabel());
            else
                writer.write(String.valueOf(item)); //last exit ...
            writer.endElement("div");
        }

    }

    /**
     * Render the component as select list box
     *
     * @param context   faces context
     * @param component the component
     * @throws IOException on errors
     */
    protected void renderSelect(FacesContext context, UIComponent component) throws IOException {
        List<SelectItem> items = FxJsfComponentUtils.getSelectItems(context, component);

        boolean restrictToGroups = false;
        if ((component instanceof UISelectOne || component instanceof UISelectMany) && items.size() > 0
                && items.get(0) instanceof FxJSFSelectItem
                && ((FxJSFSelectItem) items.get(0)).isFxSelectListItem()) {
            //we have an FxSelectListItem in a SelectOne -> check if cascaded
            final long itemId = (Long) items.get(0).getValue();
            if (itemId >= 0) {
                FxSelectList list = CacheAdmin.getEnvironment().getSelectListItem(itemId).getList();
                restrictToGroups = list.isOnlySameLevelSelect();
                if (list.isCascaded()) {
                    List<SelectItem> converted = new ArrayList<SelectItem>(items.size());
                    for (SelectItem check : items) {
                        final Long currentItemId = (Long) check.getValue();
                        if (currentItemId < 0) {
                            converted.add(check);
                            continue;
                        }
                        final FxSelectListItem listItem = list.getItem(currentItemId);
                        final boolean forceDisplay = ((FxJSFSelectItem) check).isForceDisplay();
                        if (!listItem.getHasChildren() || forceDisplay) {
                            if (!forceDisplay)
                                check.setLabel(listItem.getLabelBreadcrumbPath());
                            else
                                restrictToGroups = false; //if forceDisplay is enabled, do not restrict to group!
                            check.setDescription(
                                    listItem.hasParentItem() ? String.valueOf(listItem.getParentItem().getId())
                                            : "0");
                            converted.add(check);
                        }
                    }
                    items = converted;
                }
            }
        }

        ResponseWriter writer = context.getResponseWriter();
        Map attrMap = component.getAttributes();

        if (component instanceof UISelectMany && restrictToGroups) {
            /*
            full js code:
                var grpData_xx = [["1", 1],["2",1],["3",2],["4",2],["5",3],["6",1]];
                
                function processChange() {
                    var s = document.getElementById('xx');
                    function gg(v) {
                        for(var x in grpData_xx)
            if(grpData_xx[x][0]==v)
                return grpData_xx[x][1];
                        return 0;
                    }
                    var g = -1;
                    for(var o in s.childNodes) {
                        if(!s.childNodes[o])
            continue;
                        if(s.childNodes[o].selected) {
            g = gg(s.childNodes[o].value);
            break;
                        }
                    }
                    if (g == -1)
                        return;
                    for(var c in s.childNodes) {
                        if(!s.childNodes[c])
            continue;
                        if(gg(s.childNodes[c].value)!=g && s.childNodes[c].selected!=undefined)
            s.childNodes[c].selected = false;
                        }
                    }
                }
            */
            StringBuilder sb = new StringBuilder(750);
            sb.append("var msGrpData_").append(component.getId()).append("=[");
            if (items.size() > 0) {
                for (SelectItem check : items) {
                    String grp = check.getDescription() == null ? "0" : check.getDescription();
                    sb.append("[\"").append(check.getValue()).append("\",").append(Long.valueOf(grp)).append("],");
                }
                sb.deleteCharAt(sb.length() - 1);
            }
            sb.append("];");
            sb.append("function pc_").append(component.getId()).append("(){");
            sb.append("var s=document.getElementById('").append(component.getClientId(context));
            sb.append("');function gg(v){for(var x in msGrpData_").append(component.getId())
                    .append(")if(msGrpData_");
            sb.append(component.getId()).append("[x][0]==v)return msGrpData_").append(component.getId())
                    .append("[x][1];return 0;}");
            sb.append(
                    "var g=-1;for(var o in s.childNodes){if(!s.childNodes[o])continue;if(s.childNodes[o].selected){g=gg(s.childNodes[o].value);break;}}");
            sb.append(
                    "if(g==-1)return;for(var c in s.childNodes){if(!s.childNodes[c])continue;if(gg(s.childNodes[c].value)!=g&&s.childNodes[c].selected!=undefined){s.childNodes[c].selected=false;}}");
            if (!StringUtils.isEmpty(String.valueOf(attrMap.get("onchange")))) {
                //execute the original onchange event
                sb.append(String.valueOf(attrMap.get("onchange"))).append(";");
            }
            sb.append("}\n");
            FxJavascriptUtils.beginJavascript(writer);
            writer.write(sb.toString());
            FxJavascriptUtils.endJavascript(writer);
        }

        writer.startElement("select", component);
        if (restrictToGroups)
            writer.writeAttribute("onchange", "pc_" + component.getId() + "();", null);
        String id;
        if (null != (id = component.getId()) && !id.startsWith(UIViewRoot.UNIQUE_ID_PREFIX))
            //noinspection UnusedAssignment
            writer.writeAttribute("id", id = component.getClientId(context), "id");

        writer.writeAttribute("name", component.getClientId(context), "clientId");
        // render styleClass attribute if present.
        String styleClass;
        if (null != (styleClass = (String) component.getAttributes().get("styleClass"))) {
            writer.writeAttribute("class", styleClass, "styleClass");
        }

        if (component instanceof UISelectMany)
            writer.writeAttribute("multiple", true, "multiple");

        // If "size" is *not* set explicitly, we have to default it correctly
        Integer size = (Integer) component.getAttributes().get("size");
        if (size == null || size == Integer.MIN_VALUE) {
            // Determine how many option(s) we need to render, and update
            // the component's "size" attribute accordingly;  The "size"
            // attribute will be rendered as one of the "pass thru" attributes
            int itemCount;
            if ("javax.faces.Listbox".equals(component.getRendererType()))
                itemCount = 1; //only 1 item if we are a listbox
            else
                itemCount = FxJsfComponentUtils.getSelectlistOptionCount(items);
            size = itemCount;
        }
        writer.writeAttribute("size", size, "size");

        //render the components default attributes if present
        for (String att : FxJsfComponentUtils.SELECTLIST_ATTRIBUTES) {
            if (restrictToGroups && "onchange".equals(att))
                continue;
            if (attrMap.get(att) != null)
                writer.writeAttribute(att, attrMap.get(att), att);
        }

        //render each option
        renderOptions(context, component, items);
        writer.endElement("select");
    }

    /**
     * Render all present options
     *
     * @param context   faces context
     * @param component the component
     * @param items     all items to render
     * @throws IOException on errors
     */
    protected void renderOptions(FacesContext context, UIComponent component, List<SelectItem> items)
            throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        assert (writer != null);

        Converter converter = null;
        if (component instanceof ValueHolder)
            converter = ((ValueHolder) component).getConverter();

        if (!items.isEmpty()) {
            Object currentSelections = FxJsfComponentUtils.getCurrentSelectedValues(component);
            Object[] submittedValues = FxJsfComponentUtils.getSubmittedSelectedValues(component);
            for (SelectItem item : items) {
                if (item instanceof SelectItemGroup) {
                    // render OPTGROUP
                    writer.startElement("optgroup", component);
                    writer.writeAttribute("label", item.getLabel(), "label");

                    // if the component is disabled, "disabled" attribute would be rendered
                    // on "select" tag, so don't render "disabled" on every option.
                    boolean componentDisabled = Boolean.TRUE.equals(component.getAttributes().get("disabled"));
                    if ((!componentDisabled) && item.isDisabled())
                        writer.writeAttribute("disabled", true, "disabled");

                    // render options of this group.
                    SelectItem[] itemsArray = ((SelectItemGroup) item).getSelectItems();
                    for (SelectItem currentOption : itemsArray)
                        renderOption(context, component, converter, currentOption, currentSelections,
                                submittedValues);
                    writer.endElement("optgroup");
                } else
                    renderOption(context, component, converter, item, currentSelections, submittedValues);
            }
        }
    }

    /**
     * Render a single option
     *
     * @param context           faces context
     * @param component         the current component
     * @param converter         the converter
     * @param curItem           the item to render
     * @param currentSelections the current selections
     * @param submittedValues   all submitted values
     * @throws IOException on errors
     */
    protected void renderOption(FacesContext context, UIComponent component, Converter converter,
            SelectItem curItem, Object currentSelections, Object[] submittedValues) throws IOException {
        ResponseWriter writer = context.getResponseWriter();

        writer.writeText("\t", component, null);
        writer.startElement("option", component);

        String valueString = getFormattedValue(context, component, curItem.getValue(), converter);
        writer.writeAttribute("value", valueString, "value");
        if (curItem instanceof FxJSFSelectItem) {
            //apply the style attribute if it is available
            writer.writeAttribute("style", ((FxJSFSelectItem) curItem).getStyle(), null);
        } else if (curItem.getValue() instanceof ObjectWithColor) {
            //apply the color to the style attribute
            String color = ((ObjectWithColor) (curItem.getValue())).getColor();
            if (!StringUtils.isEmpty(color))
                writer.writeAttribute("style", "color:" + color, null);
        }

        Object valuesArray;
        Object itemValue;
        boolean containsValue;
        if (submittedValues != null) {
            containsValue = FxJsfComponentUtils.containsaValue(submittedValues);
            if (containsValue) {
                valuesArray = submittedValues;
                itemValue = valueString;
            } else {
                valuesArray = currentSelections;
                itemValue = curItem.getValue();
            }
        } else {
            valuesArray = currentSelections;
            itemValue = curItem.getValue();
        }

        if (FxJsfComponentUtils.isSelectItemSelected(context, component, itemValue, valuesArray, converter)) {
            writer.writeAttribute("selected", true, "selected");
        }

        Boolean disabledAttr = (Boolean) component.getAttributes().get("disabled");
        boolean componentDisabled = disabledAttr != null && disabledAttr.equals(Boolean.TRUE);

        // if the component is disabled, "disabled" attribute would be rendered
        // on "select" tag, so don't render "disabled" on every option.
        if ((!componentDisabled) && curItem.isDisabled())
            writer.writeAttribute("disabled", true, "disabled");

        String labelClass;
        if (componentDisabled || curItem.isDisabled())
            labelClass = (String) component.getAttributes().get("disabledClass");
        else
            labelClass = (String) component.getAttributes().get("enabledClass");

        if (labelClass != null)
            writer.writeAttribute("class", labelClass, "labelClass");

        if (curItem.isEscape()) {
            String label = curItem.getLabel();
            if (label == null)
                label = valueString;
            writer.writeText(label, component, "label");
        } else
            writer.write(curItem.getLabel());

        writer.endElement("option");
        writer.writeText("\n", component, null);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object getConvertedValue(FacesContext context, UIComponent component, Object submittedValue)
            throws ConverterException {
        if (component instanceof UISelectMany)
            return convertSelectManyValue(context, ((UISelectMany) component), (String[]) submittedValue);
        else
            return convertSelectOneValue(context, ((UISelectOne) component), (String) submittedValue);
    }

    /**
     * Overloads getFormattedValue to take a advantage of a previously
     * obtained converter.
     *
     * @param context      the FacesContext for the current request
     * @param component    UIComponent of interest
     * @param currentValue the current value of <code>component</code>
     * @param converter    the component's converter
     * @return the currentValue after any associated Converter has been
     *         applied
     * @throws ConverterException if the value cannot be converted
     */
    protected String getFormattedValue(FacesContext context, UIComponent component, Object currentValue,
            Converter converter) throws ConverterException {

        // formatting is supported only for components that support
        // converting value attributes.
        if (!(component instanceof ValueHolder)) {
            if (currentValue != null)
                return currentValue.toString();
            return null;
        }

        if (converter == null) {
            // If there is a converter attribute, use it to to ask application
            // instance for a converter with this identifer.
            converter = ((ValueHolder) component).getConverter();
        }

        if (converter == null) {
            // if value is null and no converter attribute is specified, then
            // return a zero length String.
            if (currentValue == null) {
                return "";
            }
            // Do not look for "by-type" converters for Strings
            if (currentValue instanceof String) {
                return (String) currentValue;
            }

            // if converter attribute set, try to acquire a converter
            // using its class type.
            try {
                converter = FacesContext.getCurrentInstance().getApplication()
                        .createConverter(currentValue.getClass());
            } catch (Exception e) {
                converter = null;
            }

            // if there is no default converter available for this identifier,
            // assume the model type to be String.
            if (converter == null) {
                return currentValue.toString();
            }
        }
        return converter.getAsString(context, component, currentValue);
    }
}