com.adobe.granite.ui.components.FormData Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2018 Adobe
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe and its suppliers, if any. The intellectual
* and technical concepts contained herein are proprietary to Adobe
* and its suppliers and are protected by all applicable intellectual
* property laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe.
**************************************************************************/
package com.adobe.granite.ui.components;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Stack;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.ValueMap;
/**
* FormData represents the values of the form.
*
*
* The values are represented as a {@link ValueMap}. The FormData is set at
* request scope, where usually it is set by the form component and read by the
* field components.
*
*/
public class FormData {
private static final String ATTR_STACK = FormData.class.getName();
/**
* Creates a new FormData representing the given values to the request scope.
*
* FormData supports nesting. By calling this method, a new FormData is created
* and becomes the current context.
*
* @param request
* The request to store the values
* @param values
* The values of the FormData
* @param nameNotFoundMode
* The mode when the FormData doesn't have an entry of a certain name
* @return The new instance of FormData
*/
@Nonnull
public static FormData push(@Nonnull SlingHttpServletRequest request, @Nonnull ValueMap values,
@Nonnull NameNotFoundMode nameNotFoundMode) {
Stack stack = getStack(request);
if (stack == null) {
stack = new Stack<>();
request.setAttribute(ATTR_STACK, stack);
}
FormData formData = new FormData(values, nameNotFoundMode);
stack.push(formData);
return formData;
}
/**
* Pops the current FormData.
*
* @param request
* The request storing the values
* @return The current FormData
* @throws IllegalStateException
* When this method is called before
* {@link #push(SlingHttpServletRequest, ValueMap, NameNotFoundMode)}
*/
@SuppressWarnings("null")
@Nonnull
public static FormData pop(@Nonnull SlingHttpServletRequest request) throws IllegalStateException {
Stack stack = getStack(request);
if (stack == null || stack.isEmpty()) {
throw new IllegalStateException("Pop is called before push");
}
return stack.pop();
}
/**
* Returns the current FormData.
*
* @param request
* The request storing the values
* @return The current FormData or {@code null} if there is none
*/
@CheckForNull
public static FormData from(@Nonnull SlingHttpServletRequest request) {
Stack stack = getStack(request);
if (stack == null || stack.isEmpty()) {
return null;
}
return stack.peek();
}
@SuppressWarnings("unchecked")
@CheckForNull
private static Stack getStack(@Nonnull SlingHttpServletRequest request) {
return (Stack) request.getAttribute(ATTR_STACK);
}
@Nonnull
private ValueMap values;
@Nonnull
private NameNotFoundMode mode;
FormData(@Nonnull ValueMap values, @Nonnull NameNotFoundMode mode) {
this.values = values;
this.mode = mode;
}
/**
* Returns the values.
*
* @return The values
*/
@Nonnull
public ValueMap getValueMap() {
return values;
}
/**
* Returns the mode of the FormData.
*
* @return The mode
*/
@Nonnull
public NameNotFoundMode getMode() {
return mode;
}
/**
* Returns the value for the given name, converted to type T.
*
*
* In the {@code NameNotFoundMode#CHECK_FRESHNESS} mode, if the given name is
* not found and the FormData is fresh, then the given fieldValue is returned.
* Otherwise, {@code null} is returned.
*
*
*
* In the {@code NameNotFoundMode#IGNORE_FRESHNESS} mode, if the given name is
* not found, then the given fieldValue is returned.
*
*
* @param name
* The name of the field
* @param fieldValue
* The value of the field
* @param type
* The class of the type
* @param
* The type of the value
* @return The value converted to type T, or the given fieldValue, or
* {@code null}, depending on the conditions described above.
*/
@CheckForNull
public T get(@Nonnull String name, @CheckForNull T fieldValue, @Nonnull Class type) {
if (mode.equals(NameNotFoundMode.IGNORE_FRESHNESS)) {
if (fieldValue == null) {
return values.get(name, type);
} else {
return values.get(name, fieldValue);
}
}
if (!values.containsKey(name) && isFresh()) {
return fieldValue;
}
return values.get(name, type);
}
/**
* Returns the value for the given name, converted to type T.
*
*
* In the {@code NameNotFoundMode#CHECK_FRESHNESS} mode, if the given name is
* not found and the FormData is fresh, then the given fieldValue is returned.
* Otherwise, the given defaultValue is returned.
*
*
*
* In the {@code NameNotFoundMode#IGNORE_FRESHNESS} mode, if the given name is
* not found, then the given fieldValue is returned.
*
*
* @param name
* The name of the field
* @param fieldValue
* The value of the field
* @param defaultValue
* The default value
* @param
* The type of the value
* @return The value converted to type T, or the given fieldValue, or the given
* default value, depending on the conditions described above.
*/
@Nonnull
public T get(@Nonnull String name, @Nonnull T fieldValue, @Nonnull T defaultValue) {
if (mode.equals(NameNotFoundMode.IGNORE_FRESHNESS)) {
return values.get(name, fieldValue);
}
if (!values.containsKey(name) && isFresh()) {
return fieldValue;
}
return values.get(name, defaultValue);
}
/**
* An overload of {@link #isSelected(String, String, boolean, boolean)} where
* {@code forceIgnoreFreshness} parameter is {@code false}.
*
* @param name
* The name of the field
* @param value
* The value of the field option to compare against
* @param isFieldOptionSelected
* {@code true} if the field option is selected; {@code false}
* otherwise.
*
* @return Whether the given value is selected or not, or the given
* isFieldOptionSelected, depending on the conditions described above.
*/
public boolean isSelected(@Nonnull String name, @CheckForNull String value, boolean isFieldOptionSelected) {
return isSelected(name, value, isFieldOptionSelected, false);
}
/**
* Returns {@code true} if the given value of the field option is selected;
* {@code false} otherwise.
*
*
* In the {@code NameNotFoundMode#CHECK_FRESHNESS} mode, if the given name is
* not found and the FormData is fresh, then the given isFieldOptionSelected is
* returned, {@code false} otherwise.
*
*
*
* In the {@code NameNotFoundMode#IGNORE_FRESHNESS} mode, if the given name is
* not found, then the given isFieldOptionSelected is returned.
*
*
* @param name
* The name of the field
* @param value
* The value of the field option to compare against
* @param isFieldOptionSelected
* {@code true} if the field option is selected; {@code false}
* otherwise.
* @param forceIgnoreFreshness
* {@code true} to force to be {@link NameNotFoundMode#IGNORE_FRESHNESS};
* {@code false} otherwise.
*
* @return Whether the given value is selected or not, or the given
* isFieldOptionSelected, depending on the conditions described above.
*/
public boolean isSelected(@Nonnull String name, @CheckForNull String value, boolean isFieldOptionSelected, boolean forceIgnoreFreshness) {
String[] formValues = values.get(name, String[].class);
if (formValues != null) {
if (value == null) {
return false;
}
return Arrays.asList(formValues).contains(value);
}
if (mode.equals(NameNotFoundMode.IGNORE_FRESHNESS) || forceIgnoreFreshness) {
return isFieldOptionSelected;
}
boolean isFresh = isFresh();
if (value != null && value.isEmpty() && !isFresh) {
// GRANITE-4320: Handle a scenario when the value is an empty string
// When it is an empty string, SlingPostServlet will remove the JCR property
// (`contentValue` will be null).
// Hence even though the `contentValue` is null, if the `value` is an empty
// string we have to return true to select it.
return true;
}
return isFresh ? isFieldOptionSelected : false;
}
private boolean isFresh() {
// https://git.corp.adobe.com/CQ/ui-classic/blob/2152156d25759e3d8605b7e4f4598b43ffcd1f3b/content/jcr_root/libs/cq/ui/widgets/source/ext/override/widgets/form/Field.js#L121-L137
Calendar created = values.get("jcr:created", Calendar.class);
Calendar lastModified = values.get("jcr:lastModified", Calendar.class);
if (lastModified == null) {
lastModified = values.get("cq:lastModified", Calendar.class);
}
if (created == null && lastModified == null) {
return true;
}
if (created == null || lastModified == null) {
return false;
}
long diff = lastModified.getTimeInMillis() - created.getTimeInMillis();
return diff >= 0 && diff <= 5;
}
/**
* The mode on how to handle the scenario when the FormData doesn't have an
* entry of a certain name.
*/
public static enum NameNotFoundMode {
/**
* When the FormData doesn't have an entry of a certain name, if the FormData is
* fresh then the value configured at the field is used instead, otherwise (it's
* not fresh) the value is taken from the FormData as usual, which will return
* {@code null}.
*
*
* The form is fresh when {@code jcr:created} property has equal value to the
* last modified property's value (taken from either {@code jcr:lastModified} or
* {@code cq:lastModified}).
*
* If either created or last modified date is not available, it is not
* considered as fresh.
*
*
*
* This mode is usually used for Authoring dialogs.
*
*/
CHECK_FRESHNESS,
/**
* When the FormData doesn't have an entry of a certain name, then the value
* configured at the field is used instead.
*/
IGNORE_FRESHNESS
}
}