SitePoint Sponsor

User Tag List

Results 1 to 19 of 19

Threaded View

  1. #1
    SitePoint Zealot sleepeasy's Avatar
    Join Date
    Sep 2003
    Location
    Bristol, UK
    Posts
    145
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    A good way to handle forms?

    I've been trying to figure out a "good way" to handle displaying and validating forms.

    My aims were to allow: simple and quick definition of the form, validation of fields, "popuation" of fields, treat all input fields the same.

    This is what I've come up with so far - I would like to hear your thoughts.

    Forms are defined in XML, an example:

    Code:
    <?xml version="1.0"?>
    <capsule describes="form">
    	<title>Login</title>
    	<intro>Access members only content and features.</intro>
    	<form action="Login_Form">
    		<fieldset>
    			<legend>Login Details</legend>
    			<input name="username" populateMultipleValues="no" submitMultipleValues="no" required="yes" type="text">
    				<label>Username</label>
    				<populator class="Cookie_FormPopulator" params="name:login_username"/>
    				<validator class="Validation_AlphaNumericValidator"/>
    				<validator class="Validation_EmailValidator"/>
    			</input>
    			<input name="password" populateMultipleValues="no" submitMultipleValues="no" required="yes" type="secret">
    				<label>Password</label>
    				<populator class="Cookie_FormPopulator" params="name:login_password"/>
    				<filter class="Filter_ProfanityFilter"/>
    				<validator class="Validation_AlphaNumericValidator"/>
    			</input>
    		</fieldset>
    	</form>
    </capsule>
    The form is validated and populated by an interestingly titled Form class:

    PHP Code:
    class Form {

        
    /**
         * Pepper_XPath object representing the Form Descriptor
         *
         * @access    private
         * @var        object Pepper_XPath
         */
        
    protected $formXPath;

        
    /**
         * DomDocument object representing the Form Descriptor
         *
         * @access    private
         * @var        object DomDocument
         */
        
    protected $formDoc;

        
    /**
         * Hash containing the names and values of fields that passed validation.
         *
         * @access    protected
         * @var        array
         */
        
    protected $validValues;
        
        
    /**
         * Denotes whether the form failed validation.
         *
         * @access    protected
         * @var        boolean
         */
        
    protected $failedValidation;

        
    /**
         * Constructor
         *
         * @access    public
         * @param    object DomDocument
         * @return    void
         */
        
    public function __construct(DomDocument $formDoc) {
            
    $this->formDoc $formDoc;
            
    $this->formXPath = new Pepper_XPath($this->formDoc);
            
    $this->validValues = array();
            
    $this->valid TRUE;
            
    $this->dataSet = array();
        } 
    // __construct()
        
        /**
         * Populates input fields with values.
         *
         * @access    public
         * @return    void
         */
        
    public function populate() {
            foreach (
    $this->dataSet as $key => $value) {
                
    // Populate fields with original values.
                
    if ($result $this->formXPath->query('/capsule/form/fieldset/input[@name="'.$key.'"]')) {
                    if (
    $node $result->item(0)) {
                        
    $node->appendChild(new DomElement('value'stripslashes(htmlentities($valueENT_QUOTES))));
                    }
                }
            }
        
            foreach (
    $this->formXPath->query('/capsule/form/fieldset/input/populator') as $populatorNode) {
                
    $input $populatorNode->parentNode;
                if (isset(
    $this->dataSet[$input->getAttribute('name')])) {
                    
    // We've already populated this above.
                    
    continue;
                }
                
                
    $class $populatorNode->getAttribute('class');
                
    $populator = new $class;
                
                if (
    $populatorNode->hasAttribute('params')) {
                    foreach (
    Pepper_Util::extractParams($populatorNode->getAttribute('params')) as $key => $value) {
                        
    $method 'set' $key;
                        
    $populator->$method($value);
                    }        
                }
                
                
    $values $populator->getValues();
                if (
    $input->getAttribute('populateMultipleValues') == 'no' && count($values) > 1) {
                    
    // Error... populator returned too many values.
                
    } else {
                    foreach (
    $values as $key => $value) {
                        
    $value $input->appendChild(new DomElement('value'$value));
                        
    $value->setAttribute('key'$key);
                    }
                }
            }
        } 
    // populate()

        /**
         * Validates each input field in the form.
         *
         * Validation rules are retrieved from the form descriptor.
         *
         * @access    public
         * @param    array DataSet containing the values to validate against.
         * @return    void
         */
        
    public function validate($dataSet) {
            
    $this->dataSet $dataSet;
            if (!
    is_array($dataSet)) {
                throw (new 
    Form_ValidationException('DataSet (argument 1 to validate()) must be an array.'));
            }
        
            foreach (
    $this->formXPath->query('/capsule/form/fieldset/input') as $input) {
                
    $inputName $input->getAttribute('name');
                if ((!isset(
    $dataSet[$inputName]) || empty($dataSet[$inputName])) && $input->getAttribute('required') == 'yes') {
                    
    $err $input->appendChild(new DomElement('error'));
                    
    $err->setAttribute('type''required');
                    
    $this->valid FALSE;
                    continue;
                }
                
                if (isset(
    $dataSet[$inputName]) & !empty($dataSet[$inputName])) {
                    if (
    $input->getAttribute('submitMultipleValues') == 'no' && is_array($dataSet[$inputName])) {
                        
    // Error... multiple values can't be submitted for this input.
                        
    $this->valid FALSE;
                        continue;
                    }
                    
                    
    $validation = new Validation();
                    
    $validation->setValue($dataSet[$inputName]);
                    foreach (
    Pepper_XmlUtil::getElementsByTagName($input'validator') as $validatorNode) {
                        
    $class $validatorNode->getAttribute('class');
                        
    $validation->addValidator(new $class);
                    }
                    if (!
    $validation->success()) {
                        
    $this->valid FALSE;
                        foreach (
    $validation->getErrors() as $err) {
                            
    $input->appendChild(new DomElement('error'$err));
                        }
                    } else {
                        
    $this->validValues[$inputName] = $dataSet[$inputName];
                    }
                }
            }
            
            if (!
    $this->valid) {
                throw (new 
    Form_ValidationException());
            }
        } 
    // validate()

        /**
         * Returns DataSet of valid values.
         *
         * @access    public
         * @return    array
         */
        
    public function getValues() {
            return 
    $this->validValues;    
        } 
    // getValues()

    // Form 
    And an example usage would be:

    PHP Code:
    class Login_Form implements ActionResponse_DocumentProducer {
        
        protected 
    $data;
        
        protected 
    $form;

        protected 
    $capsule;
        
        protected 
    $responseValid;
        
        public function 
    __construct() {
            
    $dfa = new Application_DataFileAccess($this);
            
    $ptr $dfa->locate('login-form-definition.xml');
            
    $ptr->setAdapter(new Pepper_DomDocument());
            
    $this->data $ptr->getFile();
            
    $this->form = new Form($this->data);
            
    $this->responseValid TRUE;
            
            
    $this->capsule = new Response_Document($this);
            
    // TODO: Get placement and template to use using Application_Settings
            
    $this->capsule->setPlacement('content');
            
    $this->capsule->setTemplate('html/minimal-slick/Form/form.xml');
        } 
    // __construct()
        
        
    public function process() {
            if (isset(
    $_POST['__action__']) && $_POST['__action__'] == get_class($this)) {
                try {
                    
    $this->form->validate($_POST);
                    
    $processor = new Login_Processor();
                    
    $processor->setValues($this->form->getValues());
                    try {
                        
    $processor->process();
                        
    // What to do? 
                        // Show confirmation: "Thanks, you are now logged in."
                        // Or display nothing?
                    
    } catch (Login_ProcessorException $e) {
                        
    // Show error.
                        
    $this->responseValid FALSE;
                    }
                } catch (
    Form_ValidationException $e) {
                    try {
                        
    $this->form->populate();
                    } catch (
    Form_ValidationException $e) {
                        
    // Show error.
                        
    $this->responseValid FALSE;
                    }
                }
            } else {
                try {
                    
    $this->form->populate();
                } catch (
    Form_ValidationException $e) {
                    
    // Show error.
                    
    $this->responseValid FALSE;
                }
            }
        } 
    // process()

        
    public function getData() {
            return 
    $this->data;
        } 
    // getData()

        
    public function responseIsValid() {
            return 
    $this->responseValid;
        } 
    // responseIsValid()

    // Login_Form 
    The form definition is used by the Form class to control validation and population of fields. The Form class alters the form definition as it encounters invalid fields, or retrieves values to populate fields with. The form definition the becomes the output document, to be transformed using XSLT.

    Populators are an attempt to make adding values to select, radio elements quickly.

    If you think I've overlooked something, or if you think the design is wrong, or anything else, let me know.
    Last edited by sleepeasy; Mar 3, 2004 at 08:18.
    Always open to question or ridicule


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •