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

org.omnifaces.util.Ajax Maven / Gradle / Ivy

There is a newer version: 4.5.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.util;

import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.omnifaces.util.Components.getCurrentComponent;
import static org.omnifaces.util.Components.getCurrentForm;
import static org.omnifaces.util.Faces.createResource;
import static org.omnifaces.util.Faces.isAjaxRequestWithPartialRendering;

import java.beans.Introspector;
import java.io.IOException;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;

import javax.faces.FacesException;
import javax.faces.application.Resource;
import javax.faces.component.UIColumn;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.component.UINamingContainer;
import javax.faces.context.FacesContext;
import javax.faces.context.PartialViewContext;

import org.omnifaces.context.OmniPartialViewContext;
import org.omnifaces.context.OmniPartialViewContextFactory;

/**
 * 

* Collection of utility methods for working with {@link PartialViewContext}. There are also shortcuts to the current * {@link OmniPartialViewContext} instance. *

* This utility class allows an easy way of programmaticaly (from inside a managed bean method) specifying new client * IDs which should be ajax-updated via {@link #update(String...)}, also {@link UIData} rows or columns on specific * index via {@link #updateRow(UIData, int)} and {@link #updateColumn(UIData, int)}, specifying callback scripts which * should be executed on complete of the ajax response via {@link #oncomplete(String...)}, and loading new JavaScript * resources on complete of the ajax response via {@link #load(String, String)}. *

* It also supports adding arguments to the JavaScript scope via {@link #data(String, Object)}, {@link #data(Map)} and * {@link #data(Object...)}. The added arguments are during the "on complete" phase as a JSON object available by * OmniFaces.Ajax.data in JavaScript context. The JSON object is encoded by {@link Json#encode(Object)} * which supports standard Java types {@link Boolean}, {@link Number}, {@link CharSequence} and {@link Date} arrays, * {@link Collection}s and {@link Map}s of them and as last resort it will use the {@link Introspector} to examine it * as a Javabean and encode it like a {@link Map}. *

* Note that {@link #updateRow(UIData, int)} and {@link #updateColumn(UIData, int)} can only update cell content when * it has been wrapped in some container component with a fixed ID. * *

Usage

*

* Some examples: *

 * // Update specific component on complete of ajax.
 * Ajax.update("formId:someId");
 * 
*
 * // Load script resource on complete of ajax.
 * Ajax.load("libraryName", "js/resourceName.js");
 * 
*
 * // Add variables to JavaScript scope.
 * Ajax.data("foo", foo); // It will be available as OmniFaces.Ajax.data.foo in JavaScript.
 * 
*
 * // Execute script on complete of ajax.
 * Ajax.oncomplete("alert(OmniFaces.Ajax.data.foo)");
 * 
* * @author Bauke Scholtz * @since 1.2 * @see Json * @see OmniPartialViewContext * @see OmniPartialViewContextFactory */ public final class Ajax { // Constants ------------------------------------------------------------------------------------------------------ private static final String ERROR_NO_SCRIPT_RESOURCE = ""; private static final String ERROR_NO_PARTIAL_RENDERING = "The current request is not an ajax request with partial rendering." + " Use Components#addScriptXxx() methods instead."; private static final String ERROR_ARGUMENTS_LENGTH = "The arguments length must be even. Encountered %d items."; private static final String ERROR_ARGUMENT_TYPE = "The argument name must be a String. Encountered type '%s' with value '%s'."; // Constructors --------------------------------------------------------------------------------------------------- private Ajax() { // Hide constructor. } // Shortcuts ------------------------------------------------------------------------------------------------------ /** * Returns the current partial view context (the ajax context). *

* Note that whenever you absolutely need this method to perform a general task, you might want to consider to * submit a feature request to OmniFaces in order to add a new utility method which performs exactly this general * task. * @return The current partial view context. * @see FacesContext#getPartialViewContext() */ public static PartialViewContext getContext() { return Faces.getContext().getPartialViewContext(); } /** * Update the given client IDs in the current ajax response. Note that those client IDs should not start with the * naming container separator character like :. This method also supports the client ID keywords * @all, @form and @this which respectively refers the entire view, the * currently submitted form as obtained by {@link Components#getCurrentForm()} and the currently processed * component as obtained by {@link UIComponent#getCurrentComponent(FacesContext)}. Any other client ID starting * with @ is by design ignored, including @none. * @param clientIds The client IDs to be updated in the current ajax response. * @see PartialViewContext#getRenderIds() */ public static void update(String... clientIds) { PartialViewContext context = getContext(); Collection renderIds = context.getRenderIds(); for (String clientId : clientIds) { if (clientId.charAt(0) != '@') { renderIds.add(clientId); } else if ("@all".equals(clientId)) { context.setRenderAll(true); } else if ("@form".equals(clientId)) { UIComponent currentForm = getCurrentForm(); if (currentForm != null) { renderIds.add(currentForm.getClientId()); } } else if ("@this".equals(clientId)) { UIComponent currentComponent = getCurrentComponent(); if (currentComponent != null) { renderIds.add(currentComponent.getClientId()); } } } } /** * Update the entire view. * @see PartialViewContext#setRenderAll(boolean) * @since 1.5 */ public static void updateAll() { getContext().setRenderAll(true); } /** * Update the row of the given {@link UIData} component at the given zero-based row index. This will basically * update all direct children of all {@link UIColumn} components at the given row index. *

* Note that the to-be-updated direct child of {@link UIColumn} must be a fullworthy JSF UI component which renders * a concrete HTML element to the output, so that JS/ajax can update it. So if you have due to design restrictions * for example a <h:panelGroup rendered="..."> without an ID, then you should give it an ID. * This way it will render a <span id="..."> which is updateable by JS/ajax. * @param table The {@link UIData} component. * @param index The zero-based index of the row to be updated. * @since 1.3 */ public static void updateRow(UIData table, int index) { if (index < 0 || table.getRowCount() < 1 || index >= table.getRowCount() || table.getChildCount() == 0) { return; } updateRowCells(table, index); } private static void updateRowCells(UIData table, int index) { FacesContext context = FacesContext.getCurrentInstance(); String parentId = table.getParent().getNamingContainer().getClientId(context); String tableId = table.getId(); char separator = UINamingContainer.getSeparatorChar(context); Collection renderIds = getContext().getRenderIds(); for (UIComponent column : table.getChildren()) { if (column instanceof UIColumn) { if (!column.isRendered()) { continue; } for (UIComponent cell : column.getChildren()) { if (!cell.isRendered()) { continue; } renderIds.add(format("%s%c%s%c%d%c%s", parentId, separator, tableId, separator, index, separator, cell.getId())); } } else if (column instanceof UIData) { // . updateRowCells((UIData) column, renderIds, tableId, index, separator); } } } private static void updateRowCells(UIData columns, Collection renderIds, String tableId, int index, char separator) { String columnId = columns.getId(); int columnCount = columns.getRowCount(); for (UIComponent cell : columns.getChildren()) { if (!cell.isRendered()) { continue; } for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) { renderIds.add(format("%s%c%d%c%s%c%d%c%s", tableId, separator, index, separator, columnId, separator, columnIndex, separator, cell.getId())); } } } /** * Update the column of the given {@link UIData} component at the given zero-based column index. This will basically * update all direct children of the {@link UIColumn} component at the given column index in all rows. The column * index is the physical column index and does not depend on whether one or more columns is rendered or not (i.e. it * is not necessarily the same column index as the enduser sees in the UI). *

* Note that the to-be-updated direct child of {@link UIColumn} must be a fullworthy JSF UI component which renders * a concrete HTML element to the output, so that JS/ajax can update it. So if you have due to design restrictions * for example a <h:panelGroup rendered="..."> without an ID, then you should give it an ID. * This way it will render a <span id="..."> which is updateable by JS/ajax. * @param table The {@link UIData} component. * @param index The zero-based index of the column to be updated. * @since 1.3 */ public static void updateColumn(UIData table, int index) { if (index < 0 || table.getRowCount() < 1 || index > table.getChildCount()) { return; } int rowCount = (table.getRows() == 0) ? table.getRowCount() : table.getRows(); if (rowCount == 0) { return; } updateColumnCells(table, index, rowCount); } private static void updateColumnCells(UIData table, int index, int rowCount) { FacesContext context = FacesContext.getCurrentInstance(); String parentId = table.getParent().getNamingContainer().getClientId(context); String tableId = table.getId(); char separator = UINamingContainer.getSeparatorChar(context); Collection renderIds = getContext().getRenderIds(); UIColumn column = findColumn(table, index); if (column != null && column.isRendered()) { for (UIComponent cell : column.getChildren()) { if (!cell.isRendered()) { continue; } String cellId = cell.getId(); for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) { renderIds.add(format("%s%c%s%c%d%c%s", parentId, separator, tableId, separator, rowIndex, separator, cellId)); } } } } private static UIColumn findColumn(UIData table, int index) { int columnIndex = 0; for (UIComponent column : table.getChildren()) { if (column instanceof UIColumn && columnIndex++ == index) { return (UIColumn) column; } } return null; } /** * Load given script resource on complete of the current ajax response. Basically, it loads the script resource as * a {@link String} and then delegates it to {@link #oncomplete(String...)}. * @param libraryName Library name of the JavaScript resource. * @param resourceName Resource name of the JavaScript resource. * @throws IllegalArgumentException When given script resource cannot be found. * @throws IllegalStateException When current request is not an ajax request with partial rendering. You should use * {@link Components#addScriptResource(String, String)} instead. * @since 2.3 */ public static void load(String libraryName, String resourceName) { Resource resource = createResource(libraryName, resourceName); if (resource == null) { throw new IllegalArgumentException(ERROR_NO_SCRIPT_RESOURCE); } try (Scanner scanner = new Scanner(resource.getInputStream(), UTF_8.name())) { oncomplete(scanner.useDelimiter("\\A").next()); } catch (IOException e) { throw new FacesException(e); } } /** * Execute the given scripts on complete of the current ajax response. * @param scripts The scripts to be executed. * @throws IllegalStateException When current request is not an ajax request with partial rendering. You should use * {@link Components#addScript(String)} instead. * @see OmniPartialViewContext#addCallbackScript(String) */ public static void oncomplete(String... scripts) { if (!isAjaxRequestWithPartialRendering()) { throw new IllegalStateException(ERROR_NO_PARTIAL_RENDERING); } OmniPartialViewContext context = OmniPartialViewContext.getCurrentInstance(); for (String script : scripts) { context.addCallbackScript(script); } } /** * Add the given data argument to the current ajax response. They are as JSON object available by * OmniFaces.Ajax.data. * @param name The argument name. * @param value The argument value. * @see OmniPartialViewContext#addArgument(String, Object) */ public static void data(String name, Object value) { OmniPartialViewContext.getCurrentInstance().addArgument(name, value); } /** * Add the given data arguments to the current ajax response. The arguments length must be even. Every first and * second argument is considered the name and value pair. The name must always be a {@link String}. They are as JSON * object available by OmniFaces.Ajax.data. * @param namesValues The argument names and values. * @throws IllegalArgumentException When the arguments length is not even, or when a name is not a string. * @see OmniPartialViewContext#addArgument(String, Object) */ public static void data(Object... namesValues) { if (namesValues.length % 2 != 0) { throw new IllegalArgumentException(format(ERROR_ARGUMENTS_LENGTH, namesValues.length)); } OmniPartialViewContext context = OmniPartialViewContext.getCurrentInstance(); for (int i = 0; i < namesValues.length; i+= 2) { if (!(namesValues[i] instanceof String)) { String type = (namesValues[i]) != null ? namesValues[i].getClass().getName() : "null"; throw new IllegalArgumentException(format(ERROR_ARGUMENT_TYPE, type, namesValues[i])); } context.addArgument((String) namesValues[i], namesValues[i + 1]); } } /** * Add the given mapping of data arguments to the current ajax response. They are as JSON object available by * OmniFaces.Ajax.data. * @param data The mapping of data arguments. * @see OmniPartialViewContext#addArgument(String, Object) */ public static void data(Map data) { OmniPartialViewContext context = OmniPartialViewContext.getCurrentInstance(); for (Entry entry : data.entrySet()) { context.addArgument(entry.getKey(), entry.getValue()); } } /** * Returns true if the given client ID was executed in the current ajax request. * @param clientId The client ID to be checked. * @return true if the given client ID was executed in the current ajax request. * @since 3.6 */ public static boolean isExecuted(String clientId) { return getContext().getExecuteIds().contains(clientId); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy