diff options
author | Markus Koivisto <markus@vaadin.com> | 2016-01-22 14:55:18 +0200 |
---|---|---|
committer | Markus Koivisto <markus@vaadin.com> | 2016-01-22 14:55:18 +0200 |
commit | 99d6de546c74f0eed230ea8253dda6b85109d2e7 (patch) | |
tree | 10fc21c557566fe3241e6e13499df18d80f8dcb2 /documentation/components/components-grid.asciidoc | |
parent | 610736d9f373d4b37fd39ff8f90aabd13eab7926 (diff) | |
download | vaadin-framework-99d6de546c74f0eed230ea8253dda6b85109d2e7.tar.gz vaadin-framework-99d6de546c74f0eed230ea8253dda6b85109d2e7.zip |
Add documentation to master branch
Change-Id: I2504bb10f1ae73ec0cbc08b7ba5a88925caa1674
Diffstat (limited to 'documentation/components/components-grid.asciidoc')
-rw-r--r-- | documentation/components/components-grid.asciidoc | 1407 |
1 files changed, 1407 insertions, 0 deletions
diff --git a/documentation/components/components-grid.asciidoc b/documentation/components/components-grid.asciidoc new file mode 100644 index 0000000000..e2013b8daa --- /dev/null +++ b/documentation/components/components-grid.asciidoc @@ -0,0 +1,1407 @@ +--- +title: Grid +order: 24 +layout: page +--- + +[[components.grid]] += [classname]#Grid# + +ifdef::web[] +[.sampler] +image:{live-demo-image}[alt="Live Demo", link="http://demo.vaadin.com/sampler/#ui/grids-and-trees/grid] +endif::web[] + +((("[classname]#Grid#"))) +[classname]#Grid# is many things, and perhaps the most versatile and powerful +component in Vaadin. Like [classname]#Table#, it allows presenting and editing +tabular data, but escapes many of [classname]#Table#'s limitations. Efficient +lazy loading of data while scrolling greatly improves performance. Grid is +scalable, mobile friendly, and extensible. + +[[components.grid.overview]] +== Overview + +[classname]#Grid# is for displaying and editing tabular data laid out in rows +and columns. At the top, a __header__ can be shown, and a __footer__ at the +bottom. In addition to plain text, the header and footer can contain HTML and +components. Having components in the header allows implementing filtering +easily. The grid data can be sorted by clicking on a column header; +shift-clicking a column header enables secondary sorting criteria. + +[[figure.components.grid.features]] +.A [classname]#Grid# Component +image::img/grid-features.png[] + +The data area can be scrolled both vertically and horizontally. The leftmost +columns can be frozen, so that they are never scrolled out of the view. The data +is loaded lazily from the server, so that only the visible data is loaded. The +smart lazy loading functionality gives excellent user experience even with low +bandwidth, such as mobile devices. + +The grid data can be edited with a row-based editor after double-clicking a row. +The fields are generated with a field factory, or set explicitly, and bound to +data with a field group. + +Grid is fully themeable with CSS and style names can be set for all grid +elements. For data rows and cells, the styles can be generated with a row or +cell style generator. + +Finally, [classname]#Grid# is designed to be extensible and used just as well +for client-side development - its GWT API is nearly identical to the server-side +API, including data binding. + +[[components.grid.overview.table]] +=== Differences to Table + +In addition to core features listed above, [classname]#Grid# has the following +API-level and functional differences to Table: + +* Grid is not a [interfacename]#Container# itself, even though it can be bound to a container data source. Consequently, columns are defined differently, and so forth. +* Rows can be added with [methodname]#addRow()# shorthand (during initialization) instead of [methodname]#addItem()#. +* Use [methodname]#setHeightByRows()# and [methodname]#setHeightMode()# instead of [methodname]#setPageLength()# to set the height in number of rows. +* Grid does not extend [classname]#AbstractSelect# and is not a field, but has its own selection API. [methodname]#addSelectionListener()# is called to define a [interfacename]#SelectionListener#. The listener also receives a collection of deselected items. +* Grid does not support having all cells in editable mode, it only supports row-based editing, with a row mini-editor that allows saving or discarding the changes. +* Grid has no generated columns. Instead, the container data source can be wrapped around a [classname]#GeneratedPropertyContainer#. +* No column icons; you can implement them in a column with an [classname]#ImageRenderer#. +* Components can not be shown in Grid cells; instead the much more efficient renderers can be used for the most common cases, and row editor for editing values. +* Limited support for drag and drop: the user can drag columns to reorder them. + +In addition, Grid has the following visual changes: + +* Multiple selection is indicated with check boxes in addition to highlighting. +* Grid does not show the row loading indicator like Table does. + + + +[[components.grid.data]] +== Binding to Data + +[classname]#Grid# is normally used by binding it to a container data source, +described in +<<dummy/../../../framework/datamodel/datamodel-container#datamodel.container,"Collecting +Items in Containers">>. The container must implement +[interfacename]#Container.Indexed# interface. By default, it is bound to an +[classname]#IndexedContainer#; Grid offers some shorthand methods to operate on +the default container, as described later. + +You can set the container in the constructor or with +[methodname]#setContainerDataSource()#. + +For example, if you have a collection of beans, you could wrap them in a Vaadin +[classname]#BeanContainer# or [classname]#BeanItemContainer#, and bind to a [classname]#Grid# as follows + + +[source, java] +---- +// Have some data +Collection<Person> people = Lists.newArrayList( + new Person("Nicolaus Copernicus", 1543), + new Person("Galileo Galilei", 1564), + new Person("Johannes Kepler", 1571)); + +// Have a container of some type to contain the data +BeanItemContainer<Person> container = + new BeanItemContainer<Person>(Person.class, people); + +// Create a grid bound to the container +Grid grid = new Grid(container); +grid.setColumnOrder("name", "born"); +layout.addComponent(grid); +---- + +Note that you need to override [methodname]#equals()# and [methodname]#hashcode()# for +the bean ([classname]#Person#) class to make the [classname]#BeanItemContainer# work properly. + +[[components.grid.basic.manual]] +=== Default Data Source and Shorthands + +Sometimes, when you have just a few fixed items that you want to display, you +can define the grid columns and add data rows manually. [classname]#Grid# is by +default bound to a [classname]#IndexedContainer#. You can define new columns +(container properties) with [methodname]#addColumn()# and then add rows (items) +with [methodname]#addRow()#. The types in the row data must match the defined +column types. + +For example: + + +[source, java] +---- +// Create a grid +Grid grid = new Grid(); + +// Define some columns +grid.addColumn("name", String.class); +grid.addColumn("born", Integer.class); + +// Add some data rows +grid.addRow("Nicolaus Copernicus", 1543); +grid.addRow("Galileo Galilei", 1564); +grid.addRow("Johannes Kepler", 1571); + +layout.addComponent(grid); +---- + +Or, if you have the data in an array: + + +[source, java] +---- +// Have some data +Object[][] people = { {"Nicolaus Copernicus", 1543}, + {"Galileo Galilei", 1564}, + {"Johannes Kepler", 1571}}; +for (Object[] person: people) + grid.addRow(person); +---- + +Note that you can not use [methodname]#addRow()# to add items if the container +is read-only or has read-only columns, such as generated columns. + + + +[[components.grid.selection]] +== Handling Selection Changes + +Selection in [classname]#Grid# is handled a bit differently from other selection +components, as it is not an [classname]#AbstractSelect#. Grid supports both +single and multiple selection, defined by the __selection mode__. Selection +events can be handled with a [interfacename]#SelectionListener#. + +[[components.grid.selection.mode]] +=== Selection Mode + +A [classname]#Grid# can be set to be in [literal]#++SINGLE++# (default), +[literal]#++MULTI++#, or [literal]#++NONE++# selection mode, defined in the +[classname]#Grid.SelectionMode# enum. + + +[source, java] +---- +// Use single-selection mode (default) +grid.setSelectionMode(SelectionMode.SINGLE); +---- + +Empty (null) selection is allowed by default, but can be disabled +with [methodname]#setDeselectAllowed()# in single-selection mode. + +The selection is handled with a different selection model object in each +respective selection mode: [classname]#SingleSelectionModel#, +[classname]#MultiSelectionModel#, and [classname]#NoSelectionModel# (in which +selection is always empty). + + +[source, java] +---- +// Pre-select an item +SingleSelectionModel selection = + (SingleSelectionModel) grid.getSelectionModel(); +selection.select( // Select 3rd item + grid.getContainerDataSource().getIdByIndex(2)); +---- + + +[[components.grid.selection.single]] +=== Handling Selection + +Changes in the selection can be handled with a +[interfacename]#SelectionListener#. You need to implement the +[methodname]#select()# method, which gets a [classname]#SelectionEvent# as +parameter. In addition to selection, you can handle clicks on rows or cells with +a [interfacename]#ItemClickListener#. + +You can get the new selection from the selection event with +[methodname]#getSelected()#, which returns a set of item IDs, or more simply +from the grid or the selection model with [methodname]#getSelectedRow()#, which +returns the single selected item ID. + +For example: + + +[source, java] +---- +grid.addSelectionListener(selectionEvent -> { // Java 8 + // Get selection from the selection model + Object selected = ((SingleSelectionModel) + grid.getSelectionModel()).getSelectedRow(); + + if (selected != null) + Notification.show("Selected " + + grid.getContainerDataSource().getItem(selected) + .getItemProperty("name")); + else + Notification.show("Nothing selected"); +}); +---- + +The current selection can be obtained from the [classname]#Grid# object by +[methodname]#getSelectedRow()# or [methodname]#getSelectedRows()#, which return +one (in single-selection mode) or all (in multi-selection mode) selected items. + + +[WARNING] +==== +Note that changes to the item set of the container data source are not +automatically reflected in the selection model and may cause the selection model +to refer to stale item IDs. This always occurs, for example, when you delete the +selected item or items. So, if you modify the item set of the container, you +should synchronize or reset the selection with the container, such as by calling +[methodname]#reset()# on the selection model. + +==== + + + + +[[components.grid.selection.multi]] +=== Multiple Selection + +In the multiple selection mode, a user can select multiple items by clicking on +the checkboxes in the leftmost column, or by using the kbd:[Space] to select/deselect the currently focused row. +Space bar is the default key for toggling the selection, but it can be customized. + +[[figure.components.grid.selection.multi]] +.Multiple Selection in [classname]#Grid# +image::img/grid-selection-multi.png[] + +The selection is managed through the [classname]#MultiSelectionMode# class. The +currently selected rows can be set with [methodname]#setSelected()# by a +collection of item IDs, or you can use [methodname]#select()# to add items to +the selection. + + +[source, java] +---- +// Grid in multi-selection mode +Grid grid = new Grid(exampleDataSource()); +grid.setSelectionMode(SelectionMode.MULTI); + +// Pre-select some items +MultiSelectionModel selection = + (MultiSelectionModel) grid.getSelectionModel(); +selection.setSelected( // Items 2-4 + grid.getContainerDataSource().getItemIds(2, 3)); + +---- + +The current selection can be read with [methodname]#getSelectedRows()#; either +in the [classname]#MultiSelectionMode# object or in the [classname]#Grid#. + + +[source, java] +---- +// Allow deleting the selected items +Button delSelected = new Button("Delete Selected", e -> { + // Delete all selected data items + for (Object itemId: selection.getSelectedRows()) + grid.getContainerDataSource().removeItem(itemId); + + // Otherwise out of sync with container + grid.getSelectionModel().reset(); + + // Disable after deleting + e.getButton().setEnabled(false); +}); +delSelected.setEnabled(grid.getSelectedRows().size() > 0); +---- + +Changes in the selection can be handled with a +[interfacename]#SelectionListener#. The selection event object provides +[methodname]#getAdded()# and [methodname]#getRemoved()# to allow determining the +differences in the selection change. + + +[source, java] +---- +// Handle selection changes +grid.addSelectionListener(selection -> { // Java 8 + Notification.show(selection.getAdded().size() + + " items added, " + + selection.getRemoved().size() + + " removed."); + + // Allow deleting only if there's any selected + deleteSelected.setEnabled( + grid.getSelectedRows().size() > 0); +}); +---- + + +[[components.grid.selection.clicks]] +=== Focus and Clicks + +In addition to selecting rows, you can focus individual cells. The focus can be +moved with arrow keys and, if editing is enabled, pressing kbd:[Enter] opens the +editor. Normally, pressing kbd:[Tab] or kbd:[Shift+Tab] moves the focus to another component, +as usual. + +When editing or in unbuffered mode, kbd:[Tab] or kbd:[Shift+Tab] moves the focus to the next or +previous cell. The focus moves from the last cell of a row forward to the +beginning of the next row, and likewise, from the first cell backward to the +end of the previous row. Note that you can extend [classname]#DefaultEditorEventHandler# +to change this behavior. + +With the mouse, you can focus a cell by clicking on it. The clicks can be handled +with an [interfacename]#ItemClickListener#. The [classname]#ItemClickEvent# +object contains various information, most importantly the ID of the clicked row +and column. + + +[source, java] +---- +grid.addItemClickListener(event -> // Java 8 + Notification.show("Value: " + + container.getContainerProperty(event.getItemId(), + event.getPropertyId()).getValue().toString())); +---- + +The clicked grid cell is also automatically focused. + +The focus indication is themed so that the focused cell has a visible focus +indicator style by default, while the row doesn't. You can enable row focus, as +well as disable cell focus, in a custom theme. See <<components.grid.css>>. + + + +[[components.grid.columns]] +== Configuring Columns + +Columns are normally defined in the container data source. The +[methodname]#addColumn()# method can be used to add columns to a container that +supports it, such as the default [classname]#IndexedContainer#. + +Column configuration is defined in [classname]#Grid.Column# objects, which can +be obtained from the grid with [methodname]#getColumn()# by the column +(property) ID. + + +[source, java] +---- +Grid.Column bornColumn = grid.getColumn("born"); +bornColumn.setHeaderCaption("Born"); +---- + +In the following, we describe the basic column configuration. + +[[components.grid.columns.order]] +=== Column Order + +You can set the order of columns with [methodname]#setColumnOrder()# for the +grid. Columns that are not given for the method are placed after the specified +columns in their natural order. + + +[source, java] +---- +grid.setColumnOrder("firstname", "lastname", "born", + "birthplace", "died"); +---- + +Note that the method can not be used to hide columns. You can hide columns with +the [methodname]#removeColumn()#, as described later, or by hiding them in a +[classname]#GeneratedPropertyContainer#. + + +[[components.grid.columns.removing]] +=== Hiding Columns + +Columns can be hidden by removing them with [methodname]#removeColumn()#. You +can remove all columns with [methodname]#removeAllColumns()#. The removed columns +are only removed from the grid, not from the container data source. + +To restore a previously removed column, you can call [methodname]#addColumn()# +with the property ID. Instead of actually adding another column to the data +source, it merely restores the previously removed one. However, column settings +such as header or editor are not restored, but must be redone. + +You can also hide columns at container-level. At least +[classname]#GeneratedpropertyContainer# allows doing so, as described in +<<dummy/../../../framework/datamodel/datamodel-container#datamodel.container.gpc,"GeneratedPropertyContainer">>. + + +[[components.grid.columns.captions]] +=== Column Captions + +Column captions are displayed in the grid header. The default captions are +generated automatically from the property ID. You can set the header caption +explicitly through the column object with [methodname]#setHeaderCaption()#. + + +[source, java] +---- +Grid.Column bornColumn = grid.getColumn("born"); +bornColumn.setHeaderCaption("Born"); +---- + +This is equivalent to setting it with [methodname]#setText()# for the header +cell; the [classname]#HeaderCell# also allows setting the caption in HTML or as +a component, as well as styling it, as described later in +<<components.grid.headerfooter>>. + + +[[components.grid.columns.width]] +=== Column Widths + +Columns have by default undefined width, which causes automatic sizing based on +the widths of the displayed data. You can set column widths explicitly by pixel +value with [methodname]#setWidth()#, or relatively using expand ratios with +[methodname]#setExpandRatio()#. + +When using expand ratios, the columns with a non-zero expand ratio use the extra +space remaining from other columns, in proportion to the defined ratios. + +You can specify minimum and maximum widths for the expanding columns with +[methodname]#setMinimumWidth()# and [methodname]#setMaximumWidth()#, +respectively. + +The user can resize columns by dragging their separators with the mouse. When resized manually, +all the columns widths are set to explicit pixel values, even if they had +relative values before. + +[[components.grid.columns.frozen]] +=== Frozen Columns + +You can set the number of columns to be frozen with +[methodname]#setFrozenColumnCount()#, so that they are not scrolled off when +scrolling horizontally. + + +[source, java] +---- +grid.setFrozenColumnCount(2); +---- + +Setting the count to [parameter]#0# disables frozen data columns; setting it to +[parameter]#-1# also disables the selection column in multi-selection mode. + + + +[[components.grid.generatedcolumns]] +== Generating Columns + +Columns with values computed from other columns or in some other way can be +generated with a container or data model that generates the property values. The +[classname]#GeneratedPropertyContainer# can be used for this purpose. It wraps +around any indexed container to extend its properties with read-only generated +properties. The generated properties can have same IDs as the original ones, +thereby replacing them with formatted or converted values. See +<<dummy/../../../framework/datamodel/datamodel-container#datamodel.container.gpc,"GeneratedPropertyContainer">> +for a detailed description of using it. + +Generated columns are read-only, so you can not add grid rows with +[methodname]#addRow()#. In editable mode, editor fields are not generated for +generated columns. + +Note that, while [classname]#GeneratedPropertyContainer# implements +[interfacename]#Container.Sortable#, the wrapped container might not, and also +sorting on the generated properties requires special handling. In such cases, +generated properties or the entire container might not actually be sortable. + + +[[components.grid.renderer]] +== Column Renderers + +A __renderer__ is a feature that draws the client-side representation of a data +value. This allows having images, HTML, and buttons in grid cells. + +[[figure.components.grid.renderer]] +.Column Renderers: Image, Date, HTML, and Button +image::img/grid-renderers.png[] + +Renderers implement the [interfacename]#Renderer# interface. You set the column +renderer in the [classname]#Grid.Column# object as follows: + + +[source, java] +---- +grid.addColumn("born", Integer.class); +... +Grid.Column bornColumn = grid.getColumn("born"); +bornColumn.setRenderer(new NumberRenderer("born in %d AD")); +---- + +Renderers require a specific data type for the column. To convert to a property +type to a type required by a renderer, you can pass an optional +[interfacename]#Converter# to [methodname]#setRenderer()#, as described later in +this section. A converter can also be used to (pre)format the property values. +The converter is run on the server-side, before sending the values to the +client-side to be rendered with the renderer. + +The following renderers are available, as defined in the server-side +[package]#com.vaadin.ui.renderers# package: + +[classname]#ButtonRenderer#:: Renders the data value as the caption of a button. A +[interfacename]#RendererClickListener# can be given to handle the button clicks. + +ifdef::web[] +Typically, a button renderer is used to display buttons for operating on a data +item, such as edit, view, delete, etc. It is not meaningful to store the button +captions in the data source, rather you want to generate them, and they are +usually all identical. + + ++ +[source, java] +---- +BeanItemContainer<Person> people = + new BeanItemContainer<>(Person.class); + +people.addBean(new Person("Nicolaus Copernicus", 1473)); +people.addBean(new Person("Galileo Galilei", 1564)); +people.addBean(new Person("Johannes Kepler", 1571)); + +// Generate button caption column +GeneratedPropertyContainer gpc = + new GeneratedPropertyContainer(people); +gpc.addGeneratedProperty("delete", + new PropertyValueGenerator<String>() { + + @Override + public String getValue(Item item, Object itemId, + Object propertyId) { + return "Delete"; // The caption + } + + @Override + public Class<String> getType() { + return String.class; + } +}); + +// Create a grid +Grid grid = new Grid(gpc); + +// Render a button that deletes the data row (item) +grid.getColumn("delete") + .setRenderer(new ButtonRenderer(e -> // Java 8 + grid.getContainerDataSource() + .removeItem(e.getItemId()))); +---- +endif::web[] +[classname]#ImageRenderer#:: Renders the cell as an image. The column type must be a +[interfacename]#Resource#, as described in +<<dummy/../../../framework/application/application-resources#application.resources,"Images +and Other Resources">>; only [classname]#ThemeResource# and +[classname]#ExternalResource# are currently supported for images in +[classname]#Grid#. + +ifdef::web[] + +[source, java] +---- +grid.addColumn("picture", Resource.class) + .setRenderer(new ImageRenderer()); +... +// Add some data rows +grid.addRow(new ThemeResource("img/copernicus-128px.jpg"), + "Nicolaus Copernicus", 1543); +grid.addRow(new ThemeResource("img/galileo-128px.jpg"), + "Galileo Galilei", 1564); +---- + ++ +Instead of creating the resource objects explicitly, as was done above, you +could generate them dynamically from file name strings using a +[interfacename]#Converter# for the column. + + ++ +[source, java] +---- +// Define some columns +grid.addColumn("picture", String.class); // Filename +grid.addColumn("name", String.class); + +// Set the image renderer +grid.getColumn("picture").setRenderer(new ImageRenderer(), + new Converter<Resource, String>() { + @Override + public String convertToModel(Resource value, + Class<? extends String> targetType, Locale l) + throws Converter.ConversionException { + return "not needed"; + } + + @Override + public Resource convertToPresentation(String value, + Class<? extends Resource> targetType, Locale l) + throws Converter.ConversionException { + return new ThemeResource("img/" + value); + } + + @Override + public Class<String> getModelType() { + return String.class; + } + + @Override + public Class<Resource> getPresentationType() { + return Resource.class; + } +}); + +// Add some data rows +grid.addRow("copernicus-128px.jpg", "Nicolaus Copernicus"); +grid.addRow("galileo-128px.jpg", "Galileo Galilei"); +grid.addRow("kepler-128px.jpg", "Johannes Kepler"); +---- ++ +You also need to define the row heights so that the images fit there. You can +set it in the theme for all data cells or for the column containing the images. + ++ +For the latter way, first define a CSS style name for grid and the column: + + ++ +[source, java] +---- +grid.setStyleName("gridwithpics128px"); +grid.setCellStyleGenerator(cell -> + "picture".equals(cell.getPropertyId())? + "imagecol" : null); +---- +ifdef::web[] ++ +Then, define the style in CSS (Sass): +endif::web[] + + ++ +[source, css] +---- +.gridwithpics128px .imagecol { + height: 128px; + background: black; + text-align: center; +} +---- +endif::web[] +[classname]#DateRenderer#:: Formats a column with a [classname]#Date# type using string formatter. The +format string is same as for [methodname]#String.format()# in Java API. The date +is passed in the parameter index 1, which can be omitted if there is only one +format specifier, such as " [literal]#++%tF++#". + +ifdef::web[] + +[source, java] +---- +Grid.Column bornColumn = grid.getColumn("born"); +bornColumn.setRenderer( + new DateRenderer("%1$tB %1$te, %1$tY", + Locale.ENGLISH)); +---- + ++ +Optionally, a locale can be given. Otherwise, the default locale (in the +component tree) is used. + +endif::web[] +[classname]#HTMLRenderer#:: Renders the cell as HTML. This allows formatting cell content, as well as using +HTML features such as hyperlinks. + +ifdef::web[] +First, set the renderer in the [classname]#Grid.Column# object: + + ++ +[source, java] +---- +grid.addColumn("link", String.class) + .setRenderer(new HtmlRenderer()); +---- +ifdef::web[] ++ +Then, in the grid data, give the cell content: +endif::web[] + + ++ +[source, java] +---- +grid.addRow("Nicolaus Copernicus", 1543, + "<a href='http://en.wikipedia.org/wiki/" + + "Nicolaus_Copernicus' target='_top'>info</a>"); +---- ++ +You could also use a [interfacename]#PropertyFormatter# or a generated column to +generate the HTML for the links. + +endif::web[] +[classname]#NumberRenderer#:: Formats column values with a numeric type extending [classname]#Number#: +[classname]#Integer#, [classname]#Double#, etc. The format can be specified +either by the subclasses of [classname]#java.text.NumberFormat#, namely +[classname]#DecimalFormat# and [classname]#ChoiceFormat#, or by +[methodname]#String.format()#. + +ifdef::web[] +For example: + + ++ +[source, java] +---- +// Define some columns +grid.addColumn("name", String.class); +grid.addColumn("born", Integer.class); +grid.addColumn("sletters", Integer.class); +grid.addColumn("rating", Double.class); + +// Use decimal format +grid.getColumn("born").setRenderer(new NumberRenderer( + new DecimalFormat("in #### AD"))); + +// Use textual formatting on numeric ranges +grid.getColumn("sletters").setRenderer(new NumberRenderer( + new ChoiceFormat("0#none|1#one|2#multiple"))); + +// Use String.format() formatting +grid.getColumn("rating").setRenderer(new NumberRenderer( + "%02.4f", Locale.ENGLISH)); + +// Add some data rows +grid.addRow("Nicolaus Copernicus", 1473, 2, 0.4); +grid.addRow("Galileo Galilei", 1564, 0, 4.2); +grid.addRow("Johannes Kepler", 1571, 1, 2.3); +---- +endif::web[] +[classname]#ProgressBarRenderer#:: Renders a progress bar in a column with a [classname]#Double# type. The value +must be between 0.0 and 1.0. + +ifdef::web[] +For example: + + ++ +[source, java] +---- +// Define some columns +grid.addColumn("name", String.class); +grid.addColumn("rating", Double.class) + .setRenderer(new ProgressBarRenderer()); + +// Add some data rows +grid.addRow("Nicolaus Copernicus", 0.1); +grid.addRow("Galileo Galilei", 0.42); +grid.addRow("Johannes Kepler", 1.0); +---- +endif::web[] +[classname]#TextRenderer#:: Displays plain text as is. Any HTML markup is quoted. + + + +[[components.grid.renderer.custom]] +=== Custom Renderers + +Renderers are component extensions that require a client-side counterpart. See +<<dummy/../../../framework/clientsidewidgets/clientsidewidgets-grid#clientsidewidgets.grid.renderers,"Renderers">> +for information on implementing custom renderers. + + +[[components.grid.renderer.converter]] +=== Converting for Rendering + +Optionally, you can give a [interfacename]#Converter# in the +[methodname]#setRenderer()#, or define it for the column, to convert the data +value to an intermediary representation that is rendered by the renderer. For +example, when using an [classname]#ImageRenderer#, you could store the image file name +in the data column, which the converter would convert to a resource, which would +then be rendered by the renderer. + +In the following example, we use a converter and [classname]#HTMLRenderer# to display boolean +values as [classname]#FontAwesome# icons +[source, java] +---- +// Have a column for hyperlink paths to Wikipedia +grid.addColumn("truth", Boolean.class); +Grid.Column truth = grid.getColumn("truth"); +truth.setRenderer(new HtmlRenderer(), + new StringToBooleanConverter( + FontAwesome.CHECK_CIRCLE_O.getHtml(), + FontAwesome.CIRCLE_O.getHtml())); +... +---- + +In this example, we use a converter to format URL paths to complete +HTML hyperlinks with [classname]#HTMLRenderer#: + + +[source, java] +---- +// Have a column for hyperlink paths to Wikipedia +grid.addColumn("link", String.class); + +Grid.Column linkColumn = grid.getColumn("link"); +linkColumn.setRenderer(new HtmlRenderer(), + new Converter<String,String>(){ + @Override + public String convertToModel(String value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + return "not implemented"; + } + + @Override + public String convertToPresentation(String value, + Class<? extends String> targetType, Locale locale) + throws Converter.ConversionException { + return "<a href='http://en.wikipedia.org/wiki/" + + value + "' target='_blank'>more info</a>"; + } + + @Override + public Class<String> getModelType() { + return String.class; + } + + @Override + public Class<String> getPresentationType() { + return String.class; + } +}); + +// Data with a hyperlink path in the third column +grid.addRow("Nicolaus Copernicus", 1473, + "Nicolaus_Copernicus"); +... +---- + +A [classname]#GeneratedPropertyContainer# could be used for much the same +purpose. + + + +[[components.grid.headerfooter]] +== Header and Footer + +A grid by default has a header, which displays column names, and can have a +footer. Both can have multiple rows and neighbouring header row cells can be +joined to feature column groups. + +[[components.grid.headerfooter.adding]] +=== Adding and Removing Header and Footer Rows + +A new header row is added with [methodname]#prependHeaderRow()#, which adds it +at the top of the header, [methodname]#appendHeaderRow()#, which adds it at the +bottom of the header, or with [methodname]#addHeaderRowAt()#, which inserts it +at the specified 0-base index. All of the methods return a +[classname]#HeaderRow# object, which you can use to work on the header further. + + +[source, java] +---- +// Group headers by joining the cells +HeaderRow groupingHeader = grid.prependHeaderRow(); +... + +// Create a header row to hold column filters +HeaderRow filterRow = grid.appendHeaderRow(); +... +---- + +Similarly, you can add footer rows with [methodname]#appendFooterRow()#, +[methodname]#prependFooterRow()#, and [methodname]#addFooterRowAt()#. + + +[[components.grid.headerfooter.joining]] +=== Joining Header and Footer Cells + +You can join two or more header or footer cells with the [methodname]#join()# +method. For header cells, the intention is usually to create column grouping, +while for footer cells, you typically calculate sums or averages. + + +[source, java] +---- +// Group headers by joining the cells +HeaderRow groupingHeader = grid.prependHeaderRow(); +HeaderCell namesCell = groupingHeader.join( + groupingHeader.getCell("firstname"), + groupingHeader.getCell("lastname")).setText("Person"); +HeaderCell yearsCell = groupingHeader.join( + groupingHeader.getCell("born"), + groupingHeader.getCell("died"), + groupingHeader.getCell("lived")).setText("Dates of Life"); +---- + + +[[components.grid.headerfooter.content]] +=== Text and HTML Content + +You can set the header caption with [methodname]#setText()#, in which case any +HTML formatting characters are quoted to ensure security. + + +[source, java] +---- +HeaderRow mainHeader = grid.getDefaultHeaderRow(); +mainHeader.getCell("firstname").setText("First Name"); +mainHeader.getCell("lastname").setText("Last Name"); +mainHeader.getCell("born").setText("Born In"); +mainHeader.getCell("died").setText("Died In"); +mainHeader.getCell("lived").setText("Lived For"); +---- + +To use raw HTML in the captions, you can use [methodname]#setHtml()#. + + +[source, java] +---- +namesCell.setHtml("<b>Names</b>"); +yearsCell.setHtml("<b>Years</b>"); +---- + + +[[components.grid.headerfooter.components]] +=== Components in Header or Footer + +You can set a component in a header or footer cell with +[methodname]#setComponent()#. Often, this feature is used to allow filtering, as +described in <<components.grid.filtering>>, which also gives an example of the +use. + + + +[[components.grid.filtering]] +== Filtering + +The ability to include components in the grid header can be used to create +filters for the grid data. Filtering is done in the container data source, so +the container must be of type that implements +[interfacename]#Container.Filterable#. + +[[figure.components.grid.filtering]] +.Filtering Grid +image::img/grid-filtering.png[] + +The filtering illustrated in <<figure.components.grid.filtering>> can be created +as follows: + + +[source, java] +---- +// Have a filterable container +IndexedContainer container = exampleDataSource(); + +// Create a grid bound to it +Grid grid = new Grid(container); +grid.setSelectionMode(SelectionMode.NONE); +grid.setWidth("500px"); +grid.setHeight("300px"); + +// Create a header row to hold column filters +HeaderRow filterRow = grid.appendHeaderRow(); + +// Set up a filter for all columns +for (Object pid: grid.getContainerDataSource() + .getContainerPropertyIds()) { + HeaderCell cell = filterRow.getCell(pid); + + // Have an input field to use for filter + TextField filterField = new TextField(); + filterField.setColumns(8); + + // Update filter When the filter input is changed + filterField.addTextChangeListener(change -> { + // Can't modify filters so need to replace + container.removeContainerFilters(pid); + + // (Re)create the filter if necessary + if (! change.getText().isEmpty()) + container.addContainerFilter( + new SimpleStringFilter(pid, + change.getText(), true, false)); + }); + cell.setComponent(filterField); +} +---- + + +[[components.grid.sorting]] +== Sorting + +A user can sort the data in a grid on a column by clicking the column header. +Clicking another time on the current sort column reverses the sort direction. +Clicking on other column headers while keeping the Shift key pressed adds a +secondary or more sort criteria. + +[[figure.components.grid.sorting]] +.Sorting Grid on Multiple Columns +image::img/grid-sorting.png[] + +Defining sort criteria programmatically can be done with the various +alternatives of the [methodname]#sort()# method. You can sort on a specific +column with [methodname]#sort(Object propertyId)#, which defaults to ascending +sorting order, or [methodname]#sort(Object propertyId, SortDirection +direction)#, which allows specifying the sort direction. + + +[source, java] +---- +grid.sort("name", SortDirection.DESCENDING); +---- + +To sort on multiple columns, you need to use the fluid sort API with +[methodname]#sort(Sort)#, which allows chaining sorting rules. Sorting rules are +created with the static [methodname]#by()# method, which defines the primary +sort column, and [methodname]#then()#, which can be used to specify any +secondary sort columns. They default to ascending sort order, but the sort +direction can be given with an optional parameter. + + +[source, java] +---- +// Sort first by city and then by name +grid.sort(Sort.by("city", SortDirection.ASCENDING) + .then("name", SortDirection.DESCENDING)); +---- + +The container data source must support sorting. At least, it must implement +[interfacename]#Container.Sortable#. Note that when using +[classname]#GeneratedPropertyContainer#, as described in +<<components.grid.generatedcolumns>>, even though the container implements the +interface, the wrapped container must also support it. Also, the generated +properties are not normally sortable, but require special handling to enable +sorting. + + +[[components.grid.editing]] +== Editing + +Grid supports line-based editing, where double-clicking a row opens the row +editor. In the editor, the input fields can be edited, as well as navigated with +kbd:[Tab] and kbd:[Shift+Tab] keys. If validation fails, an error is displayed and the user +can correct the inputs. + +To enable editing, you need to call [methodname]#setEditorEnabled(true)# for the +grid. + + +[source, java] +---- +Grid grid = new Grid(GridExample.exampleDataSource()); +grid.setEditorEnabled(true); +---- + +Grid supports two row editor modes - buffered and unbuffered. The default mode is +buffered. The mode can be changed with [methodname]#setBuffered(false)# + +[[components.grid.editing.buffered]] +=== Buffered Mode + +The editor has a [guibutton]#Save# button that commits +the data item to the container data source and closes the editor. The +[guibutton]#Cancel# button discards the changes and exits the editor. + +A row under editing is illustrated in <<figure.components.grid.editing>>. + +[[figure.components.grid.editing]] +.Editing a Grid Row +image::img/grid-editor-basic.png[] + +[[components.grid.editing.unbuffered]] +=== Unbuffered Mode + +The editor has no buttons and all changed data is committed directly +to the container. If another row is clicked, the editor for the current row is closed and +a row editor for the clicked row is opened. + +[[components.grid.editing.fields]] +=== Editor Fields + +The editor fields are by default generated with a [interfacename]#FieldFactory# +and bound to the container data source with a [classname]#FieldGroup#, which +also handles tasks such as validation, as explained later. + +To disable editing in a particular column, you can call +[methodname]#setEditable()# in the [classname]#Column# object with +[parameter]#false# parameter. + +[[components.grid.editing.editorfields]] +=== Customizing Editor Fields + +The editor fields are normally created by the field factory of the editor's field +group, which creates the fields according to the data types of their respective +columns. To customize the editor fields of specific properties, such as to style +them or to set up validation, you can provide them with +[methodname]#setEditorField()# in the respective columns. + +In the following example, we configure a field with validation and styling: + + +[source, java] +---- +TextField nameEditor = new TextField(); + +// Custom CSS style +nameEditor.addStyleName("nameeditor"); + +// Custom validation +nameEditor.addValidator(new RegexpValidator( + "^\\p{Alpha}+ \\p{Alpha}+$", + "Need first and last name")); + +grid.getColumn("name").setEditorField(nameEditor); +---- + +Setting an editor field to [parameter]#null# deletes the currently existing +editor field, whether it was automatically generated or set explicitly with the +setter. It will be regenerated with the factory the next time it is needed. + + +ifdef::web[] +[[components.grid.editing.captions]] +=== Customizing Editor Buttons + +In the buffered mode, the editor has two buttons: [guibutton]#Save# and [guibutton]#Cancel#. You can +set their captions with [methodname]#setEditorSaveCaption()# and +[methodname]#setEditorCancelCaption()#, respectively. + +In the following example, we demonstrate one way to translate the captions: + + +[source, java] +---- +// Captions are stored in a resource bundle +ResourceBundle bundle = ResourceBundle.getBundle( + MyAppCaptions.class.getName(), + Locale.forLanguageTag("fi")); // Finnish + +// Localize the editor button captions +grid.setEditorSaveCaption( + bundle.getString(MyAppCaptions.SaveKey)); +grid.setEditorCancelCaption( + bundle.getString(MyAppCaptions.CancelKey)); +---- + +endif::web[] + +[[components.grid.editing.fieldgroup]] +=== Binding to Data with a Field Group + +Data binding to the item under editing is handled with a +[classname]#FieldGroup#, which you need to set with +[methodname]#setEditorFieldGroup#. This is mostly useful when using +special-purpose field groups, such as [classname]#BeanFieldGroup# to enable bean +validation. + +For example, assuming that we want to enable bean validation for a bean such as +the following: + + +[source, java] +---- +public class Person implements Serializable { + @NotNull + @Size(min=2, max=10) + private String name; + + @Min(1) + @Max(130) + private int age; + ...] +---- + +We can now use a [classname]#BeanFieldGroup# in the [classname]#Grid# as +follows: + + +[source, java] +---- +Grid grid = new Grid(exampleBeanDataSource()); +grid.setColumnOrder("name", "age"); +grid.setEditorEnabled(true); + +// Enable bean validation for the data +grid.setEditorFieldGroup( + new BeanFieldGroup<Person>(Person.class)); + +// Have some extra validation in a field +TextField nameEditor = new TextField(); +nameEditor.addValidator(new RegexpValidator( + "^\\p{Alpha}+ \\p{Alpha}+$", + "Need first and last name")); +grid.setEditorField("name", nameEditor); +---- + +To use bean validation as in the example above, you need to include an +implementation of the Bean Validation API in the classpath, as described in +<<dummy/../../../framework/datamodel/datamodel-itembinding#datamodel.itembinding.beanvalidation,"Bean +Validation">>. + + +ifdef::web[] +[[components.grid.editing.validation]] +=== Handling Validation Errors + +The input fields are validated when the value is updated. The default +error handler displays error indicators in the invalid fields, as well as the +first error in the editor. + +[[figure.components.grid.errors]] +.Editing a Grid Row +image::img/grid-editor-errors.png[] + +You can modify the error handling by implementing a custom +[interfacename]#EditorErrorHandler# or by extending the +[classname]#DefaultEditorErrorHandler#. + +endif::web[] + +[[components.grid.editing.fieldfactory]] +=== Editor Field Factory + +The fields are generated by the [classname]#FieldFactory# of the field group; +you can also set it with [methodname]#setEditorFieldFactory()#. Alternatively, +you can create the editor fields explicitly with [methodname]#setEditorField()#. + +[[components.grid.scrolling]] +== Programmatic Scrolling + +You can scroll to first item with [methodname]#scrollToStart()#, to end with +[methodname]#scrollToEnd()#, or to a specific row with [methodname]#scrollTo()#. + + +[[components.grid.stylegeneration]] +== Generating Row or Cell Styles + +You can style entire rows with a [interfacename]#RowStyleGenerator# or +individual cells with a [interfacename]#CellStyleGenerator#. + +[[components.grid.stylegeneration.row]] +=== Generating Row Styles + +You set a [interfacename]#RowStyleGenerator# to a grid with +[methodname]#setRowStyleGenerator()#. The [methodname]#getStyle()# method gets a +[classname]#RowReference#, which contains various information about the row and +a reference to the grid, and should return a style name or [parameter]#null# if +no style is generated. + +For example, to add a style names to rows having certain values in one column, +you can style them as follows: + + +[source, java] +---- +grid.setRowStyleGenerator(rowRef -> {// Java 8 + if (! ((Boolean) rowRef.getItem() + .getItemProperty("alive") + .getValue()).booleanValue()) + return "grayed"; + else + return null; +}); +---- + +You could then style the rows with CSS as follows: + + +[source, css] +---- +.v-grid-row.grayed { + color: gray; +} +---- + + +[[components.grid.stylegeneration.cell]] +=== Generating Cell Styles + +You set a [interfacename]#CellStyleGenerator# to a grid with +[methodname]#setCellStyleGenerator()#. The [methodname]#getStyle()# method gets +a [classname]#CellReference#, which contains various information about the cell +and a reference to the grid, and should return a style name or [parameter]#null# +if no style is generated. + +For example, to add a style name to a specific column, you can match on the +property ID of the column as follows: + + +[source, java] +---- +grid.setCellStyleGenerator(cellRef -> // Java 8 + "born".equals(cellRef.getPropertyId())? + "rightalign" : null); +---- + +You could then style the cells with a CSS rule as follows: + + +[source, css] +---- +.v-grid-cell.rightalign { + text-align: right; +} +---- + + + +[[components.grid.css]] +== Styling with CSS + + +[source, css] +---- +.v-grid { + .v-grid-scroller, .v-grid-scroller-horizontal { } + .v-grid-tablewrapper { + .v-grid-header { + .v-grid-row { + .v-grid-cell, .frozen, .v-grid-cell-focused { } + } + } + .v-grid-body { + .v-grid-row, + .v-grid-row-stripe, + .v-grid-row-has-data { + .v-grid-cell, .frozen, .v-grid-cell-focused { } + } + } + .v-grid-footer { + .v-grid-row { + .v-grid-cell, .frozen, .v-grid-cell-focused { } + } + } + } + .v-grid-header-deco { } + .v-grid-footer-deco { } + .v-grid-horizontal-scrollbar-deco { } + .v-grid-editor { + .v-grid-editor-cells { } + .v-grid-editor-footer { + .v-grid-editor-message { } + .v-grid-editor-buttons { + .v-grid-editor-save { } + .v-grid-editor-cancel { } + } + } + } +} +---- + +A [classname]#Grid# has an overall [literal]#++v-grid++# style. The actual grid +has three parts: a header, a body, and a footer. The scrollbar is a custom +element with [literal]#++v-grid-scroller++# style. In addition, there are some +decoration elements. + +Grid cells, whether thay are in the header, body, or footer, have a basic +[literal]#++v-grid-cell++# style. Cells in a frozen column additionally have a +[literal]#++frozen++# style. Rows have [literal]#++v-grid-row++# style, and +every other row has additionally a [literal]#++v-grid-row-stripe++# style. + +The focused row has additionally [literal]#++v-grid-row-focused++# style and +focused cell [literal]#++v-grid-cell-focused++#. By default, cell focus is +visible, with the border stylable with [parameter]#$v-grid-cell-focused-border# +parameter in Sass. Row focus has no visible styling, but can be made visible +with the [parameter]#$v-grid-row-focused-background-color# parameter or with a +custom style rule. + +In editing mode, a [literal]#++v-grid-editor++# overlay is placed on the row +under editing. In addition to the editor field cells, it has an error message +element, as well as the buttons. + + +((())) + + |