Vaadinを使用したシンプルなフレームワークUI ERP

ハブルカット


はじめに


なぜそれまでだったのですか? 1年前、彼らはERPシステムの作成を開始しました。 そして、その瞬間から私たちのとげのある道が始まりました。 私たちは、私たちが取り組むことになる技術のスタックを特定しました。 タスクについて簡単に説明し、動作するように設定します。


トレーニングと並行した「開発」の間に、アプリケーションのインターフェースと将来のアーキテクチャを引き出し始めました。 最後に、私は書いてみました 別の フレームワーク。


この記事では、何が行われたのか、どのような構造を実装したのか、クラスの具体的な実装について説明しようとします。 自作 フレームワーク。 さて、私はあなたにさらなる計画を教えます。



スタック


使用するスタックについて簡単に説明すると、フィンランドのWebフレームワークVaadin 7.7を使用しました。 これは、ほぼ同じ言語(Java) で単一ページのアプリケーションを作成できるツールです。 つまり 言語構成要素を使用してインターフェイス要素を記述し、それをHTML + JSコードに変換してブラウザーに表示します。


アプリケーションはサーバーで完全に実行され、すべての計算を処理します。 ブラウザーでのユーザーアクションはサーバーで処理され、要素のサイズが変更され、サーバーで再描画され、結果がブラウザーでユーザーに送信されます。 一般的に、多くの低電力ユーザーマシンがあります。 電話、タブレット、古い事務用車を読む そして、いくつかの強力なサーバー。


API


フレームワークが追求する目的は次のとおりです。一般的なインターフェイスに要素をすばやく追加します
使い慣れたインターフェイスで目的のデータ構造を表示します。 アクティブな録音を通じて作業を提供するだけでなく。 つまり テーブル内の選択した行を操作し、必要なリンクを追加します。


構造は次のとおりです。


パッケージ役職
データデータコンテナ
TreeDataContainer
要素ボトムタブ
コモンロジック
コモンビュー
フィルターパネル
ロジック
メニュー
MenuNavigator
モード
ワークスペース
許可修飾子アクセス
アクセス許可
PermissionAccessUI

データ


データパッケージに 、データをUI要素にバインドするために必要なコンポーネントが含まれています。 現在のバージョンは、テーブルとツリーにデータをすばやく割り当てるための追加のメソッドを持つコンテナを実装しています。 パッケージには2つのクラスがあります。DataContainer-特定のデータクラスを格納するために作成される派生コンテナに基づく抽象クラス。 TreeDataContainer-要素を階層で保存し、ツリー構造を表示するためのDataContainerクラスを実装します。


すべてのクラスの使用例は、次のセクションにあります。


要素


グラフィックスおよびシステムロジックの要素を記述するすべてのクラスが配置されているパッケージ。


インターフェイスを構築するために取られるアプローチは、現在のUIのすべての必要なコンポーネントが格納される個別のタイプを使用することです。 標準のVaddinコンポーネント-ViewコンポーネントとCommonViewの実装、およびメニュービュー間のナビゲーションコンポーネントを使用しました。 束の中のこれらのコンポーネントの動作のロジックは、 Vaadinの 、およびMavenアーキタイプを使用して生成する方法から取得されます。


CommonViewの実装には、 Logicインターフェースの実装への参照を含めるか、 CommonLogicの既存の実装を拡張する必要があります


また、既存のインターフェイスでの動作モードのリストを含むMode列挙もあります。


主なグラフィック要素はWorkspaceです。 これは、2つのテーブル(
DataContainerデータが割り当てられている)、メインのもの( getTable()メソッド)には現在の情報、すべての要素のリストを持つテーブル( getTableAll()メソッドを選択して、現在のコンテナに追加することができますgetTableAll()


Workspaceのナビゲーションは、 MenuNavigator要素を実装します。 追加および削除、印刷の組み込み、 FilterPanelクラスで説明されているテーブルのフィルタリングパネルの組み込みなど、 Workspaceを操作するための標準メソッドのリストについて説明します。


コンテナーに追加された情報( getTable()メソッドからテーブルに設定)を編集する機能のために、 BottomTabsクラスが使用され 、そこに情報を編集するためのインターフェースを含むタブが追加されます:テーブル、フィールド、ドロップダウンリストおよび必要なすべて


許可


このパッケージには、グラフィック要素へのアクセス権を実装するためのクラスと、ロールを使用してアクセス権を増やすメカニズムが含まれています。


ModifierAccess -UIへのアクセスの利用可能なレベルのリスト:無効、読み取り、編集。


PermissionAccess-権限の昇格の原則が適用される、アクセス権を設定するメカニズムを実装するクラス。 つまり ユーザーが1つのグループでアイテムの読み取り権を、別のグループで編集中に割り当てられている場合、結果として、ユーザーは最大の権利(編集権)を利用できます。


PermissionAccessUI-権利が割り当てられているグラフィックコンポーネントに実装されるインターフェイス。


実装


DataContainerクラスは、 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(); //.... } 

キャプションリストにより、テーブルやツリーへのコンテナの便利な割り当てのために設計されており、
headers 、クラスのどのプロパティが列の形で表示されるか、どのヘッダーがあり、どれが折りたたまれるかを説明する可視です。


コンテナをテーブルに割り当てるメカニズムは、 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は次のコードを実装します。


 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(); } //... } 

ここで、 table()およびtableAll() 、現在のコンテナとすべてのレコード(参照)を含むコンテナのテーブルを構築するためのメソッドです。 navigatorLayout()は、ナビゲーション(別名MenuNavigator )およびWorkspaceの現在のインスタンスを操作するためのメニューを作成します。 filterPanel() -現在のコンテナでテーブルのフィルタリングパネルを作成します。 veritcalSplitPanel()は、タブ付きのveritcalSplitPanel()作成について説明します タブ table()作成されたtable()内の選択されたアイテムを編集しtable()


MenuNavigatorクラスは、 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"); } //... } 

クラスは共通のメニュー項目を作成し、動作のロジックを記述します モーダルモードであり、このクラスを実装して作業の望ましいロジックを記述する必要があります


table()作成されたtable()内の選択されたレコードの編集は、UI BottomTabsに追加された要素を使用して行われtable()


 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)); } } } //... } 

コンポーネントをブックマークにすばやく追加するために、リストもここに実装されますcaptionsブックマークヘッダーの説明、 components -このブックマークに含まれる要素とresource -表示されるアイコン。


アクセス権を実装するには、 PermissionAccessUIを実装し、アクセスレベルに応じて、このクラスでアクティブなものとそうでないものを示すメソッドを実装する必要があります。


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

以下は、 Workspaceクラスのこれらのメソッドの実装です。


 //... 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; } //... 

PermissionAccessクラスは、関数を実行するfinalクラスです。 スクラップ Utilsクラス 自分では好きではありませんが、別の実装を思いついていませんPermissionAccessUIコンポーネントを受け取り、指定されたロジックに従って、アクセスのレベルを上げます。


 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; } } } //... } 


データ


ドメイン( Beanでもあります)を記述するいくつかの抽象クラスのコンテナを作成する例は、 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); } } 

Beanの仕様に従った従来の実装。


彼のために、すべてのレコードを含むコンテナを作成します。


 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; } } 

addCaptionaddHeaderaddCollapsedのどこに、 Elementクラスのプロパティがリストされます。これは、列の形式で使用され、どの順序で、どのヘッダーが非表示になります。


UIのクラス実装


MyLayoutクラスとしてのWorkspaceクラスの実装:


 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 -> { }; } } 

すべてのコンポーネントと現在のコンテナ( ItemClickEvent.ItemClickListenerメソッド)を持つテーブルのエントリを選択するときの動作が説明されている場合、ここでは空です。 また、現在のコンテナをテーブルlogic.setDataToTable(container.loadAllData(), getTable())設定するlogic.setDataToTable(container.loadAllData(), getTable())logic.setDataToTable(container.loadAllData(), getTable())について説明します。


MyMenuクラスでMenuNavigatorを実装する:


 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); } } 

押されたボタンのスタイルの変更を説明し、それによって別のモードをオンにする必要がある場合。


そして、 MyTabSheetグラフィックスを記述する最後の要素は、 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 ); } } 

Labelコンポーネントがインストールされている4つのブックマークが作成され、Amazonアイコンがすべてのブックマークに配置されている場合、 広告にカウントしないでください、ちょうど文字Aが最初に来る


結果はこのインターフェースです:


GitHubイメージ


おわりに


初めて多くのことが起こりました。 でもまあ その結果、異なるデータセットを表示するための新しいインターフェイスをすばやく作成し、それらを操作するロジックを記述することができるシンプルなフレームワークが作成されます。 また、データベースを作成するためのインターフェースとなるディレクトリ(リスト)のエディターを作成できるコンポーネントを追加する予定です。 APIと機能セットを改善するだけでなく、多くの多くのテスト(アイデアがまだ出ていないため、そのようなことをテストする方法を提案できることを嬉しく思います)を作成します。 データベース機能も作成します。


PS


この実装は生産状況で発生し、主なアプリケーションは特定の顧客向けですが、このプロジェクトは他の問題を解決するために使用できると思います。


謝辞


@djecksonは、フィルタリングのためにFilterPanelクラスを開発し、プロジェクトに積極的に参加しました。


参照資料


リポジトリへのリンク、接続方法の説明があります。



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


All Articles