Часть 1 Часть 2 Часть 3 Всем привет! В новой части мы рассмотрим использование JSFFI.  Добавим в наше приложение возможность установки даты дедлайна. Допустим, требуется сделать не просто текстовый input, а чтобы это был выпадающий datepicker. Можно, конечно, написать свой datepicker на рефлексе, но ведь существует большое множество различных JS библиотек, которыми можно воспользоваться. Когда существует уже готовый код на JS, который, например, слишком большой, чтобы переписывать с использованием GHCJS, есть возможность подключить его с помощью JSFFI (JavaScript Foreign Function Interface). В нашем случае мы будем использовать flatpickr. Создадим новый модуль JSFFI, сразу добавим его импорт в Main. Вставим в созданный файл следующий код: Так же не забудем добавить в элемент head необходимые скрипт и стили: Пробуем скомпилировать, так же как и раньше, и получаем следующую ошибку: Действительно, сейчас мы собираем наше приложение с помощью GHC, который понятия не имеет, что такое JSFFI. Напомним, что сейчас запускается сервер, который с помощью вебсокетов отправляет обновленный DOM, когда требуется, и код на JavaScript для него чужд. Здесь напрашивается вывод, что использовать наш datepicker при сборке с помощью GHC не получится. Тем не менее, в продакшене GHC для клиента не будет использоваться, мы будем компилировать в JS при помощи GHCJS, и полученный JS встраивать уже в нашу страницу. ghcid не поддерживает GHCJS поэтому смысла грузиться в nix shell нет, мы будем использовать nix сразу для сборки: В корневой директории приложения появится директория todo-client-bin со следующей структурой: Открыв index.html в браузере, увидим наше приложение. Мы собрали проект с помощью GHCJS, но ведь для разработки все равно удобнее использовать GHC вместе с ghcid, поэтому модифицируем модуль JSFFI следующем образом: Мы добавили условную компиляцию: в зависимости от платформы, либо будем использовать вызов JS функций, либо заглушку. Теперь требуется изменить форму добавления нового задания, добавив туда поле выбора даты: Скомпилируем наше приложение, попробуем его запустить, и мы все еще ничего не увидим. Если посмотрим в консоль разработчика в браузере, увидим следующую ошибку: Замечаем, что необходимая нам функция не определена. Так получается, потому что элемент script со ссылкой создается динамически, равно как и вообще все элементы страницы. Поэтому, когда мы используем вызов функции flatpickr, скрипт, содержащий библиотеку с этой функцией может быть еще не загружен. Надо явно расставить порядок загрузки.
Решим эту проблему при помощи пакета reflex-dom-contrib. Этот пакет содержит много полезных при разработке функций. Его подключение нетривиально. Дело в том, что на Hackage лежит устаревшая версия этого пакета, поэтому придется брать его напрямую c GitHub. Обновим default.nix следующим образом. Добавим импорт модуля import Reflex.Dom.Contrib.Widgets.ScriptDependent и внесем изменения в форму: Мы воспользовались новой функцией widgetHoldUntilDefined, которая построит элемент, переданный ей в последнем параметре, только в тот момент, когда указанный скрипт будет загружен.
Теперь, если загрузим нашу страницу, полученную при помощи GHCJS, мы увидим используемый нами datepicker. Но мы никак не задействовали это поле. Изменим тип Todo, не забыв добавить импорт Data.Time: Теперь изменим функцию с формой для нового задания: И добавим отображение даты в списке: Полученный результат, как всегда, можно посмотреть в нашем репозитории. В следующей части мы рассмотрим как реализовать роутинг в приложении на Reflex. Версия на английском языке: https://typeable.io/blog/2021-06-22-reflex-4.html Haskell программист