Пример программирования валидации на CoffeeScript.
CoffeeScript приятен тем, что множество даже сложных задач с ним можно делать быстро и легко. Получается симпатичный удобочитаемый код, который достаточно легко отлаживать.
Рассмотрим следующий код, реализующий класс валидации. Некоторые методы используют jQuery.
Каждый из методов этого класса может быть использован сам по себе.
Метод find позволяет найти указанное правило в строке определения правил валидации, написанной в духе CodeIgniter. В конкретном проекте, где в качестве бекенда выступил CI, мне было удобно непосредственно экспортировать объект правил в клиент.
Метод validate, принимающий список полей и правил, для каждого элемента формы выполнит очистку данных и проверку соответствия правилам. В случае успешного прохождения валидации метод вернет объект с чистыми данными. В случае обнаружения ошибок метод вызывет брата show_errors() и вернут false. Данные и ошибки сохраняются, как свойства класса. Поэтому они доступны для дальнейшего использования, и по этой же причине они очищаются перед началом.
Метод show_errors вставит сообщения об ошибках рядом с полями формы, и установит фокус (курсор) в первое поле, содержащее ошибку. Так пользователь может сразу отредактировать некорректные данные, без необходимости лишний раз тянуться за мышью и щелкать.
Метод lang найдет подходящее сообщение об ошибке, вставит в него значение из правила, если оно есть, и вернет строку.
Метод rule_value вернет значение из правила. Сделано на скорую руку, наверное можно сделать значительно красивее посредством регулярных выражений. Если у вас есть время и понимание – напишите в ЛС или комментарии, проверим и обновим код.
Методы min_length, max_length, valid_email, required, min_value, max_value, numeric и alpha_numeric проводят проверку с соответствующими правилами. Вы легко можете добавить методы.
Здесь валидация не только проверяет данные, но и очищает их. В данном коде это делают методы trim и integer.
Элементарный метод parse_rules принимает строку правил, отформатированную в духе CodeIgniter, и преобразует ее в массив.
Метод validate_field, принимает для обработки имя поля, строку и правила, производит обработку строки и проверку на соотвествие правилам, после чего помещает ее в локальный объект данных для последующего использования.
Метод set_error просто запоминает ошибку.
Теперь версия кода с построчными комментариями.
Используются сообщения об ошибках, хранящиеся в объекте, как показано ниже.
Для обработки сообщений об ошибках используется популярный минималистичный JS-шаблонизатор, вот его код, портированный на CoffeeScript
Частая внутрифреймворковая задача — безопасить кавычки.
Допустим, есть такие поля в форме.
Тогда мы можем задать правила для валидации и вызвать обработку по сабмиту формы.
Получается минималистичный, гибкий и даже красивый код.
CoffeeScript приятен тем, что множество даже сложных задач с ним можно делать быстро и легко. Получается симпатичный удобочитаемый код, который достаточно легко отлаживать.
Рассмотрим следующий код, реализующий класс валидации. Некоторые методы используют jQuery.
class validation
find: (rules, rule) ->
rules = rules.split '|'
(rule in rules)
validate: (rules) ->
$('.form_error').remove()
delete @errors_list
@fields = {}
@data = {}
for field, rule of rules
@fields[field] = $("input[name=\"#{field}\"]");
@data[field] = @fields[field].val()
@validate_field field, @data[field], rules_string
unless @errors_list?
@data
else
@show_errors()
false
show_errors: ->
focus_set = false
for field, errors of @errors_list
messages = []
for error in errors
messages.push @lang error
@fields[field].before nano templates.form_error, {message: messages.join(' ')}
unless focus_set
@fields[field].focus()
focus_set = true
lang: (error) ->
[rule,value] = @rule_value error
if value?
nano lang.errors[rule], {val: value})
else
lang.errors[rule]
rule_value: (rule)->
x = rule.split '['
if x[1]?
if x[1].substr(x[1].length - 1) == ']'
x[1] = x[1].substr 0, x[1].length - 1
[x[0],x[1]]
min_length: (str,length) ->
(str.length >= length)
max_length: (str,length) ->
(str.length < length)
valid_email: (str) ->
(/^[\w\d_-]+@+[\w\d_-]+\.+[\w]/i.test(str))
required: (str) ->
(@min_length str, 1)
min_value: (num,value) ->
(num > value)
max_value: (num,value) ->
(num < value)
numeric: (num) ->
(/[^\d]/g.test(num))
alpha_numeric: (str) ->
(/[^\da-z]/gi.test(str))
trim: (str) ->
str.replace /^\s+|\s+$/g, ''
integer: (str) ->
parseInt(str)
parse_rules: (rules) ->
rules.split '|'
validate_field: (field,str,rules) ->
rules = @parse_rules rules
for rule_string in rules
[rule,value] = @rule_value(rule_string)
result = @[rule] str, value
unless result in [true,false] # post processing
str = @data[field] = result
else unless 'required' not in rules and str.length is 0
if result is no
@set_error field, rule_string
break
set_error: (field,rule) ->
if @errors_list is undefined
@errors_list = {}
if @errors_list[field] is undefined
@errors_list[field] = []
@errors_list[field].push rule
Каждый из методов этого класса может быть использован сам по себе.
Метод find позволяет найти указанное правило в строке определения правил валидации, написанной в духе CodeIgniter. В конкретном проекте, где в качестве бекенда выступил CI, мне было удобно непосредственно экспортировать объект правил в клиент.
Метод validate, принимающий список полей и правил, для каждого элемента формы выполнит очистку данных и проверку соответствия правилам. В случае успешного прохождения валидации метод вернет объект с чистыми данными. В случае обнаружения ошибок метод вызывет брата show_errors() и вернут false. Данные и ошибки сохраняются, как свойства класса. Поэтому они доступны для дальнейшего использования, и по этой же причине они очищаются перед началом.
Метод show_errors вставит сообщения об ошибках рядом с полями формы, и установит фокус (курсор) в первое поле, содержащее ошибку. Так пользователь может сразу отредактировать некорректные данные, без необходимости лишний раз тянуться за мышью и щелкать.
Метод lang найдет подходящее сообщение об ошибке, вставит в него значение из правила, если оно есть, и вернет строку.
Метод rule_value вернет значение из правила. Сделано на скорую руку, наверное можно сделать значительно красивее посредством регулярных выражений. Если у вас есть время и понимание – напишите в ЛС или комментарии, проверим и обновим код.
Методы min_length, max_length, valid_email, required, min_value, max_value, numeric и alpha_numeric проводят проверку с соответствующими правилами. Вы легко можете добавить методы.
Здесь валидация не только проверяет данные, но и очищает их. В данном коде это делают методы trim и integer.
Элементарный метод parse_rules принимает строку правил, отформатированную в духе CodeIgniter, и преобразует ее в массив.
Метод validate_field, принимает для обработки имя поля, строку и правила, производит обработку строки и проверку на соотвествие правилам, после чего помещает ее в локальный объект данных для последующего использования.
Метод set_error просто запоминает ошибку.
Теперь версия кода с построчными комментариями.
class validation
find: (rules, rule) ->
# преобразует строку в массив, разделив по символу |
rules = rules.split '|'
# вернет результат проверки - есть ли rule в массиве rules
(rule in rules)
validate: (rules) ->
# удалит все отбражаемые сообщения об ошибках - с классом .form_error
$('.form_error').remove()
# удалит список ошибок
delete @errors_list
# очистит локальный список ссылок на поля формы
@fields = {}
# создает (очищает) локальный объект данных
@data = {}
# пройдет в цикле все правила
for field, rule of rules
# сохранит ссылку на поле формы (просто для быстродействия)
@fields[field] = $("input[name=\"#{field}\"]");
# извлечет данные из поля формы
@data[field] = @fields[field].val()
# проведет валидацию
@validate_field field, @data[field], rules_string
# если локальный список ошибок не существует
unless @errors_list?
# вернет данные
@data
else
# вызовет метод для отображения сообщений об ошибках
@show_errors()
# вернет false
false
show_errors: ->
# отметит, что фокус еще никуда не устанавливался
focus_set = false
# обойдет в цикле объект с ошибками
for field, errors of @errors_list
# тут у нас будут сообщения об ошибках
messages = []
for error in errors
# преобразует сообщения об ошибках в читаемый вид
messages.push @lang error
# перед каждым полем вставит сообщения об ошибках, связанные с этим полем
@fields[field].before nano templates.form_error, {message: messages.join(' ')}
# поставит фокус в первое поле, с которым связаны сообщения об ошибках
unless focus_set
@fields[field].focus()
focus_set = true
lang: (error) ->
# получит значение из правила, если оно есть
[rule,value] = @rule_value error
if value?
# обработает строку со значением
nano lang.errors[rule], {val: value})
else
# если значения нет - вернет языковую строку, как есть
lang.errors[rule]
rule_value: (rule)->
# обработает конструкцию вида min_length[3]
x = rule.split '['
if x[1]?
if x[1].substr(x[1].length - 1) == ']'
x[1] = x[1].substr 0, x[1].length - 1
[x[0],x[1]]
min_length: (str,length) ->
# вернет true, если длина строки больше или равна заданному значению
(str.length >= length)
max_length: (str,length) ->
# вернет true, если длина строки меньше заданного значения
(str.length < length)
valid_email: (str) ->
# вернет true, если строка соответствует паттерну
(/^[\w\d_-]+@+[\w\d_-]+\.+[\w]/i.test(str))
required: (str) ->
# вернет true, если строка не пустая
(@min_length str, 1)
min_value: (num,value) ->
# вернет true, если число больше заданного
(num > value)
max_value: (num,value) ->
# вернет true, если число меньше заданного
(num < value)
numeric: (num) ->
# вернет true, если строка содержит только цифры
(/[^\d]/g.test(num))
alpha_numeric: (str) ->
# вернет true, если строка содержит только цифры и буквы
(/[^\da-z]/gi.test(str))
trim: (str) ->
# очистит строку от ведущих и замыкающих пробельных символов
str.replace /^\s+|\s+$/g, ''
integer: (str) ->
# приведет в целочисленный вид
parseInt(str)
parse_rules: (rules) ->
# преобразует строку правил в массив
rules.split '|'
validate_field: (field,str,rules) ->
# преобразует строку правил в массив
rules = @parse_rules rules
# в цикле исследует все предоставленные правила
for rule_string in rules
# получит значение из правила, если оно есть
[rule,value] = @rule_value(rule_string)
# вызовет колбек для обработки или проверки
result = @[rule] str, value
# если результат обработки - что-то, кроме false или true
# - значит была обработка, а не проверка
unless result in [true,false]
# перепишем результат
str = @data[field] = result
# если не просили пустую строку, но ничего не получили
else unless 'required' not in rules and str.length is 0
if result is no
# ловим ошибку
@set_error field, rule_string
# а смысл дальше искать?
# Если хотите - уберите эту строку,
# мне нужна была полная аналогия с CI
break
set_error: (field,rule) ->
# создаст объект ошибок, если его нет
if @errors_list is undefined
@errors_list = {}
# создаст ветку об ошибках конкретного поля, если его еще нет
if @errors_list[field] is undefined
@errors_list[field] = []
# запомнит сообщение об ошибке
@errors_list[field].push rule
Используются сообщения об ошибках, хранящиеся в объекте, как показано ниже.
lang =
errors:
min_length: 'Недостаточная длина, нужно как минимум {val} симв.'
max_length: 'Слишком длинная строка, допускается максимум {val} симв.'
min_value: 'Слишком малое значение, нужно как минимум {val}.'
max_value: 'Слишком большое значение, нежно не более {val}.'
numeric: 'Допускаются только цифры.'
alpha_numeric: 'Допускаются только цифры и буквы латиницы.'
valid_email: 'Нужно указать правильный адрес электронной почты.'
required: 'Здесь необходимо указать данные.'
Для обработки сообщений об ошибках используется популярный минималистичный JS-шаблонизатор, вот его код, портированный на CoffeeScript
_nano_regex = /\{([\w\.]*)\}/g
nano = (template, data) ->
template.replace _nano_regex, (str, key) ->
keys = key.split(".")
value = data[keys.shift()]
$.each keys, ->
value = value[this]
(if (value is null or value is `undefined`) then "" else value)
Частая внутрифреймворковая задача — безопасить кавычки.
add_quotes = (str) ->
(if str? then str.replace /"/g, """ else '')
Использование
Допустим, есть такие поля в форме.
<label>Имя</label>
<input type=”text” name=”name” value=”Василий”>
<label>Фамилия</label>
<input type=”text” name=”surname”>
<label>Электропочта</label>
<input type=”text” name=”email”>
Тогда мы можем задать правила для валидации и вызвать обработку по сабмиту формы.
rules =
name: 'trim|required|min_length[2]|max_length[255]'
surname: 'trim|max_length[255]'
email: 'trim|required|valid_email|max_length[255]'
validation = new validation
$('body').on 'submit', 'form', =>
data = validation.validate rules
if data isnt off
# отправляем их аяксом на сервер
else
# ошибки приключились. В принципе, можем больше ничего не делать – появятся сообщения об ошибках и даже курсор встанет в нужное поле
Получается минималистичный, гибкий и даже красивый код.