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.data.table;
018
019import java.util.List;
020
021import org.apache.wicket.AttributeModifier;
022import org.apache.wicket.Component;
023import org.apache.wicket.behavior.Behavior;
024import org.apache.wicket.extensions.markup.html.repeater.data.grid.DataGridView;
025import org.apache.wicket.markup.ComponentTag;
026import org.apache.wicket.markup.html.WebMarkupContainer;
027import org.apache.wicket.markup.html.basic.Label;
028import org.apache.wicket.markup.html.navigation.paging.IPageableItems;
029import org.apache.wicket.markup.html.panel.Panel;
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.RepeatingView;
034import org.apache.wicket.markup.repeater.data.IDataProvider;
035import org.apache.wicket.model.IModel;
036import org.apache.wicket.util.lang.Args;
037import org.apache.wicket.util.string.Strings;
038import org.apache.wicket.util.visit.IVisit;
039import org.apache.wicket.util.visit.IVisitor;
040
041
042/**
043 * A data table builds on data grid view to introduce toolbars. Toolbars can be used to display
044 * sortable column headers, paging information, filter controls, and other information.
045 * <p>
046 * Data table also provides its own markup for an html table so the developer does not need to provide it
047 * himself. This makes it very simple to add a datatable to the markup, however, some flexibility.
048 * <p>
049 * Example
050 * 
051 * <pre>
052 * &lt;table wicket:id=&quot;datatable&quot;&gt;&lt;/table&gt;
053 * </pre>
054 * 
055 * And the related Java code: ( the first column will be sortable because its sort property is
056 * specified, the second column will not )
057 * 
058 * <pre>
059 * // Application specific POJO to view/edit
060 * public class MyEntity {
061 *   private String firstName;
062 *   private String lastName;
063 *
064 *   // getters and setters
065 * }
066 *
067 * public class MyEntityProvider implements IDataProvider&lt;MyEntity&gt; {
068 *     ...
069 * }
070 *
071 * List&lt;IColumn&lt;MyEntity, String&gt;&gt; columns = new ArrayList&lt;&gt;();
072 * 
073 * columns.add(new PropertyColumn&lt;MyEntity, String&gt;(new Model&lt;String&gt;(&quot;First Name&quot;), &quot;firstName&quot;, &quot;firstName&quot;));
074 * columns.add(new PropertyColumn&lt;MyEntity, String&gt;(new Model&lt;String&gt;(&quot;Last Name&quot;), &quot;lastName&quot;));
075 * 
076 * DataTable&lt;MyEntity,String&gt; table = new DataTable&lt;&gt;(&quot;datatable&quot;, columns, new MyEntityProvider(), 10);
077 * table.addBottomToolbar(new NavigationToolbar(table));
078 * table.addTopToolbar(new HeadersToolbar(table, null));
079 * add(table);
080 * </pre>
081 * 
082 * @see DefaultDataTable
083 * 
084 * @author Igor Vaynberg (ivaynberg)
085 * 
086 * @param <T>
087 *            The model object type
088 * @param <S>
089 *            the type of the sorting parameter
090 * 
091 */
092public class DataTable<T, S> extends Panel implements IPageableItems
093{
094        static abstract class CssAttributeBehavior extends Behavior
095        {
096                private static final long serialVersionUID = 1L;
097
098                protected abstract String getCssClass();
099
100                /**
101                 * @see Behavior#onComponentTag(Component, ComponentTag)
102                 */
103                @Override
104                public void onComponentTag(final Component component, final ComponentTag tag)
105                {
106                        String className = getCssClass();
107                        if (!Strings.isEmpty(className))
108                        {
109                                tag.append("class", className, " ");
110                        }
111                }
112        }
113
114        private static final long serialVersionUID = 1L;
115
116        private final DataGridView<T> datagrid;
117
118        private final WebMarkupContainer body;
119
120        private final List<? extends IColumn<T, S>> columns;
121
122        private final ToolbarsContainer topToolbars;
123
124        private final ToolbarsContainer bottomToolbars;
125
126        private final Caption caption;
127
128        private final ColGroup colGroup;
129
130        private long toolbarIdCounter;
131
132        /**
133         * Constructor
134         * 
135         * @param id
136         *            component id
137         * @param columns
138         *            list of IColumn objects
139         * @param dataProvider
140         *            imodel for data provider
141         * @param rowsPerPage
142         *            number of rows per page
143         */
144        public DataTable(final String id, final List<? extends IColumn<T, S>> columns,
145                final IDataProvider<T> dataProvider, final long rowsPerPage)
146        {
147                super(id);
148
149                Args.notNull(columns, "columns");
150
151                this.columns = columns;
152                this.caption = new Caption("caption", getCaptionModel());
153                add(caption);
154                this.colGroup = new ColGroup("colGroup");
155                add(colGroup);
156                body = newBodyContainer("body");
157                datagrid = newDataGridView("rows", columns, dataProvider);
158                datagrid.setItemsPerPage(rowsPerPage);
159                body.add(datagrid);
160                add(body);
161                topToolbars = new ToolbarsContainer("topToolbars");
162                bottomToolbars = new ToolbarsContainer("bottomToolbars");
163                add(topToolbars);
164                add(bottomToolbars);
165        }
166
167        /**
168         * Factory method for the DataGridView
169         *
170         * @param id
171         *          The component id
172         * @param columns
173         *            list of IColumn objects
174         * @param dataProvider
175         *            imodel for data provider
176         * @return the data grid view
177         */
178        protected DataGridView<T> newDataGridView(String id, List<? extends IColumn<T, S>> columns, IDataProvider<T> dataProvider)
179        {
180                return new DefaultDataGridView(id, columns, dataProvider);
181        }
182
183        /**
184         * Returns the model for table's caption. The caption wont be rendered if the model has empty
185         * value.
186         * 
187         * @return the model for table's caption
188         */
189        protected IModel<String> getCaptionModel()
190        {
191                return null;
192        }
193
194        public final ColGroup getColGroup()
195        {
196                return colGroup;
197        }
198
199        /**
200         * Create the MarkupContainer for the <tbody> tag. Developers may subclass it to provide their own
201         * (modified) implementation.
202         * 
203         * @param id
204         * @return A new markup container
205         */
206        protected WebMarkupContainer newBodyContainer(final String id)
207        {
208                return new WebMarkupContainer(id);
209        }
210
211        /**
212         * Set the 'class' attribute for the tbody tag.
213         * 
214         * @param cssStyle
215         */
216        public final void setTableBodyCss(final String cssStyle)
217        {
218                body.add(AttributeModifier.replace("class", cssStyle));
219        }
220
221        /**
222         * Adds a toolbar to the datatable that will be displayed after the data
223         * 
224         * @param toolbar
225         *            toolbar to be added
226         * 
227         * @see AbstractToolbar
228         */
229        public void addBottomToolbar(final AbstractToolbar toolbar)
230        {
231                addToolbar(toolbar, bottomToolbars);
232        }
233
234        /**
235         * Adds a toolbar to the datatable that will be displayed before the data
236         * 
237         * @param toolbar
238         *            toolbar to be added
239         * 
240         * @see AbstractToolbar
241         */
242        public void addTopToolbar(final AbstractToolbar toolbar)
243        {
244                addToolbar(toolbar, topToolbars);
245        }
246
247        /**
248         * @return the container with the toolbars at the top
249         */
250        public final WebMarkupContainer getTopToolbars()
251        {
252                return topToolbars;
253        }
254
255        /**
256         * @return the container with the toolbars at the bottom
257         */
258        public final WebMarkupContainer getBottomToolbars()
259        {
260                return bottomToolbars;
261        }
262
263        /**
264         * @return the container used for the table body
265         */
266        public final WebMarkupContainer getBody()
267        {
268                return body;
269        }
270
271        /**
272         * @return the component used for the table caption
273         */
274        public final Caption getCaption()
275        {
276                return caption;
277        }
278
279        /**
280         * @return dataprovider
281         */
282        public final IDataProvider<T> getDataProvider()
283        {
284                return datagrid.getDataProvider();
285        }
286
287        /**
288         * @return array of column objects this table displays
289         */
290        public final List<? extends IColumn<T, S>> getColumns()
291        {
292                return columns;
293        }
294
295        /**
296         * @see org.apache.wicket.markup.html.navigation.paging.IPageable#getCurrentPage()
297         */
298        @Override
299        public final long getCurrentPage()
300        {
301                return datagrid.getCurrentPage();
302        }
303
304        /**
305         * @see org.apache.wicket.markup.html.navigation.paging.IPageable#getPageCount()
306         */
307        @Override
308        public final long getPageCount()
309        {
310                return datagrid.getPageCount();
311        }
312
313        /**
314         * @return total number of rows in this table
315         */
316        public final long getRowCount()
317        {
318                return datagrid.getRowCount();
319        }
320
321        /**
322         * @return number of rows per page
323         */
324        @Override
325        public final long getItemsPerPage()
326        {
327                return datagrid.getItemsPerPage();
328        }
329
330        /**
331         * @see org.apache.wicket.markup.html.navigation.paging.IPageable#setCurrentPage(long)
332         */
333        @Override
334        public final void setCurrentPage(final long page)
335        {
336                datagrid.setCurrentPage(page);
337                onPageChanged();
338        }
339
340
341        /**
342         * Sets the item reuse strategy. This strategy controls the creation of {@link Item}s.
343         * 
344         * @see RefreshingView#setItemReuseStrategy(IItemReuseStrategy)
345         * @see IItemReuseStrategy
346         * 
347         * @param strategy
348         *            item reuse strategy
349         * @return this for chaining
350         */
351        public final DataTable<T, S> setItemReuseStrategy(final IItemReuseStrategy strategy)
352        {
353                datagrid.setItemReuseStrategy(strategy);
354                return this;
355        }
356
357        /**
358         * Sets the number of items to be displayed per page
359         * 
360         * @param items
361         *            number of items to display per page
362         * 
363         */
364        @Override
365        public void setItemsPerPage(final long items)
366        {
367                datagrid.setItemsPerPage(items);
368        }
369
370        /**
371         * @see org.apache.wicket.markup.html.navigation.paging.IPageableItems#getItemCount()
372         */
373        @Override
374        public long getItemCount()
375        {
376                return datagrid.getItemCount();
377        }
378
379        private void addToolbar(final AbstractToolbar toolbar, final ToolbarsContainer container)
380        {
381                Args.notNull(toolbar, "toolbar");
382
383                container.getRepeatingView().add(toolbar);
384        }
385
386        /**
387         * Factory method for Item container that represents a cell in the underlying DataGridView
388         * 
389         * @see Item
390         * 
391         * @param id
392         *            component id for the new data item
393         * @param index
394         *            the index of the new data item
395         * @param model
396         *            the model for the new data item
397         * 
398         * @return DataItem created DataItem
399         */
400        protected Item<IColumn<T, S>> newCellItem(final String id, final int index,
401                final IModel<IColumn<T, S>> model)
402        {
403                return new Item<>(id, index, model);
404        }
405
406        /**
407         * Factory method for Item container that represents a row in the underlying DataGridView
408         * 
409         * @see Item
410         * 
411         * @param id
412         *            component id for the new data item
413         * @param index
414         *            the index of the new data item
415         * @param model
416         *            the model for the new data item.
417         * 
418         * @return DataItem created DataItem
419         */
420        protected Item<T> newRowItem(final String id, final int index, final IModel<T> model)
421        {
422                return new Item<>(id, index, model);
423        }
424
425        /**
426         * @see org.apache.wicket.Component#onDetach()
427         */
428        @Override
429        protected void onDetach()
430        {
431                super.onDetach();
432
433                for (IColumn<T, S> column : columns)
434                {
435                        column.detach();
436                }
437        }
438
439        /**
440         * Event listener for page-changed event
441         */
442        protected void onPageChanged()
443        {
444                // noop
445        }
446
447        /**
448         * @see AbstractToolbar
449         */
450        String newToolbarId()
451        {
452                toolbarIdCounter++;
453                return String.valueOf(toolbarIdCounter).intern();
454        }
455
456        @Override
457        protected void onComponentTag(ComponentTag tag)
458        {
459                checkComponentTag(tag, "table");
460                super.onComponentTag(tag);
461        }
462
463        /**
464         * This class acts as a repeater that will contain the toolbar. It makes sure that the table row
465         * group (e.g. thead) tags are only visible when they contain rows in accordance with the HTML
466         * specification.
467         * 
468         * @author igor.vaynberg
469         */
470        private static class ToolbarsContainer extends WebMarkupContainer
471        {
472                private static final long serialVersionUID = 1L;
473
474                private final RepeatingView toolbars;
475
476                /**
477                 * Constructor
478                 * 
479                 * @param id
480                 */
481                private ToolbarsContainer(final String id)
482                {
483                        super(id);
484                        toolbars = new RepeatingView("toolbars");
485                        add(toolbars);
486                }
487
488                public RepeatingView getRepeatingView()
489                {
490                        return toolbars;
491                }
492
493                @Override
494                public void onConfigure()
495                {
496                        super.onConfigure();
497
498                        toolbars.configure();
499
500                        Boolean visible = toolbars.visitChildren(new IVisitor<Component, Boolean>()
501                        {
502                                @Override
503                                public void component(Component object, IVisit<Boolean> visit)
504                                {
505                                        object.configure();
506                                        if (object.isVisible())
507                                        {
508                                                visit.stop(Boolean.TRUE);
509                                        }
510                                        else
511                                        {
512                                                visit.dontGoDeeper();
513                                        }
514                                }
515                        });
516                        if (visible == null)
517                        {
518                                visible = false;
519                        }
520                        setVisible(visible);
521                }
522        }
523
524        /**
525         * A caption for the table. It renders itself only if {@link DataTable#getCaptionModel()} has
526         * non-empty value.
527         */
528        public static class Caption extends Label
529        {
530                /**
531                 */
532                private static final long serialVersionUID = 1L;
533
534                /**
535                 * Construct.
536                 * 
537                 * @param id
538                 *            the component id
539                 * @param model
540                 *            the caption model
541                 */
542                public Caption(String id, IModel<String> model)
543                {
544                        super(id, model);
545                }
546
547                @Override
548                protected void onConfigure()
549                {
550                        setRenderBodyOnly(Strings.isEmpty(getDefaultModelObjectAsString()));
551
552                        super.onConfigure();
553                }
554
555                @Override
556                protected IModel<String> initModel()
557                {
558                        // don't try to find the model in the parent
559                        return null;
560                }
561        }
562
563        private class DefaultDataGridView extends DataGridView<T>
564        {
565                public DefaultDataGridView(String id, List<? extends IColumn<T, S>> columns, IDataProvider<T> dataProvider)
566                {
567                        super(id, columns, dataProvider);
568                }
569
570                @SuppressWarnings({ "rawtypes", "unchecked" })
571                @Override
572                protected Item newCellItem(final String id, final int index, final IModel model)
573                {
574                        Item item = DataTable.this.newCellItem(id, index, model);
575                        final IColumn<T, S> column = DataTable.this.columns.get(index);
576                        if (column instanceof IStyledColumn)
577                        {
578                                item.add(new CssAttributeBehavior()
579                                {
580                                        private static final long serialVersionUID = 1L;
581
582                                        @Override
583                                        protected String getCssClass()
584                                        {
585                                                return ((IStyledColumn<T, S>)column).getCssClass();
586                                        }
587                                });
588                        }
589                        return item;
590                }
591
592                @Override
593                protected Item<T> newRowItem(final String id, final int index, final IModel<T> model)
594                {
595                        return DataTable.this.newRowItem(id, index, model);
596                }
597        }
598}