Пищеварительные функторы с переменным числом субформ (Snap/Heist)

Я работаю над переносом сайта с PHP на Snap w/ Heist. Я перенес некоторые из более простых форм на успешное использование пищеварительных функторов, но теперь я должен сделать сложные, которые требуют использования подчиненных форм.

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

как выглядит форма в версии PHP

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

Вот что у меня есть для подформы измерений:

data AdDimensions = AdDimensions
    { sizeId :: Int64
    , layoutId :: Int64
    , dimensions :: Maybe String
    }

adDimensionsForm :: Monad m => AdDimensions -> Form Text m AdDimensions
adDimensionsForm d = AdDimensions
    <$> "size_id" .: stringRead "Must be a number" (Just $ sizeId d)
    <*> "layout_id" .: stringRead "Must be a number" (Just $ layoutId d)
    <*> "dimensions" .: opionalString (dimensions d)

Определение формы кажется не совсем правильным (может быть, здесь у меня совершенно неверная идея?). AdDimensions.dimensions должен быть Maybe String, поскольку при возвращении из базы данных при выполнении запроса он будет иметь значение NULL, чтобы получить список всех возможных комбинаций size_id / layout_id для нового размера объявления, но он не будет равен NULL из аналогичного запроса, который будет выполнен при создании формы редактирования. Само поле обязательно для заполнения (ad_dimensions.dimensions установлен в not null в базе данных).

Отсюда я понятия не имею, куда обратиться, чтобы сообщить родительской форме, что у нее есть список подчиненных форм или как я мог бы отобразить их, используя Heist.

2 ответа

Решение

С использованием listOf функциональность (которой не было, когда вопрос был первоначально задан / получен ответ), так можно поступить. Это требует 2 форм, где форма, представляющая тип вашего списка, является формулой:

data Thing = Thing { name: Text, properties: [(Text, Text)] }

thingForm :: Monad m => Maybe Thing -> Form Text m Thing
thingForm p = Thing
    <$> "name" .: text (name <$> p)
    <*> "properties" .: listOf propertyForm (properties <$> p)

propertyForm :: Monad m => Maybe (Text, Text) -> Form Text m (Text, Text)
propertyForm p = ( , )
    <$> "name" .: text (fst <$> p)
    <*> "value" .: text (snd <$> p)

Простые формы

Если у вас есть простой список элементов, то для этого используются несколько вариантов слияния dig digi-functors-heist, но вы можете обнаружить, что в результате вы получите неверную разметку, особенно если ваша форма находится в таблице.

<label>Name <dfInputText ref="formname" /></label>

<fieldset>
    <legend>Properties</legend>

    <dfInputList ref="codes"><ul>
    <dfListItem><li itemAttrs><dfLabel ref="name">Name <dfInputText ref="name" /></dfLabel>
        <dfLabel ref="code">Value <dfInputText ref="value" required /></dfLabel>
        <input type="button" name="remove" value="Remove" /></li></dfListItem>
    </ul>

    <input type="button" name="add" value="Add another property" /></dfInputList>
</fieldset>

Существует digicFunctors, предоставляющий JavaScript для управления добавлением и удалением элементов из формы с зависимостью jQuery. Я написал свою собственную, чтобы избежать зависимости от jQuery, поэтому я не использую предоставленную addControl или же removeControl сращивания (атрибуты для элементов типа кнопки).

Сложные формы

Форма в OP не может использовать сплайс, предоставляемый dig жидкокристаллическим функтором-heist, потому что метки являются динамическими (например, они поступают из базы данных) и потому что мы хотим, чтобы это было в сложной компоновке таблицы. Это означает, что мы должны выполнить 2 дополнительные задачи:

Создать разметку вручную

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

Для динамических форм (например, форм, где пользователям разрешено добавлять или удалять новые элементы на лету), вам потребуется скрытое поле индексов:

<input type='hidden' name='formname.fieldname.indices' value='0,1,2,3' />
  • formname = все, что вы назвали свою форму, когда вы запустили ее через runForm
  • fieldname = имя поля списка в главной форме (при необходимости измените его, если вы используете подчиненные формы), в этом примере оно будет называться "properties"
  • значение = разделенный запятыми список чисел, представляющих индексы подчиненных форм, которые должны быть обработаны при отправке формы

Когда один из элементов из вашего списка будет удален или добавлен новый, этот список необходимо будет скорректировать, иначе новые элементы будут полностью игнорироваться, а удаленные элементы все еще будут присутствовать в вашем списке. Этот шаг не нужен для статических форм, таких как в OP.


Создание остальной части формы должно быть довольно простым, если вы уже знаете, как писать соединения. Разбейте данные соответствующим образом (groupBy, chunksOf и т. Д.) И отправьте их через ваши соединения.

Если вы уже не можете определить это, просматривая разметку, сгенерированную с помощью digrical-splices-heist, вам нужно будет вставить значение индекса вашей подчиненной формы как часть полей для каждой подчиненной формы. Вот как должен выглядеть выходной HTML для первого поля нашего списка подчиненных форм:

<input type='text' name='formname.properties.0.name' value='Foo' />
<input type='text' name='formname.properties.0.value' value='Bar' />

(Подсказка: заархивируйте свой список вместе с бесконечным списком, начинающимся с 0)

При обработке ошибок извлекайте данные из формы

(Я прошу прощения заранее, если ни один из этого кода на самом деле не может скомпилировать, как написано, но, надеюсь, это иллюстрирует процесс)

Эта часть менее прямолинейна, чем другая, для этого вам придется копаться во внутренностях пищеварительных функторов. По сути, мы собираемся использовать те же функции, что и dig прелестно-функторы-грабли, чтобы вернуть данные и наполнить их нашей вещью. Функция нам нужна listSubViews:

-- where `v` is the view returned by `runForm`
-- the return type will be `[View v]`, in our example `v` will be `Text`
viewList = listSubViews "properties" v

Для статической формы это может быть просто сжатие этого списка вместе с вашим списком данных.

let x = zipWith (curry updatePropertyData) xs viewList

И тогда вашей функции updatePropertyData нужно будет обновить ваши записи, извлекая информацию из представления, используя fileInputRead функция:

updatePropertyData :: (Text, Text) -> View Text -> (Text, Text)
updatePropertyData x v =
    let
        -- pull the field information we want out of the subview
        -- this is a `Maybe Text
        val = fieldInputRead "value" v
    in
        -- update the tuple
        maybe x ((fst x, )) val

Я написал специальный комбинатор для этого некоторое время назад для пищеварительных функторов-0.2. Это было очень полнофункциональное решение, включающее код JavaScript, позволяющий динамически добавлять и удалять поля. Этот код был основан на гораздо более ранней реализации, которую мы с Крисом сделали для пакета формул, который в конечном итоге заменил digrical-функторы. Эта функция никогда не портировалась для работы с новым API, который получил в 0,3 пищеварительный функтор.

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

Изменить: Это было сделано сейчас для последних пищеварительных функторов. Смотрите функцию listOf.

Другие вопросы по тегам