* @link http://www.pradosoft.com/ * @copyright Copyright © 2005-2014 PradoSoft * @license http://www.pradosoft.com/license/ * @package Prado\Web\UI\WebControls */ namespace Prado\Web\UI\WebControls; use Prado\Exceptions\TConfigurationException; use Prado\Exceptions\TNotSupportedException; use Prado\TPropertyValue; use Prado\Exceptions\TInvalidDataTypeException; use Prado\Web\Javascripts\TJavaScript; use Prado\Web\UI\IValidator; /** * TBaseValidator class * * TBaseValidator serves as the base class for validator controls. * * Validation is performed when a postback control, such as a TButton, a TLinkButton * or a TTextBox (under AutoPostBack mode) is submitting the page and * its CausesValidation property is true. * You can also manually perform validation by calling {@link TPage::validate()}. * The input control to be validated is specified by {@link setControlToValidate ControlToValidate}. * * Validator controls always validate the associated input control on the serve side. * In addition, if {@link getEnableClientScript EnableClientScript} is true, * validation will also be performed on the client-side using javascript. * Client-side validation will validate user input before it is sent to the server. * The form data will not be submitted if any error is detected. This avoids * the round-trip of information necessary for server-side validation. * * You can use multiple validator controls to validate a single input control, * each responsible for validating against a different criteria. * For example, on a user registration form, you may want to make sure the user * enters a value in the username text box, and the input must consist of only word * characters. You can use a {@link TRequiredFieldValidator} to ensure the input * of username and a {@link TRegularExpressionValidator} to ensure the proper input. * * If an input control fails validation, the text specified by the {@link setErrorMessage ErrorMessage} * property is displayed in the validation control. However, if the {@link setText Text} * property is set, it will be displayed instead. If both {@link setErrorMessage ErrorMessage} * and {@link setText Text} are empty, the body content of the validator will * be displayed. Error display is controlled by {@link setDisplay Display} property. * * You can also customized the client-side behaviour by adding javascript * code to the subproperties of the {@link getClientSide ClientSide} * property. See quickstart documentation for further details. * * You can also place a {@link TValidationSummary} control on a page to display error messages * from the validators together. In this case, only the {@link setErrorMessage ErrorMessage} * property of the validators will be displayed in the {@link TValidationSummary} control. * * Validators can be partitioned into validation groups by setting their * {@link setValidationGroup ValidationGroup} property. If the control causing the * validation also sets its ValidationGroup property, only those validators having * the same ValidationGroup value will do input validation. * * Note, the {@link TPage::getIsValid IsValid} property of the current {@link TPage} * instance will be automatically updated by the validation process which occurs * after {@link TPage::onLoad onLoad} of {@link TPage} and before the postback events. * Therefore, if you use the {@link TPage::getIsValid()} property in * the {@link TPage::onLoad()} method, you must first explicitly call * the {@link TPage::validate()} method. * * Notes to Inheritors When you inherit from TBaseValidator, you must * override the method {@link evaluateIsValid}. * * @author Qiang Xue * @package Prado\Web\UI\WebControls * @since 3.0 */ abstract class TBaseValidator extends TLabel implements IValidator { /** * @var boolean whether the validation succeeds */ private $_isValid=true; /** * @var boolean whether the validator has been registered with the page */ private $_registered=false; /** * @var TValidatorClientSide validator client-script options. */ private $_clientSide; /** * Controls for which the client-side validation3.js file needs to handle * them specially. * @var array list of control class names */ private static $_clientClass = array('THtmlArea', 'THtmlArea4', 'TDatePicker', 'TListBox', 'TCheckBoxList'); /** * Constructor. * This method sets the foreground color to red. */ public function __construct() { parent::__construct(); $this->setForeColor('red'); } /** * Registers the validator with page. * @param mixed event parameter */ public function onInit($param) { parent::onInit($param); $this->getPage()->getValidators()->add($this); $this->_registered=true; } /** * Unregisters the validator from page. * @param mixed event parameter */ public function onUnload($param) { if($this->_registered && ($page=$this->getPage())!==null) $page->getValidators()->remove($this); $this->_registered=false; parent::onUnload($param); } /** * Adds attributes to renderer. Calls parent implementation and renders the * client control scripts. * @param THtmlWriter the renderer */ protected function addAttributesToRender($writer) { $display=$this->getDisplay(); $visible=$this->getEnabled(true) && !$this->getIsValid(); if($display===TValidatorDisplayStyle::None || (!$visible && $display===TValidatorDisplayStyle::Dynamic)) $writer->addStyleAttribute('display','none'); else if(!$visible) $writer->addStyleAttribute('visibility','hidden'); $writer->addAttribute('id',$this->getClientID()); parent::addAttributesToRender($writer); $this->renderClientControlScript($writer); } /** * Returns an array of javascript validator options. * @return array javascript validator options. */ protected function getClientScriptOptions() { $control = $this->getValidationTarget(); $options['ID'] = $this->getClientID(); $options['FormID'] = $this->getPage()->getForm()->getClientID(); $options['Display'] = $this->getDisplay(); $options['ErrorMessage'] = $this->getErrorMessage(); if($this->getFocusOnError()) { $options['FocusOnError'] = $this->getFocusOnError(); $options['FocusElementID'] = $this->getFocusElementID(); } $options['ValidationGroup'] = $this->getValidationGroup(); if($control) $options['ControlToValidate'] = $control->getClientID(); $options['ControlCssClass'] = $this->getControlCssClass(); $options['ControlType'] = $this->getClientControlClass($control); $options['Enabled'] = $this->getEnabled(true); //get date format from date picker target control if($control instanceof TDatePicker) $options['DateFormat'] = $control->getDateFormat(); $options = array_merge($options,$this->getClientSide()->getOptions()->toArray()); return $options; } /** * Gets the Control type for client-side validation. If new cases exists in * TBaseValidator::$_clientClass, be sure to update the corresponding * "Javascript/validation3.js" file as well. * @param TControl control to validate. * @return string control type for client-side validation. */ private function getClientControlClass($control) { foreach(self::$_clientClass as $type) if($control instanceof $type) return $type; $reflectionClass = new \ReflectionClass($control); return $reflectionClass->getShortName(); } /** * Gets the TValidatorClientSide that allows modification of the client- * side validator events. * * The client-side validator supports the following events. * # OnValidate -- raised before client-side validation is * executed. * # OnValidationSuccess -- raised after client-side validation is completed * and is successfull, overrides default validator error messages updates. * # OnValidationError -- raised after client-side validation is completed * and failed, overrides default validator error message updates. * * You can attach custom javascript code to each of these events * * @return TValidatorClientSide javascript validator event options. */ public function getClientSide() { if($this->_clientSide===null) $this->_clientSide = $this->createClientSide(); return $this->_clientSide; } /** * @return TValidatorClientSide javascript validator event options. */ protected function createClientSide() { return new TValidatorClientSide; } /** * Renders the javascript code to the end script. * If you override this method, be sure to call the parent implementation * so that the event handlers can be invoked. * @param THtmlWriter the renderer */ public function renderClientControlScript($writer) { $scripts = $this->getPage()->getClientScript(); if ($this->getEnableClientScript()) $scripts->registerPradoScript('validator'); $formID=$this->getPage()->getForm()->getClientID(); $scriptKey = "TBaseValidator:$formID"; if($this->getEnableClientScript() && !$scripts->isEndScriptRegistered($scriptKey)) { $manager['FormID'] = $formID; $options = TJavaScript::encode($manager); $scripts->registerEndScript($scriptKey, "new Prado.ValidationManager({$options});"); } if($this->getEnableClientScript()) $this->registerClientScriptValidator(); } /** * Override parent implementation to update the control CSS Class before * the validated control is rendered */ public function onPreRender ($param) { parent::onPreRender($param); $this->updateControlCssClass(); } /** * Update the ControlToValidate component's css class depending * if the ControlCssClass property is set, and whether this is valid. * @return boolean true if change, false otherwise. */ protected function updateControlCssClass() { if(($cssClass=$this->getControlCssClass())!=='') { $control=$this->getValidationTarget(); if($control instanceof TWebControl) { $class = preg_replace ('/ '.preg_quote($cssClass).'/', '',$control->getCssClass()); if(!$this->getIsValid()) { $class .= ' '.$cssClass; $control->setCssClass($class); } elseif ($control->getIsValid()) $control->setCssClass($class); } } } /** * Registers the individual validator client-side javascript code. */ protected function registerClientScriptValidator() { $key = 'prado:'.$this->getClientID(); if(!$this->getPage()->getClientScript()->isEndScriptRegistered($key)) { $options = TJavaScript::encode($this->getClientScriptOptions()); $script = 'new '.$this->getClientClassName().'('.$options.');'; $this->getPage()->getClientScript()->registerEndScript($key, $script); } } /** * Gets the name of the javascript class responsible for performing validation for this control. * This method overrides the parent implementation. * @return string the javascript class name */ abstract protected function getClientClassName(); /** * This method overrides the parent implementation to forbid setting ForControl. * @param string the associated control ID * @throws TNotSupportedException whenever this method is called */ public function setForControl($value) { throw new TNotSupportedException('basevalidator_forcontrol_unsupported',get_class($this)); } /** * This method overrides parent's implementation by setting {@link setIsValid IsValid} to true if disabled. * @param boolean whether the validator is enabled. */ public function setEnabled($value) { $value=TPropertyValue::ensureBoolean($value); parent::setEnabled($value); if(!$value) $this->_isValid=true; } /** * @return TValidatorDisplayStyle the style of displaying the error message. Defaults to TValidatorDisplayStyle::Fixed. */ public function getDisplay() { return $this->getViewState('Display',TValidatorDisplayStyle::Fixed); } /** * @param TValidatorDisplayStyle the style of displaying the error message */ public function setDisplay($value) { $this->setViewState('Display',TPropertyValue::ensureEnum($value,'TValidatorDisplayStyle'),TValidatorDisplayStyle::Fixed); } /** * @return boolean whether client-side validation is enabled. */ public function getEnableClientScript() { return $this->getViewState('EnableClientScript',true); } /** * @param boolean whether client-side validation is enabled. */ public function setEnableClientScript($value) { $this->setViewState('EnableClientScript',TPropertyValue::ensureBoolean($value),true); } /** * @return string the text for the error message. */ public function getErrorMessage() { return $this->getViewState('ErrorMessage',''); } /** * Sets the text for the error message. * @param string the error message */ public function setErrorMessage($value) { $this->setViewState('ErrorMessage',$value,''); } /** * @return string the ID path of the input control to validate */ public function getControlToValidate() { return $this->getViewState('ControlToValidate',''); } /** * Sets the ID path of the input control to validate. * The ID path is the dot-connected IDs of the controls reaching from * the validator's naming container to the target control. * @param string the ID path */ public function setControlToValidate($value) { $this->setViewState('ControlToValidate',$value,''); } /** * @return boolean whether to set focus at the validating place if the validation fails. Defaults to false. */ public function getFocusOnError() { return $this->getViewState('FocusOnError',false); } /** * @param boolean whether to set focus at the validating place if the validation fails */ public function setFocusOnError($value) { $this->setViewState('FocusOnError',TPropertyValue::ensureBoolean($value),false); } /** * Gets the ID of the HTML element that will receive focus if validation fails and {@link getFocusOnError FocusOnError} is true. * Defaults to the client ID of the {@link getControlToValidate ControlToValidate}. * @return string the ID of the HTML element to receive focus */ public function getFocusElementID() { if(($id=$this->getViewState('FocusElementID',''))==='') { $target=$this->getValidationTarget(); /* Workaround: TCheckBoxList and TRadioButtonList nests the actual * inputs inside a table; we ensure the first input gets focused */ if($target instanceof TCheckBoxList && $target->getItemCount()>0) { $id=$target->getClientID().'_c0'; } else { $id=$target->getClientID(); } } return $id; } /** * Sets the ID of the HTML element that will receive focus if validation fails and {@link getFocusOnError FocusOnError} is true. * @param string the ID of the HTML element to receive focus */ public function setFocusElementID($value) { $this->setViewState('FocusElementID', $value, ''); } /** * @return string the group which this validator belongs to */ public function getValidationGroup() { return $this->getViewState('ValidationGroup',''); } /** * @param string the group which this validator belongs to */ public function setValidationGroup($value) { $this->setViewState('ValidationGroup',$value,''); } /** * @return boolean whether the validation succeeds */ public function getIsValid() { return $this->_isValid; } /** * Sets the value indicating whether the validation succeeds * @param boolean whether the validation succeeds */ public function setIsValid($value) { $this->_isValid=TPropertyValue::ensureBoolean($value); } /** * @return TControl control to be validated. Null if no control is found. * @throws TConfigurationException if {@link getControlToValidate * ControlToValidate} is empty or does not point to a valid control */ public function getValidationTarget() { if(($id=$this->getControlToValidate())!=='' && ($control=$this->findControl($id))!==null) return $control; else throw new TConfigurationException('basevalidator_controltovalidate_invalid',get_class($this)); } /** * Retrieves the property value of the control being validated. * @param TControl control being validated * @return string property value to be validated * @throws TInvalidDataTypeException if the control to be validated does not implement {@link \Prado\Web\UI\IValidatable}. */ protected function getValidationValue($control) { if($control instanceof \Prado\Web\UI\IValidatable) return $control->getValidationPropertyValue(); else throw new TInvalidDataTypeException('basevalidator_validatable_required',get_class($this)); } /** * Validates the specified control. * Do not override this method. Override {@link evaluateIsValid} instead. * @return boolean whether the validation succeeds */ public function validate() { $this->onValidate(); if($this->getVisible(true) && $this->getEnabled(true)) { $target=$this->getValidationTarget(); // if the target is not a disabled web control if($target===null || ($target!==null && !($target instanceof TWebControl && !$target->getEnabled(true)))) { if($this->evaluateIsValid()) { $this->setIsValid(true); $this->onValidationSuccess(); } else { if($target) $target->setIsValid(false); $this->setIsValid(false); $this->onValidationError(); } } else { $this->evaluateIsValid(); $this->setIsValid(true); $this->onValidationSuccess(); } } else { $this->setIsValid(true); } return $this->getIsValid(); } /** * @return string the css class that is applied to the control being validated in case the validation fails */ public function getControlCssClass() { return $this->getViewState('ControlCssClass',''); } /** * @param string the css class that is applied to the control being validated in case the validation fails */ public function setControlCssClass($value) { $this->setViewState('ControlCssClass',$value,''); } /** * This is the major method for validation. * Derived classes should implement this method to provide customized validation. * @return boolean whether the validation succeeds */ abstract protected function evaluateIsValid(); /** * This event is raised when the validator succeeds in validation. */ public function onValidationSuccess() { $this->raiseEvent('OnValidationSuccess',$this,null); } /** * This event is raised when the validator fails in validation. */ public function onValidationError() { $this->raiseEvent('OnValidationError',$this,null); } /** * This event is raised right before the validator starts to perform validation. * You may use this event to change the behavior of validation. * For example, you may disable the validator if certain condition is satisfied. * Note, the event will NOT be raised if the validator is invisible. */ public function onValidate() { $this->raiseEvent('OnValidate',$this,null); } /** * Renders the validator control. * @param THtmlWriter writer for the rendering purpose */ public function renderContents($writer) { if(($text=$this->getText())!=='') $writer->write($text); else if(($text=$this->getErrorMessage())!=='') $writer->write($text); else parent::renderContents($writer); } }