Хватить это терпеть! Или зачем столько чанков в FormIt!?
        Привет сообществу!
Небольшая хитрость которая поможет новичкам избавиться от множества чанков/файлов писем с сайта и реализовать их в одном чанке/файле. Кроме этого пару слов о том, что происходит с MODX в конце заметки.
Предисловие:
Пару месяцев назад в чате был небольшой джентльменский спор между Павлом Зарубином и Евгением на тему «Так ли хорош FormIt?». В этом споре, я разместился где-то посередине так как я вижу и плюсы и минусы данного компонента. Эта заметка об одном из минусов, ну и конечно же решение.
Итак, проблема
А если быть точнее, столько же, сколько и различных форм на сайте. Когда ко мне попадают чужие проекты, я вижу там 100500 чанков которые отличаются только тем, что у них разное количество полей или просто разные поля.
Они выглядят примерно так:
Хоть я и в бэкенде недавно, но меня это удручает, поскольку сегодня существует понятие «Наследование» и например в miniShop2 те же самые письма реализованы прекрасно с помощью fenom.
Предлагаю самое простое решение, воспользоваться хуком и немного схитрить используя свойство fieldNames сниппета FormIt который служит тому чтобы при записи данных форм в БД с помощью родного хука FormItSaveForm переименовывать названия полей, ведь по умолчанию будут использованы ключи глобального массива с формой. Синтаксис такой: Название поля==Новое название и это всё через запятую. Название поля — это атрибут name у полей (Вдруг кто не знал).
«Что этот сниппет себе позволяет?!» или что сниппет/хук делает объясню вкратце:
Забирает значение свойства fieldNames и указанных данных пользователя заполнившего форму и с помощьюмагии методов создает новое поле fields, т.е. в чанке письма будет доступен плейсхолдер 
[[+fields]] в котором будет JSON массив с названием и значением от пользователя.
Теперь в вызове сниппета FormIt указывайте в свойство fieldNames список нужных полей и пользуйтесь одним чанком.
Недавно Иван Бочкарёв писал о том, что происходит с MODX на Github и мне также хочется призвать разработчиков к активности. Я считаю, что MODX переживает не лучшие времена и понятно, что его развитие в основном зависит от более опытных разработчиков и от MODX LLC, но мы ведь тоже можем хоть немного повлиять на развитие нашей платформы. Я также в свою очередь делаю вклад насколько мне позволяют знания, опыт, время и возможности. Вступайте в группу MODX Contributors
Вам спасибо за внимание, а мне потому что…
☕ Угостить чашкой кофе
UPD
Обновлен сниппет для значений radio
UPD: 14.11.2019
Обновлен сниппет/хук, добавлен trim     
    
    
                                                                                
            Небольшая хитрость которая поможет новичкам избавиться от множества чанков/файлов писем с сайта и реализовать их в одном чанке/файле. Кроме этого пару слов о том, что происходит с 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
$fields = explode(',', $hook->formit->config['fieldNames']);
foreach ($fields as $key => $field) {
    $f = explode('==', trim($field));
    $value = $hook->getValue($f[0]);
    
    if (is_array($value)) {
        $value = implode(', ', $value);
    }
    
    $result[] = array(
        'name' => $f[1],
        'value' => $value,
    );
}
$hook->setValue('fields', $modx->toJSON($result));
return true;«Что этот сниппет себе позволяет?!» или что сниппет/хук делает объясню вкратце:
Забирает значение свойства fieldNames и указанных данных пользователя заполнившего форму и с помощью
[[+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
Вам спасибо за внимание, а мне потому что…
☕ Угостить чашкой кофе
UPD
Обновлен сниппет для значений radio
UPD: 14.11.2019
Обновлен сниппет/хук, добавлен trim
Комментарии: 21
                Хорошее решение, а вот ссылки на телеграмм уже почти год не работают. Не устаю повторять, что для телеги конверсия ссылок в формате: @groupname гораздо выше            
                    
                Справедливое замечание
Я поменял тип группы: t.me/modx_contributors
                    Я поменял тип группы: t.me/modx_contributors
Хорошее решениеСпасибо
а вот ссылки на телеграмм уже почти год не работают. Не устаю повторять, что для телеги конверсия ссылок в формате: @groupname гораздо вышеПоменял в заметке
                не знал, что формит умеет в 'emailTpl' => '@FILE chunks/email/email.tpl'
или я где-то пропустил подключение pdoTools?
я использовую formit через ajaxform, и у меня не получалось в чанках письма юзать fenom — только родной синтаксис и тот без модификаторов. то есть если значение [[+phone]] пустое, то в письме так и будет — "[[+phone]]"
                    или я где-то пропустил подключение pdoTools?
я использовую formit через ajaxform, и у меня не получалось в чанках письма юзать fenom — только родной синтаксис и тот без модификаторов. то есть если значение [[+phone]] пустое, то в письме так и будет — "[[+phone]]"
- Added default file-based chunks as objects
 - Added support for pdoTools getChunk method
 
не знал, что формит умеет в 'emailTpl' => '@FILE chunks/email/email.tpl'В версии 3.0.0 появилась:
или я где-то пропустил подключение pdoTools?
                да, надо читать чейндж-логи))            
                    
                Вот такой вариант, работает 
                    <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>Добавляю все поля которые мне нужны из разных форм. Клиенту приходят только те поля, что заполнены.            
                Выложу свой способ управления множеством форм для сниппета ajaxForm, может пригодится.
Создаём чанк ajaxForms:
Это единый чанк, в котором вызывается сниппет ajaxForm с необходимыми параметрами.
В моём случае каждая отдельная форма — это отдельный чанк. Если эти чанки мало чем различаются, то можно использовать extends из fenom, переписывая лишь изменяющиеся значения, а остальное не трогать.
И далее в шаблоне страницы вызов нужной формы:
В нём мы подменили значения, передающиеся в чанк ajaxForm.
Пример чанка callback с формой:
Если нужно вызвать похожую форму но с другим заголовком и другими параметрами, то создадим чанк question, который наследует чанк callback:
И вызов этой формы в шаблоне страницы:
Таким образом, для всех форм мы можем задать единые параметры вызова сниппета ajaxForm, и при необходимости перезаписать их. А также под каждую форму либо создать отдельные чанки, либо унаследовать от одного чанка кучу других с индивидуальными параметрами.
Дополнительно я скрываю уведомления jgrowl js-скриптом:
Все уведомления об ошибках показываются под полями форм, в коде span c классом error. А если форма успешно отправлена, то с помощью скрипта выше мы скрываем popup-форму, если она была открыта, и показываем другое popup-окно:
Оно же всплывает и при отправке обычных не popup-форм. Да, насчёт popup-окон, актуально при использовании flexbox3.
                    Создаём чанк 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.
                Позвольте переписать ваш чанк 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',
]}Так ведь намного лучше
                Согласен, так красивее :)            
                    
                И чанк 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>            
                Если используется хук FormItSaveForm, то можно обойтись без лишнего сниппета:
                    {foreach $_pls['savedForm.values'] | json_decode as $label => $value}
    {$label}: {$value | e}
{/foreach}            немного схитрить используя свойство 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}            А разве прописав кастомный параметр при вызове FormIt (или AjaxForm) он не попадает прямиком в чанк формы?Паша, попадёт конечно, отличная реализация, только в заметке речь о чанках писем или я что-то не понял?)
                Ой, точно! Видимо я что-то упустил)) Кстати, хорошая статья! =)            
                    Кстати, хорошая статья! =)Спасибо большое :)
                Можно модифицировать параметр &fieldNames так чтобы можно было выводить разные типы инпутов и задать обязательные параметры, возможно что-нибудь ещё
                    &fieldNames=`name:text:required==Имя, email:email:required==E-mail`            
                ребят, а кто-нибудь делал зависимую валидацию полей в formit?
нужно так: если поле name_1 имеет значени true, то поле name_2 становится обязательным.
родной параметр validate вроде как по одному полю работает, а хуки не выводят сообщение об ошибке.
                    нужно так: если поле name_1 имеет значени true, то поле name_2 становится обязательным.
родной параметр validate вроде как по одному полю работает, а хуки не выводят сообщение об ошибке.
                хук сделать, не?            
                    
                Разве не выводят?
                    $hook->addError('field_name', 'lexicon_key');
return !$hook->hasErrors();            
                Для универсальных писем, с проверкой
                    Телефон: {$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}            
                            Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.