📜 ⬆️ ⬇️

Simple framework UI ERP with Vaadin

Habrkat


Introduction


Why was it started? A year ago, began to write the ERP system. And from that moment our thorny path began. Defined a stack of technologies with which we will work. Briefly described the task and got to work.


During the course of training and parallel development, I began to draw the interface and the future architecture of the application. In the end, I tried to write another one freymork.


In this article I will try to describe what was done, what structure I implemented, describe the concrete implementation of classes, why, write examples of use. samopisny freymovrka. Well, tell you further plans.



Stack


Briefly about the stack used, we used the Finnish web-framework Vaadin 7.7. This is a tool that allows you to write single page application in almost one language (Java). Those. using language constructs to describe interface elements, which are then translated into HTML + JS code and displayed in the browser.


The application runs completely on the server, taking on all the calculations. User actions in the browser are processed on the server, the element is resized, it is redrawn on the server and sent the result to the user in the browser. In general, there will be many low-power user machines. read phones, tablets, old office machines and a couple of powerful servers.


API


The goals of the framework are to: quickly add elements to the common interface
displaying the desired data structure in the already familiar to all interface. As well as providing work through active recording. Those. working with the selected row in the table and adding necessary links to it.


The structure is the following:


PackageTitle
DataDatacontainer
TreeDataContainer
ElementsBottomtabs
Commonlogic
Commonview
Filterpanel
Logic
Menu
MenuNavigator
Mode
Workspace
PermissionModifierAccess
Permissionaccess
PermissionAccessUI

Data


In the Data package, the components that are necessary for binding (binding) data with UI elements. In the current version, containers are implemented that have additional methods for quickly assigning data to tables and trees. There are two classes in the package: DataContainer is an abstract class based on which derived containers are created to store certain classes of data. TreeDataContainer - implements the DataContainer class for storing elements with an indication of the hierarchy, and for displaying tree structures.


Examples of using all classes will be in the following sections.


Elements


A package in which there are all classes describing the elements of graphics and logic of the system.


The approach taken to building the interface is the use of individual views in which all the necessary components of the current UI are stored. We used the standard Vaddin components - the View component and its CommonView implementation, as well as the navigation component between Menu views. The logic of the work of these components in a bundle is taken from the Vaadin example , the example and how to generate it with maven archetype .


The CommonView implementation must contain a link to the Logic interface implementation or extend the existing CommonLogic implementation.


There is also a Mode listing which contains a list of modes of operation with existing interfaces.


The main graphic element is Workspace . This is a class in which there are two tables (in
which DataContainer data is assigned), the main one ( getTable() method) contains current information, a table with a list of all elements ( getTableAll() method) that can be selected to be added to the current container.


Navigation in Workspace implements the MenuNavigator element. It describes the list of standard methods of work with the Workspace , such as enabling the Add and Remove modes, Print, enable the filtering panel for tables described in the FilterPanel class.


To be able to edit the added information into the container (set into a table from the getTable () method), the BottomTabs class is used , into which tabs are added that contain an interface for editing information: tables, fields, drop-down lists and everything you need.


Permission


The package contains classes for implementing access rights to graphic elements and mechanisms for elevating access rights using roles.


ModifierAccess - enumeration of the available levels of access to the UI: disabled, reading, editing.


PermissionAccess is a class that implements the mechanisms for setting access rights, where the principle of elevation works. Those. if the user is assigned the right to read an item in one group, and edit to the other in the other, the user will ultimately have access to the maximum right — the right to edit.


PermissionAccessUI - the interface that implements into the graphic components to which rights are assigned.


Implementation


The DataContainer class is a class for storing data structures in the form of a container that extends the BeanItemContainer .


 abstract public class DataContainer<T> extends BeanItemContainer<T> { private ArrayList<String> captions = new ArrayList<>(); private ArrayList<Boolean> visible = new ArrayList<>(); private final ArrayList<String> headers = new ArrayList<>(); public DataContainer(Class<T> type) { super(type); if (validCaption()) initHeaders(); } private boolean validCaption() { return captions.size() == visible.size() && captions.size() == headers.size(); } abstract protected void initHeaders(); abstract public DataContainer loadAllData(); //.... } 

Created for easy assignment of a container to tables and trees, due to lists of captions ,
headers , visible in which are described which class properties will be displayed in the form of columns, which headers will have and which of them will be collapsed.


The mechanism for assigning a container to a table is implemented in CommonLogic :


 abstract public class CommonLogic implements Logic { private View view; public CommonLogic(View view){ this.view = view; } public View getView(){ return this.view; } public void setDataToTable(DataContainer container, CustomTable table) { if (container == null || table == null) return; table.setContainerDataSource(container); table.setVisibleColumns(container.getCaption()); table.setColumnHeaders(container.getHeaders()); table.setColumnCollapsingAllowed(true); for (int i = 0; i < container.getCaption().length; i++) { table.setColumnCollapsed(container.getCaption()[i], container.getVisible()[i].booleanValue()); } } } 

Workspace implements the following code:


 abstract public class Workspace extends CssLayout implements PermissionAccessUI { private Logic logic; private Float splitPosition = 50f; private Mode mode = Mode.NORMAL; public String CAPTION = ""; public ThemeResource PICTURE = null; private FilterTable table = null; private FilterTable tableAll = null; private ItemClickEvent.ItemClickListener editItemClickListener; private ItemClickEvent.ItemClickListener editItemClickListenerAll; private VerticalSplitPanel verticalSplitPanel = null; private HorizontalSplitPanel horizontalSplitPanel = null; private BottomTabs bottomTabs = null; private MenuNavigator navigator = null; private FilterPanel filterPanel = null; private ModifierAccess permissionAccess = ModifierAccess.HIDE; private VerticalLayout layout; private ItemClickEvent.ItemClickListener selectItemClickListener; private ItemClickEvent.ItemClickListener selectItemClickListenerAll; public Workspace(Logic logic) { this.logic = logic; table(); tableAll(); navigatorLayout(); filterPanel(); horizontalSplitPanel(); verticalSplitPanel(); addComponent(verticalSplitPanel); editOff(); setSizeFull(); } //... } 

Where table() and tableAll() methods for constructing a table for the current container and for a container with all records (directory). navigatorLayout() creates a menu for navigating (it’s also MenuNavigator ) and working with the current instance of Workspace . filterPanel() - creates a filter panel for the table with the current container. veritcalSplitPanel() describes how to create a bottom panel with tabs. tabs to edit the selected items in the table created in table() .


The MenuNavigator class provides a standard set of methods for working with the implementation of the Workspace :


 public abstract class MenuNavigator extends MenuBar implements PermissionAccessUI { private ModifierAccess permissionAccess = ModifierAccess.HIDE; private MenuItem add; private MenuItem delete; private MenuItem print; private MenuItem filter; public static final String ENABLE_BUTTON_STYLE ="highlight"; private Workspace parent; public MenuNavigator(String caption, Workspace parent) { this.parent = parent; setWidth("100%"); Command addCommand = menuItem -> add(); Command deleteCommand = menuItem -> delete(); Command printCommand = menuItem -> print(); Command filterCommand = menuItem -> filter(); add = this.addItem("add" + caption, new ThemeResource("ico16/add.png"), addCommand); add.setDescription(""); delete = this.addItem("delete" + caption, new ThemeResource("ico16/delete.png"), deleteCommand); delete.setDescription(""); print = this.addItem("print" + caption, new ThemeResource("ico16/printer.png"), printCommand); print.setDescription(""); filter = this.addItem("filter" + caption, new ThemeResource("ico16/filter.png"), filterCommand); filter.setDescription(""); this.setStyleName("v-menubar-menuitem-caption-null-size"); this.addStyleName("menu-navigator"); } //... } 

The class creates common menu items, describes the behavior logic in quasi- modal mode and requires the implementer of this class to describe the desired logic of operation.


Editing selected entries in a table created in table() done using elements added to BottomTabs in UI:


 abstract public class BottomTabs extends TabSheet implements PermissionAccessUI { private ModifierAccess permissionAccess = ModifierAccess.HIDE; private final List<String> captions = new ArrayList<>(); private final List<Component> components = new ArrayList<>(); private final List<Resource> resources = new ArrayList<>(); public BottomTabs() { captions.removeAll(captions); components.removeAll(components); resources.removeAll(resources); setSizeFull(); init(); } private void init() { initTabs(); for (int i = 0; i < this.components.size(); i++) { if (i < resources.size() && i < captions.size()) { this.addTab(this.components.get(i) , this.captions.get(i) , this.resources.get(i)); } } } //... } 

Lists for quicker adding of a component to bookmarks are also implemented here: captions - description of bookmarks headers, components - which element will be in this bookmark and resource - which icon for it will be displayed.


To implement access rights, you need to implement PermissionAccessUI and implement methods in it that should show what is active in this class and what is not, depending on the level of access:


 public interface PermissionAccessUI { void setPermissionAccess(ModifierAccess permission); void replacePermissionAccess(ModifierAccess permissionAccess); ModifierAccess getModifierAccess(); } 

and below is the implementation of these methods in the Workspace class:


 //... public void setPermissionAccess(ModifierAccess permission) { if (navigator != null) { navigator.replacePermissionAccess(permission); } if (bottomTabs != null) { bottomTabs.replacePermissionAccess(permission); } this.permissionAccess = permission; switch (permission) { case EDIT: { this.setVisible(true); this.setEnabled(true); break; } case READ: { this.setVisible(true); this.setEnabled(false); break; } case HIDE: { this.setVisible(false); this.setEnabled(false); break; } } } public void replacePermissionAccess(ModifierAccess permissionAccess) { PermissionAccess.replacePermissionAccess(this, permissionAccess); } public ModifierAccess getModifierAccess() { return permissionAccess; } //... 

The PermissionAccess class is the final class that performs the function. waste Class utils I myself do not like it but have not yet come up with another implementation , it takes the PermissionAccessUI component and, in accordance with the specified logic, increases the access level:


 public final class PermissionAccess { //... public static void replacePermissionAccess(PermissionAccessUI component, ModifierAccess newValue) { switch (component.getModifierAccess()) { case EDIT: { if (newValue.equals(ModifierAccess.HIDE) || newValue.equals(ModifierAccess.READ)) break; component.setPermissionAccess(newValue); break; } case READ: { if (newValue.equals(ModifierAccess.HIDE)) break; component.setPermissionAccess(newValue); break; } case HIDE: { component.setPermissionAccess(newValue); break; } } } //... } 

Examples


Data


An example of creating a container of some abstract class describing a subject area (it is also Bean ), let's call it Element :


 public class Element implements Serializable { private Integer id = 0; private String name = "element"; private Float price = 0.0F; public Element(Integer id, String name, Float price) { this.id = id; this.name = name; this.price = price; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Float getPrice() { return price; } public void setPrice(Float price) { this.price = price; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Element element = (Element) o; return Objects.equals(id, element.id) && Objects.equals(name, element.name) && Objects.equals(price, element.price); } @Override public int hashCode() { return Objects.hash(id, name, price); } } 

Classic implementation according to Bean specification.


For it we will create a container that will contain all the records.


 public class ElementContainer extends DataContainer<Element> { public ElementContainer() { super(Element.class); } @Override protected void initHeaders() { addCaption("id", "name", "price"); addHeader("ID", "", ""); addCollapsed(true, false, false); } @Override public DataContainer loadAllData() { add(new Element(1, "name1", 1.0f)); add(new Element(2, "name2", 2.0f)); add(new Element(3, "name3", 3.0f)); add(new Element(4, "name4", 4.0f)); add(new Element(5, "name5", 5.0f)); add(new Element(6, "name6", 6.0f)); add(new Element(7, "name7", 7.0f)); add(new Element(8, "name8", 8.0f)); add(new Element(9, "name9", 9.0f)); add(new Element(10, "name10", 10.0f)); add(new Element(11, "name11", 11.0f)); return this; } } 

Where in the addCaption , addHeader , addCollapsed the property of the Element class is listed, which will be used as columns, in what sequence, which headings and which ones will be hidden.


Implementing Classes for UI


Implementing the Workspace class as a MyLayout class:


 public class MyLayout extends Workspace { private ElementContainer container = new ElementContainer(); private MyTabSheet tabSheet; private MyMenu menu; public MyLayout(Logic logic) { super(logic); tabSheet = new MyTabSheet(); menu = new MyMenu("myMenu", this); logic.setDataToTable(container.loadAllData(), getTable()); setBottomTabs(tabSheet); setNavigator(menu); } @Override protected ItemClickEvent.ItemClickListener editTableItemClick() { return itemClickEvent -> { }; } @Override protected ItemClickEvent.ItemClickListener selectTableItemClick() { return itemClickEvent -> { }; } @Override protected ItemClickEvent.ItemClickListener editTableAllItemClick() { return itemClickEvent -> { }; } @Override protected ItemClickEvent.ItemClickListener selectTableAllItemClick() { return itemClickEvent -> { }; } } 

Where behavior is described when selecting a record in a table with all components and the current container (methods ItemClickEvent.ItemClickListener ), here they are empty. Also logic.setDataToTable(container.loadAllData(), getTable()) here describes how to set the current container to a table.


Implementing MenuNavigator in the MyMenu class:


 public class MyMenu extends MenuNavigator { public MyMenu(String caption, Workspace parent) { super(caption, parent); } @Override public void add() { if (getAdd().getStyleName() == null) getAdd().setStyleName(ENABLE_BUTTON_STYLE); else getAdd().setStyleName(null); } @Override public void delete() { if (getDelete().getStyleName() == null) getDelete().setStyleName(ENABLE_BUTTON_STYLE); else getDelete().setStyleName(null); } @Override public void print() { if (getPrint().getStyleName() == null) getPrint().setStyleName(ENABLE_BUTTON_STYLE); else getPrint().setStyleName(null); } } 

Where describes the change in the style of the pressed button and thus another mode should be included.


And the last element describing the graphics MyTabSheet is the implementation of BottomTabs :


 public class MyTabSheet extends BottomTabs { public MyTabSheet() { super(); } @Override public void initTabs() { addCaption("Tab1", "Tab2", "Tab3", "Tab4"); addComponent(new Label("label1"), new Label("label2"), new Label("label3"), new Label("label4")); addResource(FontAwesome.AMAZON, FontAwesome.AMAZON, FontAwesome.AMAZON, FontAwesome.AMAZON ); } } 

Where 4 bookmarks are created, in which the Label components are installed, and the Amazon icon is placed on all the bookmarks, do not count for advertising, just the letter A goes first .


This results in the following interface:


Picture from GitHub


Conclusion


Something happened a lot for the first time. But oh well The result is a simple framework that allows you to quickly create new interfaces for displaying a different set of data and describe the logic of working with them. It is also planned to add components that allow the creation of reference book editors (lists), which will be an interface for filling databases. Write a lot of tests (I will be happy with suggestions on how to test such things, because no ideas have yet appeared), as well as improve the API and the set of functions. Also create functionality for working with databases.


PS


This implementation arose in the course of the production situation, and the main application is for a specific customer, but I think this project can be used to solve other problems.


Thanks


@djeckson for developing the FilterPanel class for filtering and active participation in the project.


Links


Link to the repository , there is a description of how to connect.


')

Source: https://habr.com/ru/post/318496/


All Articles