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 * <table wicket:id="datatable"></table> 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<MyEntity> { 068 * ... 069 * } 070 * 071 * List<IColumn<MyEntity, String>> columns = new ArrayList<>(); 072 * 073 * columns.add(new PropertyColumn<MyEntity, String>(new Model<String>("First Name"), "firstName", "firstName")); 074 * columns.add(new PropertyColumn<MyEntity, String>(new Model<String>("Last Name"), "lastName")); 075 * 076 * DataTable<MyEntity,String> table = new DataTable<>("datatable", 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}