001: /* XulElement.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Mon Jun 20 16:01:40 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.zul.impl;
020:
021: import java.util.Iterator;
022: import java.util.Map;
023: import java.util.HashMap;
024:
025: import org.zkoss.lang.Objects;
026: import org.zkoss.lang.Strings;
027: import org.zkoss.xml.HTMLs;
028:
029: import org.zkoss.zk.ui.Executions;
030: import org.zkoss.zk.ui.HtmlBasedComponent;
031: import org.zkoss.zk.ui.WrongValueException;
032: import org.zkoss.zk.ui.event.Events;
033: import org.zkoss.zk.ui.sys.ComponentsCtrl;
034: import org.zkoss.zk.au.Command;
035: import org.zkoss.zul.Popup;
036: import org.zkoss.zul.au.in.ColSizeCommand;
037: import org.zkoss.zul.au.in.PagingCommand;
038: import org.zkoss.zul.au.in.PageSizeCommand;
039: import org.zkoss.zul.event.ZulEvents;
040:
041: /**
042: * The fundamental class for XUL elements.
043: *
044: * @author tomyeh
045: */
046: abstract public class XulElement extends HtmlBasedComponent {
047: static {
048: //register commands
049: new ColSizeCommand(ZulEvents.ON_COL_SIZE, 0);
050: //Don't use Command.IGNORE_OLD_EQUIV since users might drag diff borders
051: new PagingCommand(ZulEvents.ON_PAGING,
052: Command.SKIP_IF_EVER_ERROR);
053: new PageSizeCommand(ZulEvents.ON_PAGE_SIZE,
054: Command.SKIP_IF_EVER_ERROR);
055: }
056:
057: /** The popup ID that will be shown when click. */
058: private String _popup;
059: /** The context ID that will be shown when right-click. */
060: private String _ctx;
061: /** The tooltip ID that will be shown when mouse-over. */
062: private String _tooltip;
063: /** The action. */
064: private String _action;
065:
066: /** Returns the ID of the popup ({@link Popup}) that should appear
067: * when the user right-clicks on the element (aka., context menu).
068: *
069: * <p>Default: null (no context menu).
070: */
071: public String getContext() {
072: return _ctx;
073: }
074:
075: /** Sets the ID of the popup ({@link Popup}) that should appear
076: * when the user right-clicks on the element (aka., context menu).
077: *
078: * <p>An onOpen event is sent to the context menu if it is going to
079: * appear. Therefore, developers can manipulate it dynamically
080: * (perhaps based on OpenEvent.getReference) by listening to the onOpen
081: * event.
082: *
083: * <p>Note: To simplify the use, it ignores the ID space when locating
084: * the component at the client. In other words, it searches for the
085: * first component with the specified ID, no matter it is in
086: * the same ID space or not.
087: *
088: * <p>(since 3.0.2) If there are two components with the same ID (of course, in
089: * different ID spaces), you can specify the UUID with the following
090: * format:<br/>
091: * <code>uuid(comp_uuid)</code>
092: *
093: * <p>Example:<br/>
094: * <pre><code>
095: * <label context="some">
096: * <label context="uuid(${some.uuid})"/>
097: * </code></pre>
098: * Both reference a component whose ID is "some".
099: * But, if there are several components with the same ID,
100: * the first one can reference to any of them.
101: * And, the second one reference to the component in the same ID space
102: * (of the label component).
103: * @see #setContext(Popup)
104: */
105: public void setContext(String context) {
106: if (!Objects.equals(_ctx, context)) {
107: _ctx = context;
108: smartUpdate("z.ctx", _ctx);
109: }
110: }
111:
112: /** Sets the UUID of the popup that should appear
113: * when the user right-clicks on the element (aka., context menu).
114: *
115: * <p>Note: it actually invokes
116: * <code>setContext("uuid(" + popup.getUuid() + ")")</code>
117: * @since 3.0.2
118: * @see #setContext(String)
119: */
120: public void setContext(Popup popup) {
121: setContext(popup != null ? "uuid(" + popup + ")" : null);
122: }
123:
124: /** Returns the ID of the popup ({@link Popup}) that should appear
125: * when the user clicks on the element.
126: *
127: * <p>Default: null (no poppup).
128: */
129: public String getPopup() {
130: return _popup;
131: }
132:
133: /** Sets the ID of the popup ({@link Popup}) that should appear
134: * when the user clicks on the element.
135: *
136: * <p>An onOpen event is sent to the popup menu if it is going to
137: * appear. Therefore, developers can manipulate it dynamically
138: * (perhaps based on OpenEvent.getReference) by listening to the onOpen
139: * event.
140: *
141: * <p>Note: To simplify the use, it ignores the ID space when locating
142: * the component at the client. In other words, it searches for the
143: * first component with the specified ID, no matter it is in
144: * the same ID space or not.
145: *
146: * <p>(since 3.0.2) If there are two components with the same ID (of course, in
147: * different ID spaces), you can specify the UUID with the following
148: * format:<br/>
149: * <code>uuid(comp_uuid)</code>
150: * @see #setPopup(Popup)
151: */
152: public void setPopup(String popup) {
153: if (!Objects.equals(_popup, popup)) {
154: _popup = popup;
155: smartUpdate("z.pop", _popup);
156: }
157: }
158:
159: /** Sets the UUID of the popup that should appear
160: * when the user clicks on the element.
161: *
162: * <p>Note: it actually invokes
163: * <code>setPopup("uuid(" + popup.getUuid() + ")")</code>
164: * @since 3.0.2
165: * @see #setPopup(String)
166: */
167: public void setPopup(Popup popup) {
168: setPopup(popup != null ? "uuid(" + popup + ")" : null);
169: }
170:
171: /** Returns the ID of the popup ({@link Popup}) that should be used
172: * as a tooltip window when the mouse hovers over the element for a moment.
173: * The tooltip will automatically disappear when the mouse is moved.
174: *
175: * <p>Default: null (no tooltip).
176: */
177: public String getTooltip() {
178: return _tooltip;
179: }
180:
181: /** Sets the ID of the popup ({@link Popup}) that should be used
182: * as a tooltip window when the mouse hovers over the element for a moment.
183: *
184: * <p>An onOpen event is sent to the tooltip if it is going to
185: * appear. Therefore, developers can manipulate it dynamically
186: * (perhaps based on OpenEvent.getReference) by listening to the onOpen
187: * event.
188: *
189: * <p>Note: To simplify the use, it ignores the ID space when locating
190: * the component at the client. In other words, it searches for the
191: * first component with the specified ID, no matter it is in
192: * the same ID space or not.
193: *
194: * <p>(since 3.0.2) If there are two components with the same ID (of course, in
195: * different ID spaces), you can specify the UUID with the following
196: * format:<br/>
197: * <code>uuid(comp_uuid)</code>
198: * @see #setTooltip(Popup)
199: */
200: public void setTooltip(String tooltip) {
201: if (!Objects.equals(_tooltip, tooltip)) {
202: _tooltip = tooltip;
203: smartUpdate("z.tip", _tooltip);
204: }
205: }
206:
207: /** Sets the UUID of the popup that should be used
208: * as a tooltip window when the mouse hovers over the element for a moment.
209: *
210: * <p>Note: it actually invokes
211: * <code>setTooltip("uuid(" + popup.getUuid() + ")")</code>
212: * @since 3.0.2
213: * @see #setTooltip(String)
214: */
215: public void setTooltip(Popup popup) {
216: setTooltip(popup != null ? "uuid(" + popup + ")" : null);
217: }
218:
219: /** Returns the client-side action (CSA).
220: * <p>The format: <br>
221: * action1: javascript1; javascript2; action2: javascript3
222: *
223: * <p>You could specify any action as long as JavaScript supports,
224: * such as onfocus, onblur, onmouseover and onmousout.
225: */
226: public String getAction() {
227: return _action;
228: }
229:
230: /** Sets the client-side action.
231: */
232: public void setAction(String action) {
233: if (action != null && action.length() == 0)
234: action = null;
235: if (!Objects.equals(_action, action)) {
236: _action = action;
237: invalidate();
238: //action is rarely changed dynamically, so we
239: //don't use smartUpdate (it requires two for-loop to
240: //replace and remove actions)
241: }
242: }
243:
244: /** Returns the attributes for onClick, onRightClick and onDoubleClick
245: * by checking whether the corresponding listeners are added,
246: * or null if none is added.
247: *
248: * @param ignoreOnClick whether to ignore onClick
249: */
250: protected String getAllOnClickAttrs(boolean ignoreOnClick) {
251: StringBuffer sb = null;
252: if (!ignoreOnClick)
253: sb = appendAsapAttr(sb, Events.ON_CLICK);
254: sb = appendAsapAttr(sb, Events.ON_DOUBLE_CLICK);
255: sb = appendAsapAttr(sb, Events.ON_RIGHT_CLICK);
256: return sb != null ? sb.toString() : null;
257: }
258:
259: /** Returns the HTML attributes representing the client-side action,
260: * or "" if no client-side action is defined.
261: *
262: * @since 3.0.0
263: */
264: public String getActionAttrs() {
265: if (_action == null)
266: return "";
267:
268: //To have smaller footprint for each component, we don't cache
269: //the parsed result
270: final StringBuffer sb = new StringBuffer(100);
271: for (Iterator it = parseAction(_action).entrySet().iterator(); it
272: .hasNext();) {
273: final Map.Entry me = (Map.Entry) it.next();
274: HTMLs.appendAttribute(sb, (String) me.getKey(),
275: toJavaScript((String) me.getValue()));
276: }
277: return sb.toString();
278: }
279:
280: //-- super --//
281: public String getOuterAttrs() {
282: final String attrs = super .getOuterAttrs();
283: final String ctx = getContext(), popup = getPopup(), tip = getTooltip();
284: //Let derives (e.g., treerow has a chance to override it)
285: if (ctx == null && tip == null && popup == null)
286: return attrs;
287:
288: final StringBuffer sb = new StringBuffer(80).append(attrs);
289: HTMLs.appendAttribute(sb, "z.ctx", ctx);
290: HTMLs.appendAttribute(sb, "z.pop", popup);
291: HTMLs.appendAttribute(sb, "z.tip", tip);
292: return sb.toString();
293: }
294:
295: /** Generates the Client-Side-Action attributes to the interior tag.
296: * Reason: onfocus is the main use.
297: */
298: public String getInnerAttrs() {
299: final String attrs = super .getInnerAttrs();
300: return _action == null ? attrs : attrs + getActionAttrs();
301: }
302:
303: /** Returns a map of actions (String name, String javascript).
304: */
305: private static final Map parseAction(String action) {
306: //1. Look for the first ':'
307: final Map map = new HashMap();
308: int k = action.indexOf(':');
309: if (k < 0)
310: throw new WrongValueException("Unknown action: " + action);
311:
312: int j = 0, len = action.length();
313: for (;;) {
314: String actnm = action.substring(j, k).trim();
315: if (actnm.length() == 0)
316: throw new WrongValueException("Unknown action: "
317: + action);
318:
319: //2. next ':'
320: int l;
321: char quote = (char) 0; //no quote
322: for (j = ++k;; ++k) {
323: if (k >= len) {
324: l = len; //next ':'
325: break;
326: }
327:
328: final char cc = action.charAt(k);
329: if (cc == '\\')
330: continue;
331:
332: if (quote != (char) 0) {
333: if (quote == cc)
334: quote = (char) 0;
335: } else if (cc == '\'' || cc == '"') {
336: quote = cc;
337: } else if (cc == ';') {
338: l = Strings.skipWhitespaces(action, k + 1);
339: for (; l < len; ++l) {
340: final char c2 = action.charAt(l);
341: if ((c2 < 'a' || c2 > 'z')
342: && (c2 < 'A' || c2 > 'Z'))
343: break; //inner loop
344: }
345:
346: l = Strings.skipWhitespaces(action, l);
347: if (l >= len) {
348: k = len;
349: break; //no more action
350: }
351: if (action.charAt(l) == ':') {
352: ++k; //after ';'
353: break; //found (and there is another action)
354: }
355:
356: k = l - 1; //since l point the next non-apha
357: }
358: }
359:
360: //3. generate it
361: final String val = action.substring(j, k).trim();
362: if (val.length() > 0) {
363: String nm = actnm.toLowerCase();
364: if ("onshow".equals(nm) || "onhide".equals(nm))
365: actnm = "z.c" + nm;
366: map.put(actnm, val);
367: }
368:
369: if (l >= len)
370: return map; //done
371: j = k;
372: k = l;
373: }
374: }
375:
376: /** Converts an action to JavaScript by interpreting #{} properly.
377: */
378: private final String toJavaScript(String action) {
379: return action != null ? ComponentsCtrl.parseClientScript(this,
380: action) : null;
381: }
382: }
|