SitePoint Sponsor

User Tag List

Results 1 to 20 of 20
  1. #1
    SitePoint Zealot
    Join Date
    Jul 2005
    Posts
    194
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Wizard form pages using the applicationController

    Hello!

    I am working on making a wizard-like form page using the ApplicationController made in the thread with the same now. Only I am not sure you I should implementate the wizard-like form page. Currently, I happen to have the following structure, such as defined below:

    Code:
    incoming request 
      |
      + framework skeleton file load all classes
      |
      + framework skeleton create Application-class instance
               +
               | App. create FrontController instance 
               | 
               +----[+] stuff should happen here
    Currently this work only, because my form has around ~20 pages full with fields to be filled in, I am planning to make it like a wizard. Only I am not sure how I should implementate this. My current idea is to make a statemachine class with all the states starting with "initialize" and then all the wizard pages. After each form post, I should jump to the next state when everything is filled in correctly, if not I should keep the current state.

    PHP Code:
    class StateMachine {
      protected 
    $states;
      protected 
    $currentStateIndex;

      public function 
    __construct() {
           
    //
           
    $this->states = array();
           
    $this->currentStateIndex 0;
      }

      public function 
    addState$stateName ) {
         
    $this->states[] = $stateName;
      }

      public 
    functio nextState() {
          
    $this->currentStateIndex++;
      }

      public function 
    getCurrentState() {
          return 
    $this->states$this->currentStateIndex ];
      }

    only I got some problem with it, because where should I keep track of the form data? Should I store in the database with a specific flag or in a session?

  2. #2
    SitePoint Guru Galo's Avatar
    Join Date
    May 2005
    Location
    Holland!
    Posts
    852
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    This is correct, actually imho the State is a pattern that solves this problem.
    If you would brake down you wizard what elements would you have, and im not talking of the actual forms, but which steps would you include.

    1. splash
    2. database configuration
    3. mod-rewrite configuration
    4. default view setup
    5. etc

    It does not matter what forms u use and how many, but if you were able to create a wizard creator you vould there configure what steps it would have and how the steps are ordered.

    The structure looks fina, but only a frontController does not do the Job, perhaps it does but this means you would have to deal with multiple controllers for different forms.

    Abstract that what varries, like the steps and components that make up your wizzard.

    hope this helps,
    Cheers,
    Galo
    Business as usual is off the menu folks, ...

  3. #3
    SitePoint Zealot
    Join Date
    Jul 2005
    Posts
    194
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Well, the form is just one big list of questions, all stored in the MySQL database. But because the questions printed on A4 is already 20 pages. I think it's better to split it in different groups which each group on a different page, first group includes client informaton (address, etc.)

    The database configuration credentials are obtained by the ConfigurationService, and then a database singleton is created, using the code below:

    PHP Code:
    $database DatabaseFactory::getDatabaseInstanceConfigurationService::fetch( new ConfigurationElement'Database''ConnectionString' ) ); 
    The main problem would be the "default view setup", I would expect that the Viewe would be used to construct the form. The model for obtained all the fields for the form, which gets transformed in the View. I suppose I would need to check if all required fields etc are valid, if so I should store the form data and then move to the next state.

  4. #4
    SitePoint Guru Galo's Avatar
    Join Date
    May 2005
    Location
    Holland!
    Posts
    852
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by wdeboer
    Well, the form is just one big list of questions, all stored in the MySQL database. But because the questions printed on A4 is already 20 pages. I think it's better to split it in different groups which each group on a different page, first group includes client informaton (address, etc.)

    The database configuration credentials are obtained by the ConfigurationService, and then a database singleton is created, using the code below:

    PHP Code:
    $database DatabaseFactory::getDatabaseInstanceConfigurationService::fetch( new ConfigurationElement'Database''ConnectionString' ) ); 
    The main problem would be the "default view setup", I would expect that the Viewe would be used to construct the form. The model for obtained all the fields for the form, which gets transformed in the View. I suppose I would need to check if all required fields etc are valid, if so I should store the form data and then move to the next state.

    Hmmm, the view is only concerning itself with the presentation and IMHO the model is used to represent the structure and data, while the controller is coped up with handling request and parsing arguments and stuff.
    Multiple views can be constructed for one Model, and if you see your form as the Model whereby the ordering of the form is the strucure and the form elements themselves are the data (which will be properly rendered by the view) you could now use an iterator to iterate through the records that the model contains, nowing this we can use an offset to decide which records we want to list, divide this into seperate steps and your done no ?

    cheers,
    Galo
    Business as usual is off the menu folks, ...

  5. #5
    SitePoint Addict
    Join Date
    May 2003
    Location
    The Netherlands
    Posts
    391
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    What about making use of a workflow object that keeps state and stores the data of each step of the process?
    There’s more than one way to skin a cat.

  6. #6
    SitePoint Guru Galo's Avatar
    Join Date
    May 2005
    Location
    Holland!
    Posts
    852
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by nacho
    What about making use of a workflow object that keeps state and stores the data of each step of the process?
    Beceause we are lokking for an overall OOP aproach and not a producual one no?
    Business as usual is off the menu folks, ...

  7. #7
    SitePoint Zealot
    Join Date
    Jul 2005
    Posts
    194
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Workflow object? What do you mean with that nacho?

    I currently got a FormModel which constructs a DomDocument of the form definition found in the database. In the controller I do:

    $response->setContent( $this->view->execute( $model ) );

    This will create the approriate form based on a xslt file, and then the result is output to the Response-object. Currently this works

  8. #8
    SitePoint Addict
    Join Date
    May 2003
    Location
    The Netherlands
    Posts
    391
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Galo
    Beceause we are lokking for an overall OOP aproach and not a producual one no?
    According to that it is also impossible to take an OOP approach towards Web development because the linear nature of the Web, no?

    Quote Originally Posted by wdeboer
    What do you mean with that nacho?
    The problem of creating multi-step forms is known to me also. Coincidentally, I came yesterday this site across. Maybe it inspires you in some way. It certainly inspired me.
    There’s more than one way to skin a cat.

  9. #9
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I have been pondering on this myself, and as such Nacho has a good idea as well? What I was thinking that if you could maintain the approach of the 'State Machine', instead of storing the data in a SESSION or the Database between each page load for each section or part of the FORM, is to store the data in an object?

    This object could then implement some validation class in it's self, and validate the data as it does so? Therefore... You have a means to easily identify if one part of the FORM is valid or not, simply by looking at any given object.

    Starting from the first object upwards, if the data is valid, you'd then put it in the database, and move onto the next object. If at some point, the data for a given object is invalid, you redisplay that given part of the FORM, using the data from the object.

    Thus, the database insertion stops with that object, and the resubmission process begins all over again. Now, how you maintain the state of those objects between page loads is something I'll leave for you to solve?

    But what I can say is that using an object to store your data offers you far more options that what a SESSION can, and why would you want to temp. store (unvalidated) data to the database between page loads (ie Going from one part of the FROM, to another part)?

  10. #10
    SitePoint Addict
    Join Date
    May 2003
    Location
    The Netherlands
    Posts
    391
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Yes, that is what I meant with the workflow object. I was actually thinking of keeping state also with the workflow object, but I guess you can as well inject a state machine like the one you already had to it.

    As Dr. Livingstone suggests, you can easily deal with such an object in the session. I wouldn't change though the way things are right now. I mean that I'd keep validating and buiding the form the very same way you did up till now and try to work out a way to implement the workflow object (and eventually the state machine) in the current situation. Maybe every form should hold a workflow, and in case of single step forms, the workflow would have only ... 1 step ...?
    There’s more than one way to skin a cat.

  11. #11
    SitePoint Addict
    Join Date
    May 2003
    Location
    The Netherlands
    Posts
    391
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I was thinking right now that I would probably rather have only validated data in the workflow object, so if the data is not validated, the workflow data is not updated.
    There’s more than one way to skin a cat.

  12. #12
    SitePoint Guru Galo's Avatar
    Join Date
    May 2005
    Location
    Holland!
    Posts
    852
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by nacho
    According to that it is also impossible to take an OOP approach towards Web development because the linear nature of the Web, no?


    The problem of creating multi-step forms is known to me also. Coincidentally, I came yesterday this site across. Maybe it inspires you in some way. It certainly inspired me.

    Hmmm your right about that, but does the web not state for a common name, and does it not cover xslt, java, javascript, html, xhtml etc, etc....
    And does a PHP class has anything to delegate to these protocols
    IMHO, when speaking of the web being linear and all has nothing to do with the fact that a OOP approach could be used to form an application which alows u to set up a wizzard, so you dont have to write a new one or adjust it whenever you need to, you could just compile a new wizzard with different settings no ?

    -Galo
    Business as usual is off the menu folks, ...

  13. #13
    SitePoint Addict
    Join Date
    May 2003
    Location
    The Netherlands
    Posts
    391
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I think that what you are calling a wizzard, I'm calling a workflow object. Does flow controller sound better?
    There’s more than one way to skin a cat.

  14. #14
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    you could just compile a new wizzard with different settings no ?
    You could easily enough make the State Machine configurable which would increase it's reuse?

    I was thinking right now that I would probably rather have only validated data in the workflow object, so if the data is not validated, the workflow data is not updated.
    Once the object in question, it's data has been validated, it's just a case of passing this over for the data to be inserted. If on the otherhand, the data is invalid, I would (proberly) reset the invalid inputs to their defaults.

    There would be a variable to flag that the objects data is not valid anyways I'd think? For the State Machine to iterate over the entire object array (as there is invalid data in one or more objects), it'd check for the flag and display the appropriate part of the FORM again, using the data from the object in question.

    There may be an assocc. Logger object as well, which holds the appropriate error message(s), assigned to each piece of data (user input). Since the Logger may be variable (ie It's not determined), the State Machine would request the error message from the object, and not the Logger it's self.

    The State Machine to me looks more and more like an Application Controller? .. Just what is a flow controller in plain english? To me there is no actual program flow, more a controlled iteration, as each of these objects are of the same Interface... They all have the same responsibility, just that the data is unique.

    There has to be more thought on this I think

  15. #15
    SitePoint Zealot
    Join Date
    Jul 2005
    Posts
    194
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Well, I am currently made the following to construct the forms, now I only need to plugin the storage and multiple-page support (possible through a xpath by groupnumber):

    Of course I still need to clean up the code, refactor etc. The multi-page form stuff need to be showed tomorrow at noon (13:00h), one of the major reasons why the code is crap. <g>

    class.FormView.inc.php
    PHP Code:
    class FormView {

        public function 
    __construct() {
        }

        public function 
    execute$model ) {
            if ( !
    $model instanceof FormModel ) {
                throw new 
    IllegalParameterException'The model should be of type: FormModel' );
            }
            
            
    // execute the model, this will return the DomDocument structure
            
    $domDocument $model->execute();
            if ( !
    $domDocument instanceof DomDocument ) {
                throw new 
    UnexpectedException'The model data should be of type: DomDocuemnt!' );
            }
            
            
    // 
            
    $xsltTemplate = new DOMDocument;
            
    $xsltTemplateFileName NamespacesResolver::getSiteTemplatesPath() . DIRECTORY_SEPARATOR 'default.xsl';
            if ( !
    file_exists$xsltTemplateFileName ) || !is_file$xsltTemplateFileName ) ) {
                throw new 
    UnexpectedException"The template file '" basename$xsltTemplateFileName ) . "' for the form transformer couldn't be found." );
            }

            echo 
    $domDocument->saveXml();
            
            
            
    // output        
            
    $xsltTemplate->load$xsltTemplateFileName );
            
    $transformation = new XSLTProcessor();
            
    $transformation ->importStyleSheet$xsltTemplate );
            return 
    $transformation->transformToXML$domDocument );
        }

    class.FormModel.inc.php
    PHP Code:
    class FormModel {
        private 
    $dom;
        protected 
    $systemNode;
            
        public function 
    __construct() {
            
    $this->dom = new DOMDocument'1.0''utf-8' );
            
    $rootElement $this->dom->createElement'document' );
            
    $this->systemNode $this->dom->createElement'system' );
            
    $rootElement->appendChild$this->systemNode );
            
    $this->dom->appendChild$rootElement );
        }

        public function 
    execute$taalcode='NL' ) {
           
    $database =& DatabaseFactory::getDatabaseInstanceConfiguration::fetch( array( 'Database''ConnectionString' ) ) ); 
            
    $recordset $database->execute"SELECT GROEPSNUMMER, OMSCHRIJVING FROM tbl_groepen WHERE TAALCODE = '" $taalcode "'" );
            
            
            
    $groupsElement $this->systemNode->appendChild$this->dom->createElement'groups' ) );
            foreach ( 
    $recordset as $record ) {
                
    $groupElement $this->dom->createElement'group' );
                
    $groupNumberAttribute $groupElement->appendChild( new DOMAttr'groupnumber'$record->GROEPSNUMMER ) );
                
                
    $groupNameElement $this->dom->createElement'name' );
                
    $groupNameElement->appendChild$this->dom->createTextNode$record->OMSCHRIJVING ) );
                
    $groupElement->appendChild$groupNameElement );
                
                
    $questionsElement $this->dom->createElement'questions' );
                
    $questionsRecordset $database->execute"SELECT 
                                                                                VRAAGNR, SERVERVELDTYPE, VRAAGOMSCHRIJVING, KEUZELIJST, KEUZELIJSTTYPE, VERPLICHT, DEFAULTWAARDE 
                                                                         FROM 
                                                                                 tbl_vragenlijst
                                                                        WHERE 
                                                                             GROEPSNUMMER =  " 
    $record->GROEPSNUMMER );
                foreach ( 
    $questionsRecordset as $questionRecord ) {
                     
    $questionElement $this->dom->createElement'question' );
                     
    $questionElement->appendChild( new DOMAttr'id'$questionRecord->VRAAGNR ) );
                     
    $questionElement->appendChild( new DOMAttr'mandatory'$questionRecord->VERPLICHT ) );

                     
    $vraagDefinition $this->dom->createElement'definition' );
                     
    $vraagDefinition->appendChild( new DOMAttr'type'$questionRecord->SERVERVELDTYPE ) );
                     
    $pickListType $this->dom->createElement'picklist' );
                     
    $pickListType->appendChild( new DOMAttr'multiselect'$questionRecord->KEUZELIJST ) );
                     
    $pickListType->appendChild$this->dom->createTextNode$questionRecord->KEUZELIJSTTYPE ) );
                     
    $vraagDefinition->appendChild$pickListType );
                     
                     
    $vraag $this->dom->createElement'description' );
                     
    $vraag->appendChild$this->dom->createTextNode$questionRecord->VRAAGOMSCHRIJVING ) );
                     
    $questionElement->appendChild$vraagDefinition);
                     
    $questionElement->appendChild$vraag );
                     
    $questionsElement->appendChild$questionElement );
                }
                
                
    $groupElement->appendChild$questionsElement );
                
                
    // add the group element to the element
                
    $groupsElement->appendChild$groupElement );
            }    
            
            
    // return the dom-xml structure
            
    return $this->dom;
        }    

    and at last the controller class that get instantiated by the Application-class, I made it know configurable through a configuraiton file.

    PHP Code:
    class FrontController implements IHandler {
        protected 
    $model;
        protected 
    $view;
        
        public function 
    __construct() {
            
        }
        
        public function 
    executeRequest $requestResponse $response ) {
            
    $this->view = new FormView();
            
            
    // output it as valid UTF-8 code <g>
            
    $mimetype    = ( stristr$_SERVER["HTTP_ACCEPT"], "application/xhtml+xml")) ? "application/xhtml+xml" "text/html";
            
    $response->setMimeType$mimetype ";charset=UTF-8" );
            
            try {
                
    $response->setContent$this->view->execute$model = new FormModel() ) );
            } catch ( 
    Exception $e ) {
                die( 
    "Internal Server Error<br />" $e->getMessage() );
            }
        }

    I would think the state machine would be a good way todo it, only problem would be the first state, though. Of course you would move forward after every succesfull form validation. But because every request will caus ethe state machine to be recreated you need to somehow keep track of the state... hmm session or hidden form field ?

    Beside of that DrLivingstone I will think about your idea, still need to grasp it, though. Let's print it out and read it again.

  16. #16
    SitePoint Guru Galo's Avatar
    Join Date
    May 2005
    Location
    Holland!
    Posts
    852
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by nacho
    I think that what you are calling a wizzard, I'm calling a workflow object. Does flow controller sound better?
    "FED - Front End Driver" , just teasing, flow controller is cool
    Business as usual is off the menu folks, ...

  17. #17
    SitePoint Zealot
    Join Date
    Mar 2004
    Location
    Australia
    Posts
    101
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    This is an example that I cooked up before

    http://xisc.com/demo/prado/examples/wizard.php

    The basic idea is to store the form data in viewstate (which may be stored in session or within the page via hidden input), and the wizard sub-pages are just parts of a single page with the current sub-page visible.

    The control structure is relatively simple, it maps commands (e.g. cancel, next, previous, jumpto, finish) to a set of designated function calls. Within these functions, you can co-ordinate the flow of the wizard, check for validations. In addition, each wizard step does have access to data within any step of the wizard. Finally, the data was saved at the very last step, after confirmation.

    Wei.

  18. #18
    SitePoint Addict
    Join Date
    May 2003
    Location
    The Netherlands
    Posts
    391
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    For the sake of simplicity I'll call the object from now on wizzard (M$ish but is OK for me).

    Wizzard Controller
    I'd make of this controller a form controller factory that depends on your state machine (or that follows an internal implementation of one). Depending on the stage you are, you would get an instance of the form controller you want to work with.

    Wizzard Model
    The model would store the validated data of each step of the process and, in case you implement the state internally, the step you're at. I suppose for the form data an array with numeric keys would be enough. Within every numeric key you could store an associative array with the data of each form.

    Wizzard View

    The wizzard could have its own view (I liked very much the example wei has made with PRADO) which could show graphically in which step of the process the user is at. This view would be a composite which would call the corresponding form view based on the form you're working at.

    I'll assume you usually call the form controller from some kind of document template controller. I'd then replace the call for a form controller for a call for a wizzard controller and let this one control which form to execute and to show. That way, you can default the wizzard to having one step and one form controller. If you were to need more steps/forms, you would have only to tell the wizzard controller how many and feed it with the corresponding form controllers for each step.

    Quote Originally Posted by Dr Livingston
    There may be an assocc. Logger object as well, which holds the appropriate error message(s), assigned to each piece of data (user input). Since the Logger may be variable (ie It's not determined), the State Machine would request the error message from the object, and not the Logger it's self.
    I'd try to keep validation in the same place it is now. Since I'd be calling the form controller I guess this should not have to be a problem.

    Quote Originally Posted by Dr Livingston
    Just what is a flow controller in plain english? To me there is no actual program flow, more a controlled iteration, as each of these objects are of the same Interface... They all have the same responsibility, just that the data is unique.
    Well, I don't agree. IMO there is a program flow, and although you're right that every form object implements the same interface (they're after all objects of the same class), they are also dependent of each other. In a multi-step process there is a tied relationship between every step. Normally, although not always, a step would be shown only after last step is been processed succesfully. If you were to treat every form independently you couldn't lay a relationship between them, which is exactly the special nature of the wizzard (better than flow controller? )

    Quote Originally Posted by wdeboer
    I would think the state machine would be a good way todo it, only problem would be the first state, though. Of course you would move forward after every succesfull form validation. But because every request will caus ethe state machine to be recreated you need to somehow keep track of the state... hmm session or hidden form field ?
    If you keep the wizzard controller in session you wouldn't have to worry about passing values around; simply recreate the wizzard controller after each request. The state machine (controller) injected in the wizzard controller or, in case you implement the state into the wizzard controller directly, its own internal state, would give you the parameter you need to recreate the corresponding form.

    Once again, kudos to wei. Impressive. I see PRADO is getting really professional.
    There’s more than one way to skin a cat.

  19. #19
    SitePoint Guru Galo's Avatar
    Join Date
    May 2005
    Location
    Holland!
    Posts
    852
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by wei
    This is an example that I cooked up before

    http://xisc.com/demo/prado/examples/wizard.php

    The basic idea is to store the form data in viewstate (which may be stored in session or within the page via hidden input), and the wizard sub-pages are just parts of a single page with the current sub-page visible.

    The control structure is relatively simple, it maps commands (e.g. cancel, next, previous, jumpto, finish) to a set of designated function calls. Within these functions, you can co-ordinate the flow of the wizard, check for validations. In addition, each wizard step does have access to data within any step of the wizard. Finally, the data was saved at the very last step, after confirmation.

    Wei.
    Like i said before, and now we have proof
    Business as usual is off the menu folks, ...

  20. #20
    SitePoint Zealot
    Join Date
    Jul 2005
    Posts
    194
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks, I will have a look at it.


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
  •