ExtJs

Материал из Eludia
Перейти к: навигация, поиск
Pm.gif Здесь описана идея, которая в настоящий момент не реализована в Eludia.pm. Однако, может быть, однажды... Такие случаи уже бывали.

Содержание

На этой странице собраны сведения об интеграции Eludia.pm с Ext JS.

На момент последней актуализации данного текста поддержка Ext JS находится в весьма ранней pre-alpha-версии и ни в коем случае не рекомендуется к промышленному использованию. Кроме того, данная функциональность присутствует не в основной (master) ветви, а в отдельной (extjs), где заметно переработан общий презентационный слой Eludia.pm.

Настоящий обзор адресован в основном опытным Eludia-разработчикам, испытывающим острую потребность в интеграции с Ext JS и готовым подключиться к данной работе.

Установка

Помимо обычной установки ядра ветви extjs и выбора $_SKIN'а ExtJs, для каждого экземпляра приложения требуется распаковать пакет Ext JS версии не ниже 3.1.1 в директорию docroot/i/ext. Проверка: по адресу /i/ext/ext-all.js должен выдаваться файл объёма более 600 Кб, а не 404 ошибка. Естественно, /i/ext тут же потребуется вынести из-под версионного контроля (для git — вписать в файл .gitignore).

Кроме того, необходимо убедиться, что для директории i/ext/src/locale/ не проставляется charset, отличный от utf-8. Пустой charset подойдёт (поскольку utf-8 подразумевается по умолчанию), но у вас в охватывающей конфигурации может стоять AddDefaultCharset для традиционного windows-1251 — тогда календарик будет показываться с искажениями.

Использование

В идеале должно быть так: обновляете ядро, переключаете $_SKIN в ранее разработанном Eludia-приложении — и оно корректно работает с новым интерфейсом. И дописывать его можно при помощи того же самого, давно знакомого API. Но, разумеется, многие реальные системы используют расширения стандартных $_SKIN'ов и "запилы" по месту — разумеется, эти фрагменты придётся переписывать.

Основное отличие от работы с привычными $_SKIN'ами заключается в том, что на выходе Presentation-процедуры ожидается не HTML, а чистый js, предназначенный для подстановки в eval в контексте страницы с загруженным Ext и готовыми панелями (об этом ниже). Попросту говоря, теперь $_REQUEST {__on_load} является единственным возвращаемым результатом draw_... и draw_item_of_... Его можно генерировать процедурами типа draw_table и draw_form, а можно и вручную — тогда весь Ext JS к вашим услугам.

А при использовании нашего API, конечно, велик риск того, что некоторые имеющиеся в ядре widget'ы на Ext JS будут портированы, мягко говоря, не скоро. Писались они в разное время разными людьми, некоторые из них автор этих строк ни разу не видел в деле — так что, увы, даже никаких оценок.

Существует и ещё одна проблема: менее острая, но в пределе чреватая даже большей трудоёмкостью. Хороший интерфейс Ext JS трудно сделать на одних умолчаниях, а потому может потребоваться практически повсеместно:

  • прописывать ширины столбцов таблиц;
  • указывать параметры dialog'ов при ссылках на формы;
  • задавать клиентскую валидацию для полей;

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

Детали реализации

Важнейшей (и весьма неприятной) особенностью тяжёлых AJAX-библиотек являются накладные расходы на загрузку (точнее, разбор) страницы. Минифицированный до предела ext-all.js занимает более полумегабайта, и его компиляция браузером (даже при извлечении из кэша) занимает секунды. Таким образом, ни о перезагрузке страницы (как в Classic), ни о подкачке самостоятельных фрагментов в IFRAME (как в TurboMilk) речи быть не может. (Строго говоря, может, но не зря же ManagedIFrame уже третий год как не войдёт в ядро Ext Js, хотя его пишет член Development Team). Так что базовый HTML (практически полностью сводящийся к ссылкам на js) загружается единственный раз, и... Да, не забыть переопределить F5 и Ctrl-R. Этого пока не сделано.

"Верхняя обвязка"

Как всегда в Eludia.pm, первое, что видит пользователь — экран '/?type=logon'. Традиционно в соответствующем HTML показывалась только форма авторизации. В случае с Ext JS logon-экран существенно расширен. Он содержит:

  • теги SCRIPT и STYLE для загрузки Ext JS;
  • js-код для создания основных панелей;
  • собственно форму авторизации.

Процедура draw_logon_form в ExtJs.pm содержит необходимый код, который строит интерфейс, воспроизводящий TurboMilk. Его можно воспринимать не как часть $_SKIN'а, а как образец для копирования. То есть процедура draw_logon Вашего приложения может не вызывать стандартную draw_logon_form, а выдавать аналогичный HTML, где, скажем, меню разделов (subsets) будет выглядеть не как ComboBox на верхней плашке, а в виде accordion слева (a la Outlook) или как-либо ещё.

Таким образом, приложение может иметь произвольный layout и весь его программный код должен описываться (точнее, загружаться) в рамках экрана типа logon. Для взаимодействия с Eludia.pm необходимо соблюдать только следующую дисциплину:

  • вся работа с Ext.Viewport'ом, его панелями и прочим подобным должна вестись через глобальный объект ui;
  • ui.sid должен хранить текущий номер сессии;
  • ui.target должен указывать на панель, в которую требуется загрузить следующую станицу;
  • ui.panel.center должен указывать на гавную панель (center-панель viewport'а, значение по умолчанию для ui.target);
  • ui.init () должна строить базовый интерфейс (создавать Ext.Viewport);
  • ui.menu_md5 содержит последнее закэшированное значение MD5-дайджеста от значения get_user_subset_menu (новая функция, может быть переопределена в приложении);
  • ui.checkMenu (md5) должна слать запрос на обновление меню, если значение аргумента отлично от ui.menu_md5.

Кроме того, HTML страницы должен содержать пустой невидимый IFRAME с именем 'invisible'.

Вход в систему

Стандартная draw_logon_form предполагает возможность как ручного ввода логина/пароля, так и автоматического входа в систему с использованием какой-либо реализации SSO. В последнем случае draw_logon_form вызывается при непустом $_REQUEST {sid} и, соответственно, ui.sid также оказывается непустым сразу на старте. В этой ситуации вызывается ui.init (), и Ext.onReady заканчивается.

В противном случае пользователю показывается Ext.Window с формой, target которой установлен в 'invisible'. Если в ответ приходит не ошибка, а redirect на страницу c непустым sid, то такая ситуация отлавливается в местной реализации функции nope: если ui.sid пуст, а в url он задан, то вместо перенаправления вызывается ui.init ().

Снова отметим, что здесь прокомментирована только стандартная draw_logon_form: в каждом приложении можно поступать по-своему.

Главное меню

Базовая навигация выглядит примерно так же, как в TurboMilk, но работает несколько иначе. Если там по ссылкам с пунктов меню получается HTML, напрямую отображаемый в __body_iframe или другом фрейме, то в данном случае с сервера возвращается чистый JavaScript, который исполняется в контексте страницы (куда уже загружен Ext). При этом js-код предполагает наличие объекта ui и, как правило, добавляет некоторые визуальные компоненты к панели ui.target. За предварительную зачистку ui.target отвечает функция nope.

Реестры (draw_table)

На нынешний момент реализован простейший вариант чисто текстовых таблиц с обязательными заголовками, 1 сторокой таблицы на 1 строку выборки и без colspan/rowspan. Элементы верхних панелей отрисовываются (pager — всегда снизу), нижних — нет. Увы.

По клику из главного меню выдаётся js-код, который порождает Ext.GridPanel и добавляет его к ui.target. Как для самой таблицы, так и для input_select'ов на toolbar'е создаются и тут же заполняются (без подзапросов) JSONStore'ы.

Впоследствии при использовании фильтров на toolbar'е и листании на сервер идут запросы с параметром $_REQUEST {__only_table} = имя_таблицы. В ответ на такие запросы возвращается чистый JSON, который обновляет содержимое JSONStore, и таблица переписовывается (а панели остаются на месте).

Гиперссылки с ячеек идут отдельной веткой в JSON-источнике для таблицы. Они упакованы таким образом, чтобы максимально сократить дублирование. При загрузке JSONStore эти структуры разворачиваются в двумерный массив ссылок, который используется при обработке события onCellClick.

Карточки (draw_form)

Насколько можно судить, в Ext JS (чтобы не сказать вообще в RIA) предполагается, что экранные формы (во всяком случае, то, что верстается с Ext.layout.FormLayout) отрываются в небольших всплывающих окнах. То есть, с точки зрения Eludia.pm, это не вообще формы, диалоги. Работа над pop-up диалогами ведётся, но пока их нет даже в alpha-версии. А пока формы рисуются по аналогии с Classic и TurboMilk, с учётом местной специфики: в рамках страницы отрабатывается js-код, который создаёт экземпляр панели с Ext.layout.FormLayout и заявленными полями — и добавляет его к ui.target.

Отметим, что Ext.form.FormPanel не используется по довольно забавной причине: пустая Ext.Panel (центральная панель viewport'а, например) содержит свой элемент FORM, и вставить в него вложенный FORM путём манипуляций с DOM невозможно (под IE, во всяком случае).

Вообще работа с формами в нынешней версии совершенно не AJAX'овская: используется старый добрый невидимый IFRAME в качестве target, и туда, как всегда, грузится js, который либо показывает Ext.MessageBox.alert с ошибкой, либо производит redirect (указанный url грузится обновлённой функцией nope в панель ui.target). Отказ от AJAX тут не только для совместимости с нашей заповедной стариной: file upload в любом случае иначе работать не может.

Не поддерживается пока и валидация на клиенте. Просто не дошли руки. Хотя, возможно, это стоит сделать: красиво, да и запросов на сервер меньше. Аналогично надо бы доработать прицельный вывод сообщений об ошибках валидации в конкретные поля. Однако, скорее всего, ответы об обработке действий в перспективе останутся js-кодом, загружаемым в IFRAME, а не JSON-пакетами с полем success, приходящими в XMLHttpRequest. Помимо упомянутой независимости от наличия файловых полей, это даёт возможность по-прежнему получать в тот же IFRAME наряду с HTML/js ещё и бинарные ответы, к которыми в случае AJAX непонятно что делать.

Вообще Ext.layout.FormLayout имеет множество неприятных ограничений (форсированно одинаковая ширина всех полей, всё в один столбик и т. д.). И в нашем классическом варианте "форма на всю страницу" для простых словарей метка с полем робко жмутся в левый верхний уголок, а панель с кнопками падает на самое дно. Возможно, стоит перейти на Ext.layout.TableLayout.

Ну а то, что из типов полей поддерживаются только static, string, date, select, (неиерархические) checkboxes и (номинально) hgroup — тут, конечно, никто не виноват, кроме лени. Есть простор для совершенствования.

Todo

Вот какие (не)доработки бросаются в глаза и просятся в первую очередь:

  • isDirty и блокировка меню в режиме редактирования;
  • иконки для кнопок;
  • диалоги;
  • отладка совместного использования draw_form и draw_table на одном экране;
  • закладки на формах (опция menu в draw_form);
  • недостающие типы полей форм (тут хитро: tree может оказаться намного проще, чем radio);
  • валидация на клиенте и доработка показа серверных ошибок;
  • draw_checkbox_cell, draw_radio_cell и прочие нетекстовые ячейки таблиц;
  • прогресс-индикация.

Осилив это всё, можно было бы гордо назвать полученную версию "бетой".

Переработка Presentation

Как уже отмечалось, по ходу работы над ExtJs.pm глобальный Presentation.pm был переделан весьма капитально. Большую часть этих изменений стоило внести давным-давно, но руки дошли только сейчас.

Главное: из Presentation.pm вынесена практически вся непосредственная генерация HTML. Как собственно склейка тегов, так и установка хэшей attributes для последующего dump_tag. Понятное дело: даже если во всех наших $_SKIN'ах, скажем, тег SELECT имел один и тот же вид, в Ext JS такая сборка HTML теряет смысл и только расходует время впустую. Поэтому все фрагменты с генерацией HTML вынесены из Presentation.pm в Generic.pm. Теперь он играет роль универсального предка унаследованных HTML $_SKIN'ов. И ExtJs.pm НЕ импорирует оттуда ничего. В ExtJs.pm на клиент по большей части передаётся просто JSON-дамп аргуметов, обрамлённый в кое-какие вызовы js-функций, определённых в ExtJs/navigation.js.

Кстати об унаследованности: $_SKIN'ы

  • GazOil
  • IsUp
  • TurbomilkGecko
  • WinCe
  • XmlDumper
  • XmlProto

выкинуты из ядра в самостоятельные git-репозитории.

Помимо переноса в Generic, множество функций были вынесены из Presentation в отдельные файлы, располагающиеся в поддиректориях Eludia/Presentation с, надеюсь, вполне прозрачными наименованиями:

  • FormFields
  • TableCells
  • ToolbarElements

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

Однако не исключено, что этот ход может нехорошо отразиться на производительности (особенно в унаследованных $_SKIN'ах), поскольку увеличилась глубина вызовов, в том числе в узких местах (типа draw_text_cell). Надо тестировать.

Общие соображения

Что в Ext JS хорошо

  • внешний вид;
  • кроссбраузерность;
  • простота и гибкость использования, особенно в части таблиц (управление столбцами);
  • реальная разгрузка сервера и сети за счёт проброса лёгкого JSON вместо тяжёлого HTML.

Что в Ext JS плохо

  • скудный (да-да!) набор полей ввода;
  • приоритет WebKit / Gecko, а не Trident (глюки в IE);
  • сложность и негибкость прграммирования, особенно в части таблиц (нет и не будет colspan/rowspan и т. д.);
  • ощутимая перегрузка клиента, торможение, постоянная угроза утечки памяти.

Любопытные факты

В версии Ext JS 3.1.0 использование формы с полем ввода 'id' приводило к неизлечимой ошибке MSIE, происходившей onUnload.

В Ext.form.CheckboxGroup надписи имеют фиксированную ширину 100 пикселей. Она устанавливается не на уровне CSS, а прямой операцией на DOM'е. Поэтому проще оказалось сделать наши checkboxes не из Ext.form.CheckboxGroup, а из Ext.tree.TreePanel.

В Ext.tree.TreePanel очень просто заявить checkbox'ы для любого множества узлов, однако ни name, ни даже id этих полей управлять невозможно. Соответственно, для использования в submit'е пришлось дублировать их hidden'ами и вешать onClick, угадывающий нужный name по id охватывающего элемента.

При отрисовке таблицы показ тела искусственно откладывается на 100 мс относительно показа панелей: "для скорости". При отключении этой ценной функции съезжают ширины столбцов относительно заголовков. Выделение заданной строки (наш $_REQUEST{__last_scrollable_table_row}) оказывается не столь тривиальной задачей, как могло бы показаться.

Ext.form.TriggerField (в частности, Ext.form.ComboBox) рисуется MSIE с небольшими дефектами (разные сдвиги поля и картинки на 1-2 пикселя вверх). Похоже, это лечится только js-операцией на DOM'е во время afterRender. Примечательный факт: ни на одной демке самого Ext JS нет TriggerField на панели поика при таблице.

Персональные инструменты
Пространства имён

Варианты
Действия
Навигация
Разработчику
Администратору
Инструменты