001: /*
002: * This file is part of the Echo Web Application Framework (hereinafter "Echo").
003: * Copyright (C) 2002-2005 NextApp, Inc.
004: *
005: * Version: MPL 1.1/GPL 2.0/LGPL 2.1
006: *
007: * The contents of this file are subject to the Mozilla Public License Version
008: * 1.1 (the "License"); you may not use this file except in compliance with
009: * the License. You may obtain a copy of the License at
010: * http://www.mozilla.org/MPL/
011: *
012: * Software distributed under the License is distributed on an "AS IS" basis,
013: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
014: * for the specific language governing rights and limitations under the
015: * License.
016: *
017: * Alternatively, the contents of this file may be used under the terms of
018: * either the GNU General Public License Version 2 or later (the "GPL"), or
019: * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
020: * in which case the provisions of the GPL or the LGPL are applicable instead
021: * of those above. If you wish to allow use of your version of this file only
022: * under the terms of either the GPL or the LGPL, and not to allow others to
023: * use your version of this file under the terms of the MPL, indicate your
024: * decision by deleting the provisions above and replace them with the notice
025: * and other provisions required by the GPL or the LGPL. If you do not delete
026: * the provisions above, a recipient may use your version of this file under
027: * the terms of any one of the MPL, the GPL or the LGPL.
028: */
029:
030: package nextapp.echo2.webcontainer.syncpeer;
031:
032: import org.w3c.dom.Document;
033: import org.w3c.dom.DocumentFragment;
034: import org.w3c.dom.Element;
035: import org.w3c.dom.Node;
036:
037: import nextapp.echo2.app.Color;
038: import nextapp.echo2.app.Component;
039: import nextapp.echo2.app.ContentPane;
040: import nextapp.echo2.app.Extent;
041: import nextapp.echo2.app.FillImage;
042: import nextapp.echo2.app.FloatingPane;
043: import nextapp.echo2.app.Font;
044: import nextapp.echo2.app.ImageReference;
045: import nextapp.echo2.app.Insets;
046: import nextapp.echo2.app.button.AbstractButton;
047: import nextapp.echo2.app.update.ServerComponentUpdate;
048: import nextapp.echo2.webcontainer.ContainerInstance;
049: import nextapp.echo2.webcontainer.DomUpdateSupport;
050: import nextapp.echo2.webcontainer.PartialUpdateManager;
051: import nextapp.echo2.webcontainer.PartialUpdateParticipant;
052: import nextapp.echo2.webcontainer.PropertyUpdateProcessor;
053: import nextapp.echo2.webcontainer.RenderContext;
054: import nextapp.echo2.webcontainer.ComponentSynchronizePeer;
055: import nextapp.echo2.webcontainer.SynchronizePeerFactory;
056: import nextapp.echo2.webcontainer.image.ImageRenderSupport;
057: import nextapp.echo2.webcontainer.propertyrender.ColorRender;
058: import nextapp.echo2.webcontainer.propertyrender.ExtentRender;
059: import nextapp.echo2.webcontainer.propertyrender.FillImageRender;
060: import nextapp.echo2.webcontainer.propertyrender.FontRender;
061: import nextapp.echo2.webrender.ServerMessage;
062: import nextapp.echo2.webrender.Service;
063: import nextapp.echo2.webrender.WebRenderServlet;
064: import nextapp.echo2.webrender.output.CssStyle;
065: import nextapp.echo2.webrender.servermessage.DomUpdate;
066: import nextapp.echo2.webrender.servermessage.VirtualPosition;
067: import nextapp.echo2.webrender.service.JavaScriptService;
068:
069: /**
070: * Synchronization peer for <code>nextapp.echo2.app.ContentPane</code> components.
071: * <p>
072: * This class should not be extended or used by classes outside of the
073: * Echo framework.
074: */
075: public class ContentPanePeer implements ComponentSynchronizePeer,
076: DomUpdateSupport, ImageRenderSupport, PropertyUpdateProcessor {
077:
078: //TODO: This needs to become a client-rendered component.
079: //TODO: Performance can be improved by implementing MORE PartialUpdateManagers.
080:
081: private static final Extent EXTENT_0 = new Extent(0);
082: private static final Insets DEFAULT_INSETS = new Insets(EXTENT_0);
083:
084: private static final String IMAGE_ID_BACKGROUND = "background";
085:
086: /**
087: * Service to provide supporting JavaScript library.
088: */
089: private static final Service CONTENT_PANE_SERVICE = JavaScriptService
090: .forResource("Echo.ContentPane",
091: "/nextapp/echo2/webcontainer/resource/js/ContentPane.js");
092:
093: static {
094: WebRenderServlet.getServiceRegistry().add(CONTENT_PANE_SERVICE);
095: }
096:
097: private PartialUpdateManager partialUpdateManager;
098:
099: /**
100: * Default constructor.
101: */
102: public ContentPanePeer() {
103: super ();
104: partialUpdateManager = new PartialUpdateManager();
105: partialUpdateManager.add(
106: ContentPane.PROPERTY_HORIZONTAL_SCROLL,
107: new PartialUpdateParticipant() {
108:
109: public void renderProperty(RenderContext rc,
110: ServerComponentUpdate update) {
111: renderScrollDirective(rc, (ContentPane) update
112: .getParent(), true);
113: }
114:
115: public boolean canRenderProperty(RenderContext rc,
116: ServerComponentUpdate update) {
117: return true;
118: }
119: });
120: partialUpdateManager.add(ContentPane.PROPERTY_VERTICAL_SCROLL,
121: new PartialUpdateParticipant() {
122:
123: public void renderProperty(RenderContext rc,
124: ServerComponentUpdate update) {
125: renderScrollDirective(rc, (ContentPane) update
126: .getParent(), false);
127: }
128:
129: public boolean canRenderProperty(RenderContext rc,
130: ServerComponentUpdate update) {
131: return true;
132: }
133: });
134: }
135:
136: /**
137: * @see nextapp.echo2.webcontainer.ComponentSynchronizePeer#getContainerId(nextapp.echo2.app.Component)
138: */
139: public String getContainerId(Component child) {
140: return getContainerId(child.getParent(), child);
141: }
142:
143: private String getContainerId(Component parent, Component child) {
144: if (child instanceof FloatingPane) {
145: return ContainerInstance.getElementId(parent) + "_float_"
146: + ContainerInstance.getElementId(child);
147: } else {
148: return ContainerInstance.getElementId(parent) + "_content_"
149: + ContainerInstance.getElementId(child);
150: }
151: }
152:
153: /**
154: * @see nextapp.echo2.webcontainer.image.ImageRenderSupport#getImage(nextapp.echo2.app.Component, java.lang.String)
155: */
156: public ImageReference getImage(Component component, String imageId) {
157: if (IMAGE_ID_BACKGROUND.equals(imageId)) {
158: FillImage backgroundImage = (FillImage) component
159: .getRenderProperty(AbstractButton.PROPERTY_BACKGROUND_IMAGE);
160: if (backgroundImage == null) {
161: return null;
162: } else {
163: return backgroundImage.getImage();
164: }
165: } else {
166: return null;
167: }
168: }
169:
170: /**
171: * @see nextapp.echo2.webcontainer.ComponentSynchronizePeer#renderAdd(nextapp.echo2.webcontainer.RenderContext,
172: * nextapp.echo2.app.update.ServerComponentUpdate, java.lang.String, nextapp.echo2.app.Component)
173: */
174: public void renderAdd(RenderContext rc,
175: ServerComponentUpdate update, String targetId,
176: Component component) {
177: Element domAddElement = DomUpdate.renderElementAdd(rc
178: .getServerMessage());
179: DocumentFragment htmlFragment = rc.getServerMessage()
180: .getDocument().createDocumentFragment();
181: renderHtml(rc, update, htmlFragment, component);
182: DomUpdate.renderElementAddContent(rc.getServerMessage(),
183: domAddElement, targetId, htmlFragment);
184: }
185:
186: /**
187: * Renders child components which were added to a
188: * <code>ContentPane</code>, as described in the provided
189: * <code>ServerComponentUpdate</code>.
190: *
191: * @param rc the relevant <code>RenderContext</code>
192: * @param update the update
193: */
194: private void renderAddChildren(RenderContext rc,
195: ServerComponentUpdate update) {
196: ContentPane contentPane = (ContentPane) update.getParent();
197: String elementId = ContainerInstance.getElementId(contentPane);
198: Component[] components = update.getParent()
199: .getVisibleComponents();
200: Component[] addedChildren = update.getAddedChildren();
201:
202: for (int componentIndex = components.length - 1; componentIndex >= 0; --componentIndex) {
203: boolean childFound = false;
204: for (int addedChildrenIndex = 0; !childFound
205: && addedChildrenIndex < addedChildren.length; ++addedChildrenIndex) {
206: if (addedChildren[addedChildrenIndex] == components[componentIndex]) {
207: Element domAddElement = DomUpdate
208: .renderElementAdd(rc.getServerMessage());
209: DocumentFragment htmlFragment = rc
210: .getServerMessage().getDocument()
211: .createDocumentFragment();
212: renderChild(rc, update, htmlFragment, contentPane,
213: components[componentIndex]);
214:
215: if (componentIndex == components.length - 1) {
216: DomUpdate.renderElementAddContent(rc
217: .getServerMessage(), domAddElement,
218: elementId, htmlFragment);
219: } else {
220: DomUpdate
221: .renderElementAddContent(
222: rc.getServerMessage(),
223: domAddElement,
224: elementId,
225: getContainerId(components[componentIndex + 1]),
226: htmlFragment);
227: }
228:
229: childFound = true;
230: }
231: }
232: }
233: }
234:
235: /**
236: * Renders an individual child component of the <code>ContentPane</code>.
237: *
238: * @param rc the relevant <code>RenderContext</code>
239: * @param update the <code>ServerComponentUpdate</code> being performed
240: * @param parentNode the outer <div> element of the
241: * <code>ContentPane</code>
242: * @param contentPane the containing <code>ContentPane</code>
243: * @param child the child <code>Component</code> to be rendered
244: */
245: private void renderChild(RenderContext rc,
246: ServerComponentUpdate update, Node parentNode,
247: ContentPane contentPane, Component child) {
248: Element containerDivElement = parentNode.getOwnerDocument()
249: .createElement("div");
250: String containerId = getContainerId(child);
251: containerDivElement.setAttribute("id", containerId);
252: if (!(child instanceof FloatingPane)) {
253: CssStyle style = new CssStyle();
254: style.setAttribute("position", "absolute");
255: style.setAttribute("overflow", "auto");
256: style.setAttribute("z-index", "0");
257: Insets insets = (Insets) contentPane.getRenderProperty(
258: ContentPane.PROPERTY_INSETS, DEFAULT_INSETS);
259: style
260: .setAttribute("top", ExtentRender
261: .renderCssAttributePixelValue(insets
262: .getTop(), "0"));
263: style.setAttribute("left",
264: ExtentRender.renderCssAttributePixelValue(insets
265: .getLeft(), "0"));
266: style.setAttribute("right", ExtentRender
267: .renderCssAttributePixelValue(insets.getRight(),
268: "0"));
269: style.setAttribute("bottom", ExtentRender
270: .renderCssAttributePixelValue(insets.getBottom(),
271: "0"));
272: containerDivElement.setAttribute("style", style
273: .renderInline());
274: VirtualPosition.renderRegister(rc.getServerMessage(),
275: containerId);
276: }
277:
278: parentNode.appendChild(containerDivElement);
279: ComponentSynchronizePeer syncPeer = SynchronizePeerFactory
280: .getPeerForComponent(child.getClass());
281: if (syncPeer instanceof DomUpdateSupport) {
282: ((DomUpdateSupport) syncPeer).renderHtml(rc, update,
283: containerDivElement, child);
284: } else {
285: syncPeer.renderAdd(rc, update, containerId, child);
286: }
287: }
288:
289: /**
290: * @see nextapp.echo2.webcontainer.ComponentSynchronizePeer#renderDispose(nextapp.echo2.webcontainer.RenderContext,
291: * nextapp.echo2.app.update.ServerComponentUpdate, nextapp.echo2.app.Component)
292: */
293: public void renderDispose(RenderContext rc,
294: ServerComponentUpdate update, Component component) {
295: rc.getServerMessage().addLibrary(CONTENT_PANE_SERVICE.getId());
296: renderDisposeDirective(rc, (ContentPane) component);
297: }
298:
299: /**
300: * Renders a directive to the outgoing <code>ServerMessage</code> to
301: * dispose the state of a <code>ContentPane</code>, performing tasks such as
302: * unregistering event listeners on the client.
303: *
304: * @param rc the relevant <code>RenderContext</code>
305: * @param contentPane the <code>ContentPane</code>
306: */
307: private void renderDisposeDirective(RenderContext rc,
308: ContentPane contentPane) {
309: ServerMessage serverMessage = rc.getServerMessage();
310: Element itemizedUpdateElement = serverMessage
311: .getItemizedDirective(ServerMessage.GROUP_ID_PREREMOVE,
312: "EchoContentPane.MessageProcessor", "dispose",
313: new String[0], new String[0]);
314: Element itemElement = serverMessage.getDocument()
315: .createElement("item");
316: itemElement.setAttribute("eid", ContainerInstance
317: .getElementId(contentPane));
318: itemizedUpdateElement.appendChild(itemElement);
319: }
320:
321: /**
322: * @see nextapp.echo2.webcontainer.DomUpdateSupport#renderHtml(nextapp.echo2.webcontainer.RenderContext,
323: * nextapp.echo2.app.update.ServerComponentUpdate, org.w3c.dom.Node, nextapp.echo2.app.Component)
324: */
325: public void renderHtml(RenderContext rc,
326: ServerComponentUpdate update, Node parentNode,
327: Component component) {
328: ContentPane contentPane = (ContentPane) component;
329:
330: ServerMessage serverMessage = rc.getServerMessage();
331: serverMessage.addLibrary(CONTENT_PANE_SERVICE.getId());
332:
333: Document document = parentNode.getOwnerDocument();
334: Element divElement = document.createElement("div");
335: divElement.setAttribute("id", ContainerInstance
336: .getElementId(component));
337:
338: CssStyle cssStyle = new CssStyle();
339: cssStyle.setAttribute("position", "absolute");
340: cssStyle.setAttribute("width", "100%");
341: cssStyle.setAttribute("height", "100%");
342: cssStyle.setAttribute("overflow", "hidden");
343: cssStyle.setAttribute("z-index", "0");
344: ColorRender
345: .renderToStyle(
346: cssStyle,
347: (Color) contentPane
348: .getRenderProperty(ContentPane.PROPERTY_FOREGROUND),
349: (Color) contentPane
350: .getRenderProperty(ContentPane.PROPERTY_BACKGROUND));
351: FontRender.renderToStyle(cssStyle, (Font) contentPane
352: .getRenderProperty(ContentPane.PROPERTY_FONT));
353: FillImageRender
354: .renderToStyle(
355: cssStyle,
356: rc,
357: this ,
358: contentPane,
359: IMAGE_ID_BACKGROUND,
360: (FillImage) contentPane
361: .getRenderProperty(ContentPane.PROPERTY_BACKGROUND_IMAGE),
362: 0);
363: divElement.setAttribute("style", cssStyle.renderInline());
364:
365: parentNode.appendChild(divElement);
366:
367: // Render initialization directive.
368: renderInitDirective(rc, contentPane);
369:
370: Component[] children = contentPane.getVisibleComponents();
371: for (int i = 0; i < children.length; ++i) {
372: renderChild(rc, update, divElement, contentPane,
373: children[i]);
374: }
375: }
376:
377: /**
378: * Renders a directive to the outgoing <code>ServerMessage</code> to
379: * initialize the state of a <code>ContentPane</code>, performing tasks
380: * such as registering event listeners on the client.
381: *
382: * @param rc the relevant <code>RenderContext</code>
383: * @param contentPane the <code>ContentPane</code>
384: */
385: private void renderInitDirective(RenderContext rc,
386: ContentPane contentPane) {
387: String elementId = ContainerInstance.getElementId(contentPane);
388: ServerMessage serverMessage = rc.getServerMessage();
389:
390: Element itemizedUpdateElement = serverMessage
391: .getItemizedDirective(
392: ServerMessage.GROUP_ID_POSTUPDATE,
393: "EchoContentPane.MessageProcessor", "init",
394: new String[0], new String[0]);
395: Element itemElement = serverMessage.getDocument()
396: .createElement("item");
397: itemElement.setAttribute("eid", elementId);
398: Extent horizontalScroll = (Extent) contentPane
399: .getRenderProperty(ContentPane.PROPERTY_HORIZONTAL_SCROLL);
400: if (horizontalScroll != null
401: && horizontalScroll.getValue() != 0) {
402: itemElement.setAttribute("horizontal-scroll", ExtentRender
403: .renderCssAttributeValue(horizontalScroll));
404: }
405: Extent verticalScroll = (Extent) contentPane
406: .getRenderProperty(ContentPane.PROPERTY_VERTICAL_SCROLL);
407: if (verticalScroll != null && verticalScroll.getValue() != 0) {
408: itemElement.setAttribute("vertical-scroll", ExtentRender
409: .renderCssAttributeValue(verticalScroll));
410: }
411: itemizedUpdateElement.appendChild(itemElement);
412: }
413:
414: /**
415: * Renders a directive to the outgoing <code>ServerMessage</code> to update
416: * the scroll bar positions of a <code>ContentPane</code>.
417: *
418: * @param rc the relevant <code>RenderContext</code>
419: * @param contentPane the <code>ContentPane</code>
420: * @param horizontal a flag indicating whether the horizontal (true) or
421: * vertical (false) scroll bar position should be updated
422: */
423: private void renderScrollDirective(RenderContext rc,
424: ContentPane contentPane, boolean horizontal) {
425: ServerMessage serverMessage = rc.getServerMessage();
426: Element scrollElement = serverMessage.appendPartDirective(
427: ServerMessage.GROUP_ID_POSTUPDATE,
428: "EchoContentPane.MessageProcessor",
429: horizontal ? "scroll-horizontal" : "scroll-vertical");
430: Extent position = (Extent) contentPane.getRenderProperty(
431: horizontal ? ContentPane.PROPERTY_HORIZONTAL_SCROLL
432: : ContentPane.PROPERTY_VERTICAL_SCROLL,
433: EXTENT_0);
434: scrollElement.setAttribute("eid", ContainerInstance
435: .getElementId(contentPane));
436: scrollElement.setAttribute("position", ExtentRender
437: .renderCssAttributeValue(position));
438: }
439:
440: /**
441: * Renders removal operations for child components which were removed from
442: * a <code>ContentPane</code>, as described in the provided
443: * <code>ServerComponentUpdate</code>.
444: *
445: * @param rc the relevant <code>RenderContext</code>
446: * @param update the update
447: */
448: private void renderRemoveChildren(RenderContext rc,
449: ServerComponentUpdate update) {
450: Component[] removedChildren = update.getRemovedChildren();
451: for (int i = 0; i < removedChildren.length; ++i) {
452: DomUpdate.renderElementRemove(rc.getServerMessage(),
453: getContainerId(update.getParent(),
454: removedChildren[i]));
455: }
456: }
457:
458: /**
459: * @see nextapp.echo2.webcontainer.ComponentSynchronizePeer#renderUpdate(nextapp.echo2.webcontainer.RenderContext,
460: * nextapp.echo2.app.update.ServerComponentUpdate, java.lang.String)
461: */
462: public boolean renderUpdate(RenderContext rc,
463: ServerComponentUpdate update, String targetId) {
464: boolean fullReplace = false;
465: if (update.hasUpdatedLayoutDataChildren()) {
466: fullReplace = true;
467: } else if (update.hasUpdatedProperties()) {
468: if (!partialUpdateManager.canProcess(rc, update)) {
469: fullReplace = true;
470: }
471: }
472:
473: if (fullReplace) {
474: // Perform full update.
475: DomUpdate.renderElementRemove(rc.getServerMessage(),
476: ContainerInstance.getElementId(update.getParent()));
477: renderAdd(rc, update, targetId, update.getParent());
478: } else {
479: partialUpdateManager.process(rc, update);
480: if (update.hasAddedChildren()
481: || update.hasRemovedChildren()) {
482: renderContentChange(rc, update);
483: }
484: }
485: return fullReplace;
486: }
487:
488: /**
489: * Processes a change to the content of a <code>ContentPane</code>.
490: * This method will invoke <code>renderRemoveChildren()</code> and
491: * <code>renderAddChildren()</cdoe>. If the main content has changed,
492: * i.e., the non-<code>FloatingPane</code> child, it will unregister
493: * and re-register scrolling listeners by rendering dispose and then
494: * init directives.
495: *
496: * @param rc the relevant <code>RenderContext</code>
497: * @param update the update
498: */
499: private void renderContentChange(RenderContext rc,
500: ServerComponentUpdate update) {
501: boolean primaryContentChanged = false;
502: Component[] addedChildren = update.getAddedChildren();
503: for (int i = 0; i < addedChildren.length; ++i) {
504: if (!(addedChildren[i] instanceof FloatingPane)) {
505: primaryContentChanged = true;
506: break;
507: }
508: }
509: if (!primaryContentChanged) {
510: Component[] removedChildren = update.getRemovedChildren();
511: for (int i = 0; i < removedChildren.length; ++i) {
512: if (!(removedChildren[i] instanceof FloatingPane)) {
513: primaryContentChanged = true;
514: break;
515: }
516: }
517: }
518:
519: if (primaryContentChanged) {
520: renderDisposeDirective(rc, (ContentPane) update.getParent());
521: }
522:
523: renderRemoveChildren(rc, update);
524: renderAddChildren(rc, update);
525:
526: if (primaryContentChanged) {
527: renderInitDirective(rc, (ContentPane) update.getParent());
528: }
529: }
530:
531: /**
532: * @see nextapp.echo2.webcontainer.PropertyUpdateProcessor#processPropertyUpdate(
533: * nextapp.echo2.webcontainer.ContainerInstance,
534: * nextapp.echo2.app.Component, org.w3c.dom.Element)
535: */
536: public void processPropertyUpdate(ContainerInstance ci,
537: Component component, Element propertyElement) {
538: if ("horizontalScroll".equals(propertyElement
539: .getAttribute(PropertyUpdateProcessor.PROPERTY_NAME))) {
540: Extent newValue = ExtentRender
541: .toExtent(propertyElement
542: .getAttribute(PropertyUpdateProcessor.PROPERTY_VALUE));
543: ci.getUpdateManager().getClientUpdateManager()
544: .setComponentProperty(component,
545: ContentPane.PROPERTY_HORIZONTAL_SCROLL,
546: newValue);
547: } else if ("verticalScroll".equals(propertyElement
548: .getAttribute(PropertyUpdateProcessor.PROPERTY_NAME))) {
549: Extent newValue = ExtentRender
550: .toExtent(propertyElement
551: .getAttribute(PropertyUpdateProcessor.PROPERTY_VALUE));
552: ci.getUpdateManager().getClientUpdateManager()
553: .setComponentProperty(component,
554: ContentPane.PROPERTY_VERTICAL_SCROLL,
555: newValue);
556: }
557: }
558: }
|