martes, 1 de enero de 2019

02. Simple form, grid, data providers and bindings

1. Introduction

Vaadin is rather simple to use if you want to create a simple application, but if you want to use it with a high level of abstraction, you have to deal with high knowledge of Java programming that can tempt you to decline. To overcome this task. the code is stuffed with the @SupressWarnings annotations. If anyone can help me to avoid this annotation, will be welcome! But I think it is a challenge. Let's go

2. Using a Grid component "at design time".

if you want to bind the grid structure to the fields of a known class at design time the code may be simple. Here is the main view class using a grid and a data provider service. Notice how the columns are created.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package org.ximodante.vaadin.blog;

@Route("")
public class MainView extends VerticalLayout {//DON'T USE IT!!!!!!!

    private CustomerService service = CustomerService.getInstance();
    private Grid<Customer> grid = new Grid<>();

    public MainView() {
     grid.setSizeFull();

     // Create the columns from the reference of the class methods
     grid.addColumn(Customer::getFirstName).setHeader("First name");
     grid.addColumn(Customer::getLastName).setHeader("Last name");
     grid.addColumn(Customer::getStatus).setHeader("Status");

     add(grid);
     setHeight("100vh");
     grid.setItems(service.findAll());
    }
}

In line 7 we should create the grid that wraps a Customer class (by means of generics)
In lines 13 to 15, the columns are bound to the class Customer using references to the class methods ( see the "::" operator)

Nice... but it cannot be used if we don't know the class to show at the design time!

3. Using a Grid component "dynamically at runtime".

Now the thing requires a higher level of abstraction and some lambda knowledge.

How we replace references to class methods?

For that purpose we need to fulfil these steps:

  1. Add additional dependencies to pom.xml
  2. Create an entity base class (Base.class) that every entity should extend
  3. Create a utility class for managing "lambdas".
  4. Replace references to class methods that are using the "::" operator

3.1 Additional dependencies to pom.xml

I am a fan of Lombok as it avoids a lot of boilerplate code by means of annotations.  Also apache commons. Add these dependencies:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.4</version>
      <scope>provided</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.8.1</version>
    </dependency>

3.2 Entity class Base.class:

Here is the code. Note the "id" field and some other methods like "clone", "equals" etc.


 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
package org.ximodante.vaadin.entities;

import java.io.Serializable;

import lombok.Getter;
import lombok.Setter;

@SuppressWarnings("serial")
public class Base implements Serializable, Cloneable{
 @Getter @Setter
 private Long id=(long) 0;
 
 public Base (Long id) {
  super();
  this.id=id;
 }
 
 public Base () {
  super();
 }
 
 public boolean isPersisted() {
  return id != null;
 }
 
 @Override
 public boolean equals(Object obj) {
  if (this == obj) return true;
  if (this.id == null) return false;
  
  if (obj instanceof Base && obj.getClass().equals(getClass())) 
   return this.id.equals(((Base) obj).id);
  
  return false;
 }

 @Override
 public int hashCode() {
  int hash = 5;
  hash = 43 * hash + (id == null ? 0 : id.hashCode());
  return hash;
 }

 @Override
 public Base clone() throws CloneNotSupportedException {
  return (Base) super.clone();
 }

 @Override
 public String toString() {
  return "id = " + id;
 }
}

3.3 Utility class for managing lambdas LambdaUtils.class


The Apache commons library is very helpful in this matter.

The first method is the replacement of the getters methods of a class. To use it we need to have the  Field object that represents the attribute of the class. The getter is considered by Vaadin a lambda that extends ValueProvider<?,?> interface (that is a functional interface), but as we are using entities that extend Base.class the generic <T extends Base> is used

The second method is the replacement of the setter methods of a class, in this case, the lambda supplied must implement the Setter<?,?> interface (that is another functional interface)

 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
package org.ximodante.vaadin.utils;

import java.lang.reflect.Field;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.ximodante.vaadin.entities.Base;

import com.vaadin.flow.data.binder.Setter;
import com.vaadin.flow.function.ValueProvider;

public class LambdaUtils {
 
 /**
  * Implementation of a Lambda of type ValueProvider that is similat to a bean getter
  * Replacement of ClassName::MethodName required by Vaadin for:
  *     1. grid.addColumn()
  *     2. Binder.bind()
  *     3. Etc
  * @param fld
  * @return
  */
 public static  <T extends Base> ValueProvider<T,?> getter(Field fld) {
  return bean->{
   try {
    return FieldUtils.readField(fld, bean, true);
   } catch (IllegalAccessException e) {
    e.printStackTrace();
    return null;
   }
  }; 
 }
 
 /**
  * Implementation of a Lambda Setter that is similar to a bean setter
  * @param fld
  * @return
  */
 public static <T extends Base> Setter<T,?> setter(Field fld) {
  return (bean, value ) -> {
   try {
    FieldUtils.writeField(fld, bean, value, true);
   } catch (IllegalAccessException e) {
    e.printStackTrace();
   }
  }; 
 }

}

3.4 Replacing the getter reference method

Now, this code:

grid.addColumn(Customer::getFirstName)

may be replaced by

grid.addColumn(LambdaUtils.getter(fld))

where "fld" is the field (java.lang.reflect.Field) that represent the firstName attribute of the Customer class. There is a utility class that manages this grid definition.


 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
package org.ximodante.vaadin.utils;

import java.lang.reflect.Field;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.ximodante.vaadin.entities.Base;
import com.vaadin.flow.component.grid.Grid;

public class GridUtils {
 /**
  * Creates a Vaadin Grid prepared to receive data of class clazz type 
  *    and only the fields indicated in the fldNames[] parameter
  *    captions or headers or the grid can be provided or not   
  * @param clazz (class to fill)
  * @param fldNames (name of the fields to populate)
  * @param captions (If an optional caption is desired)
  * @return a vaadin grid 
  */
 public static <T extends Base> Grid<T> newGrid(Class<T> clazz, String[] fldNames, String[] captions) {
  Grid<T> grd=new Grid<>();
  if (captions==null) captions=fldNames;
  if (captions.length!=fldNames.length) 
   throw new java.lang.RuntimeException("Error in creating a vaadin Grid:There are different number of Fields and Captions ");
  int i=0;
  for (String fldName: fldNames) {
   Field fld=FieldUtils.getField(clazz, fldName, true);
   grd.addColumn(LambdaUtils.getter(fld))
    .setHeader(captions[i++]);
  }
  return grd;
 }
 
 
 /**
  * Delegate to LambdaUtils
  * Creates a Vaadin Grid prepared to receive data of class clazz type 
  *   displaying all the fields of the class
  * Delegate the Lambda creation to LambdaUtils  
  * @param clazz (class to fill)
  * @return a vaadin grid 
  */
 public static Grid<Base> newGrid(Class<? extends Base> clazz) {
  var grd=new Grid<Base>();
    
     for (Field fld:FieldUtils.getAllFields(clazz)) 
      grd.addColumn(LambdaUtils.getter(fld))
       .setHeader(fld.getName());

     return grd;
 }
}


4. Binding

By means of the binding process, someone can assign a component to an attribute from a bean, thus any input produced in a control automatically affects the bean that has been bound.

In a form component, the steps for binding are:
  1. Create a Binder instance to the bean class to bind
  2. Bind every control to the bean property by means of the getters and setters (And we will make use of the LambdaUtils class seen previously). It is important to distinguish if the attribute needs conversion to String, as the binding may use another method. (                binder.bind(component, "fldName") or binder.forField(component).withConverter(converter).bind(LambdaUtils.getter(field), LambdaUtils.setter(field))
  3.  Use the method "bindInstanceFields(this)" from the Binder instance to bind the components to the bean instance. (NOTE that in the first step the binder was associated with the class and this step associates the binder to the class instance!)

5. A basic form for updating a bean. Controls and binding

In this form, we will search for all the attributes of a bean and create dynamically a component for each one. The control will vary depending on the class of the attribute. For dates, we will use a "DatePicker", for numbers and strings a "TextBox". Conversion is needed when dealing with numbers so the process of binding control may vary. Here is the class:


  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
package org.ximodante.vaadin;

import java.lang.reflect.Field;

import org.apache.commons.lang3.reflect.FieldUtils;
import org.ximodante.vaadin.dataproviders.BaseProvider;
import org.ximodante.vaadin.entities.Base;
import org.ximodante.vaadin.utils.ComponentSelector;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.Binder;

/**
 * 
 * @author ximo dante
 * 
 * This is a scketch of the UI 
 * 
 * +----------------------------------------------+
 * |        Form for updating customers           |                       
 * |                                              |
 * | label1                                       |
 * | +-----------------------------------------+  |
 * | | Field 1                                 |  |
 * | +-----------------------------------------+  |
 * | label2                                       |
 * | +-----------------------------------------+  |
 * | | Field 2                                 |  |
 * | +-----------------------------------------+  |
 * |   ...........                                |
 * |                                              |
 * | +-------------+   +---------------+          |
 * | | Save button |   | Delete button |          |
 * | +-------------+   +---------------+          |
 * +----------------------------------------------+
 */

@SuppressWarnings("serial")
public class BaseForm<T extends Base> extends FormLayout {
 
 //private HasValue<?,?>[] comps=null;
 private Object[] comps=null;
 private Button save = null;
 private Button delete = null;
 private HorizontalLayout buttons=null;
 private BaseProvider<T> service; 
 private T bean;
 private MainView mainView;
 private Binder<T> binder = null;
 
 @SuppressWarnings({ "unchecked", "rawtypes" })
 public BaseForm(MainView mainView, T bean) {
     // Init general params
     this.mainView = mainView;
     this.bean=bean;
     service= BaseProvider.getInstance(bean.getClass());
     binder=new Binder(bean.getClass()); //Bind a "bean"
     
     // Create Components
     createFields();
     createButtons();
     add(buttons);
     
     // Binds the bean with the the form's controls.
     binder.bindInstanceFields(this);
     setBean(null); // Initialize the bean 
 }
 
 /**
  * Create field components dynamically and binds to the bean
  */
 private void createFields() {
  Field[] flds=FieldUtils.getAllFields(bean.getClass());
  comps=new Object[flds.length];
  int i=0;
  for (Field fld:flds) {
   // Create component and binds to the property dynamically
   comps[i]=ComponentCreator.newComponent(fld, binder);
   add((Component)comps[i]); //Need casting !!!
   i++;
  }
 }
 
 private void createButtons() {
  save = new Button("Save");
  save.getElement().setAttribute("theme", "primary");
  save.addClickListener(e -> this.save());
  
  delete = new Button("Delete");
  delete.addClickListener(e -> this.delete());
  
  buttons = new HorizontalLayout(save, delete);
 }
 
 /**
  * By means of this method, the bean is assigned to the form
  * @param bean
  */
 public void setBean(T bean) {
     this.bean = bean;
     binder.setBean(bean);
     boolean enabled = bean != null;
     save.setEnabled(enabled);
     delete.setEnabled(enabled);
     if (enabled) ((TextField)comps[0]).focus(); // Set focus to first component 
 }

 /**
  * Delete action of the button
  */
 private void delete() {
     service.delete(bean);
     mainView.updateList();
     setBean(null);
 }


 /**
  * Save action of the button
  */
 private void save() {
     service.save(bean);
     mainView.updateList();
     setBean(null);
 }
}

We should notice:

  1. The class is wrapping <T extends Base> which means that all the entities that will be displayed in this form should extend Base.class
  2. There are 2 buttons and an array of objects that represent the components to create dynamically
  3. A bean that will be managed in this form
  4. A service that provides de bean
  5. A binder that wraps a class of the generic type T
  6. Creation of components (by means of a helper class ComponentCreator that creates the components by reflection of the fields of the class and binds each component to the attributes of the class)
  7. Creation of buttons and assigning an action to each one
  8. The binding of the elements of the form to the instance of the T class.
  9. The implementation of the actions of the buttons

5.1 Helper class for creating controls and binding to class attributes


Here is the code notice:

  1. Component creation (lines 59, 62, 72, ..)
  2. Converter creators (lines 80, 84, 88, ...)
  3. The binding without conversion (line 116)
  4. The binding with conversion of types (lines 117-120)


  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
package org.ximodante.vaadin.utils;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

import com.vaadin.flow.component.HasValue;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.data.converter.Converter;
import com.vaadin.flow.data.converter.StringToBigDecimalConverter;
import com.vaadin.flow.data.converter.StringToBigIntegerConverter;
import com.vaadin.flow.data.converter.StringToBooleanConverter;
import com.vaadin.flow.data.converter.StringToDoubleConverter;
import com.vaadin.flow.data.converter.StringToFloatConverter;
import com.vaadin.flow.data.converter.StringToIntegerConverter;
import com.vaadin.flow.data.converter.StringToLongConverter;

public class ComponentCreator {
 
 /**
  * Gets the enum values for combos
  * @param cls
  * @return
  * @throws NoSuchFieldException
  * @throws IllegalAccessException
  */
 private static List<?> getEnumValues (Class<?> cls) 
  throws NoSuchFieldException, IllegalAccessException {
  Object[] possibleValues = cls.getEnumConstants();
  return Arrays.asList(possibleValues);
    }
 
 /**
  * Create components based on the class of the attribute and binds the componets to the bean 
  * @param fld
  * @param binder
  * @return
  */
 @SuppressWarnings({ "unchecked", "rawtypes" })
 public static HasValue<?, ?> newComponent(Field fld, Binder binder) {
  HasValue<?, ?> cmp = null;
  Class<?>cls=fld.getType();
  var fldName=fld.getName();
  Converter converter=null;
  System.out.println(fldName);
  System.out.println(cls.getName());
  
  //@see Olli Tietäväinen in: 
     //  https://vaadin.com/forum/thread/15385912/binding-from-integer-not-working
     
  if (cls==String.class)  cmp=new TextField(fldName);
  
  else if (cls.isEnum()) {
   cmp=new ComboBox(fldName);
   List<?> lst=null;
   try {
    lst = getEnumValues(cls);
   } catch (NoSuchFieldException | IllegalAccessException e) {
    e.printStackTrace();
   }
   ((ComboBox) cmp).setItems(lst);
     
  }else if (cls==LocalDate.class ) {
   cmp=new DatePicker();
   ((DatePicker)cmp).setLabel(fldName);
   ((DatePicker)cmp).setLocale(new Locale("es"));
   ((DatePicker)cmp).addValueChangeListener(
           event -> Notification.show("EA->" + event.getValue()));
     
  }else if (cls==Long.class) {
      cmp=new TextField(fldName);
      converter=new StringToLongConverter("must be long");
  
  }else if (cls==Integer.class) {
       cmp=new TextField(fldName);
       converter=new StringToIntegerConverter("must be integer");
     
  }else if (cls==Byte.class) {
       cmp=new TextField(fldName);
       converter=new StringToIntegerConverter("must be byte");
  
  }else if (cls==BigInteger.class) {
       cmp=new TextField(fldName);
       converter=new StringToBigIntegerConverter("must be biginteger");
  
  }else if (cls==Float.class) {
       cmp=new TextField(fldName);
       converter=new StringToFloatConverter("must be float");
  
  }else if (cls==Double.class) {
       cmp=new TextField(fldName);
       converter=new StringToDoubleConverter("must be double");
  
  }else if (cls==BigDecimal.class) {
       cmp=new TextField(fldName);
       converter=new StringToBigDecimalConverter("must be bigdecimal");
  
  }else if (cls==Boolean.class) {
       cmp=new TextField(fldName);
       converter=new StringToBooleanConverter("must be bigdecimal");
  
  }else {
       System.out.println("Field Type not found.");
       cmp=new TextField(fldName);
     }
     
  // BINDING CONTROLS TO BEAN
  if (converter==null) binder.bind(cmp,fldName);
  else binder.forField(cmp)
         .withConverter(converter)
         //.bind(Item::getItemCount, Item::setItemCount);
         .bind(LambdaUtils.getter(fld), LambdaUtils.setter(fld));
       
  return cmp;
 }

}


6. A mock service for providing data.

We should use a database for managing information, but this mock service may do the trick. Notice the use of synchronized keyword.
Some other entities that extend Base.class can be added to the "ensureTestData" method.
Notice also that this service is created as a singleton in the old way. I think that it is better creating this singleton using CDI annotations ... but that's for a future post.


  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
package org.ximodante.vaadin.dataproviders;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.ximodante.vaadin.entities.Base;
import org.ximodante.vaadin.entities.Customer;
import org.ximodante.vaadin.enums.CustomerStatus;

public class BaseProvider <T extends Base>{
 
 @SuppressWarnings("rawtypes")
 private static BaseProvider instance;
 private static final Logger LOGGER = Logger.getLogger(BaseProvider.class.getName());
 private final HashMap<Long, T> contacts = new HashMap<>();
 private long nextId = 0;
 private Class<T> clazz;

 private BaseProvider(Class<T> clazz) {
  this.clazz=clazz;
 }

 /**
  * @return a reference to an example facade for Customer objects.
  */
 @SuppressWarnings({ "rawtypes", "unchecked" })
 public static BaseProvider getInstance(Class<?> clazz) {
  if (instance == null) {
   instance = new BaseProvider(clazz);
   instance.ensureTestData();
  }
  return instance;
 }

 /**
  * @return all available Customer objects.
  */
 public synchronized List<T> findAll() {
  return findAll(null);
 }

 /**
  * Finds all Customer's that match given filter and limits the resultset.
  *
  * @param stringFilter:
  *            filter that returned objects should match or null/empty string
  *            if all objects should be returned.
  * @param start :     the index of first result
  * @param maxresults: maximum result count
  * @return list a Customer objects
  */
 //public synchronized List<Customer> findAll(String stringFilter, int start, int maxresults) {
 @SuppressWarnings("unchecked")
 public synchronized List<T> findAll(String stringFilter, int start, int maxresults) {
  ArrayList<T> arrayList = new ArrayList<>();
  for (T contact : contacts.values()) {
   try {
    boolean passesFilter = (stringFilter == null || stringFilter.isEmpty())
      || contact.toString().toLowerCase().contains(stringFilter.toLowerCase());
    if (passesFilter) {
     arrayList.add((T) contact.clone());
    }
   } catch (CloneNotSupportedException ex) {
    Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
   }
  }
  Collections.sort(arrayList, new BaseComparator<T>());
  if (start<0 || maxresults<0) {
   start=0;
   maxresults=arrayList.size();
  }
  int end = start + maxresults;
  if (end > arrayList.size()) {
   end = arrayList.size();
  }
  return arrayList.subList(start, end);
 }

 /**
  * Finds all Customer's that match given filter.
  *
  * @param stringFilter
  *            filter that returned objects should match or null/empty string
  *            if all objects should be returned.
  * @return list a Customer objects
  */
 public synchronized List<T> findAll(String stringFilter) {
  return findAll(stringFilter, -1, -1); //Return all elements
 }
 /**
  * @return the amount of all customers in the system
  */
 public synchronized long count() {
  return contacts.size();
 }

 /**
  * Deletes a customer from a system
  * @param value
  *            the Customer to be deleted
  */
 public synchronized void delete(T value) {
  contacts.remove(value.getId());
 }

 /**
  * Persists or updates customer in the system. Also assigns an identifier
  * for new Customer instances.
  * @param entry
  */
 @SuppressWarnings("unchecked")
 public synchronized void save(T entry) {
  if (entry == null) {
   LOGGER.log(Level.SEVERE,
     "Customer is null. Are you sure you have connected your form to the application as described in tutorial chapter 7?");
   return;
  }
  if (entry.getId() == null || entry.getId() == 0) {
   entry.setId(nextId++);
  }
  try {
   entry = (T) entry.clone();
  } catch (Exception ex) {
   throw new RuntimeException(ex);
  }
  contacts.put(entry.getId(), entry);
 }

 /**
  * Sample data generation
  */
 public void ensureTestData() {
  if (findAll().isEmpty()) {
   
   // Data supplier for Customer class
   if (this.clazz.isAssignableFrom(Customer.class)) {
    final String[] names = new String[] { "Gabrielle Patel", "Brian Robinson", "Eduardo Haugen",
     "Koen Johansen", "Alejandro Macdonald", "Angel Karlsson", "Yahir Gustavsson", "Haiden Svensson",
     "Emily Stewart", "Corinne Davis", "Ryann Davis", "Yurem Jackson", "Kelly Gustavsson",
     "Eileen Walker", "Katelyn Martin", "Israel Carlsson", "Quinn Hansson", "Makena Smith",
     "Danielle Watson", "Leland Harris", "Gunner Karlsen", "Jamar Olsson", "Lara Martin",
     "Ann Andersson", "Remington Andersson", "Rene Carlsson", "Elvis Olsen", "Solomon Olsen",
     "Jaydan Jackson", "Bernard Nilsen" };
    Random r = new Random(0);
    //Long i=0L;
    for (String name : names) {
     String[] split = name.split(" ");
     @SuppressWarnings("unchecked")
     T c = (T) new Customer();
     ((Customer)c).setFirstName(split[0]);
     ((Customer)c).setLastName(split[1]);
     ((Customer)c).setStatus(CustomerStatus.values()[r.nextInt(CustomerStatus.values().length)]);
     save(c);
     //System.out.println(i);
    }
   } // else a supplier for other classes   
  }
 }
}


7. The bean class: Customer.class

we could also create other entities


 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
package org.ximodante.vaadin.entities;

import java.time.LocalDate;
import org.ximodante.vaadin.enums.CustomerStatus;

import lombok.Getter;
import lombok.Setter;

/**
 * A entity object, like in any other Java application. In a typical real world
 * application this could for example be a JPA entity.
 */
@SuppressWarnings("serial")
public class Customer extends Base {

 @Getter @Setter
 private String firstName = "";

 @Getter @Setter
 private String lastName = "";

 @Getter @Setter
 private LocalDate birthDate;
 //private Date birthDate;

 @Getter @Setter
 private CustomerStatus status;

 @Getter @Setter
 private String email = "";

 @Override
 public Customer clone() throws CloneNotSupportedException {
  return (Customer) super.clone();
 }
 @Override
 public String toString() {
  return firstName + " " + lastName;
 }
 public Customer() {
  super();
 }
 public Customer(Long id) {
  super(id);
 }
}

8. The MainView class

Don't use the first MainView displayed in this post, as it is not valid for dynamic creation of components. Use this one:


  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
package org.ximodante.vaadin;

import org.ximodante.vaadin.dataproviders.BaseProvider;
import org.ximodante.vaadin.entities.Base;
import org.ximodante.vaadin.entities.Customer;
import org.ximodante.vaadin.utils.GridUtils;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.PWA;

/**
 * 
 * @author ximo dante
 * 
 * This is a scketch of the UI 
 * 
 * +---------------------------------------------------------------------------------------+
 * |  TOOLBAR
 * |  +-------------------------+   +---------------------+  +-------------------------+   |
 * |  | Filter customer textbox |   | Clear Filter Button |  | Add new customer button |   |
 * |  +-------------------------+   +---------------------+  +-------------------------+   |
 * +----------------------------------------+----------------------------------------------+
 * |    Grid with customers values          |        Form for updating customers           |                       
 * |                                        |                                              |
 * | +-----------------------------------+  | label1                                       |
 * | | customer 1                        |  | +-----------------------------------------+  |
 * | +-----------------------------------+  | | Field 1                                 |  |
 * | | customer 2                        |  | +-----------------------------------------+  |
 * | +-----------------------------------+  | label2                                       |
 * | | customer 3                        |  | +-----------------------------------------+  |
 * | +-----------------------------------+  | | Field 2                                 |  |
 * | |                                   |  | +-----------------------------------------+  |
 * | |   .....                           |  |   ...........                                |
 * | +-----------------------------------+  |                                              |
 * |                                        | +-------------+   +---------------+          |
 * |                                        | | Save button |   | Delete button |          |
 * |                                        | +-------------+   +---------------+          |
 * +----------------------------------------+----------------------------------------------+
 */
@SuppressWarnings("serial")
@Route("")
@PWA(name = "Project Base for Vaadin Flow", shortName = "Project Base")
public class MainView extends VerticalLayout {
 
 @SuppressWarnings("rawtypes")
 private BaseProvider service = BaseProvider.getInstance(Customer.class);
 
 @SuppressWarnings("rawtypes")
 private Grid grid =null;
 
 private TextField filterText = null;
 private Button clearFilterTextBtn = null;
 private HorizontalLayout filtering = null;
 private BaseForm<Base> form= null;
 private HorizontalLayout main=null;
 private Button addCustomerBtn = null;
 private HorizontalLayout toolbar = null;
    
 public MainView() {
 
  createGrid();       // Add columns and headers to the grid assigning class attributes
  createBaseForm(); // Create the editing form
  createToolbar();    // Create the tool-bar with the filter, delete filter button and new customer buton
  createMainLayout(); // Display the grid and the form
  
  
  add(toolbar, main);  // Place the tool-bar over the main panel
  setHeight("100vh");
     
     updateList();        // Fill the grid with data
    }
 
 @SuppressWarnings("unchecked")
 private void createGrid() {
  grid=GridUtils.newGrid(Customer.class);
     grid.setSizeFull();
     
     // When a record is selected, it is transmitted to the form
     grid.asSingleSelect().addValueChangeListener(event -> {
      form.setBean((Base) event.getValue());
  });
 }
 
 /**
  * Creates the edit form
  */
 @SuppressWarnings({ "unchecked", "rawtypes" })
 private void createBaseForm() {
  form = new BaseForm(this, new Customer());
 }
 
 /**
  * Display filter textbox, clear filter button and add customer button
  */
 private void createToolbar() {
  // Filter text
  filterText = new TextField();
  filterText.setPlaceholder("Filter by name...");
        filterText.setValueChangeMode(ValueChangeMode.EAGER);
        filterText.addValueChangeListener(e -> updateList());
        
        // Clear filter text
        clearFilterTextBtn = new Button(new Icon(VaadinIcon.CLOSE_CIRCLE));
        clearFilterTextBtn.addClickListener(e -> filterText.clear());
        
        filtering = new HorizontalLayout(filterText, clearFilterTextBtn);
        
        addCustomerBtn = new Button("Add new customer");
        addCustomerBtn.addClickListener(e -> {
                grid.asSingleSelect().clear();
                form.setBean(new Customer());
        });
        toolbar = new HorizontalLayout(filtering, addCustomerBtn);
 }
 
 private void createMainLayout() {
  main = new HorizontalLayout(grid, form);
  main.setAlignItems(Alignment.START);
  main.setSizeFull();
 }
 
 /**
  * Update the grid with the values supplied by the data provider service
  */
 @SuppressWarnings("unchecked")
 public void updateList() {
     grid.setItems(service.findAll(filterText.getValue()));
 }
} 


9. The execution

Here is an image of the project


Right-click on the project and Run as- Run on server,  and here you are...



Happy coding!








No hay comentarios:

Publicar un comentario