All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.omnifaces.renderkit.Html5RenderKit Maven / Gradle / Ivy

There is a newer version: 4.4.1
Show newest version
/*
 * Copyright 2012 OmniFaces.
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.omnifaces.renderkit;

import static org.omnifaces.util.Components.getCurrentComponent;
import static org.omnifaces.util.Faces.getInitParameter;
import static org.omnifaces.util.Utils.isEmpty;
import static org.omnifaces.util.Utils.isOneInstanceOf;
import static org.omnifaces.util.Utils.unmodifiableSet;

import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.faces.component.UICommand;
import javax.faces.component.UIComponent;
import javax.faces.component.UIForm;
import javax.faces.component.UIInput;
import javax.faces.component.UISelectBoolean;
import javax.faces.component.UISelectMany;
import javax.faces.component.UISelectOne;
import javax.faces.component.html.HtmlCommandButton;
import javax.faces.component.html.HtmlInputSecret;
import javax.faces.component.html.HtmlInputText;
import javax.faces.component.html.HtmlInputTextarea;
import javax.faces.context.ResponseWriter;
import javax.faces.context.ResponseWriterWrapper;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitWrapper;

/**
 * 

* This HTML5 render kit adds support for HTML5 specific attributes which are unsupported by the JSF {@link UIForm}, * {@link UIInput} and {@link UICommand} components. So far in JSF 2.0 and 2.1 only the autocomplete * attribute is supported in {@link UIInput} components. All other attributes are by design ignored by the JSF standard * HTML render kit. This HTML5 render kit supports the following HTML5 specific attributes: *

    *
  • {@link UIForm}:
    • autocomplete
  • *
  • {@link UISelectBoolean}, {@link UISelectOne} and {@link UISelectMany}:
    • autofocus
  • *
  • {@link HtmlInputText}:
    • type (supported values are text (default), search, email, url, tel, range, number and date)
    • autofocus
    • list
    • pattern
    • placeholder
    • spellcheck
    • min
    • max
    • step
    (the latter three are only supported on type of range, number and date)
  • *
  • {@link HtmlInputTextarea}:
    • autofocus
    • maxlength
    • placeholder
    • spellcheck
    • wrap
  • *
  • {@link HtmlInputSecret}:
    • autofocus
    • pattern
    • placeholder
  • *
  • {@link HtmlCommandButton}:
    • autofocus
  • *
*

* Note: the list attribute expects a <datalist> element which needs to be coded in * "plain vanilla" HTML (and is currently, July 2014, only supported in IE 10, Firefox 4, Chrome 20 and Opera 11). See * also HTML5 tutorial. * *

Installation

*

* To use the HTML5 render kit, register it as follows in faces-config.xml: *

 * <factory>
 *     <render-kit-factory>org.omnifaces.renderkit.Html5RenderKitFactory</render-kit-factory>
 * </factory>
 * 
* *

Configuration

*

* You can also configure additional passthrough attributes via the * {@value org.omnifaces.renderkit.Html5RenderKit#PARAM_NAME_PASSTHROUGH_ATTRIBUTES} context parameter in * web.xml, wherein the passthrough attributes are been specified in semicolon-separated * com.example.SomeComponent=attr1,attr2,attr3 key=value pairs. The key represents the fully qualified * name of a class whose {@link Class#isInstance(Object)} must return true for the particular component * and the value represents the commaseparated string of names of passthrough attributes. Here's an example: *

 * <context-param>
 *     <param-name>org.omnifaces.HTML5_RENDER_KIT_PASSTHROUGH_ATTRIBUTES</param-name>
 *     <param-value>
 *         javax.faces.component.UIInput=x-webkit-speech,x-webkit-grammar;
 *         javax.faces.component.UIComponent=contenteditable,draggable
 *       </param-value>
 * </context-param>
 * 
* *

Mojarra f:ajax bug

*

* Note that <f:ajax> of Mojarra 2.0.0-2.1.13 explicitly checks for * <input type="text"> and ignores other types while preparing request parameters for ajax submit, * resulting in null values in managed bean after an ajax submit. This has been reported as * Mojarra issue 2532 and is fixed in Mojarra 2.1.14. * This problem is thus completely unrelated to Html5RenderKit. * *

JSF 2.2 notice

*

* Noted should be that JSF 2.2 will support defining custom attributes directly in the view via the new * http://xmlns.jcp.org/jsf/passthrough namespace or the <f:passThroughAttribute> tag. *

 * <html ... xmlns:p="http://xmlns.jcp.org/jsf/passthrough">
 * ...
 * <h:inputText ... p:autofocus="true" />
 * 
* (you may want to use a instead of p as namespace prefix to avoid clash with PrimeFaces * default namespace) *

* Or: *

 * <h:inputText ...>
 *     <f:passThroughAttribute name="autofocus" value="true" />
 * </h:inputText>
 * 
* * @author Bauke Scholtz * @since 1.1 */ public class Html5RenderKit extends RenderKitWrapper { // Constants ------------------------------------------------------------------------------------------------------ /** The context parameter name to specify additional passthrough attributes. */ public static final String PARAM_NAME_PASSTHROUGH_ATTRIBUTES = "org.omnifaces.HTML5_RENDER_KIT_PASSTHROUGH_ATTRIBUTES"; private static final Set HTML5_UIFORM_ATTRIBUTES = unmodifiableSet( "autocomplete" // "novalidate" attribute is not useable in a JSF form. ); private static final Set HTML5_SELECT_ATTRIBUTES = unmodifiableSet( "autofocus" // "form" attribute is not useable in a JSF form. ); private static final Set HTML5_TEXTAREA_ATTRIBUTES = unmodifiableSet( "autofocus", "maxlength", "placeholder", "spellcheck", "wrap" // "form" attribute is not useable in a JSF form. // "required" attribute can't be used as it would override JSF default "required" attribute behaviour. ); private static final Set HTML5_INPUT_ATTRIBUTES = unmodifiableSet( "autofocus", "list", "pattern", "placeholder", "spellcheck" // "form*" attributes are not useable in a JSF form. // "multiple" attribute is only applicable on and and can't be // decoded by standard HtmlInputText. // "required" attribute can't be used as it would override JSF default "required" attribute behaviour. ); private static final Set HTML5_INPUT_PASSWORD_ATTRIBUTES = unmodifiableSet( "autofocus", "pattern", "placeholder" // "form*" attributes are not useable in a JSF form. // "required" attribute can't be used as it would override JSF default "required" attribute behaviour. ); private static final Set HTML5_INPUT_RANGE_ATTRIBUTES = unmodifiableSet( "max", "min", "step" ); private static final Set HTML5_INPUT_RANGE_TYPES = unmodifiableSet( "range", "number", "date" ); private static final Set HTML5_INPUT_TYPES = unmodifiableSet( "text", "search", "email", "url", "tel", HTML5_INPUT_RANGE_TYPES ); private static final Set HTML5_BUTTON_ATTRIBUTES = unmodifiableSet( "autofocus" // "form" attribute is not useable in a JSF form. ); private static final String ERROR_INVALID_INIT_PARAM = "Context parameter '" + PARAM_NAME_PASSTHROUGH_ATTRIBUTES + "' is in invalid syntax."; private static final String ERROR_INVALID_INIT_PARAM_CLASS = "Context parameter '" + PARAM_NAME_PASSTHROUGH_ATTRIBUTES + "'" + " references a class which is not found in runtime classpath: '%s'"; private static final String ERROR_UNSUPPORTED_HTML5_INPUT_TYPE = "HtmlInputText type '%s' is not supported. Supported types are " + HTML5_INPUT_TYPES + "."; // Properties ----------------------------------------------------------------------------------------------------- private RenderKit wrapped; private Map, Set> passthroughAttributes; // Constructors --------------------------------------------------------------------------------------------------- /** * Construct a new HTML5 render kit around the given wrapped render kit. * @param wrapped The wrapped render kit. */ public Html5RenderKit(RenderKit wrapped) { this.wrapped = wrapped; passthroughAttributes = initPassthroughAttributes(); } // Actions -------------------------------------------------------------------------------------------------------- /** * Returns a new HTML5 response writer which in turn wraps the default response writer. */ @Override public ResponseWriter createResponseWriter(Writer writer, String contentTypeList, String characterEncoding) { return new Html5ResponseWriter(super.createResponseWriter(writer, contentTypeList, characterEncoding)); } @Override public RenderKit getWrapped() { return wrapped; } // Helpers -------------------------------------------------------------------------------------------------------- @SuppressWarnings("unchecked") private static Map, Set> initPassthroughAttributes() { String passthroughAttributesParam = getInitParameter(PARAM_NAME_PASSTHROUGH_ATTRIBUTES); if (isEmpty(passthroughAttributesParam)) { return null; } Map, Set> passthroughAttributes = new HashMap<>(); for (String passthroughAttribute : passthroughAttributesParam.split("\\s*;\\s*")) { String[] classAndAttributeNames = passthroughAttribute.split("\\s*=\\s*", 2); if (classAndAttributeNames.length != 2) { throw new IllegalArgumentException(ERROR_INVALID_INIT_PARAM); } String className = classAndAttributeNames[0]; Object[] attributeNames = classAndAttributeNames[1].split("\\s*,\\s*"); Set attributeNameSet = unmodifiableSet(attributeNames); try { passthroughAttributes.put((Class) Class.forName(className), attributeNameSet); } catch (ClassNotFoundException e) { throw new IllegalArgumentException(String.format(ERROR_INVALID_INIT_PARAM_CLASS, className), e); } } return passthroughAttributes; } // Nested classes ------------------------------------------------------------------------------------------------- /** * This HTML5 response writer does all the job. * @author Bauke Scholtz */ class Html5ResponseWriter extends ResponseWriterWrapper { // Properties ------------------------------------------------------------------------------------------------- private ResponseWriter wrapped; // Constructors ----------------------------------------------------------------------------------------------- public Html5ResponseWriter(ResponseWriter wrapped) { this.wrapped = wrapped; } // Actions ---------------------------------------------------------------------------------------------------- @Override public ResponseWriter cloneWithWriter(Writer writer) { return new Html5ResponseWriter(super.cloneWithWriter(writer)); } /** * An override which checks if the given component is an instance of {@link UIForm} or {@link UIInput} and then * write HTML5 attributes which are explicitly been set by the developer. */ @Override public void startElement(String name, UIComponent component) throws IOException { super.startElement(name, component); if (component == null) { return; // Either the renderer is broken, or it's plain text/html. } if (component instanceof UIForm && "form".equals(name)) { writeHtml5AttributesIfNecessary(component.getAttributes(), HTML5_UIFORM_ATTRIBUTES); } else if (component instanceof UIInput) { writeHtml5AttributesIfNecessary((UIInput) component, name); } else if (component instanceof UICommand && "input".equals(name)) { writeHtml5AttributesIfNecessary(component.getAttributes(), HTML5_BUTTON_ATTRIBUTES); } if (passthroughAttributes != null) { for (Entry, Set> entry : passthroughAttributes.entrySet()) { if (entry.getKey().isInstance(component)) { writeHtml5AttributesIfNecessary(component.getAttributes(), entry.getValue()); } } } } /** * An override which checks if an attribute of type="text" is been written by an {@link UIInput} * component and if so then check if the type attribute isn't been explicitly set by the developer * and if so then write it. * @throws IllegalArgumentException When the type attribute is not supported. */ @Override public void writeAttribute(String name, Object value, String property) throws IOException { if ("type".equals(name) && "text".equals(value)) { UIComponent component = getCurrentComponent(); if (component instanceof HtmlInputText) { Object type = component.getAttributes().get("type"); if (type != null) { if (HTML5_INPUT_TYPES.contains(type)) { super.writeAttribute(name, type, null); return; } else { throw new IllegalArgumentException( String.format(ERROR_UNSUPPORTED_HTML5_INPUT_TYPE, type)); } } } } super.writeAttribute(name, value, property); } @Override public ResponseWriter getWrapped() { return wrapped; } // Helpers ---------------------------------------------------------------------------------------------------- private void writeHtml5AttributesIfNecessary(UIInput component, String name) throws IOException { if (isInput(component, name)) { Map attributes = component.getAttributes(); writeHtml5AttributesIfNecessary(attributes, HTML5_INPUT_ATTRIBUTES); if (HTML5_INPUT_RANGE_TYPES.contains(attributes.get("type"))) { writeHtml5AttributesIfNecessary(attributes, HTML5_INPUT_RANGE_ATTRIBUTES); } } else if (isInputPassword(component, name)) { writeHtml5AttributesIfNecessary(component.getAttributes(), HTML5_INPUT_PASSWORD_ATTRIBUTES); } else if (isTextarea(component, name)) { writeHtml5AttributesIfNecessary(component.getAttributes(), HTML5_TEXTAREA_ATTRIBUTES); } else if (isSelect(component, name)) { writeHtml5AttributesIfNecessary(component.getAttributes(), HTML5_SELECT_ATTRIBUTES); } } private void writeHtml5AttributesIfNecessary(Map attributes, Set names) throws IOException { for (String name : names) { Object value = attributes.get(name); if (value != null) { super.writeAttribute(name, value, null); } } } private boolean isInput(UIInput component, String name) { return component instanceof HtmlInputText && "input".equals(name); } private boolean isInputPassword(UIInput component, String name) { return component instanceof HtmlInputSecret && "input".equals(name); } private boolean isTextarea(UIInput component, String name) { return component instanceof HtmlInputTextarea && "textarea".equals(name); } private boolean isSelect(UIInput component, String name) { return isOneInstanceOf(component.getClass(), UISelectBoolean.class, UISelectOne.class, UISelectMany.class) && ("input".equals(name) || "select".equals(name)); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy