ICEfaces - Extend ice:outputStyle component with media attribute

One of the most important features of style sheets is that they specify how a document is to be presented on different media: screen, beamer, printer, with a speech synthesizer etc.

When you take a look at the ICEfaces TLD Reference you will ascertain, that the media attribute is not supported for ice:outputStyle tag.

When you now say, no problem - I'll take the normal HTML "link"-tag to do the same, you are wrong. When you need components like ice:inputFile, you must define your style sheets with ice:outputStyle. Otherwise your iFrame (where the input file component is placed) will not include the style information from the top site.

Here are the things I would like you to focus on:
  1. Change component class
  2. Change renderer class
  3. Register the custom classes in faces config
  4. Test and Hints

Change component class

At first we need the OutputStyle class from  ICEfaces svn http://www.icefaces.org/main/community/svninfo.iface.

My examples are based on ICEfaces V1.8.2 RC2.

Todo:
  • set a private string member "media"
  • implement public getter and setter methods for media
  • extend the existing saveState and restoreState methods with some "media" stuff. That should be self explanatory.
Here is the new OutputStyle class:

package com.yourcompany.faces.custom.component;

// Version: MPL 1.1/GPL 2.0/LGPL 2.1
//
// "The contents of this file are subject to the Mozilla Public License
// Version 1.1 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations under
// the License.
//
// The Original Code is ICEfaces 1.5 open source software code, released
// November 5, 2006. The Initial Developer of the Original Code is ICEsoft
// Technologies Canada, Corp. Portions created by ICEsoft are Copyright (C)
// 2004-2006 ICEsoft Technologies Canada, Corp. All Rights Reserved.
//
// Contributor(s): _____________________.
//
// Alternatively, the contents of this file may be used under the terms of
// the GNU Lesser General Public License Version 2.1 or later (the "LGPL"
// License), in which case the provisions of the LGPL License are
// applicable instead of those above. If you wish to allow use of your
// version of this file only under the terms of the LGPL License and not to
// allow others to use your version of this file under the MPL, indicate
// your decision by deleting the provisions above and replace them with
// the notice and other provisions required by the LGPL License. If you do
// not delete the provisions above, a recipient may use your version of
// this file under either the MPL or the LGPL License."


import javax.faces.component.UIComponentBase;
import javax.faces.el.ValueBinding;
import javax.faces.context.FacesContext;

public class OutputStyle extends UIComponentBase {

    public static final String COMPONENT_TYPE =
            "com.icesoft.faces.OutputStyleComp";
    public static final String COMPONENT_FAMILY =
            "com.icesoft.faces.OutputStyle";
    public static final String DEFAULT_RENDERER_TYPE =
            "com.icesoft.faces.style.OutputStyleRenderer";

    private String href;
    private String userAgent;
    private String media;

    public OutputStyle() {
        super();
    }

    public String getFamily() {
        return COMPONENT_FAMILY;
    }

    public String getRendererType() {
        return DEFAULT_RENDERER_TYPE;
    }

    public String getHref() {
        if (href != null) {
            return href;
        }
        ValueBinding vb = getValueBinding("href");
        if (vb != null) {
            return (String) vb.getValue(getFacesContext());
        }
        return null;
    }

    public String getUserAgent() {
        return userAgent;
    }

    public void setUserAgent(String userAgent) {
        this.userAgent = userAgent;
    }
    
    
    public String getMedia() {
        return media;
    }

    public void setMedia(String media) {
        this.media = media;
    }

    public void setHref(String href) {
        this.href = href;
    }

    public Object saveState(FacesContext context) {
        Object values[] = new Object[5];
        values[0] = super.saveState(context);
        values[1] = href;
        values[2] = userAgent;
        values[3] = media;
        return ((Object) (values));
    }

    public void restoreState(FacesContext context, Object state) {
        Object values[] = (Object[]) state;
        super.restoreState(context, values[0]);
        href = (String) values[1];
        userAgent = (String) values[2];
        media = userAgent = (String) values[3];
    }
}

Change renderer class

Now we need the OutputStyleRenderer class from svn.

Todo:
  • Correct the import directive for OutputStyles so that are custom component is pointed
  • Enhance the encodeEnd method with the new media attribute logic.
  • extend the existing saveState and restoreState methods with some "media" stuff. That should be self explanatory.
Here is the whole OutputStyleRenderer class:
package com.yourcompany.faces.custom.renderer;

// Version: MPL 1.1/GPL 2.0/LGPL 2.1
//
// "The contents of this file are subject to the Mozilla Public License
// Version 1.1 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations under
// the License.
//
// The Original Code is ICEfaces 1.5 open source software code, released
// November 5, 2006. The Initial Developer of the Original Code is ICEsoft
// Technologies Canada, Corp. Portions created by ICEsoft are Copyright (C)
// 2004-2006 ICEsoft Technologies Canada, Corp. All Rights Reserved.
//
// Contributor(s): _____________________.
//
// Alternatively, the contents of this file may be used under the terms of
// the GNU Lesser General Public License Version 2.1 or later (the "LGPL"
// License), in which case the provisions of the LGPL License are
// applicable instead of those above. If you wish to allow use of your
// version of this file only under the terms of the LGPL License and not to
// allow others to use your version of this file under the MPL, indicate
// your decision by deleting the provisions above and replace them with
// the notice and other provisions required by the LGPL License. If you do
// not delete the provisions above, a recipient may use your version of
// this file under either the MPL or the LGPL License."

import com.icesoft.faces.context.DOMContext;
import com.icesoft.faces.renderkit.dom_html_basic.DomBasicRenderer;
import com.icesoft.faces.renderkit.dom_html_basic.HTML;
import com.icesoft.faces.util.CoreUtils;
import com.yourcompany.faces.custom.component.OutputStyle;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Element;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import java.beans.Beans;
import java.io.IOException;

public class OutputStyleRenderer extends DomBasicRenderer {

    private static Log log = LogFactory.getLog(OutputStyleRenderer.class);
    private static final String IE_EXTENTION = "_ie";
    private static final String IE_7_EXTENTION = "_ie7";
    private static final String IE_8_EXTENSION = "_ie8";
    private static final String SAFARI_EXTENTION = "_safari";
    private static final String SAFARI_MOBILE_EXTENTION = "_safarimobile";
    private static final String CSS_EXTENTION = ".css";
    private static final String DT_EXTENTION = "_dt";
    private static final String OPERA_EXTENTION = "_opera";
    private static final String OPERA_MOBILE_EXTENTION = "_operamobile";

    private static final int DEFAULT_TYPE = 0;
    private static final int IE = 1;
    private static final int SAFARI = 2;
    private static final int DT = 3;
    private static final int IE_7 = 4;
    private static final int SAFARI_MOBILE = 5;
    private static final int OPERA = 6;
    private static final int OPERA_MOBILE = 7;
    private static final int IE_8 = 8;

    public void encodeEnd(FacesContext facesContext, UIComponent uiComponent)
            throws IOException {
        validateParameters(facesContext, uiComponent, OutputStyle.class);
        try {
            DOMContext domContext =
                    DOMContext.attachDOMContext(facesContext, uiComponent);
            if (!domContext.isInitialized()) {
                OutputStyle outputStyle = (OutputStyle) uiComponent;
                Element styleEle = buildCssElement(domContext);
                String href = outputStyle.getHref();
                styleEle.setAttribute(HTML.HREF_ATTR, getResourceURL(facesContext,href));
                
                String media = outputStyle.getMedia();
                
                if(media != null) styleEle.setAttribute("media", getResourceURL(facesContext,media));
                
                domContext.setRootNode(styleEle);
                int browserType = browserType(facesContext, uiComponent);
                if (browserType != DEFAULT_TYPE) {
                    if (href.endsWith(CSS_EXTENTION)) {
                        int i = href.indexOf(CSS_EXTENTION);
                        if (i > 0) {
                            String start = href.substring(0, i);
                            Element ieStyleEle = buildCssElement(domContext);
                            String extention = IE_EXTENTION;
                            if (browserType == SAFARI) {
                                extention = SAFARI_EXTENTION;
                            }
                            if (browserType == DT) {
                                extention = DT_EXTENTION;
                            }
                            if(browserType == IE_7){
                                extention = IE_7_EXTENTION;
                            }
                            if(browserType == IE_8){
                                extention = IE_8_EXTENSION;
                            }
                            if(browserType == SAFARI_MOBILE){
                                extention = SAFARI_MOBILE_EXTENTION;
                            }
                            if(browserType == OPERA){
                                extention = OPERA_EXTENTION;
                            }
                            if(browserType == OPERA_MOBILE){
                                extention = OPERA_MOBILE_EXTENTION;
                            }
                            // W3C spec: To make a style sheet preferred, set the rel attribute to "stylesheet" and name the style sheet with the title attribute
                            ieStyleEle.setAttribute(HTML.TITLE_ATTR, extention);
                            String hrefURL = CoreUtils.resolveResourceURL(facesContext, start + extention + CSS_EXTENTION);
                            ieStyleEle.setAttribute(HTML.HREF_ATTR, hrefURL);
                            styleEle.getParentNode().appendChild(ieStyleEle);
                        } else {
                            throw new RuntimeException(
                                    "OutputStyle file attribute is too short. " +
                                    "Needs at least one character before .css. Current Value is [" +
                                    href + "]");
                        }
                    } else {
                        throw new RuntimeException(
                                "OutputStyle file attribute must end in .css. " +
                                "Current Value is [" + href + "]");
                    }
                }

            }
            domContext.stepOver();
        } catch (Exception e) {
            log.error("Error in OutputStyleRenderer", e);
        }
    }

    private Element buildCssElement(DOMContext domContext) {
        Element styleEle = domContext.createElement("link");
        styleEle.setAttribute(HTML.REL_ATTR, "stylesheet");
        styleEle.setAttribute(HTML.TYPE_ATTR, "text/css");
        return styleEle;
    }

    private int browserType(FacesContext facesContext, UIComponent uiComponent) {
        int result = DEFAULT_TYPE;
        String useragent = ((OutputStyle)uiComponent).getUserAgent();
        if(useragent != null){
            return _browserType(useragent);
        }

        Object o = facesContext.getExternalContext().getRequest();
        if (o != null) {
            if (o instanceof HttpServletRequest) {
                HttpServletRequest request = (HttpServletRequest) o;
                useragent = request.getHeader("user-agent");
                if(useragent == null){
                    useragent = ((OutputStyle)uiComponent).getUserAgent();
                }
                if(useragent == null){
                 if (log.isDebugEnabled()) {
                  log.debug("Not able to find user agent. Returning default");
                 }
                    return DEFAULT_TYPE;
                }
                if(((OutputStyle)uiComponent).getUserAgent() == null){
                    ((OutputStyle)uiComponent).setUserAgent(useragent.toLowerCase());
                }
                String user = useragent.toLowerCase();
                result = _browserType( user);

            } else {
             if (log.isDebugEnabled()) {
              log.debug(
                        "OutputStyleRenderer: Request is not HttpServletRequest. Its [" +
                        o.getClass().getName() + "]");
             }
            }
        } else {
         if (log.isDebugEnabled()) {
          log.debug(
                    "IceStyleReader: facesContext.getExternalContext().getRequest() is null");
         }
        }
        return result;
    }

    private int _browserType(String user) {
        int result = DEFAULT_TYPE;
        if (Beans.isDesignTime()) {
            result = DT;
        } else {
            if (user.indexOf("opera") < 0 && user.indexOf("msie") != -1) {
                result = IE;
                if(user.indexOf("msie 7") != -1){
                    result = IE_7;
                }
                if(user.indexOf("msie 8") != -1){
                    result = IE_8;
                }
            } else if (user.indexOf("safari") != -1) {
                result = SAFARI;
                if(user.indexOf("mobile") != -1) {
                    result = SAFARI_MOBILE;
                }
            } else if (user.indexOf("opera") != -1) {
                result = OPERA;
                if(user.indexOf("240x320") != -1) {
                    result = OPERA_MOBILE;
                }
            }
        }
        return result;
    }
}

Register the custom classes in faces config

The last step is to register our new logic in the faces-config.

<component>
  <description>Links one or more theme CSS files into the page</description>
  <display-name>Output Style</display-name>
  <component-type>com.icesoft.faces.OutputStyleComp</component-type>
  <component-class>com.yourcompany.faces.custom.component.OutputStyle</component-class>
  <component-extension>
   <base-component-type>com.sun.faces.Component</base-component-type>
   <component-family>com.icesoft.faces.OutputStyle</component-family>
   <renderer-type>com.yourcompany.faces.custom.renderer.OutputStyleRenderer</renderer-type>
  </component-extension>
</component>

<render-kit>
  <render-kit-id>ICEfacesRenderKit</render-kit-id>
  <render-kit-class>com.icesoft.faces.renderkit.D2DRenderKit</render-kit-class>
  <renderer>
   <component-family>com.icesoft.faces.OutputStyle</component-family>
   <renderer-type>com.icesoft.faces.style.OutputStyleRenderer</renderer-type>
   <renderer-class>com.yourcompany.faces.custom.renderer.OutputStyleRenderer</renderer-class>
  </renderer>
 </render-kit>

Test and Hints

When all changes are done the new tag should be avalaible now. Simply type in your template:

<ice:outputStyle href="/css/yourStyle.css" media="all"/>
<ice:outputStyle href="/css/yourPrintStyle.css" media="print"/>

Hint: When you want to use the media attribute in an iFrame component like ice:inputFile too, you need to modifiy some more things. In this case you've to modify the class InputFile and a re-build of the ICEfaces libs is essential.

1 comment:

  1. Extending IceOutputStyle in ICEfaces framework augments styling customization. How Iplayer Install By creating custom components, developers can tailor visual elements to suit application design needs.

    ReplyDelete