uniGUI – это библиотека, позволяющая создавать веб-приложения в классической Delphi-манере, посредством визуальных компонентов, причём, что является немаловажным, в большинстве случаев она полностью скрывает от разработчика всю клиентскую (браузерную) «кухню»: не требуется знать ни HTML, ни CSS, ни JavaScript, а вся разработка ведётся лишь на одном языке – Delphi.
Если смотреть на картину издалека, то вырисовываются 3 сценария использования библиотеки:
- Наиболее благоприятный, для которого она в целом и создавалась, когда описанная сильная, радужная сторона uniGUI проявляет себя в полной мере – перенос (полный или частичный – неважно) настольных приложений в веб, что возможно проделать быстро, привычным способом и с высоким качеством только в случаях, когда поставляемые с библиотекой компоненты сразу обеспечивают требуемый функционал – к примеру, фильтрацию и сортировку по столбцам таблицы.
- Очень похож на первый и в какой-то мере вытекает из него, с тем лишь отличием, что штатный набор компонентов не покрывает все необходимые по ТЗ возможности; если снова взять для примера таблицу, то таким требованием может стать многоуровневая группировка строк по произвольным столбцам. Если удастся найти сторонние решения с нужными возможностями, то сценарий сводится к первому, а если нет, то потребуется самостоятельная доработка, что уже весьма нетривиально, т. к. здесь не обойтись не просто без знаний JavaScript, а ещё и глубокого владения фреймворком Ext JS, на основе которого работает браузерная сторона uniGUI.
- Последний сценарий может показаться странным и даже чужеродным для тех, кто применял на практике в основном первый, – речь об отказе от всей богатой палитры визуальных компонентов и разработке клиентской части веб-приложения исключительно с использованием родной для неё триады – HTML, CSS и JavaScript. Данная статья покажет, в каких случаях подобное является не просто возможным, но и оптимальным, а также продемонстрирует способ реализовать задуманное.
Постановка проблемы
Рассмотрим ситуацию, когда перед Delphi-разработчиком, знакомым с uniGUI, ставят следующую задачу: создать современно выглядящий динамический (с AJAX) сайт по предоставленному макету, следование которому должно быть очень точным – все цвета, шрифты, отступы, возможные тени и прочие дизайнерские решения железобетонны, отход от них недопустим. Вероятность того, что макету будет соответствовать одна из поставляемых с библиотекой тем оформления, почти нулевая; шанс того, что с помощью штатных настроек (через Инспектор объектов) и некоторых приёмов (через CSS) удастся подкорректировать внешний вид компонентов до требуемого, уже выше, но тоже мал. Если же оказывается, что дизайн, ко всему прочему, содержит различные анимации, то пиши пропало – uniGUI данную тему обходит стороной и помочь ничем не сможет.
Здесь наилучшим выходом (имеется в виду для конечного пользователя) видится ручная вёрстка по макету, плюс которой также в максимальной производительности, ибо часто даже несколько десятков графических объектов uniGUI не только создаются с ощутимой задержкой, но и отрисовываются тоже не мгновенно, а если счёт доходит до сотни и выше, то почти гарантировано неприемлемое замедление. Само-собой, когда Delphi-программист владеет вёрсткой, то и эту часть работы может выполнить он, а если нет, то её вполне реально передать другому человеку, потому что, как будет видно ниже, эти структурные блоки сайта – клиентская триада и серверный код – весьма независимы и могут разрабатываться по отдельности.
Статика
Итак, имея на руках свёрстанный макет, необходимо определиться со способом его отображения, что не имеет такого уж очевидного решения, т. к. работа ведётся на базе конкретной выбранной библиотеки с присущими ей ограничениями и правилами, поэтому первая задача ясна – каким-то образом встроить произвольный HTML в некий контейнер из состава uniGUI.
Поиск контейнера
Попробуем начать с малого и исследовать минимальный, пустой проект, воспользовавшись для его создания мастером:
В результате его работы появится проект с тремя файлами:
- ServerModule.pas – содержит модуль данных
UniServerModule
, существующий всегда в одном экземпляре и создаваемый при старте веб-сервера. - MainModule.pas – в рамках нашего небольшого эксперимента не интересен и в принципе может быть удалён из проекта, делая его действительно минимально возможным.
- Main.pas – содержит главную форму
MainForm
– единственный пока доступный визуальный элемент, поэтому дальнейшему препарированию подвергнется именно он.
Не меняя ничего, запустим проект (а фактически веб-сервер) и посмотрим на результат в браузере:
Текущий вид, конечно же, далеко не назвать типичным сайтом (всё выглядит как имитация обычного приложения), но пока это не имеет значения – мы лишь изучаем структуру, для чего воспользуемся Инспектором и исследуем показанную форму:
Как видно на снимке, она предоставляет пустой блок div
, который вполне подходит для наших задач; есть несколько способов размещения в нём произвольного HTML, здесь же предпочтителен вариант, позволяющий брать код из любого источника во время выполнения приложения, для чего следует воспользоваться событием OnCreate
формы (с целью упрощения примера, загрузка не идёт из файла, БД или иного внешнего ресурса, но ничего не мешает сделать иначе):
procedure TMainForm.UniFormCreate(Sender: TObject);
const
HTML =
'<p>Произвольный текст</p>' +
'<table>' +
'<tr> <td>1</td><td>2</td> </tr>' +
'<tr> <td>3</td><td>4</td> </tr>' +
'</table>';
begin
WebForm.JSForm.Update(HTML);
end;
Полученный таким образом результат вполне оправдывает ожидания:
Настройка формы
После успешных изысканий с поиском подходящего вместилища для HTML, необходимо избавиться от оформления формы в виде окна, что делается очень просто – достаточно воспользоваться свойством UniServerModule
.MainFormDisplayMode
, установив его значение в mfPage
:
Остаётся последний, не вытекающий прямо из изображения выше, штрих: чтобы контейнер отслеживал изменение размеров окна браузера, нужно настроить пару свойств формы (приведена выдержка из dfm-файла):
object MainForm: TMainForm
...
AlignmentControl = uniAlignmentClient
Layout = 'fit'
end
Эта манипуляция, что можно видеть по снимку, также уменьшает вложенность блоков, составляющих форму.
Таким образом, имеется способ отображения статического содержимого произвольного объёма и сложности – обычно это те части сайта, что не меняются в течение всего сеанса работы с ним – заголовок, меню, подвал, блок со второстепенной информацией и т. п. Осталось дело за динамикой – реакцией на действия пользователя, требующей обращения к серверу.
Динамика
Данный раздел станет иллюстрироваться на примере действующего сайта, созданного по описываемому в статье методу. Кратко о нём, чтобы понимать приводящиеся ниже изображения, – это агрегатор цен на издания определённого музыкального направления, где пользователь по названию исполнителя (группы) или альбома может увидеть все предложения ряда магазинов, например:
Цифрами показаны элементы, нажатие на которые приводит к отправке AJAX-запроса на сервер:
- По введённому в текстовое поле названию выполняется поиск групп и альбомов, результат которого представлен на снимке. Дальнейшие примеры продолжат использовать именно этот вариант.
- Происходит переход ко всем имеющимся в продаже альбомам конкретного исполнителя.
Реализация
Начнём с клиентской части. Изображение в виде стрелки , реагирующее на нажатие, представлено следующим HTML-кодом:
<img src="files/Кнопка поиска.svg" onclick="Search( document.getElementById('header-search-input') )">
Интерес здесь представляет лишь событие onclick
, вызывающее функцию Search
с довольно примитивной реализацией, ибо она лишь должна передать поисковую фразу на сервер:
function Search(Input)
{
if (Input.value.trim() != '')
ajaxRequest( Ext.getCmp("MainForm"), "Search", ["Phrase=" + Input.value] );
}
Следует остановиться на параметрах применённой ajaxRequest
:
Ext.getCmp("MainForm")
– служит для указания на серверный компонент, который должен обработать AJAX-запрос, однако мы, как говорилось в разделе про статику, используем лишь форму (пустую), поэтому только ей и можем адресовать запрос. Если читатель имеет опыт с этой библиотекой, то наверняка знает, что обычно такой параметр выглядит иначе – если, например, запрос должна получить кнопка, то он записывается какMainForm.Button1
, и логично было бы в данном случае написать простоMainForm
, но это не сработает, поэтому и применяется чуть более сложная конструкция, которая, для своей работоспособности, потребует несложных действий и в серверной части."Search"
– название запроса, чтобы его можно было отличить от других, если таковые будут.["Phrase=" + Input.value]
– параметры в виде строки.
Далее, дабы не прерывать логическую цепочку с потоком данных, перейдём к серверной стороне, а если точнее, то к событию OnAjaxEvent
, ответственному за обработку поступающих запросов:
procedure TMainForm.UniFormAjaxEvent(Sender: TComponent; EventName: string; Params: TUniStrings);
begin
if EventName = 'Search' then
Search( Params.Values['Phrase'] );
...
end;
Код события тривиален и в пояснениях не нуждается, поэтому рассмотрим метод Search
, формирующий ответ и выполняющий его отправку браузеру:
procedure TMainForm.Search(const Phrase: string);
function StringToJSString(const UnsafeString: string; const QuoteChar: Char = ''''): string;
begin
Result := QuoteChar + UnsafeString.Replace('', '\').Replace(QuoteChar, '' + QuoteChar) + QuoteChar;
end;
var
BandsHTML, AlbumsHTML: string;
begin
// Формирование HTML-кода по данным из БД или любого другого источника.
BandsHTML := ...;
AlbumsHTML := ...;
UniSession.AddJS( Format('ShowSearchResults(%s, %s);', [StringToJSString(BandsHTML), StringToJSString(AlbumsHTML)]) );
end;
В последней строке этого кода фигурирует функция ShowSearchResults
, вызов которой последует на клиентской стороне после получения браузером ответа на AJAX-запрос. Её реализация тоже не представляет какой-либо сложности:
function ShowSearchResults(BandsHTML, AlbumsHTML)
{
document.getElementById("found-bands").innerHTML = BandsHTML;
document.getElementById("found-albums").innerHTML = AlbumsHTML;
}
Для наглядности, на снимке ниже показан один из узлов DOM, содержимое которого динамически меняется в приведённой функции:
Чтобы закончить раздел, необходимо вернуться чуть назад, где говорилось про необходимость настройки формы так, дабы появилась возможность идентифицировать её при отправке AJAX-запроса, – речь шла о клиентском событии, располагающемся в свойстве ClientEvents.UniEvents
:
В нём всего лишь требуется назначить форме текстовый идентификатор MainForm
(причём обратите внимание на выбранное значение выделенного красным списка – по умолчанию оно другое).
Сторонние файлы
Развёртывание веб-приложения подробно описано в документации uniGUI, но в нашем случае имеются собственные файлы, отвечающие за отображение ручной вёрстки, – это CSS и скрипты; пути к ним должны быть явно прописаны в серверном модуле данных UniServerModule
в свойстве CustomFiles
, например так:
Автор: Пьянков Сергей Михайлович