Pull to refresh

CoffeeScript в примерах. Валидация

Reading time7 min
Views5.2K
Пример программирования валидации на 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
		# ошибки приключились. В принципе, можем больше ничего не делать – появятся сообщения об ошибках и даже курсор встанет в нужное поле


Получается минималистичный, гибкий и даже красивый код.
Tags:
Hubs:
+2
Comments16

Articles