Модуль для маркетплейс — от идеи до старта продаж. Часть 8 — форма настроек и снова инсталлятор

Пора бы уже начать решать поставленную задачу, а то уже на протяжении 7ми последних частей — один трёп 🙂 Но перед этим снова придется немного повозиться с инсталлятором, т.к. появились дополнительные потребности, а также нужно исправить недочеты, сделанные ранее. Чувствую, что с инсталлятором придется возиться до момента самого запуска …

Благодаря информации из обсуждения в предыдущей статье стало ясно, что оказывается в битриксе предусмотрено два способа инсталляции модуля — автоматический и интерактивный. Автоматический способ используется при инсталляции системы и не подразумевает взаимодействия с пользователем в момент установки, тогда как интерактивный способ вызывается пользователем вручную в момент установки модуля из админки. В связи с этим есть две точки входа для установки модуля — комплекс методов InstallDB, InstallFiles и InstallEvents (для автоматической установки), а также метод DoInstall (для интерактивной).

В момент автоматической установки битрикс вызывает методы в следующем порядке: InstallDB > InstallEvents > InstallFiles. Значит мы должны со своей стороны обеспечить корректную работу инсталлятора при вызове этих методов последовательно именно в этом порядке. В связи с этими умозаключениями пришлось немного поменять порядок вызова методов автоматической установки внутри метода интерактивной установки. В моем случае это не сыграло ровно никакой роли, т.к. метод установки почтовых событый у меня пустой. Ну да ладно ..

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

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

Меню

Ну и последнее нововведение, которое появилось в инсталляторе — это установка обработчиков событий. Я уже упоминал, что нужно будет добавлять собственные пункты меню, и что делать я это буду с помощью события OnBuildGlobalMenu. Еще я упоминал, что регистрировать события я буду в runtime, т.к. это несколько удобнее. Но чтобы регистрировать события в runtime, например в файле %module_root%/include.php , нужно чтобы кто-то подключил наш модуль извне. Это неудобно, т.к. работа модуля начинает зависеть от внешнего кода, а нам это не надо. Для решения этой проблемы в битриксе есть функционал, который позволяет добавить обработчики событий для модулей в БД, и эти события будут регистрироваться автоматически, если модуль установлен. Для добавления таких обработчиков событий существует функция RegisterModuleDependencies, или её новый аналог из d7 — \Bitrix\Main\EventManager::registerEventHandlerCompatible. Данный метод добавляет событие в табличку b_module_to_module, если его там нет, и прописывает там все необходимые параметры для регистрации события. Ну а при возникновении первого события в жизненном цикле страницы (OnPageStart, вероятно), все эти события запрашиваются из БД и регистрируются в рантайме.

На текущий момент у меня есть лишь одно событие. Что если мне понадобится в будущем завести несколько событий? Если использовать функцию \RegisterModuleDependencies, то мне придется делать регистрацию всех событий частью инсталлятора, а это менее удобно в долгосрочной поддержке. Поэтому я решил прибегнуть к следующему, комбинированному способу:

  • в момент инсталляции модуля я зарегистрирую обработчик только для одного события — OnPageStart
  • в обработчике этого события я буду регистрировать всё то, что мне необходимо для работы, включая нужные мне обработчики событий

Для регистрации обработчиков мне потребуется какой-то класс, в котором будет храниться вся логика, связанная с регистрацией обработчиков событий. Пусть это будет \Maximaster\Coupanda\EventHandlersRegistry. В нем будет всего один метод — register, задачей которого будет регистрация всех обработчиков событий, которые мне необходимы. Ну и поскольку обработчик мне нужен всего один, то и регистрироваться будет только он один. Код такой:

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

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

Вот он, родимый.

Вызывающий скрипт

Наконец-то! Добрались до визуала, интерактива и логики, теперь работать должно стать поинтереснее 🙂

Итак, опираясь на функциональные требования, где мы составили набросок элементов интерфейса, можно теперь набросать форму настроек процесса генерации. Но для того, чтобы создавать какой-то интерфейс, нужно сначала добавить URL, по которому этот интерфейс будет отображаться. Ранее я уже решил, что вся логика, связанная с отображением формы настроек, а также с рендерингом процесса генерации у меня будет располагаться в моем собственном компоненте. Компонент этот будет подключаться в скрипте %module_root%/admin/register.php. Создаем этот скрипт:

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

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

Форма настроек

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

Исходя из документации на структуру модулей битрикс, все компоненты должны располагаться в директории %module_root%/install/components, а уже оттуда должны копироваться в /bitrix/components. Так и сделаем — добавляем директорию %module_root%/install/components/maximaster/coupanda.coupon.generator, переинсталлируем и видим, что создалась директория /bitrix/components/maximaster/coupanda.coupon.generator

Дальше предполагается, что вся работа по созданию компонента будет вестись в этой директории, но для меня такой подход не удобен, поэтому я просто удаляю эту директорию и создаю симлинк на %module_root%/install/components/maximaster/coupanda.coupon.generator, что позволяет мне работать внутри своего репозитория и исключает неудобства, связанные с необходимостью заливки измененного компонента в инсталлятор обратно (правда в этом случае при переустановке надо следить за этим, но меня теперь уже переустановка не интересует пока).

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

  • список всех скидок
  • список типов купонов
  • всякие системные вещи вроде инициализации компонента, подключения модулей и подключения шаблона компонента
  • сам шаблон с формой
  • ссылка на страницу со списком битриксовых скидок

Список всех скидок получить просто:

Список всех типов купонов — в общем-то тоже:

Писать о том, как подключить модули — я не буду 🙂 Банально

Остается завернуть это все в компонент и написать шаблон компонента.

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

  • \CAdminTabControl — это класс, который имеет набор методов для формирования административной страницы, разбитой на табы
  • \CAdminForm — наследник предыдущего класса, используется для построения форм со стандартными элементами управления
  • \CAdminTabControlDrag — новый класс, который позволяет строить страницы вида той, что используется для просмотра информации о заказе с возможностью перетаскивания блоков

Я буду использовать первый, т.к. моя страница не является целиком формой (поэтому мне не подойдет \CAdminForm), а использовать \CAdminTabControlDrag я не хочу в виду того, что данный способ построения интерфейса не очень подходит для целей данной страницы.

Ок, делаем шаблон компонента:

И получаем на выходе рабочую страницу с рабочими табами (правда пустую пока :))

После этого с помощью обычных таблиц и элементов форм рендерим всю страницу с настройками. Из не очень часто встречающегося, нам потребуется пара инпутов для ввода периода с датами. Для их рендеринга приходится функция \CalendarPeriod. Также у нас будет инпут с выбором пользователя, который можно отрендерить с помощью функции \FindUserID. Небольшие пояснения выводятся с помощью функций \BeginNote и \EndNote.
Не знаю, есть ли более современные аналоги для этих функций, поделитесь в комментариях, если вдруг знаете.

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

Функция рендерит span и script теги, которые в паре работают как вопросик с тултипом в нативном битриксовом интерфейсе.

После того, как были запилены все элементы формы, получился вот такой монстрик:

По-моему норм.

Все последние изменения модуля — тут.

В следующей статье будем оживлять эту форму с помощью битриксовой адской js-библиотеки и ванильного javascript’а (а может и jq, там видно будет).