Блог


05 мая 2010

Контроллер и валидация форм

Мы не сильно ограничивали контроллер и не пытались "изобрести велосипед", когда реализовывали этот слой логики системы. Основная задача контроллера - это дать разработчику возможность реализовать все то, что он не может сделать через API. Взять, например, реализацию API форм. 

Формы редко существуют без механизма проверки данных, которые через них передаются. У newt:form есть несколько примитивных встроенных проверок: required у полей question, а также проверка на тип данных, которая реализована совместно с объектным хранилищем. Разработчику должна быть предоставлена возможность накладывать произвольные проверки на все данные под все требования бизнес-логики приложения. Рассмотрим на примере, как это реализовать. Попутно обратите внимание, что подобным методом можно не только проверять, но и модифицировать данные.

XML

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

Форма отправки резюме:

Copy Source | Copy HTML
<?xml version="1.0" encoding="utf-8"?>
 
<root xmlns:newt="http://adv.ru/newt" xmlns:html="http://adv.ru/html">
 
    <!-- Controller for validation and getting region email. -->
     <!-- We have to pass handler if we want to run SQL inside the controller. -->
     <newt:include match="query[_forward and regionhr_id[not(@value = '')]]">
        <newt:action function="getEMail.regionhr" var="regionemail">
            <arg value=""/>
            <arg value="" />
        </newt:action>
    </newt:include>
 
    <newt:form lang="ru" id="send-resume"
        add-query="regionemail=&amp;filesize=">
        <form id="send-resume" action="" method="post" last="yes" enctype="application/x-www-form-urlencoded">
 
        <!-- Process actions only in case of correct query data. -->
         <newt:include match="query[_forward and filesize/@value &lt;= 102400 and regionemail[not(@value = '')]]">
        <actions hide="yes">
            <action type="mail-to" on-error="Cannot send letter">
                 <param name="from" value="email@example.com"/>
                <param name="to" value="" />
                <param name="subject" value="Резюме на вакансию"/>
                <param name="xslt" value="send-resume"/>
                <newt:http-env query="yes" />
            </action>
        </actions>
        </newt:include>
 
        <!-- Inserting some error marks in case of mistakes. -->
         <newt:include match="query[_forward and filesize/@value &gt; 102400]">
            <error>Приложеный файл слишком велик.</error>
        </newt:include>
 
        <newt:include match="query[_forward and regionemail[not(@value = '')]]">
            <error>Невозможно отправить e-mail.</error>
        </newt:include>
 
        <newt:include match="query[_forward and filesize/@value = '']">
            <error>Нет файла, приложите файл резюме.</error>
        </newt:include>
 
            <!-- Don't pay attention to this. -->
             <question name="regionhr" id="regionhr" type="select">
                <label>Регион:</label>
                <newt:transform template="transform-base-to-answer">
                    <newt:base id="regionhr" add-query="get-regionhr"
                        query-filter="regionhr_id" request="request-career" />
                <newt:http-env query="yes" />
                </newt:transform>
            </question>
 
            <question name="vacancy" id="vacancy" type="text" required="yes"
                on-error="Это поле должно быть заполнено">
                <label>Название вакансии:</label>
                <answer />
            </question>
 
            <question name="fio" id="fio" type="text" required="yes"
                on-error="Это поле должно быть заполнено">
                <label>ФИО:</label>
                <answer />
            </question>
            <question name="phone" id="phone" type="text">
                <label>Контактный телефон:</label>
                <answer />
            </question>
            <question name="email" id="email" type="text">
                <label>E-mail:</label>
                <answer />
            </question>
 
            <question name="resumefile" required="yes" id="resumefile" type="file">
                <label><span>Приложить</span> резюме (файл не более 100kB):</label>
                <answer />
            </question>
 
            <!-- CAPTCHA -->
             <question name="mcaptcha" id="mcaptcha" type="text" required="yes"
                on-error="Введены не верные символы">
                <label>Введите символы, изображенные на картинке:</label>
                           <answer value="" />
            </question>
        </form>
    </newt:form>
 
</root>
 

Задачей контроллера является получение e-mail адреса из базы по ID региона, который пришел из формы (попутно можно дополнительные проверки встроить в сам контроллер). Результат кладется в request scope (var="regionemail"), которые через метод add-query затем передается внутрь newt:form:

Copy Source | Copy HTML
add-query="regionemail=&amp;filesize="

Далее, эти данные в форме доступны как обычные данные из query (query/filesize и query/regionemail). Если вам нужно внутри формы подставить часть этих данных в поля, можно воспользоваться возможностями EL, а именно (отправим письмо на адрес, полученный в контроллере):

Copy Source | Copy HTML
<param name="to" value="" />

Задача же проверки данных формы облегчается тем, что нам в примере надо сделать лишь корректную проверку прилагаемого файла-резюме, а для этого у нас уже есть все данные, доступные в специальном объекте query. Поэтому специальный контроллер писать нет необходимости. При помощи и проверок параметров query методами XPath на их корректность мы управляем структурой формы.

Проверка размера файла, добавляем ошибку для отображения:

Copy Source | Copy HTML
<newt:include match="query[_forward and filesize/@value &gt; 102400]">
    <error>Приложеный файл слишком велик.</error>
</newt:include>

Обобщаем все проверки, чтобы избежать сохранения формы в случае наличия ошибки:

Copy Source | Copy HTML
<newt:include match="query[_forward and filesize/@value &lt;= 102400 and regionemail[not(@value = '')]]">
    <actions>
        ...
    </actions>
</newt:include>

Contoller

Сам вызываемый функционал контроллера может содержать как проверку данных (пришедших через специальный объект query), так и просто выборку. В нашем примере это небольшая функция, написанная на языке Groovy.

Пример передачи данных в контроллер:

Copy Source | Copy HTML
<arg value="" />

getEmail.groovy - выбираем данные для формы:

Copy Source | Copy HTML
// We have to find correct e-mail for resume sending.
def regionhr(dbHandler,id) {
    id = new Long(id)
    def row = dbHandler.firstRow("select email from regionhr where id=$id");
    return (row != null) ? row[0] : '';
}

XSLT

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

Copy Source | Copy HTML
<?xml version="1.0" encoding="windows-1251"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
 
    <xsl:import href="default.xsl"/>
 
    ....
 
    <xsl:template match="form">
        <xsl:choose>
            <xsl:when test="$query/_forward and (//error or //question[@error])">
                <xsl:call-template name="form"></xsl:call-template>
            </xsl:when>
            <xsl:when test="$query/_forward and not(//error)">
                <p>Ваше резюме отправлено в кадровую службу в регионе.</p>
            </xsl:when>
            <xsl:otherwise>
                <xsl:call-template name="form"></xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
 
    <xsl:template match="error">
            <div class="red">
                <xsl:value-of select="text()" /></div>
    </xsl:template>
 
    <xsl:template name="form">
        <form method="{@method}" action="{@action}" enctype="{@enctype}" class="{@class}" id="{@id}">
        <div class="bgBot">
        <div class="bgTop">
            <h4>Отправьте свое резюме</h4>
            <xsl:apply-templates />
            <p><br />
                <xsl:call-template name="required-text" /></p>
            <xsl:call-template name="submit-button-forward"/>
        </div>
        </div>
        </form>
    </xsl:template>
 
    ...

Комментарии

подождите ..