Хватить это терпеть! Или зачем столько чанков в FormIt!?

Привет сообществу!

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

Предисловие:

Пару месяцев назад в чате был небольшой джентльменский спор между Павлом Зарубином и Евгением на тему «Так ли хорош FormIt?». В этом споре, я разместился где-то посередине так как я вижу и плюсы и минусы данного компонента. Эта заметка об одном из минусов, ну и конечно же решение.

Итак, проблема

Много чанков


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

Они выглядят примерно так:

...
<li>Имя: [[+name]]</li>
<li>Телефон: [[+phone]]</li>
...

...
<li>Имя: [[+name]]</li>
<li>Email: [[+email]]</li>
...

...
<li>Имя: [[+name]]</li>
<li>Email: [[+email]]</li>
<li>Тема обращения: [[+subject]]</li>
<li>Сообщение: [[+message]]</li>
...
и так далее.

Хоть я и в бэкенде недавно, но меня это удручает, поскольку сегодня существует понятие «Наследование» и например в miniShop2 те же самые письма реализованы прекрасно с помощью fenom.

Решение


Предлагаю самое простое решение, воспользоваться хуком и немного схитрить используя свойство fieldNames сниппета FormIt который служит тому чтобы при записи данных форм в БД с помощью родного хука FormItSaveForm переименовывать названия полей, ведь по умолчанию будут использованы ключи глобального массива с формой. Синтаксис такой: Название поля==Новое название и это всё через запятую. Название поля — это атрибут name у полей (Вдруг кто не знал).

1. Вызов FormIt

// Синтаксис MODX

[[FormIt?
    ...
    &hooks=`fields,email`    // сниппет fields мы с вами создадим в следующем шаге
    &fieldNames=`name==Имя,phone==Контактный телефон`
    &emailTpl=`email.tpl`
]]

// Синтаксис fenom

{'FormIt' | snippet : [
    ...
    'hooks' => 'fields,email',    // сниппет fields мы с вами создадим в следующем шаге
    'fieldNames' => 'name==Имя,phone==Контактный телефон',
    'emailTpl' => '@FILE chunks/email/email.tpl'
]}

2. Создание сниппета/хука
Создадим сниппет и укажем fields в качестве названия с таким кодом:

<?php
$fieldNames = explode(',', $hook->formit->config['fieldNames']);

foreach ($fieldNames as $key => $field) {
    $fIeld = explode('==', $field);
    
    $result[] = array(
        'name' => $fIeld[1],
        'value' => $hook->getValue($fIeld[0]),
    );
}

$hook->setValue('fields', $modx->toJSON($result));

return true;

«Что этот сниппет себе позволяет?!» или что сниппет/хук делает объясню вкратце:

Забирает значение свойства fieldNames и указанных данных пользователя заполнившего форму и с помощью магии методов создает новое поле fields, т.е. в чанке письма будет доступен плейсхолдер
[[+fields]] в котором будет JSON массив с названием и значением от пользователя.

3. Чанк/файл email.tpl

Я лично из лагеря fenom, но стараюсь и ради тех кто всё еще пользуется родным синтаксисом
Если вы не пользуйтесь fenom, то весьма кстати вам пригодиться сниппет getImageList который идёт вместе с MIGX.

Кстати, у меня есть пара заметок о MIGX где я подробно рассказывал о нём. Вот первая заметка, а вот вторая, почитайте, уверен, будет интересно, если еще не читали.

// Синтаксис MODX


<h3>Обратная связь:</h3>

[[getImageList?
    &value=`[[+fields]]`
    &tpl=`@CODE:<li>[[+name]]: [[+value]]</li>`
]]


// Синтаксис fenom

<h3>Обратная связь:</h3>

{foreach $fields | fromJSON as $field}
    <li>{$field['name']}: {$field['value']}</li>
{/foreach}

Итог

Теперь в вызове сниппета FormIt указывайте в свойство fieldNames список нужных полей и пользуйтесь одним чанком.

__________________________________



Пару слов о самом MODX

К данной заметке это не относится, но хочется выразится о MODX и о MODX3

Недавно Иван Бочкарёв писал о том, что происходит с MODX на Github и мне также хочется призвать разработчиков к активности. Я считаю, что MODX переживает не лучшие времена и понятно, что его развитие в основном зависит от более опытных разработчиков и от MODX LLC, но мы ведь тоже можем хоть немного повлиять на развитие нашей платформы. Я также в свою очередь делаю вклад насколько мне позволяют знания, опыт, время и возможности. Вступайте в группу MODX Contributors

Вам спасибо за внимание, а мне потому что… Если что я Баха?

Если вдруг кому-то захочется поблагодарить рублём, то так уж и быть: Карта Сбербанка +79609354545
Баха Волков
11 февраля 2019, 14:56
25
858
+24
Поблагодарить автора Отправить деньги

Комментарии: 21

Yar
Yar
11 февраля 2019, 15:04
+1
Хорошее решение, а вот ссылки на телеграмм уже почти год не работают. Не устаю повторять, что для телеги конверсия ссылок в формате: @groupname гораздо выше
    Иван Бочкарев
    11 февраля 2019, 15:18
    0
    Справедливое замечание
    Я поменял тип группы: t.me/modx_contributors
    Баха Волков
    11 февраля 2019, 15:22
    0
    Хорошее решение
    Спасибо

    а вот ссылки на телеграмм уже почти год не работают. Не устаю повторять, что для телеги конверсия ссылок в формате: @groupname гораздо выше
    Поменял в заметке
mngatoff
11 февраля 2019, 15:06
0
не знал, что формит умеет в 'emailTpl' => '@FILE chunks/email/email.tpl'
или я где-то пропустил подключение pdoTools?
я использовую formit через ajaxform, и у меня не получалось в чанках письма юзать fenom — только родной синтаксис и тот без модификаторов. то есть если значение [[+phone]] пустое, то в письме так и будет — "[[+phone]]"
    Баха Волков
    11 февраля 2019, 15:20
    0
    не знал, что формит умеет в 'emailTpl' => '@FILE chunks/email/email.tpl'
    или я где-то пропустил подключение pdoTools?
    В версии 3.0.0 появилась:

    • Added default file-based chunks as objects
    • Added support for pdoTools getChunk method
      mngatoff
      11 февраля 2019, 15:22
      0
      да, надо читать чейндж-логи))
    Денис
    12 февраля 2019, 22:32
    +3
    Вот такой вариант, работает
    <table>
        [[+name:notempty=`<tr>
            <td style="padding:2px 7px 2px 0px"><b>Имя:</b></td>
            <td>[[+name]]</td>
        </tr>`]]
        [[+phone:notempty=`<tr>
            <td style="padding:2px 7px 2px 0px"><b>Телефон:</b></td>
            <td>[[+phone]]</td>
        </tr>`]]
        [[+email:notempty=`<tr>
            <td style="padding:2px 7px 2px 0px"><b>Почта:</b></td>
            <td>[[+email]]</td>
        </tr>`]]
        [[+link:notempty=`<tr>
            <td style="padding:2px 7px 2px 0px"><b>Ссылка:</b></td>
            <td>[[+link]]</td>
        </tr>`]]
        [[+mess:notempty=`<tr>
            <td style="padding:2px 7px 2px 0px"><b>Сообщение:</b></td>
            <td>[[+mess]]</td>
        </tr>`]]
        <tr>
            <td style="padding:2px 7px 2px 0px"><b>Страница:</b></td>
            <td><a href="[[++site_url]][[+uri]]">[[+page]]</a></td>
        </tr>
    </table>
    Добавляю все поля которые мне нужны из разных форм. Клиенту приходят только те поля, что заполнены.
Николай
11 февраля 2019, 16:04
2
+1
Выложу свой способ управления множеством форм для сниппета ajaxForm, может пригодится.

Создаём чанк ajaxForms:

{if !$form} {set $form = ''}{/if}
{if !$hooks} {set $hooks = 'email,FormItSaveForm'}{/if}
{if !$emailSubject} {set $emailSubject = 'Тема письма'}{/if}
{if !$emailTo} {set $emailTo = $_modx->config['callback_email']}{/if}
{if !$validationErrorMessage} {set $validationErrorMessage = 'В форме содержатся ошибки!'}{/if}
{if !$successMessage} {set $successMessage = '<div class="name">Спасибо</div><p>Ваше сообщение успешно отправлено</p>'}{/if}
{if !$vTextMaxLength} {set $vTextMaxLength = '<div>Проверьте правильность заполнения</div>'}{/if}
{if !$vTextMinLength} {set $vTextMinLength = '<div>Проверьте правильность заполнения</div>'}{/if}
{if !$vTextRequired} {set $vTextRequired = 'Это поле обязательно для заполнения'}{/if}
{if !$validate} {set $validate = 'name:required'}{/if}
{if !$formFields} {set $formFields = 'name,pageId,pagetitle,email,message,file'}{/if}
{if !$formName} {set $formName = 'Имя формы'}{/if}
{if !$validationErrorMessage} {set $validationErrorMessage = 'В форме содержатся ошибки!'}{/if}

[[!ajaxForm?
	&form=`{$form}`
	&hooks=`{$hooks}`
	&emailSubject=`{$emailSubject}`
	&emailTo=`{$emailTo}`
	&validationErrorMessage=`{$validationErrorMessage}`
	&successMessage=`{$successMessage}`
	&vTextMaxLength=`{$vTextMaxLength}`
	&vTextMinLength=`{$vTextMinLength}`
	&vTextRequired=`{$vTextRequired}`
	&validate=`{$validate}`
	&validationErrorMessage=`{$validationErrorMessage}`
	&formName=`{$formName}`
	&formFields=`{$formFields}`
	&fieldNames=`{$fieldNames}
    `
]]

Это единый чанк, в котором вызывается сниппет ajaxForm с необходимыми параметрами.

В моём случае каждая отдельная форма — это отдельный чанк. Если эти чанки мало чем различаются, то можно использовать extends из fenom, переписывая лишь изменяющиеся значения, а остальное не трогать.

И далее в шаблоне страницы вызов нужной формы:

{include 'ajaxForms' 
    form='callback'
    emailSubject='Обратный звонок'
    validate='name:required,phone:required'
    formFields='name,phone,pagetitle'
    formName='Обратный звонок'
}

В нём мы подменили значения, передающиеся в чанк ajaxForm.

Пример чанка callback с формой:

<div style="display: none;" id="{block 'id'}callback{/block}" class="popup">
    <div class="title">{block 'title'}Заказать обратный звонок{/block}</div>
    <div class="desc">{block 'desc'}Оставьте заявку, и мы свяжемся с вами в самое ближайшее время{/block}</div>
    <form role="form" method="POST" action="[[~[[*id]]]]" enctype="multipart/form-data">
        <input type="hidden" name="pagetitle" value="[[*pagetitle]] ([[*id]])">
            
        <input type="text" name="name" value="[[!+fi.name]]" placeholder="Ваше имя" required="required" />
        <span class="error error_name">[[+fi.error.name]]</span>
        
        <input type="tel" name="phone" value="[[!+fi.phone]]" placeholder="Ваш телефон" required="required" />
        <span class="error error_phone">[[+fi.error.phone]]</span>
        
        <div class="privacy">Отправляя заявку, Вы соглашаетесь на обработку персональных данных
согласно <a href="{29|url}">Пользовательскому соглашению</a></div>
        
        <button class="button-yellow" type="submit" value="{md5(rand())}" name="submit">Отправить</button>
    </form>
</div>

Если нужно вызвать похожую форму но с другим заголовком и другими параметрами, то создадим чанк question, который наследует чанк callback:

{extends 'callback'}

{block 'id'}question{/block}
{block 'title'}Задать вопрос{/block}
{block 'desc'}Задайте свой вопрос, и мы с вами свяжемся{/block}

И вызов этой формы в шаблоне страницы:

{include 'ajaxForms' 
    form='question'
    emailSubject='Задать вопрос'
    validate='name:required,phone:required'
    formFields='name,phone,pagetitle'
    formName='Задать вопрос'
}

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

Дополнительно я скрываю уведомления jgrowl js-скриптом:

$(document).on('af_complete', function(event, response) {
    if (response.success) {
        $.fancybox.close();

        $.fancybox.open({
        	src  : '#popup-success',
        	type : 'inline',
        	opts : {
        		afterShow : function( instance, current ) {
        			// console.info( 'done!' );
        		}
        	}
        });
    } else {
        for (var prop in response.data) {}
    }
    response.message='';
});

Все уведомления об ошибках показываются под полями форм, в коде span c классом error. А если форма успешно отправлена, то с помощью скрипта выше мы скрываем popup-форму, если она была открыта, и показываем другое popup-окно:

<div style="display: none;" id="popup-success" class="popup">
    <div class="title">Данные успешно отправлены</div>
    <div class="success">
        <img src="/assets/img/success.png" alt="">
    </div>
</div>

Оно же всплывает и при отправке обычных не popup-форм. Да, насчёт popup-окон, актуально при использовании flexbox3.
    Баха Волков
    11 февраля 2019, 16:23
    2
    +5
    Позвольте переписать ваш чанк ajaxForms:

    {'!AjaxForm' | snippet : [
        'form' => $form,
        'hooks' => $hooks !: 'email,FormItSaveForm',
        'emailSubject' => $emailSubject !: 'Тема письма',
        'emailTo' => $emailTo !: $_modx->config.callback_email,
        'validationErrorMessage' => $validationErrorMessage !: 'В форме содержатся ошибки!',
        'successMessage' => $successMessage !: '<div class="name">Спасибо</div><p>Ваше сообщение успешно отправлено</p>',
        'vTextMaxLength' => $vTextMaxLength !: '<div>Проверьте правильность заполнения</div>',
        'vTextMinLength' => $vTextMinLength !: '<div>Проверьте правильность заполнения</div>',
        'vTextRequired' => $vTextRequired !: 'Это поле обязательно для заполнения',
        'validate' => $validate !: 'name:required',
        'formName' => $formName !: 'Имя формы',
        'formFields' => $formFields !: 'name,pageId,pagetitle,email,message,file',
    ]}

    Так ведь намного лучше
      Николай
      11 февраля 2019, 16:25
      0
      Согласен, так красивее :)
    Баха Волков
    11 февраля 2019, 16:27
    +2
    И чанк callback также:

    <div style="display: none;" id="{block 'id'}callback{/block}" class="popup">
        <div class="title">{block 'title'}Заказать обратный звонок{/block}</div>
        <div class="desc">{block 'desc'}Оставьте заявку, и мы свяжемся с вами в самое ближайшее время{/block}</div>
        <form role="form" method="POST" action="{('id' | resource) | url}" enctype="multipart/form-data">
            <input type="hidden" name="pagetitle" value="{'pagetitle' | resource} ({'id' | resource})">
                
            <input type="text" name="name" value="" placeholder="Ваше имя" required="required" />
            <span class="error error_name"></span>
            
            <input type="tel" name="phone" value="" placeholder="Ваш телефон" required="required" />
            <span class="error error_phone"></span>
            
            <div class="privacy">Отправляя заявку, Вы соглашаетесь на обработку персональных данных
    согласно <a href="{29 | url}">Пользовательскому соглашению</a></div>
            
            <button class="button-yellow" type="submit" value="{md5(rand())}" name="submit">Отправить</button>
        </form>
    </div>
Andrey Grachov
11 февраля 2019, 17:14
+1
Если используется хук FormItSaveForm, то можно обойтись без лишнего сниппета:

{foreach $_pls['savedForm.values'] | json_decode as $label => $value}
    {$label}: {$value | e}
{/foreach}
Павел Гвоздь
11 февраля 2019, 19:52
+1
немного схитрить используя свойство fieldNames сниппета FormIt
А разве прописав кастомный параметр при вызове FormIt (или AjaxForm) он не попадает прямиком в чанк формы?
Я делаю примерно так:
{'!AjaxForm' | snippet : [
    ...
    'formFields' => [
        'name' => [
            'type' => 'text',
            'label' => '',
            'placeholder' => 'Ваше имя',
            'required' => true,
        ],
        'email' => [
            'type' => 'email',
            'label' => '',
            'placeholder' => 'Ваш email',
        ],
        'phone' => [
            'type' => 'text',
            'label' => '',
            'placeholder' => 'Контактный телефон',
            'required' => true,
        ],
    ],
    ...
]}

И в чанке формы:
{foreach $formFields as $fk => $fv}
    {if $fv['label']?}
        <div class="form__label-w">
            <label class="form__label">
                {$fv['label']}
            </label>
        </div>
    {/if}

    {switch $fv['type']}
        {case 'text'}
            <div class="form__input-w">
                <input class="form__input" type="text" name="{$fk}" placeholder="{$fv['placeholder']}">
            </div>

        {case 'email'}
            <div class="form__input-w">
                <input class="form__input" type="email" name="{$fk}" placeholder="{$fv['placeholder']}">
            </div>

        {case 'textarea'}
            <div class="form__input-w">
                <textarea class="form__input" name="{$fk}" placeholder="{$fv['placeholder']}"></textarea>
            </div>
    {/switch}

    <div class="form__error error_{$fk}"></div>
{/foreach}
    Баха Волков
    11 февраля 2019, 20:06
    +1
    А разве прописав кастомный параметр при вызове FormIt (или AjaxForm) он не попадает прямиком в чанк формы?
    Паша, попадёт конечно, отличная реализация, только в заметке речь о чанках писем или я что-то не понял?)
      Павел Гвоздь
      11 февраля 2019, 20:08
      +2
      Ой, точно! Видимо я что-то упустил)) Кстати, хорошая статья! =)
        Баха Волков
        11 февраля 2019, 20:23
        +1
        Кстати, хорошая статья! =)
        Спасибо большое :)
Куницкий Алексей
11 февраля 2019, 19:58
0
Можно модифицировать параметр &fieldNames так чтобы можно было выводить разные типы инпутов и задать обязательные параметры, возможно что-нибудь ещё

&fieldNames=`name:text:required==Имя, email:email:required==E-mail`
mngatoff
13 февраля 2019, 00:49
0
ребят, а кто-нибудь делал зависимую валидацию полей в formit?

нужно так: если поле name_1 имеет значени true, то поле name_2 становится обязательным.

родной параметр validate вроде как по одному полю работает, а хуки не выводят сообщение об ошибке.
    Николай Савин
    13 февраля 2019, 08:21
    +1
    хук сделать, не?
    Павел Гвоздь
    13 февраля 2019, 08:34
    0
    Разве не выводят?
    $hook->addError('field_name', 'lexicon_key');
    return !$hook->hasErrors();
Андрей Степаненко
05 марта 2019, 04:21
0
Для универсальных писем, с проверкой

Телефон: {$phone}

{set $data = $fields | fromJSON}
{if count($data)}
    <em>Текст сообщения</em>
    <ul style="list-style: none">
        {foreach $data as $field}
            <li>— <b>{$field['name']}</b>: {$field['value']}</li>
        {/foreach}
    </ul>
{/if}
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.