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:
- Change component class
- Change renderer class
- Register the custom classes in faces config
- 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.