1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
|
---
title: Table
order: 21
layout: page
---
[[components.table]]
= [classname]#Table#
ifdef::web[]
[.sampler]
image:{live-demo-image}[alt="Live Demo", link="http://demo.vaadin.com/sampler/#ui/grids-and-trees/table"]
endif::web[]
((("[classname]#Table#", id="term.components.table", range="startofrange")))
The [classname]#Table# component is intended for presenting tabular data
organized in rows and columns. The [classname]#Table# is one of the most
versatile components in Vaadin. Table cells can include text or arbitrary UI
components. You can easily implement editing of the table data, for example
clicking on a cell could change it to a text field for editing.
The data contained in a [classname]#Table# is managed using the Data Model of
Vaadin (see
<<dummy/../../../framework/datamodel/datamodel-overview.asciidoc#datamodel.overview,"Binding
Components to Data">>), through the [classname]#Container# interface of the
[classname]#Table#. This makes it possible to bind a table directly to a data
source, such as a database query. Only the visible part of the table is loaded
into the browser and moving the visible window with the scrollbar loads content
from the server. While the data is being loaded, a tooltip will be displayed
that shows the current range and total number of items in the table. The rows of
the table are __items__ in the container and the columns are __properties__.
Each table row (item) is identified with an __item identifier__ (IID), and each
column (property) with a __property identifier__ (PID).
When creating a table, you first need to define columns with
[methodname]#addContainerProperty()#. This method comes in two flavors. The
simpler one takes the property ID of the column and uses it also as the caption
of the column. The more complex one allows differing PID and header for the
column. This may make, for example, internationalization of table headers
easier, because if a PID is internationalized, the internationalization has to
be used everywhere where the PID is used. The complex form of the method also
allows defining an icon for the column from a resource. The "default value"
parameter is used when new properties (columns) are added to the table, to fill
in the missing values. (This default has no meaning in the usual case, such as
below, where we add items after defining the properties.)
[source, java]
----
Table table = new Table("The Brightest Stars");
// Define two columns for the built-in container
table.addContainerProperty("Name", String.class, null);
table.addContainerProperty("Mag", Float.class, null);
// Add a row the hard way
Object newItemId = table.addItem();
Item row1 = table.getItem(newItemId);
row1.getItemProperty("Name").setValue("Sirius");
row1.getItemProperty("Mag").setValue(-1.46f);
// Add a few other rows using shorthand addItem()
table.addItem(new Object[]{"Canopus", -0.72f}, 2);
table.addItem(new Object[]{"Arcturus", -0.04f}, 3);
table.addItem(new Object[]{"Alpha Centauri", -0.01f}, 4);
// Show exactly the currently contained rows (items)
table.setPageLength(table.size());
----
In this example, we used an increasing [classname]#Integer# object as the Item
Identifier, given as the second parameter to [methodname]#addItem()#. The actual
rows are given simply as object arrays, in the same order in which the
properties were added. The objects must be of the correct class, as defined in
the [methodname]#addContainerProperty()# calls.
.Basic Table Example
image::img/table-example1.png[]
Scalability of the [classname]#Table# is largely dictated by the container. The
default [classname]#IndexedContainer# is relatively heavy and can cause
scalability problems, for example, when updating the values. Use of an optimized
application-specific container is recommended. Table does not have a limit for
the number of items and is just as fast with hundreds of thousands of items as
with just a few. With the current implementation of scrolling, there is a limit
of around 500 000 rows, depending on the browser and the pixel height of rows.
Common selection component features are described in
<<dummy/../../../framework/components/components-selection#components.selection,"Selection
Components">>.
[[components.table.selecting]]
== Selecting Items in a Table
The [classname]#Table# allows selecting one or more items by clicking them with
the mouse. When the user selects an item, the IID of the item will be set as the
property of the table and a [classname]#ValueChangeEvent# is triggered. To
enable selection, you need to set the table __selectable__. You will also need
to set it as __immediate__ in most cases, as we do below, because without it,
the change in the property will not be communicated immediately to the server.
The following example shows how to enable the selection of items in a
[classname]#Table# and how to handle [classname]#ValueChangeEvent# events that
are caused by changes in selection. You need to handle the event with the
[methodname]#valueChange()# method of the
[classname]#Property.ValueChangeListener# interface.
[source, java]
----
// Allow selecting items from the table.
table.setSelectable(true);
// Send changes in selection immediately to server.
table.setImmediate(true);
// Shows feedback from selection.
final Label current = new Label("Selected: -");
// Handle selection change.
table.addValueChangeListener(new Property.ValueChangeListener() {
public void valueChange(ValueChangeEvent event) {
current.setValue("Selected: " + table.getValue());
}
});
----
.Table Selection Example
image::img/table-example2.png[]
If the user clicks on an already selected item, the selection will deselected
and the table property will have [parameter]#null# value. You can disable this
behaviour by setting [methodname]#setNullSelectionAllowed(false)# for the table.
The selection is the value of the table's property, so you can get it with
[methodname]#getValue()#. You can get it also from a reference to the table
itself. In single selection mode, the value is the item identifier of the
selected item or [parameter]#null# if no item is selected. In multiple selection
mode (see below), the value is a [classname]#Set# of item identifiers. Notice
that the set is unmodifiable, so you can not simply change it to change the
selection.
=== Multiple Selection Mode
A table can also be in __multiselect__ mode, where a user can select multiple
items by clicking them with left mouse button while holding the kbd:[Ctrl] key (or kbd:[Meta] key) pressed. If kbd:[Ctrl] is not held, clicking an item will select it and
other selected items are deselected. The user can select a range by selecting an
item, holding the kbd:[Shift] key pressed, and clicking another item, in which case
all the items between the two are also selected. Multiple ranges can be selected
by first selecting a range, then selecting an item while holding kbd:[Ctrl], and then
selecting another item with both kbd:[Ctrl] and kbd:[Shift] pressed.
The multiselect mode is enabled with the [methodname]#setMultiSelect()# method
of the [classname]#AbstractSelect# superclass of [classname]#Table#. Setting
table in multiselect mode does not implicitly set it as __selectable__, so it
must be set separately.
The [methodname]#setMultiSelectMode()# property affects the control of multiple
selection: [parameter]#MultiSelectMode.DEFAULT# is the default behaviour, which
requires holding the kbd:[Ctrl] (or kbd:[Meta]) key pressed while selecting items, while in
[parameter]#MultiSelectMode.SIMPLE# holding the kbd:[Ctrl] key is not needed. In the
simple mode, items can only be deselected by clicking them.
[[components.table.features]]
== Table Features
=== Page Length and Scrollbar
The default style for [classname]#Table# provides a table with a scrollbar. The
scrollbar is located at the right side of the table and becomes visible when the
number of items in the table exceeds the page length, that is, the number of
visible items. You can set the page length with [methodname]#setPageLength()#.
Setting the page length to zero makes all the rows in a table visible, no matter
how many rows there are. Notice that this also effectively disables buffering,
as all the entire table is loaded to the browser at once. Using such tables to
generate reports does not scale up very well, as there is some inevitable
overhead in rendering a table with Ajax. For very large reports, generating HTML
directly is a more scalable solution.
[[components.table.features.resizing]]
=== Resizing Columns
You can set the width of a column programmatically from the server-side with
[methodname]#setColumnWidth()#. The column is identified by the property ID and
the width is given in pixels.
The user can resize table columns by dragging the resize handle between two
columns. Resizing a table column causes a [classname]#ColumnResizeEvent#, which
you can handle with a [classname]#Table.ColumnResizeListener#. The table must be
set in immediate mode if you want to receive the resize events immediately,
which is typical.
[source, java]
----
table.addColumnResizeListener(new Table.ColumnResizeListener(){
public void columnResize(ColumnResizeEvent event) {
// Get the new width of the resized column
int width = event.getCurrentWidth();
// Get the property ID of the resized column
String column = (String) event.getPropertyId();
// Do something with the information
table.setColumnFooter(column, String.valueOf(width) + "px");
}
});
// Must be immediate to send the resize events immediately
table.setImmediate(true);
----
See <<figure.component.table.columnresize>> for a result after the columns of a
table has been resized.
[[figure.component.table.columnresize]]
.Resizing Columns
image::img/table-column-resize.png[]
[[components.table.features.reordering]]
=== Reordering Columns
If [methodname]#setColumnReorderingAllowed(true)# is set, the user can reorder
table columns by dragging them with the mouse from the column header,
[[components.table.features.collapsing]]
=== Collapsing Columns
When [methodname]#setColumnCollapsingAllowed(true)# is set, the right side of
the table header shows a drop-down list that allows selecting which columns are
shown. Collapsing columns is different than hiding columns with
[methodname]#setVisibleColumns()#, which hides the columns completely so that
they can not be made visible (uncollapsed) from the user interface.
You can collapse columns programmatically with
[methodname]#setColumnCollapsed()#. Collapsing must be enabled before collapsing
columns with the method or it will throw an [classname]#IllegalAccessException#.
[source, java]
----
// Allow the user to collapse and uncollapse columns
table.setColumnCollapsingAllowed(true);
// Collapse this column programmatically
try {
table.setColumnCollapsed("born", true);
} catch (IllegalAccessException e) {
// Can't occur - collapsing was allowed above
System.err.println("Something horrible occurred");
}
// Give enough width for the table to accommodate the
// initially collapsed column later
table.setWidth("250px");
----
See <<figure.component.table.columncollapsing>>.
[[figure.component.table.columncollapsing]]
.Collapsing Columns
image::img/table-column-collapsing.png[]
If the table has undefined width, it minimizes its width to fit the width of the
visible columns. If some columns are initially collapsed, the width of the table
may not be enough to accomodate them later, which will result in an ugly
horizontal scrollbar. You should consider giving the table enough width to
accomodate columns uncollapsed by the user.
[[components.table.features.components]]
=== Components Inside a Table
The cells of a [classname]#Table# can contain any user interface components, not
just strings. If the rows are higher than the row height defined in the default
theme, you have to define the proper row height in a custom theme.
When handling events for components inside a [classname]#Table#, such as for the
[classname]#Button# in the example below, you usually need to know the item the
component belongs to. Components do not themselves know about the table or the
specific item in which a component is contained. Therefore, the handling method
must use some other means for finding out the Item ID of the item. There are a
few possibilities. Usually the easiest way is to use the [methodname]#setData()#
method to attach an arbitrary object to a component. You can subclass the
component and include the identity information there. You can also simply search
the entire table for the item with the component, although that solution may not
be so scalable.
The example below includes table rows with a [classname]#Label# in HTML content
mode, a multiline [classname]#TextField#, a [classname]#CheckBox#, and a
[classname]#Button# that shows as a link.
[source, java]
----
// Create a table and add a style to allow setting the row height in theme.
final Table table = new Table();
table.addStyleName("components-inside");
/* Define the names and data types of columns.
* The "default value" parameter is meaningless here. */
table.addContainerProperty("Sum", Label.class, null);
table.addContainerProperty("Is Transferred", CheckBox.class, null);
table.addContainerProperty("Comments", TextField.class, null);
table.addContainerProperty("Details", Button.class, null);
/* Add a few items in the table. */
for (int i=0; i<100; i++) {
// Create the fields for the current table row
Label sumField = new Label(String.format(
"Sum is <b>$%04.2f</b><br/><i>(VAT incl.)</i>",
new Object[] {new Double(Math.random()*1000)}),
ContentMode.HTML);
CheckBox transferredField = new CheckBox("is transferred");
// Multiline text field. This required modifying the
// height of the table row.
TextField commentsField = new TextField();
commentsField.setRows(3);
// The Table item identifier for the row.
Integer itemId = new Integer(i);
// Create a button and handle its click. A Button does not
// know the item it is contained in, so we have to store the
// item ID as user-defined data.
Button detailsField = new Button("show details");
detailsField.setData(itemId);
detailsField.addClickListener(new Button.ClickListener() {
public void buttonClick(ClickEvent event) {
// Get the item identifier from the user-defined data.
Integer iid = (Integer)event.getButton().getData();
Notification.show("Link " +
iid.intValue() + " clicked.");
}
});
detailsField.addStyleName("link");
// Create the table row.
table.addItem(new Object[] {sumField, transferredField,
commentsField, detailsField},
itemId);
}
// Show just three rows because they are so high.
table.setPageLength(3);
----
See the http://demo.vaadin.com/book-examples-vaadin7/book#component.table.components.components2[on-line example, window="_blank"].
The row height has to be set higher than the default with a style rule such as
the following:
[source, css]
----
/* Table rows contain three-row TextField components. */
.v-table-components-inside .v-table-cell-content {
height: 54px;
}
----
See the http://demo.vaadin.com/book-examples-vaadin7/book#component.table.components.components2[on-line example, window="_blank"].
The table will look as shown in <<figure.components.table.components-inside>>.
[[figure.components.table.components-inside]]
.Components in a Table
image::img/table-components.png[]
[[components.table.features.iterating]]
=== Iterating Over a Table
As the items in a [classname]#Table# are not indexed, iterating over the items
has to be done using an iterator. The [methodname]#getItemIds()# method of the
[classname]#Container# interface of [classname]#Table# returns a
[classname]#Collection# of item identifiers over which you can iterate using an
[classname]#Iterator#. For an example about iterating over a [classname]#Table#,
please see
<<dummy/../../../framework/datamodel/datamodel-container#datamodel.container,"Collecting
Items in Containers">>. Notice that you may not modify the [classname]#Table#
during iteration, that is, add or remove items. Changing the data is allowed.
[[components.table.features.filtering]]
=== Filtering Table Contents
A table can be filtered if its container data source implements the
[classname]#Filterable# interface, as the default [classname]#IndexedContainer#
does. See
<<dummy/../../../framework/datamodel/datamodel-container#datamodel.container.filtered,"Filterable
Containers">>. ((("Container",
"Filterable")))
[[components.table.editing]]
== Editing the Values in a Table
Normally, a [classname]#Table# simply displays the items and their fields as
text. If you want to allow the user to edit the values, you can either put them
inside components as we did earlier or simply call
[methodname]#setEditable(true)#, in which case the cells are automatically
turned into editable fields.
Let us begin with a regular table with a some columns with usual Java types,
namely a [classname]#Date#, [classname]#Boolean#, and a [classname]#String#.
[source, java]
----
// Create a table. It is by default not editable.
Table table = new Table();
// Define the names and data types of columns.
table.addContainerProperty("Date", Date.class, null);
table.addContainerProperty("Work", Boolean.class, null);
table.addContainerProperty("Comments", String.class, null);
...
----
You could put the table in editable mode right away. We continue the example by
adding a check box to switch the table between normal and editable modes:
[source, java]
----
CheckBox editable = new CheckBox("Editable", true);
editable.addValueChangeListener(valueChange -> // Java 8
table.setEditable((Boolean) editable.getValue()));
----
Now, when you check to checkbox, the components in the table turn into editable
fields, as shown in <<figure.component.table.editable>>.
[[figure.component.table.editable]]
.A Table in Normal and Editable Mode
image::img/table-editable3.png[]
[[components.table.editing.fieldfactories]]
=== Field Factories
The field components that allow editing the values of particular types in a
table are defined in a field factory that implements the
[classname]#TableFieldFactory# interface. The default implementation is
[classname]#DefaultFieldFactory#, which offers the following crude mappings:
.Type to Field Mappings in [classname]#DefaultFieldFactory#
[options="header"]
|===============
|Property Type|Mapped to Field Class
|[classname]#Date#|A[classname]#DateField#.
|[classname]#Boolean#|A[classname]#CheckBox#.
|[classname]#Item#|A[classname]#Form#(deprecated in Vaadin 7). The fields of the form are automatically created from the item's properties using a[classname]#FormFieldFactory#. The normal use for this property type is inside a[classname]#Form#and is less useful inside a[classname]#Table#.
|__other__|A[classname]#TextField#. The text field manages conversions from the basic types, if possible.
|===============
Field factories are covered with more detail in
<<dummy/../../../framework/datamodel/datamodel-itembinding#datamodel.itembinding,"Creating
Forms by Binding Fields to Items">>. You could just implement the
[classname]#TableFieldFactory# interface, but we recommend that you extend the
[classname]#DefaultFieldFactory# according to your needs. In the default
implementation, the mappings are defined in the
[methodname]#createFieldByPropertyType()# method (you might want to look at the
source code) both for tables and forms.
ifdef::web[]
[[components.table.editing.navigation]]
=== Navigation in Editable Mode
In the editable mode, the editor fields can have focus. Pressing Tab moves the
focus to next column or, at the last column, to the first column of the next
item. Respectively, pressing kbd:[Shift+Tab] moves the focus backward. If the focus is
in the last column of the last visible item, the pressing kbd:[Tab] moves the focus
outside the table. Moving backward from the first column of the first item moves
the focus to the table itself. Some updates to the table, such as changing the
headers or footers or regenerating a column, can move the focus from an editor
component to the table itself.
The default behaviour may be undesirable in many cases. For example, the focus
also goes through any read-only editor fields and can move out of the table
inappropriately. You can provide better navigation is to use event handler for
shortcut keys such as kbd:[Tab], kbd:[Arrow Up], kbd:[Arrow Down], and kbd:[Enter].
[source, java]
----
// Keyboard navigation
class KbdHandler implements Handler {
Action tab_next = new ShortcutAction("Tab",
ShortcutAction.KeyCode.TAB, null);
Action tab_prev = new ShortcutAction("Shift+Tab",
ShortcutAction.KeyCode.TAB,
new int[] {ShortcutAction.ModifierKey.SHIFT});
Action cur_down = new ShortcutAction("Down",
ShortcutAction.KeyCode.ARROW_DOWN, null);
Action cur_up = new ShortcutAction("Up",
ShortcutAction.KeyCode.ARROW_UP, null);
Action enter = new ShortcutAction("Enter",
ShortcutAction.KeyCode.ENTER, null);
public Action[] getActions(Object target, Object sender) {
return new Action[] {tab_next, tab_prev, cur_down,
cur_up, enter};
}
public void handleAction(Action action, Object sender,
Object target) {
if (target instanceof TextField) {
// Move according to keypress
int itemid = (Integer) ((TextField) target).getData();
if (action == tab_next || action == cur_down)
itemid++;
else if (action == tab_prev || action == cur_up)
itemid--;
// On enter, just stay where you were. If we did
// not catch the enter action, the focus would be
// moved to wrong place.
if (itemid >= 0 && itemid < table.size()) {
TextField newTF = valueFields.get(itemid);
if (newTF != null)
newTF.focus();
}
}
}
}
// Panel that handles keyboard navigation
Panel navigator = new Panel();
navigator.addStyleName(Reindeer.PANEL_LIGHT);
navigator.addComponent(table);
navigator.addActionHandler(new KbdHandler());
----
The main issue in implementing keyboard navigation in an editable table is that
the editor fields do not know the table they are in. To find the parent table,
you can either look up in the component container hierarchy or simply store a
reference to the table with [methodname]#setData()# in the field component. The
other issue is that you can not acquire a reference to an editor field from the
[classname]#Table# component. One solution is to use some external collection,
such as a [classname]#HashMap#, to map item IDs to the editor fields.
[source, java]
----
// Can't access the editable components from the table so
// must store the information
final HashMap<Integer,TextField> valueFields =
new HashMap<Integer,TextField>();
----
The map has to be filled in a [classname]#TableFieldFactory#, such as in the
following. You also need to set the reference to the table there and you can
also set the initial focus there.
[source, java]
----
table.setTableFieldFactory(new TableFieldFactory () {
public Field createField(Container container, Object itemId,
Object propertyId, Component uiContext) {
TextField field = new TextField((String) propertyId);
// User can only edit the numeric column
if ("Source of Fear".equals(propertyId))
field.setReadOnly(true);
else { // The numeric column
// The field needs to know the item it is in
field.setData(itemId);
// Remember the field
valueFields.put((Integer) itemId, field);
// Focus the first editable value
if (((Integer)itemId) == 0)
field.focus();
}
return field;
}
});
----
The issues are complicated by the fact that the editor fields are not generated
for the entire table, but only for a cache window that includes the visible
items and some items above and below it. For example, if the beginning of a big
scrollable table is visible, the editor component for the last item does not
exist. This issue is relevant mostly if you want to have wrap-around navigation
that jumps from the last to first item and vice versa.
endif::web[]
[[components.table.headersfooters]]
== Column Headers and Footers
[classname]#Table# supports both column headers and footers; the headers are
enabled by default.
[[components.table.headersfooters.headers]]
=== Headers
The table header displays the column headers at the top of the table. You can
use the column headers to reorder or resize the columns, as described earlier.
By default, the header of a column is the property ID of the column, unless
given explicitly with [methodname]#setColumnHeader()#.
[source, java]
----
// Define the properties
table.addContainerProperty("lastname", String.class, null);
table.addContainerProperty("born", Integer.class, null);
table.addContainerProperty("died", Integer.class, null);
// Set nicer header names
table.setColumnHeader("lastname", "Name");
table.setColumnHeader("born", "Born");
table.setColumnHeader("died", "Died");
----
The text of the column headers and the visibility of the header depends on the
__column header mode__. The header is visible by default, but you can disable it
with [methodname]#setColumnHeaderMode(Table.COLUMN_HEADER_MODE_HIDDEN)#.
[[components.table.headersfooters.footers]]
=== Footers
The table footer can be useful for displaying sums or averages of values in a
column, and so on. The footer is not visible by default; you can enable it with
[methodname]#setFooterVisible(true)#. Unlike in the header, the column headers
are empty by default. You can set their value with
[methodname]#setColumnFooter()#. The columns are identified by their property
ID.
The following example shows how to calculate average of the values in a column:
[source, java]
----
// Have a table with a numeric column
Table table = new Table("Custom Table Footer");
table.addContainerProperty("Name", String.class, null);
table.addContainerProperty("Died At Age", Integer.class, null);
// Insert some data
Object people[][] = { {"Galileo", 77},
{"Monnier", 83},
{"Vaisala", 79},
{"Oterma", 86}};
for (int i=0; i<people.length; i++)
table.addItem(people[i], new Integer(i));
// Calculate the average of the numeric column
double avgAge = 0;
for (int i=0; i<people.length; i++)
avgAge += (Integer) people[i][1];
avgAge /= people.length;
// Set the footers
table.setFooterVisible(true);
table.setColumnFooter("Name", "Average");
table.setColumnFooter("Died At Age", String.valueOf(avgAge));
// Adjust the table height a bit
table.setPageLength(table.size());
----
The resulting table is shown in
<<figure.components.table.headersfooters.footer>>.
[[figure.components.table.headersfooters.footer]]
.A Table with a Footer
image::img/table-footer.png[]
[[components.table.headersfooters.clicks]]
=== Handling Mouse Clicks on Headers and Footers
Normally, when the user clicks a column header, the table will be sorted by the
column, assuming that the data source is [classname]#Sortable# and sorting is
not disabled. In some cases, you might want some other functionality when the
user clicks the column header, such as selecting the column in some way.
Clicks in the header cause a [classname]#HeaderClickEvent#, which you can handle
with a [classname]#Table.HeaderClickListener#. Click events on the table header
(and footer) are, like button clicks, sent immediately to server, so there is no
need to set [methodname]#setImmediate()#.
[source, java]
----
// Handle the header clicks
table.addHeaderClickListener(new Table.HeaderClickListener() {
public void headerClick(HeaderClickEvent event) {
String column = (String) event.getPropertyId();
Notification.show("Clicked " + column +
"with " + event.getButtonName());
}
});
// Disable the default sorting behavior
table.setSortDisabled(true);
----
Setting a click handler does not automatically disable the sorting behavior of
the header; you need to disable it explicitly with
[methodname]#setSortDisabled(true)#. Header click events are not sent when the
user clicks the column resize handlers to drag them.
The [classname]#HeaderClickEvent# object provides the identity of the clicked
column with [methodname]#getPropertyId()#. The [methodname]#getButton()# reports
the mouse button with which the click was made: [parameter]#BUTTON_LEFT#,
[parameter]#BUTTON_RIGHT#, or [parameter]#BUTTON_MIDDLE#. The
[methodname]#getButtonName()# a human-readable button name in English: "
[parameter]#left#", " [parameter]#right#", or " [parameter]#middle#". The
[methodname]#isShiftKey()#, [methodname]#isCtrlKey()#, etc., methods indicate if
the kbd:[Shift], kbd:[Ctrl], kbd:[Alt] or other modifier keys were pressed during the click.
Clicks in the footer cause a [classname]#FooterClickEvent#, which you can handle
with a [classname]#Table.FooterClickListener#. Footers do not have any default
click behavior, like the sorting in the header. Otherwise, handling clicks in
the footer is equivalent to handling clicks in the header.
[[components.table.columngenerator]]
== Generated Table Columns
A table can have generated columns which values can be calculated based on the
values in other columns. The columns are generated with a class implementing the
[interfacename]#Table.ColumnGenerator# interface.
The [classname]#GeneratedPropertyContainer# described in
<<dummy/../../../framework/datamodel/datamodel-container#datamodel.container.gpc,"GeneratedPropertyContainer">>
is another way to accomplish the same task at container level. In addition to
generating values, you can also use the feature for formatting or styling
columns.
ifdef::web[]
[[components.table.columngenerator.generator]]
=== Defining a Column Generator
Column generators are objects that implement the
[classname]#Table.ColumnGenerator# interface and its
[methodname]#generateCell()# method. The method gets the identity of the item
and column as its parameters, in addition to the table object, and has to return
a component. The interface is functional, so you can also define it by a lambda
expression or a method reference in Java 8.
The following example defines a generator for formatting [classname]#Double#
valued fields according to a format string (as in
[classname]#java.util.Formatter#).
[source, java]
----
/** Formats the value in a column containing Double objects. */
class ValueColumnGenerator implements Table.ColumnGenerator {
String format; /* Format string for the Double values. */
/**
* Creates double value column formatter with the given
* format string.
*/
public ValueColumnGenerator(String format) {
this.format = format;
}
/**
* Generates the cell containing the Double value.
* The column is irrelevant in this use case.
*/
public Component generateCell(Table source, Object itemId,
Object columnId) {
// Get the object stored in the cell as a property
Property prop =
source.getItem(itemId).getItemProperty(columnId);
if (prop.getType().equals(Double.class)) {
Label label = new Label(String.format(format,
new Object[] { (Double) prop.getValue() }));
// Set styles for the column: one indicating that it's
// a value and a more specific one with the column
// name in it. This assumes that the column name
// is proper for CSS.
label.addStyleName("column-type-value");
label.addStyleName("column-" + (String) columnId);
return label;
}
return null;
}
}
----
The column generator is called for all the visible (or more accurately cached)
items in a table. If the user scrolls the table to another position in the
table, the columns of the new visible rows are generated dynamically.
Generated column cells are automatically updated when a property value in the
table row changes. Note that a generated cell, even if it is a field, does not
normally have a property value bound to the table's container, so changes in
generated columns do not trigger updates in other generated columns. It should
also be noted that if a generated column cell depends on values in other rows,
changes in the other rows do not trigger automatic update. You can get notified
of such value changes by listening for them with a
[interfacename]#ValueChangeListener# in the generated components. If you do so,
you must remove such listeners when the generated components are detached from
the UI or otherwise the listeners will accumulate in the container when the
table is scrolled back and forth, causing possibly severe memory leak.
endif::web[]
ifdef::web[]
[[components.table.columngenerator.adding]]
=== Adding Generated Columns
You add new generated columns to a [classname]#Table# with
[methodname]#addGeneratedColumn()#. It takes a property ID of the generated
column as the first parameter and the generator as the second.
[source, java]
----
// Define the generated columns and their generators
table.addGeneratedColumn("date", // Java 8:
this::generateNonEditableCell);
table.addGeneratedColumn("price",
new PriceColumnGenerator());
table.addGeneratedColumn("consumption",
new ConsumptionColumnGenerator());
table.addGeneratedColumn("dailycost",
new DailyCostColumnGenerator());
----
Notice that the [methodname]#addGeneratedColumn()# always places the generated
columns as the last column, even if you defined some other order previously. You
will have to set the proper order with [methodname]#setVisibleColumns()#.
[source, java]
----
table.setVisibleColumns("date", "quantity", "price", "total");
----
endif::web[]
ifdef::web[]
[[components.table.columngenerator.editable]]
=== Generators in Editable Table
When you set a table as [parameter]#editable#, table cells change to editable
fields. When the user changes the values in the fields, the generated cells in
the same row are updated automatically. However, putting a table with generated
columns in editable mode has a few quirks. One is that the editable mode does
not affect generated columns. You have two alternatives: either you generate the
editing fields in the generator or, in case of formatter generators, remove the
generators in the editable mode to allow editing the values. The following
example uses the latter approach.
[source, java]
----
// Have a check box that allows the user
// to make the quantity and total columns editable.
final CheckBox editable = new CheckBox(
"Edit the input values - calculated columns are regenerated");
editable.setImmediate(true);
editable.addClickListener(new ClickListener() {
public void buttonClick(ClickEvent event) {
table.setEditable(editable.booleanValue());
// The columns may not be generated when we want to
// have them editable.
if (editable.booleanValue()) {
table.removeGeneratedColumn("quantity");
table.removeGeneratedColumn("total");
} else { // Not editable
// Show the formatted values.
table.addGeneratedColumn("quantity",
new ValueColumnGenerator("%.2f l"));
table.addGeneratedColumn("total",
new ValueColumnGenerator("%.2f e"));
}
// The visible columns are affected by removal
// and addition of generated columns so we have
// to redefine them.
table.setVisibleColumns("date", "quantity",
"price", "total", "consumption", "dailycost");
}
});
----
You will also have to set the editing fields in [parameter]#immediate# mode to
have the update occur immediately when an edit field loses the focus. You can
set the fields in [parameter]#immediate# mode with the a custom
[classname]#TableFieldFactory#, such as the one given below, that just extends
the default implementation to set the mode:
[source, java]
----
public class ImmediateFieldFactory extends DefaultFieldFactory {
public Field createField(Container container,
Object itemId,
Object propertyId,
Component uiContext) {
// Let the DefaultFieldFactory create the fields...
Field field = super.createField(container, itemId,
propertyId, uiContext);
// ...and just set them as immediate.
((AbstractField)field).setImmediate(true);
return field;
}
}
...
table.setTableFieldFactory(new ImmediateFieldFactory());
----
If you generate the editing fields with the column generator, you avoid having
to use such a field factory, but of course have to generate the fields for both
normal and editable modes.
<<figure.ui.table.generated>> shows a table with columns calculated (blue) and
simply formatted (black) with column generators.
[[figure.ui.table.generated]]
.Table with Generated Columns in Normal and Editable Mode
image::img/table-generatedcolumns1.png[]
endif::web[]
[[components.table.columnformatting]]
== Formatting Table Columns
The displayed values of properties shown in a table are normally formatted using
the [methodname]#toString()# method of each property. Customizing the format in
a table column can be done in several ways:
* Using [classname]#ColumnGenerator# to generate a second column that is formatted. The original column needs to be set invisible. See <<components.table.columngenerator>>.
* Using a [classname]#Converter# to convert between the property data model and its representation in the table.
* Using a [classname]#GeneratedPropertyContainer# as a wrapper around the actual container to provide formatting.
* Overriding the default [methodname]#formatPropertyValue()# in [classname]#Table#.
As using a [classname]#PropertyFormatter# is generally much more awkward than
overriding the [methodname]#formatPropertyValue()#, its use is not described
here.
You can override [methodname]#formatPropertyValue()# as is done in the following
example:
[source, java]
----
// Create a table that overrides the default
// property (column) format
final Table table = new Table("Formatted Table") {
@Override
protected String formatPropertyValue(Object rowId,
Object colId, Property property) {
// Format by property type
if (property.getType() == Date.class) {
SimpleDateFormat df =
new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
return df.format((Date)property.getValue());
}
return super.formatPropertyValue(rowId, colId, property);
}
};
// The table has some columns
table.addContainerProperty("Time", Date.class, null);
... Fill the table with data ...
----
You can also distinguish between columns by the [parameter]#colId# parameter,
which is the property ID of the column. [classname]#DecimalFormat# is useful for
formatting decimal values.
[source, java]
----
... in formatPropertyValue() ...
} else if ("Value".equals(pid)) {
// Format a decimal value for a specific locale
DecimalFormat df = new DecimalFormat("#.00",
new DecimalFormatSymbols(locale));
return df.format((Double) property.getValue());
}
...
table.addContainerProperty("Value", Double.class, null);
----
A table with the formatted date and decimal value columns is shown in
<<figure.components.table.columnformatting>>.
[[figure.components.table.columnformatting]]
.Formatted Table Columns
image::img/table-columnformatting.png[]
You can use CSS for further styling of table rows, columns, and individual cells
by using a [classname]#CellStyleGenerator#. It is described in
<<components.table.css>>.
[[components.table.css]]
== CSS Style Rules
Styling the overall style of a [classname]#Table# can be done with the following
CSS rules.
[source, css]
----
.v-table {}
.v-table-header-wrap {}
.v-table-header {}
.v-table-header-cell {}
.v-table-resizer {} /* Column resizer handle. */
.v-table-caption-container {}
.v-table-body {}
.v-table-row-spacer {}
.v-table-table {}
.v-table-row {}
.v-table-cell-content {}
----
Notice that some of the widths and heights in a table are calculated dynamically
and can not be set in CSS.
ifdef::web[]
[[components.table.css.cellstylegenerator]]
=== Generating Cell Styles With [interfacename]#CellStyleGenerator#
The [classname]#Table.CellStyleGenerator# interface allows you to set the CSS
style for each individual cell in a table. You need to implement the
[methodname]#getStyle()#, which gets the row (item) and column (property)
identifiers as parameters and can return a style name for the cell. The returned
style name will be concatenated to prefix "
[literal]#++v-table-cell-content-++#".
The [methodname]#getStyle()# is called also for each row, so that the
[parameter]#propertyId# parameter is [literal]#++null++#. This allows setting a
row style.
Alternatively, you can use a [classname]#Table.ColumnGenerator# (see
<<components.table.columngenerator>>) to generate the actual UI components of
the cells and add style names to them.
[source, java]
----
Table table = new Table("Table with Cell Styles");
table.addStyleName("checkerboard");
// Add some columns in the table. In this example, the property
// IDs of the container are integers so we can determine the
// column number easily.
table.addContainerProperty("0", String.class, null, "", null, null);
for (int i=0; i<8; i++)
table.addContainerProperty(""+(i+1), String.class, null,
String.valueOf((char) (65+i)), null, null);
// Add some items in the table.
table.addItem(new Object[]{
"1", "R", "N", "B", "Q", "K", "B", "N", "R"}, new Integer(0));
table.addItem(new Object[]{
"2", "P", "P", "P", "P", "P", "P", "P", "P"}, new Integer(1));
for (int i=2; i<6; i++)
table.addItem(new Object[]{String.valueOf(i+1),
"", "", "", "", "", "", "", ""}, new Integer(i));
table.addItem(new Object[]{
"7", "P", "P", "P", "P", "P", "P", "P", "P"}, new Integer(6));
table.addItem(new Object[]{
"8", "R", "N", "B", "Q", "K", "B", "N", "R"}, new Integer(7));
table.setPageLength(8);
// Set cell style generator
table.setCellStyleGenerator(new Table.CellStyleGenerator() {
public String getStyle(Object itemId, Object propertyId) {
// Row style setting, not relevant in this example.
if (propertyId == null)
return "green"; // Will not actually be visible
int row = ((Integer)itemId).intValue();
int col = Integer.parseInt((String)propertyId);
// The first column.
if (col == 0)
return "rowheader";
// Other cells.
if ((row+col)%2 == 0)
return "black";
else
return "white";
}
});
----
You can then style the cells, for example, as follows:
[source, css]
----
/* Center the text in header. */
.v-table-header-cell {
text-align: center;
}
/* Basic style for all cells. */
.v-table-checkerboard .v-table-cell-content {
text-align: center;
vertical-align: middle;
padding-top: 12px;
width: 20px;
height: 28px;
}
/* Style specifically for the row header cells. */
.v-table-cell-content-rowheader {
background: #E7EDF3
url(../default/table/img/header-bg.png) repeat-x scroll 0 0;
}
/* Style specifically for the "white" cells. */
.v-table-cell-content-white {
background: white;
color: black;
}
/* Style specifically for the "black" cells. */
.v-table-cell-content-black {
background: black;
color: white;
}
----
The table will look as shown in <<figure.components.table.cell-style>>.
[[figure.components.table.cell-style]]
.Cell Style Generator for a Table
image::img/table-cellstylegenerator1.png[]
endif::web[]
(((range="endofrange", startref="term.components.table")))
|