001: /* AbstractTag.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Tue Oct 4 09:15:59 2005, Created by tomyeh
010: }}IS_NOTE
011:
012: Copyright (C) 2005 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.zhtml.impl;
020:
021: import java.util.Iterator;
022: import java.util.Map;
023: import java.util.LinkedHashMap;
024: import java.util.Set;
025: import java.util.HashSet;
026:
027: import org.zkoss.lang.Objects;
028: import org.zkoss.xml.XMLs;
029: import org.zkoss.xml.HTMLs;
030:
031: import org.zkoss.zk.ui.Component;
032: import org.zkoss.zk.ui.Components;
033: import org.zkoss.zk.ui.AbstractComponent;
034: import org.zkoss.zk.ui.UiException;
035: import org.zkoss.zk.ui.WrongValueException;
036: import org.zkoss.zk.ui.sys.ComponentsCtrl;
037: import org.zkoss.zk.ui.event.Events;
038: import org.zkoss.zk.ui.event.EventListener;
039: import org.zkoss.zk.ui.ext.DynamicPropertied;
040: import org.zkoss.zk.ui.ext.RawId;
041:
042: /**
043: * The raw component used to generate raw HTML elements.
044: *
045: * <p>Note: ZHTML components ignore the page listener since it handles
046: * non-deferrable event listeners
047: * (see {@link org.zkoss.zk.ui.event.Deferrable}).
048: *
049: * @author tomyeh
050: */
051: public class AbstractTag extends AbstractComponent implements
052: DynamicPropertied, RawId {
053: /** The tag name. */
054: protected String _tagnm;
055: private Map _props;
056:
057: protected AbstractTag(String tagname) {
058: if (tagname == null || tagname.length() == 0)
059: throw new IllegalArgumentException("A tag name is required");
060: _tagnm = tagname;
061: }
062:
063: protected AbstractTag() {
064: }
065:
066: /** Returns the CSS class.
067: * Due to Java's limitation, we cannot use the name called getClas.
068: * <p>Default: null (the default value depends on element).
069: */
070: public String getSclass() {
071: return (String) getDynamicProperty("class");
072: }
073:
074: /** Sets the CSS class.
075: */
076: public void setSclass(String sclass) {
077: setDynamicProperty("class", sclass);
078: }
079:
080: /** Returns the CSS style.
081: * <p>Default: null.
082: */
083: public String getStyle() {
084: return (String) getDynamicProperty("style");
085: }
086:
087: /** Sets the CSS style.
088: *
089: * <p>Note: if display is not specified as part of style,
090: * the returned value of {@link #isVisible} is assumed.
091: * In other words, if not visible and dispaly is not specified as part of style,
092: * "display:none" is appended.
093: *
094: * <p>On the other hand, if display is specified, then {@link #setVisible}
095: * is called to reflect the visibility, if necessary.
096: */
097: public void setStyle(String style) {
098: setDynamicProperty("style", style);
099: }
100:
101: /** Returns the tag name.
102: */
103: public String getTag() {
104: return _tagnm;
105: }
106:
107: //-- DynamicPropertys --//
108: public boolean hasDynamicProperty(String name) {
109: return ComponentsCtrl.isReservedAttribute(name);
110: }
111:
112: /** Returns the dynamic property, or null if not found.
113: * Note: it must be a String object or null.
114: */
115: public Object getDynamicProperty(String name) {
116: return _props != null ? _props.get(name) : null;
117: }
118:
119: /** Sets the dynamic property.
120: * Note: it converts the value to a string object (by use of
121: * {@link Objects#toString}).
122: *
123: * <p>Note: it handles the style property specially. Refer to {@link #setStyle}
124: * for details.
125: */
126: public void setDynamicProperty(String name, Object value)
127: throws WrongValueException {
128: if (name == null)
129: throw new WrongValueException("name is required");
130: if (!hasDynamicProperty(name))
131: throw new WrongValueException(
132: "Attribute not allowed: "
133: + name
134: + "\nSpecify the ZK namespace if you want to use special ZK attributes");
135:
136: String sval = Objects.toString(value);
137: if ("style".equals(name))
138: sval = filterStyle(sval);
139:
140: setDynaProp(name, sval);
141: smartUpdate(name, sval);
142: }
143:
144: /** Processes the style. */
145: private String filterStyle(String style) {
146: if (style != null) {
147: final int j = HTMLs.getSubstyleIndex(style, "display");
148: if (j >= 0) { //display is specified
149: super .setVisible(!"none".equals(HTMLs.getSubstyleValue(
150: style, j)));
151: return style; //done
152: }
153: }
154:
155: if (!isVisible()) {
156: int len = style != null ? style.length() : 0;
157: if (len == 0)
158: return "display:none;";
159: if (style.charAt(len - 1) != ';')
160: style += ';';
161: style += "display:none;";
162: }
163: return style;
164: }
165:
166: /** Set the dynamic property 'blindly'. */
167: private void setDynaProp(String name, String value) {
168: if (value == null) {
169: if (_props != null)
170: _props.remove(name);
171: } else {
172: if (_props == null)
173: _props = new LinkedHashMap();
174: _props.put(name, value);
175: }
176: }
177:
178: /** Whether to hide the id attribute.
179: * <p>Default: false.
180: * <p>Some tags, such as {@link org.zkoss.zhtml.Html}, won't generate the id attribute.
181: * They will override this method to return true.
182: */
183: protected boolean shallHideId() {
184: return false;
185: }
186:
187: //-- Component --//
188: /** Changes the visibility of this component.
189: * <p>Note: it will adjust the style ({@link #getStyle}) based on the visibility.
190: *
191: * @return the previous visibility
192: */
193: public boolean setVisible(boolean visible) {
194: final boolean old = super .setVisible(visible);
195: if (old != visible) {
196: final String style = (String) getDynamicProperty("style");
197: if (visible) {
198: if (style != null) {
199: final int j = HTMLs.getSubstyleIndex(style,
200: "display");
201: if (j >= 0) {
202: final String val = HTMLs.getSubstyleValue(
203: style, j);
204: if ("none".equals(val)) {
205: String newstyle = style.substring(0, j);
206: final int k = style.indexOf(';', j + 7);
207: if (k >= 0)
208: newstyle += style.substring(k + 1);
209: setDynaProp("style", newstyle);
210: }
211: }
212: }
213: } else {
214: if (style == null) {
215: setDynaProp("style", "display:none;");
216: } else {
217: final int j = HTMLs.getSubstyleIndex(style,
218: "display");
219: if (j >= 0) {
220: final String val = HTMLs.getSubstyleValue(
221: style, j);
222: if (!"none".equals(val)) {
223: String newstyle = style.substring(0, j)
224: + "display:none;";
225: final int k = style.indexOf(';', j + 7);
226: if (k >= 0)
227: newstyle += style.substring(k + 1);
228: setDynaProp("style", newstyle);
229: }
230: } else {
231: final int len = style.length();
232: String newstyle = len > 0
233: && style.charAt(len - 1) != ';' ? style + ';'
234: : style;
235: setDynaProp("style", style + "display:none;");
236: }
237: }
238: }
239: }
240: return old;
241: }
242:
243: public boolean addEventListener(String evtnm, EventListener listener) {
244: final EventInfo ei;
245: for (int j = 0;; ++j) {
246: if (j >= _evts.length)
247: throw new UiException("Not supported event: " + evtnm);
248: if (_evts[j].name.equals(evtnm)) { //found
249: ei = _evts[j];
250: break;
251: }
252: }
253:
254: final boolean bAddType = ei.typed && !isTypeDeclared();
255: final boolean ret = super .addEventListener(evtnm, listener);
256: if (ret) {
257: smartUpdate(ei.attr,
258: Events.isListened(this , evtnm, true) ? "true"
259: : null);
260: //Bug 1477271: Tom M Yeh:
261: //We check non-deferable only. Otherwise, if users add a page
262: //event listener, all ZHTML will generate z.onChange.
263: if (bAddType && isTypeDeclared()) {
264: smartUpdate("z.type", "zhtml.main.Raw");
265: smartUpdate("z.init", true);
266: }
267: }
268: return ret;
269: }
270:
271: private boolean isTypeDeclared() {
272: for (int j = 0; j < _evts.length; ++j)
273: if (_evts[j].typed
274: && Events.isListened(this , _evts[j].name, true)) //asap only
275: return true;
276: return false;
277: }
278:
279: public void redraw(java.io.Writer out) throws java.io.IOException {
280: if (_tagnm == null)
281: throw new UiException("The tag name is not initialized yet");
282:
283: out.write('<');
284: out.write(_tagnm);
285:
286: boolean typeDeclared = false;
287: for (int j = 0; j < _evts.length; ++j) {
288: if (Events.isListened(this , _evts[j].name, true)) { //asap only
289: if (_evts[j].typed)
290: typeDeclared = true;
291: out.write(' ');
292: out.write(_evts[j].attr);
293: out.write("=\"true\"");
294: }
295: }
296:
297: if (typeDeclared)
298: out.write(" z.type=\"zhtml.main.Raw\"");
299:
300: if (typeDeclared || !shallHideId()
301: || !Components.isAutoId(getUuid())) {
302: out.write(" id=\"");
303: out.write(getUuid());
304: out.write('"');
305: }
306:
307: if (_props != null) {
308: for (Iterator it = _props.entrySet().iterator(); it
309: .hasNext();) {
310: final Map.Entry me = (Map.Entry) it.next();
311: final String key = (String) me.getKey();
312: final String val = (String) me.getValue();
313: out.write(' ');
314: out.write(key);
315: out.write("=\"");
316: out.write(XMLs.encodeAttribute(val));
317: out.write('"');
318: }
319: }
320:
321: if (isChildable()) {
322: boolean divGened = false;
323: if ("body".equals(_tagnm)) {
324: if (_props != null && _props.containsKey("class")) {
325: out.write("><div class=\"zk\">\n");
326: divGened = true;
327: } else {
328: out.write(" class=\"zk\">\n");
329: }
330: } else {
331: out.write('>');
332: }
333:
334: for (Iterator it = getChildren().iterator(); it.hasNext();)
335: ((Component) it.next()).redraw(out);
336:
337: if (divGened)
338: out.write("\n</div>");
339: out.write("</");
340: out.write(_tagnm);
341: out.write('>');
342: } else {
343: out.write("/>");
344: }
345: }
346:
347: public boolean isChildable() {
348: return !HTMLs.isOrphanTag(_tagnm);
349: }
350:
351: //Cloneable//
352: public Object clone() {
353: final AbstractTag clone = (AbstractTag) super .clone();
354: if (clone._props != null)
355: clone._props = new LinkedHashMap(clone._props);
356: return clone;
357: }
358:
359: //Object//
360: public String toString() {
361: return "[" + _tagnm + ' ' + getId() + ']';
362: }
363:
364: private static class EventInfo {
365: /** The event name.
366: */
367: private final String name;
368: /** The attribute that will be generated to the client side.
369: */
370: private final String attr;
371: /** Whether to generate z.type
372: */
373: private final boolean typed;
374:
375: private EventInfo(String name, String attr, boolean typed) {
376: this .name = name;
377: this .attr = attr;
378: this .typed = typed;
379: }
380: }
381:
382: private static final EventInfo[] _evts = {
383: new EventInfo(Events.ON_CLICK, "z.lfclk", false),
384: new EventInfo(Events.ON_CHANGE, "z.onChange", true) };
385: }
|