Displaying Data Using DataTable in Apache Wicket

April 30, 2011

Java

«»

Exporting data to CSV

Even though web applications have come a long way, they are still not as good at quickly hacking and slashing tabular data as most desktop spreadsheet processors. But, in order to get the data into the desktop software we need to be able to export it from the web application. In this recipe we will build a reusable way to export data, in CSV format, from DataTable components:

 

Getting ready

To get started, see the Getting Ready section of the fi rst recipe in this chapter.

Create various utility classes:

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
Pager.java
 
	public class Pager {
		private final int p;
		private final int t;
		public Pager(int perPage, int total) {
			this.p = perPage;
			this.t = total;
		}
		public int pages() {
			return t / p + ((t % p > 0) ? 1 : 0);
		}
		public int offset(int page) {
			return p * page;
		}
		public int count(int page) {
			return Math.min(offset(page) + p, t);
		}
	}
 
	CsvWriter.java
	public class CsvWriter {
		private final PrintWriter out;
		private boolean first = true;
		public CsvWriter(OutputStream os) {
			out = new PrintWriter(os);
		}
		public CsvWriter write(Object value) {
			if (!first) {
				out.append(",");
			}
			out.append("\"");
			if (value != null) {
				out.append(value.toString().replace("\"", "\"\"")
					.replace("\n", " "));
			}
			out.append("\"");
			first = false;
			return this;
		}
		public CsvWriter endLine() {
			out.append("\r\n");
			first = true;
			return this;
		}
		public CsvWriter flush() {
			out.flush();
			return this;
		}
		public void close() {
			out.close();
		}
	}

How to do it…

  1. Create a IColumn mixin that will mark columns as exportable:
    1
    2
    3
    4
    
    ExportableColumn.java
    	public interface ExportableColumn extends IColumn {
    		void exportCsv(T object, CsvWriter writer);
    	}
  2. Implement an exportable property column:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    ExportablePropertyColumn.java
    	public class ExportablePropertyColumn<T> extends PropertyColumn<T>
    	implements
    		ExportableColumn<T> {
    		public ExportablePropertyColumn(IModel displayModel,
    			String propertyExpression) {
    			super(displayModel, propertyExpression);
    		}
    		public void exportCsv(final T object, CsvWriter writer) {
    			IModel<?> textModel = createLabelModel(new
    			AbstractReadOnlyModel<T>() {
    				@Override
    				public T getObject() {
    					return object;
    				}
    			});
    			writer.write(textModel.getObject());
    			textModel.detach();
    		}
    	}
  3. Create a link that will perform the CSV export:
    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
    
    CsvExportLink.java
    	public class CsvExportLink<T> extends Link<Void> {
    		private final DataTable<T> table;
    		public CsvExportLink(String id, DataTable<T> table) {
    			super(id);
    			this.table = table;
    		}
    		@Override
    		public void onClick() {
    			WebResponse response = (WebResponse) getResponse();
    			response.setAttachmentHeader("export.csv");
    			response.setContentType("text/csv");
    			OutputStream out = getResponse().getOutputStream();
    			CsvWriter writer = new CsvWriter(out);
    			List<ExportableColumn<T>> exportable =
    				getExportableColumns();
    			Pager pager = new Pager(100, table.getDataProvider().
    				size());
    			for (int i = 0; i < pager.pages(); i++) {
    				Iterator<? extends T> it = table.getDataProvider().
    				iterator(
    					pager.offset(i), pager.count(i));
    					while (it.hasNext()) {
    						T object = it.next();
    						for (ExportableColumn<T> col : exportable) {
    							col.exportCsv(object, writer);
    						}
    						writer.endLine();
    					}
    			}
    			writer.close();
    			throw new AbortException();
    		}
    		private List<ExportableColumn<T>> getExportableColumns() {
    			List<ExportableColumn<T>> exportable = new
    			ArrayList<ExportableColumn<T>>(
    			table.getColumns().length);
    			for (IColumn<?> column : table.getColumns()) {
    				if (column instanceof ExportableColumn<?>) {
    					exportable.add((ExportableColumn<T>) column);
    				}
    			}
    			return exportable;
    		}
    	}
  4. Change the table to use exportable columns:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    List<IColumn<Contact>> columns = new
    	ArrayList<IColumn<Contact>>();
    		columns.add(new ExportablePropertyColumn<Contact>(Model.
    			of("Name"),
    			"name"));
    		columns.add(new ExportablePropertyColumn<Contact>(Model.
    			of("Email"),
    			"email"));
    		columns.add(new ExportablePropertyColumn<Contact>(Model.
    			of("Phone"),
    			"phone"));
  5. Add the export link:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    HomePage.html
    	<body>
    		<table wicket:id="contacts" class="contacts"></table>
    		<a wicket:id="csv">Export to CSV</a>
    	</body>
     
    	HomePage.java
    	add(new CsvExportLink("csv", contacts));</li>
    </ol>
    <h3>How it works...</h3>
    What we want to happen is that when the export link is clicked, the user is prompted to download a fi le that contains an export of all the rows in the DataTable in CSV format. As we want this to be reusable across DataTables and across various types of objects, the DataTable displays, ideally, the columns that we want to know how to export data. This way we can iterate the columns and build each CSV record. In order to do this, we fi rst defi ne a mixin interface that columns that know how to export their content into a CSV will implement:
    <pre lang="LANGUAGE" line="1">public interface ExportableColumn<T> extends IColumn<T> {
    		void exportCsv(T object, CsvWriter writer);
    	}

    The interface adds a single method that allows the column to contribute to the CSV record that represents an object in one of the rows of the DataTable.

    Next, let’s build an actual implementation of an ExportableColumn we will use in our example. To keep things simple we will extend the columns we used in the Getting Started section:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    public class ExportablePropertyColumn<T> extends PropertyColumn<T>
    	implements
    		ExportableColumn<T> {
    			public ExportablePropertyColumn(IModel displayModel,
    			String propertyExpression) {
    				super(displayModel, propertyExpression);
    			}
    			public void exportCsv(final T object, CsvWriter writer) {
    				IModel<?> textModel = createLabelModel(new
    				AbstractReadOnlyModel<T>() {
    					@Override
    					public T getObject() {
    						return object;
    					}
    				});
    				writer.write(textModel.getObject());
    				textModel.detach();
    			}
    		}

    Our implementation piggy-backs on PropertyColumn’s createLabelModel() method to create a model that is used to populate the cells, and writes the value of that model into the SV writer.

    With the basics out of the way, it is time to get down and dirty and create the link that will create the CSV export.

    We begin by creating a subclass of Link and passing in the DataTable the link will create the export for:

    1
    2
    3
    4
    5
    6
    7
    
    public class CsvExportLink<T> extends Link<Void> {
    		private final DataTable<T> table;
    		public CsvExportLink(String id, DataTable<T> table) {
    			super(id);
    			this.table = table;
    		}
    	}

    As the export has to iterate only over columns that implement ExportableColumn interface, we create a helper method to retrieve only those columns:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    public class CsvExportLink<T> extends Link<Void> {
    		private List<ExportableColumn<T>> getExportableColumns() {
    			List<ExportableColumn<T>> exportable = new
    			ArrayList<ExportableColumn<T>>(
    			table.getColumns().length);
    			for (IColumn<?> column : table.getColumns()) {
    				if (column instanceof ExportableColumn<?>) {
    					exportable.add((ExportableColumn<T>) column);
    				}
    			}
    			return exportable;
    		}
    	}

    Now we are ready to implement the export code which will be placed inside the onClick() method. We begin by setting the necessary headers on the response object:

    1
    2
    3
    
    WebResponse response = (WebResponse) getResponse();
    	response.setAttachmentHeader("export.csv");
    	response.setContentType("text/csv");

    setAttachmentHeader() method will cause the browser to prompt the user to
    download a file.

    Next, we create the CsvWriter helper and connect it to the response’s output stream:

    1
    2
    3
    
    WebResponse response = (WebResponse) getResponse();
    	OutputStream out = getResponse().getOutputStream();
    	CsvWriter writer = new CsvWriter(out);

    Finally, it’s the actual export loop, which we perform in chunks of 100 records at a time:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    List<ExportableColumn<T>> exportable = getExportableColumns();
    	Pager pager = new Pager(100, table.getDataProvider().size());
    	for (int i = 0; i < pager.pages(); i++) {
    		Iterator<? extends T> it = table.getDataProvider().iterator(
    		pager.offset(i), pager.count(i));
    		while (it.hasNext()) {
    			T object = it.next();
    			for (ExportableColumn<T> col : exportable) {
    				col.exportCsv(object, writer);
    			}
    			writer.endLine();
    		}
    	}

    After we have written the entire CSV content into the response we close the writer and abort any further Wicket-related processing:

    1
    2
    
    writer.close();
    	throw new AbortException();

    There’s more…

    DataTable comes with support for toolbars. Toolbars are specialized Panels that can be easily added to the top or the bottom of the table. In the next section, we will see how to create a reusable toolbar that contains various export options.

    Moving data export to a toolbar

    Toolbars make it convenient to customize DataTables. For example, the table headers are generated by the HeadersToolbar, while paging is generated by the NavigationToolbar.
    Likewise, it makes sense to create an ExportToolbar that will contain all the export-related functionality and can be easily added to any DataTable.

    Let’s create such a toolbar and put our CsvExportLink into it.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    ExportToolbar.html
    	<wicket:panel>
    		<tr class="export">
    			<td wicket:id="span">
    				<a wicket:id="csv">Export to CSV</a>
    			</td>
    		</tr>
    	</wicket:panel>
     
    	ExportToolbar.java
    	public class ExportToolbar<T> extends AbstractToolbar {
    		public ExportToolbar(final DataTable<T> table) {
    			super(table);
    			WebMarkupContainer span = new WebMarkupContainer("span") {
    				@Override
    				protected void onComponentTag(ComponentTag tag) {
    					tag.put("colspan", table.getColumns().length);
    				}
    			};
    			add(span);
    			span.add(new CsvExportLink<T>("csv", table));
    		}
    	}

    With the toolbar complete we can add it to our DataTable:

    1
    2
    
    HomePage.java
    		contacts.addBottomToolbar(new ExportToolbar(contacts));
    email

    «»

    Comments

    comments