Source Code Cross Referenced for AbstractTree.java in  » J2EE » wicket » org » apache » wicket » markup » html » tree » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » J2EE » wicket » org.apache.wicket.markup.html.tree 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         * Licensed to the Apache Software Foundation (ASF) under one or more
0003:         * contributor license agreements.  See the NOTICE file distributed with
0004:         * this work for additional information regarding copyright ownership.
0005:         * The ASF licenses this file to You under the Apache License, Version 2.0
0006:         * (the "License"); you may not use this file except in compliance with
0007:         * the License.  You may obtain a copy of the License at
0008:         *
0009:         *      http://www.apache.org/licenses/LICENSE-2.0
0010:         *
0011:         * Unless required by applicable law or agreed to in writing, software
0012:         * distributed under the License is distributed on an "AS IS" BASIS,
0013:         * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014:         * See the License for the specific language governing permissions and
0015:         * limitations under the License.
0016:         */
0017:        package org.apache.wicket.markup.html.tree;
0018:
0019:        import java.io.Serializable;
0020:        import java.util.ArrayList;
0021:        import java.util.Collections;
0022:        import java.util.Enumeration;
0023:        import java.util.HashMap;
0024:        import java.util.Iterator;
0025:        import java.util.List;
0026:        import java.util.Map;
0027:
0028:        import javax.swing.event.TreeModelEvent;
0029:        import javax.swing.event.TreeModelListener;
0030:        import javax.swing.tree.TreeModel;
0031:        import javax.swing.tree.TreeNode;
0032:
0033:        import org.apache.wicket.Component;
0034:        import org.apache.wicket.ResourceReference;
0035:        import org.apache.wicket.ajax.AjaxRequestTarget;
0036:        import org.apache.wicket.behavior.HeaderContributor;
0037:        import org.apache.wicket.markup.MarkupStream;
0038:        import org.apache.wicket.markup.html.WebMarkupContainer;
0039:        import org.apache.wicket.markup.html.internal.HtmlHeaderContainer;
0040:        import org.apache.wicket.markup.html.panel.Panel;
0041:        import org.apache.wicket.markup.html.resources.JavascriptResourceReference;
0042:        import org.apache.wicket.model.IDetachable;
0043:        import org.apache.wicket.model.IModel;
0044:        import org.apache.wicket.model.Model;
0045:        import org.apache.wicket.util.string.AppendingStringBuffer;
0046:
0047:        /**
0048:         * This class encapsulates the logic for displaying and (partial) updating the
0049:         * tree. Actual presentation is out of scope of this class. User should derive
0050:         * they own tree (if needed) from {@link DefaultAbstractTree} or {@link Tree}
0051:         * (recommended).
0052:         * 
0053:         * @author Matej Knopp
0054:         */
0055:        public abstract class AbstractTree extends Panel implements 
0056:                ITreeStateListener, TreeModelListener {
0057:
0058:            /**
0059:             * Interface for visiting individual tree items.
0060:             */
0061:            private static interface IItemCallback {
0062:                /**
0063:                 * Visits the tree item.
0064:                 * 
0065:                 * @param item
0066:                 *            the item to visit
0067:                 */
0068:                void visitItem(TreeItem item);
0069:            }
0070:
0071:            /**
0072:             * This class represents one row in rendered tree (TreeNode). Only TreeNodes
0073:             * that are visible (all their parent are expanded) have TreeItem created
0074:             * for them.
0075:             */
0076:            private final class TreeItem extends WebMarkupContainer {
0077:                /**
0078:                 * whether this tree item should also render it's children to response.
0079:                 * this is set if we need the whole subtree rendered as one component in
0080:                 * ajax response, so that we can replace it in one step (replacing
0081:                 * individual rows is very slow in javascript, therefore we replace the
0082:                 * whole subtree)
0083:                 */
0084:                private final static int FLAG_RENDER_CHILDREN = FLAG_RESERVED8;
0085:
0086:                private static final long serialVersionUID = 1L;
0087:
0088:                /**
0089:                 * tree item children - we need this to traverse items in correct order
0090:                 * when rendering
0091:                 */
0092:                private List children = null;
0093:
0094:                /** tree item level - how deep is this item in tree */
0095:                private final int level;
0096:
0097:                /**
0098:                 * Construct.
0099:                 * 
0100:                 * @param id
0101:                 *            The component id
0102:                 * @param node
0103:                 *            tree node
0104:                 * @param level
0105:                 *            current level
0106:                 */
0107:                public TreeItem(String id, final TreeNode node, int level) {
0108:                    super (id, new Model((Serializable) node));
0109:
0110:                    nodeToItemMap.put(node, this );
0111:                    this .level = level;
0112:                    setOutputMarkupId(true);
0113:
0114:                    // if this isn't a root item in rootless mode
0115:                    if (level != -1) {
0116:                        populateTreeItem(this , level);
0117:                    }
0118:                }
0119:
0120:                /**
0121:                 * @return The children
0122:                 */
0123:                public List getChildren() {
0124:                    return children;
0125:                }
0126:
0127:                /**
0128:                 * @return The current level
0129:                 */
0130:                public int getLevel() {
0131:                    return level;
0132:                }
0133:
0134:                /**
0135:                 * @see org.apache.wicket.Component#getMarkupId()
0136:                 */
0137:                public String getMarkupId() {
0138:                    // this is overriden to produce id that begins with id of tree
0139:                    // if the tree has set (shorter) id in markup, we can use it to
0140:                    // shorten the id of individual TreeItems
0141:                    return AbstractTree.this .getMarkupId() + "_" + getId();
0142:                }
0143:
0144:                /**
0145:                 * @return parent item
0146:                 */
0147:                public TreeItem getParentItem() {
0148:                    return (TreeItem) nodeToItemMap
0149:                            .get(((TreeNode) getModelObject()).getParent());
0150:                }
0151:
0152:                /**
0153:                 * Sets the children.
0154:                 * 
0155:                 * @param children
0156:                 *            The children
0157:                 */
0158:                public void setChildren(List children) {
0159:                    this .children = children;
0160:                }
0161:
0162:                /**
0163:                 * Whether to render children.
0164:                 * 
0165:                 * @return whether to render children
0166:                 */
0167:                protected final boolean isRenderChildren() {
0168:                    return getFlag(FLAG_RENDER_CHILDREN);
0169:                }
0170:
0171:                /**
0172:                 * @see org.apache.wicket.MarkupContainer#onRender(org.apache.wicket.markup.MarkupStream)
0173:                 */
0174:                protected void onRender(final MarkupStream markupStream) {
0175:                    // is this root and tree is in rootless mode?
0176:                    if (this  == rootItem && isRootLess() == true) {
0177:                        // yes, write empty div with id
0178:                        // this is necesary for createElement js to work correctly
0179:                        getResponse().write(
0180:                                "<div style=\"display:none\" id=\""
0181:                                        + getMarkupId() + "\"></div>");
0182:                        markupStream.skipComponent();
0183:                    } else {
0184:                        // remember current index
0185:                        final int index = markupStream.getCurrentIndex();
0186:
0187:                        // render the item
0188:                        super .onRender(markupStream);
0189:
0190:                        // should we also render children (ajax response)
0191:                        if (isRenderChildren()) {
0192:                            // visit every child
0193:                            visitItemChildren(this , new IItemCallback() {
0194:                                public void visitItem(TreeItem item) {
0195:                                    // rewind markupStream
0196:                                    markupStream.setCurrentIndex(index);
0197:                                    // render child
0198:                                    item.onRender(markupStream);
0199:                                }
0200:                            });
0201:                            // 
0202:                        }
0203:                    }
0204:                }
0205:
0206:                public void renderHead(final HtmlHeaderContainer container) {
0207:                    super .renderHead(container);
0208:
0209:                    if (isRenderChildren()) {
0210:                        // visit every child
0211:                        visitItemChildren(this , new IItemCallback() {
0212:                            public void visitItem(TreeItem item) {
0213:                                // write header contributions from the children of item
0214:                                item.visitChildren(new Component.IVisitor() {
0215:                                    public Object component(Component component) {
0216:                                        if (component.isVisible()) {
0217:                                            component.renderHead(container);
0218:                                            return CONTINUE_TRAVERSAL;
0219:                                        } else {
0220:                                            return CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
0221:                                        }
0222:                                    }
0223:                                });
0224:                            }
0225:                        });
0226:                    }
0227:                }
0228:
0229:                protected final void setRenderChildren(boolean value) {
0230:                    setFlag(FLAG_RENDER_CHILDREN, value);
0231:                }
0232:
0233:                protected void onAttach() {
0234:                    super .onAttach();
0235:
0236:                    if (isRenderChildren()) {
0237:                        // visit every child
0238:                        visitItemChildren(this , new IItemCallback() {
0239:                            public void visitItem(TreeItem item) {
0240:                                item.attach();
0241:                            }
0242:                        });
0243:                    }
0244:                }
0245:
0246:                protected void onDetach() {
0247:                    super .onDetach();
0248:                    Object object = getModelObject();
0249:                    if (object instanceof  IDetachable) {
0250:                        ((IDetachable) object).detach();
0251:                    }
0252:
0253:                    if (isRenderChildren()) {
0254:                        // visit every child
0255:                        visitItemChildren(this , new IItemCallback() {
0256:                            public void visitItem(TreeItem item) {
0257:                                item.detach();
0258:                            }
0259:                        });
0260:                    }
0261:
0262:                    //children are rendered, clear the flag
0263:                    setRenderChildren(false);
0264:                }
0265:
0266:                protected void onBeforeRender() {
0267:                    AbstractTree.this .onBeforeRenderInternal();
0268:                    super .onBeforeRender();
0269:
0270:                    if (isRenderChildren()) {
0271:                        // visit every child
0272:                        visitItemChildren(this , new IItemCallback() {
0273:                            public void visitItem(TreeItem item) {
0274:                                item.beforeRender();
0275:                            }
0276:                        });
0277:                    }
0278:                }
0279:
0280:                protected void onAfterRender() {
0281:                    super .onAfterRender();
0282:                    if (isRenderChildren()) {
0283:                        // visit every child
0284:                        visitItemChildren(this , new IItemCallback() {
0285:                            public void visitItem(TreeItem item) {
0286:                                item.afterRender();
0287:                            }
0288:                        });
0289:                    }
0290:                }
0291:            }
0292:
0293:            /**
0294:             * Components that holds tree items. This is similiar to ListView, but it
0295:             * renders tree items in the right order.
0296:             */
0297:            private class TreeItemContainer extends WebMarkupContainer {
0298:                private static final long serialVersionUID = 1L;
0299:
0300:                /**
0301:                 * Construct.
0302:                 * 
0303:                 * @param id
0304:                 *            The component id
0305:                 */
0306:                public TreeItemContainer(String id) {
0307:                    super (id);
0308:                }
0309:
0310:                /**
0311:                 * @see org.apache.wicket.MarkupContainer#remove(org.apache.wicket.Component)
0312:                 */
0313:                public void remove(Component component) {
0314:                    // when a treeItem is removed, remove reference to it from
0315:                    // nodeToItemMAp
0316:                    if (component instanceof  TreeItem) {
0317:                        nodeToItemMap.remove(((TreeItem) component)
0318:                                .getModelObject());
0319:                    }
0320:                    super .remove(component);
0321:                }
0322:
0323:                /**
0324:                 * renders the tree items, making sure that items are rendered in the
0325:                 * order they should be
0326:                 * 
0327:                 * @param markupStream
0328:                 */
0329:                protected void onRender(final MarkupStream markupStream) {
0330:                    // Save position in markup stream
0331:                    final int markupStart = markupStream.getCurrentIndex();
0332:
0333:                    // have we rendered at least one item?
0334:                    final class Rendered {
0335:                        boolean rendered = false;
0336:                    }
0337:                    ;
0338:                    final Rendered rendered = new Rendered();
0339:
0340:                    // is there a root item? (non-empty tree)
0341:                    if (rootItem != null) {
0342:                        IItemCallback callback = new IItemCallback() {
0343:                            public void visitItem(TreeItem item) {
0344:                                // rewind markup stream
0345:                                markupStream.setCurrentIndex(markupStart);
0346:
0347:                                // render component
0348:                                item.render(markupStream);
0349:
0350:                                rendered.rendered = true;
0351:                            }
0352:                        };
0353:
0354:                        // visit item and it's children
0355:                        visitItemAndChildren(rootItem, callback);
0356:                    }
0357:
0358:                    if (rendered.rendered == false) {
0359:                        // tree is empty, just move the markupStream
0360:                        markupStream.skipComponent();
0361:                    }
0362:                }
0363:            }
0364:
0365:            /**
0366:             * Returns an iterator that iterates trough the enumeration.
0367:             * 
0368:             * @param enumeration
0369:             *            The enumeration to iterate through
0370:             * @return The iterator
0371:             */
0372:            private static final Iterator toIterator(
0373:                    final Enumeration enumeration) {
0374:                return new Iterator() {
0375:                    private final Enumeration e = enumeration;
0376:
0377:                    public boolean hasNext() {
0378:                        return e.hasMoreElements();
0379:                    }
0380:
0381:                    public Object next() {
0382:                        return e.nextElement();
0383:                    }
0384:
0385:                    public void remove() {
0386:                        throw new UnsupportedOperationException(
0387:                                "Remove is not supported on enumeration.");
0388:                    }
0389:                };
0390:            }
0391:
0392:            private boolean attached = false;
0393:
0394:            /** comma separated list of ids of elements to be deleted. */
0395:            private final AppendingStringBuffer deleteIds = new AppendingStringBuffer();
0396:
0397:            /**
0398:             * whether the whole tree is dirty (so the whole tree needs to be
0399:             * refreshed).
0400:             */
0401:            private boolean dirtyAll = false;
0402:
0403:            /**
0404:             * list of dirty items. if children property of these items is null, the
0405:             * chilren will be rebuild.
0406:             */
0407:            private final List dirtyItems = new ArrayList();
0408:
0409:            /**
0410:             * list of dirty items which need the DOM structure to be created for them
0411:             * (added items)
0412:             */
0413:            private final List dirtyItemsCreateDOM = new ArrayList();
0414:
0415:            /** counter for generating unique ids of every tree item. */
0416:            private int idCounter = 0;
0417:
0418:            /** Component whose children are tree items. */
0419:            private TreeItemContainer itemContainer;
0420:
0421:            /**
0422:             * map that maps TreeNode to TreeItem. TreeItems only exists for TreeNodes,
0423:             * that are visibled (their parents are not collapsed).
0424:             */
0425:            private final Map nodeToItemMap = new HashMap();
0426:
0427:            /**
0428:             * we need to track previous model. if the model changes, we unregister the
0429:             * tree from listeners of old model and register the tree as litener of new
0430:             * model.
0431:             */
0432:            private TreeModel previousModel = null;
0433:
0434:            /** root item of the tree. */
0435:            private TreeItem rootItem = null;
0436:
0437:            /** whether the tree root is shown. */
0438:            private boolean rootLess = false;
0439:
0440:            /** stores reference to tree state. */
0441:            private ITreeState state;
0442:
0443:            /**
0444:             * Tree constructor
0445:             * 
0446:             * @param id
0447:             *            The component id
0448:             */
0449:            public AbstractTree(String id) {
0450:                super (id);
0451:                init();
0452:            }
0453:
0454:            /**
0455:             * Tree constructor
0456:             * 
0457:             * @param id
0458:             *            The component id
0459:             * @param model
0460:             *            The tree model
0461:             */
0462:            public AbstractTree(String id, IModel model) {
0463:                super (id, model);
0464:                init();
0465:            }
0466:
0467:            /** called when all nodes are collapsed. */
0468:            public final void allNodesCollapsed() {
0469:                invalidateAll();
0470:            }
0471:
0472:            /** called when all nodes are expaned. */
0473:            public final void allNodesExpanded() {
0474:                invalidateAll();
0475:            }
0476:
0477:            /**
0478:             * Returns the TreeState of this tree.
0479:             * 
0480:             * @return Tree state instance
0481:             */
0482:            public ITreeState getTreeState() {
0483:                if (state == null) {
0484:                    state = newTreeState();
0485:
0486:                    // add this object as listener of the state
0487:                    state.addTreeStateListener(this );
0488:                    // FIXME: Where should we remove the listener?
0489:                }
0490:                return state;
0491:            }
0492:
0493:            /**
0494:             * This method is called before the onAttach is called. Code here gets
0495:             * executed before the items have been populated.
0496:             */
0497:            protected void onBeforeAttach() {
0498:            }
0499:
0500:            // This is necessary because MarkupContainer.onBeforeRender involves calling
0501:            // beforeRender on children, which results in stack overflow when called from TreeItem
0502:            private void onBeforeRenderInternal() {
0503:                if (attached == false) {
0504:                    onBeforeAttach();
0505:
0506:                    checkModel();
0507:
0508:                    // Do we have to rebuld the whole tree?
0509:                    if (dirtyAll && rootItem != null) {
0510:                        clearAllItem();
0511:                    } else {
0512:                        // rebuild chilren of dirty nodes that need it
0513:                        rebuildDirty();
0514:                    }
0515:
0516:                    // is root item created? (root item is null if the items have not
0517:                    // been created yet, or the whole tree was dirty and clearAllITem
0518:                    // has been called
0519:                    if (rootItem == null) {
0520:                        TreeNode rootNode = (TreeNode) ((TreeModel) getModelObject())
0521:                                .getRoot();
0522:                        if (rootNode != null) {
0523:                            if (isRootLess()) {
0524:                                rootItem = newTreeItem(rootNode, -1);
0525:                            } else {
0526:                                rootItem = newTreeItem(rootNode, 0);
0527:                            }
0528:                            itemContainer.add(rootItem);
0529:                            buildItemChildren(rootItem);
0530:                        }
0531:                    }
0532:
0533:                    attached = true;
0534:                }
0535:            }
0536:
0537:            /**
0538:             * Called at the beginning of the request (not ajax request, unless we are
0539:             * rendering the entire component)
0540:             */
0541:            public void onBeforeRender() {
0542:                onBeforeRenderInternal();
0543:                super .onBeforeRender();
0544:            }
0545:
0546:            /**
0547:             * @see org.apache.wicket.MarkupContainer#onDetach()
0548:             */
0549:            public void onDetach() {
0550:                attached = false;
0551:                super .onDetach();
0552:            }
0553:
0554:            /**
0555:             * Call to refresh the whole tree. This should only be called when the
0556:             * roodNode has been replaced or the entiry tree model changed.
0557:             */
0558:            public final void invalidateAll() {
0559:                updated();
0560:                this .dirtyAll = true;
0561:            }
0562:
0563:            /**
0564:             * @return whether the tree root is shown
0565:             */
0566:            public final boolean isRootLess() {
0567:                return rootLess;
0568:            };
0569:
0570:            /**
0571:             * @see org.apache.wicket.markup.html.tree.ITreeStateListener#nodeCollapsed(javax.swing.tree.TreeNode)
0572:             */
0573:            public final void nodeCollapsed(TreeNode node) {
0574:                if (isNodeVisible(node) == true) {
0575:                    invalidateNodeWithChildren(node);
0576:                }
0577:            }
0578:
0579:            /**
0580:             * @see org.apache.wicket.markup.html.tree.ITreeStateListener#nodeExpanded(javax.swing.tree.TreeNode)
0581:             */
0582:            public final void nodeExpanded(TreeNode node) {
0583:                if (isNodeVisible(node) == true) {
0584:                    invalidateNodeWithChildren(node);
0585:                }
0586:            }
0587:
0588:            /**
0589:             * @see org.apache.wicket.markup.html.tree.ITreeStateListener#nodeSelected(javax.swing.tree.TreeNode)
0590:             */
0591:            public final void nodeSelected(TreeNode node) {
0592:                if (isNodeVisible(node)) {
0593:                    invalidateNode(node, isForceRebuildOnSelectionChange());
0594:                }
0595:            }
0596:
0597:            /**
0598:             * @see org.apache.wicket.markup.html.tree.ITreeStateListener#nodeUnselected(javax.swing.tree.TreeNode)
0599:             */
0600:            public final void nodeUnselected(TreeNode node) {
0601:                if (isNodeVisible(node)) {
0602:                    invalidateNode(node, isForceRebuildOnSelectionChange());
0603:                }
0604:            }
0605:
0606:            /**
0607:             * Determines whether the TreeNode needs to be rebuilt if it is selected
0608:             * or deselected
0609:             * @return true if the node should be rebuilt after (de)selection, false otherwise
0610:             */
0611:            protected boolean isForceRebuildOnSelectionChange() {
0612:                return true;
0613:            }
0614:
0615:            /**
0616:             * Sets whether the root of the tree should be visible.
0617:             * 
0618:             * @param rootLess
0619:             *            whether the root should be visible
0620:             */
0621:            public void setRootLess(boolean rootLess) {
0622:                if (this .rootLess != rootLess) {
0623:                    this .rootLess = rootLess;
0624:                    invalidateAll();
0625:
0626:                    // if the tree is in rootless mode, make sure the root node is
0627:                    // expanded
0628:                    if (rootLess == true && getModelObject() != null) {
0629:                        getTreeState().expandNode(
0630:                                (TreeNode) ((TreeModel) getModelObject())
0631:                                        .getRoot());
0632:                    }
0633:                }
0634:            }
0635:
0636:            /**
0637:             * @see javax.swing.event.TreeModelListener#treeNodesChanged(javax.swing.event.TreeModelEvent)
0638:             */
0639:            public final void treeNodesChanged(TreeModelEvent e) {
0640:                // has root node changed?
0641:                if (e.getChildren() == null) {
0642:                    if (rootItem != null) {
0643:                        invalidateNode((TreeNode) rootItem.getModelObject(),
0644:                                true);
0645:                    }
0646:                } else {
0647:                    // go through all changed nodes
0648:                    Object[] children = e.getChildren();
0649:                    if (children != null) {
0650:                        for (int i = 0; i < children.length; i++) {
0651:                            TreeNode node = (TreeNode) children[i];
0652:                            if (isNodeVisible(node)) {
0653:                                // if the nodes is visible invalidate it
0654:                                invalidateNode(node, true);
0655:                            }
0656:                        }
0657:                    }
0658:                }
0659:            };
0660:
0661:            /**
0662:             * Marks the last but one visible child node of the given item as dirty, if
0663:             * give child is the last item of parent.
0664:             * 
0665:             * We need this to refresh the previous visible item in case the inserted /
0666:             * deleteditem was last. The reason is that the line shape of previous item
0667:             * chages from L to |- .
0668:             * 
0669:             * @param parent
0670:             * @param child
0671:             */
0672:            private void markTheLastButOneChildDirty(TreeItem parent,
0673:                    TreeItem child) {
0674:                if (parent.getChildren().indexOf(child) == parent.getChildren()
0675:                        .size() - 1) {
0676:                    // go through the childrend backwards, start at the last but one
0677:                    // item
0678:                    for (int i = parent.getChildren().size() - 2; i >= 0; --i) {
0679:                        TreeItem item = (TreeItem) parent.getChildren().get(i);
0680:
0681:                        // invalidate the node and it's children, so that they are
0682:                        // redrawn
0683:                        invalidateNodeWithChildren((TreeNode) item
0684:                                .getModelObject());
0685:
0686:                    }
0687:                }
0688:            }
0689:
0690:            /**
0691:             * @see javax.swing.event.TreeModelListener#treeNodesInserted(javax.swing.event.TreeModelEvent)
0692:             */
0693:            public final void treeNodesInserted(TreeModelEvent e) {
0694:                // get the parent node of inserted nodes
0695:                TreeNode parent = (TreeNode) e.getTreePath()
0696:                        .getLastPathComponent();
0697:
0698:                if (isNodeVisible(parent) && isNodeExpanded(parent)) {
0699:                    TreeItem parentItem = (TreeItem) nodeToItemMap.get(parent);
0700:                    for (int i = 0; i < e.getChildren().length; ++i) {
0701:                        TreeNode node = (TreeNode) e.getChildren()[i];
0702:                        int index = e.getChildIndices()[i];
0703:                        TreeItem item = newTreeItem(node,
0704:                                parentItem.getLevel() + 1);
0705:                        itemContainer.add(item);
0706:                        parentItem.getChildren().add(index, item);
0707:
0708:                        markTheLastButOneChildDirty(parentItem, item);
0709:
0710:                        dirtyItems.add(item);
0711:                        dirtyItemsCreateDOM.add(item);
0712:                    }
0713:                }
0714:            }
0715:
0716:            /**
0717:             * @see javax.swing.event.TreeModelListener#treeNodesRemoved(javax.swing.event.TreeModelEvent)
0718:             */
0719:            public final void treeNodesRemoved(TreeModelEvent e) {
0720:                // get the parent node of inserted nodes
0721:                TreeNode parent = (TreeNode) e.getTreePath()
0722:                        .getLastPathComponent();
0723:                TreeItem parentItem = (TreeItem) nodeToItemMap.get(parent);
0724:
0725:                if (isNodeVisible(parent) && isNodeExpanded(parent)) {
0726:
0727:                    for (int i = 0; i < e.getChildren().length; ++i) {
0728:                        TreeNode node = (TreeNode) e.getChildren()[i];
0729:
0730:                        TreeItem item = (TreeItem) nodeToItemMap.get(node);
0731:                        if (item != null) {
0732:                            markTheLastButOneChildDirty(parentItem, item);
0733:
0734:                            parentItem.getChildren().remove(item);
0735:
0736:                            // go though item children and remove every one of them
0737:                            visitItemChildren(item, new IItemCallback() {
0738:                                public void visitItem(TreeItem item) {
0739:                                    removeItem(item);
0740:
0741:                                    // unselect the node
0742:                                    getTreeState().selectNode(
0743:                                            (TreeNode) item.getModelObject(),
0744:                                            false);
0745:                                }
0746:                            });
0747:
0748:                            removeItem(item);
0749:                        }
0750:                    }
0751:                }
0752:            }
0753:
0754:            /**
0755:             * @see javax.swing.event.TreeModelListener#treeStructureChanged(javax.swing.event.TreeModelEvent)
0756:             */
0757:            public final void treeStructureChanged(TreeModelEvent e) {
0758:                // get the parent node of changed nodes
0759:                TreeNode node = (TreeNode) e.getTreePath()
0760:                        .getLastPathComponent();
0761:
0762:                // has the tree root changed?
0763:                if (e.getTreePath().getPathCount() == 1
0764:                        && node.equals(rootItem.getModelObject())) {
0765:                    invalidateAll();
0766:                } else {
0767:                    invalidateNodeWithChildren(node);
0768:                }
0769:            }
0770:
0771:            /**
0772:             * Updates the changed portions of the tree using given AjaxRequestTarget.
0773:             * Call this method if you modified the tree model during an ajax request
0774:             * target and you want to partially update the component on page. Make sure
0775:             * that the tree model has fired the proper listener functions.
0776:             * <p>
0777:             * <b>You can only call this method once in a request.</b>
0778:             * 
0779:             * @param target
0780:             *            Ajax request target used to send the update to the page
0781:             */
0782:            public final void updateTree(final AjaxRequestTarget target) {
0783:                if (target == null) {
0784:                    return;
0785:                }
0786:
0787:                // check whether the model hasn't changed
0788:                checkModel();
0789:
0790:                // is the whole tree dirty
0791:                if (dirtyAll) {
0792:                    // render entire tree component
0793:                    target.addComponent(this );
0794:                } else {
0795:                    // remove DOM elements that need to be removed
0796:                    if (deleteIds.length() != 0) {
0797:                        String js = getElementsDeleteJavascript();
0798:
0799:                        // add the javascript to target
0800:                        target.prependJavascript(js);
0801:                    }
0802:
0803:                    // We have to repeat this as long as there are any dirty items to be
0804:                    // created.
0805:                    // The reason why we can't do this in one pass is that some of the
0806:                    // items
0807:                    // may need to be inserted after items that has not been inserted
0808:                    // yet, so we have
0809:                    // to detect those and wait until the items they depend on are
0810:                    // inserted.
0811:                    while (dirtyItemsCreateDOM.isEmpty() == false) {
0812:                        for (Iterator i = dirtyItemsCreateDOM.iterator(); i
0813:                                .hasNext();) {
0814:                            TreeItem item = (TreeItem) i.next();
0815:                            TreeItem parent = item.getParentItem();
0816:                            int index = parent.getChildren().indexOf(item);
0817:                            TreeItem previous;
0818:                            // we need item before this (in dom structure)
0819:
0820:                            if (index == 0) {
0821:                                previous = parent;
0822:                            } else {
0823:                                previous = (TreeItem) parent.getChildren().get(
0824:                                        index - 1);
0825:                                // get the last item of previous item subtree
0826:                                while (previous.getChildren() != null
0827:                                        && previous.getChildren().size() > 0) {
0828:                                    previous = (TreeItem) previous
0829:                                            .getChildren().get(
0830:                                                    previous.getChildren()
0831:                                                            .size() - 1);
0832:                                }
0833:                            }
0834:                            // check if the previous item isn't waiting to be inserted
0835:                            if (dirtyItemsCreateDOM.contains(previous) == false) {
0836:                                // it's already in dom, so we can use it as point of
0837:                                // insertion
0838:                                target
0839:                                        .prependJavascript("Wicket.Tree.createElement(\""
0840:                                                + item.getMarkupId()
0841:                                                + "\","
0842:                                                + "\""
0843:                                                + previous.getMarkupId()
0844:                                                + "\")");
0845:
0846:                                // remove the item so we don't process it again
0847:                                i.remove();
0848:                            } else {
0849:                                // we don't do anything here, inserting this item will
0850:                                // have to wait
0851:                                // until the previous item gets inserted
0852:                            }
0853:                        }
0854:                    }
0855:
0856:                    // iterate through dirty items
0857:                    for (Iterator i = dirtyItems.iterator(); i.hasNext();) {
0858:                        TreeItem item = (TreeItem) i.next();
0859:                        // does the item need to rebuild children?
0860:                        if (item.getChildren() == null) {
0861:                            // rebuld the children
0862:                            buildItemChildren(item);
0863:
0864:                            // set flag on item so that it renders itself together with
0865:                            // it's children
0866:                            item.setRenderChildren(true);
0867:                        }
0868:
0869:                        // add the component to target
0870:                        target.addComponent(item);
0871:                    }
0872:
0873:                    // clear dirty flags
0874:                    updated();
0875:                }
0876:            }
0877:
0878:            /**
0879:             * Returns whether the given node is expanded.
0880:             * 
0881:             * @param node
0882:             *            The node to inspect
0883:             * @return true if the node is expanded, false otherwise
0884:             */
0885:            protected final boolean isNodeExpanded(TreeNode node) {
0886:                // In root less mode the root node is always expanded
0887:                if (isRootLess() && rootItem != null
0888:                        && rootItem.getModelObject().equals(node)) {
0889:                    return true;
0890:                }
0891:
0892:                return getTreeState().isNodeExpanded(node);
0893:            }
0894:
0895:            /**
0896:             * Creates the TreeState, which is an object where the current state of tree
0897:             * (which nodes are expanded / collapsed, selected, ...) is stored.
0898:             * 
0899:             * @return Tree state instance
0900:             */
0901:            protected ITreeState newTreeState() {
0902:                return new DefaultTreeState();
0903:            }
0904:
0905:            /**
0906:             * Called after the rendering of tree is complete. Here we clear the dirty
0907:             * flags.
0908:             */
0909:            protected void onAfterRender() {
0910:                super .onAfterRender();
0911:                // rendering is complete, clear all dirty flags and items
0912:                updated();
0913:            }
0914:
0915:            /**
0916:             * This method is called after creating every TreeItem. This is the place
0917:             * for adding components on item (junction links, labels, icons...)
0918:             * 
0919:             * @param item
0920:             *            newly created tree item. The node can be obtained as
0921:             *            item.getModelObject()
0922:             * 
0923:             * @param level
0924:             *            how deep the component is in tree hierarchy (0 for root item)
0925:             */
0926:            protected abstract void populateTreeItem(WebMarkupContainer item,
0927:                    int level);
0928:
0929:            /**
0930:             * Builds the children for given TreeItem. It recursively traverses children
0931:             * of it's TreeNode and creates TreeItem for every visible TreeNode.
0932:             * 
0933:             * @param item
0934:             *            The parent tree item
0935:             */
0936:            private final void buildItemChildren(TreeItem item) {
0937:                List items;
0938:
0939:                // if the node is expanded
0940:                if (isNodeExpanded((TreeNode) item.getModelObject())) {
0941:                    // build the items for children of the items' treenode.
0942:                    items = buildTreeItems(nodeChildren((TreeNode) item
0943:                            .getModelObject()), item.getLevel() + 1);
0944:                } else {
0945:                    // it's not expanded, just set children to an empty list
0946:                    items = Collections.EMPTY_LIST;
0947:                }
0948:
0949:                item.setChildren(items);
0950:            }
0951:
0952:            /**
0953:             * Builds (recursively) TreeItems for the given Iterator of TreeNodes.
0954:             * 
0955:             * @param nodes
0956:             *            The nodes to build tree items for
0957:             * @param level
0958:             *            The current level
0959:             * @return List with new tree items
0960:             */
0961:            private final List buildTreeItems(Iterator nodes, int level) {
0962:                List result = new ArrayList();
0963:
0964:                // for each node
0965:                while (nodes.hasNext()) {
0966:                    TreeNode node = (TreeNode) nodes.next();
0967:                    // create tree item
0968:                    TreeItem item = newTreeItem(node, level);
0969:                    itemContainer.add(item);
0970:
0971:                    // builds it children (recursively)
0972:                    buildItemChildren(item);
0973:
0974:                    // add item to result
0975:                    result.add(item);
0976:                }
0977:
0978:                return result;
0979:            }
0980:
0981:            /**
0982:             * Checks whether the model has been chaned, and if so unregister and
0983:             * register listeners.
0984:             */
0985:            private final void checkModel() {
0986:                // find out whether the model object (the TreeModel) has been changed
0987:                TreeModel model = (TreeModel) getModelObject();
0988:                if (model != previousModel) {
0989:                    if (previousModel != null) {
0990:                        previousModel.removeTreeModelListener(this );
0991:                    }
0992:
0993:                    previousModel = model;
0994:
0995:                    if (model != null) {
0996:                        model.addTreeModelListener(this );
0997:                    }
0998:                    // model has been changed, redraw whole tree
0999:                    invalidateAll();
1000:                }
1001:            }
1002:
1003:            /**
1004:             * Removes all TreeItem components.
1005:             */
1006:            private final void clearAllItem() {
1007:                visitItemAndChildren(rootItem, new IItemCallback() {
1008:                    public void visitItem(TreeItem item) {
1009:                        item.remove();
1010:                    }
1011:                });
1012:                rootItem = null;
1013:            }
1014:
1015:            /**
1016:             * Returns the javascript used to delete removed elements.
1017:             * 
1018:             * @return The javascript
1019:             */
1020:            private String getElementsDeleteJavascript() {
1021:                // build the javascript call
1022:                final AppendingStringBuffer buffer = new AppendingStringBuffer(
1023:                        100);
1024:
1025:                buffer.append("Wicket.Tree.removeNodes(\"");
1026:
1027:                // first parameter is the markup id of tree (will be used as prefix to
1028:                // build ids of child items
1029:                buffer.append(getMarkupId() + "_\",[");
1030:
1031:                // append the ids of elements to be deleted
1032:                buffer.append(deleteIds);
1033:
1034:                // does the buffer end if ','?
1035:                if (buffer.endsWith(",")) {
1036:                    // it does, trim it
1037:                    buffer.setLength(buffer.length() - 1);
1038:                }
1039:
1040:                buffer.append("]);");
1041:
1042:                return buffer.toString();
1043:            }
1044:
1045:            //
1046:            // State and Model callbacks
1047:            //
1048:
1049:            /**
1050:             * returns the short version of item id (just the number part).
1051:             * 
1052:             * @param item
1053:             *            The tree item
1054:             * @return The id
1055:             */
1056:            private String getShortItemId(TreeItem item) {
1057:                // show much of component id can we skip? (to minimize the length of
1058:                // javascript being sent)
1059:                final int skip = getMarkupId().length() + 1; // the length of id of
1060:                // tree and '_'.
1061:                return item.getMarkupId().substring(skip);
1062:            }
1063:
1064:            private final static ResourceReference JAVASCRIPT = new JavascriptResourceReference(
1065:                    AbstractTree.class, "res/tree.js");
1066:
1067:            /**
1068:             * Initialize the component.
1069:             */
1070:            private final void init() {
1071:                setVersioned(false);
1072:
1073:                // we need id when we are replacing the whole tree
1074:                setOutputMarkupId(true);
1075:
1076:                // create container for tree items
1077:                itemContainer = new TreeItemContainer("i");
1078:                add(itemContainer);
1079:
1080:                add(HeaderContributor.forJavaScript(JAVASCRIPT));
1081:            }
1082:
1083:            /**
1084:             * Invalidates single node (without children). On the next render, this node
1085:             * will be updated. Node will not be rebuilt, unless forceRebuild is true.
1086:             * 
1087:             * @param node
1088:             *            The node to invalidate
1089:             * @param forceRebuild
1090:             */
1091:            private final void invalidateNode(TreeNode node,
1092:                    boolean forceRebuild) {
1093:                if (dirtyAll == false) {
1094:                    // get item for this node
1095:                    TreeItem item = (TreeItem) nodeToItemMap.get(node);
1096:
1097:                    if (item != null) {
1098:                        boolean createDOM = false;
1099:
1100:                        if (forceRebuild) {
1101:                            // recreate the item
1102:                            int level = item.getLevel();
1103:                            List children = item.getChildren();
1104:                            String id = item.getId();
1105:
1106:                            // store the parent of old item
1107:                            TreeItem parent = item.getParentItem();
1108:
1109:                            // if the old item has a parent, store it's index
1110:                            int index = parent != null ? parent.getChildren()
1111:                                    .indexOf(item) : -1;
1112:
1113:                            createDOM = dirtyItemsCreateDOM.contains(item);
1114:
1115:                            dirtyItems.remove(item);
1116:                            dirtyItemsCreateDOM.remove(item);
1117:
1118:                            item.remove();
1119:
1120:                            item = newTreeItem(node, level, id);
1121:                            itemContainer.add(item);
1122:
1123:                            item.setChildren(children);
1124:
1125:                            // was the item an root item?
1126:                            if (parent == null) {
1127:                                rootItem = item;
1128:                            } else {
1129:                                parent.getChildren().set(index, item);
1130:                            }
1131:                        }
1132:
1133:                        dirtyItems.add(item);
1134:                        if (createDOM) {
1135:                            dirtyItemsCreateDOM.add(item);
1136:                        }
1137:                    }
1138:                }
1139:            }
1140:
1141:            /**
1142:             * Invalidates node and it's children. On the next render, the node and
1143:             * children will be updated. Node children will be rebuilt.
1144:             * 
1145:             * @param node
1146:             *            The node to invalidate
1147:             */
1148:            private final void invalidateNodeWithChildren(TreeNode node) {
1149:                if (dirtyAll == false) {
1150:                    // get item for this node
1151:                    TreeItem item = (TreeItem) nodeToItemMap.get(node);
1152:
1153:                    // is the item visible?
1154:                    if (item != null) {
1155:                        // go though item children and remove every one of them
1156:                        visitItemChildren(item, new IItemCallback() {
1157:                            public void visitItem(TreeItem item) {
1158:                                removeItem(item);
1159:                            }
1160:                        });
1161:
1162:                        // set children to null so that they get rebuild
1163:                        item.setChildren(null);
1164:
1165:                        // add item to dirty items
1166:                        dirtyItems.add(item);
1167:                    }
1168:                }
1169:            }
1170:
1171:            /**
1172:             * Returns whether the given node is visibled, e.g. all it's parents are
1173:             * expanded.
1174:             * 
1175:             * @param node
1176:             *            The node to inspect
1177:             * @return true if the node is visible, false otherwise
1178:             */
1179:            private final boolean isNodeVisible(TreeNode node) {
1180:                while (node.getParent() != null) {
1181:                    if (isNodeExpanded(node.getParent()) == false) {
1182:                        return false;
1183:                    }
1184:                    node = node.getParent();
1185:                }
1186:                return true;
1187:            }
1188:
1189:            /**
1190:             * Creates a tree item for given node.
1191:             * 
1192:             * @param node
1193:             *            The tree node
1194:             * @param level
1195:             *            The level
1196:             * @return The new tree item
1197:             */
1198:            private final TreeItem newTreeItem(TreeNode node, int level) {
1199:                return new TreeItem("" + idCounter++, node, level);
1200:            }
1201:
1202:            /**
1203:             * Creates a tree item for given node with specified id.
1204:             * 
1205:             * @param node
1206:             *            The tree node
1207:             * @param level
1208:             *            The level
1209:             * @param id
1210:             *            the component id
1211:             * @return The new tree item
1212:             */
1213:            private final TreeItem newTreeItem(TreeNode node, int level,
1214:                    String id) {
1215:                return new TreeItem(id, node, level);
1216:            }
1217:
1218:            /**
1219:             * Return the representation of node children as Iterator interface.
1220:             * 
1221:             * @param node
1222:             *            The tree node
1223:             * @return iterable presentation of node children
1224:             */
1225:            private final Iterator nodeChildren(TreeNode node) {
1226:                return toIterator(node.children());
1227:            }
1228:
1229:            /**
1230:             * Rebuilds children of every item in dirtyItems that needs it. This method
1231:             * is called for non-partial update.
1232:             */
1233:            private final void rebuildDirty() {
1234:                // go through dirty items
1235:                for (Iterator i = dirtyItems.iterator(); i.hasNext();) {
1236:                    TreeItem item = (TreeItem) i.next();
1237:                    // item chilren need to be rebuilt
1238:                    if (item.getChildren() == null) {
1239:                        buildItemChildren(item);
1240:                    }
1241:                }
1242:            }
1243:
1244:            /**
1245:             * Removes the item, appends it's id to deleteIds. This is called when a
1246:             * items parent is being deleted or rebuilt.
1247:             * 
1248:             * @param item
1249:             *            The item to remove
1250:             */
1251:            private void removeItem(TreeItem item) {
1252:                // even if the item is dirty it's no longer necessary to update id
1253:                dirtyItems.remove(item);
1254:
1255:                // if the item was about to be created
1256:                if (dirtyItemsCreateDOM.contains(item)) {
1257:                    // we needed to create DOM element, we no longer do
1258:                    dirtyItemsCreateDOM.remove(item);
1259:                } else {
1260:                    // add items id (it's short version) to ids of DOM elements that
1261:                    // will be
1262:                    // removed
1263:                    deleteIds.append(getShortItemId(item));
1264:                    deleteIds.append(",");
1265:                }
1266:
1267:                // remove the id
1268:                // note that this doesn't update item's parent's children list
1269:                item.remove();
1270:            }
1271:
1272:            /**
1273:             * Calls after the tree has been rendered. Clears all dirty flags.
1274:             */
1275:            private final void updated() {
1276:                this .dirtyAll = false;
1277:                this .dirtyItems.clear();
1278:                this .dirtyItemsCreateDOM.clear();
1279:                deleteIds.clear(); // FIXME: Recreate it to save some space?
1280:            }
1281:
1282:            /**
1283:             * Call the callback#visitItem method for the given item and all it's
1284:             * chilren.
1285:             * 
1286:             * @param item
1287:             *            The tree item
1288:             * @param callback
1289:             *            item call back
1290:             */
1291:            private final void visitItemAndChildren(TreeItem item,
1292:                    IItemCallback callback) {
1293:                callback.visitItem(item);
1294:                visitItemChildren(item, callback);
1295:            }
1296:
1297:            /**
1298:             * Call the callback#visitItem method for every child of given item.
1299:             * 
1300:             * @param item
1301:             *            The tree item
1302:             * @param callback
1303:             *            The callback
1304:             */
1305:            private final void visitItemChildren(TreeItem item,
1306:                    IItemCallback callback) {
1307:                if (item.getChildren() != null) {
1308:                    for (Iterator i = item.getChildren().iterator(); i
1309:                            .hasNext();) {
1310:                        TreeItem child = (TreeItem) i.next();
1311:                        visitItemAndChildren(child, callback);
1312:                    }
1313:                }
1314:            }
1315:
1316:            /**
1317:             * Returns the component associated with given node, or null, if node is not
1318:             * visible. This is useful in situations when you want to touch the node
1319:             * element in html.
1320:             * 
1321:             * @param node
1322:             *            Tree node
1323:             * @return Component associated with given node, or null if node is not
1324:             *         visible.
1325:             */
1326:            public Component getNodeComponent(TreeNode node) {
1327:                return (Component) nodeToItemMap.get(node);
1328:            }
1329:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.