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 */
017package org.apache.wicket.extensions.markup.html.repeater.tree;
018
019import java.util.List;
020import java.util.Optional;
021import java.util.Set;
022
023import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
024import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable;
025import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
026import org.apache.wicket.extensions.markup.html.repeater.tree.table.ITreeColumn;
027import org.apache.wicket.extensions.markup.html.repeater.tree.table.ITreeDataProvider;
028import org.apache.wicket.extensions.markup.html.repeater.tree.table.NodeModel;
029import org.apache.wicket.extensions.markup.html.repeater.tree.table.TreeDataProvider;
030import org.apache.wicket.markup.repeater.IItemReuseStrategy;
031import org.apache.wicket.markup.repeater.Item;
032import org.apache.wicket.markup.repeater.RefreshingView;
033import org.apache.wicket.markup.repeater.data.IDataProvider;
034import org.apache.wicket.model.IModel;
035import org.apache.wicket.util.lang.Args;
036import org.apache.wicket.util.visit.IVisit;
037import org.apache.wicket.util.visit.IVisitor;
038
039/**
040 * A tree with tabular markup.
041 * 
042 * @author svenmeier
043 * 
044 * @param <T>
045 *            The model object type
046 * @param <S>
047 *            the type of the sort property
048 */
049public abstract class TableTree<T, S> extends AbstractTree<T>
050{
051        private static final long serialVersionUID = 1L;
052
053        private final DataTable<T, S> table;
054
055        /**
056         * Constructor
057         * 
058         * @param id
059         *            component id
060         * @param columns
061         *            list of IColumn objects
062         * @param dataProvider
063         *            imodel for data provider
064         * @param rowsPerPage
065         *            number of rows per page
066         */
067        public TableTree(final String id, final List<? extends IColumn<T, S>> columns,
068                final ITreeProvider<T> dataProvider, final long rowsPerPage)
069        {
070                this(id, columns, dataProvider, rowsPerPage, null);
071        }
072
073        /**
074         * Constructor
075         * 
076         * @param id
077         *            component id
078         * @param columns
079         *            list of IColumn objects
080         * @param provider
081         *            provider of the tree
082         * @param rowsPerPage
083         *            number of rows per page
084         * @param state
085         *            the expansion state
086         */
087        public TableTree(final String id, final List<? extends IColumn<T, S>> columns,
088                final ITreeProvider<T> provider, final long rowsPerPage, IModel<? extends Set<T>> state)
089        {
090                super(id, provider, state);
091
092                Args.notEmpty(columns, "columns");
093                for (IColumn<T, S> column : columns)
094                {
095                        if (column instanceof ITreeColumn<?, ?>)
096                        {
097                                ((ITreeColumn<T, S>)column).setTree(this);
098                        }
099                }
100
101                this.table = newDataTable("table", columns, newDataProvider(provider), rowsPerPage);
102                add(table);
103
104                // see #updateBranch(Object, AjaxRequestTarget)
105                setOutputMarkupId(true);
106        }
107
108        /**
109         * Factory method for the wrapped {@link DataTable}.
110         * 
111         * Note: If overwritten, the DataTable's row items have to output their markupId, or
112         * {@link #updateNode(Object, IPartialPageRequestHandler)} will fail.
113         * 
114         * @param id
115         * @param columns
116         * @param dataProvider
117         * @param rowsPerPage
118         * @return nested data table
119         */
120        protected DataTable<T, S> newDataTable(String id, List<? extends IColumn<T, S>> columns,
121                IDataProvider<T> dataProvider, long rowsPerPage)
122        {
123                return new DataTable<T, S>(id, columns, dataProvider, rowsPerPage)
124                {
125                        private static final long serialVersionUID = 1L;
126
127                        @Override
128                        protected Item<T> newRowItem(String id, int index, IModel<T> model)
129                        {
130                                Item<T> item = TableTree.this.newRowItem(id, index, model);
131
132                                // see #update(Node);
133                                item.setOutputMarkupId(true);
134
135                                return item;
136                        }
137                };
138        }
139
140        /**
141         * Get the nested table.
142         * 
143         * @return the nested table
144         */
145        public DataTable<T, S> getTable()
146        {
147                return table;
148        }
149
150        /**
151         * Sets the item reuse strategy. This strategy controls the creation of {@link Item}s.
152         * 
153         * @see RefreshingView#setItemReuseStrategy(IItemReuseStrategy)
154         * @see IItemReuseStrategy
155         * 
156         * @param strategy
157         *            item reuse strategy
158         * @return this for chaining
159         */
160        @Override
161        public final TableTree<T, S> setItemReuseStrategy(final IItemReuseStrategy strategy)
162        {
163                table.setItemReuseStrategy(strategy);
164
165                super.setItemReuseStrategy(strategy);
166
167                return this;
168        }
169
170        /**
171         * For updating of a single branch the whole table is added to the ART.
172         */
173        @Override
174        public void updateBranch(T node, IPartialPageRequestHandler target)
175        {
176                // TableTree always outputs markupId
177                target.add(this);
178        }
179
180        /**
181         * For an update of a node the complete row item is added to the ART.
182         */
183        @Override
184        public void updateNode(T t, IPartialPageRequestHandler target)
185        {
186                final IModel<T> model = getProvider().model(t);
187                table.getBody().visitChildren(Item.class, new IVisitor<Item<T>, Void>()
188                {
189                        @Override
190                        public void component(Item<T> item, IVisit<Void> visit)
191                        {
192                                NodeModel<T> nodeModel = (NodeModel<T>)item.getModel();
193
194                                if (model.equals(nodeModel.getWrappedModel()))
195                                {
196                                        // row items are configured to output their markupId
197                                        target.add(item);
198                                        visit.stop();
199                                        return;
200                                }
201                                visit.dontGoDeeper();
202                        }
203                });
204                model.detach();
205        }
206
207        /**
208         * Hook method to create an {@link ITreeDataProvider}.
209         * 
210         * @param provider
211         *            the tree provider
212         * @return the data provider
213         */
214        protected ITreeDataProvider<T> newDataProvider(ITreeProvider<T> provider)
215        {
216                return new TreeDataProvider<T>(provider)
217                {
218                        private static final long serialVersionUID = 1L;
219
220                        @Override
221                        protected boolean iterateChildren(T object)
222                        {
223                                return TableTree.this.getState(object) == State.EXPANDED;
224                        }
225                };
226        }
227
228        /**
229         * Create a row item for the nested {@link DataTable}.
230         * 
231         * @param id
232         *            component id
233         * @param index
234         *            index of row
235         * @param model
236         *            model for row
237         * @return row item
238         */
239        protected Item<T> newRowItem(String id, int index, IModel<T> model)
240        {
241                Item<T> item = new Item<>(id, index, model);
242
243                return item;
244        }
245}