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.Alignment;
038: import nextapp.echo2.app.Border;
039: import nextapp.echo2.app.Color;
040: import nextapp.echo2.app.Component;
041: import nextapp.echo2.app.Extent;
042: import nextapp.echo2.app.Font;
043: import nextapp.echo2.app.ImageReference;
044: import nextapp.echo2.app.Insets;
045: import nextapp.echo2.app.Row;
046: import nextapp.echo2.app.LayoutData;
047: import nextapp.echo2.app.layout.RowLayoutData;
048: import nextapp.echo2.app.update.ServerComponentUpdate;
049: import nextapp.echo2.webcontainer.ContainerInstance;
050: import nextapp.echo2.webcontainer.DomUpdateSupport;
051: import nextapp.echo2.webcontainer.PartialUpdateManager;
052: import nextapp.echo2.webcontainer.RenderContext;
053: import nextapp.echo2.webcontainer.RenderState;
054: import nextapp.echo2.webcontainer.ComponentSynchronizePeer;
055: import nextapp.echo2.webcontainer.SynchronizePeerFactory;
056: import nextapp.echo2.webcontainer.image.ImageRenderSupport;
057: import nextapp.echo2.webcontainer.partialupdate.BorderUpdate;
058: import nextapp.echo2.webcontainer.partialupdate.ColorUpdate;
059: import nextapp.echo2.webcontainer.partialupdate.InsetsUpdate;
060: import nextapp.echo2.webcontainer.propertyrender.AlignmentRender;
061: import nextapp.echo2.webcontainer.propertyrender.BorderRender;
062: import nextapp.echo2.webcontainer.propertyrender.CellLayoutDataRender;
063: import nextapp.echo2.webcontainer.propertyrender.ColorRender;
064: import nextapp.echo2.webcontainer.propertyrender.ExtentRender;
065: import nextapp.echo2.webcontainer.propertyrender.FontRender;
066: import nextapp.echo2.webcontainer.propertyrender.InsetsRender;
067: import nextapp.echo2.webrender.output.CssStyle;
068: import nextapp.echo2.webrender.servermessage.DomUpdate;
069:
070: /**
071: * Synchronization peer for <code>nextapp.echo2.app.Row</code> components.
072: * <p>
073: * This class should not be extended or used by classes outside of the
074: * Echo framework.
075: */
076: public class RowPeer implements ComponentSynchronizePeer,
077: DomUpdateSupport, ImageRenderSupport {
078:
079: /**
080: * <code>RenderState</code> implementation.
081: */
082: private static class RowPeerRenderState implements RenderState {
083:
084: /**
085: * The child <code>Component</code> which had the highest index during
086: * the last rendering. This information is necessary when rendering
087: * cell spacing, as the last component will not have a "spacing" row
088: * beneath it. Thus, if it is no longer the last component due to an
089: * add, one will need to be added beneath it.
090: */
091: public Component lastChild;
092: }
093:
094: private PartialUpdateManager partialUpdateManager;
095:
096: /**
097: * Default constructor.
098: */
099: public RowPeer() {
100: partialUpdateManager = new PartialUpdateManager();
101: partialUpdateManager.add(Row.PROPERTY_BORDER, new BorderUpdate(
102: Row.PROPERTY_BORDER, null, BorderUpdate.CSS_BORDER));
103: partialUpdateManager.add(Row.PROPERTY_FOREGROUND,
104: new ColorUpdate(Row.PROPERTY_FOREGROUND, null,
105: ColorUpdate.CSS_COLOR));
106: partialUpdateManager.add(Row.PROPERTY_BACKGROUND,
107: new ColorUpdate(Row.PROPERTY_BACKGROUND, null,
108: ColorUpdate.CSS_BACKGROUND_COLOR));
109: partialUpdateManager.add(Row.PROPERTY_INSETS, new InsetsUpdate(
110: Row.PROPERTY_INSETS, null, InsetsUpdate.CSS_PADDING));
111: }
112:
113: /**
114: * @see nextapp.echo2.webcontainer.ComponentSynchronizePeer#getContainerId(nextapp.echo2.app.Component)
115: */
116: public String getContainerId(Component child) {
117: return ContainerInstance.getElementId(child.getParent())
118: + "_cell_" + ContainerInstance.getElementId(child);
119: }
120:
121: /**
122: * @see nextapp.echo2.webcontainer.image.ImageRenderSupport#getImage(nextapp.echo2.app.Component, java.lang.String)
123: */
124: public ImageReference getImage(Component component, String imageId) {
125: // Only source of ImageReferences from this component are CellLayoutData background images:
126: // Delegate work to CellLayoutDataRender convenience method.
127: return CellLayoutDataRender.getCellLayoutDataBackgroundImage(
128: component, imageId);
129: }
130:
131: /**
132: * Returns the <code>RowLayoutData</code> of the given child, or null if
133: * it does not provide layout data.
134: *
135: * @param child the child component
136: * @return the layout data
137: * @throws java.lang.RuntimeException if the the provided
138: * <code>LayoutData</code> is not a <code>RowLayoutData</code>
139: */
140: private RowLayoutData getLayoutData(Component child) {
141: LayoutData layoutData = (LayoutData) child
142: .getRenderProperty(Component.PROPERTY_LAYOUT_DATA);
143: if (layoutData == null) {
144: return null;
145: } else if (layoutData instanceof RowLayoutData) {
146: return (RowLayoutData) layoutData;
147: } else {
148: throw new RuntimeException(
149: "Invalid LayoutData for Row Child: "
150: + layoutData.getClass().getName());
151: }
152: }
153:
154: /**
155: * @see nextapp.echo2.webcontainer.ComponentSynchronizePeer#renderAdd(nextapp.echo2.webcontainer.RenderContext,
156: * nextapp.echo2.app.update.ServerComponentUpdate, java.lang.String, nextapp.echo2.app.Component)
157: */
158: public void renderAdd(RenderContext rc,
159: ServerComponentUpdate update, String targetId,
160: Component component) {
161: Element domAddElement = DomUpdate.renderElementAdd(rc
162: .getServerMessage());
163: DocumentFragment htmlFragment = rc.getServerMessage()
164: .getDocument().createDocumentFragment();
165: renderHtml(rc, update, htmlFragment, component);
166: DomUpdate.renderElementAddContent(rc.getServerMessage(),
167: domAddElement, targetId, htmlFragment);
168: }
169:
170: /**
171: * Renders a child component.
172: *
173: * @param rc the relevant <code>RenderContext</code>
174: * @param update the update
175: * @param parentElement the HTML element which should contain the child
176: * @param child the child component to render
177: */
178: private void renderAddChild(RenderContext rc,
179: ServerComponentUpdate update, Element parentElement,
180: Component child) {
181: ComponentSynchronizePeer syncPeer = SynchronizePeerFactory
182: .getPeerForComponent(child.getClass());
183: if (syncPeer instanceof DomUpdateSupport) {
184: ((DomUpdateSupport) syncPeer).renderHtml(rc, update,
185: parentElement, child);
186: } else {
187: syncPeer
188: .renderAdd(rc, update, getContainerId(child), child);
189: }
190: }
191:
192: /**
193: * Renders child components which were added to a <code>Row</code>, as
194: * described in the provided <code>ServerComponentUpdate</code>.
195: *
196: * @param rc the relevant <code>RenderContext</code>
197: * @param update the update
198: */
199: private void renderAddChildren(RenderContext rc,
200: ServerComponentUpdate update) {
201: Element domAddElement = DomUpdate.renderElementAdd(rc
202: .getServerMessage());
203: Row row = (Row) update.getParent();
204: String elementId = ContainerInstance.getElementId(row);
205: String trElementId = elementId + "_tr";
206:
207: Component[] components = update.getParent()
208: .getVisibleComponents();
209: Component[] addedChildren = update.getAddedChildren();
210:
211: for (int componentIndex = components.length - 1; componentIndex >= 0; --componentIndex) {
212: boolean childFound = false;
213: for (int addedChildrenIndex = 0; !childFound
214: && addedChildrenIndex < addedChildren.length; ++addedChildrenIndex) {
215: if (addedChildren[addedChildrenIndex] == components[componentIndex]) {
216: DocumentFragment htmlFragment = rc
217: .getServerMessage().getDocument()
218: .createDocumentFragment();
219: renderChild(rc, update, htmlFragment, row,
220: components[componentIndex]);
221: if (componentIndex == components.length - 1) {
222: DomUpdate.renderElementAddContent(rc
223: .getServerMessage(), domAddElement,
224: trElementId, htmlFragment);
225: } else {
226: DomUpdate
227: .renderElementAddContent(
228: rc.getServerMessage(),
229: domAddElement,
230: trElementId,
231: elementId
232: + "_cell_"
233: + ContainerInstance
234: .getElementId(components[componentIndex + 1]),
235: htmlFragment);
236: }
237: childFound = true;
238: }
239: }
240: }
241:
242: // Special case: Recall the child which was rendered at the last index of the row on the previous
243: // rendering. If this child is still present but is no longer at the last index, render a spacing
244: // cell after it (if necessary).
245: RowPeerRenderState renderState = (RowPeerRenderState) rc
246: .getContainerInstance().getRenderState(row);
247: if (renderState != null && renderState.lastChild != null) {
248: int previousLastChildIndex = row
249: .visibleIndexOf(renderState.lastChild);
250: if (previousLastChildIndex != -1
251: && previousLastChildIndex != row
252: .getVisibleComponentCount() - 1) {
253: // At this point it is known that the child which was previously last is present, but is no longer last.
254:
255: // In the event the child was removed and re-added, the special case is unnecessary.
256: boolean lastChildMoved = false;
257: for (int i = 0; i < addedChildren.length; ++i) {
258: if (renderState.lastChild == addedChildren[i]) {
259: lastChildMoved = true;
260: }
261: }
262: if (!lastChildMoved) {
263: DocumentFragment htmlFragment = rc
264: .getServerMessage().getDocument()
265: .createDocumentFragment();
266: renderSpacingCell(htmlFragment, row,
267: renderState.lastChild);
268: DomUpdate
269: .renderElementAddContent(
270: rc.getServerMessage(),
271: domAddElement,
272: trElementId,
273: elementId
274: + "_cell_"
275: + ContainerInstance
276: .getElementId(components[previousLastChildIndex + 1]),
277: htmlFragment);
278: }
279: }
280: }
281: }
282:
283: /**
284: * Renders an individual child component of the <code>Row</code>.
285: *
286: * @param rc the relevant <code>RenderContext</code>
287: * @param update the <code>ServerComponentUpdate</code> being performed
288: * @param parentNode the containing node to which the child should be
289: * appended
290: * @param child The child <code>Component</code> to be rendered
291: */
292: private void renderChild(RenderContext rc,
293: ServerComponentUpdate update, Node parentNode,
294: Component component, Component child) {
295: Document document = parentNode.getOwnerDocument();
296: String childId = ContainerInstance.getElementId(child);
297: Element tdElement = document.createElement("td");
298: String cellId = ContainerInstance.getElementId(component)
299: + "_cell_" + childId;
300: tdElement.setAttribute("id", cellId);
301:
302: // Configure cell style.
303: CssStyle cssStyle = new CssStyle();
304: RowLayoutData layoutData = getLayoutData(child);
305: CellLayoutDataRender.renderToElementAndStyle(tdElement,
306: cssStyle, child, layoutData, "0px");
307: CellLayoutDataRender.renderBackgroundImageToStyle(cssStyle, rc,
308: this , component, child);
309: if (layoutData != null) {
310: ExtentRender.renderToStyle(cssStyle, "width", layoutData
311: .getWidth());
312: }
313: tdElement.setAttribute("style", cssStyle.renderInline());
314:
315: parentNode.appendChild(tdElement);
316:
317: renderSpacingCell(parentNode, (Row) component, child);
318:
319: renderAddChild(rc, update, tdElement, child);
320: }
321:
322: /**
323: * @see nextapp.echo2.webcontainer.ComponentSynchronizePeer#renderDispose(nextapp.echo2.webcontainer.RenderContext,
324: * nextapp.echo2.app.update.ServerComponentUpdate, nextapp.echo2.app.Component)
325: */
326: public void renderDispose(RenderContext rc,
327: ServerComponentUpdate update, Component component) {
328: }
329:
330: /**
331: * @see nextapp.echo2.webcontainer.DomUpdateSupport#renderHtml(nextapp.echo2.webcontainer.RenderContext,
332: * nextapp.echo2.app.update.ServerComponentUpdate, org.w3c.dom.Node, nextapp.echo2.app.Component)
333: */
334: public void renderHtml(RenderContext rc,
335: ServerComponentUpdate update, Node parentNode,
336: Component component) {
337: Row row = (Row) component;
338: Border border = (Border) row
339: .getRenderProperty(Row.PROPERTY_BORDER);
340: String elementId = ContainerInstance.getElementId(row);
341: Document document = parentNode.getOwnerDocument();
342:
343: Element divElement = document.createElement("div");
344: divElement.setAttribute("id", elementId);
345: parentNode.appendChild(divElement);
346:
347: CssStyle divCssStyle = new CssStyle();
348: BorderRender.renderToStyle(divCssStyle, border);
349: ColorRender.renderToStyle(divCssStyle, (Color) row
350: .getRenderProperty(Row.PROPERTY_FOREGROUND),
351: (Color) row.getRenderProperty(Row.PROPERTY_BACKGROUND));
352: FontRender.renderToStyle(divCssStyle, (Font) row
353: .getRenderProperty(Row.PROPERTY_FONT));
354: Insets insets = (Insets) row
355: .getRenderProperty(Row.PROPERTY_INSETS);
356: if (insets == null) {
357: divCssStyle.setAttribute("padding", "0px");
358: } else {
359: InsetsRender.renderToStyle(divCssStyle, "padding", insets);
360: }
361: divElement.setAttribute("style", divCssStyle.renderInline());
362:
363: Element tableElement = document.createElement("table");
364: tableElement.setAttribute("id", elementId + "_table");
365: tableElement.setAttribute("style",
366: "padding:0px;border-collapse:collapse;");
367:
368: AlignmentRender.renderToElement(divElement, ((Alignment) row
369: .getRenderProperty(Row.PROPERTY_ALIGNMENT)), row);
370:
371: divElement.appendChild(tableElement);
372:
373: Element tbodyElement = document.createElement("tbody");
374: tbodyElement.setAttribute("id", elementId + "_tbody");
375: tableElement.appendChild(tbodyElement);
376:
377: Element trElement = document.createElement("tr");
378: trElement.setAttribute("id", elementId + "_tr");
379: tbodyElement.appendChild(trElement);
380:
381: Component[] children = row.getVisibleComponents();
382: for (int i = 0; i < children.length; ++i) {
383: renderChild(rc, update, trElement, component, children[i]);
384: }
385:
386: storeRenderState(rc, row);
387: }
388:
389: /**
390: * Renders removal operations for child components which were removed from
391: * a <code>Row</code>, as described in the provided
392: * <code>ServerComponentUpdate</code>.
393: *
394: * @param rc the relevant <code>RenderContext</code>
395: * @param update the update
396: */
397: private void renderRemoveChildren(RenderContext rc,
398: ServerComponentUpdate update) {
399: Component[] removedChildren = update.getRemovedChildren();
400: Component parent = update.getParent();
401: String parentId = ContainerInstance.getElementId(parent);
402: for (int i = 0; i < removedChildren.length; ++i) {
403: String childId = ContainerInstance
404: .getElementId(removedChildren[i]);
405: DomUpdate.renderElementRemove(rc.getServerMessage(),
406: parentId + "_cell_" + childId);
407: DomUpdate.renderElementRemove(rc.getServerMessage(),
408: parentId + "_spacing_" + childId);
409: }
410:
411: int componentCount = parent.getVisibleComponentCount();
412: if (componentCount > 0) {
413: DomUpdate
414: .renderElementRemove(
415: rc.getServerMessage(),
416: parentId
417: + "_spacing_"
418: + ContainerInstance
419: .getElementId(parent
420: .getVisibleComponent(componentCount - 1)));
421: }
422: }
423:
424: /**
425: * Renders a "spacing cell" beneath a row cell to provide
426: * cell spacing.
427: *
428: * @param parentNode the containing node to which the child
429: * should be appended
430: * @param row the <code>Row</code> being updated
431: * @param child the child preceeding the spacing row
432: */
433: private void renderSpacingCell(Node parentNode, Row row,
434: Component child) {
435: Extent cellSpacing = (Extent) row
436: .getRenderProperty(Row.PROPERTY_CELL_SPACING);
437: if (!ExtentRender.isZeroLength(cellSpacing)
438: && row.visibleIndexOf(child) != row
439: .getVisibleComponentCount() - 1) {
440: Element spacingElement = parentNode.getOwnerDocument()
441: .createElement("td");
442: spacingElement.setAttribute("id", ContainerInstance
443: .getElementId(row)
444: + "_spacing_"
445: + ContainerInstance.getElementId(child));
446: CssStyle spacingCssStyle = new CssStyle();
447: spacingCssStyle.setAttribute("width", ExtentRender
448: .renderCssAttributeValue(cellSpacing));
449: spacingCssStyle.setAttribute("font-size", "1px");
450: spacingCssStyle.setAttribute("line-height", "0px");
451: spacingElement.setAttribute("style", spacingCssStyle
452: .renderInline());
453: parentNode.appendChild(spacingElement);
454: }
455: }
456:
457: /**
458: * @see nextapp.echo2.webcontainer.ComponentSynchronizePeer#renderUpdate(nextapp.echo2.webcontainer.RenderContext,
459: * nextapp.echo2.app.update.ServerComponentUpdate, java.lang.String)
460: */
461: public boolean renderUpdate(RenderContext rc,
462: ServerComponentUpdate update, String targetId) {
463: // Determine if fully replacing the component is required.
464: boolean fullReplace = false;
465: if (update.hasUpdatedLayoutDataChildren()) {
466: // TODO: Perform fractional update on LayoutData change instead of full replace.
467: fullReplace = true;
468: } else if (update.hasUpdatedProperties()) {
469: if (!partialUpdateManager.canProcess(rc, update)) {
470: fullReplace = true;
471: }
472: }
473:
474: if (fullReplace) {
475: // Perform full update.
476: DomUpdate.renderElementRemove(rc.getServerMessage(),
477: ContainerInstance.getElementId(update.getParent()));
478: renderAdd(rc, update, targetId, update.getParent());
479: } else {
480: // Perform incremental updates.
481: if (update.hasRemovedChildren()) {
482: renderRemoveChildren(rc, update);
483: }
484: if (update.hasUpdatedProperties()) {
485: partialUpdateManager.process(rc, update);
486: }
487: if (update.hasAddedChildren()) {
488: renderAddChildren(rc, update);
489: }
490: }
491:
492: storeRenderState(rc, update.getParent());
493: return fullReplace;
494: }
495:
496: /**
497: * Update the stored <code>RenderState</code>.
498: *
499: * @param rc the relevant <code>RenderContext</code>
500: * @param component the <code>Row</code> component
501: */
502: private void storeRenderState(RenderContext rc, Component component) {
503: int componentCount = component.getVisibleComponentCount();
504: RowPeerRenderState renderState = new RowPeerRenderState();
505: if (componentCount > 0) {
506: renderState.lastChild = component
507: .getVisibleComponent(componentCount - 1);
508: }
509: rc.getContainerInstance()
510: .setRenderState(component, renderState);
511: }
512: }
|