将单选按钮分解为JSF中的列

时间:2009-07-19 19:50:18

标签: jsf radio-button alignment

当我使用h:selectOneRadio并提供列表中的值列表时      整个单选按钮部分作为单个完整列表公开。我需要将它安排在3列中。我试过给

<h:panelGrid id="radioGrid" columns="3">
<h:selectOneRadio id="radio1" value="#{bean.var}">
<f:selectItems id="rval" value="#{bean.list}"/>
</h:selectOneRadio>
</h:panelGrid>

但渲染部分没有区别。它没有分成几列。我做错了什么?

3 个答案:

答案 0 :(得分:3)

我已经调整了Damo提供的代码,使用h:selectOneRadio代替h:selectManycheckbox。要使其正常工作,您需要在faces-config.xml中注册它,并使用:

<render-kit>
    <renderer>
        <component-family>javax.faces.SelectOne</component-family>
        <renderer-type>javax.faces.Radio</renderer-type>
        <renderer-class>test.components.SelectOneRadiobuttonListRenderer</renderer-class>
    </renderer>
</render-kit>

要编译它,您还需要JSF实现(通常在您的应用服务器中的某种jsf-impl.jar中找到)。

代码输出div中的radiobuttons,而不是表格。然后,您可以根据需要使用CSS来设置它们的样式。我建议给checkboxDiv和内部div赋予固定宽度,然后将内部div显示为内联块:

div.radioButtonDiv{
    width: 300px;
}

div.radioButtonDiv div{
    display: inline-block;
    width: 100px;
}

哪个应该提供您要查找的3列

代码:

package test.components;

import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;

import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UISelectMany;
import javax.faces.component.UISelectOne;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.model.SelectItem;

import com.sun.faces.renderkit.RenderKitUtils;
import com.sun.faces.renderkit.html_basic.MenuRenderer;
import com.sun.faces.util.MessageUtils;
import com.sun.faces.util.Util;

/**
 * This component ensures that h:selectOneRadio doesn't get rendered using
 * tables.  It is adapted from the code at:
 * http://www.blog.locuslive.com/?p=15
 * 
 * To register it for use, place the following in your faces config:
 * 
 * <render-kit>
 *      <renderer>
 *         <component-family>javax.faces.SelectOne</component-family>
 *         <renderer-type>javax.faces.Radio</renderer-type>
 *         <renderer-class>test.components.SelectOneRadiobuttonListRenderer</renderer-class>
 *      </renderer>
 * </render-kit>
 *  
 * The original comment is below:
 * 
 * ----------------------------------------------------------------------------- *  
 * This is a custom renderer for the h:selectManycheckbox
 * It is intended to bypass the incredibly sucky table based layout used
 * by the standard component.
 *
 * This layout uses an enclosing div with divs for each input.
 * This gives a default layout similar to a vertical layout
 * The layout can then be controlled by css
 *
 * This renderer assigns an class of "checkboxDiv" to the enclosing div
 * The class and styleClass attributes are then applied to the internal
 * divs that house the inputs
 *
 * The following attributes are ignored as they are no longer required when using CSS:
 * - pageDirection
 * - border
 *
 * Note that I am not supporting optionGroups at this stage. They would be relatively
 * easy to implement with another enclosing div
 *
 * @author damianharvey
 *
 */
public class SelectOneRadiobuttonListRenderer extends MenuRenderer {

    public void encodeEnd(FacesContext context, UIComponent component)
            throws IOException {

        if (context == null) {
            throw new NullPointerException(
                    MessageUtils.getExceptionMessageString(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID,
                                                                        "context"));
        }
        if (component == null) {
            throw new NullPointerException(
                    MessageUtils.getExceptionMessageString(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID,
                                                                        "component"));
        }

        // suppress rendering if "rendered" property on the component is
        // false.
        if (!component.isRendered()) {
            return;
        }

        ResponseWriter writer = context.getResponseWriter();
        assert(writer != null);

        writer.startElement("div", component);
        if (shouldWriteIdAttribute(component)) {
            writeIdAttributeIfNecessary(context, writer, component);
        }
        writer.writeAttribute("class", "radioButtonDiv", "class");

        Iterator items = RenderKitUtils.getSelectItems(context, component).iterator();
        SelectItem curItem = null;
        int idx = -1;
        while (items.hasNext()) {
            curItem = (SelectItem) items.next();
            idx++;
            renderOption(context, component, curItem, idx);
        }

        writer.endElement("div");

    }

    protected void renderOption(FacesContext context, UIComponent component, SelectItem curItem, int itemNumber)
            throws IOException {

        ResponseWriter writer = context.getResponseWriter();
        assert(writer != null);

        // disable the check box if the attribute is set.
        String labelClass = null;
        boolean componentDisabled = Util.componentIsDisabled(component);

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

        writer.startElement("div", component);  //Added by DAMIAN

        String styleClass = (String) component.getAttributes().get("styleClass");
        String style = (String) component.getAttributes().get("style");

        if (styleClass != null) {
            writer.writeAttribute("class", styleClass, "class");
        }
        if (style != null) {
            writer.writeAttribute("style", style, "style");
        }

        writer.startElement("input", component);
        writer.writeAttribute("name", component.getClientId(context), "clientId");
        String idString = component.getClientId(context) + NamingContainer.SEPARATOR_CHAR + Integer.toString(itemNumber);
        writer.writeAttribute("id", idString, "id");
        String valueString = getFormattedValue(context, component, curItem.getValue());
        writer.writeAttribute("value", valueString, "value");
        writer.writeAttribute("type", "radio", null);

        Object submittedValues[] = getSubmittedSelectedValues(context, component);
        boolean isSelected;

        Class type = String.class;
        Object valuesArray = null;
        Object itemValue = null;
        if (submittedValues != null) {
            valuesArray = submittedValues;
            itemValue = valueString;
        } else {
            valuesArray = getCurrentSelectedValues(context, component);
            itemValue = curItem.getValue();
        }
        if (valuesArray != null) {
            type = valuesArray.getClass().getComponentType();
        }

        // I don't know what this does, but it doens't compile.  Commenting it
        // out doesn't seem to hurt
        // Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
        // requestMap.put(ConverterPropertyEditorBase.TARGET_COMPONENT_ATTRIBUTE_NAME,
        //      component);

        Object newValue = context.getApplication().getExpressionFactory().
                coerceToType(itemValue, type);

        isSelected = isSelected(newValue, valuesArray);

        if (isSelected) {
            writer.writeAttribute(getSelectedTextString(), Boolean.TRUE, null);
        }

        // Don't render the disabled attribute twice if the 'parent'
        // component is already marked disabled.
        if (!Util.componentIsDisabled(component)) {
            if (curItem.isDisabled()) {
                    writer.writeAttribute("disabled", true, "disabled");
            }
        }

        // Apply HTML 4.x attributes specified on UISelectMany component to all
        // items in the list except styleClass and style which are rendered as
        // attributes of outer most table.
        RenderKitUtils.renderPassThruAttributes(writer, component, new String[] { "border", "style" });
        RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, component);

        writer.endElement("input");
        writer.startElement("label", component);
        writer.writeAttribute("for", idString, "for");
        // if enabledClass or disabledClass attributes are specified, apply
        // it on the label.
        if (labelClass != null) {
            writer.writeAttribute("class", labelClass, "labelClass");
        }
        String itemLabel = curItem.getLabel();
        if (itemLabel != null) {
            writer.writeText(" ", component, null);
            if (!curItem.isEscape()) {
                // It seems the ResponseWriter API should
                // have a writeText() with a boolean property
                // to determine if it content written should
                // be escaped or not.
                writer.write(itemLabel);
            }
            else {
                writer.writeText(itemLabel, component, "label");
            }
        }
        writer.endElement("label");

        writer.endElement("div");   //Added by Damian
    }

    // ------------------------------------------------- Package Private Methods


    String getSelectedTextString() {

        return "checked";

    }

    /** For some odd reason this is a private method in the MenuRenderer superclass
     *
     * @param context
     * @param component
     * @return
     */
    private Object getCurrentSelectedValues(FacesContext context,
            UIComponent component) {

        if (component instanceof UISelectMany) {
            UISelectMany select = (UISelectMany) component;
            Object value = select.getValue();

            if (value instanceof Collection) {

                Collection<?> list = (Collection) value;
                int size = list.size();
                if (size > 0) {
                    // get the type of the first element - Should
                    // we assume that all elements of the List are
                    // the same type?
                    return list.toArray((Object[]) Array.newInstance(list.iterator().next().getClass(), size));
                }
                else {
                    return ((Collection) value).toArray();
                }

            }
            else if (value != null && !value.getClass().isArray()) {
                logger.warning("The UISelectMany value should be an array or a collection type, the actual type is " + value.getClass().getName());
            }

            return value;
        }

        UISelectOne select = (UISelectOne) component;
        Object returnObject;
        if (null != (returnObject = select.getValue())) {
            Object ret = Array.newInstance(returnObject.getClass(), 1);
            Array.set(ret, 0, returnObject);
            return ret;
        }
        return null;

    }

    /** For some odd reason this is a private method in the MenuRenderer superclass
     *
     * @param context
     * @param component
     * @return
     */
    private Object[] getSubmittedSelectedValues(FacesContext context, UIComponent component) {

        if (component instanceof UISelectMany) {
            UISelectMany select = (UISelectMany) component;
            return (Object[]) select.getSubmittedValue();
        }

        UISelectOne select = (UISelectOne) component;
        Object returnObject;
        if (null != (returnObject = select.getSubmittedValue())) {
            return new Object[] { returnObject };
        }
        return null;

    }

    /** For some odd reason this is a private method in the MenuRenderer superclass
     *
     * @param itemValue
     * @param valueArray
     * @return
     */
    private boolean isSelected(Object itemValue, Object valueArray) {

        if (null != valueArray) {
            if (!valueArray.getClass().isArray()) {
                logger.warning("valueArray is not an array, the actual type is " + valueArray.getClass());
                return valueArray.equals(itemValue);
            }
            int len = Array.getLength(valueArray);
            for (int i = 0; i < len; i++) {
                Object value = Array.get(valueArray, i);
                if (value == null) {
                    if (itemValue == null) {
                        return true;
                    }
                }
                else if (value.equals(itemValue)) {
                    return true;
                }
            }
        }
        return false;

    }


}

答案 1 :(得分:0)

h:panelGrid只包含一个子节点(h:selectOneRadio),因此它只会渲染一列。 h:selectOneRadio也呈现HTML表格。它的渲染器只提供两种布局(lineDirection和pageDirection)。

您有几个选择

  • 在页面加载后使用JavaScript修改表格
  • 找到实现所需功能的3rd party控件
  • 编写您自己的selectOneRadio控件

答案 2 :(得分:0)