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 java.util.HashSet;
033: import java.util.Set;
034:
035: import org.w3c.dom.Document;
036: import org.w3c.dom.DocumentFragment;
037: import org.w3c.dom.Element;
038: import org.w3c.dom.Node;
039:
040: import nextapp.echo2.app.Border;
041: import nextapp.echo2.app.Component;
042: import nextapp.echo2.app.Extent;
043: import nextapp.echo2.app.Grid;
044: import nextapp.echo2.app.ImageReference;
045: import nextapp.echo2.app.Insets;
046: import nextapp.echo2.app.LayoutData;
047: import nextapp.echo2.app.layout.GridLayoutData;
048: import nextapp.echo2.app.update.ServerComponentUpdate;
049: import nextapp.echo2.webcontainer.ContainerInstance;
050: import nextapp.echo2.webcontainer.DomUpdateSupport;
051: import nextapp.echo2.webcontainer.RenderContext;
052: import nextapp.echo2.webcontainer.ComponentSynchronizePeer;
053: import nextapp.echo2.webcontainer.SynchronizePeerFactory;
054: import nextapp.echo2.webcontainer.image.ImageRenderSupport;
055: import nextapp.echo2.webcontainer.propertyrender.BorderRender;
056: import nextapp.echo2.webcontainer.propertyrender.CellLayoutDataRender;
057: import nextapp.echo2.webcontainer.propertyrender.ColorRender;
058: import nextapp.echo2.webcontainer.propertyrender.ExtentRender;
059: import nextapp.echo2.webcontainer.propertyrender.FontRender;
060: import nextapp.echo2.webcontainer.propertyrender.InsetsRender;
061: import nextapp.echo2.webrender.ClientProperties;
062: import nextapp.echo2.webrender.output.CssStyle;
063: import nextapp.echo2.webrender.servermessage.DomUpdate;
064:
065: /**
066: * Synchronization peer for <code>nextapp.echo2.app.Grid</code> components.
067: * <p>
068: * This class should not be extended or used by classes outside of the
069: * Echo framework.
070: */
071: public class GridPeer implements ComponentSynchronizePeer,
072: DomUpdateSupport, ImageRenderSupport {
073:
074: /**
075: * A string of periods used for the IE 100% Table Width workaround.
076: */
077: private static final String SIZING_DOTS = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
078: + ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ";
079:
080: /**
081: * @see nextapp.echo2.webcontainer.ComponentSynchronizePeer#getContainerId(nextapp.echo2.app.Component)
082: */
083: public String getContainerId(Component child) {
084: return ContainerInstance.getElementId(child.getParent())
085: + "_td_" + ContainerInstance.getElementId(child);
086: }
087:
088: /**
089: * @see nextapp.echo2.webcontainer.image.ImageRenderSupport#getImage(nextapp.echo2.app.Component, java.lang.String)
090: */
091: public ImageReference getImage(Component component, String imageId) {
092: // Only source of ImageReferences from this component are CellLayoutData background images:
093: // Delegate work to CellLayoutDataRender convenience method.
094: return CellLayoutDataRender.getCellLayoutDataBackgroundImage(
095: component, imageId);
096: }
097:
098: /**
099: * Returns the <code>GridLayoutData</code> of the given child,
100: * or null if it does not provide layout data.
101: *
102: * @param child the child component
103: * @return the layout data
104: * @throws java.lang.RuntimeException if the the provided
105: * <code>LayoutData</code> is not a <code>GridLayoutData</code>
106: */
107: private GridLayoutData getLayoutData(Component child) {
108: LayoutData layoutData = (LayoutData) child
109: .getRenderProperty(Component.PROPERTY_LAYOUT_DATA);
110: if (layoutData == null) {
111: return null;
112: } else if (layoutData instanceof GridLayoutData) {
113: return (GridLayoutData) layoutData;
114: } else {
115: throw new RuntimeException(
116: "Invalid LayoutData for Grid Child: "
117: + layoutData.getClass().getName());
118: }
119: }
120:
121: /**
122: * @see nextapp.echo2.webcontainer.ComponentSynchronizePeer#renderAdd(nextapp.echo2.webcontainer.RenderContext,
123: * nextapp.echo2.app.update.ServerComponentUpdate, java.lang.String, nextapp.echo2.app.Component)
124: */
125: public void renderAdd(RenderContext rc,
126: ServerComponentUpdate update, String targetId,
127: Component component) {
128: Element domAddElement = DomUpdate.renderElementAdd(rc
129: .getServerMessage());
130: DocumentFragment htmlFragment = rc.getServerMessage()
131: .getDocument().createDocumentFragment();
132: renderHtml(rc, update, htmlFragment, component);
133: DomUpdate.renderElementAddContent(rc.getServerMessage(),
134: domAddElement, targetId, htmlFragment);
135: }
136:
137: /**
138: * Renders a child component.
139: *
140: * @param rc the relevant <code>RenderContext</code>
141: * @param update the update
142: * @param parentElement the HTML element which should contain the child
143: * @param child the child component to render
144: */
145: private void renderAddChild(RenderContext rc,
146: ServerComponentUpdate update, Element parentElement,
147: Component child) {
148: ComponentSynchronizePeer syncPeer = SynchronizePeerFactory
149: .getPeerForComponent(child.getClass());
150: if (syncPeer instanceof DomUpdateSupport) {
151: ((DomUpdateSupport) syncPeer).renderHtml(rc, update,
152: parentElement, child);
153: } else {
154: syncPeer
155: .renderAdd(rc, update, getContainerId(child), child);
156: }
157: }
158:
159: /**
160: * @see nextapp.echo2.webcontainer.ComponentSynchronizePeer#renderDispose(nextapp.echo2.webcontainer.RenderContext,
161: * nextapp.echo2.app.update.ServerComponentUpdate, nextapp.echo2.app.Component)
162: */
163: public void renderDispose(RenderContext rc,
164: ServerComponentUpdate update, Component component) {
165: }
166:
167: /**
168: * @see nextapp.echo2.webcontainer.DomUpdateSupport#renderHtml(nextapp.echo2.webcontainer.RenderContext,
169: * nextapp.echo2.app.update.ServerComponentUpdate, org.w3c.dom.Node, nextapp.echo2.app.Component)
170: */
171: public void renderHtml(RenderContext rc,
172: ServerComponentUpdate update, Node parentNode,
173: Component component) {
174: Document document = parentNode.getOwnerDocument();
175: Grid grid = (Grid) component;
176: Border border = (Border) grid
177: .getRenderProperty(Grid.PROPERTY_BORDER);
178: String elementId = ContainerInstance.getElementId(grid);
179: GridProcessor gridProcessor = new GridProcessor(grid);
180: int columnCount = gridProcessor.getColumnCount();
181: int rowCount = gridProcessor.getRowCount();
182:
183: Element tableElement = document.createElement("table");
184: tableElement.setAttribute("id", elementId);
185:
186: CssStyle tableCssStyle = new CssStyle();
187: tableCssStyle.setAttribute("border-collapse", "collapse");
188:
189: Insets gridInsets = (Insets) grid
190: .getRenderProperty(Grid.PROPERTY_INSETS);
191: String defaultInsetsAttributeValue = gridInsets == null ? "0px"
192: : InsetsRender.renderCssAttributeValue(gridInsets);
193:
194: ColorRender.renderToStyle(tableCssStyle, component);
195: FontRender.renderToStyle(tableCssStyle, component);
196: BorderRender.renderToStyle(tableCssStyle, border);
197: ExtentRender.renderToStyle(tableCssStyle, "height",
198: (Extent) grid.getRenderProperty(Grid.PROPERTY_HEIGHT));
199:
200: Extent width = (Extent) grid
201: .getRenderProperty(Grid.PROPERTY_WIDTH);
202: boolean render100PercentWidthWorkaround = false;
203: if (rc
204: .getContainerInstance()
205: .getClientProperties()
206: .getBoolean(
207: ClientProperties.QUIRK_IE_TABLE_PERCENT_WIDTH_SCROLLBAR_ERROR)) {
208: if (width != null && width.getUnits() == Extent.PERCENT
209: && width.getValue() == 100) {
210: width = null;
211: render100PercentWidthWorkaround = true;
212: }
213: }
214: ExtentRender.renderToStyle(tableCssStyle, "width", width);
215:
216: Extent borderSize = border == null ? null : border.getSize();
217: if (borderSize != null) {
218: if (!rc
219: .getContainerInstance()
220: .getClientProperties()
221: .getBoolean(
222: ClientProperties.QUIRK_CSS_BORDER_COLLAPSE_INSIDE)) {
223: tableCssStyle.setAttribute("margin", ExtentRender
224: .renderCssAttributeValueHalf(borderSize));
225: }
226: }
227:
228: tableElement
229: .setAttribute("style", tableCssStyle.renderInline());
230:
231: parentNode.appendChild(tableElement);
232:
233: boolean someColumnsHaveWidths = false;
234: for (int i = 0; i < columnCount; ++i) {
235: if (gridProcessor.getColumnWidth(i) != null) {
236: someColumnsHaveWidths = true;
237: }
238: }
239: if (someColumnsHaveWidths) {
240: Element colGroupElement = document
241: .createElement("colgroup");
242: tableElement.appendChild(colGroupElement);
243:
244: for (int i = 0; i < columnCount; ++i) {
245: Element colElement = document.createElement("col");
246: Extent columnWidth = gridProcessor.getColumnWidth(i);
247: if (columnWidth != null) {
248: colElement
249: .setAttribute(
250: "style",
251: "width:"
252: + ExtentRender
253: .renderCssAttributeValue(columnWidth));
254: }
255: colGroupElement.appendChild(colElement);
256: }
257: }
258:
259: Element tbodyElement = document.createElement("tbody");
260: tbodyElement.setAttribute("id", elementId + "_tbody");
261: tableElement.appendChild(tbodyElement);
262:
263: Set renderedCells = new HashSet();
264:
265: for (int rowIndex = 0; rowIndex < rowCount; ++rowIndex) {
266: Element trElement = document.createElement("tr");
267: trElement.setAttribute("id", elementId + "_tr_" + rowIndex);
268: if (gridProcessor.getRowHeight(rowIndex) != null) {
269: trElement.setAttribute("style", "height:"
270: + ExtentRender
271: .renderCssAttributeValue(gridProcessor
272: .getRowHeight(rowIndex)));
273: }
274: tbodyElement.appendChild(trElement);
275:
276: for (int columnIndex = 0; columnIndex < columnCount; ++columnIndex) {
277: Component cell = gridProcessor.getContent(columnIndex,
278: rowIndex);
279: if (cell == null) {
280: Element tdElement = document.createElement("td");
281: trElement.appendChild(tdElement);
282: continue;
283: }
284: if (renderedCells.contains(cell)) {
285: // Cell already rendered.
286: continue;
287: }
288: renderedCells.add(cell);
289:
290: Element tdElement = document.createElement("td");
291: tdElement.setAttribute("id", elementId + "_td_"
292: + ContainerInstance.getElementId(cell));
293: trElement.appendChild(tdElement);
294:
295: int columnSpan = gridProcessor.getColumnSpan(
296: columnIndex, rowIndex);
297: if (columnSpan > 1) {
298: tdElement.setAttribute("colspan", Integer
299: .toString(columnSpan));
300: }
301:
302: int rowSpan = gridProcessor.getRowSpan(columnIndex,
303: rowIndex);
304: if (rowSpan > 1) {
305: tdElement.setAttribute("rowspan", Integer
306: .toString(rowSpan));
307: }
308:
309: CssStyle tdCssStyle = new CssStyle();
310: BorderRender.renderToStyle(tdCssStyle, (Border) grid
311: .getRenderProperty(Grid.PROPERTY_BORDER));
312: CellLayoutDataRender.renderToElementAndStyle(tdElement,
313: tdCssStyle, cell, getLayoutData(cell),
314: defaultInsetsAttributeValue);
315: CellLayoutDataRender.renderBackgroundImageToStyle(
316: tdCssStyle, rc, this , grid, cell);
317: tdElement.setAttribute("style", tdCssStyle
318: .renderInline());
319:
320: if (rowIndex == 0 && render100PercentWidthWorkaround) {
321: // Render string of "sizing dots" in first cell.
322: Element sizingDivElement = document
323: .createElement("div");
324: sizingDivElement
325: .setAttribute("style",
326: "font-size:50px;height:0px;overflow:hidden;");
327: sizingDivElement.appendChild(document
328: .createTextNode(SIZING_DOTS));
329: tdElement.appendChild(sizingDivElement);
330: }
331:
332: renderAddChild(rc, update, tdElement, cell);
333: }
334: }
335: }
336:
337: /**
338: * @see nextapp.echo2.webcontainer.ComponentSynchronizePeer#renderUpdate(nextapp.echo2.webcontainer.RenderContext,
339: * nextapp.echo2.app.update.ServerComponentUpdate, java.lang.String)
340: */
341: public boolean renderUpdate(RenderContext rc,
342: ServerComponentUpdate update, String targetId) {
343: String parentId = ContainerInstance.getElementId(update
344: .getParent());
345: DomUpdate.renderElementRemove(rc.getServerMessage(), parentId);
346: renderAdd(rc, update, targetId, update.getParent());
347: return true;
348: }
349: }
|