Predvoditelev.RU

MODx: Делаем удобную форму обратной связи

30 Октябрь 2008, 12:37

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

Сейчас я делаю сайты на CMS MODx и соответсвенно форму обратной связи буду писать для неё. Что необходимо получить:

  • поля проверяются «на лету» (по мере заполнения пользователем формы);
  • использование каптчи;
  • возможность отправить сообщение, только когда все поля заполнены верно.

Для работы с формой воспользуемся сниппетом eForm, для проверки полей на лету будем использовать JavaScript. Итак, приступим.

Для начала создадим новый документ, в котором у нас будет отображаться форма обратной связи. Назовем его «Обратная связь» и в содержимое запишем следующее:

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

[!nikoFeedBack]]

Последняя строчка — некэшируемый вызов сниппета nikoFeedBack, который и отвечает за вывод нашей формы.

Теперь создаем новый сниппет: название — «nikoFeedBack», описание — «Форма обратной связи».

Код сниппета:

<?php
function fbValidateName($value) { return strlen($value)>=5; }
function fbValidateText($value) { return strlen($value)>=15; }

$modx->regClientStartupScript($modx->config['site_url'].'assets/snippets/nikoFeedBack/validate.js');

$outForm = $modx->runSnippet(
        "eForm",
        array(
            "formid" => "FeedBackForm",
            "to" => "admin@site.ru",
            "tpl" => "eFBForm",
            "report" => "eFBReport",
            "thankyou" => "eFBThanks",
            "from" => "[+fbEMail+]",
            "fromname" => "site.ru",
            "subject" => "Посетители сайта пишут",
            "vericode" => "1"
        )
);

echo $outForm;
?>

В начале сниппета задается две функции: fbValidateName и fbValidateText. Они отвечают за проверку корректности введенных данных полей «Имя» и «Текст сообщения». Откуда эти функции вызываются — опишу позднее.

Затем с помощью стандартной функции в MODx regClientStartupScript добавляем скрипт validate.js (в нем будут храниться скрипты, отвечающие за проверку введенных значений на лету) для загрузки внутрь тега <head>.

Далее вызывается сниппет eForm, который используется для работы с формами в MODx. При вызове сниппета передаем ему следующие параметры:

  • formid – Идентификатор формы, который используется при отправке данных («FeedBackForm»).
  • to - Адрес Email на который будет послана информация. Можно использовать несколько адресов разделяя их запятой (,). Если не указано, то будет использован системный адрес. В данном случае информация будет отправлена на адрес admin@site.ru.
  • tpl - Название чанка (не число) или идентификатор документа (число) для используемого шаблона. Наш чанк с формой будет называться «eFBForm».
  • report - Название чанка (не число) или идентификатор документа (число) для шаблона отчета. Это шаблон для письма, которое будет отправлено с сайта к нам на почту, у нас он называется «eFBReport».
  • thankyou - Название чанка (не число) или идентификатор документа (число) для используемого шаблона сообщения выводимого пользователю после отправки данных формы. Наш чанк с благодарностью пользователю за оставленное сообщение будет называться «eFBThanks».
  • from - Устанавливает адрес отправителя письма. В качестве адреса устанавливаем плейсхолдер [+fbEMail+], который будет содержать адрес электронной почты пользователя.
  • fromname - Устанавливает имя отправителя письма. Пишем название нашего сайта, чтобы видеть сразу откуда письмо.
  • subject - Тема письма. Для примера поставим «Посетители сайта пишут».
  • vericode - Включает использование каптчи в форме.

Перейдем к чанкам. Для начала создадим чанк eFBForm, содержащий форму.

<p>Пишите сообщения и не сомневайтесь - вы будете услышаны!</p>

<div class="fbForm">
   
    <p><span style="color:#900;">[+validationmessage+]</span></p>
   
    <form method="post" action="[~[*id*]~]" id="FeedBackForm">
       
        <div class="form">
           
            <div class="name">Ваше имя и фамилия</div>
            <div class="error" id="fbNameError"></div>
            <div class="element"><input type="text" name="fbName" id="fbName" eform="Имя::1:Должно содержать не менее 5 символов.:#FUNCTION fbValidateName" value="[+fbName+]"></div>
           
            <div class="name">Адрес электронной почты</div>
            <div class="error" id="fbEMailError"></div>
            <div class="element"><input type="text" name="fbEMail" id="fbEMail" eform="EMail:email:1:EMail &raquo; Некорректный адрес электронной почты." value="[+fbEMail+]"></div>
           
            <div class="name">Текст сообщения</div>
            <div class="error" id="fbTextError"></div>
            <div class="element"><textarea name="fbText" id="fbText" eform="Текст сообщения:html:1:Должен содержать не менее 15 символов.:#FUNCTION fbValidateText"  cols="40" rows="5">[+fbText+]</textarea></div>
           
            <table cellpadding="0" cellspacing="0" class="captcha-test">
                <tr>
                    <td>
                        <div class="name">Защита от спама</div>
                        <div class="captcha"><img src="[+verimageurl+]" alt="Проверочный код" border="1"></div>
                    </td>
                    <td>
                        <div class="name">Код на картинке:</div>
                        <div class="element"><input type="text" name="vericode" id="vericode" value=""></div>
                    </td>
                </tr>
            </table>
           
            <div class="submit"><input type="submit" name="fbSubmit" id="fbSubmit" value="Отправить"></div>
           
        </div>
       
    </form>

</div>

[+validationmessage+] - этот плэйсхолдер выводит ошибки, если данные, отправленные на сервер, были некорректными или отправка сообщения не удалась.

[+verimageurl+] - плэйсхолдер содержит адрес картинки с каптчей. Для проверки каптчи — необходимо использовать INPUT c именем «vericode».

Пустые слои с идентификаторами <имя поля>Error — используются для вывода ошибок при заполнении полей, когда проверка данных проводится «на лету».

Как вы заметили поля формы имеют дополнительный параметр efrom. Он используется для настройки встроенного в eForm анализатора, определяющего формат и тип данных с соответствующей проверкой каждого поля. Базовый вид параметра eform следующий:

[description/title]:[datatype]:[required]:[validation message]:[validation rule]

description/title — Название (описание) поля.
datatype — Тип данных.
required – Обязательно ли заполнение этого поля.
validation message — Сообщение при неудачной проверке значения поля.
validation rule — Правило проверки значения поля.

Вот здесь мы и используем функции fbValidateName и fbValidateText, описанные в сниппете nikoFeedBack, для проверки корректности значений полей «Имя» и «Текст сообщения».

Стили используемые в форме:

* { padding : 0px; margin : 0px; border: none; }
BODY {
        color: #252525;
        font-family: Tahoma, Arial, Helvetica, sans-serif;
        font-size: 10pt;
}
.fbForm .form { padding-left: 40px; }
.fbForm .name { padding-top: 12px; font-size: 8pt; color: #7d7d7d; }
.fbForm .element { padding-top: 2px; }
.fbForm .element INPUT, .fbForm .element TEXTAREA {
        border: 1px solid #ccc;
        background-color: #E6EFF6;
        font-size: 10pt;       
        color: #2C5883;
}
.fbForm INPUT#fbName,
.fbForm INPUT#fbEMail {
        background-image: url(images/icon_form.gif);
        background-repeat: no-repeat;
        width: 250px;
        padding: 4px 0px 4px 25px;
}
.fbForm INPUT#fbName    { background-position:3px -3px; }
.fbForm INPUT#fbEMail { background-position:3px -33px; }
.fbForm TEXTAREA#fbText {
        padding: 4px 4px 4px 25px;
        width: 425px;
        font-family: Tahoma;
        background: #E6EFF6 url(images/icon_form.gif) no-repeat 3px -93px;
}
.fbForm .captcha-test TD { vertical-align: top; padding-right: 15px; }
.fbForm .captcha { padding-top: 3px; }
.fbForm .captcha IMG { border: 1px solid #ccc; }
.fbForm INPUT#vericode { width: 130px; padding: 4px 0px 4px 25px; background: #E6EFF6 url(images/icon_form.gif) no-repeat 3px -63px; }
.fbForm .submit { padding-top: 15px; }
.fbForm INPUT#fbSubmit {
        border: 1px solid #ccc;
        background-color: #f7f7f7;
        cursor: pointer;
        padding: 2px 15px 2px 15px;
}
.fbForm .error { font-size: 8pt; color: red; display: none; }

Создадим чанк eFBThanks, который используется после успешной отправки сообщения. Тут всё просто:

<p>Cообщение отправлено.</p>
<p>Благодаря вашей помощи проект может стать лучше!</p>

Теперь чанк eFBReport, который является шаблоном для отправляемого письма:

<p>Поступило сообщение через обратную связь сайта <b>site.ru</b>.</p>
<p>
[+postdate+]
<br>
[+fbName+] ([+fbEMail+]) пишет:
</p>
----------------------------------------------
<p>
[+fbText+]
</p>
----------------------------------------------
<p>Для ответа можно использовать эту ссылку: <a href="mailto:[+fbEMail+]?subject=RE: site.ru">[+fbEMail+]</a></p>

В чанке используются следующие плейсхолдеры: [+postdate+] - дата отправки сообщения; [+fbName+], [+fbEMail+], [+fbText+] - соответствующие поля формы.

На этом основную часть нашей формы мы сделали :) Она уже вполне работоспособна. Корректность введенных данных проверяется на сервере. Теперь добавим в нашу форму немного динамичности. Что мы сделаем:

  • проверка корректности введенных значений на лету;
  • кнопка отправить будет доступна только когда все поля формы заполнены верно (за исключением каптчи — это проверяется на сервере всегда).

Создаем папку nikoFeedBack в папке assets/snippets/. Теперь в созданной папке создаем файл validate.js. Содержание файла следующее:

var fbNowShowError; // Выводить ли при проверки поля ошибки

function fbIsFormValid() {
       
        var formFeedBack = document.getElementById("FeedBackForm");
        var result = true;
       
        for (var i=0; i < formFeedBack.elements.length; i++) {
                if (typeof formFeedBack.elements[i].valid == "boolean") {
                        result = result && formFeedBack.elements[i].valid;
                }
        }
       
        return result;
       
}

function fbValidateField(txtField) {
       
        var fbNameError = document.getElementById("fbNameError");
        var fbEMailError = document.getElementById("fbEMailError");
        var fbTextError = document.getElementById("fbTextError");
       
        // Проверка имени
        if (txtField.name == "fbName") {
                if (txtField.value.length < 5) {
                        if (fbNowShowError) {
                                fbNameError.style.display = "block";
                                fbNameError.innerHTML = "Имя должно содержать не менее 5 символов";
                        }
                        txtField.valid = false;
                } else {
                        if (fbNowShowError) {
                                fbNameError.style.display = "none";
                        }
                        txtField.valid = true;
                }
        }
       
        // Проверка email
        if (txtField.name == "fbEMail") {
                var re = /^[\.\-_A-Za-z0-9]+?@[\.\-A-Za-z0-9]+?\.[A-Za-z0-9]{2,6}$/;
                if (!re.test(txtField.value)) {
                        if (fbNowShowError) {
                                fbEMailError.style.display = "block";
                                fbEMailError.innerHTML = "Некорректный адрес электронной почты";
                        }
                        txtField.valid = false;
                } else {
                        if (fbNowShowError) {
                                fbEMailError.style.display = "none";
                        }
                        txtField.valid = true;
                }
        }
       
        // Проверка текста сообщения
        if (txtField.name == "fbText") {
                if (txtField.value.length < 15) {
                        if (fbNowShowError) {
                                fbTextError.style.display = "block";
                                fbTextError.innerHTML = "Текст сообщения должен содержать не менее 15 символов";
                        }
                        txtField.valid = false;
                } else {
                        if (fbNowShowError) {
                                fbTextError.style.display = "none";
                        }
                        txtField.valid = true;
                }
        }
       
        var fbSubmit = document.getElementById("fbSubmit");
       
        if (fbIsFormValid()) {
                fbSubmit.disabled = false;
                fbSubmit.style.cursor = 'pointer';
        } else {
                fbSubmit.disabled = true;
                fbSubmit.style.cursor = 'default';
        }
       
}

function fbValidateFieldOnChange(oEvent) {
       
        fbNowShowError = true;
       
        oEvent = oEvent || window.event;
        var txtField = oEvent.target || oEvent.srcElement;
       
        fbValidateField(txtField);
}

function fbValidateFieldOnKeyUp(oEvent) {
       
        fbNowShowError = false;
       
        oEvent = oEvent || window.event;
        var txtField = oEvent.target || oEvent.srcElement;
       
        fbValidateField(txtField);
}

window.onload = function () {
       
        var fbName = document.getElementById("fbName");
        var fbEMail = document.getElementById("fbEMail");
        var fbText = document.getElementById("fbText");
        var fbVeriCode = document.getElementById("vericode");
        var fbSubmit = document.getElementById("fbSubmit");
       
        fbSubmit.disabled = true;
        fbSubmit.style.cursor = 'default';
       
        fbName.valid = false;
        fbEMail.valid = false;
        fbText.valid = false;
       
        fbName.onchange = fbValidateFieldOnChange;
        fbEMail.onchange = fbValidateFieldOnChange;
        fbText.onchange = fbValidateFieldOnChange;
       
        fbName.onkeyup = fbValidateFieldOnKeyUp;
        fbEMail.onkeyup = fbValidateFieldOnKeyUp;
        fbText.onkeyup = fbValidateFieldOnKeyUp;
       
        fbNowShowError = true;
        if (fbName.value != '') fbValidateField(fbName);
        if (fbEMail.value != '') fbValidateField(fbEMail);
        if (fbText.value != '') fbValidateField(fbText);
        fbNowShowError = false;
       
        fbVeriCode.value = '';
       
        fbValidateField;
       
};

Кратко опишу, что делает каждая их функций:

fbIsFormValid — проверяет все ли поля формы заполнены правильно.
fbValidateField — проверяет правильно ли заполнено поле. Автоматически определяется тип поля — и проводится соответствующая проверка. По окончании проверки поля проверяется — правильно ли заполнены остальные поля, и если правильно — то кнопка «Отправить» становится доступной, если не правильно — то недоступной.
fbValidateFieldOnChange — функция, вызываемая при изменении поля (после потери им фокуса). Сама функция — вызывает функцию проверки поля.
fbValidateFieldOnKeyUp — функция, вызываемая каждый раз при изменении поля. Сама функция — вызывает функцию проверки поля.

Разные функции на события OnChange и OnKeyUp вызываются для того, чтобы задать - выдавать ли ошибку при проверке или нет. То есть, когда объект не потерял фокус, но произошло его изменение (событие OnKeyUp) — производится проверка данного поля и всех остальных полей для того, чтобы в случае правильности заполнения сделать активной кнопку «Отправить», но при этом ошибки не выводятся, либо продолжают показываться. В случае же когда поле теряет фокус (событие OnChange) помимо проверки — показывать или нет кнопку «Отправить» - также показывается сообщение об ошибке в случае некорректных данных или исчезает ошибка, если данные введены верно. Довольно запутанно, но это необходимо, например, для того, чтобы при первоначальном заполнении полей — сразу не выскакивали ошибки (если разделения не будет, то как только мы начнем вводить к примеру Имя, то сразу вылезет ошибка).

В конце с помощью конструкции window.onload = function () { … } после загрузки страницы задаем обработчики событий для полей и сразу проверяем все поля на валидность. Плюс данного способа в том, что форма остаётся рабочей даже в том случае, если у пользователя отключен JavaScript.

Всё! Форма обратной связи с проверкой данных «на лету» готова. Пользуйтесь :).

В приложенном архиве лежит файл стилей, изображения, используемые для оформления, файлы с чанками eFBForm, eFBReport, eFBThanks, файл со сниппетом nikoFeedBack и файл с JS-скриптами validate.js.

Приложение к статье (5.4 kb).

Добавить комментарий

7 responses for this post

  1. Zodios Says:

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

  2. PhiNik Says:

    Да, если была возможность отсылать сообщение не на почту, а прямо в админку было бы интересно. Может, есть у кого какие наработки по данному вопросу?
    А по сниппету могу сказать что сложно реализовано как-то чересчур сложно, почему просто не воспользоваться стандартным построением формы с помощью eForm и не создавать новые сниппеты?

  3. Предводителев Сергей Says:

    Хорошая идея - смотреть сообщения в кмс, а не отправлять на почту! Надо будет подумать :)

    PhiNik, не пользуемся стандартным построением формы потому, что необходимо использовать свои функции для проверки полей (fbValidateName и fbValidateText) и из сниппета подключаем яваскрипт.

  4. Предводителев Сергей Says:

    Для того, чтобы отправлять сообщения в кмс, нужно внести немного изменений в сниппет nikoFeedBack.

    Добавить функцию:

    function fbSubmitMessage($fields) {
    global $modx;

    $txt = $modx->getChunk(’eFBReport’);
    foreach ($fields as $key => $val) {
    $txt = str_replace(’[+'.$key.'+]‘,$val,$txt);
    }

    $modx->sendAlert(’alert’,'admin’,1,$fields[subject].’ (’.$fields[fbName].’)',$txt,1);

    return true;
    }

    и добавить параметры noemail=1 (чтобы на почту не отправлялось) и eFormOnMailSent=fbSubmitMessage (чтобы запустилась наша функция отправки в кмс) при вызове eForm, те в итоге вот такой вызов:

    $outForm = $modx->runSnippet(
    “eForm”,
    array(
    “formid” => “FeedBackForm”,
    “to” => “admin@site.ru”,
    “tpl” => “eFBForm”,
    “report” => “eFBReport”,
    “thankyou” => “eFBThanks”,
    “from” => “[+fbEMail+]“,
    “fromname” => “site.ru”,
    “subject” => “Посетители сайта пишут”,
    “vericode” => “1″,
    “eFormOnMailSent” => “fbSubmitMessage”,
    “noemail” => “1″
    )
    );

    Всё просто. MODx рулит )))

  5. stas Says:

    А не знаете, существует ли решение постить комментарии с авторизацией через OpenID? Я нашел одно, но оно отказывается у меня работать из-за ограничений на хостинге Мастерхоста.

  6. Предводителев Сергей Says:

    Планирую на одном из своих сайтов сделать комментирование… Если найду какое-то решение - напишу :)

  7. Алексей Морозов Says:

    Большое спасибо, за статью. Я сам болею modx ом, а тут еще и то что нужно для работы. Спасибо еще раз, мне очень помогло.

Leave a Reply