Статьи

Создание сетки CRUD для сущностей с помощью Dragome SDK

Что такое Драгом?

Dragome  — это комплект разработки программного обеспечения (SDK) для создания интернет-приложения RICH (RIA). Он предоставляет разработчикам механизм написания клиентского приложения на Java.

Код Javascript генерируется непосредственно из байт-кода, что дает много преимуществ по сравнению с преобразованиями источника в источник.

Dragome имеет открытый исходный код, полностью бесплатен и распространяется по лицензии GPL v3.0.

Зачем использовать Dragome SDK?

  • Кодируйте все на Java (на стороне сервера и на стороне клиента), оно будет автоматически преобразовано в javascript.
  • Более высокий уровень абстракции для разработки GUI с использованием компонентов.
  • Высокая производительность для сгенерированного кода на стороне клиента.
  • Процесс разработки на основе Java и повторное использование кода.
  • Используя отличную поддержку отладки, предлагаемую зрелыми Java IDE, такими как Eclipse, разработчики могут отлаживать клиентское приложение так же, как Java-приложение.
  • Используйте встроенную поддержку IDE для рефакторинга кода Java, чтобы улучшить гибкую разработку.
  • Вы минимизируете контакт с файлами HTML. Управляемый графическими дизайнерами, а не разработчиками, Dragome использует те же файлы, которые редактируют дизайнеры, просто простые HTML-файлы.
  • Обновления дизайна пользовательского интерфейса могут быть развернуты не ролями разработчика, поскольку код полностью отделен от файлов HTML.
  • Используйте продолжение в своем развитии: вы можете приостановить свою программу и продолжить ее, когда вам нужно.
  • Интеграция модульного тестирования: вы также можете запускать свои тесты Junit в браузере.
  • Java 8 включена.

Модули Dragome SDK

Dragome SDK можно разделить на пять основных частей:

  • Компилятор Dragome Bytecode to JavaScript: это самая важная часть Dragome, которая делает его мощным инструментом для создания RIA. Компилятор Dragome используется для перевода всего кода приложения, написанного на Java, в JavaScript.
  • Библиотека эмуляции JRE: Dragome включает в себя библиотеку, которая эмулирует подмножество библиотеки времени выполнения Java.
  • Привязки форм: на основе проекта gwt-pectin этот модуль обеспечивает легкие возможности привязки данных. Он использует API декларативного стиля (guice style) для определения моделей, команд и форм, а также связывания их с компонентами.
  • Callback evictor: отвечает за избавление от ада обратного вызова, для этого он использует инструментарий байт-кода и динамические прокси.
  • Регистратор метода: Простой метод-перехватчик для автоматического обнаружения изменений модели, он также использует инструментарий байт-кода.

Dragome предоставляет два режима выполнения, рабочий режим для выполнения всего на стороне клиента и режим отладки, который выполняет все на виртуальной машине Java без компиляции в javascript и выполняет удаленные обновления для браузера DOM.

Системные Требования

  • JDK 1,7 или выше.
  • Памяти нет минимальных требований.
  • Дискового пространства нет минимальных требований.
  • Операционная система без минимальных требований.
  • Важное примечание об отладке : не требуется использование какого-либо плагина для вашей IDE или для вашего браузера (любая современная версия Chrome к настоящему времени и любая версия Firefox в ближайшее время, Safari и IE в будущих версиях)

Пример Crud Grid

Этот пример основан на  AngularJS CRUD Grid с WebApi, EF, Bootstrap, Font Awesome & Toastr . Смотрите оригинальную демо  здесь .

Пример грубой сетки

Этот урок о Dragome версии того же самого приложения. Мы используем службу на стороне сервера Java для взаимодействия со встроенной базой данных через Hibernate для сопоставления ORM. Для построения пользовательского интерфейса мы используем механизм Component Builders, предоставленный Dragome, который обрабатывает несколько аналогичных концепций в среде AngularJS: двухстороннее связывание данных, подход шаблона переключателя / случая, компоненты показа или стиля с использованием выражений, автоматическое обнаружение изменений модели, обработка событий, шаблонные повторители с возможностями фильтрации и упорядочения и т. д.

Механизм Component Builders — это слой над компонентами Dragome UI и модулем «Привязки форм», которые значительно упрощают создание компонентов.

То же самое приложение может быть достигнуто с использованием компонентов напрямую, но оно будет более подробным и более трудным для реализации, чтобы получить те же преимущества связывания данных.

Также это может быть реализовано с помощью DOM-манипуляций и прямой обработки событий, точно так же, как компоненты Dragome делают это внутренне, но, опять же, это может быть намного более многословным, и поскольку в этом использовании будет почти любой уровень абстракции, это может быть очень трудным задача для выполнения.

Серверный код

На стороне сервера есть только один класс, это реализация нашего сервиса, который предоставляет экземпляры требуемых объектов, а также метаданные для создания пользовательского интерфейса.

Он обрабатывает взаимодействия с ORM EntityManager, такие как поиск, удаление, сохранение и обновление сущностей. Также он предоставляет метаданные для сущностей Place и People. Посмотрите, как пользоваться услугами  здесь .

Код на стороне клиента

Весь оставшийся код будет выполняться на стороне клиента, этот java-код будет преобразован в javascript и будет загружен браузером для: создания пользовательского интерфейса, управления экземплярами сущностей, сериализации / десериализации структур данных, установления связи с сервером, и т.п.

Наша первая настройка — это  создание  экземпляра CrudGrid , это основная модель, которую мы будем использовать, передавая, какую сущность мы хотим показать. Затем для начала сборки всех компонентов мы будем использовать  ComponentBuilder, созданный  с использованием текущей VisualPanel , где будут висеть все компоненты и подкомпоненты. Чтобы увидеть, как использовать шаблоны, взгляните на Template Engine .

CrudGrid crudGrid= new CrudGrid(entityType);
ComponentBuilder componentBuilder= new ComponentBuilder(this);

Раздел фильтра

Первый раздел, который мы строим, это панель фильтров.

Текстовое поле привязывается к  фильтрующему  свойству   экземпляра crudGrid с  помощью  метода toProperty , ранее мы выбрали  компонент VisualTextField для представления этого значения в представлении.

В случае « remove-filter » мы также добавляем условие, чтобы отключить его, когда выражение выполнено.

В целях очистки мы прикрепили прослушиватель щелчков, чтобы установить строку фильтра на пустое значение, поэтому при нажатии этой кнопки будет очищен двусторонний фильтр привязки данных, и   компонент фильтра (текстовое поле) будет автоматически обновлен с этим новым значением.

Раздел фильтра

Исходный код

private void buildFilter()
{
componentBuilder.bindTemplate("filter")
.as(VisualTextField.class)
.toProperty(crudGrid::getFilter, crudGrid::setFilter)
.build();




componentBuilder.bindTemplate("remove-filter")
.as(VisualLabel.class)
.disableWhen(() -> crudGrid.getFilter().length() == 0)
.onClick(v -> crudGrid.setFilter(""))
.build();
}

Связанный фрагмент HTML

<div class="input-group col-md-5 row filter">
<span class="input-group-addon"><i class="glyphicon glyphicon-filter"></i></span>
<input type="text" class="form-control" data-template="filter">
<span class="input-group-addon btn btn-default" data-template="remove-filter"><i class="glyphicon glyphicon-remove"></i></span>
</div>

заголовок

Для « add-mode-toggler » мы также добавляем условие, но в этом случае для целей стилизации каждый предоставленный стиль будет применяться всякий раз, когда логическое выражение « Соответственно » принимает значение «истина» или «ложь».

На этом этапе нам нужно создать заголовок каждого столбца с: поведением сортировки, настроенным стилем и отображением его имени. Для повторения шаблона « заголовок таблицы » для каждого столбца в  crudGrid.getColumns ()  мы используем метод « repeat ».

Метод Repeat выполнит проходящий блок для каждого столбца, этот блок получит экземпляр столбца и компоновщик для продолжения родительского конструирования, а также для создания новых внутренних компонентов.

Раздел заголовка

Исходный код

private void buildHeader()
{
componentBuilder.bindTemplate("add-mode-toggler")
.as(VisualLabel.class)
.onClick(v -> crudGrid.toggleAddMode())
.styleWith("glyphicon-minus", "glyphicon-plus")
.accordingTo(() -> crudGrid.isAddMode())
.build();
componentBuilder.bindTemplate("table-header")
.as(VisualPanel.class)
.toList(crudGrid.getColumns())
.repeat((column, builder) -> {
builder.onClick(() -> crudGrid.setOrderColumn(column)).build();
builder.styleWith(column.getStyleName()).when(() -> true);




builder.bindTemplate("column-name")
.as(VisualLabel.class)
.to(() -> column.getName())
.build();




builder.bindTemplate("order-icon")
.as(VisualLabel.class)
.styleWith("glyphicon-sort-by-alphabet", "glyphicon-sort-by-alphabet-alt")
.accordingTo(() -> crudGrid.getOrderColumn().getOrder().equals(Order.ASC))
.showWhen(() -> crudGrid.getOrderColumn() == column)
.build();
});
}

Связанный фрагмент HTML

 <tr>
<th class="col-md-1">
<div class="btn-toolbar"><i class="btn btn-default glyphicon" data-template="add-mode-toggler"></i></div>
</th>




<th data-template="table-header">
<div>
<span data-template="column-name"></span>
<i class="glyphicon" data-template="order-icon"></i>
</div>
</th>
</tr>

Добавить раздел

В этом разделе нам нужно показать разные компоненты в зависимости от   свойства поиска столбца. Для этого мы используем инструмент для выбора шаблонов с помощью выражения. Мы повторяем каждый столбец как предыдущий раздел, но внутри каждого шаблона мы хотим показать поле со списком или текстовое поле в зависимости от того, является ли это значением поиска или нет.

Используя   команду switchWith, мы настраиваем выражение желания переключателя, затем внутри создания потомков мы можем выразить регистр по умолчанию, используя  switchDefaultCase  и  switchCase  для конкретного значения регистра. Оба метода получают блок кода, который отвечает за сборку всего компонента (ов) дела.

Только компоненты, которые принадлежат текущему случаю, будут построены и показаны.

Добавить раздел

Исходный код

private void buildAddSection()
{




...




childrenBuilder.bindTemplate("columns")
.as(VisualPanel.class)
.toList(crudGrid.getColumns())
.repeat((column, builder) -> {




builder.switchWith(() -> !column.isLookup()).buildChildren(columnBuilder -> {




columnBuilder.bindTemplate("input")
.switchDefaultCase((caseBuilder) -> 
caseBuilder
.as(VisualTextField.class)
.toProperty(() -> crudGrid.getItem().getObject(), column.getName())
.disableWhen(() -> column.isAutoincrement())
.build());




columnBuilder.bindTemplate("select")
.switchCase(() -> false, (caseBuilder) -> 
caseBuilder
.to(new VisualComboBoxImpl(crudGrid.getLookupData(column.getLookupEntityType())))
.toProperty(() -> crudGrid.getItem().getObject(), column.getName())
.showWhen(() -> column.isLookup())
.build());
});
});




....

Связанный фрагмент HTML

 <td data-template="columns">
<input class="form-control" data-template="input"/>
<select data-template="select" class="form-control"></select>
</td>

Раздел объектов

Используя тот же механизм сверху для повторения столбцов, мы повторяем каждый элемент, класс элементов содержит ссылку на саму сущность. В этом случае мы хотим повторить их в определенном порядке и отфильтровать каждое из них по определенному выражению.

Для   метода orderBy мы указываем метод получения, который позволяет механизму повторителя получать значение для сравнения, и мы также указываем возрастающий или убывающий порядок, который мы хотим использовать.

Для целей фильтрации мы используем   метод фильтра , определяющий поставщика тестера, чтобы проверить каждый элемент, должен ли он быть показан или нет.

Раздел объектов

Исходный код

private void buildObjects()
{
componentBuilder.bindTemplate("objects")
.as(VisualPanel.class)
.toListProperty(crudGrid::getItems)
.orderBy(crudGrid.getColumnValueGetter(), () -> crudGrid.getOrderColumn().getOrder())
.filter(crudGrid::getFilterTester)
.repeat((item, itemBuilder) -> {
buildToolbar(item, itemBuilder);
buildColumns(item, itemBuilder);
});
}

Связанный фрагмент HTML

 <tr data-template="objects">
<td>
<div class="btn-toolbar" data-template="toolbar">
<div class="btn-group" data-template="view-mode">
<i class="btn btn-default glyphicon glyphicon-edit" data-template="edit"></i>
<i class="btn btn-default glyphicon glyphicon-trash" data-template="trash"></i>
</div>
<div class="btn-group" data-template="edit-mode">
<i class="btn btn-default glyphicon glyphicon-save" data-template="save"></i>
<i class="btn btn-default glyphicon glyphicon-remove" data-template="remove"></i>
</div>
</div>
</td>
<td data-template="columns">
<div data-template="view-mode">
<span data-template="not-lookup"></span>
<span data-template="lookup"></span>
</div>




<div data-template="edit-mode">
<input class="form-control" data-template="input"/>
<select data-template="select" class="form-control"></select>
</div>
</td>
</tr>

Отображение значений

Чтобы показать все значения столбцов, нам нужно повторить каждое из них, получить доступ к определенному значению и решить, как его показать.

В этом последнем разделе мы можем увидеть использование вложенных команд switch /  case , в коде верхнего уровня мы используем переключатель для выбора  editMode case (встроенное редактирование).

Второй переключатель находится в случае режима редактирования, там нам нужно выбрать, показывать ли текстовое поле или комбинированный список в зависимости от свойства поиска текущего столбца.

buildColumns исходный код

private void buildColumns(Item item, ComponentBuilder itemBuilder)
{
itemBuilder.bindTemplate("columns")
.as(VisualPanel.class)
.toList(crudGrid.getColumns())
.repeat((column, columnBuilder) -> {




columnBuilder
.switchWith(() -> item.isEditMode())
.buildChildren(columnChildrenBuilder -> {
buildViewMode(item, column, columnChildrenBuilder);
buildEditMode(item, column, columnChildrenBuilder);
});
});
}

buildEditMode исходный код

private void buildEditMode(Item item, Column column, ComponentBuilder columnChildrenBuilder)
{
columnChildrenBuilder.bindTemplate("edit-mode")
.switchCase(() -> true, (caseBuilder) -> {
return caseBuilder
.as(VisualPanel.class)
.switchWith(() -> !column.isLookup())
.buildChildren(viewModePanelBuilder -> {
viewModePanelBuilder.bindTemplate("input")
.switchDefaultCase((lookupCaseBuilder) -> 
lookupCaseBuilder
.as(VisualTextField.class)
.toProperty(item.getObject(), column.getName())
.disableWhen(() -> column.isAutoincrement())
.build());




viewModePanelBuilder.bindTemplate("select")
.switchCase(() -> false, (lookupCaseBuilder) -> 
lookupCaseBuilder
.to(new VisualComboBoxImpl<>(crudGrid.getLookupData(column.getLookupEntityType())))
.toProperty(item.getObject(), column.getName())
.disableWhen(() -> column.isAutoincrement())
.build());
}).build();
});
}

Основная модель

Весь пользовательский интерфейс взаимодействует с моделью CrudGrid, которая отвечает за хранение информации, выполнение действий над моделью, связь с сервером и предоставление некоторых инструментов, помогающих разработчикам создавать пользовательский интерфейс. Этот класс полностью отделен от создания Dragome UI, сборщиков, методологов или любого механизма, специфичного для фреймворка.

Единственная точка соединения — использование экземпляров ObservableList для автоматического обнаружения изменений в списке, но это можно считать независимым от фреймворка, поскольку он взаимодействует только со слушателем внутри.

public class CrudGrid
{
private boolean loading= true;
private String filter= "";
private boolean addMode;
private List<Item> objects;
private Item item;
private List<Column> columns;
private Tester<Item> filterTester= updateFilterTester();
private Column orderColumn;
private EntitiesProviderService entitiesProviderService;
private Class<?> entityType;




public CrudGrid(Class<?> entityType)
{
this.entityType= entityType;
List<Identifiable> all= entitiesProviderService.getAll((Class) entityType);
List<Item> result= new ArrayList<Item>();
for (Identifiable object : all)
result.add(new ItemImpl(object));




objects= new ObservableList<Item>(result);
columns= new ObservableList<Column>(entitiesProviderService.getColumnsFor(entityType));




item= initItem();
orderColumn= columns.get(0);




setLoading(false);
}




public void addObject()
{
getItems().add(item);
Identifiable added= entitiesProviderService.add(item.getObject());
item.getObject().setId(added.getId());
toggleAddMode();
}




public void deleteObject(Item item)
{
objects.remove(item);
entitiesProviderService.delete(item.getObject());
}
...

Полный шаблон для CrudGridComponent

Как правило, код пользовательского интерфейса связан с шаблоном HTML, который содержит вложенные элементы с атрибутами шаблона данных, совпадающими с именем использования bindTemplate.

Вы можете видеть, что нет никакой связи между шаблонами и кодом Java, за исключением тех атрибутов шаблона данных, которые отмечают, где находится каждый шаблон и подшаблон.

Внутри шаблонов нет логики, это чистый HTML без специальных тегов и стандартного использования атрибутов. Это делает их взаимозаменяемыми с другими шаблонами с той же спецификацией (те же вложенные шаблоны данных в элементах).

Графические дизайнеры могут работать независимо от разработчиков на всех этапах разработки, обновления дизайна пользовательского интерфейса могут выполняться любой ролью, поскольку с точки зрения разработчиков не требуется никакого вмешательства.

шаблон

<body>
<div data-template="loading">Loading...</div>
<div class="input-group col-md-5 row filter">
<span class="input-group-addon"><i class="glyphicon glyphicon-filter"></i></span>
<input type="text" class="form-control" data-template="filter">
<span class="input-group-addon btn btn-default" data-template="remove-filter"><i class="glyphicon glyphicon-remove"></i></span>
</div>




<table class="crud-grid table table-striped table-bordered table-condensed table-hover">
<tr>
<th class="col-md-1">
<div class="btn-toolbar"><i class="btn btn-default glyphicon" data-template="add-mode-toggler"></i></div>
</th>




<th data-template="table-header">
<div>
<span data-template="column-name"></span>
<i class="glyphicon" data-template="order-icon"></i>
</div>
</th>
</tr>
<tr data-template="add-section">
<td>
<div class="btn-toolbar">
<div class="btn-group">
<i class="btn btn-default glyphicon glyphicon-save" data-template="save-button"></i>
<i class="btn btn-default glyphicon glyphicon-remove" data-template="remove-button"></i>
</div>
</div>
</td>
<td data-template="columns">
<input class="form-control" data-template="input"/>
<select data-template="select" class="form-control"></select>
</td>
</tr>
<tr data-template="objects">
<td>
<div class="btn-toolbar" data-template="toolbar">
<div class="btn-group" data-template="view-mode">
<i class="btn btn-default glyphicon glyphicon-edit" data-template="edit"></i>
<i class="btn btn-default glyphicon glyphicon-trash" data-template="trash"></i>
</div>
<div class="btn-group" data-template="edit-mode">
<i class="btn btn-default glyphicon glyphicon-save" data-template="save"></i>
<i class="btn btn-default glyphicon glyphicon-remove" data-template="remove"></i>
</div>
</div>
</td>
<td data-template="columns">
<div data-template="view-mode">
<span data-template="not-lookup"></span>
<span data-template="lookup"></span>
</div>




<div data-template="edit-mode">
<input class="form-control" data-template="input"/>
<select data-template="select" class="form-control"></select>
</div>
</td>
</tr>
</table>
</body>