/* AbstractTag.java
Purpose:
Description:
History:
Tue Oct 4 09:15:59 2005, Created by tomyeh
Copyright (C) 2005 Potix Corporation. All Rights Reserved.
{{IS_RIGHT
This program is distributed under LGPL Version 2.1 in the hope that
it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zhtml.impl;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.zkoss.html.HTMLs;
import org.zkoss.lang.Objects;
import org.zkoss.xml.XMLs;
import org.zkoss.zk.au.DeferredValue;
import org.zkoss.zk.ui.AbstractComponent;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Execution;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.ext.DynamicPropertied;
import org.zkoss.zk.ui.ext.RawId;
import org.zkoss.zk.ui.ext.render.DirectContent;
import org.zkoss.zk.ui.sys.BooleanPropertyAccess;
import org.zkoss.zk.ui.sys.ComponentCtrl;
import org.zkoss.zk.ui.sys.ComponentsCtrl;
import org.zkoss.zk.ui.sys.HtmlPageRenders;
import org.zkoss.zk.ui.sys.PropertyAccess;
import org.zkoss.zk.ui.sys.StringPropertyAccess;
/**
* The raw component used to generate raw HTML elements.
*
* <p>
* Note: ZHTML components ignore the page listener since it handles non-deferrable event listeners
* (see {@link org.zkoss.zk.ui.event.Deferrable}).
*
* @author tomyeh
*/
public class AbstractTag extends AbstractComponent implements DynamicPropertied, RawId {
static {
addClientEvent(AbstractTag.class, Events.ON_CLICK, 0);
}
/** The tag name. */
protected String _tagnm;
private Map<String, Object> _props;
protected AbstractTag(String tagname) {
if (tagname == null || tagname.length() == 0)
throw new IllegalArgumentException("A tag name is required");
_tagnm = tagname;
}
protected AbstractTag() {
}
/**
* Returns the CSS class. Due to Java's limitation, we cannot use the name called getClas.
* <p>
* Default: null (the default value depends on element).
*/
public String getSclass() {
return (String) getDynamicProperty("class");
}
/**
* Sets the CSS class.
*/
public void setSclass(String sclass) {
setDynamicProperty("class", sclass);
}
/**
* Returns the CSS style.
* <p>
* Default: null.
*/
public String getStyle() {
return (String) getDynamicProperty("style");
}
/**
* Sets the CSS style.
*
* <p>
* Note: if display is not specified as part of style, the returned value of {@link #isVisible}
* is assumed. In other words, if not visible and display is not specified as part of style,
* "display:none" is appended.
*
* <p>
* On the other hand, if display is specified, then {@link #setVisible} is called to reflect the
* visibility, if necessary.
*/
public void setStyle(String style) {
setDynamicProperty("style", style);
}
/**
* Returns the accesskey of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public String getAccesskey() {
return (String) getDynamicProperty("accesskey");
}
/**
* Sets the accesskey of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public void setAccesskey(String accesskey) throws WrongValueException {
setDynamicProperty("accesskey", accesskey);
}
/**
* Returns the contenteditable of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public boolean isContenteditable() {
final Boolean b = (Boolean) getDynamicProperty("contenteditable");
return b != null && b.booleanValue();
}
/**
* Sets the contenteditable of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public void setContenteditable(boolean contenteditable) throws WrongValueException {
setDynamicProperty("contenteditable", contenteditable ? Boolean.valueOf(contenteditable) : null);
}
/**
* Returns the dir of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public String getDir() {
return (String) getDynamicProperty("dir");
}
/**
* Sets the dir of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public void setDir(String dir) throws WrongValueException {
setDynamicProperty("dir", dir);
}
/**
* Returns the draggable of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public boolean isDraggable() {
final Boolean b = (Boolean) getDynamicProperty("draggable");
return b != null && b.booleanValue();
}
/**
* Sets the draggable of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public void setDraggable(boolean draggable) throws WrongValueException {
setDynamicProperty("draggable", draggable ? Boolean.valueOf(draggable) : null);
}
/**
* Returns the hidden of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public boolean isHidden() {
final Boolean b = (Boolean) getDynamicProperty("hidden");
return b != null && b.booleanValue();
}
/**
* Sets the hidden of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public void setHidden(boolean hidden) throws WrongValueException {
setDynamicProperty("hidden", hidden ? Boolean.valueOf(hidden) : null);
}
/**
* Returns the lang of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public String getLang() {
return (String) getDynamicProperty("lang");
}
/**
* Sets the lang of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public void setLang(String lang) throws WrongValueException {
setDynamicProperty("lang", lang);
}
/**
* Returns the spellcheck of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public boolean isSpellcheck() {
final Boolean b = (Boolean) getDynamicProperty("spellcheck");
return b != null && b.booleanValue();
}
/**
* Sets the spellcheck of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public void setSpellcheck(boolean spellcheck) throws WrongValueException {
setDynamicProperty("spellcheck", spellcheck ? Boolean.valueOf(spellcheck) : null);
}
/**
* Returns the tabindex of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public Integer getTabindex() {
return (Integer) getDynamicProperty("tabindex");
}
/**
* Sets the tabindex of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public void setTabindex(Integer tabindex) throws WrongValueException {
setDynamicProperty("tabindex", tabindex);
}
/**
* Returns the title of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public String getTitle() {
return (String) getDynamicProperty("title");
}
/**
* Sets the title of this tag.
* <p>Notice that this attribute refers to the corresponding attribute of the HTML5 specification.
* Hence, it would still be rendered to client-side as a DOM attribute even if the browser doesn’t support it.
* @since 8.0.3
*/
public void setTitle(String title) throws WrongValueException {
setDynamicProperty("title", title);
}
/**
* Returns the tag name.
*/
public String getTag() {
return _tagnm;
}
// -- DynamicPropertys --//
public boolean hasDynamicProperty(String name) {
return ComponentsCtrl.isReservedAttribute(name);
}
/**
* Returns the dynamic property, or null if not found. Note: it must be a String object or null.
*/
public Object getDynamicProperty(String name) {
return _props != null ? _props.get(name) : null;
}
/**
* Sets the dynamic property. Note: it converts the value to a string object (by use of
* {@link Objects#toString}).
*
* <p>
* Note: it handles the style property specially. Refer to {@link #setStyle} for details.
*/
public void setDynamicProperty(String name, Object value) throws WrongValueException {
if (name == null)
throw new WrongValueException("name is required");
if (!hasDynamicProperty(name))
throw new WrongValueException("Attribute not allowed: " + name
+ "\nSpecify the ZK namespace if you want to use special ZK attributes");
String sval = Objects.toString(value);
if ("style".equals(name)) {
sval = filterStyle(sval);
setDynaProp(name, sval);
} else if ("src".equals(name)) {
// ZK-3011: should defer until render
EncodedURL url = new EncodedURL(sval);
setDynaProp(name, url);
smartUpdate("dynamicProperty", new Object[] { name, url }, true);
return;
} else if ("textContent".equals(name)) {
setDynaProp(name, sval);
if (!getChildren().isEmpty())
invalidate();
} else
setDynaProp(name, value);
// B80-ZK-2716: style and textContent are both dynamiccProperty
smartUpdate("dynamicProperty", new String[] { name, sval }, true);
}
private String getEncodedURL(String src) {
final Desktop dt = getDesktop(); // it might not belong to any desktop
return dt != null ? dt.getExecution().encodeURL(src != null ? src : "~./img/spacer.gif")
: "";
}
/** Processes the style. */
private String filterStyle(String style) {
if (style != null) {
final int j = HTMLs.getSubstyleIndex(style, "display");
if (j >= 0) { // display is specified
super.setVisible(!"none".equals(HTMLs.getSubstyleValue(style, j)));
return style; // done
}
}
if (!isVisible()) {
int len = style != null ? style.length() : 0;
if (len == 0)
return "display:none;";
if (style.charAt(len - 1) != ';')
style += ';';
style += "display:none;";
}
return style;
}
/** Set the dynamic property 'blindly'. */
private void setDynaProp(String name, Object value) {
if (value == null) {
if (_props != null)
_props.remove(name);
} else {
if (_props == null)
_props = new LinkedHashMap<String, Object>();
_props.put(name, value);
}
}
/**
* Whether to hide the id attribute.
* <p>
* Default: false.
* <p>
* Some tags, such as {@link org.zkoss.zhtml.Html}, won't generate the id attribute. They shall
* override this method to return true.
*/
protected boolean shallHideId() {
return false;
}
// -- Component --//
/**
* Changes the visibility of this component.
* <p>
* Note: it will adjust the style ({@link #getStyle}) based on the visibility.
*
* @return the previous visibility
*/
public boolean setVisible(boolean visible) {
final boolean old = super.setVisible(visible);
if (old != visible) {
final String style = getStyle();
if (visible) {
if (style != null) {
final int j = HTMLs.getSubstyleIndex(style, "display");
if (j >= 0) {
final String val = HTMLs.getSubstyleValue(style, j);
if ("none".equals(val)) {
String newstyle = style.substring(0, j);
final int k = style.indexOf(';', j + 7);
if (k >= 0)
newstyle += style.substring(k + 1);
setDynaProp("style", newstyle);
}
}
}
} else {
if (style == null) {
setDynaProp("style", "display:none;");
} else {
final int j = HTMLs.getSubstyleIndex(style, "display");
if (j >= 0) {
final String val = HTMLs.getSubstyleValue(style, j);
if (!"none".equals(val)) {
String newstyle = style.substring(0, j) + "display:none;";
final int k = style.indexOf(';', j + 7);
if (k >= 0)
newstyle += style.substring(k + 1);
setDynaProp("style", newstyle);
}
} else {
final int len = style.length();
String newstyle = len > 0 && style.charAt(len - 1) != ';' ? style + ';'
: style;
setDynaProp("style", style + "display:none;");
}
}
}
}
return old;
}
/**
* Returns the widget class, "zhtml.Widget".
*
* @since 5.0.0
*/
public String getWidgetClass() {
return "zhtml.Widget";
}
public void redraw(java.io.Writer out) throws java.io.IOException {
if (_tagnm == null)
throw new UiException("The tag name is not initialized yet");
final Execution exec = Executions.getCurrent();
if (exec == null || exec.isAsyncUpdate(null) || !HtmlPageRenders.isDirectContent(exec)) {
super.redraw(out); // generate JavaScript
return;
}
TagRenderContext rc = PageRenderer.getTagRenderContext(exec);
final boolean rcRequired = rc == null;
Object ret = null;
if (rcRequired) {
ret = PageRenderer.beforeRenderTag(exec);
rc = PageRenderer.getTagRenderContext(exec);
}
out.write(getPrologHalf(false));
rc.renderBegin(this, getClientEvents(), getSpecialRendererOutput(this), false);
redrawChildrenDirectly(rc, exec, out);
out.write(getEpilogHalf());
rc.renderEnd(this);
if (rcRequired) {
out.write(rc.complete());
PageRenderer.afterRenderTag(exec, ret);
}
}
/**
* Renders the children directly to the given output. Notice it is called only if
* {@link #redraw} is going to render the content (HTML tags) directly. If it is about to
* generate the JavaScript code {@link #redrawChildren} will be called instead.
* <p>
* You have to override this method if the deriving class has additional information to render.
*
* @since 5.0.7
*/
protected void redrawChildrenDirectly(TagRenderContext rc, Execution exec, java.io.Writer out)
throws java.io.IOException {
for (Component child = getFirstChild(); child != null;) {
Component next = child.getNextSibling();
if (((ComponentCtrl) child).getExtraCtrl() instanceof DirectContent) {
((ComponentCtrl) child).redraw(out);
} else {
HtmlPageRenders.setDirectContent(exec, false);
rc.renderBegin(child, null, getSpecialRendererOutput(child), true);
HtmlPageRenders.outStandalone(exec, child, out);
rc.renderEnd(child);
HtmlPageRenders.setDirectContent(exec, true);
}
child = next;
}
}
protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer)
throws java.io.IOException {
super.renderProperties(renderer);
render(renderer, "prolog", getPrologHalf(false));
render(renderer, "epilog", getEpilogHalf());
}
/**
* @param hideUuidIfNoId
* whether not to generate UUID if possible
*/
/* package */ String getPrologHalf(boolean hideUuidIfNoId) {
final StringBuilder sb = new StringBuilder(128).append('<').append(_tagnm);
if ((!hideUuidIfNoId && !shallHideId()) || getId().length() > 0)
sb.append(" id=\"").append(getUuid()).append('"');
if (_props != null) {
for (Iterator it = _props.entrySet().iterator(); it.hasNext();) {
final Map.Entry me = (Map.Entry) it.next();
if (!"textContent".equals(me.getKey())) { // ignore textContent
// ZK-3011: should getValue if it's a deferredValue
Object v = me.getValue();
if (v instanceof DeferredValue) {
v = ((DeferredValue) v).getValue();
}
sb.append(' ').append(me.getKey()).append("=\"")
.append(XMLs.encodeAttribute(Objects.toString(v))).append('"');
}
}
}
if (!isOrphanTag())
sb.append('/');
sb.append('>');
Object textContent = getDynamicProperty("textContent");
if (textContent != null)
sb.append(XMLs.escapeXML((String) textContent));
return sb.toString();
}
/* package */ String getEpilogHalf() {
return isOrphanTag() ? "</" + _tagnm + '>' : "";
}
protected boolean isChildable() {
return isOrphanTag();
}
/**
* Returns whether this tag is an orphan tag, i.e., it shall be in the form of <tag/>.
*
* @since 5.0.8
*/
protected boolean isOrphanTag() {
return !HTMLs.isOrphanTag(_tagnm);
}
// --ComponentCtrl--//
private static HashMap<String, PropertyAccess> _properties = new HashMap<String, PropertyAccess>(
5);
static {
_properties.put("id", new StringPropertyAccess() {
public void setValue(Component cmp, String value) {
((AbstractTag) cmp).setId(value);
}
public String getValue(Component cmp) {
return ((AbstractTag) cmp).getId();
}
});
_properties.put("sclass", new StringPropertyAccess() {
public void setValue(Component cmp, String value) {
((AbstractTag) cmp).setSclass(value);
}
public String getValue(Component cmp) {
return ((AbstractTag) cmp).getSclass();
}
});
_properties.put("style", new StringPropertyAccess() {
public void setValue(Component cmp, String value) {
((AbstractTag) cmp).setStyle(value);
}
public String getValue(Component cmp) {
return ((AbstractTag) cmp).getStyle();
}
});
_properties.put("visible", new BooleanPropertyAccess() {
public void setValue(Component cmp, Boolean value) {
((AbstractTag) cmp).setVisible(value);
}
public Boolean getValue(Component cmp) {
return ((AbstractTag) cmp).isVisible();
}
});
}
public PropertyAccess getPropertyAccess(String prop) {
PropertyAccess pa = _properties.get(prop);
if (pa != null)
return pa;
return super.getPropertyAccess(prop);
}
// Cloneable//
public Object clone() {
final AbstractTag clone = (AbstractTag) super.clone();
if (clone._props != null)
clone._props = new LinkedHashMap<String, Object>(clone._props);
return clone;
}
// Object//
public String toString() {
return "[" + _tagnm + ' ' + super.toString() + ']';
}
public Object getExtraCtrl() {
return new ExtraCtrl();
}
protected class ExtraCtrl implements DirectContent {
}
// ZK-3097
private class EncodedURL implements org.zkoss.zk.au.DeferredValue, Serializable {
private String _src;
public EncodedURL(String src) {
_src = src;
}
public Object getValue() {
return getEncodedURL(_src);
}
}
}