Живые формы. На изнанке

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

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

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

1. Условный валидатор (серверная часть).

<?php

class ConditionalValidator extends CValidator
{
	public $condition;
	public $validator;
	public $conditionFn = null;
	
	protected $_validator;
	protected $_params = array();
	
	public function __set($key, $val)
	{
		$this->_params[$key] = $val;
	}
	
	protected function validateAttribute($object, $attribute)
	{
		if ($this->condition && $this->evaluateExpression($this->condition, array(
			'model' => $object, 
			'attribute' => $attribute))) 
		{
			$this->initValidator($object)->validateAttribute($object, $attribute);
		}
	}
	
	protected function initValidator($model)
	{
		if (null === $this->_validator) {
			$this->_validator = self::createValidator($this->validator, $model, $this->attributes, $this->_params);
		}
		return $this->_validator;
	}
	
	public function clientValidateAttribute($object, $attribute)
	{
		if ($this->conditionFn !== false) {
			$conditionFn = $this->conditionFn ? $this->conditionFn : 'condition'.ucfirst($attribute).'Validate';
			return 'if (typeof ' . $conditionFn . ' == "function" && ' . $conditionFn. '(value, messages, attribute)) {'.
				$this->initValidator($object)->clientValidateAttribute($object, $attribute).
			'}';
		}
	}
}

2. Правило валидации (серверная часть).

array(' company_name',
		'ConditionalValidator', 
		'condition' => '$model->user_type == User::TYPE_COMPANY_REP', 
		'validator' => 'required', 
		'conditionFn' => 'required_condition'),

3. Поля ввода (клиентская часть).

<div class="control-group">
	<?php echo $form->labelEx($model, 'user_type', array('class'=>'control-label')); ?>
	<div class="controls">
		<?php echo $form->dropdownList($model, 'user_type', User::listTypes(), array(
			'class'=>'input', 
			'empty' => 'Select Value'
		)); ?>
		<?php echo $form->error($model, 'user_type', array('class'=>'help-inline')); ?>
	</div>
</div>
<div class="control-group">
	<?php echo $form->labelEx($model, 'company_name', array('class'=>'control-label')); ?>
	<div class="controls">
		<?php echo $form->textField($model, 'company_name', array(
			'class'=>'input',
			'data-required' => "user_type = 'COMPANY_REP'",
		)); ?>
		<?php echo $form->error($model, 'company_name', array('class'=>'help-inline')); ?>
	</div>
</div>

4. Код инициализации (клиентская часть).

<script type="text/javascript">
function required_condition(v,m,a) {
	return $('#form').liveform('getIsRequired', $('#' + a.inputID));
}
$(function() {
	$('#form').liveform({
		'inputcontainer':'.control-group',
		'inputlabel':'.control-label',
	});
});
</script>

Теперь подробнее о том, что было написано выше. Первое, с чего начнем, это — условный валидатор. Ну тут все просто, обычный валидатор созданный для того, чтобы запускать другой валидатор при выполнении определенного условия. Честно говоря, я кроме как в паре с валидатором required более ни с чем его не использовал. Он проверяет условие, заданное параметром condition. Если оно выполняется, запускается валидатор, указанный в параметре validator. Чтобы проверить, нужно ли запускать валидацию атрибута на стороне клиента, мы использовали функцию required_condition, которую передали в параметре conditionFn. В этой функции мы проверяем является ли атрибут обязательным через вызов метода getIsRequired плагина liveform. Ну а результат работы метода будет зависеть от значения атрибута data-required проверяемого элемента.

В итоге, когда пользователь пытается отправить форму, срабатывает стандартная клиентская валидация Yii. Если user_type = 'COMPANY_REP', то дополнительно срабатывает клиентская валидация для атрибута company_name. Если валидация проходит, то данные отправляются на сервер и там еще раз проходят аналогичную проверку.