001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.wicket.extensions.markup.html.tree.table;
018:
019: import javax.swing.tree.TreeModel;
020: import javax.swing.tree.TreeNode;
021:
022: import org.apache.wicket.Component;
023: import org.apache.wicket.IClusterable;
024: import org.apache.wicket.MarkupContainer;
025: import org.apache.wicket.ResourceReference;
026: import org.apache.wicket.behavior.AbstractBehavior;
027: import org.apache.wicket.extensions.markup.html.tree.DefaultAbstractTree;
028: import org.apache.wicket.extensions.markup.html.tree.table.ColumnLocation.Alignment;
029: import org.apache.wicket.markup.ComponentTag;
030: import org.apache.wicket.markup.html.WebMarkupContainer;
031: import org.apache.wicket.markup.html.basic.Label;
032: import org.apache.wicket.markup.html.panel.Fragment;
033: import org.apache.wicket.markup.html.tree.AbstractTree;
034: import org.apache.wicket.model.AbstractReadOnlyModel;
035: import org.apache.wicket.model.IModel;
036: import org.apache.wicket.model.Model;
037:
038: /**
039: * TreeTable is a component that represents a grid with a tree. It's divided
040: * into columns. One of the columns has to be column derived from
041: * {@link AbstractTreeColumn}.
042: *
043: * @author Matej Knopp
044: */
045: public class TreeTable extends DefaultAbstractTree {
046: /**
047: * Callback for rendering tree node text.
048: */
049: public static interface IRenderNodeCallback extends IClusterable {
050: /**
051: * Renders the tree node to text.
052: *
053: * @param node
054: * The tree node to render
055: * @return the tree node as text
056: */
057: public String renderNode(TreeNode node);
058: }
059:
060: /**
061: * Represents a content of a cell in TreeColumn (column containing the
062: * actual tree).
063: *
064: * @author Matej Knopp
065: */
066: private class TreeFragment extends Fragment {
067: private static final long serialVersionUID = 1L;
068:
069: /**
070: * Constructor.
071: *
072: *
073: * @param id
074: * @param node
075: * @param level
076: * @param renderNodeCallback
077: * The call back for rendering nodes
078: */
079: public TreeFragment(String id, final TreeNode node, int level,
080: final IRenderNodeCallback renderNodeCallback) {
081: super (id, "fragment");
082:
083: add(newIndentation(this , "indent", node, level));
084:
085: add(newJunctionLink(this , "link", "image", node));
086:
087: MarkupContainer nodeLink = newNodeLink(this , "nodeLink",
088: node);
089: add(nodeLink);
090:
091: nodeLink.add(newNodeIcon(nodeLink, "icon", node));
092:
093: nodeLink.add(new Label("label",
094: new AbstractReadOnlyModel() {
095: private static final long serialVersionUID = 1L;
096:
097: /**
098: * @see org.apache.wicket.model.AbstractReadOnlyModel#getObject()
099: */
100: public Object getObject() {
101: return renderNodeCallback.renderNode(node);
102: }
103: }));
104: }
105: }
106:
107: /** Reference to the css file. */
108: private static final ResourceReference CSS = new ResourceReference(
109: DefaultAbstractTree.class, "res/tree-table.css");
110:
111: private static final long serialVersionUID = 1L;
112:
113: /**
114: * Creates a tree cell for given node. This method is supposed to be used by
115: * TreeColumns (columns that draw the actual tree).
116: *
117: * @param parent
118: * Parent component
119: *
120: * @param id
121: * Component ID
122: *
123: * @param node
124: * Tree node for the row
125: *
126: * @param level
127: * How deep is the node nested (for convenience)
128: *
129: * @param callback
130: * Used to get the display string
131: *
132: * @param table
133: * Tree table
134: *
135: * @return The tree cell
136: */
137: public static Component newTreeCell(MarkupContainer parent,
138: String id, TreeNode node, int level,
139: IRenderNodeCallback callback, TreeTable table) {
140: return table.newTreePanel(parent, id, node, level, callback);
141: }
142:
143: // columns of the TreeTable
144: private IColumn columns[];
145:
146: /**
147: * Creates the TreeTable for the given array of columns.
148: *
149: * @param id
150: * @param columns
151: */
152: public TreeTable(String id, IColumn columns[]) {
153: super (id);
154: init(columns);
155: }
156:
157: /**
158: * Creates the TreeTable for the given model and array of columns.
159: *
160: * @param id
161: * The component id
162: * @param model
163: * The tree model
164: * @param columns
165: * The columns
166: */
167: public TreeTable(String id, IModel model, IColumn columns[]) {
168: super (id, model);
169: init(columns);
170: }
171:
172: /**
173: * Creates the TreeTable for the given TreeModel and array of columns.
174: *
175: * @param id
176: * The component id
177: * @param model
178: * The tree model
179: * @param columns
180: * The columns
181: */
182: public TreeTable(String id, TreeModel model, IColumn columns[]) {
183: super (id, model);
184: init(columns);
185: }
186:
187: private boolean hasLeftColumn() {
188: for (int i = 0; i < columns.length; ++i) {
189: if (columns[i].getLocation().getAlignment().equals(
190: Alignment.LEFT))
191: return true;
192: }
193: return false;
194: }
195:
196: /**
197: * Adds the header to the TreeTable.
198: */
199: protected void addHeader() {
200: // create the view for side columns
201: SideColumnsView sideColumns = new SideColumnsView(
202: "sideColumns", null);
203: add(sideColumns);
204: if (columns != null)
205: for (int i = 0; i < columns.length; i++) {
206: IColumn column = columns[i];
207: if (column.getLocation().getAlignment() == Alignment.LEFT
208: || column.getLocation().getAlignment() == Alignment.RIGHT) {
209: Component component = column.newHeader(sideColumns,
210: "" + i);
211: sideColumns.add(component);
212: sideColumns.addColumn(column, component, null);
213: }
214: }
215:
216: // create the view for middle columns
217: MiddleColumnsView middleColumns = new MiddleColumnsView(
218: "middleColumns", null, hasLeftColumn());
219: add(middleColumns);
220: if (columns != null)
221: for (int i = 0; i < columns.length; i++) {
222: IColumn column = columns[i];
223: if (column.getLocation().getAlignment() == Alignment.MIDDLE) {
224: Component component = column.newHeader(
225: middleColumns, "" + i);
226: middleColumns.add(component);
227: middleColumns.addColumn(column, component, null);
228: }
229: }
230: };
231:
232: /**
233: * @see org.apache.wicket.markup.html.tree.DefaultAbstractTree#getCSS()
234: */
235: protected ResourceReference getCSS() {
236: return CSS;
237: }
238:
239: /**
240: * Creates a new instance of the TreeFragment.
241: *
242: * @param parent
243: * The parent component
244: * @param id
245: * The component id
246: * @param node
247: * The tree node
248: * @param level
249: * The level of the tree row
250: * @param renderNodeCallback
251: * The node call back
252: * @return The tree panel
253: */
254: protected Component newTreePanel(MarkupContainer parent, String id,
255: final TreeNode node, int level,
256: IRenderNodeCallback renderNodeCallback) {
257: return new TreeFragment(id, node, level, renderNodeCallback);
258: }
259:
260: /**
261: * @see AbstractTree#onBeforeAttach()
262: */
263: protected void onBeforeAttach() {
264: // has the header been added yet?
265: if (get("sideColumns") == null) {
266: // no. initialize columns first
267: if (columns != null)
268: for (int i = 0; i < columns.length; i++) {
269: IColumn column = columns[i];
270: column.setTreeTable(this );
271: }
272:
273: // add the tree table header
274: addHeader();
275: }
276: }
277:
278: /**
279: * Populates one row of the tree.
280: *
281: * @param item
282: * the tree node component
283: * @param level
284: * the current level
285: */
286: protected void populateTreeItem(WebMarkupContainer item, int level) {
287: final TreeNode node = (TreeNode) item.getModelObject();
288:
289: // add side columns
290: SideColumnsView sideColumns = new SideColumnsView(
291: "sideColumns", node);
292: item.add(sideColumns);
293: if (columns != null)
294: for (int i = 0; i < columns.length; i++) {
295: IColumn column = columns[i];
296: if (column.getLocation().getAlignment() == Alignment.LEFT
297: || column.getLocation().getAlignment() == Alignment.RIGHT) {
298: Component component;
299: // first try to create a renderable
300: IRenderable renderable = column
301: .newCell(node, level);
302:
303: if (renderable == null) {
304: // if renderable failed, try to create a regular
305: // component
306: component = column.newCell(sideColumns, "" + i,
307: node, level);
308: sideColumns.add(component);
309: } else {
310: component = null;
311: }
312:
313: sideColumns
314: .addColumn(column, component, renderable);
315: }
316: }
317:
318: // add middle columns
319: MiddleColumnsView middleColumns = new MiddleColumnsView(
320: "middleColumns", node, hasLeftColumn());
321: if (columns != null)
322: for (int i = 0; i < columns.length; i++) {
323: IColumn column = columns[i];
324: if (column.getLocation().getAlignment() == Alignment.MIDDLE) {
325: Component component;
326: // first try to create a renderable
327: IRenderable renderable = column
328: .newCell(node, level);
329:
330: if (renderable == null) {
331: // if renderable failed, try to create a regular
332: // component
333: component = column.newCell(middleColumns, ""
334: + i, node, level);
335: middleColumns.add(component);
336: } else {
337: component = null;
338: }
339:
340: middleColumns.addColumn(column, component,
341: renderable);
342: }
343: }
344: item.add(middleColumns);
345:
346: // do distinguish between selected and unselected rows we add an
347: // behavior
348: // that modifies row css class.
349: item.add(new AbstractBehavior() {
350: private static final long serialVersionUID = 1L;
351:
352: public void onComponentTag(Component component,
353: ComponentTag tag) {
354: super .onComponentTag(component, tag);
355: if (getTreeState().isNodeSelected(node)) {
356: tag.put("class", "row-selected");
357: } else {
358: tag.put("class", "row");
359: }
360: }
361: });
362: }
363:
364: /**
365: * Internal initialization. Also checks if at least one of the columns is
366: * derived from AbstractTreeColumn.
367: *
368: * @param columns
369: * The columns
370: */
371: private void init(IColumn columns[]) {
372: boolean found = false;
373: if (columns != null)
374: for (int i = 0; i < columns.length; i++) {
375: IColumn column = columns[i];
376: if (column instanceof AbstractTreeColumn) {
377: found = true;
378: break;
379: }
380: }
381: if (found == false) {
382: throw new IllegalArgumentException(
383: "At least one column in TreeTable must be derived from AbstractTreeColumn.");
384: }
385:
386: this .columns = columns;
387:
388: // Attach the javascript that resizes the header according to the body
389: // This is necessary to support fixed position header. The header does
390: // not
391: // scroll together with body. The body contains vertical scrollbar. The
392: // header width must be same as body content width, so that the columns
393: // are properly aligned.
394: add(new Label("attachJavascript", new Model() {
395: private static final long serialVersionUID = 1L;
396:
397: public Object getObject() {
398: return "Wicket.TreeTable.attachUpdate(\""
399: + getMarkupId() + "\");";
400: }
401: }).setEscapeModelStrings(false));
402: }
403: }
|