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

org.omnifaces.component.script.Highlight Maven / Gradle / Ivy

There is a newer version: 4.4.1
Show newest version
/*
 * Copyright 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
 *
 *     https://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.component.script;

import static jakarta.faces.event.PhaseId.RENDER_RESPONSE;
import static java.lang.Boolean.TRUE;
import static java.lang.String.format;
import static org.omnifaces.config.OmniFaces.OMNIFACES_LIBRARY_NAME;
import static org.omnifaces.config.OmniFaces.OMNIFACES_SCRIPT_NAME;
import static org.omnifaces.util.Components.addFacesScriptResource;
import static org.omnifaces.util.Components.addScriptResource;
import static org.omnifaces.util.Components.getCurrentForm;
import static org.omnifaces.util.Events.subscribeToRequestBeforePhase;

import java.io.IOException;
import java.util.EnumSet;
import java.util.Set;

import jakarta.faces.component.FacesComponent;
import jakarta.faces.component.UIForm;
import jakarta.faces.component.UIInput;
import jakarta.faces.component.visit.VisitContext;
import jakarta.faces.component.visit.VisitHint;
import jakarta.faces.component.visit.VisitResult;
import jakarta.faces.context.FacesContext;

import org.omnifaces.util.State;

/**
 * 

* The <o:highlight> is a helper component which highlights all invalid {@link UIInput} components * and the associated labels by adding an error style class to them. Additionally, it by default focuses the first * invalid {@link UIInput} component. The <o:highlight /> component can be placed anywhere in the * view, as long as there's only one of it. Preferably put it somewhere in the master template for forms. *

 * <h:form>
 *     <h:inputText value="#{bean.input1}" required="true" />
 *     <h:inputText value="#{bean.input2}" required="true" />
 *     <h:commandButton value="Submit" action="#{bean.submit}" />
 * </h:form>
 * <o:highlight />
 * 
*

* The default error style class name is error. You need to specify a CSS style associated with the class * yourself. For example, *

 * label.error {
 *     color: #f00;
 * }
 * input.error, select.error, textarea.error {
 *     background-color: #fee;
 * }
 * 
*

* You can override the default error style class by the styleClass attribute: *

 * <o:highlight styleClass="invalid" />
 * 
*

* You can disable the default focus on the first invalid input element setting the focus attribute. *

 * <o:highlight styleClass="invalid" focus="false" />
 * 
*

* Since version 2.5, the error style class will be removed from the input element and its associated label when the * enduser starts using the input element. * * @author Bauke Scholtz * @see OnloadScript * @see ScriptFamily */ @FacesComponent(Highlight.COMPONENT_TYPE) public class Highlight extends OnloadScript { // Public constants ----------------------------------------------------------------------------------------------- /** The component type, which is {@value org.omnifaces.component.script.Highlight#COMPONENT_TYPE}. */ public static final String COMPONENT_TYPE = "org.omnifaces.component.script.Highlight"; // Private constants ---------------------------------------------------------------------------------------------- private static final Set VISIT_HINTS = EnumSet.of(VisitHint.SKIP_UNRENDERED); private static final String DEFAULT_STYLECLASS = "error"; private static final Boolean DEFAULT_FOCUS = TRUE; private static final String SCRIPT = "OmniFaces.Highlight.apply([%s], '%s', %s);"; private enum PropertyKeys { // Cannot be uppercased. They have to exactly match the attribute names. styleClass, focus } // Variables ------------------------------------------------------------------------------------------------------ private final State state = new State(getStateHelper()); // Init ----------------------------------------------------------------------------------------------------------- /** * The constructor instructs Faces to register all scripts during the render response phase if necessary. */ public Highlight() { subscribeToRequestBeforePhase(RENDER_RESPONSE, this::registerScriptsIfNecessary); } private void registerScriptsIfNecessary() { // This is supposed to be declared via @ResourceDependency, but JSF 3 and Faces 4 use a different script // resource name which cannot be resolved statically. addFacesScriptResource(); // Required for jsf.ajax.request. addScriptResource(OMNIFACES_LIBRARY_NAME, OMNIFACES_SCRIPT_NAME); } // Actions -------------------------------------------------------------------------------------------------------- /** * Visit all components of the current {@link UIForm}, check if they are an instance of {@link UIInput} and are not * {@link UIInput#isValid()} and finally append them to an array in JSON format and render the script. *

* Note that the {@link FacesContext#getClientIdsWithMessages()} could also be consulted, but it does not indicate * whether the components associated with those client IDs are actually {@link UIInput} components which are not * {@link UIInput#isValid()}. Also note that the highlighting is been done by delegating the job to JavaScript * instead of directly changing the component's own styleClass attribute; this is chosen so because we * don't want the changed style class to be saved in the server side view state as it may result in potential * inconsistencies because it's supposed to be an one-time change. */ @Override public void encodeChildren(FacesContext context) throws IOException { UIForm form = getCurrentForm(); if (form == null) { return; } StringBuilder clientIds = new StringBuilder(); form.visitTree(VisitContext.createVisitContext(context, null, VISIT_HINTS), (visitContext, component) -> { if (component instanceof UIInput && !((UIInput) component).isValid()) { if (clientIds.length() > 0) { clientIds.append(','); } String clientId = component.getClientId(visitContext.getFacesContext()); clientIds.append('"').append(clientId).append('"'); } return VisitResult.ACCEPT; }); if (clientIds.length() > 0) { context.getResponseWriter().write(format(SCRIPT, clientIds, getStyleClass(), isFocus())); } } /** * This component is per definiton only rendered when the current request is a postback request and the * validation has failed. */ @Override public boolean isRendered() { FacesContext context = getFacesContext(); return context.isPostback() && context.isValidationFailed() && super.isRendered(); } // Getters/setters ------------------------------------------------------------------------------------------------ /** * Returns the error style class which is to be applied on invalid inputs. Defaults to error. * @return The error style class which is to be applied on invalid inputs. */ public String getStyleClass() { return state.get(PropertyKeys.styleClass, DEFAULT_STYLECLASS); } /** * Sets the error style class which is to be applied on invalid inputs. * @param styleClass The error style class which is to be applied on invalid inputs. */ public void setStyleClass(String styleClass) { state.put(PropertyKeys.styleClass, styleClass); } /** * Returns whether the first error element should gain focus. Defaults to true. * @return Whether the first error element should gain focus. */ public boolean isFocus() { return state.get(PropertyKeys.focus, DEFAULT_FOCUS); } /** * Sets whether the first error element should gain focus. * @param focus Whether the first error element should gain focus. */ public void setFocus(boolean focus) { state.put(PropertyKeys.focus, focus); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy