Forms: Веб-формы

Компонент, позволяющий оставлять сообщения на сайте: заказы, отзывы, обращения, жалобы и т.п. Каждое сообщение сохраняется в базе данных, а также (если нужно) отправляется письмом. Предоставляется возможность создавать неограниченое количество гибко настраиваемых веб-форм, каждое со своим набором полей.

Документация

Читать

Часто мы сталкиваемся с задачами по созданию на сайтах веб-форм, посредством которых посетители оставляют разного рода сообщения для владельца сайта. Наиболее типичные примеры - форма заказа товара, запрос, отзыв. В случае, если такая форма только одна, задача не вызывает особых затруднений: достаточно создать контроллер, который будет отрисовывать страницу с формой и обрабатывать POST-запрос. Хотя и в этом случае можно запутаться в нюансах: ведь необходимо обеспечить валидацию данных, отправку письма, сохранение поста в базе данных. Что же говорить о более сложных случаях?

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

  • Валидация введенных данных
  • Защита от роботов (CAPTCHA)
  • Отправка письма по одному или нескольким адресам
  • Сохранение поста в базе данных
  • Поддержка произвольного числа форм с разным реквизитным составом и пр.
  • Возможность создания пользовательских шаблонов как для отдельных полей, так и для целых форм.
  • Возможность программного расширения, например вмешательства в процесс формирования письма, записи в БД.

Все это реализовано в компоненте Component.Forms.

Создание новой формы

Допустим, у нас магазин, в котором продают молотки. Есть страница, на которой расписываются достоинства разного рода молотков. Внизу этой страницы требуется отобразить форму "Заказать молоток".

Идем в админ - "Веб-формы", жмем "Добавить веб-форму". В поле "Идентификатор" пишем, например "order" - это должно быть уникальное буквосочетание для каждой формы. "Название формы" - не принципиально, это всего лишь произвольная строка под которой форма будет фигурировать в админе. Самое интересное - "Параметры". В этом поле, собственно, и описывается форма. Основных параметров не много:

//Заголовок, который будет отображаться над формой. Можно поставить "none".
 header = Заказать

//Страница, на которую будет сделан редирект после успешной обработки поста
 ok = /order-ok.htm

//Текст на submit-кнопке
 submit = Заказать

//Описание полей
 fields = {

         itemtype = {
                 type = select
                 caption = Тип
                 items = {
                         1 = Столярный
                         2 = Слесарный
                         3 = Кузнечная кувалда
                         4 = Деревянная киянка
                         5 = Молоток невропатолога
                 }
         }

         name = {
                 caption = Ваше имя
                 style = width:100%
         }

         email = {
                 caption = E-Mail
                 style = width:300px
         }

         phone = {
                 caption = Телефон
                 style = width:500px
         }

         notes = {
                 caption = Дополнительные пожелания
                 type = textarea
                 style = width:100%;height:200px
         }
 }

Легко заметить, что описание полей идентично описанию полей в CMS.Fields

Установка формы на страницу

Для установки на страницу воспользуемся вставкой %form{<идентификатор>} или ajax-вариантом %ajax_form{<идентификатор>}.

%form{order} %ajax_form{order}

Собственно, это все минимально необходимые действия. Теперь на странице есть форма, данные формы сохраняются в БД, администратор их может посмотреть, нажав в админе ссылку с именем формы. Теперь будем добавлять всяческие фичи.

Настройка админа

Во-первых, настроим таблицу со списком пришедших заказов. По умолчанию там наличествует только дата и время заказа. Поэтому следует перечислить поля, которые нужно выводить в таблице, добавив параметр admin_list:

 admin_list = {
        itemtype = Тип
        name = Имя клиента
        email = E-Mail
 }

Во-вторых, давайте зайдем в редактирование пришедшего заказа. Как и следовало ожидать, наблюдаем там поле с заголовком "Ваше имя" - с тем же самым заголовком, который на форме заказа на сайте. Но ведь это же админ! Там должно быть написано "Имя клиента". Для этого воспользуемся условным описанием параметров полей:

 fields = {

        .....

        name = {
                caption = Ваше имя
                in admin caption = Имя клиента
                style = width:100%
        }

        .....
 }

Т.е. в админе (in admin) поле будет иметь один caption, а во всех прочих случаях - другой. Такми способом можно описывать любые параметры. Например, это может потребоваться для параметра "style". Условное описание параметров можно делать групповым:

 fields = {

        .....

        name = {
                caption = Ваше имя
                style = width:100%
                in admin = {
                        caption = Имя клиента
                        style = width:90%
                }
        }

        .....
 }

Отправка письма

Для отправки письма требуется указать следующее:

 # Кому отправлять письмо
 send_to = info@mysite.ru

 # Тема письма
 send_subject = Заказ с сайта

 # Шаблон письма
 send_template = send

 # Адрес отправителя
 send_from = email

Параметр "send_to" может также быть массивом (в случае если требуется отправка на несколько адресов):

 send_to = {
        info@mysite.ru
        support@mysite.ru
 }

Шаблон письма send.phtml лежит в каталоге app/components/Forms/views. Допускается как правка этого шаблона, так и создание других шаблонов. Кроме шаблона письма необходим так же общий шаблон обрамления писем - app/views/layouts/mail.phtml.

В параметре send_from допускается явно указывать E-Mail, а также указывать имя поля формы, значение которого следует подставлять в качестве адреса отправителя. Последнее позволяет отправлять письмо как бы от посетителя сайта, который заполнил форму и указал свой E-Mail.

Отправка письма посетителю

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

 # Поле формы, в котором посетитель указывает свой E-Mail
 send_replay_to = email

 # Тема письма
 send_replay_subject = Ваш заказ принят

 # Шаблон письма (если не указан, то будет использован "send")
 send_replay_template = send-replay

 # Адрес отправителя
 send_replay_from = shop@mysite.com

Адрес отправителя лучше держать в настройках:

 # Адрес отправителя
 send_replay_from = var:myemail

Сохранение данных в cookie

Иногда значение части полей требуется сохранять чтобы избежать их повторного набора. Например, контактные данные клиента. Для этого служит параметр поля "cookie", значение которого является именем переменной cookie.

 fields = {

        .....

        name = {
                caption = Ваше имя
                cookie = client_name
        }

        .....
 }

Защита от роботов

Для защиты от роботов применяется графический цифровой код. Для его добавления в форму достаточно создать поле типа "protect" и с именем "protect":

 fields = {

        .....

        protect = {
                caption = Контрольный код
                type = protect
        }

        .....
 }

В случае несовпадения введенного кода с показанным будет отображено сообщение об ошибке "Неправильный цифровой код". Если требуется другое сообщение, то его можно указать в параметре "error_message" этого поля.

Для использования "невидимой" защиты от роботов необходимо установить параметр hidden=true

 fields = {

        .....

        protect = {
                type = protect
                hidden = true
        }

        .....
 }

Валидация введенных данных

Валидация настраивается аналогично валидации CMS.Fields

Пример:

 fields = {

        index = {
                caption = Почтовый индекс
                style = width:100px

                # Допускается ввод только цифр (но можно ничего не вводить)
                validate_match = /^\d*$/
                validate_match_message = Почтовый индекс неправильный
        }

        email = {
                caption = E-Mail
                style = width:300px

                # Обязательное наличие емейла
      		validate_presence = Введите E-Mail!
      		validate_email = E-Mail некорректный!
        }
 }

Формы с вариациями

А теперь представим себе, что в нашем магазине продаются не только молотки, но еще и лопаты. И на странице с лопатами необходимо сделать аналогичную форму, только в выборе типа должны присутствовать совсем другие варианты (для лопат).

Напрашивается решение: сделать аналогичным способом другую форму и на нужной странице ее отображать. Да, можно и так. Только у этого способа есть недостаток: заказы будут падать в разные списки (у каждой формы свой список), а хозяину сайта наверняка захочется смотреть общий список заказов, в котором будут вперемешку и молотки и лопаты.

Хочется иметь возможность отобразить одну форму, но разными способами. Например так:

 %form{order.hammer}
 %form{order.shovel}

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

Во-первых, давайте сделаем так, чтобы заголовок формы менялся в зависимости от того, как она вызывается:

 header = Заказать
 in order.hammer header = Заказать молоток
 in order.shovel header = Заказать лопату

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

Во-вторых, давайте добавим hidden-поле, значением которого будет "Молоток" или "Лопата". Почему hidden? А потому, что если клиент находится на странице "Молотки", то и форма будет о молотках - видимое поле там явно не к месту. А вот в админе нужно видеть что, собственно, заказано.

 fields = {

        .....

        item = {
                type = hidden
                value = unknown
                in order.hammer value = Молоток
                in order.shovel value = Лопата
                in admin = {
                        caption = Товар
                        type = input
                        style = width:500px;
                }
        }

        .....
}

Что мы здесь видим? Поле типа hidden с тремя вариантами значений (два явных и один по умолчанию). Кроме этого следует понимать, что в админе поле должно быть вовсе не hidden. Поэтому для админа указан тип input, а также параметры caption и style, которые для hidden-поля смысла не имеют.

В-третьих, нам нужно по другому описать поле выбора типа, дабы получить разные списки вариантов:

 fields = {

        .....

        itemtype = {
                type = select
                caption = Тип

                in order.hammer items = {
                        1 = Столярный
                        2 = Слесарный
                        3 = Кузнечная кувалда
                        4 = Деревянная киянка
                        5 = Молоток невропатолога
                }

                in order.shovel items = {
                        1 = Совковая
                        2 = Штыковая
                        3 = Снегоуборочная
                        4 = Саперная
                }

                in admin = {
                        type = input
                        style = width:500px;
                }
        }

        .....
 }

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

Есть еще одна возможность: указать, что поле будет создано только для одного из вариантов формы. Для этого служит параметр "in". Например, при заказе лопаты мы просим указать еще и длину черенка:

 fields = {

        .....

        length = {
                caption = Длина черенка, см
                in = order.shovel
        }

        .....
 }

Пользовательские шаблоны

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

Для отображения формы используются четыре шаблона:

  1. layout.phtml. Это шаблон самого верхнего уровня. Его не рекомендуется переопределять.
  2. inner.phtml. Шаблон отображает заголовок формы, начало и конец формы, а также служебные поля.
  3. fields.phtml. Шаблон отображает основные поля формы.
  4. errors.phtml. Шаблон для вывода ошибок содержимого формы(при серверной валидации).

Для переопределения любого шаблона отдельной формы, необходимо создать одноименный шаблон в папке с названием формы в директории шаблонов компонента. Например, для переопределения шаблона inner.phtml формы messages необходимо создать шаблон app/components/Forms/views/messages/inner.phtml. Так же можно изменить шаблон для определенной вариации формы. При этом название папки с шаблоном должно быть вида имяформы_вариация. Например для формы messages.feedback, шаблоны должны находится в папке app/components/Forms/views/messages_feedback. Следует помнить, что пути к шаблонам кэшируются и после создания или удаления шаблона следует очищать кэш.

Для изменения отображения отдельных полей можно воспользоваться опцией template в настройках поля. Так же можно использовать другие опции полей.

Хитрая валидация

Иногда требуется сделать валидацию сразу по нескольким полям. Например должен быть введен либо телефон, либо емейл, либо оба сразу (но что-нибудь одно - обязательно). Или вообще какие-нибудь дикие условия: пользователям, у которых емейл в домене mail.ru, по пятницам лопаты не продавать! Очевидно, что обычная валидация здесь не подойдет.

Что делать? Придется отказаться от встроенной валидации и писать свою. Стандартный скрипт валидации находится тут: tao/files/scripts/validation.js, его можно использовать как основу для своей валидации, изменив логику где необходимо. Для внедрения своей валидации применяется механизм Events, подробнее о котором можно прочитать здесь. Порядок подключения своей валидации следующий:

  • 1) Создаем js-файл с валидацией, например, в /app/component/Forms/scripts/validation.js
  • 2) Подписываемся на событие
Events::add_listener("cms.fields.{form_name}.validation.js", 'Component_Forms::on_{form_name}_validation');

, где {form_name} - имя формы.

  • 3) В обработчике события аргументом функции является путь к файлу валидации, изменяем его на путь к нашему файлу
static function on_{form_name}_validation(&$js_validation)
{
    $js_validation = CMS::component_static_path('scripts/validation.js', 'Forms');
}

Валидацию на стороне сервера следует добавить в контроллер app/components/Forms/Controller.php. Напишите метод контроллера на примере следующих:

        # Валидация всех форм
        protected function form_errors($form) {}

        # Валидация только формы order
        protected function form_errors_order($form) {}

        # Валидация только формы order.shovel
        protected function form_errors_order_shovel($form) {}

В метод передается объект-форма после выполнения $form->process(). Данный метод должен возвращать:

  • Строку с сообщением об ошибке (если найдена одна ошибка)
  • Массив сообщений или объект-итератор (если найдено несколько ошибок)
  • False если ошибок не найдено.

Программные расширения

Одно программное расширение мы уже рассмотрели на примере валидации формы (метод form_errors). Существуют и другие аналогичные методы:

// Вызывается перед записью объекта в БД.
// Параметр - объект типа Component_Forms_EntityPost.
// В этом методе удобно делать всякого рода модификации данных перед записью.
protected function on_before_insert($post) {}

// Вызывается после записи объекта в БД.
protected function on_after_insert($post) {}

// Создает объект Mail::Message(), готовый к отправке (только с незаполненным полем To).
protected function create_email($post) {}

Напоминаю, что каждый метод можно записать в виде <имя метода>_<идентификатор формы> чтобы конкретизировать для каких форм требуется запуск той или иной функциональности.

Интеграция с другими контроллерами

Установка значений полей и заголовка формы

Допустим, у вас есть каталог товаров. Требуется на страницу с описанием конкретной модели поместить форму заказа этой модели. Часто отрисовку и обработку формы заказа в данном случае делают частью механизма каталога (т.е. каталог и магазин в одном флаконе). Но это можно сделать и с помощью Component.Forms. Задача состоит только в том, чтобы организовать в форме hidden-поле с указанием - что, собственно, заказывается. Можно также менять и заголовок формы.

Делается это так. В контроллере каталога устанавливаем соответствующие значения:

Component_Forms::$vars['order_header'] = "Заказать cканер Fujitsu ScanSnap S300";
Component_Forms::$vars['order_item'] = "Сканер Fujitsu ScanSnap S300";

В параметрах формы пишем следующим образом:

 header = {order_header}

 fields = {

        .....

        item = {
                type = hidden
                value = {order_item}
                ....
        }

        .....
 }

Запись значения параметра в фигурных скобках (в одну строку) означает, что это не константа, а значение из массива Component_Forms::$vars. После этого нам только остается в шаблон соответствующей страницы поместить вставку - %form{order}

Создание новой формы

Случается, что компоненту требуется определенная форма(например, компоненту каталог может требоваться простейшая форма заказа). Если форма простая по своему поведению и не требуются сложные выборки по пришедшим сообщениям, то целесообразно использовать компонент Forms для создания и отображения этой формы. Создавать её вручную каждый раз при установке нашего компонента очень неудобно, поэтому компонент Forms предоставляет возможность создавать необходимые формы сторонним компонентам. Для этой цели используется функция

CMS::objects()->forms->create($form_name, $form_data)
  • $form_name - идентификатор формы
  • $form_data - параметры формы в виде массива, структура аналогична стандартной. Чтобы задать название формы, используем параметр title. Пример:
   $form_data = array(
        'title' => 'Форма заказа',
	'header' => 'Заказать',
	'ok' => '/order-ok.htm',
	'submit' => 'Заказать',
	'fields' => array(
		'itemtype' => array(
			'type' => 'select',
			'caption' => 'Тип',
			'items' => array(
				'Столярный',
				'Слесарный',
				'Кузнечная кувалда',
				'Деревянная киянка',
				'Молоток невропатолога'
			)
		),
		'name' => array(
			'caption' => 'Ваше имя',
			'style' => 'width:100%',
			'value' => '{order_item}'
		),
		'email' => array(
			'caption' => 'E-Mail',
			'style' => 'width:300px',
			'validate_presence' => 'Введите E-Mail!',
			'validate_email' => 'E-Mail некорректный!'
		),
		'phone' => array(
			'caption' => 'Телефон',
			'style' => 'width:500px'
		),
		'notes' => array(
			'caption' => 'Дополнительные пожелания',
			'type' => 'textarea',
			'style' => 'width:100%;height:200px'
		),
	),
	'admin_list' => array(
	   'itemtype' => 'Тип',
	   'name' => 'Имя клиента',
	   'email' => 'E-Mail'
	),
    );

Ajax-формы

Часто возникает необходимость при отправке данных формы оставаться на текущей странице без её перезагрузки. Для решения этой проблемы с версии 4.1 появилась возможность использовать ajax-формы. Их вставка происходит через insertion %ajax_form{form_name}. При отправке данных формы возможны следующие сценарии:

  1. Ошибка в данных, обнаруженная js-валидацией. В этом случае появляется стандартный alert с текстом ошибки.
  2. Ошибка в данных, которая обнаруживается серверной валидацией. В этом случае текст ошибки отобразится выше формы.
  3. В данных нет ошибок, обращение записывается в БД. В этом случае вместо формы отобразится сообщение об отправке данных, взятое из параметра ajax_ok настроек формы. В случае отсутствия параметра отобразится сообщение по умолчанию.

Полезные возможности

Placeholder

Часто дизайнеры рисуют форму, у которых заголовки полей находятся не в label, а внутри самого поля, и при вводе значения должны исчезать. Специально для реализации этого функционала в html5 ввели атрибут placeholder. Его поддерживают input(с типом text,password и тд) и textarea. Как его использовать в нашем компоненте? Добавим в настройки поля этот атрибут:

 name = {
     placeholder = Введите имя
 }

В html-коде у нас будет

<input type="text" placeholder="Введите имя" id="messages_name" value="" name="messages[name]">

И все было бы хорошо если бы не internet explorer - он не поддерживает этот атрибут даже в 9 версии. В компоненте его поддержка в IE реализована через яваскрипт.

Не все гладко и в остальных браузерах - тут возникают некоторые сложности при стилизации placeholder. FF, Chrome и IE10 ввели поддержку собственных псеводэлементов -webkit-input-placeholder, -moz-placeholder и -ms-input-placeholder, но Opera пока этого не сделала. Так же стоит учитывать, что стили, у которых прописан псевдоэлемент, можно описывать в css только по отдельности! Поэтому в стилях по умолчанию у нас три строки:

.form-placeholder { color: #ccc; } /* Для IE */
div.form [placeholder]::-moz-placeholder { opacity: 1; color: #ccc; } /* Для FF */
div.form [placeholder]::-webkit-input-placeholder { color: #ccc; } /* Для Chrome, Safari */
div.form [placeholder]:-ms-input-placeholder { color: #ccc; } /* Для IE10 */

То есть для изменения стиля placeholder, править его придется во всех трех местах, но в Opera он все равно будет отображаться по умолчанию.

Приложение A. Запись параметров формы и полей

  • Если параметр записан в виде "параметр = {идентификатор}", то его значение берется из массива Component_Forms::$vars.
  • Если параметр записан в виде "параметр = var:идентификатор", то его значение берется из настроек (CMS_Vars).
  • Если параметр записан в виде "параметр = идентификатор", где "идентификатор" - идентификатор одного из полей формы, то значение параметра берется из данных формы. Имеет смысл только для параметров, используемых при обработке поста. Например, "send_from" - E-Mail отправителя.

Во всех остальных случаях считается, что значение параметра - константа.

Приложение B. Типы полей

В веб-формах применимы большинство полей CMS.Fields. При необходимости можно создать свой тип поля, подробности описаны в статье CMS.Fields: создание произвольного типа поля

Все версии

Обращаем ваше внимание, что установка и обновление компонентов штатным способом осуществляется из системы управления вашим сайтом ("Библиотека компонентов"). Данный раздел несет прежде всего информационную функцию. Однако, вы можете скачивать инсталляционные пакеты компонентов в целях ознакомления или в случае если функционирование библиотеки компонентов по какой-то причине затруднено.


Версия Дата
4.2.11 11.12.2015
4.2.10 26.08.2015
4.2.9 08.12.2014
4.2.8 02.12.2014
4.2.7 12.11.2014
4.2.6 12.11.2014
4.2.5 24.04.2014
4.2.4 30.01.2014
4.2.3 23.01.2014
4.2.2 23.01.2014
4.2.1 10.01.2014
4.2.0 10.01.2014
4.1.15 10.01.2014
4.1.14 30.12.2013
4.1.13
Изменения
Реализованы методы CMS::objects()->forms->exists($form_name) CMS::objects()->forms->create($form_name, $data)
08.11.2013
4.1.12 01.11.2013
4.1.11
Изменения
Исправлена ошибка рендера ajax-формы
18.10.2013
4.1.10 17.10.2013
4.1.9 17.10.2013
4.1.8 17.10.2013
4.1.7
Изменения
Исправлена ошибка поиска шаблонов
15.10.2013
4.1.6
Изменения
Удалены ненужные константы MODULE и VERSION
07.10.2013
4.1.5 03.10.2013