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 go2. 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:
- Add additional dependencies to pom.xml
- Create an entity base class (Base.class) that every entity should extend
- Create a utility class for managing "lambdas".
- 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:
- Create a Binder instance to the bean class to bind
- 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))
- 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:
- The class is wrapping <T extends Base> which means that all the entities that will be displayed in this form should extend Base.class
- There are 2 buttons and an array of objects that represent the components to create dynamically
- A bean that will be managed in this form
- A service that provides de bean
- A binder that wraps a class of the generic type T
- 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)
- Creation of buttons and assigning an action to each one
- The binding of the elements of the form to the instance of the T class.
- 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:
- Component creation (lines 59, 62, 72, ..)
- Converter creators (lines 80, 84, 88, ...)
- The binding without conversion (line 116)
- 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 entities1 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 projectRight-click on the project and Run as- Run on server, and here you are...
Happy coding!
No hay comentarios:
Publicar un comentario