Commit 77181785 authored by Marcello Pivanti's avatar Marcello Pivanti
Browse files

Initial Commit

parents
This diff is collapsed.
THIS IS A **PROOF OF CONCEPT** THAT DEMONSTRATE HOW TO IMPLEMENT THE WIZARD UI PATTERN ON TOP OF THE **YII2-WORKFLOW** EXTENSION.
For more information about **yii2-workflow** please refer to the [User Guide](http://raoul2000.github.io/yii2-workflow/).
# A Wizard Implementation
The word *wizard* does not only refers to some Gandalf guy or any other old man wearing a funny hat and loaded with super magic power. In the world of user interface a wizard is a design pattern that can be applied in the case where *the user wants to achieve a single goal which can be broken down into dependable sub-tasks* (from [ui-patterns.com](http://ui-patterns.com/patterns/Wizard)).
To learn more about wizard, see [Designing Interfaces](http://designinginterfaces.com/patterns/wizard/).
## The problem
The problem is simple : how can we implement a complex step-by-step process that will guide the user to its final goal ? The complexity can come from the fact that depending on the choice made by the user, he/she can be guided through different steps (conditional branching). In other words, we are considering a case where there would be several path (ordered sequence of steps) to go from the first step to the last one (the goal).
The purpose of this Yii2 extension is to solve this problem using the workflow paradigm implemented by **yii2-workflow** extension.
## The Wizflow Solution
### Workflow Definition
A workflow can be viewed as a representation of all possibles steps that the user has to perform for navigate through a wizard widget. So, why not simply define a workflow representing all possible path of our wizard ? We could also use the **metadata** attributes provided by *yii2-workflow* to associate some data with each steps of our wizard (each *status* of our workflow).
We need :
- the model name
- the view name
Let's see that on a very basic wizard that will ask stupid questions to the user (like for instance *"what color do you prefer between green and blue"*) and depending on her reply, perform the corresponding transitions. On a more linear use case, we will also be able to go from one step to the other.
Here is our *wizflow* :
<img src="wizflow.png" alt="the wizflow" />
As you can see, the status *step1* is associated with a Yii2 model that contains attributes the user must select, and a Yii2 view name that represent the form to display to the user.
Defining the wokflow definition for this wizflow is easy : [check this out](https://github.com/raoul2000/yii2-wizflow/blob/master/example/models/Wizflow.php).
Read more about [Workflow Definition](http://raoul2000.github.io/yii2-workflow/workflow-creation/).
### The Wizflow Manager
Now last thing is a component able to navigate through this wizflow, based on user inputs and the current step (i.e. status). This component is the **WizflowManager**. The one provided with this extension is also responsible for path persistence, that is keep track of all successive steps the user has performed through the wizflow (a very basic session storage persistence is implemented).
If you take a look to the *[WizflowManager](https://github.com/raoul2000/yii2-wizflow/blob/master/src/WizflowManager.php)* code, you will see that the **getNextStep** method is in charge of providing the next status the user will reach when the button *NEXT* is pushed. To define what is the next step, the *WizflowManager* invokes the `SimpleWorkflowBehavior.getNextStatuses(true,true)` method which includes model validation. If more than one next step (next status) is available, the first one is used: **it is the developper responsability to make sure that validation rules and transitions only allow one next step**.
In the example included in the *yii-wizflow* extension the workflow above is configured. This workflow includes a branching between *step1* and the *blue* or *green* status. Depending on the user input, the validation rules must be exclusive in order to only select on possible destination :
- if the user select "blue" as favorite color, the next step is "blue"
- if the user select "green" as favorite color, the next step is "green"
That's the way branching in handled : validation rules applied at a given step should be configured in order to select **only one destination** as the *next step*. Again, this is developer responsability to ensure this is done correctly.
Read more about the [Workflow Driven Attribute validation](http://raoul2000.github.io/yii2-workflow/concept-validation/).
# Install Examples
The *yii2-wizflow* extension comes with a set of Yii2 models, controller and views dedicated to override the **basic Yii2 application template** and demonstrate yii-wizflow in action.
Here is a summary of commands to run to install the example Wizflow application. You'll find a detailed description below.
```
composer global require "fxp/composer-asset-plugin:~1.2.2"
composer create-project yiisoft/yii2-app-basic wizflow 2.0.9
cd wizflow
composer require elitedivision/amos-wizflow:@dev
cp vendor/elitedivision/amos-wizflow/example/* .
```
To install this demo app you must :
- create a new yii2 application based on the **basic** template. To do so, follow [Yii2 installation guide](http://www.yiiframework.com/download/)
- install the *amos-wizflow* extension
- override all folders at the root of your yii2 app with the ones provided in `vendor/elitedivision/amos-wizflow/example`
If you already have a running Yii2 application based on the *basic* template, here are the steps you can perform manually:
- declare the **WizflowManager** component in `APP_FOLDER/conf/web.php`. This component implements all the logic between the workflow definition and model/view. It also handle the persistence layer by saving user entries into the current session.
```php
'components' => [
'wizflowManager' => [
'class' => '\elitedivision\amos\wizflow\WizflowManager'
],
// etc ...
```
- declare the **workflowSource** component in `APP_FOLDER/conf/web.php`. This is the standard yii2-wizflow source component.
```php
'components' => [
'workflowSource' => [
// use default settings : workflow definition is stored in an object and can be
// retrieved with the getDefinition() method
'class' => '\raoul2000\workflow\source\file\WorkflowFileSource'
],
// etc ...
```
- add a new action in `APP_FOLDER/controllers/SiteController.php`. This action is used during wizard navigation.
```php
public function actions()
{
return [
// other actions ...
'wizflow' => [
'class' => '\elitedivision\amos\wizflow\WizardPlayAction'
],
];
}
```
- add the *finish* action in `APP_FOLDER/controllers/SiteController.php`. This action is invoked at the end of the wizard, to display a summary of choices made by the user.
```php
public function actionFinish()
{
return $this->render('finish',[
'path' => Yii::$app->wizflowManager->getPath()
]);
}
```
- navigate to [http://hostname/index.php?r=site/wizflow](http://host/index.php?r=site/wizflow)
- enjoy
{
"name": "arter/amos-wizflow",
"description": "The wizard UI pattern implemented using yii2-workflow",
"keywords": [
"amos",
"yii2",
"extension",
"workflow",
" wizard"
],
"type": "plugin",
"license": "BSD-3-Clause",
"minimum-stability": "stable",
"require": {
"php": ">=5.4.0",
"yiisoft/yii2": "*",
"raoul2000/yii2-workflow": "*"
},
"config": {
"process-timeout": 1800
},
"autoload": {
"psr-4": {
"elitedivision\\amos\\wizflow\\": "src"
}
},
"require-dev": {
"yiisoft/yii2-faker": "*"
}
}
\ No newline at end of file
<?php
/**
* Art-ER Attrattività, ricerca e territorio dell’Emilia-Romagna
* OPEN 2.0
*
* @package Open20Package
* @category CategoryName
* @author Elite Division S.r.l.
*/
$params = require(__DIR__ . '/params.php');
$config = [
'id' => 'basic',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log'],
'components' => [
'workflowSource' => [
'class' => 'raoul2000\workflow\source\file\WorkflowFileSource'
],
'wizflowManager' => [
'class' => '\elitedivision\amos\wizflow\WizflowManager',
'workflowSourceName' => 'workflowSource'
],
'request' => [
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
'cookieValidationKey' => '3GufHzdPMW2Fv8ZnM7Eq6X3iTLvvB5v0',
],
'cache' => [
'class' => 'yii\caching\FileCache',
],
'user' => [
'identityClass' => 'app\models\User',
'enableAutoLogin' => true,
],
'errorHandler' => [
'errorAction' => 'site/error',
],
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
[
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
],
],
],
]
];
if (YII_ENV_DEV) {
// configuration adjustments for 'dev' environment
$config['bootstrap'][] = 'debug';
$config['modules']['debug'] = [
'class' => 'yii\debug\Module',
];
$config['bootstrap'][] = 'gii';
$config['modules']['gii'] = [
'class' => 'yii\gii\Module',
];
}
return $config;
<?php
/**
* Art-ER Attrattività, ricerca e territorio dell’Emilia-Romagna
* OPEN 2.0
*
* @package Open20Package
* @category CategoryName
* @author Elite Division S.r.l.
*/
namespace app\controllers;
use Yii;
use yii\web\Controller;
class SiteController extends Controller
{
public function actions()
{
return [
'error' => [
'class' => 'yii\web\ErrorAction',
],
'wizflow' => [
'class' => '\elitedivision\amos\wizflow\WizardPlayAction'
]
];
}
public function actionFinish()
{
return $this->render('finish',[
'path' => Yii::$app->wizflowManager->getPath()
]);
}
public function actionIndex()
{
return $this->render('index');
}
}
<?php
/**
* Art-ER Attrattività, ricerca e territorio dell’Emilia-Romagna
* OPEN 2.0
*
* @package Open20Package
* @category CategoryName
* @author Elite Division S.r.l.
*/
namespace app\models;
use yii\base\Model;
use yii\helpers\Html;
/**
* ContactForm is the model behind the contact form.
*/
class BlueForm extends Model implements \elitedivision\amos\wizflow\WizflowModelInterface
{
public $blueStuff;
public $status;
/**
* @return array the validation rules.
*/
public function rules()
{
return [
[['blueStuff'], 'required'],
];
}
public function summary()
{
return 'blue like : ' . Html::encode($this->blueStuff);
}
/**
* @return array customized attribute labels
*/
public function attributeLabels()
{
return [
'greenStuff' => 'name somthing blue',
];
}
}
<?php
/**
* Art-ER Attrattività, ricerca e territorio dell’Emilia-Romagna
* OPEN 2.0
*
* @package Open20Package
* @category CategoryName
* @author Elite Division S.r.l.
*/
namespace app\models;
use yii\base\Model;
/**
* ContactForm is the model behind the contact form.
*/
class FinalForm extends Model implements \elitedivision\amos\wizflow\WizflowModelInterface
{
public $rate;
public $status;
/**
* @return array the validation rules.
*/
public function rules()
{
return [
[['rate'], 'required'],
];
}
public function summary()
{
return 'this is : ' . $this->rate;
}
/**
* @return array customized attribute labels
*/
public function attributeLabels()
{
return [
'rate' => 'Did you like your wizflow experience ?',
];
}
}
<?php
/**
* Art-ER Attrattività, ricerca e territorio dell’Emilia-Romagna
* OPEN 2.0
*
* @package Open20Package
* @category CategoryName
* @author Elite Division S.r.l.
*/
namespace app\models;
use yii\base\Model;
use yii\helpers\Html;
/**
* ContactForm is the model behind the contact form.
*/
class GreenForm extends Model implements \elitedivision\amos\wizflow\WizflowModelInterface
{
public $greenStuff;
public $status;
/**
* @return array the validation rules.
*/
public function rules()
{
return [
[['greenStuff'], 'required'],
];
}
public function summary()
{
return 'green like : ' . Html::encode($this->greenStuff);
}
/**
* @return array customized attribute labels
*/
public function attributeLabels()
{
return [
'greenStuff' => 'name somthing green',
];
}
}
<?php
/**
* Art-ER Attrattività, ricerca e territorio dell’Emilia-Romagna
* OPEN 2.0
*
* @package Open20Package
* @category CategoryName
* @author Elite Division S.r.l.
*/
namespace app\models;
use raoul2000\workflow\validation\WorkflowScenario;
use yii\base\Model;
/**
* ContactForm is the model behind the contact form.
*/
class Step1Form extends Model implements \elitedivision\amos\wizflow\WizflowModelInterface
{
public $favoriteColor;
public $status;
/**
* @return array the validation rules.
*/
public function rules()
{
return [
// name, email, subject and body are required
[['favoriteColor'], 'required'],
[['favoriteColor'], 'compare', 'compareValue' => 'blue', 'operator' => '==',
'on' => WorkflowScenario::enterStatus('Wizflow/blue')],
[['favoriteColor'], 'compare', 'compareValue' => 'green', 'operator' => '==',
'on' => WorkflowScenario::enterStatus('Wizflow/green')],
];
}
public function summary()
{
return 'your favorite color is ' . $this->favoriteColor;
}
/**
* @return array customized attribute labels
*/
public function attributeLabels()
{
return [
'favoriteColor' => 'your favorite color',
];
}
}
<?php
/**
* Art-ER Attrattività, ricerca e territorio dell’Emilia-Romagna
* OPEN 2.0
*
* @package Open20Package
* @category CategoryName
* @author Elite Division S.r.l.
*/
namespace app\models;
use yii\base\Model;
use yii\helpers\Html;
/**
* ContactForm is the model behind the contact form.
*/
class WelcomeForm extends Model implements \elitedivision\amos\wizflow\WizflowModelInterface
{
public $name;
public $email;
public $status;
/**
* @return array the validation rules.
*/
public function rules()
{
return [
[['name', 'email'], 'required'],
['email', 'email'],
];
}
public function summary()
{
return 'Hello <b>' . Html::encode($this->name) . '</b>';
}
}
<?php
/**
* Art-ER Attrattività, ricerca e territorio dell’Emilia-Romagna
* OPEN 2.0
*
* @package Open20Package
* @category CategoryName
* @author Elite Division S.r.l.
*/
namespace app\models;
use raoul2000\workflow\source\file\IWorkflowDefinitionProvider;
/**
* This is the workflow definition. Metadata is used at each wizard step
* to store model and view names.
*/
class Wizflow implements IWorkflowDefinitionProvider
{
public function getDefinition()
{
return [
'initialStatusId' => 'welcome',
'status' => [
'welcome' => [
'transition' => ['step1'],
'metadata' => [
'model' => [
'class' => '\app\models\WelcomeForm'
],
'view' => 'step-welcome'
]
],
'step1' => [
'transition' => ['blue', 'green'],
'metadata' => [
'model' => [
'class' => '\app\models\Step1Form'
],
'view' => 'step-1'
]
],
'blue' => [
'transition' => ['final'],
'metadata' => [
'model' => [
'class' => '\app\models\BlueForm'
],
'view' => 'step-blue'
]
],
'green' => [
'transition' => ['final'],
'metadata' => [
'model' => [
'class' => '\app\models\GreenForm'
],
'view' => 'step-green'
]
],
'final' => [
'transition' => [],
'metadata' => [
'model' => [
'class' => '\app\models\FinalForm'
],
'view' => 'step-final'
]
]
]
];
}
}
<?php
/**
* Art-ER Attrattività, ricerca e territorio dell’Emilia-Romagna
* OPEN 2.0
*
* @package Open20Package
* @category CategoryName
* @author Elite Division S.r.l.
*/
/* @var $this \yii\web\View */
/* @var $content string */
use app\assets\AppAsset;
use yii\bootstrap\Nav;
use yii\bootstrap\NavBar;
use yii\helpers\Html;
use yii\widgets\Breadcrumbs;
AppAsset::register($this);
?>
<?php $this->beginPage() ?>
<!DOCTYPE html>
<html lang="<?= Yii::$app->language ?>">
<head>
<meta charset="<?= Yii::$app->charset ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<?= Html::csrfMetaTags() ?>
<title><?= Html::encode($this->title) ?></title>
<?php $this->head() ?>
</head>
<body>
<?php $this->beginBody() ?>
<div class="wrap">
<?php
NavBar::begin([
'brandLabel' => 'Wizflow Demo App',
'brandUrl' => Yii::$app->homeUrl,
'options' => [
'class' => 'navbar-inverse navbar-fixed-top',