Wizards and MVC

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Re: Wizards and MVC

Post by Jenk »

Another way to look at it, is that a Wizard is just a paginated form.
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: Wizards and MVC

Post by VladSun »

Jenk wrote:Another way to look at it, is that a Wizard is just a paginated form.
That's only the "linear" wizard, IMHO
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Re: Wizards and MVC

Post by Jenk »

There's another type of wizard? :P
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Re: Wizards and MVC

Post by John Cartwright »

Jenk wrote:There's another type of wizard? :P
I imagine he is refering to "wizards" where steps are not required to be completed sequentially.
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Re: Wizards and MVC

Post by Jenk »

That's even more like a paginated form than a linear wizard then! :P
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Wizards and MVC

Post by Christopher »

Jenk wrote:There's another type of wizard? :P
I think paginated forms is a simple way to view a wizard (and not a bad one necessarily) . However there are a number of cases where there are either multiple paths through a sequence or where certain steps can be skipped. I gave the checkout process as a place where I often use an Application Controller. Here are some examples of the non-linear cases I mentioned using checkout as a use case.

A common step in the checkout process is to require the customer to either log-in or register. However, if the customer is already logged-in then this step can be skipped and they can go directly to confirming the order, their address, or whatever the next step would be. This log-in or register also shows the idea of multiple paths through the sequence.

Another example of multiple paths I have implemented is different steps for different types of customers. For example, there my be regular customers and business customers. The registration and checkout sequences may have more options or be more involved for business customers.

As Josh said, all this can be done with if()'s in your controllers. I think most people don't have Application Controller code available or perhaps don't like the more event driven style of setting up states and transitions for it require.
(#10850)
User avatar
kr1pt
Forum Newbie
Posts: 8
Joined: Sun Jan 30, 2011 4:38 am
Location: Croatia

Re: Wizards and MVC

Post by kr1pt »

VladSun wrote:How do you guys write your wizard pages in a way that fits the MVC approach? I'm not interested in passing the current model state. I'm more interested in the M-V-C relations defined.

The problem I'm facing is that the Controller doesn't know the logic flow to follow - it's in the Model.
In homepage, Router checks if there is controller selected in URL. If isn't, script gets default controller from configuration file and instantiates it. If method for controller is not set, it loads $controller->index() method. If not, loads: $controller->$selected_method();

Abstract class "Base controller", has function for loading models, and accessing them through variable objects. $this->my_model = new Model();

And controller goes and checks for data in model, and just renders it.

Controller example:

Code: Select all

class Blog // implements Controller; extends Controller
{
    public function index()
    {
        $this->get_view('blog_index');
    }
    
    public function new_post()
    {
        $this->get_view('blog_new_post');
    }
    
    public function get_post($id = null)
    {
        if (!is_null($id))
        {
            $this->get_model('My_Model');
            
            $this->my_model->get_post_by_id($id);
            
            $this->variables['posts'] = $this->my_model->get_post_by_id($id);
            
            /**
             * View file:
             * 
             * foreach ($this->variables as $variable => $key)
             *  return $key
             */
            $this->get_view('blog_get_post');
        }
    }
}
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: Wizards and MVC

Post by VladSun »

kr1pt, it's not the "wizard" I meant.
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: Wizards and MVC

Post by VladSun »

I faced two problems:
1) A "silent" stage - i.e. stage that will be executed, but its View is not rendered ever. Input data need some processing, but no user interaction needed - just jumping to the next stage. [ SOLVED ]
2) A "repeated" stage - i.e. a stage (together with substages hierarchy) that should be repeated several times (in a loop) [ STILL SOLVING IT]
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Wizards and MVC

Post by Christopher »

Again, the point of an Application Controller is that the "stages" do not need to know any of this -- the rules/conditions manage the program flow. So silent or repeated are irrelevant.
(#10850)
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: Wizards and MVC

Post by VladSun »

What I have by now is:

(it's JS, remember? And it has some AJAX calls in it, so it's all asynchronous)

Application.js

Code: Select all

		this.configurator	= new Ext.App.Model.Configurator();
		
		this.wizard = new Ext.ex.Wizard.Panel(
		{
			configurator	: this.configurator,
			router		: new Ext.ex.Wizard.Router(
			{
				map	:
				[
					{
						object	: new Ext.App.Component.Wizard.Stage.FileUpload(),
						stages	:
						[
							{
								object	: new Ext.App.Component.Wizard.Stage.SchemaInfo()
							},
							{
								object	: new Ext.App.Component.Wizard.Stage.TablesPicker()
							},
							{
								object	: new Ext.App.Component.Wizard.Stage.RelationsEditor()
							}
						]
					}
				]
			})
		});
Every stage object extends the Ext.ex.Wizard.Stage.Component:

Code: Select all

Ext.ex.Wizard.Stage.Component = function(config)
{
    Ext.apply(this, config);
    Ext.ex.Wizard.Stage.Component.superclass.constructor.call(this, config || {});

	this.addEvents(
	{
		stageInitialized	: true,
		stagePrepared		: true,
		stageCancelled		: true,
		stageCompleted		: true,
		stageFailed		: true,
		stageReady		: true
	});
}

Ext.extend(Ext.ex.Wizard.Stage.Component, Ext.Panel,
{
	model		: null,

	init	: function (model)
	{
		this.model = model;
		this.fireEvent('stageInitialized', true, this);
	},

	prepare	: function ()
	{
		this.fireEvent('stagePrepared', true, this);
	},

	redraw	: function ()
	{
	},

	process	: function ()
	{
		this.fireEvent('stageCompleted', true, this);
	},

	cancel	: function ()
	{
		this.reset();
		this.fireEvent('stageCancelled', true, this);
	},

	reset	: function ()
	{
	}
});
Stages notify listeners (i.e. the wizard) about their state. The Wizard makes decision what to do.

After all stages report "stageInitialized" Wizard gets the first available stage object from the Router and initialize it.

On "stageReady" Wizard checks whether the stage "requests" to be executed or not. If not, the next stage object is requested from the router.

On stageProcess the stage is executed. On stageCompleted Wizard gets the next stage from the Router. Etc.

So, on every step my stages decide whether their are needed or not. It's done by passing the current configurator/model state.

An absolutely centralized logic model would be too complicated to define and implement (sounds like a God object IMHO).
I just define the routes, but the stage object itself decides whether it should be executed or not.
Last edited by VladSun on Fri Feb 04, 2011 7:15 am, edited 1 time in total.
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: Wizards and MVC

Post by VladSun »

Maybe the JS example is too complicated because of its AJAX nature. In PHP that would be something like this:

Code: Select all

interface IStage
{
	function setModel($model);

	function preWizardInit(); // check for some preconditions met before the Wizard starts. Run at Wizard init.
	function didPreWizardInitSucceeded(); // shell we run the Wizard at all?

	function preProcess(); // check for some condtions (together with $model current data). Run "before" stage.
	function didPreProcessSucceeded(); // Stage has errors? If yes Wizard stops - Next not available, Prev available
	function isStageSilent(); // Does Stage need a View or it may process silently then jumping to the next stage
	function isStageRequired(); // Is Stage required at all, if not all substages are not required either.

	function process(); // If isStageRequired == true, execute this method
	function didProcessSucceeded(); // Stage has Errors?
}

Code: Select all

class Router
{
	public function setMap($map);
	public function setStateManager($stateManager);
	public function getNextStage(); // returns IStage
}

Code: Select all

class Wizard extends ActionController
{
	public function __construct($router, $stateManager);

	public function nextStage();
	public function finish();
}
An example of usage:

Code: Select all

class MyWizard extends Wizard
{
	public function __construct()
	(
		parent::__construct
		(
			new Router
			(
				array
				(
					array
					(
						'stage' => new Stage1(),
						'substages' => array
						(
							array
							(
								'stage'	=> new Stage1_1(),
								'substages' => null
							),
							array
							(
								'stage'	=> new Stage1_2(),
								'substages' => array
								(
									'stage'	=> new Stage1_2_1(),
									'substages' => null
								),
							),
						)
					),
					array
					(
						'stage' => new Stage2(),
						'substages' => null
					),
				)
			),
			new StateManager()
		);
	)
}
There are 10 types of people in this world, those who understand binary and those who don't
Post Reply