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

Привет! Меня зовут Никита, я Java‑разработчик в Red Collar. Так вышло, что я стал первопроходцем платформы Jmix и в короткие сроки сделал на ней уже два проекта. Кажется, мне даже понравилось ? Делюсь своим опытом и мыслями, почему вообще решил познакомиться с платформой. Также кратко расскажу об этапах работы над проектами, который реализовали на Jmix.
Коротко о Jmix
Jmix — это высокоуровневая Java-платформа на базе открытого кода для быстрого создания корпоративных информационных систем. Платформа Jmix — преемник другой известной платформы, CUBA Platform. Поставляется с расширенными инструментами и обширным набором функциональных модулей.
Система позволяет создавать как серверную часть, так и пользовательский интерфейс приложения. Подходит для разработки приложений со сложной моделью данных и широким пользовательским интерфейсом.
В чем суть Jmix и почему Java-разработчику проще адаптироваться
Jmix основан на Spring Boot, который де-факто является стандартом для создания корпоративных веб-приложений Java. Это означает, что разработчик может использовать множество сторонних библиотек и фреймворков с минимальной конфигурацией в дополнение к функциям, предоставляемым Jmix. Джависту будет несложно освоить платформу — здесь есть подробная документация в двух языках, которая поможет разобраться на старте.
Платформа предоставляет множество функций с генерацией кода. Это не значит, что код вообще писать не придется, просто сокращается время на рутинные процессы.
Jmix Studio — что это такое
Отдельного внимания заслуживает компонент платформы Jmix Studio, который является плагином для IntelliJ IDEA. Studio реализует визуальные инструменты для разработки приложения и помогает на всех этапах:
создание и настройка проекта;
определение модели данных;
создание сценариев миграции базы данных;
разработка экранов пользовательского интерфейса в визуальном редакторе.
Модуль обеспечивает расширенную навигацию, автозавершение кода и проверки, специфичные для проектов Jmix. Одновременно с разработкой кода в стандартном «текстовом» режиме разработчик может полноценно использовать визуальные редакторы экранных форм, бизнес-процессов и иной инструментарий для ускорения разработки проекта.
Мы обратили внимание на платформу, потому что она ускоряет работу, позволяет быстро создавать UI-компоненты и навешивать на них информацию. Вместе с Java-коллегой начали факультативно знакомиться с Jmix. В процессе коллега поняла, что она старовер и ей ближе Spring, а я продолжил изучать Jmix — «Java на стероидах», получается.
Вскоре к нам пришел клиент с запросом оперативно сделать промо-сайт с большим количеством анимации, 3D-моделей и графики, а также с удобной админкой для управления каталогом, вакансиями и формами обратной связи. Решили попробовать сделать с Jmix админку, которую обычно пишем на Laravel и на Bitrix.
На реализацию ушел месяц. Функциональность сайта должна была включать:
Каталог товаров с фильтрами, анимацией.
Раздел с вакансиями.
Формы обратной связи для клиентов и партнеров.
Административную панель с гибкой системой ролей для управления всеми разделами сайта.
Отдельно можно отметить удобную работу с поддержкой гибкой системы ролей. Также можно с помощью кода сделать несколько классов и назначить пользователям их роли и ограничения.
В рамках нашего проекта админкой могут пользоваться несколько человек — со стороны и нас, и клиента. Сделали несколько ролей в админке. 1 пользователь мог редактировать товары, но не мог редактировать раздел с вакансиями. 2 пользователь, наоборот, редактирует вакансии, но товары не затрагивает.
Второй проект на Jmix: ошибки, шишки и решения
Между первым и вторым — перерыв оказался небольшим, и я сразу занялся следующим схожим проектом. Здесь подробнее расскажу об этапах работы над геймдев-сайтом, реализованном с Jmix.
На первый взгляд казалось, что проект будет очень похожим на первый — после него я уже многое проработал, набрал шишек, которые уже прошли, но…

Реализация
Основное отличие второго проекта от первого: необходимости работать с блочной структурой контента на некоторых страницах — с поддержкой разных типов блоков и их порядка. К сожалению, Jmix не поддерживал такой функционал из коробки. Его необходимо было реализовать.
Помучил вопросами нашего PHP-разработчика, придумал базовую структуру и приступил к реализации. В основе лежит интерфейс Block, у которого есть свойство type — все блоки будут наследоваться от этого интерфейса, определять свой тип и уже в наследнике хранить нужные блоку данные.
public interface ContentBlock { @JSONPropertyIgnore BlockType getBlockType(); }
public enum BlockType { RICH_TEXT_AREA("richTextAreaBlock"), PRIVACY_TERMS("privacyTermsBlock"), CODE("codeBlock"), QUOTE("quoteBlock"), IMAGE_WITH_CAPTION("imageWithCaptionBlock"), GAME_SCREENSHOT("gameScreenshot"), GAME_FEATURE("gameFeature"), ABOUT_VALUE("aboutValue"), ABOUT_PERK("aboutPerk"), GAME("game"); private String type; BlockType(String type) { this.type = type; } public String getType() { return type; } }
Далее для каждого блока создавался визуальный компонент для удобного заполнения. В самом компоненте каждый блок являлся своеобразным стейтом для сохранения или для отображения при редактировании сущностей. Каждому визуальному компоненту автоматически добавлялись кнопки для изменения их порядка в родителе.
public class RichTextAreaBlock implements Serializable, ContentBlock { private final String type = getBlockType().getType(); private String label; private String content; @Override @JSONPropertyIgnore public BlockType getBlockType() { return BlockType.RICH_TEXT_AREA; } }
@UiController("RichTextAreaFragment") @UiDescriptor("rich-text-area-fragment.xml") public class RichTextAreaFragment extends ScreenFragment { private RichTextAreaBlock state; // ...... }
Для хранения использовал тип JsonObject, который впоследствии мапился в Java класс. Что мы получаем в итоге: например, возьмем Новость — у нас есть блок, а у блока есть несколько наследников. В админке с помощью кнопок создаем нужные блоки — они отображаются в контейнере. С помощью кнопок, встроенных в каждый компонент, мы можем менять блоки местами.
Сохраняя сущность, проходимся по очереди по каждому компоненту в контейнере, берем из него стейт, собираем их в массив. Дальше сохраняем это либо в виде строки, либо в виде JsonObject’а и кладем его в поле таблицы.
Что получилось:

JavaScript Component
После того, как создал блочную структуру для контента, возник вопрос с текстовым контентом новостей и вакансий. Нужно было выделить текст, добавить разные заголовки, создать маркированные и немаркированные списки, добавить выравнивание текста.
Подумал о RichTextArea из коробки Jmix, но его функциональности не хватило. Здесь в историю с двух ног врывается JavaScript Component — компонент пользовательского интерфейса, который может работать с любой оболочкой JavaScript без реализации компонента Vaadin. Таким образом, можно интегрировать любой чистый компонент JavaScript в свое приложение на основе Jmix.
Работает это таким образом:
Объявляем его в XML-дескрипторе экрана.
Прописываем нужные зависимости на локальные файлы, стили, так и обычные зависимости (с рядом ограничений).
Далее создаем коннектор — код на JS.
Сonnector JS
ui_ex1_components_javascript_RichTextEditor = function () { let connector = this; let element = connector.getElement(); element.innerHTML = ` <div id="${connector.getState().data.options.editorId}"/> `; connector.onStateChange = function () { let state = connector.getState(); let data = state.data; let toolbarOptions = { container: [ [{header: [2, 3, 4, false]}], ["bold"], ["underline"], [{list: "ordered"}, {list: "bullet"}], [ {align: ""}, {align: "center"}, {align: "right"}, {align: "justify"}, ], ["link"], [{direction: "rtl"}], ["clean"], ], }; let formats = ["link", "list", "header", "bold","underline", "direction", "align"]; let quill = new Quill(`#${connector.getState().data.options.editorId}`, { theme: "snow", formats: formats, modules: { toolbar: toolbarOptions, }, }); quill.on('text-change', function (delta, oldDelta, source) { if (source === 'user') { connector.valueChanged(quill.getText(), quill.getContents(), quillGetHTML(quill.getContents())); } }); connector.setValue = function (value) { quill.setContents(htmlToDelta(value)) quill.update(); } } function quillGetHTML(inputDelta) { var tempCont = document.createElement("div"); (new Quill(tempCont)).setContents(inputDelta); return tempCont.getElementsByClassName("ql-editor")[0].innerHTML; } function htmlToDelta(html) { const div = document.createElement('div'); div.setAttribute('id', 'htmlToDelta'); div.innerHTML = `<div id="quillEditor" style="display:none">${html}</div>`; document.body.appendChild(div); let formats = ["link", "list", "header", "bold", "underline", "align", "direction"]; let toolbarOptions = [ [{header: [2, 3, 4, false]}], ["bold"], [{list: "ordered"}, {list: "bullet"}], [ {align: ""}, {align: "center"}, {align: "right"}, {align: "justify"}, ], [{direction: "rtl"}], ["link"], ["clean"], // remove formatting button ]; const quill = new Quill('#quillEditor', { theme: 'snow', formats: formats, modules: { toolbar: toolbarOptions, }, }); const delta = quill.getContents(); document.getElementById('htmlToDelta').remove(); return delta; } };
В Java-реализации экрана создаем переменную компонента. Если надо, добавляем функции, которые можно вызвать из коннектора. Саму переменную можно использовать, чтобы вызывать функции внутри JS-файла.
На один из популярных редакторов текста был мануал в документах Jmix. Это редактор Quill, и он подошел. Все минимальные функции для задач он поддерживал. Я мог спокойно поменять местами или убрать кнопки из тулбара, которые мне не нужны.
Итоги: с чего начать изучение Jmix и стоит ли начинать
По факту, в обучении ничего сверхъестественного нет. Джависту будет легко ворваться — пролистать документацию, сделать компонент и свериться с докой. Отличие в том, что помимо работы с данными, здесь оперируем с UI-компонентами, при этом уверенных знаний как у фронта в верстке здесь не надо — все на XML.
Хорошо работает с миграцией, не нужно писать вручную. Например, для сущности у нас был определенный набор полей. Если клиент решит заменить одно поле другим, не нужно писать миграцию. Убираем в коде одно поле, добавляем другое, прописываем тип, настраиваем связи. При запуске проекта автоматически происходит миграция.
Подводя небольшие итоги, проговорю, что с Jmix в кратчайшие сроки сделали и внедрили структуру, которая в базовом виде пытается быть похожей на то, что происходит в Bitrix и Laravel.
