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

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

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

what the form looks like in the PHP version

Эта форма гарантированно имеет минимум 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.

Ответ 1

Используя функциональность 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)

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

Если у вас есть простой список элементов, digestive-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>

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

Комплексные формы

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

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

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

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

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

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


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

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

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

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

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

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

Эта часть менее прямолинейна, чем другая, вам придется прорыть внутренности пищеварительных функций для этого. В принципе, мы будем использовать те же функции, что и пищеварительные функции-heist, чтобы вернуть данные и заполнить нашу Thing. Нам нужна функция 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

Ответ 2

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

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

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