001: /*
002: * Copyright 2000,2005 wingS development team.
003: *
004: * This file is part of wingS (http://wingsframework.org).
005: *
006: * wingS is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU Lesser General Public License
008: * as published by the Free Software Foundation; either version 2.1
009: * of the License, or (at your option) any later version.
010: *
011: * Please see COPYING for the complete licence.
012: */
013: package org.wings.plaf.css;
014:
015: import org.apache.commons.logging.Log;
016: import org.apache.commons.logging.LogFactory;
017: import org.wings.*;
018: import org.wings.border.SDefaultBorder;
019: import org.wings.border.SEmptyBorder;
020: import org.wings.dnd.DragSource;
021: import org.wings.dnd.DropTarget;
022: import org.wings.io.Device;
023: import org.wings.io.StringBuilderDevice;
024: import org.wings.plaf.*;
025: import org.wings.plaf.css.dwr.CallableManager;
026: import org.wings.plaf.css.script.OnPageRenderedScript;
027: import org.wings.script.ScriptListener;
028: import org.wings.session.BrowserType;
029: import org.wings.session.ScriptManager;
030: import org.wings.util.SessionLocal;
031:
032: import java.io.IOException;
033: import java.io.Serializable;
034: import java.util.List;
035: import java.util.Map;
036:
037: /**
038: * Partial CG implementation that is common to all ComponentCGs.
039: *
040: * @author <a href="mailto:engels@mercatis.de">Holger Engels</a>
041: */
042: public abstract class AbstractComponentCG<COMPONENT_TYPE extends SComponent>
043: implements ComponentCG<COMPONENT_TYPE>, SConstants,
044: Serializable {
045: private static final Log log = LogFactory
046: .getLog(AbstractComponentCG.class);
047:
048: /**
049: * An invisible icon / graphic (spacer graphic)
050: */
051: private static final SIcon BLIND_ICON = new SResourceIcon(
052: "org/wings/icons/blind.gif");
053:
054: /**
055: * Be careful with this shared StringBuilder. Don't use it in situations where unknown code is called, that might
056: * use the StringBuilder, too.
057: */
058: public static final SessionLocal<StringBuilder> STRING_BUILDER = new SessionLocal<StringBuilder>() {
059: protected StringBuilder initialValue() {
060: return new StringBuilder();
061: }
062: };
063:
064: protected AbstractComponentCG() {
065: }
066:
067: /**
068: * Renders the default HTML TABLE prefix code for a component. This prefix will handle
069: * <ul>
070: * <li>All CSS Attrbiutes declared on the SComponent</li>
071: * <li>Components CSS class</li>
072: * <li>Components id</li>
073: * <li>Borders and insets (only if TABLE is used)</li>
074: * <li>Components ToolTip hooks</li>
075: * <li>Components Popup menu hooks</li>
076: * <li>components event id <code>eid</code> </li>
077: * </ul>
078: *
079: * @param device The output device to use
080: * @param component The component to render
081: * @throws IOException on error on the output device
082: */
083: protected final void writeTablePrefix(Device device,
084: COMPONENT_TYPE component) throws IOException {
085: writePrefix(device, component, true, null);
086: }
087:
088: /**
089: * Renders the default HTML TABLE prefix code for a component. This prefix will handle
090: * <ul>
091: * <li>All CSS Attrbiutes declared on the SComponent</li>
092: * <li>Components CSS class</li>
093: * <li>Components id</li>
094: * <li>Borders and insets (only if TABLE is used)</li>
095: * <li>Components ToolTip hooks</li>
096: * <li>Components Popup menu hooks</li>
097: * <li>components event id <code>eid</code> </li>
098: * </ul>
099: *
100: * @param device The output device to use
101: * @param component The component to render
102: * @throws IOException on error on the output device
103: */
104: protected final void writeTablePrefix(Device device,
105: COMPONENT_TYPE component, Map optionalAttributes)
106: throws IOException {
107: writePrefix(device, component, true, optionalAttributes);
108: }
109:
110: /**
111: * Renders the closing suffix for a TABLE based component prefix.
112: * @param device The output device to use
113: * @param component The component to render
114: * @throws IOException on error on the output device
115: */
116: protected final void writeTableSuffix(Device device,
117: COMPONENT_TYPE component) throws IOException {
118: writeSuffix(device, component, true);
119: }
120:
121: /**
122: * Renders a DIV prefix code for a component. <b>Discouraged</b>
123: * This prefix will handle
124: * <ul>
125: * <li>All CSS Attrbiutes declared on the SComponent</li>
126: * <li>Components CSS class</li>
127: * <li>Components id</li>
128: * <li>Borders and insets (only if TABLE is used)</li>
129: * <li>Components ToolTip hooks</li>
130: * <li>Components Popup menu hooks</li>
131: * <li>components event id <code>eid</code> </li>
132: * </ul>
133: *
134: * @param device The output device to use
135: * @param component The component to render
136: * @param optionalAttributes A map of additional CSS attributes
137: * @throws IOException on error on the output device
138: */
139: protected final void writeDivPrefix(Device device,
140: COMPONENT_TYPE component, Map optionalAttributes)
141: throws IOException {
142: writePrefix(device, component, false, optionalAttributes);
143: }
144:
145: /**
146: * Renders the closing suffix for a DIV prefix.
147: * @param device The output device to use
148: * @param component The component to render
149: * @throws IOException on error on the output device
150: */
151: protected final void writeDivSuffix(Device device,
152: COMPONENT_TYPE component) throws IOException {
153: writeSuffix(device, component, false);
154: }
155:
156: /**
157: * Renders the HTML prefix code for a component. This prefix will handle
158: * <ul>
159: * <li>All CSS Attrbiutes declared on the SComponent</li>
160: * <li>Components CSS class</li>
161: * <li>Components id</li>
162: * <li>Borders and insets (only if TABLE is used)</li>
163: * <li>Components ToolTip hooks</li>
164: * <li>Components Popup menu hooks</li>
165: * <li>components event id <code>eid</code> </li>
166: * </ul>
167: *
168: * @param device The output device to use
169: * @param component The component to render
170: * @param useTable <code>true</code> if it should be wrapped into a <code>TABLE</code> element <b>(recommended!)</b> or a <code>DIV</code>
171: * @param optionalAttributes A map of additional CSS attributes
172: * @throws IOException on error on the output device
173: */
174: protected final void writePrefix(final Device device,
175: final COMPONENT_TYPE component, final boolean useTable,
176: Map optionalAttributes) throws IOException {
177: // This is the containing element of a component
178: // it is responsible for styles, sizing...
179: if (useTable) {
180: device.print("<table"); // table
181: } else {
182: device.print("<div"); // div
183: }
184:
185: // We cant render this here.
186: // Utils.writeEvents(device, component, null);
187:
188: Utils.writeAllAttributes(device, component);
189:
190: // Render the optional attributes.
191: Utils.optAttributes(device, optionalAttributes);
192:
193: if (useTable) {
194: device.print("><tr><td"); // table
195:
196: if (component.getBorder() != null) {
197: Utils.printInlineStylesForInsets(device, component
198: .getBorder().getInsets());
199: }
200:
201: device.print('>'); // table
202: } else {
203: device.print('>'); // div
204: }
205: }
206:
207: protected final void writeSuffix(Device device,
208: COMPONENT_TYPE component, boolean useTable)
209: throws IOException {
210: if (useTable) {
211: device.print("</td></tr></table>");
212: } else {
213: device.print("</div>");
214: }
215: }
216:
217: /**
218: * Write JS code for context menus. Common implementaton for MSIE and gecko.
219: * @deprecated use {@link Utils#writeContextMenu(Device, SComponent)}
220: */
221: protected static void writeContextMenu(Device device,
222: SComponent component) throws IOException {
223: Utils.writeContextMenu(device, component);
224: }
225:
226: /**
227: * Write Tooltip code.
228: * @deprecated use {@link Utils#writeTooltipMouseOver(Device, SComponent)}
229: */
230: protected static void writeTooltipMouseOver(Device device,
231: SComponent component) throws IOException {
232: Utils.writeTooltipMouseOver(device, component);
233: }
234:
235: /**
236: * Install the appropriate CG for <code>component</code>.
237: *
238: * @param component the component
239: */
240: public void installCG(COMPONENT_TYPE component) {
241: Class clazz = component.getClass();
242: while (clazz.getPackage() == null
243: || !("org.wings".equals(clazz.getPackage().getName()) || "org.wingx"
244: .equals(clazz.getPackage().getName())))
245: clazz = clazz.getSuperclass();
246: String style = clazz.getName();
247: style = style.substring(style.lastIndexOf('.') + 1);
248: component.setStyle(style); // set default style name to component class (ie. SLabel).
249: component.setBorder(SDefaultBorder.INSTANCE); // the default style writes _no_ attributes, thus the stylesheet is in effect
250:
251: if (Utils.isMSIE(component)) {
252: final CGManager manager = component.getSession()
253: .getCGManager();
254: Object value;
255: int verticalOversize = 0;
256: value = manager.getObject(style + ".verticalOversize",
257: Integer.class);
258: if (value != null)
259: verticalOversize = ((Integer) value).intValue();
260: int horizontalOversize = 0;
261: value = manager.getObject(style + ".horizontalOversize",
262: Integer.class);
263: if (value != null)
264: horizontalOversize = ((Integer) value).intValue();
265:
266: if (verticalOversize != 0 || horizontalOversize != 0)
267: component.putClientProperty("oversize",
268: new SEmptyBorder(verticalOversize,
269: horizontalOversize, verticalOversize,
270: horizontalOversize));
271: }
272: }
273:
274: /**
275: * Uninstall the CG from <code>component</code>.
276: *
277: * @param component the component
278: */
279: public void uninstallCG(COMPONENT_TYPE component) {
280: }
281:
282: /**
283: * Retrieve a empty blind icon.
284: * @return A empty blind icon.
285: */
286: protected final SIcon getBlindIcon() {
287: return BLIND_ICON;
288: }
289:
290: public void componentChanged(COMPONENT_TYPE component) {
291: /*
292: InputMap inputMap = component.getInputMap();
293: if (inputMap != null && inputMap.size() > 0) {
294: if (!(inputMap instanceof VersionedInputMap)) {
295: inputMap = new VersionedInputMap(inputMap);
296: component.setInputMap(inputMap);
297: }
298:
299: final VersionedInputMap versionedInputMap = (VersionedInputMap) inputMap;
300: final Integer inputMapVersion = (Integer) component.getClientProperty("inputMapVersion");
301: if (inputMapVersion == null || versionedInputMap.getVersion() != inputMapVersion.intValue()) {
302: //InputMapScriptListener.install(component);
303: component.putClientProperty("inputMapVersion", new Integer(versionedInputMap.getVersion()));
304: }
305: }
306: */
307:
308: // Add script listener support.
309: List scriptListenerList = component.getScriptListenerList();
310: if (scriptListenerList != null && !scriptListenerList.isEmpty()) {
311: // BSC START ----------------: Bad code : behaviour injection !!!
312: /*
313:
314: HINT: Just try a
315: - oldList = xx;
316: - if (!currerntList.equals(oldList) !!!!!!!!!
317:
318: if (!(scriptListenerList instanceof VersionedList)) {
319: scriptListenerList = new VersionedList(scriptListenerList);
320: component.setScriptListenerList(scriptListenerList);
321: }
322:
323:
324: final VersionedList versionedList = (VersionedList) scriptListenerList;
325: final Integer scriptListenerListVersion = (Integer) component.getClientProperty("scriptListenerListVersion");
326: if (scriptListenerListVersion == null || versionedList.getVersion() != scriptListenerListVersion.intValue())
327: */if (true) // BSC ------------ END
328: {
329: /* TODO: this code destroys the dwr functionality
330: List removeCallables = new ArrayList();
331: // Remove all existing - and maybe unusable - DWR script listeners.
332: for (Iterator iter = CallableManager.getInstance().callableNames().iterator(); iter.hasNext();) {
333: Object o = iter.next();
334: if (o instanceof String) {
335: removeCallables.add(o);
336: }
337: }
338:
339: for (Iterator iter = removeCallables.iterator(); iter.hasNext(); ) {
340: Object o = iter.next();
341: if (o instanceof String) {
342: CallableManager.getInstance().unregisterCallable((String) o);
343: }
344: }
345: */
346:
347: // Add DWR script listener support.
348: ScriptListener[] scriptListeners = component
349: .getScriptListeners();
350: for (ScriptListener scriptListener1 : scriptListeners) {
351: if (scriptListener1 instanceof DWRScriptListener) {
352: DWRScriptListener scriptListener = (DWRScriptListener) scriptListener1;
353: CallableManager.getInstance().registerCallable(
354: scriptListener.getCallableName(),
355: scriptListener.getCallable());
356: }
357: }
358:
359: //component.putClientProperty("scriptListenerListVersion", new Integer(versionedList.getVersion()));
360: }
361: }
362: }
363:
364: /**
365: * This method renders the component (and all of its subcomponents) to the given device.
366: */
367: public final void write(final Device device,
368: final COMPONENT_TYPE component) throws IOException {
369: Utils.printDebug(device, "<!-- ");
370: Utils.printDebug(device, component.getName());
371: Utils.printDebug(device, " -->");
372: component.fireRenderEvent(SComponent.START_RENDERING);
373:
374: try {
375: org.wings.border.SBorder border = component.getBorder();
376: if (border != null) {
377: border.getCG().writeComponentBorderPrefix(device,
378: component);
379: }
380:
381: writeInternal(device, component);
382: ScriptManager.getInstance().addScriptListeners(
383: component.getScriptListeners());
384:
385: if (border != null) {
386: border.getCG().writeComponentBorderSufix(device,
387: component);
388: }
389: } catch (RuntimeException e) {
390: log.fatal("Runtime exception during rendering of "
391: + component.getName(), e);
392: device.print("<blink>" + e.getClass().getName()
393: + " during code generation of "
394: + component.getName() + "("
395: + component.getClass().getName() + ")</blink>\n");
396: }
397:
398: component.fireRenderEvent(SComponent.DONE_RENDERING);
399: Utils.printDebug(device, "<!-- /");
400: Utils.printDebug(device, component.getName());
401: Utils.printDebug(device, " -->");
402:
403: updateDragAndDrop(component);
404: }
405:
406: protected void updateDragAndDrop(final SComponent component) {
407: if (component instanceof DragSource
408: && ((DragSource) component).isDragEnabled()) {
409: StringBuilder builder = STRING_BUILDER.get();
410: builder.setLength(0);
411:
412: String name = component.getName();
413: builder.append("var ds_");
414: builder.append(name);
415: builder.append(" = new wingS.dnd.DD(\"");
416: builder.append(name);
417: builder.append("\");");
418: writeRegisterDragHandle(builder, component);
419: builder.append("\n");
420:
421: ScriptManager.getInstance().addScriptListener(
422: new OnPageRenderedScript(builder.toString()));
423: }
424: if (component instanceof DropTarget) {
425: StringBuilder builder = STRING_BUILDER.get();
426: builder.setLength(0);
427:
428: String name = component.getName();
429: builder.append("var dt_");
430: builder.append(name);
431: builder.append(" = new YAHOO.util.DDTarget(\"");
432: builder.append(name);
433: builder.append("\");\n");
434: ScriptManager.getInstance().addScriptListener(
435: new OnPageRenderedScript(builder.toString()));
436: }
437: }
438:
439: protected void writeRegisterDragHandle(StringBuilder builder,
440: SComponent component) {
441: String dragHandle = getDragHandle(component);
442: if (dragHandle != null) {
443: String name = component.getName();
444: builder.append("ds_");
445: builder.append(name);
446: builder.append(".setHandleElId(\"");
447: builder.append(dragHandle);
448: builder.append("\");");
449: }
450: }
451:
452: protected String getDragHandle(SComponent component) {
453: return null;
454: }
455:
456: public abstract void writeInternal(Device device,
457: COMPONENT_TYPE component) throws IOException;
458:
459: /**
460: * @return true if current browser is Microsoft Internet Explorer
461: */
462: protected final boolean isMSIE(final SComponent component) {
463: return component.getSession().getUserAgent().getBrowserType() == BrowserType.IE;
464: }
465:
466: public Update getComponentUpdate(COMPONENT_TYPE component) {
467: updateDragAndDrop(component);
468: return new ComponentUpdate(this , component);
469: }
470:
471: protected static class ComponentUpdate<COMPONENT_TYPE extends SComponent>
472: extends AbstractUpdate<COMPONENT_TYPE> {
473: private final AbstractComponentCG<COMPONENT_TYPE> cg;
474:
475: public ComponentUpdate(AbstractComponentCG<COMPONENT_TYPE> cg,
476: COMPONENT_TYPE component) {
477: super (component);
478: this .cg = cg;
479: }
480:
481: @Override
482: public int getProperty() {
483: return FULL_REPLACE_UPDATE;
484: }
485:
486: @Override
487: public int getPriority() {
488: return 4;
489: }
490:
491: @Override
492: public Handler getHandler() {
493: String htmlCode = "";
494: String exception = null;
495:
496: try {
497: StringBuilderDevice htmlDevice = new StringBuilderDevice(
498: 1024);
499: cg.write(htmlDevice, component);
500: htmlCode = htmlDevice.toString();
501: } catch (Throwable t) {
502: log.fatal("An error occured during rendering", t);
503: exception = t.getClass().getName();
504: }
505:
506: UpdateHandler handler = new UpdateHandler("component");
507: handler.addParameter(component.getName());
508: handler.addParameter(htmlCode);
509: if (exception != null) {
510: handler.addParameter(exception);
511: }
512: return handler;
513: }
514: }
515: }
|