/*
* #%L
* carewebframework
* %%
* Copyright (C) 2008 - 2016 Regenstrief Institute, Inc.
* %%
* 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.
*
* This Source Code Form is also subject to the terms of the Health-Related
* Additional Disclaimer of Warranty and Limitation of Liability available at
*
* http://www.carewebframework.org/licensing/disclaimer.
*
* #L%
*/
package org.carewebframework.ui.zk;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang.reflect.FieldUtils;
import org.carewebframework.common.MiscUtil;
import org.carewebframework.common.StrUtil;
import org.carewebframework.ui.FrameworkWebSupport;
import org.zkoss.json.JavaScriptValue;
import org.zkoss.lang.Exceptions;
import org.zkoss.zk.au.AuResponse;
import org.zkoss.zk.au.out.AuInvoke;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.ComponentNotFoundException;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.HtmlBasedComponent;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.ForwardEvent;
import org.zkoss.zk.ui.ext.Disable;
import org.zkoss.zk.ui.metainfo.PageDefinition;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zk.ui.util.ConventionWires;
import org.zkoss.zul.A;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Combobox;
import org.zkoss.zul.Html;
import org.zkoss.zul.Label;
import org.zkoss.zul.Popup;
import org.zkoss.zul.impl.InputElement;
import org.zkoss.zul.impl.MeshElement;
import org.zkoss.zul.impl.XulElement;
/**
* General purpose ZK extensions and convenience methods.
*/
public class ZKUtil {
/**
* Resource prefix for resources within this package
*/
public static final String RESOURCE_PREFIX = getResourcePath(ZKUtil.class);
private static final String CUSTOM_COLOR_OVERRIDE = "setCustomColor";
private static final String MASK_ANCHOR = "maskTarget";
private static final EventListener<Event> deferredDelivery = new EventListener<Event>() {
@Override
public void onEvent(Event event) throws Exception {
Events.sendEvent(event);
}
};
/**
* Possible match modes for hierarchical tree search.
*/
public enum MatchMode {
AUTO, // Autodetect index vs label
INDEX, // By node index.
CASE_SENSITIVE, // Case sensitive by node label.
CASE_INSENSITIVE // Case insensitive by node label.
};
/**
* Returns the ZK resource path for the specified class.
*
* @param clazz Class to evaluate
* @return String representing resource path
*/
public static final String getResourcePath(Class<?> clazz) {
return getResourcePath(clazz.getPackage());
}
/**
* Returns the ZK resource path for the specified class.
*
* @param clazz Class to evaluate
* @param up Number of path levels to remove
* @return String representing resource path
*/
public static final String getResourcePath(Class<?> clazz, int up) {
return getResourcePath(clazz.getPackage(), up);
}
/**
* Returns the ZK resource path for the specified package.
*
* @param pkg Package to evaluate
* @return String representing resource path
*/
public static final String getResourcePath(Package pkg) {
return getResourcePath(pkg.getName());
}
/**
* Returns the ZK resource path for the specified package.
*
* @param pkg Package to evaluate
* @param up Number of path levels to remove
* @return String representing resource path
*/
public static final String getResourcePath(Package pkg, int up) {
return getResourcePath(pkg.getName(), up);
}
/**
* Returns the ZK resource path for the package name.
*
* @param name Package name
* @return String representing resource path
*/
public static final String getResourcePath(String name) {
return getResourcePath(name, 0);
}
/**
* Returns the ZK resource path for the package name.
*
* @param name Package name
* @param up Number of path levels to remove
* @return String representing resource path
*/
public static final String getResourcePath(String name, int up) {
String path = StringUtils.chomp(name.replace('.', '/'), "/");
while (up > 0) {
int i = path.lastIndexOf("/");
if (i <= 0) {
break;
} else {
path = path.substring(0, i);
up--;
}
}
return "~./" + path + "/";
}
/**
* Loads a page definition from the cache. If not present in the cache, the page is created and
* placed in the cache.
*
* @param filename Filename of the zul page.
* @return A page definition.
*/
public static PageDefinition loadCachedPageDefinition(String filename) {
Validate.notNull(filename, "'filename' argument must not be null");
return ZulGlobalCache.getInstance().get(filename);
}
/**
* Loads a page definition from a zul page. If the page is an embedded resource, prefix the file
* name with the owning class name followed by a colon. For example,
* "org.carewebframework.ui.component.PatientSelection:patientSelection.zul"
*
* @param filename File name pointing to the zul page.
* @return A page definition representing the zul page.
*/
public static PageDefinition loadZulPageDefinition(String filename) {
InputStream is = null;
InputStreamReader reader = null;
Class<?> containingClass = null;
int i = filename.indexOf(":");
try {
if (i > 0) {
containingClass = Class.forName(filename.substring(0, i));
filename = filename.substring(i + 1);
}
if (containingClass == null) {
return Executions.getCurrent().getPageDefinition(filename.replace("/zkau/web/", "~./"));
}
is = containingClass.getResourceAsStream(filename);
if (is == null) {
throw new IOException("Zul page not found: " + filename);
}
reader = new InputStreamReader(is);
return Executions.getCurrent().getPageDefinitionDirectly(reader, null);
} catch (Exception e) {
IOUtils.closeQuietly(reader);
IOUtils.closeQuietly(is);
throw MiscUtil.toUnchecked(e);
}
}
/**
* Loads a page definition from a zul page. If the page is an embedded resource, prefix the file
* name with the owning class name followed by a colon. For example,
* "org.carewebframework.ui.component.PatientSelection:patientSelection.zul" Also, extracts any
* query parameters present and returns them in the specified map.
*
* @param url url of the zul page and, optionally, may include query parameters.
* @param args Upon return, will be populated by any query parameters present in the url.
* @return A page definition representing the zul page.
*/
public static PageDefinition loadZulPageDefinition(String url, Map<Object, Object> args) {
String pcs[] = url.split("\\?", 2);
if (args != null) {
Map<String, String> queryArgs = (pcs.length < 2 ? null : FrameworkWebSupport.queryStringToMap(pcs[1]));
if (queryArgs != null) {
args.putAll(queryArgs);
}
}
return loadZulPageDefinition(pcs[0]);
}
/**
* Loads a zul page. May contain a query string which is passed as an argument map.
*
* @param filename File name pointing to the zul page.
* @param parent Component to become the parent of the created page.
* @return The top level component of the created zul page.
*/
public static Component loadZulPage(String filename, Component parent) {
Map<Object, Object> args = new HashMap<>();
PageDefinition pageDefinition;
try {
pageDefinition = loadZulPageDefinition(filename, args);
} catch (Exception e) {
throw MiscUtil.toUnchecked(e);
}
return Executions.getCurrent().createComponents(pageDefinition, parent, args);
}
/**
* Loads a zul page with additional arguments passed as an argument map.
*
* @param filename File name pointing to the zul page.
* @param parent Component to become the parent of the created page.
* @param args Arguments to pass to zul page.
* @return The top level component of the created zul page.
*/
public static Component loadZulPage(String filename, Component parent, Map<Object, Object> args) {
return loadZulPage(filename, parent, args, null);
}
/**
* Loads a zul page with additional arguments passed as an argument map.
*
* @param filename File name pointing to the zul page.
* @param parent Component to become the parent of the created page.
* @param args Arguments to pass to zul page.
* @param controller If specified, the zul page is autowired to the controller.
* @return The top level component of the created zul page.
*/
public static Component loadZulPage(String filename, Component parent, Map<Object, Object> args, Object controller) {
PageDefinition pageDefinition = loadZulPageDefinition(filename);
Component top = Executions.getCurrent().createComponents(pageDefinition, parent, args);
wireController(top, controller);
return top;
}
/**
* Creates a dialog for the specified class. Autowires variables and forwards and applies the
* onMove event listener to restrict movement to the browser view port. This makes several
* assumptions. First, the zul page backed by the class must be named the same as the class with
* a lowercase first character. Second, the zul page must be located in a subfolder named the
* same as the fully qualified package name and located under the web folder. Third, the top
* level component of the zul page must correspond to the specified class. For example, if the
* class is org.carewebframework.sample.MyClass, the corresponding zul page must be
* web/org/carewebframework/sample/myClass.zul and the top level component in the zul page must
* be of the type myClass.
*
* @param <T> Type
* @param clazz Class backing the dialog.
* @return Instance of the dialog.
* @throws Exception if problem occurs creating dialog
*/
@SuppressWarnings("unchecked")
public static <T extends Component> T createDialog(Class<T> clazz) throws Exception {
String zul = getResourcePath(clazz) + StringUtils.uncapitalize(clazz.getSimpleName()) + ".zul";
T dialog = (T) ZKUtil.loadZulPage(zul, null);
MoveEventListener.add(dialog);
wireController(dialog);
return dialog;
}
/**
* Detaches all children from a parent.
*
* @param parent Parent component.
*/
public static void detachChildren(Component parent) {
detachChildren(parent, null);
}
/**
* Detaches children from a parent. If exclusions is not null, any exclusions will not be
* removed.
*
* @param parent Parent component.
* @param exclusions List of child components to be excluded, or null to detach all children.
*/
public static void detachChildren(Component parent, List<? extends Component> exclusions) {
detach(parent.getChildren(), exclusions);
}
/**
* Detaches a list of components.
*
* @param components List of components to detach.
*/
public static void detach(List<? extends Component> components) {
detach(components, null);
}
/**
* Detaches a list of components. If exclusions is not null, any exclusions will not be removed.
*
* @param components List of components to detach.
* @param exclusions List of components to be excluded, or null to detach all.
*/
public static void detach(List<? extends Component> components, List<? extends Component> exclusions) {
for (int i = components.size() - 1; i >= 0; i--) {
Component comp = components.get(i);
if (exclusions == null || !exclusions.contains(comp)) {
comp.detach();
}
}
}
/**
* Recurses over all children of specified component that implement the Disable interface or a
* "disabled" property and enables or disables them.
*
* @param parent Parent whose children's disable status is to be modified.
* @param disable The disable status for the children.
*/
public static void disableChildren(Component parent, boolean disable) {
List<Component> children = parent.getChildren();
for (int i = children.size() - 1; i >= 0; i--) {
Component comp = children.get(i);
if (comp instanceof Disable) {
((Disable) comp).setDisabled(disable);
} else {
try {
PropertyUtils.setSimpleProperty(comp, "disabled", disable);
} catch (Exception e) {}
}
disableChildren(comp, disable);
}
}
/**
* Returns the original event. Walks the chain of forwarded events until it encounters the
* original event.
*
* @param event The event whose origin is sought.
* @return The original event in a chain of zero or more forwarded events.
*/
public static Event getEventOrigin(Event event) {
while (event instanceof ForwardEvent) {
event = ((ForwardEvent) event).getOrigin();
}
return event;
}
/**
* Removes event listeners with the specified event name from a component.
*
* @param component Component whose event listeners are to be removed.
* @param evtnm The name of the event.
*/
public static void removeEventListeners(Component component, String evtnm) {
removeEventListeners(component, evtnm, null);
}
/**
* Removes event listeners with the specified event name and, optionally, of the specified
* class, from a component.
*
* @param component Component whose event listeners are to be removed.
* @param evtnm The name of the event.
* @param elClass Class of the event listener to be removed (null to ignore).
*/
public static void removeEventListeners(Component component, String evtnm, Class<?> elClass) {
List<EventListener<?>> list = new ArrayList<>();
for (EventListener<?> el : component.getEventListeners(evtnm)) {
if (elClass == null || elClass.equals(el.getClass())) {
list.add(el);
}
}
for (EventListener<?> el : list) {
component.removeEventListener(evtnm, el);
}
}
/**
* Finds the first child component of the specified class, or null if none found;
*
* @param <T> The child class.
* @param parent Parent whose children are to be searched.
* @param childClass Class of the child component being sought.
* @return Child component matching the specified class, or null if not found.
*/
public static <T extends Component> T findChild(Component parent, Class<T> childClass) {
return findChild(parent, childClass, null);
}
/**
* Finds a child component of the specified class, or null if none found;
*
* @param <T> The child class.
* @param parent Parent whose children are to be searched.
* @param childClass Class of the child component being sought.
* @param lastChild Child after which search will begin, or null to begin with first child.
* @return Child component matching the specified class, or null if not found.
*/
@SuppressWarnings("unchecked")
public static <T extends Component> T findChild(Component parent, Class<T> childClass, Component lastChild) {
Component child = parent == null ? null : lastChild == null ? parent.getFirstChild() : lastChild.getNextSibling();
while (child != null && !childClass.isInstance(child)) {
child = child.getNextSibling();
}
return (T) child;
}
/**
* Finds the first ancestor that belongs to the specified class.
*
* @param <T> The ancestor class.
* @param child Child whose ancestors are to be searched.
* @param ancestorClass Class being sought.
* @return Ancestor matching requested class, or null if not found.
*/
@SuppressWarnings("unchecked")
public static <T extends Component> T findAncestor(Component child, Class<T> ancestorClass) {
Component ancestor = child == null ? null : child.getParent();
while (ancestor != null && !ancestorClass.isInstance(ancestor)) {
ancestor = ancestor.getParent();
}
return (T) ancestor;
}
/**
* Returns the first visible child, if any.
*
* @param parent The parent component.
* @param recurse If true, all descendant levels are also searched using a breadth first
* strategy.
* @return The first visible child encountered, or null if not found.
*/
public static Component firstVisibleChild(Component parent, boolean recurse) {
return firstVisibleChild(parent, Component.class, recurse);
}
/**
* Returns the first visible child of a given class, if any.
*
* @param <T> The child class.
* @param parent The parent component.
* @param clazz The child class to consider.
* @param recurse If true, all descendant levels are also searched using a breadth first
* strategy.
* @return The first visible child encountered, or null if not found.
*/
@SuppressWarnings("unchecked")
public static <T extends Component> T firstVisibleChild(Component parent, Class<T> clazz, boolean recurse) {
for (Component child : parent.getChildren()) {
if (clazz.isInstance(child) && child.isVisible()) {
return (T) child;
}
}
if (recurse) {
for (Component child : parent.getChildren()) {
T comp = firstVisibleChild(child, clazz, recurse);
if (comp != null) {
return comp;
}
}
}
return null;
}
/**
* Sets focus to first input element under the parent that is capable of receiving focus.
*
* @param parent Parent component.
* @param select If true, select contents after setting focus.
* @return The input element that received focus, or null if focus was not set.
*/
public static InputElement focusFirst(Component parent, boolean select) {
for (Component child : parent.getChildren()) {
InputElement ele;
if (child instanceof InputElement) {
ele = (InputElement) child;
if (ele.isVisible() && !ele.isDisabled() && !ele.isReadonly()) {
ele.focus();
if (select) {
ele.select();
}
return ele;
}
} else if ((ele = focusFirst(child, select)) != null) {
return ele;
}
}
return null;
}
/**
* Moves a child to the position specified by an index.
*
* @param child Child to move
* @param index Move child to this position.
*/
public static void moveChild(Component child, int index) {
if (child == null) {
return;
}
Component parent = child.getParent();
Component refChild = parent.getChildren().get(index);
if (child != refChild) {
parent.insertBefore(child, refChild);
}
}
/**
* Swaps the position of the two child components.
*
* @param child1 The first child.
* @param child2 The second child.
*/
public static void swapChildren(Component child1, Component child2) {
Component parent = child1.getParent();
if (parent == null || parent != child2.getParent()) {
throw new IllegalArgumentException("Components do not have the same parent.");
}
if (child1 != child2) {
Component ref1 = child1.getNextSibling();
Component ref2 = child2.getNextSibling();
parent.insertBefore(child1, ref2);
parent.insertBefore(child2, ref1);
}
}
/**
* Returns a component of the specified ID in the same ID space. Components in the same ID space
* are called fellows. This is mainly used in cases where you can't call a method/function (e.g.
* EL in zul). This may be deprecated after we move to ZK5, which should provide what we need
* via <a href="http://docs.zkoss.org/wiki/Zk.Widget#Overview:_zk.Widget">ZK Widget</a>.
*
* @see org.zkoss.zk.ui.Component
* @param component Component to search for fellow
* @param id fellow component's id
* @return Component fellow component or null if not found
* @throws org.zkoss.zk.ui.ComponentNotFoundException When no fellow component found
*/
public static Component getFellow(Component component, String id) throws ComponentNotFoundException {
return component.getFellow(id);
}
/**
* Returns a component of the specified ID in the same ID space. Components in the same ID space
* are called fellows. This is mainly used in cases where you can't call a method/function (e.g.
* EL in zul). This may be deprecated after we move to ZK5, which should provide what we need
* via <a href="http://docs.zkoss.org/wiki/Zk.Widget#Overview:_zk.Widget">ZK Widget</a>.
*
* @see org.zkoss.zk.ui.Component
* @param component Component to search for fellow
* @param id fellow component's id
* @return Component fellow component or null if not found
*/
public static Component getFellowIfAny(Component component, String id) {
return component.getFellowIfAny(id);
}
/**
* Adds, removes, or replaces a style in a component.
*
* @param component The component whose style is to be modified.
* @param styleName The name of the style to modify.
* @param styleValue The new value for the style. If null or empty, the style is removed if it
* exists. Otherwise, if the style does not already exist, it is added with this
* value. If the style already exists, its value is replaced.
* @return The previous value for the style. Returns null if the style did not previously exist.
*/
public static String updateStyle(HtmlBasedComponent component, String styleName, String styleValue) {
String style = component.getStyle();
if (StringUtils.isEmpty(style) && StringUtils.isEmpty(styleValue)) {
return null;
}
String[] styles = style == null ? null : style.split("\\;");
StringBuilder sb = new StringBuilder();
String oldValue = null;
boolean found = false;
if (styles != null) {
for (String aStyle : styles) {
String[] nvp = aStyle.split("\\:", 2);
if (nvp.length == 0) {
continue;
}
String name = nvp[0].trim();
String val = nvp.length < 2 ? "" : nvp[1];
if (name.equals(styleName)) {
found = true;
oldValue = val;
val = styleValue;
}
if (!StringUtils.isEmpty(val)) {
sb.append(name).append(':').append(val).append(';');
}
}
}
if (!found && !StringUtils.isEmpty(styleValue)) {
sb.append(styleName).append(':').append(styleValue).append(';');
}
component.setStyle(sb.length() == 0 ? null : sb.toString());
return oldValue;
}
/**
* Adds or removes a class from a component's sclass property, preserving any other class names
* that may be present.
*
* @param component Component whose sclass will be modified.
* @param className The class name to add or remove.
* @param remove If true, the class is removed; otherwise, it is appended.
* @return Returns the original sclass property value.
*/
public static String updateSclass(HtmlBasedComponent component, String className, boolean remove) {
String oclass = component.getSclass();
component.setSclass(updateCSSclass(oclass, className, remove));
return oclass;
}
/**
* Adds or removes a class from a component's zclass property, preserving any other class names
* that may be present.
*
* @param component Component whose zclass will be modified.
* @param className The class name to add or remove.
* @param remove If true, the class is removed; otherwise, it is appended.
* @return Returns the original zclass property value.
*/
public static String updateZclass(HtmlBasedComponent component, String className, boolean remove) {
String oclass = component.getZclass();
component.setZclass(updateCSSclass(oclass, className, remove));
return oclass;
}
/**
* Adds or removes a class from a list of style classes, preserving any other class names that
* may be present.
*
* @param oclass Current style classes.
* @param className The class name to add or remove.
* @param remove If true, the class is removed; otherwise, it is appended.
* @return Returns the new class values.
*/
public static String updateCSSclass(String oclass, String className, boolean remove) {
if (StringUtils.isEmpty(oclass)) {
return remove ? null : className;
}
String nclass = " " + oclass + " ";
className = " " + className + " ";
boolean exists = nclass.contains(className);
if (exists == remove) {
if (remove) {
nclass = nclass.replace(className, " ");
} else {
nclass = oclass + className;
}
return StringUtils.trimToNull(nclass);
}
return oclass;
}
/**
* Toggles between two mutually exclusive sclass values, depending on the given boolean value.
*
* @param component Component whose sclass will be modified.
* @param classIfTrue Style class to be applied if value is true.
* @param classIfFalse Style class to be applied if value is false.
* @param value Value to determine which class is added and which is removed.
* @return Returns the original sclass property value.
*/
public static String toggleSclass(HtmlBasedComponent component, String classIfTrue, String classIfFalse, boolean value) {
String oclass = component.getSclass();
updateSclass(component, classIfTrue, !value);
updateSclass(component, classIfFalse, value);
return oclass;
}
/**
* Returns named boolean attribute from the specified component.
*
* @param c The component containing the desired attribute.
* @param attr The name of the attribute.
* @return Value of named attribute, or null if not found.
*/
public static boolean getAttributeBoolean(Component c, String attr) {
Boolean val = getAttribute(c, attr, Boolean.class);
return val != null ? val : BooleanUtils.toBoolean(getAttributeString(c, attr));
}
/**
* Returns named String attribute from the specified component.
*
* @param c The component containing the desired attribute.
* @param attr The name of the attribute.
* @return Value of named attribute, or null if not found.
*/
public static String getAttributeString(Component c, String attr) {
String val = getAttribute(c, attr, String.class);
return val == null ? "" : val;
}
/**
* Returns named integer attribute from the specified component.
*
* @param c The component containing the desired attribute.
* @param attr The name of the attribute.
* @param defaultVal The default value
* @return Value of named attribute, or null if not found.
*/
public static int getAttributeInt(Component c, String attr, int defaultVal) {
Integer val = getAttribute(c, attr, Integer.class);
return val != null ? val : defaultVal;
}
/**
* Returns named List attribute from the specified component.
*
* @param c The component containing the desired attribute.
* @param attr The name of the attribute.
* @return Value of named attribute, or null if not found.
*/
public static List<?> getAttributeList(Component c, String attr) {
return getAttribute(c, attr, List.class);
}
/**
* Returns named List attribute from the specified component.
*
* @param <T> Class of the list elements.
* @param c The component containing the desired attribute.
* @param attr The name of the attribute.
* @param elementClass The expected class of the list elements.
* @return Value of named attribute, or null if not found.
*/
@SuppressWarnings("unchecked")
public static <T> List<T> getAttributeList(Component c, String attr, Class<T> elementClass) {
return getAttribute(c, attr, List.class);
}
/**
* Returns named XulElement attribute from the specified component.
*
* @param c The component containing the desired attribute.
* @param attr The name of the attribute.
* @return Value of named attribute, or null if not found.
*/
public static XulElement getAttributeXulElement(Component c, String attr) {
return getAttribute(c, attr, XulElement.class);
}
/**
* Returns named Component attribute from the specified component.
*
* @param c The component containing the desired attribute.
* @param attr The name of the attribute.
* @return Value of named attribute, or null if not found.
*/
public static Component getAttributeComponent(Component c, String attr) {
return getAttribute(c, attr, Component.class);
}
/**
* Returns named attribute of the specified type from the specified component.
*
* @param <T> Class of the attribute value.
* @param c The component containing the desired attribute.
* @param attr The name of the attribute.
* @param clazz The expected type of the attribute value.
* @return Value of named attribute, or null if not found.
*/
@SuppressWarnings("unchecked")
public static <T> T getAttribute(Component c, String attr, Class<T> clazz) {
Object val = c == null || attr == null ? null : c.getAttribute(attr);
return clazz.isInstance(val) ? (T) val : null;
}
/**
* Invokes a JavaScript function on the client.
*
* @param functionName The function name.
* @param depends Component on which function execution depends. If the component is removed
* before the execution request is sent, the request will be removed. May be null to
* indicate no dependency.
* @param args List of arguments for the function.
*/
public static void invokeJS(String functionName, Component depends, Object... args) {
AuResponse rsp = new AuResponse(functionName, depends, args);
Clients.response(rsp);
}
/**
* Adds watermark text to the specified component.
*
* @param c Component to receive watermark text.
* @param watermark The watermark text.
*/
public static void addWatermark(Component c, String watermark) {
addWatermark(c, watermark, "gray", null, false);
}
/**
* Adds watermark (placeholder) text to the specified component.
*
* @param c Component to receive watermark text.
* @param watermark The watermark text.
* @param color Optional color for watermark text (may be null).
* @param font Optional font for watermark text (may be null).
* @param hideOnFocus True sets visibility to true on component focus
*/
public static void addWatermark(Component c, String watermark, String color, String font, boolean hideOnFocus) {
invokeJS("cwf_addWatermark", c, c.getUuid(), watermark, color, font, hideOnFocus);
}
/**
* Removes a watermark from the specified component.
*
* @param c Component whose watermark is to be removed.
*/
public static void removeWatermark(Component c) {
addWatermark(c, null);
}
/**
* Places a semi-transparent mask over the specified component to disable user interaction.
*
* @param c Component to be disabled.
* @param caption Caption text to appear over disabled component.
*/
public static void addMask(Component c, String caption) {
addMask(c, caption, null, null);
}
/**
* Places a semi-transparent mask over the specified component to disable user interaction.
*
* @param c Component to be disabled.
* @param caption Caption text to appear over disabled component.
* @param popup Optional popup to display when context menu is invoked.
*/
public static void addMask(Component c, String caption, Popup popup) {
addMask(c, caption, popup, null);
}
/**
* Places a semi-transparent mask over the specified component to disable user interaction.
*
* @param component Component to be disabled.
* @param caption Caption text to appear over disabled component.
* @param popup Optional popup to display when context menu is invoked.
* @param hint Optional tooltip text.
*/
public static void addMask(Component component, String caption, Popup popup, String hint) {
String anchor = getMaskAnchor(component);
AuResponse rsp = new AuResponse("cwf_addMask", component, new Object[] { component, caption, popup, hint, anchor });
Clients.response(rsp);
}
/**
* Removes any disable mask over a component.
*
* @param component The component.
*/
public static void removeMask(Component component) {
AuResponse rsp = new AuResponse("cwf_removeMask", component, new Object[] { component });
Clients.response(rsp);
}
/**
* Returns the uuid of the mask's anchor..
*
* @param component Component
* @return The subid of the mask anchor, or null if none specified.
*/
private static String getMaskAnchor(Component component) {
return (String) component.getAttribute(MASK_ANCHOR);
}
/**
* Sets the subid of the real mask anchor.
*
* @param component Component
* @param target The subid of the real anchor, or null to remove existing anchor.
*/
public static void setMaskAnchor(Component component, String target) {
component.setAttribute(MASK_ANCHOR, target);
}
/**
* Adds or removes a badge to/from an HTML element.
*
* @param selector Selector for the target element.
* @param label Text for the badge. Null value removes any existing badge.
* @param classes Optional additional CSS classes for badge.
*/
public static void setBadge(String selector, String label, String classes) {
AuResponse rsp = new AuInvoke("cwf.setBadge", new Object[] { selector, label, classes });
Clients.response(rsp);
}
/**
* Wires a controller (events and component references) for the specified component where the
* component serves as its own controller.
*
* @param component The source component.
*/
public static void wireController(Component component) {
wireController(component, component);
}
/**
* Wires a controller (events and component references) for the specified component.
*
* @param component The source component.
* @param controller The controller to be wired.
*/
public static void wireController(Component component, Object controller) {
if (controller != null) {
ConventionWires.wireVariables(component, controller);
ConventionWires.addForwards(component, controller);
if (!(controller instanceof Component)) {
Events.addEventListeners(component, controller);
}
}
}
/**
* Wires variables from a map into a controller. Useful to inject parameters passed in an
* argument map.
*
* @param map The argument map.
* @param controller The controller to be wired.
*/
public static void wireController(Map<?, ?> map, Object controller) {
if (map == null || map.isEmpty() || controller == null) {
return;
}
for (Entry<?, ?> entry : map.entrySet()) {
String key = entry.getKey().toString();
Object value = entry.getValue();
try {
PropertyUtils.setProperty(controller, key, value);
} catch (Exception e) {
try {
FieldUtils.writeField(controller, key, value, true);
} catch (Exception e1) {}
}
}
}
/**
* Fires an event, deferring delivery if the desktop of the target is not currently active.
*
* @param event The event to fire.
*/
public static void fireEvent(Event event) {
fireEvent(event, deferredDelivery);
}
/**
* Fires an event to the specified listener, deferring delivery if the desktop of the target is
* not currently active.
*
* @param event The event to fire.
* @param listener The listener to receive the event.
*/
public static void fireEvent(Event event, EventListener<Event> listener) {
Desktop dtp = event.getTarget() == null ? null : event.getTarget().getDesktop();
if (dtp != null && !inEventThread(dtp)) {
Executions.schedule(dtp, listener, event);
} else {
try {
listener.onEvent(event);
} catch (Exception e) {
throw MiscUtil.toUnchecked(e);
}
}
}
/**
* Returns true if in the specified desktop's event thread.
*
* @param dtp Desktop instance.
* @return True if the current event thread is the desktop's event thread.
*/
public static boolean inEventThread(Desktop dtp) {
return dtp.getExecution() != null && dtp.getExecution() == Executions.getCurrent();
}
/**
* Formats an exception for display.
*
* @param exc Exception to format.
* @return The displayable form of the exception.
*/
public static String formatExceptionForDisplay(Throwable exc) {
return exc == null ? null : Exceptions.getMessage(Exceptions.getRealCause(exc));
}
/**
* Send a print request to the browser client.
*
* @param selectors List of selectors whose content shall be printed.
* @param styleSheets List of stylesheets to be applied before printing.
* @param preview If true, open in preview mode. If false, submit directly for printing.
*/
public static void printToClient(List<String> selectors, List<String> styleSheets, boolean preview) {
invokeJS("cwf_print", null, selectors, styleSheets, preview);
}
/**
* Send a print request to the browser client.
*
* @param selectors Comma-delimited list of selectors whose content shall be printed.
* @param styleSheets Comma-delimited list of stylesheets to be applied before printing.
* @param preview If true, open in preview mode. If false, submit directly for printing.
*/
public static void printToClient(String selectors, String styleSheets, boolean preview) {
invokeJS("cwf_print", null, selectors, styleSheets, preview);
}
/**
* Provides a more convenient method signature for getting a ZK label with arguments. Also
* recognizes line continuation with backslash characters.
*
* @param key Label key.
* @param args Optional argument list.
* @return The key value or null if not found.
* @deprecated Use StrUtil.getLabel
*/
@Deprecated
public static String getLabel(String key, Object... args) {
return StrUtil.getLabel(key, args);
}
/**
* Returns a component of a type suitable for displaying the specified text. For text that is a
* URL, returns an anchor. For text that begins with <html>, returns an HTML component.
* All other text returns a label.
*
* @param text Text to be displayed.
* @return Component of the appropriate type.
*/
public static XulElement getTextComponent(String text) {
String frag = text == null ? "" : StringUtils.substring(text, 0, 20).toLowerCase();
if (frag.contains("<html>")) {
return new Html(text);
}
if (frag.matches("^https?:\\/\\/.+$")) {
A anchor = new A(text);
anchor.setHref(text);
anchor.setTarget("_blank");
return anchor;
}
return new Label(text);
}
/**
* Returns the node associated with the specified \-delimited path.
*
* @param <ANCHOR> Class of the anchor component.
* @param <NODE> Class of the node component.
* @param root Root component of hierarchy.
* @param anchorClass Class of the anchor component.
* @param nodeClass Class of the node component.
* @param path \-delimited path to search.
* @param create If true, nodes are created as needed.
* @param matchMode The match mode.
* @return The node corresponding to the specified path, or null if not found.
*/
@SuppressWarnings("unchecked")
public static <ANCHOR extends Component, NODE extends Component> NODE findNode(Component root, Class<ANCHOR> anchorClass,
Class<NODE> nodeClass, String path,
boolean create, MatchMode matchMode) {
String[] pcs = path.split("\\\\");
Component node = null;
try {
for (String pc : pcs) {
if (pc.isEmpty()) {
continue;
}
Component parent = node == null ? root : ZKUtil.findChild(node, anchorClass);
if (parent == null) {
if (!create) {
return null;
}
node.appendChild(parent = anchorClass.newInstance());
}
node = null;
int index = matchMode == MatchMode.INDEX || matchMode == MatchMode.AUTO ? NumberUtils.toInt(pc, -1) : -1;
MatchMode mode = matchMode != MatchMode.AUTO ? matchMode
: index >= 0 ? MatchMode.INDEX : MatchMode.CASE_INSENSITIVE;
List<Component> children = parent.getChildren();
int size = children.size();
if (mode == MatchMode.INDEX) {
if (index < 0) {
index = size;
}
int deficit = index - size;
if (!create && deficit >= 0) {
return null;
}
while (deficit-- >= 0) {
parent.appendChild(nodeClass.newInstance());
}
node = children.get(index);
} else {
for (Component child : children) {
String label = BeanUtils.getProperty(child, "label");
if (mode == MatchMode.CASE_SENSITIVE ? pc.equals(label) : pc.equalsIgnoreCase(label)) {
node = child;
break;
}
}
if (node == null) {
if (!create) {
return null;
}
node = nodeClass.newInstance();
parent.appendChild(node);
BeanUtils.setProperty(node, "label", pc);
}
}
if (node == null) {
break;
}
}
} catch (Exception e) {
throw MiscUtil.toUnchecked(e);
}
return (NODE) node;
}
/**
* Converts a JavaScript snippet to serializable form. If the snippet does not have a function
* wrapper, a no-argument wrapper will be added.
*
* @param snippet JS code snippet.
* @return A JavaScriptValue object or null if the input was null.
*/
public static JavaScriptValue toJavaScriptValue(String snippet) {
return snippet == null ? null
: new JavaScriptValue(snippet.startsWith("function") ? snippet : "function() {" + snippet + "}");
}
/**
* Applies the specified color setting to the target component. If the target implements a
* custom method for performing this operation, that method will be invoked. Otherwise, the
* background color of the target is set.
*
* @param comp Component to receive the color setting.
* @param color Color value.
*/
public static void applyColor(HtmlBasedComponent comp, String color) {
if (comp.getWidgetOverride(CUSTOM_COLOR_OVERRIDE) != null) {
Executions.getCurrent().addAuResponse(new AuInvoke(comp, CUSTOM_COLOR_OVERRIDE, color));
} else {
updateStyle(comp, "background-color", color);
}
}
/**
* Sets the JS code for applying a custom color to a component.
*
* @param comp Target component.
* @param snippet The JS snippet. If a function body is not supplied, a single argument function
* wrapper will be applied.
*/
public static void setCustomColorLogic(HtmlBasedComponent comp, String snippet) {
snippet = snippet == null ? null : snippet.startsWith("function") ? snippet : "function(value) {" + snippet + "}";
comp.setWidgetOverride(CUSTOM_COLOR_OVERRIDE, snippet);
}
/**
* Recursively wires input elements to a common event handler for the detection of changes.
*
* @param parent The parent component.
* @param targetComponent The component to receive the change events.
* @param targetEvent The name of the event to send to the target component.
*/
public static void wireChangeEvents(Component parent, Component targetComponent, String targetEvent) {
for (Component child : parent.getChildren()) {
String sourceEvents = null;
if (child instanceof Combobox) {
sourceEvents = Events.ON_CHANGING + "," + Events.ON_SELECT;
} else if (child instanceof InputElement) {
if (((InputElement) child).getInstant()) {
sourceEvents = Events.ON_CHANGE;
} else {
sourceEvents = Events.ON_CHANGE + "," + Events.ON_CHANGING;
}
} else if (child instanceof Checkbox) {
sourceEvents = Events.ON_CHECK;
} else if (child instanceof MeshElement) {
sourceEvents = Events.ON_SELECT;
}
if (sourceEvents != null) {
for (String eventName : sourceEvents.split("\\,")) {
child.addForward(eventName, targetComponent, targetEvent);
}
}
wireChangeEvents(child, targetComponent, targetEvent);
}
}
/**
* Enforces static class.
*/
private ZKUtil() {
};
}