Разрыв радиокнопки в колонках в JSF

Когда я использую 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>

Но нет разницы в рендеринге. Его не разбивают на столбцы. Что я делаю неправильно?

Ответ 1

Я адаптировал код, данный Дамо, для работы с h: selectOneRadio вместо h: selectManycheckbox. Чтобы получить его работу, вам необходимо зарегистрировать его в ваших лицах-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 на вашем сервере приложений).

Код выводит радиоблок в divs вместо таблицы. Затем вы можете использовать CSS для их стилизации, как бы вы ни хотели. Я хотел бы предложить установить фиксированную ширину в checkboxDiv и внутренние divs, а затем включить внутренние divs как встроенные блоки:

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;

    }


}

Ответ 2

h: panelGrid содержит только одного дочернего элемента (h: selectOneRadio), поэтому он будет отображать только один столбец. H: selectOneRadio также отображает таблицу HTML. Его рендеринг предлагает только два макета (lineDirection и pageDirection).

У вас есть несколько вариантов

  • использовать JavaScript для изменения таблицы после загрузки страницы
  • найдите сторонний контроль, который реализует нужные функции.
  • напишите свой собственный элемент управления selectOneRadio