You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

GeneratedColumnExample.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.automatedtests.featurebrowser;
  5. import java.util.Collection;
  6. import java.util.Date;
  7. import java.util.GregorianCalendar;
  8. import java.util.Vector;
  9. import com.vaadin.data.Container;
  10. import com.vaadin.data.Item;
  11. import com.vaadin.data.Property;
  12. import com.vaadin.data.Container.Indexed;
  13. import com.vaadin.data.util.BeanItem;
  14. import com.vaadin.ui.AbstractField;
  15. import com.vaadin.ui.BaseFieldFactory;
  16. import com.vaadin.ui.CheckBox;
  17. import com.vaadin.ui.Component;
  18. import com.vaadin.ui.CustomComponent;
  19. import com.vaadin.ui.Field;
  20. import com.vaadin.ui.Label;
  21. import com.vaadin.ui.Table;
  22. import com.vaadin.ui.VerticalLayout;
  23. import com.vaadin.ui.Button.ClickEvent;
  24. import com.vaadin.ui.Button.ClickListener;
  25. /**
  26. * This example demonstrates the use of generated columns in a table. Generated
  27. * columns can be used for formatting values or calculating them from other
  28. * columns (or properties of the items).
  29. *
  30. * For the data model, we use POJOs bound to a custom Container with BeanItem
  31. * items.
  32. *
  33. * @author magi
  34. */
  35. @SuppressWarnings("serial")
  36. public class GeneratedColumnExample extends CustomComponent {
  37. /**
  38. * The business model: fill-up at a gas station.
  39. */
  40. public class FillUp {
  41. Date date;
  42. double quantity;
  43. double total;
  44. public FillUp() {
  45. }
  46. public FillUp(int day, int month, int year, double quantity,
  47. double total) {
  48. date = new GregorianCalendar(year, month - 1, day).getTime();
  49. this.quantity = quantity;
  50. this.total = total;
  51. }
  52. /** Calculates price per unit of quantity (€/l). */
  53. public double price() {
  54. if (quantity != 0.0) {
  55. return total / quantity;
  56. } else {
  57. return 0.0;
  58. }
  59. }
  60. /** Calculates average daily consumption between two fill-ups. */
  61. public double dailyConsumption(FillUp other) {
  62. double difference_ms = date.getTime() - other.date.getTime();
  63. double days = difference_ms / 1000 / 3600 / 24;
  64. if (days < 0.5) {
  65. days = 1.0; // Avoid division by zero if two fill-ups on the
  66. // same day.
  67. }
  68. return quantity / days;
  69. }
  70. /** Calculates average daily consumption between two fill-ups. */
  71. public double dailyCost(FillUp other) {
  72. return price() * dailyConsumption(other);
  73. }
  74. // Getters and setters
  75. public Date getDate() {
  76. return date;
  77. }
  78. public void setDate(Date date) {
  79. this.date = date;
  80. }
  81. public double getQuantity() {
  82. return quantity;
  83. }
  84. public void setQuantity(double quantity) {
  85. this.quantity = quantity;
  86. }
  87. public double getTotal() {
  88. return total;
  89. }
  90. public void setTotal(double total) {
  91. this.total = total;
  92. }
  93. };
  94. /**
  95. * This is a custom container that allows adding BeanItems inside it. The
  96. * BeanItem objects must be bound to an object. The item ID is an Integer
  97. * from 0 to 99.
  98. *
  99. * Most of the interface methods are implemented with just dummy
  100. * implementations, as they are not needed in this example.
  101. */
  102. public class MySimpleIndexedContainer implements Container, Indexed {
  103. Vector<BeanItem> items;
  104. Object itemtemplate;
  105. public MySimpleIndexedContainer(Object itemtemplate) {
  106. this.itemtemplate = itemtemplate;
  107. items = new Vector<BeanItem>(); // Yeah this is just a test
  108. }
  109. public boolean addContainerProperty(Object propertyId, Class type,
  110. Object defaultValue) throws UnsupportedOperationException {
  111. throw new UnsupportedOperationException();
  112. }
  113. public Item addItem(Object itemId) throws UnsupportedOperationException {
  114. throw new UnsupportedOperationException();
  115. }
  116. public Object addItem() throws UnsupportedOperationException {
  117. throw new UnsupportedOperationException();
  118. }
  119. /**
  120. * This addItem method is specific for this container and allows adding
  121. * BeanItem objects. The BeanItems must be bound to MyBean objects.
  122. */
  123. public void addItem(BeanItem item) throws UnsupportedOperationException {
  124. items.add(item);
  125. }
  126. public boolean containsId(Object itemId) {
  127. if (itemId instanceof Integer) {
  128. int pos = ((Integer) itemId).intValue();
  129. if (pos >= 0 && pos < items.size()) {
  130. return items.get(pos) != null;
  131. }
  132. }
  133. return false;
  134. }
  135. /**
  136. * The Table will call this method to get the property objects for the
  137. * columns. It uses the property objects to determine the data types of
  138. * the columns.
  139. */
  140. public Property getContainerProperty(Object itemId, Object propertyId) {
  141. if (itemId instanceof Integer) {
  142. int pos = ((Integer) itemId).intValue();
  143. if (pos >= 0 && pos < items.size()) {
  144. Item item = items.get(pos);
  145. // The BeanItem provides the property objects for the items.
  146. return item.getItemProperty(propertyId);
  147. }
  148. }
  149. return null;
  150. }
  151. /** Table calls this to get the column names. */
  152. public Collection getContainerPropertyIds() {
  153. Item item = new BeanItem(itemtemplate);
  154. // The BeanItem knows how to get the property names from the bean.
  155. return item.getItemPropertyIds();
  156. }
  157. public Item getItem(Object itemId) {
  158. if (itemId instanceof Integer) {
  159. int pos = ((Integer) itemId).intValue();
  160. if (pos >= 0 && pos < items.size()) {
  161. return items.get(pos);
  162. }
  163. }
  164. return null;
  165. }
  166. public Collection getItemIds() {
  167. Vector ids = new Vector(items.size());
  168. for (int i = 0; i < items.size(); i++) {
  169. ids.add(Integer.valueOf(i));
  170. }
  171. return ids;
  172. }
  173. public Class getType(Object propertyId) {
  174. return BeanItem.class;
  175. }
  176. public boolean removeAllItems() throws UnsupportedOperationException {
  177. throw new UnsupportedOperationException();
  178. }
  179. public boolean removeContainerProperty(Object propertyId)
  180. throws UnsupportedOperationException {
  181. throw new UnsupportedOperationException();
  182. }
  183. public boolean removeItem(Object itemId)
  184. throws UnsupportedOperationException {
  185. throw new UnsupportedOperationException();
  186. }
  187. public int size() {
  188. return items.size();
  189. }
  190. public Object addItemAt(int index) throws UnsupportedOperationException {
  191. // TODO Auto-generated method stub
  192. return null;
  193. }
  194. public Item addItemAt(int index, Object newItemId)
  195. throws UnsupportedOperationException {
  196. // TODO Auto-generated method stub
  197. return null;
  198. }
  199. public Object getIdByIndex(int index) {
  200. return Integer.valueOf(index);
  201. }
  202. public int indexOfId(Object itemId) {
  203. return ((Integer) itemId).intValue();
  204. }
  205. public Object addItemAfter(Object previousItemId)
  206. throws UnsupportedOperationException {
  207. // TODO Auto-generated method stub
  208. return null;
  209. }
  210. public Item addItemAfter(Object previousItemId, Object newItemId)
  211. throws UnsupportedOperationException {
  212. // TODO Auto-generated method stub
  213. return null;
  214. }
  215. public Object firstItemId() {
  216. return new Integer(0);
  217. }
  218. public boolean isFirstId(Object itemId) {
  219. return ((Integer) itemId).intValue() == 0;
  220. }
  221. public boolean isLastId(Object itemId) {
  222. return ((Integer) itemId).intValue() == (items.size() - 1);
  223. }
  224. public Object lastItemId() {
  225. return new Integer(items.size() - 1);
  226. }
  227. public Object nextItemId(Object itemId) {
  228. int pos = indexOfId(itemId);
  229. if (pos >= items.size() - 1) {
  230. return null;
  231. }
  232. return getIdByIndex(pos + 1);
  233. }
  234. public Object prevItemId(Object itemId) {
  235. int pos = indexOfId(itemId);
  236. if (pos <= 0) {
  237. return null;
  238. }
  239. return getIdByIndex(pos - 1);
  240. }
  241. }
  242. /** Formats the dates in a column containing Date objects. */
  243. class DateColumnGenerator implements Table.ColumnGenerator {
  244. /**
  245. * Generates the cell containing the Date value. The column is
  246. * irrelevant in this use case.
  247. */
  248. public Component generateCell(Table source, Object itemId,
  249. Object columnId) {
  250. Property prop = source.getItem(itemId).getItemProperty(columnId);
  251. if (prop.getType().equals(Date.class)) {
  252. Label label = new Label(String.format("%tF",
  253. new Object[] { (Date) prop.getValue() }));
  254. label.addStyleName("column-type-date");
  255. return label;
  256. }
  257. return null;
  258. }
  259. }
  260. /** Formats the value in a column containing Double objects. */
  261. class ValueColumnGenerator implements Table.ColumnGenerator {
  262. String format; /* Format string for the Double values. */
  263. /** Creates double value column formatter with the given format string. */
  264. public ValueColumnGenerator(String format) {
  265. this.format = format;
  266. }
  267. /**
  268. * Generates the cell containing the Double value. The column is
  269. * irrelevant in this use case.
  270. */
  271. public Component generateCell(Table source, Object itemId,
  272. Object columnId) {
  273. Property prop = source.getItem(itemId).getItemProperty(columnId);
  274. if (prop.getType().equals(Double.class)) {
  275. Label label = new Label(String.format(format,
  276. new Object[] { (Double) prop.getValue() }));
  277. // Set styles for the column: one indicating that it's a value
  278. // and a more
  279. // specific one with the column name in it. This assumes that
  280. // the column
  281. // name is proper for CSS.
  282. label.addStyleName("column-type-value");
  283. label.addStyleName("column-" + (String) columnId);
  284. return label;
  285. }
  286. return null;
  287. }
  288. }
  289. /** Table column generator for calculating price column. */
  290. class PriceColumnGenerator implements Table.ColumnGenerator {
  291. public Component generateCell(Table source, Object itemId,
  292. Object columnId) {
  293. // Retrieve the item.
  294. BeanItem item = (BeanItem) source.getItem(itemId);
  295. // Retrieves the underlying POJO from the item.
  296. FillUp fillup = (FillUp) item.getBean();
  297. // Do the business logic
  298. double price = fillup.price();
  299. // Create the generated component for displaying the calcucated
  300. // value.
  301. Label label = new Label(String.format("%1.2f €",
  302. new Object[] { new Double(price) }));
  303. // We set the style here. You can't use a CellStyleGenerator for
  304. // generated columns.
  305. label.addStyleName("column-price");
  306. return label;
  307. }
  308. }
  309. /** Table column generator for calculating consumption column. */
  310. class ConsumptionColumnGenerator implements Table.ColumnGenerator {
  311. /**
  312. * Generates a cell containing value calculated from the item.
  313. */
  314. public Component generateCell(Table source, Object itemId,
  315. Object columnId) {
  316. Indexed indexedSource = (Indexed) source.getContainerDataSource();
  317. // Can not calculate consumption for the first item.
  318. if (indexedSource.isFirstId(itemId)) {
  319. Label label = new Label("N/A");
  320. label.addStyleName("column-consumption");
  321. return label;
  322. }
  323. // Index of the previous item.
  324. Object prevItemId = indexedSource.prevItemId(itemId);
  325. // Retrieve the POJOs.
  326. FillUp fillup = (FillUp) ((BeanItem) indexedSource.getItem(itemId))
  327. .getBean();
  328. FillUp prev = (FillUp) ((BeanItem) source.getItem(prevItemId))
  329. .getBean();
  330. // Do the business logic
  331. return generateCell(fillup, prev);
  332. }
  333. public Component generateCell(FillUp fillup, FillUp prev) {
  334. double consumption = fillup.dailyConsumption(prev);
  335. // Generate the component for displaying the calculated value.
  336. Label label = new Label(String.format("%3.2f l",
  337. new Object[] { new Double(consumption) }));
  338. // We set the style here. You can't use a CellStyleGenerator for
  339. // generated columns.
  340. label.addStyleName("column-consumption");
  341. return label;
  342. }
  343. }
  344. /** Table column generator for calculating daily cost column. */
  345. class DailyCostColumnGenerator extends ConsumptionColumnGenerator {
  346. @Override
  347. public Component generateCell(FillUp fillup, FillUp prev) {
  348. double dailycost = fillup.dailyCost(prev);
  349. // Generate the component for displaying the calculated value.
  350. Label label = new Label(String.format("%3.2f €",
  351. new Object[] { new Double(dailycost) }));
  352. // We set the style here. You can't use a CellStyleGenerator for
  353. // generated columns.
  354. label.addStyleName("column-dailycost");
  355. return label;
  356. }
  357. }
  358. /**
  359. * Custom field factory that sets the fields as immediate.
  360. */
  361. public class ImmediateFieldFactory extends BaseFieldFactory {
  362. @Override
  363. public Field createField(Class type, Component uiContext) {
  364. // Let the BaseFieldFactory create the fields
  365. Field field = super.createField(type, uiContext);
  366. // ...and just set them as immediate
  367. ((AbstractField) field).setImmediate(true);
  368. return field;
  369. }
  370. }
  371. public GeneratedColumnExample() {
  372. final Table table = new Table();
  373. // Define table columns. These include also the column for the generated
  374. // column, because we want to set the column label to something
  375. // different than the property ID.
  376. table
  377. .addContainerProperty("date", Date.class, null, "Date", null,
  378. null);
  379. table.addContainerProperty("quantity", Double.class, null,
  380. "Quantity (l)", null, null);
  381. table.addContainerProperty("price", Double.class, null, "Price (€/l)",
  382. null, null);
  383. table.addContainerProperty("total", Double.class, null, "Total (€)",
  384. null, null);
  385. table.addContainerProperty("consumption", Double.class, null,
  386. "Consumption (l/day)", null, null);
  387. table.addContainerProperty("dailycost", Double.class, null,
  388. "Daily Cost (€/day)", null, null);
  389. // Define the generated columns and their generators.
  390. table.addGeneratedColumn("date", new DateColumnGenerator());
  391. table
  392. .addGeneratedColumn("quantity", new ValueColumnGenerator(
  393. "%.2f l"));
  394. table.addGeneratedColumn("price", new PriceColumnGenerator());
  395. table.addGeneratedColumn("total", new ValueColumnGenerator("%.2f €"));
  396. table.addGeneratedColumn("consumption",
  397. new ConsumptionColumnGenerator());
  398. table.addGeneratedColumn("dailycost", new DailyCostColumnGenerator());
  399. // Create a data source and bind it to the table.
  400. MySimpleIndexedContainer data = new MySimpleIndexedContainer(
  401. new FillUp());
  402. table.setContainerDataSource(data);
  403. // Generated columns are automatically placed after property columns, so
  404. // we have to set the order of the columns explicitly.
  405. table.setVisibleColumns(new Object[] { "date", "quantity", "price",
  406. "total", "consumption", "dailycost" });
  407. // Add some data.
  408. data.addItem(new BeanItem(new FillUp(19, 2, 2005, 44.96, 51.21)));
  409. data.addItem(new BeanItem(new FillUp(30, 3, 2005, 44.91, 53.67)));
  410. data.addItem(new BeanItem(new FillUp(20, 4, 2005, 42.96, 49.06)));
  411. data.addItem(new BeanItem(new FillUp(23, 5, 2005, 47.37, 55.28)));
  412. data.addItem(new BeanItem(new FillUp(6, 6, 2005, 35.34, 41.52)));
  413. data.addItem(new BeanItem(new FillUp(30, 6, 2005, 16.07, 20.00)));
  414. data.addItem(new BeanItem(new FillUp(2, 7, 2005, 36.40, 36.19)));
  415. data.addItem(new BeanItem(new FillUp(6, 7, 2005, 39.17, 50.90)));
  416. data.addItem(new BeanItem(new FillUp(27, 7, 2005, 43.43, 53.03)));
  417. data.addItem(new BeanItem(new FillUp(17, 8, 2005, 20, 29.18)));
  418. data.addItem(new BeanItem(new FillUp(30, 8, 2005, 46.06, 59.09)));
  419. data.addItem(new BeanItem(new FillUp(22, 9, 2005, 46.11, 60.36)));
  420. data.addItem(new BeanItem(new FillUp(14, 10, 2005, 41.51, 50.19)));
  421. data.addItem(new BeanItem(new FillUp(12, 11, 2005, 35.24, 40.00)));
  422. data.addItem(new BeanItem(new FillUp(28, 11, 2005, 45.26, 53.27)));
  423. // Have a check box that allows the user to make the quantity
  424. // and total columns editable.
  425. final CheckBox editable = new CheckBox(
  426. "Edit the input values - calculated columns are regenerated");
  427. editable.setImmediate(true);
  428. editable.addListener(new ClickListener() {
  429. public void buttonClick(ClickEvent event) {
  430. table.setEditable(editable.booleanValue());
  431. // The columns may not be generated when we want to have them
  432. // editable.
  433. if (editable.booleanValue()) {
  434. table.removeGeneratedColumn("quantity");
  435. table.removeGeneratedColumn("total");
  436. } else {
  437. // In non-editable mode we want to show the formatted
  438. // values.
  439. table.addGeneratedColumn("quantity",
  440. new ValueColumnGenerator("%.2f l"));
  441. table.addGeneratedColumn("total", new ValueColumnGenerator(
  442. "%.2f €"));
  443. }
  444. // The visible columns are affected by removal and addition of
  445. // generated columns so we have to redefine them.
  446. table.setVisibleColumns(new Object[] { "date", "quantity",
  447. "price", "total", "consumption", "dailycost" });
  448. }
  449. });
  450. // Use a custom field factory to set the edit fields as immediate.
  451. // This is used when the table is in editable mode.
  452. table.setFieldFactory(new ImmediateFieldFactory());
  453. // Setting the table itself as immediate has no relevance in this
  454. // example,
  455. // because it is relevant only if the table is selectable and we want to
  456. // get the selection changes immediately.
  457. table.setImmediate(true);
  458. table.setHeight("300px");
  459. VerticalLayout layout = new VerticalLayout();
  460. layout.setMargin(true);
  461. layout
  462. .addComponent(new Label(
  463. "Table with column generators that format and calculate cell values."));
  464. layout.addComponent(table);
  465. layout.addComponent(editable);
  466. layout.addComponent(new Label(
  467. "Columns displayed in blue are calculated from Quantity and Total. "
  468. + "Others are simply formatted."));
  469. layout.setExpandRatio(table, 1);
  470. layout.setSizeUndefined();
  471. setCompositionRoot(layout);
  472. // setSizeFull();
  473. }
  474. }