7
votes

I am developping a JSF custom component performing "responsive" multi-columns display.

It uses the "CSS Multi-column Layout Module" ( http://www.w3.org/TR/css3-multicol/ ) (tutorial in French : http://www.alsacreations.com/tuto/lire/1557-les-multicolonnes-en-css3.html ).

I actually have the following solution, which works :

Component class :

@Log4j
@FacesComponent(ResponsiveMultiColumnsTable.SENAT_COMPONENT_TYPE)
@ResourceDependency(library="senat",name="senat-columns.css")
public class ResponsiveMultiColumnsTable extends UIData {
    public static final String SENAT_COMPONENT_FAMILY = "fr.senat.faces.components";
    public static final String SENAT_COMPONENT_TYPE = SENAT_COMPONENT_FAMILY + ".ResponsiveMultiColumnsTable";
    public static final String DEFAULT_RENDERER = SENAT_COMPONENT_FAMILY + ".ResponsiveMultiColumnsPanelRenderer";

    private enum PropertyKeys { style, styleClass }

    public ResponsiveMultiColumnsTable() {
      setRendererType(DEFAULT_RENDERER);
    }

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

    public String getStyle() {
        return (String) getStateHelper().eval(PropertyKeys.style);
    }

    public void setStyle(String param) {
        getStateHelper().put(PropertyKeys.style, param);
    }

    public String getStyleClass() {
        return (String) getStateHelper().eval(PropertyKeys.styleClass);
    }

    public void setStyleClass(String param) {
        getStateHelper().put(PropertyKeys.styleClass, param);
    }

}

Renderer class :

@FacesRenderer(componentFamily = ResponsiveMultiColumnsTable.COMPONENT_FAMILY, rendererType = ResponsiveMultiColumnsTable.DEFAULT_RENDERER)
public class ResponsiveMultiColumnsTableRenderer extends CoreRenderer {

    @Override
    public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
        ResponsiveMultiColumnsTable detail = (ResponsiveMultiColumnsTable) component;
        ResponseWriter writer = context.getResponseWriter();
        String clientId = detail.getClientId(context);
        String style = detail.getStyle();
        String styleClass = detail.getStyleClass();

        writer.startElement("div", detail);
        writer.writeAttribute("id", clientId, "id");
        String extStyleClass = "senat-details-columns";
        if (styleClass != null) {
            extStyleClass += " " + styleClass;
        }
        writer.writeAttribute("class", "senat-details-columns " + extStyleClass, "styleClass");
        if ((style != null) && !style.isEmpty()) {
            writer.writeAttribute("style", style, "style");
        }
    }

    @Override
    public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        writer.endElement("div");
    }

    @Override
    public void encodeChildren(FacesContext context, UIComponent component) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        ResponsiveMultiColumnsTable grid = (ResponsiveMultiColumnsTable) component;

        int rowIndex = grid.getFirst();
        int rows = grid.getRows();
        int itemsToRender = rows != 0 ? rows : grid.getRowCount();

        for(int i = rowIndex ; i < itemsToRender ; i++) {
            grid.setRowIndex(i);
            if(!grid.isRowAvailable()) {
                break;
            }

            writer.startElement("div", null);

            renderChildren(context, grid);

            writer.endElement("div");
        }

    }
}

Component declaration in taglib :

    <tag>
    <description>
        <![CDATA[
                    *** À compléter ***
        ]]>
    </description>
    <tag-name>responsiveMultiColumnsTable</tag-name>
            <component>
                <component-type>fr.senat.faces.components.ResponsiveMultiColumnsTable</component-type>
            </component>

    <attribute>
                <description>Liste à afficher
                </description>
                <name>value</name>
                <required>true</required>
                <type>java.util.List</type>
    </attribute>
            <attribute>
                <description>Nom de la variable d'itération</description>
                <name>var</name>
                <required>true</required>
                <type>java.lang.String</type>
    </attribute>
    <attribute>
                <description>Élément de style à ajouter</description>
                <name>style</name>
                <required>false</required>
                <type>java.lang.String</type>
    </attribute>
    <attribute>
                <description>Classe à ajouter</description>
                <name>styleClass</name>
                <required>false</required>
                <type>java.lang.String</type>
    </attribute>
</tag>

Stylesheet :

.senat-details-columns {
    -webkit-columns: 3;
    -moz-columns: 3;
    columns: 3;
}

@media (max-width: 1024px) {
    .senat-details-columns {
        -webkit-columns: 2;
        -moz-columns: 2;
        columns: 2;
    }
}

@media (max-width: 640px) {
    .senat-details-columns {
        -webkit-columns: 1;
        -moz-columns: 1;
        columns: 1;
    }
}

I would like to be able to indicate data such as which column count to use at which resolution. One way to do that would be to dynamically generate a stylesheet fragment then include it.

The base dynamic stylesheet would look like :

.#{dynamic-style} {
    -webkit-columns: #{max-col-count};
    -moz-columns: #{max-col-count};
    columns: #{max-col-count};
}

@media (max-width: #{max-middle-res}px) {
    .#{dynamic-style} {
        -webkit-columns: #{middle-col-count};
        -moz-columns: #{middle-col-count};
        columns: #{middle-col-count};
    }
}

@media (max-width: #{max-min-res}px) {
    .#{dynamic-style} {
        -webkit-columns: #{min-col-count};
        -moz-columns: #{middle-col-count};
        columns: #{middle-col-count};
    }
}

How can I dynamically process it and include it ?

The problem I see is that I can have multiple uses of the same stylesheet with different parameters. If there was only one use, just including EL expressions in the stylesheet could do the job.

Another solution would be to append style just before the component, but it seems a bit ugly to me.

1
JSF is a tool which generates client code dynamically at server side. Considering that resolution is client specific, it's very difficult to play with it at server side. I think the best you could achieve is to differ between the client device itself (consider some framework as Spring Mobile for that).Xtreme Biker
@XtremeBiker or to use a front-end framework that already ease this work like BootstrapLuiggi Mendoza
Are those variables supposed to be request-based or session-wide or application-wide? Where/how exactly did you intend to prepare/set those EL variables?BalusC
@BalusC I would to pass them as parameters of my custom component. Something like <sc:responsiveMultiColumnsTable max-min-res="640"... >. And I would like to use the same components with different parameters on the same page.Ludovic Pénet
@XtremeBiker & Luiggi the goal is to have the benefits of compiled java code. I can of course obtain the expected result with pure HTML/CSS3, but it seems less maintenable to me.Ludovic Pénet

1 Answers

-1
votes

You can simply put EL expressions into .ccs files and JSF will process them just as it does with .xhtml files.

You will need to include them with <h:outputStylesheet or use /javax.faces.resource/foo.css?ln=bar directly from HTML.