Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically pass through all on* event attributes #1880

21 changes: 20 additions & 1 deletion api/src/main/java/jakarta/faces/annotation/FacesConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import jakarta.faces.component.UIInput.ValidateEmptyFields;
import jakarta.faces.component.UINamingContainer;
import jakarta.faces.component.UIViewRoot;
import jakarta.faces.component.html.HtmlEvents;
import jakarta.faces.context.ExternalContext;
import jakarta.faces.context.FacesContext;
import jakarta.faces.convert.Converter;
Expand All @@ -68,6 +69,13 @@
@Retention(RUNTIME)
public @interface FacesConfig {

/**
* <p class="changed_added_5_0">
* Returns {@value HtmlEvents#ADDITIONAL_HTML_EVENT_NAMES_PARAM_NAME} as {@link String} array with default of empty string array.
* </p>
*/
@Nonbinding String[] additionalHtmlEventNames() default {};

/**
* <p class="changed_added_5_0">
* Returns {@value UIInput#ALWAYS_PERFORM_VALIDATION_WHEN_REQUIRED_IS_TRUE} as {@link Boolean} with default of {@code false}.
Expand Down Expand Up @@ -266,6 +274,7 @@
* </p>
*/
@Nonbinding int websocketEndpointPort() default 0;

/**
* <p class="changed_added_4_0">
* Supports inline instantiation of the {@link FacesConfig} qualifier.
Expand All @@ -282,6 +291,11 @@ public static final class Literal extends AnnotationLiteral<FacesConfig> impleme
*/
public static final Literal INSTANCE = new Literal();

@Override
public String[] additionalHtmlEventNames() {
return EMPTY_STRING_ARRAY;
}

@Override
public boolean alwaysPerformValidationWhenRequiredIsTrue() {
return false;
Expand Down Expand Up @@ -431,7 +445,12 @@ public int websocketEndpointPort() {
*
* @since 5.0
*/
public static enum ContextParam {
public enum ContextParam {

/**
* Returns {@value HtmlEvents#ADDITIONAL_HTML_EVENT_NAMES_PARAM_NAME} as {@link String} array with default of empty string array.
*/
ADDITIONAL_HTML_EVENT_NAMES(HtmlEvents.ADDITIONAL_HTML_EVENT_NAMES_PARAM_NAME, FacesConfig::additionalHtmlEventNames, StringArray.SPACE_SEPARATED),

/**
* Returns {@value UIInput#ALWAYS_PERFORM_VALIDATION_WHEN_REQUIRED_IS_TRUE} as {@link Boolean} with default of {@code false}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public interface ClientBehaviorHolder {
/**
* <p class="changed_added_2_0">
* Attaches a {@link ClientBehavior} to the component implementing this interface for the specified event. Valid event
* names for a UIComponent implementation are defined by {@code ClientBehaviorHolder.getEventNames()}.
* names for a UIComponent implementation are defined by {@link ClientBehaviorHolder#getEventNames()}.
* </p>
*
* @param eventName the logical name of the client-side event to attach the behavior to.
Expand Down
8 changes: 2 additions & 6 deletions api/src/main/java/jakarta/faces/component/html/HtmlBody.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@
package jakarta.faces.component.html;

import static jakarta.faces.component.html.HtmlComponentUtils.handleAttribute;
import static jakarta.faces.component.html.HtmlEvents.getHtmlDocumentElementEventNames;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;

import jakarta.faces.component.UIOutput;
import jakarta.faces.component.behavior.ClientBehaviorHolder;
Expand Down Expand Up @@ -576,12 +575,9 @@ public void setXmlns(java.lang.String xmlns) {
handleAttribute(this, "xmlns", xmlns);
}

private static final Collection<String> EVENT_NAMES = Collections.unmodifiableCollection(
Arrays.asList("click", "dblclick", "keydown", "keypress", "keyup", "load", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup", "unload"));

@Override
public Collection<String> getEventNames() {
return EVENT_NAMES;
return getHtmlDocumentElementEventNames(getFacesContext());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
package jakarta.faces.component.html;

import static jakarta.faces.component.html.HtmlComponentUtils.handleAttribute;
import static jakarta.faces.component.html.HtmlEvents.getFacesActionSourceEventNames;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;

import jakarta.faces.component.UICommand;
import jakarta.faces.component.behavior.ClientBehaviorHolder;
import jakarta.faces.event.BehaviorEvent.FacesComponentEvent;

/**
* <p>
Expand Down Expand Up @@ -823,17 +823,14 @@ public void setType(java.lang.String type) {
getStateHelper().put(PropertyKeys.type, type);
}

private static final Collection<String> EVENT_NAMES = Collections.unmodifiableCollection(Arrays.asList("blur", "change", "click", "action", "dblclick",
"focus", "keydown", "keypress", "keyup", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup", "select"));

@Override
public Collection<String> getEventNames() {
return EVENT_NAMES;
return getFacesActionSourceEventNames(getFacesContext());
}

@Override
public String getDefaultEventName() {
return "action";
return FacesComponentEvent.action.name();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
package jakarta.faces.component.html;

import static jakarta.faces.component.html.HtmlComponentUtils.handleAttribute;
import static jakarta.faces.component.html.HtmlEvents.getFacesActionSourceEventNames;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;

import jakarta.faces.component.UICommand;
import jakarta.faces.component.behavior.ClientBehaviorHolder;
import jakarta.faces.event.BehaviorEvent.FacesComponentEvent;

/**
* <p>
Expand Down Expand Up @@ -842,17 +842,14 @@ public void setType(java.lang.String type) {
handleAttribute(this, "type", type);
}

private static final Collection<String> EVENT_NAMES = Collections.unmodifiableCollection(Arrays.asList("blur", "click", "action", "dblclick", "focus",
"keydown", "keypress", "keyup", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup"));

@Override
public Collection<String> getEventNames() {
return EVENT_NAMES;
return getFacesActionSourceEventNames(getFacesContext());
}

@Override
public String getDefaultEventName() {
return "action";
return FacesComponentEvent.action.name();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@
package jakarta.faces.component.html;

import static jakarta.faces.component.html.HtmlComponentUtils.handleAttribute;
import static jakarta.faces.component.html.HtmlEvents.getHtmlBodyElementEventNames;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;

import jakarta.faces.component.UIData;
import jakarta.faces.component.behavior.ClientBehaviorHolder;
Expand Down Expand Up @@ -934,12 +933,9 @@ public void setWidth(java.lang.String width) {
handleAttribute(this, "width", width);
}

private static final Collection<String> EVENT_NAMES = Collections.unmodifiableCollection(
Arrays.asList("click", "dblclick", "keydown", "keypress", "keyup", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup"));

@Override
public Collection<String> getEventNames() {
return EVENT_NAMES;
return getHtmlBodyElementEventNames(getFacesContext());
}

@Override
Expand Down
186 changes: 186 additions & 0 deletions api/src/main/java/jakarta/faces/component/html/HtmlEvents.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package jakarta.faces.component.html;

import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toUnmodifiableList;

import java.util.Arrays;
import java.util.Collection;
import java.util.EnumMap;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Stream;

import jakarta.faces.annotation.FacesConfig.ContextParam;
import jakarta.faces.component.ActionSource;
import jakarta.faces.component.EditableValueHolder;
import jakarta.faces.component.behavior.ClientBehaviorHolder;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.BehaviorEvent.FacesComponentEvent;

/**
* <p class="changed_added_5_0">
* Events supported by HTML elements as per <a href="https://html.spec.whatwg.org/multipage/webappapis.html#event-handlers-on-elements,-document-objects,-and-window-objects">current spec</a>.
* These can be used to supply {@link ClientBehaviorHolder#getEventNames()} and {@link ClientBehaviorHolder#getDefaultEventName()}.
* </p>
*
* @since 5.0
*/
public final class HtmlEvents {

/**
* Events supported by all HTML document elements.
*/
public enum HtmlDocumentElementEvent {
abort,
auxclick,
beforeinput,
beforematch,
beforetoggle,
cancel,
canplay,
canplaythrough,
change,
click,
close,
contextlost,
contextmenu,
contextrestored,
copy,
cuechange,
cut,
dblclick,
drag,
dragend,
dragenter,
dragleave,
dragover,
dragstart,
drop,
durationchange,
emptied,
ended,
formdata,
input,
invalid,
keydown,
keypress,
keyup,
loadeddata,
loadedmetadata,
loadstart,
mousedown,
mouseenter,
mouseleave,
mousemove,
mouseout,
mouseover,
mouseup,
paste,
pause,
play,
playing,
progress,
ratechange,
reset,
securitypolicyviolation,
seeked,
seeking,
select,
slotchange,
stalled,
submit,
suspend,
timeupdate,
toggle,
volumechange,
waiting,
wheel;
}

/**
* Events supported by all HTML body elements, in addition to the events supported by all HTML document elements.
*/
public enum HtmlBodyElementEvent {
blur,
error,
focus,
load,
resize,
scroll,
scrollend;
}

/**
* The name of the context-param whose value must represent a space-separated list of additional HTML event names.
* All supported HTML event names are defined in the enums {@link HtmlDocumentElementEvent} and {@link HtmlBodyElementEvent}.
* Any HTML event name which you wish to add to these enums can be supplied via this context-param.
* Duplicates will be automatically filtered, case sensitive.
*/
public static final String ADDITIONAL_HTML_EVENT_NAMES_PARAM_NAME = "jakarta.faces.ADDITIONAL_HTML_EVENT_NAMES";

private enum CacheKey {
ADDITIONAL_HTML_EVENT_NAMES,
HTML_DOCUMENT_ELEMENT_EVENT_NAMES,
HTML_BODY_ELEMENT_EVENT_NAMES,
FACES_ACTION_SOURCE_EVENT_NAMES,
FACES_EDITABLE_VALUE_HOLDER_EVENT_NAMES;
}

private static final Map<CacheKey, Collection<String>> CACHE = new EnumMap<>(CacheKey.class);

private HtmlEvents() {
throw new AssertionError();
}

/**
* @param context The involved faces context.
* @return All additional HTML event names specified via {@link #ADDITIONAL_HTML_EVENT_NAMES_PARAM_NAME}.
*/
public static Collection<String> getAdditionalHtmlEventNames(FacesContext context) {
return cache(CacheKey.ADDITIONAL_HTML_EVENT_NAMES, () -> collect(stream(ContextParam.ADDITIONAL_HTML_EVENT_NAMES.<String[]>getValue(context))));
}

/**
* @param context The involved faces context.
* @return All supported event names for HTML document elements, including additional HTML event names.
*/
public static Collection<String> getHtmlDocumentElementEventNames(FacesContext context) {
return cache(CacheKey.HTML_DOCUMENT_ELEMENT_EVENT_NAMES, () -> merge(getAdditionalHtmlEventNames(context), HtmlDocumentElementEvent.values()));
}

/**
* @param context The involved faces context.
* @return All supported event names for HTML body elements, including HTML document element event names.
*/
public static Collection<String> getHtmlBodyElementEventNames(FacesContext context) {
return cache(CacheKey.HTML_BODY_ELEMENT_EVENT_NAMES, () -> merge(getHtmlDocumentElementEventNames(context), HtmlBodyElementEvent.values()));
}

/**
* @param context The involved faces context.
* @return All supported event names for HTML implementations of Faces {@link ActionSource} components, including HTML body element event names.
*/
public static Collection<String> getFacesActionSourceEventNames(FacesContext context) {
return cache(CacheKey.FACES_ACTION_SOURCE_EVENT_NAMES, () -> merge(getHtmlBodyElementEventNames(context), FacesComponentEvent.action));
}

/**
* @param context The involved faces context.
* @return All supported event names for HTML implementations of Faces {@link EditableValueHolder} components, including HTML body element event names.
*/
public static Collection<String> getFacesEditableValueHolderEventNames(FacesContext context) {
return cache(CacheKey.FACES_EDITABLE_VALUE_HOLDER_EVENT_NAMES, () -> merge(getHtmlBodyElementEventNames(context), FacesComponentEvent.valueChange));
}

private static Collection<String> collect(Stream<String> stream) {
return stream.sorted().distinct().collect(toUnmodifiableList());
}

private static Collection<String> merge(Collection<String> eventNames, Enum<?>... enumValues) {
return collect(Stream.concat(Arrays.stream(enumValues).map(e -> e.name()), eventNames.stream()));
}

private static Collection<String> cache(CacheKey key, Supplier<Collection<String>> supplier) {
return CACHE.computeIfAbsent(key, $ -> supplier.get());
}
}
Loading