lsFusion 4: сводные таблицы, графики, карты и календари, OAuth-аутентификация, темная тема и многое другое

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.


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


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


Стоит отметить, что если в предыдущих версиях акцент делался преимущественно на логику предметной области (то что под капотом), в четвертой версии основное внимание было уделено логике представлений (тому что на поверхности) — UI и всему что с этим связано.


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


Но хватит лирических отступлений, вернемся к тому, что же появилось в новой версии.


Оглавление
  • Новые представления списков
    • Группировочные представления
      • Сводная таблица
      • Графики и диаграммы
    • Карта и календарь
    • Кастомизируемые представления
  • Темная тема и обновление дизайна
  • OAuth аутентификация и саморегистрация
  • Обратная интернационализация
  • Переход по ссылке
  • Вычисляемые заголовки контейнеров и форм
  • Полноэкранный режим в веб
  • Ручное обновление представлений
  • Выполнение http-запросов на клиенте
  • Расширение формы в контексте вызова
  • Оптимизация работы с DOM

Новые представления списков


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


Тут, конечно, стоит отметить одну важную вещь. Так уж получилось, что по многим причинам frontend уже достаточно давно почти полностью и бесповоротно ушел в веб (а точнее javascript и HTML). Найти готовую компоненту, да еще и под нормальной лицензией, для десктоп (тот же Java Swing или RCP) сейчас практически невозможно. Соответственно все новые представления в платформе поддерживаются только под веб. Переписывать всех их под десктоп на наш взгляд не имеет никакого смысла. Это, впрочем, не означает, что десктоп-клиент поддерживаться не будет (как минимум по причине наличия огромного количества оборудования у которого нет нормального http-интерфейса). Но функция десктоп-клиента в lsFusion все же будет больше сводится либо к специализированным OLTP-интерфейсам, либо к интерфейсам, где время отклика очень критично. Веб-клиент же будет считаться основным клиентом, и соответственно lsFusion можно также будет рассматривать как платформу разработки веб-приложений.


Группировочные представления


Так получилось, что платформа lsFusion изначально использовалась в относительно крупных проектах (с террабайтными базами и сотнями, а то и тысячами, одновременных пользователей) и, как правило, аналитический модуль (BI) в таких проектах устанавливался отдельно (как для производительности, так и потому что специализированные BI-решения имеют максимально эргономичные интерфейсы именно для задач аналитики). Безусловно, в крупных проектах такая архитектура оправдана, но в проектах поменьше, да и в отдельных подзадачах крупных проектов, часто возникает необходимость во встроенных инструментах аналитики.


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




Отметим, что на физическом уровне операция группировки в группировочных представлениях может выполняться как на сервере БД, так и на клиенте. Платформа управляет этим выбором автоматически: так если количество групп уменьшается, или количество исходных данных меньше порога, то данные обрабатываются на клиенте (чтобы исключить лишние обращения к серверу). В большинстве остальных случаев группировка осуществляется на сервере SQL запросом (впрочем, промежуточные итоги все равно вычисляются на клиенте).


Изменение группировок может происходить пользователем при помощи соответствующего drag-drop интерфейса. Разработчик же может задавать группировки при помощи соответствующего синтаксиса в операторе FORM.
FORM myReport
    OBJECTS l = Ledger PIVOT 'Area Chart'
    PROPERTIES (l) customer ROW, sum MEASURE
;

Сводная таблица


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


  • Может иметь в качестве колонок данные. Стоит отметить, что обычные таблицы тоже умеют в качестве колонок использовать данные — через механизм групп-в-колонки, но на самом деле это абсолютно независимый механизм и часто используется вместе с группировочными представлениями.
  • Может показывать промежуточные итоги в рядах и колонках. При этом пользователь может сворачивать группы, тем самым показывая только итоги без детализации внутри.

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


  1. Упорядочивание данных происходит в рамках каждой группы
  2. Для фильтрации данных можно использовать как обычную фильтрацию (которая работает для всех представлений), так и клиентскую (выпадающий список в drag-drop панели со свойством). Первая при этом обрабатывается на сервере, вторая соответственно на клиенте (за исключением фильтрации колонок, которая также по сути выполняется на сервере).
  3. Выгрузка в Excel делается на клиенте, при этом сохраняется все форматирование сводной таблицы, начиная от фона ячеек и заканчивая функционалом сворачивания / разворачивания групп

Также для сводной таблицы поддерживается механизм детализации (drill-down) — при двойном нажатии мышкой по ячейке со значением показателя, можно выбрать поле по которому необходимо получить детализацию, после чего это поле автоматически добавится в качестве группировки, а значения ряда, по которому было двойное нажатие, будут добавлены в качестве фильтра.


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






Графики и диаграммы


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


На данный момент в платформе из коробки поддерживаются следующие типы графиков / диаграмм:


  • Столбчатая диаграмма (вертикальная / горизонтальная, обычная / стековая)
  • Линейный график
  • Круговая диаграмма
  • Плоскостная диаграмма
  • Точечная диаграмма



Приведенные выше типы графиков являются наиболее распространенными типами графиков. Для их отрисовки используется open-source библиотека plotly, соответственно список типов графиков может быть легко расширен при помощи любой из других многочисленных поддерживаемых этой библиотек компонент (впрочем стоит отметить что далеко не все из этих компонент подходят для группировочного представления). Правда, пока возможности добавлять свои типы графиков в группировочное представление у разработчика нет (по аналогии, например, с кастомизируемыми представлениями, о них чуть позже), но в одной из ближайших версий такая возможность скорее всего появится.


Диаграммы поддерживают выгрузку в img-формат, а также клиентскую фильтрацию (когда можно включать / исключать отдельные пункты легенды) и упорядочивание. Тут стоит отметить, что пользовательского интерфейса упорядочивания в самих диаграммах нет, поэтому чтобы его задать, необходимо переключиться на сводную таблицу, задать там, а затем переключиться назад.


Карта и календарь


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


Для решения такого рода задач в четвертой версии lsFusion были добавлены два наиболее распространенных представления:


  • Карта
    • Позволяет отображать список объектов в виде точек и / или полигонов на карте.
    • Для этих точек можно задавать цвет, картинку, компоненту которая будет отображаться всплывающим окном при выборе точки на карте и т.п.
    • Точки автоматически объединяются в группы, если масштаб карты не позволяет показать все точки одновременно в одном месте
    • Позволяет редактировать данные (например перемещать точки на карте или изменять полигоны), при этом автоматически изменяются значения соответствующих свойств (как если бы пользователь вводил эти значения в таблице вручную).
  • Календарь
    • Позволяет отображать список объектов в виде дат или интервалов дат на календаре.
    • Позволяет редактировать данные (например перемещать элементы в календаре).





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


  • Карта
    • longitude — долгота точки
    • latitude — ширина точки
    • polygon — строка с координатами вершин полигона через пробел и запятую
    • line — идентификатор линии точки
    • icon — иконка точки
    • и т.д.
  • Календарь
    • date, dateTime — дата или дата / время события
    • dateFrom, dateTimeFrom — дата или дата / время начала события
    • dateTo, dateTimeTo — дата или дата / время окончания события
    • name — заголовок события
    • и т.д.
FORM map 'Map'
    OBJECTS o = Element MAP
    PROPERTIES (o) longitude, latitude, polygon
    PROPERTIES name = name(o) IF o IS Point, color = RGB(25500IF isInAnyArea(o)
    PROPERTIES SHOWIF o IS Point namePopup = name(o) PANEL, inParis '' = 'I am ' + (CASE WHEN isInAnyArea(o) THEN '' ELSE 'not ') + 'in Paris. Drag me' PANEL
    PROPERTIES (o) 'Add point' = NEW[Point], 'Add area' = NEW[Area], DELETE
;
FORM calendar 'Calendar'
    OBJECTS e = Event CALENDAR
    PROPERTIES (e) date, name, NEWDELETE
;
При желании с картой и календарем можно поэкспериментировать на официальном сайте lsFusion в разделе Попробовать онлайн (Режим Платформа -> Карта и календарь).

Кастомизируемые представления


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


Поддержка этих представлений состоит из двух частей:


  1. Загрузка заданных javascript и css файлов при открытии web-клиента. Так по умолчанию все файлы, находящиеся в папке web (и всех ее подпапках) на сервере приложений, передаются с сервера приложений на веб-сервер, а затем и в браузер, где они автоматически загружаются в открываемую страницу web-клиента. Отметим, что эта операция напрямую не связана с кастомизируемыми представлениями, и теоретически может быть использована в других механизмах, например, в кастомизируемых клиентских действиях.
  2. Создание для группы объектов кастомизируемого представления. При создании такого представления необходимо задать javascript-функцию, которая будет вызвана в момент отрисовки представления (если быть точным необходимо задать имя поля window содержащего функцию отрисовки, но так как при загрузке javascript файла все function автоматически кладутся в одноименные поля window, разница не настолько принципиальна). Функция отрисовки должна иметь три параметра:
    • element — элемент DOM, в котором необходимо отрисовать представление
    • objects — список javascript-объектов, поля которых соответствуют свойствам этого представления (имя поля = имя экспорта свойства, значение поля = значение свойства)
    • controller — javascript-объект, содержащий методы по изменению данных / состояния группы объектов (например, изменению свойств, текущих объектов).

Созданное кастомизируемое представление группы объектов автоматически становится ее представлением по умолчанию.


Пример
В папку web сервера приложений кладем следующие js и css файлы (с любым именем, например test.js и test.css):

function calendar(element, objects, controller) {
    if(controller.calendar == null) { // lazy initialization
        controller.calendar = new FullCalendar.Calendar(element, {
            height: 'parent',
            editable: true,
            eventChange: function(info) {
                controller.changeDateProperty('date', controller.objects[info.event.extendedProps.index], info.event.start.getFullYear(),
                    info.event.start.getMonth() + 1,info.event.start.getUTCDate() + 1); // month and day are zero-based in full calendar
            },
            eventClick: function(info) {
                controller.changeSimpleGroupObject(controller.objects[info.event.extendedProps.index], false, info.el);
            }
        });
        setTimeout(function () {
            controller.calendar.render();
        }, 0);
    }

    controller.objects = objects; // need to save it to work with changes
    controller.calendar.setOption('events', objects.map((obj, index) =>
        Object.assign({}, obj, {
            index: index, // needed to work with changes
            classNames: controller.isCurrent(obj) ? 'event-highlight' : '' // highlighting current element
        })));
}

.event-highlight {
    border-color: #2C4751;
    background-color: #2C4751;
}

Также в эту же папку (web) скачиваем файлы с календарем (например отсюда js и css, конечные имена этих файлов в папке web, опять-таки, не принципиальны).
Ну и наконец, создаем форму использующую это представление для своего единственного объекта e:
CLASS Event;
date = DATA DATE (Event);
date(Event e) <- currentDate() WHEN SET(e IS Event);
title = DATA STRING (Event);
title(Event e) <- 'Event' + e WHEN SET(e IS Event);

FORM calendar
    OBJECTS e=Event CUSTOM 'calendar'
    PROPERTIES (e) date, title, NEWEDITDELETE 
;

NAVIGATOR {
    NEW calendar;
}
Получаем вот такой результат:



Темная тема и обновление дизайна


В четвертой версии lsFusion используется новый дизайн, созданный на основе дизайна Flatlaf, создатели, которого, в свою очередь, вдохновлялись дизайном продуктов Intellij. При этом если в десктоп-клиенте Flatlaf использован как библиотека, то в веб-клиент он портирован «вручную» насколько это возможно. У нового дизайна в lsFusion две темы:


  • Светлая (по умолчанию) — в серо-белых тонах
  • Темная — в черно-серых тонах

Самой сложной задачей в поддержке нескольких тем является «тематизация» программно задаваемых цветов и изображений. Естественно, эту задачу можно было переложить на разработчика, заставив его задавать цвета / изображения в разных темах явно (с разным постфиксом). Ну и само собой, такая возможность в lsFusion также имеется, но, скорее всего, мало кто из разработчиков согласился бы делать такую значительную дополнительную работу в бизнес-приложениях только ради дизайна (где дизайн в целом вторичен). Поэтому платформа умеет сама автоматически адаптировать все отображаемые цвета и изображения под выбранную тему при помощи специальных фильтров (собственно, большинство системных изображений тематизируются именно таким способом).


OAuth аутентификация и саморегистрация


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


  • Кто-то должен создавать пользователя и задавать его учетные данные (в частности логин и пароль). Особенно это может быть неудобно, если среди пользователей есть внешние пользователи не из числа сотрудников владельца информационной системы.
  • Пользователю надо помнить и вводить логин и пароль для еще одной системы.

Для решения этих проблем существует (и очень сильно распространена) так называемая OAuth аутентификация. Суть этой аутентификации в том, что пользователь может входить в некоторую систему (например, разработанную на lsFusion) при помощи учетных записей других сервисов / соцсетей. При этом если пользователь уже вошел в учетные записи этих сервисов / соцсетей, то вводить логин / пароль еще раз ему уже не требуется (единственное, что требуется — это первый раз дать разрешение на использование своих учетных данных). На текущий момент «из коробки» в lsFusion поддерживаются аутентификация при помощи следующих сервисов / соцсетей: Facebook, Google, Github, Яндекс. Кроме того администратор может добавить и другие сервисы задав соответствующие параметры (Authorization URI, Token URI и т.п.). Впрочем, добавлять можно только сервисы работающие по схеме аналогичной уже поддерживаемым (так как OAuth аутентификация это достаточно абстрактный стандарт, и, скажем, ВКонтакте работает по другой схеме).


Также в четвертой версии lsFusion реализованы привычные пользователям функции — «зарегистрироваться» и «забыли пароль». «Зарегистрироваться» позволяет пользователю самому зарегистрироваться в системе при помощи соответствующего веб-интерфейса. Функция «забыли пароль» высылает пользователю на электронную почту новый пароль (соответственно, для этого пользователя электронная почта должна быть задана).


Отметим, что при саморегистрации (как при помощи кнопки «зарегистрироваться», так и при помощи OAuth-аутентификации) созданному пользователю по умолчанию устанавливается роль «Самостоятельная регистрация». В свою очередь, эта роль по умолчанию имеет доступ только к элементу навигатора «Учетная запись». Соответственно, предполагается, что администратор должен либо расширить права этой роли, либо изменить роли созданного пользователя на те, которые должны у него быть.


Также в четвертой версии платформы добавлена поддержка аутентификации пользователя непосредственно в url при помощи параметров user и password (например http://myserver?user=X&password=Y). Естественно, такой способ аутентификации весьма небезопасен, поэтому его имеет смысл использовать только когда, например, все сетевое окружение защищено при помощи VPN.


Обратная интернационализация


В третьей версии lsFusion интернационализация работает следующим образом:


  • Разработчик в любом строковом литерале может использовать один или несколько идентификаторов (в фигурных скобках, например 'Some text {x.y}').
  • Когда этот литерал нужно передать на клиента или вообще куда-то вывести:
    • Платформа в зависимости от языка пользователя определяет файл со значениями идентификаторов на нужном языке (так называемый файл локализации),
    • Заменяет в этом литерале все идентификаторы на соответствующие значения.

Такой способ интернационализации достаточно прост, весьма распространен, однако при этом не очень удобен. Так:


  • Для всех строковых литералов необходимо сразу генерировать / придумывать идентификаторы.
  • Существенно снижается красота и читабельность кода (который начинает пестрить всеми этими спецсимволами и точками).

Для решения этих двух проблем в четвертой версии платформы появилась так называемая «обратная интернационализация». Ее смысл заключается в следующем:


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

Понятно, что при таком подходе существует проблема омонимов, но:


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

Также платформа позволяет при запуске сервера:


  • генерировать идентификаторы для всех строковых литералов, для которых соответствующих им идентификаторов не существует;
  • добавлять сгенерированные идентификаторы в конец файлов локализации.

Все это позволяет интернационализировать любое приложение практически в несколько действий:


  • Сгенерировать файл локализации по умолчанию.
  • Для каждого языка загрузить этот файл, скажем, в Google Translate. Так как переводятся не сложносочиненные предложения, такой перевод уже достаточно качественный. Но естественно для лучшего результата, переведенный файл лучше отдать на вычитку профессиональным переводчикам.
  • Положить файл локализации по умолчанию, а также переведенные файлы в папку ресурсов сервера приложений.
  • PROFIT

Результат такого процесса, можно посмотреть, например, на одной из демо-версий решений на lsFusion. Логин / пароль: guestuk / guestuk (для украинского языка), guestbe / guestbe (для белорусского языка).


Переход по ссылке


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


  • При зажатом CTRL (по аналогии с Excel). Те свойства, для которых операция перехода по ссылке поддерживается (существует обработка события EDIT_OBJECT) подсвечиваются как гиперссылки.
  • При включении режима «перехода по ссылке» в правом верхнем углу.
  • В контекстном меню (нажатии правой кнопки мыши) при выборе пункта «перейти по ссылке».
  • При двойном нажатии левой кнопки мыши (если для нее нет обработки события CHANGE, которое срабатывает при одиночном нажатии, а также обработки двойного нажатия для всей таблицы)

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


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


Групповое изменение «одним запросом»


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


  • Запрос значения у пользователя выполняется только для текущего объекта.
  • Результат этого запроса используется как для изменения текущего объекта, так и для изменения всех остальных объектов.

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


В четвертой версии операция группового изменения выполняется по-другому.
Теперь в качестве обработки события группового изменения (GROUP_CHANGE) генерируется следующее действие:
onChange(a);
PUSH REQUEST
    FOR [FILTER formY.a](ga) AND NOT a=ga DO
        onChange(ga);
где onChange — обработка события изменения (CHANGE), a — объект для которого выполняется групповая корректировка (тут для простоты будем рассматривать случай когда объект в группе объектов один, для нескольких объектов все работает аналогично).
Теперь представим, что в качестве обработки события изменения свойства f(a,b) задано следующее действие:
DIALOG formX OBJECTS x = f(a,b) CHANGE// вызвать форму formX, и изменить f(a,b) на значение объекта x
Что происходит дальше? А дальше начинается «магия». Верхняя обработка превращается в (все эти опции CHANGE на самом деле не более, чем синтаксический сахар):
REQUEST 
    DIALOG formX OBJECTS x=f(a,b) INPUT DO requestedX() <- x; 
DO 
    f(a,b) <- requestedX();

PUSH REQUEST 
    FOR [FILTER formY.a](ga) AND NOT a=ga DO
        REQUEST 
            DIALOG formX OBJECTS x=f(a,b) INPUT DO requestedX() <- x; 
        DO 
            f(a,b) <- requestedX();
Далее платформа при компиляции PUSH REQUEST убирает все внутренние REQUEST (оставляя только DO часть). Соответственно, вторая часть обработки будет выглядеть так:
PUSH REQUEST 
    FOR [FILTER formY.a](ga) AND NOT a=ga DO
        f(a,b) <- requestedX();
Ну и наконец, выполняется одна из главных оптимизаций lsFusion, превращение FOR в WHERE:
PUSH REQUEST 
        f(a,b) <- requestedX() WHERE [FILTER formY.a](ga) AND NOT a=ga;
Соответственно получается, инструкция выполняется «одним запросом».

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


Вычисляемые заголовки контейнеров и форм


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

DESIGN order {
    caption = 'Заказ №' + number(o);

    lines {
        caption  = 'Строки (' + (GROUP SUM 1 IF line(OrderDetail od) = o) + ')';
    }
}

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


Полноэкранный режим в веб


Классический вариант использования ИС пользователем выглядит следующим образом:


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

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


Ручное обновление представлений


Все пользовательские интерфейсы в lsFusion реактивны «из коробки» — любая форма автоматически инкрементально обновляется при изменении любых данных влияющих на ее представление (например, изменениях свойств, текущих объектов, вкладок и т.п.). Это, безусловно, удобно и красиво, но иногда, особенно с появлением группировочных представлений, чтобы не грузить напрасно сервера, возникает необходимость организовать процесс, при котором пользователь сначала задает те или иные настройки и только когда все настройки будут заданы, представление должно обновиться (тоже инкрементально). Для реализации такого процесса в четвертой версии появилась возможность включать / выключать автоматическое обновление представлений групп объектов (как пользователю, так и разработчику).


В этом случае если платформа видит, что изменились данные влияющие на представление группы объектов, она не обновляет его сразу, а делает его блеклым и показывает кнопку «Обновить». Соответственно представление обновляется только при нажатии на эту кнопку.


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


Выполнение http-запросов на клиенте


Небольшое, но важное изменение в контексте все большего фокуса на веб. Дело в том, что при использовании веба в бизнес-приложениях есть 2 концептуальных проблемы:


  • Низкая производительность. Некоторые особенности javascript, и, что более важно, перегруженность css и html (при одновременно относительно высокой декларативности) создают достаточно большой оверхед по сравнению с нативными клиентами.
  • Высокая ограниченность «песочницы» браузера. Эта проблема следует из высоких требований к безопасности, так как браузер используется пользователями при работе с самыми различными сайтами / сервисами, многие из которых могут быть небезопасными.

С первой проблемой браузеры активно борются (особенно стоило отметить Chrome с его V8 в этом плане), со второй проблемой все, конечно, сложнее. Доступ к портам, диску и другому оборудованию в браузерах закрыт, поэтому производители оборудования сейчас пошли другим путем — вместо драйверов они поставляют целые службы, общение с которыми осуществляется через http-интерфейс. А посылка запроса через http-интерфейс как раз то немногое, что клиентский код в браузере имеет право делать.


Теперь вернемся к lsFusion. Для работы с внешними системами, в том числе по http-протоколу, в платформе используется оператор EXTERNAL. Правда особенность этого оператора в том, что в третьей версии он мог выполняться только на сервере, а значит использовать его для той же работы с оборудованием на клиенте было невозможно. Соответственно в четвертой версии платформы для протокола HTTP появилась возможность выполнять оператор EXTERNAL на клиенте. Синтаксически это выглядит следующим образом:

reportX(Pos p) {
    generateUUID();
    EXPORT reportX JSON CHARSET 'UTF-8';
    TRY {
        EXTERNAL HTTP CLIENT 'http://' + hostname(p) + ':16732/requests' PARAMS exportFile();
    } CATCH {
        CASE
            WHEN statusHttp() > 299 THEN
                MESSAGE 'Ошибка - ' + STRING(statusHttp());
            WHEN NOT statusHttp() THEN
                MESSAGE 'Ошибка - нет связи с хостом АТОЛ v.10';
            ELSE
                MESSAGE 'Ошибка - потеряна связь с хостом АТОЛ v.10';
    }
}
Физически, как и при выполнении других клиентских действий (например, показе форм / сообщений), платформа сама останавливает выполнение действия на сервере, передает управление на клиента, там выполняет http запрос, после чего возвращает управление на сервер и продолжает выполнение остановленного действия на сервере.

Расширение формы в контексте вызова


Очень часто при открытии формы возникает необходимость показывать не все данные, а только данные удовлетворяющие некоторому условию. Причем, что важно, во многих случаях это условие может зависеть от контекста, в котором открывается форма. Так, например, может быть необходимо использовать в условии локальные свойства заполняемые в контексте вызова или параметры действия, из которого открывается форма. В предыдущих версиях платформы для реализации таких сценариев, как правило создавались виртуальные объекты — «параметры» (начальные значения которых передавались в блоке OBJECTS) и / или вместо локальных использовались глобальные свойства. Безусловно такой способ можно использовать и в четвертой версии, но во многих случаях это все же требует слишком много лишних действий со стороны разработчика, поэтому в четвертой версии появился гораздо более удобный и простой способ изменять фильтры формы — блок FILTERS в операторах открытия формы:

exportAndShowIncomes (Supplier s) {
    // i - объект прихода на форме приходов (incomes)
    EXPORT incomes FILTERS supplier(i) = s;
    open(exportFile());
    DIALOG incomes FILTERS supplier(i) = s;
}
Также как и в случае с вычисляемыми заголовками важным нововведением является скорее не сам функционал добавления дополнительных фильтров на форму, а вообще инфраструктура использования контекста открытия формы внутри самой формы. Так в следующих версиях можно будет легко реализовать поддержку добавления например дополнительных свойств или порядков при открытии формы, при этом в процессе добавления этих элементов можно также будет использовать параметры из контекста открытия формы. Впрочем все эти дополнительные элементы (за исключением фильтров) зависят от контекста гораздо реже, поэтому для их добавления, все же логичнее будет использовать механизм наследования (агрегации) форм, который также появится в одной из следующих версий (об этом механизме подробно будет рассказано в следующих статьях).

Оптимизация работы с DOM


Так получилось, что в самых первых версиях lsFusion поддерживался только десктоп-клиент, и соответственно при разработке веб-клиента часть архитектурных решений было взято оттуда. Впоследствии, при реализации все большей асинхронности ситуация изменилась на противоположную (десктоп-клиент начал повторять архитектуру браузера), но некоторые особенности Swing (в частности то, что панельное свойство считалось таблицей с одним рядом / колонкой), а также поддержка старых версий браузеров / стандартов HTML достаточно серьезно перегружали DOM. Все это существенно увеличивало время отрисовки страниц, а также их инкрементального обновления. И если для настольных компьютеров это не сильно важно, то для мобильных устройств такое падение производительности может быть критично. Дело в том, что в вопросе реализации клиента для мобильных устройств существует два подхода:


  1. Создание нативного мобильного клиента “браузера” по аналогии с десктоп-клиентом.
  2. Использование веб-клиента в мобильном браузере.

Недостаток первого подхода — необходимость установки ещё одного приложения на мобильное устройство пользователя. Впрочем, такое приложение может быть одно для всех lsFusion-приложений (по аналогии с веб-браузерами), и в будущих версиях такой мобильный клиент безусловно появится. Пока же на мобильных устройствах можно использовать веб-клиент (что успешно и делается при работе с lsFusion), который с учетом оптимизации работы с DOM позволяет обеспечить вполне достаточную для комфортной работы производительность. Например, так выглядит мобильный веб-клиент для MyCompany (необходимо запускать именно из мобильного браузера). Естественно, предполагается, что содержание и дизайн форм должны быть адаптированы под мобильные устройства (то есть, содержать минимум информации и больше полагаться на действия / кнопки, чем на текстовый ввод).


Заключение


Полный список новых возможностей четвертой версии можно найти по ссылке (сгенерирован на основе задач на github). Кроме того в четвертой версии есть много мелких изменений, связанных с пользовательской эргономикой (UX) и developer experience (DX), которые по тем или иным причинам не были оформлены как задачи. Ну и также был проведен большой объем рефакторинга кода (прежде всего в веб-клиенте) для подготовки к пятой и шестой версиям, о которых будет рассказано в следующей статье.


Ну и напоследок хотелось еще раз упомянуть MyCompany. Платформа это конечно хорошо (например, для custom-made решений), но все же в абсолютном большинстве случаев создавать решение с нуля может быть экономически неэффективно, даже если платформа ускоряет процесс разработки на порядок. Поэтому на рынке бизнес-приложений, а точнее ERP-фреймворков, платформу принято рассматривать/ поставлять вместе с некоторым набором «базовых» решений на ней. Соответственно в качество одного из таких решений и было создано решение MyCompany. С одной стороны это готовое решение для SME (тот же ERP например это все же больше решение для крупной розницы), с другой стороны это своего рода полуфабрикат для построения сторонних сложных специализированных решений (как для внутреннего использования, так и для коммерческих поставок, благо лицензия Apache 2.0 это позволяет). Также параллельно ведется разработка решения для WMS, так как, как показывает практика, задачи WMS куда проще и удобней реализовывать с использованием свойство-ориентированной парадигмы (которая использует lsFusion), нежели классической объектно-ориентированной (и в частности документо-ориентированной).


Источник: https://habr.com/ru/company/lsfusion/blog/534756/


Интересные статьи

Интересные статьи

Математический аппарат нередко формировался в процессе решения практических задач: зачатки векторной алгебры возникли при попытках «сложить» скорости и силы, понятие скорости привело ...
Системы контроля версий уже давно стали повседневным инструментом разработчика. В больших монорепозиториях требования к ним оказываются весьма специфическими. Из-за этого компании либо адаптируют...
Тяжело искать ответы в бесконечном пространстве. Математика уровня старших классов может помочь вам сузить область поисков. Учитывая, что люди изучают свойства чисел тысячи лет, можно было б...
Недавно были анонсированы сразу два мероприятия о разработке многопоточных и распределённых систем: конференция Hydra (11-12 июля) и школа SPTDC (8-12 июля). Люди, которым близка эта тема, по...
5-6 апреля, то есть уже на этих выходных, пройдёт JPoint 2019 — международная Java-конференция для опытных Java-разработчиков. Если вы не смогли поучаствовать, вживую или онлайн, то у нас есть ...