Add multistep form

refs #6136
This commit is contained in:
Johannes Meyer 2014-05-13 14:24:51 +02:00
parent e1230eb8ae
commit 4cf5fe6fdd
4 changed files with 522 additions and 27 deletions

View File

@ -3,11 +3,9 @@
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
require_once 'Mockery/Loader.php';
$mockeryLoader = new \Mockery\Loader;
$mockeryLoader->register();
use \Mockery;
use Icinga\Web\Session;
use Icinga\Web\Wizard\Page;
use Icinga\Web\Wizard\Wizard;
use Icinga\Web\Controller\ActionController;
class InstallController extends ActionController
@ -21,24 +19,82 @@ class InstallController extends ActionController
*/
protected $requiresAuthentication = false;
/**
* Show the wizard and run the installation once its finished
*/
public function indexAction()
{
$finished = false;
$this->view->installer = 'some log info, as html';
$this->view->wizard = Mockery::mock();
$this->view->wizard->shouldReceive('isFinished')->andReturn($finished)
->shouldReceive('getTitle')->andReturn('Web')
->shouldReceive('getPages')->andReturnUsing(function () {
$a = array(Mockery::mock(array('getTitle' => 'childTest', 'getChildPages' => array(
Mockery::mock(array('getTitle' => 'child1')),
Mockery::mock(array('getTitle' => 'child2'))
), 'isActiveChild' => false))); for ($i=0;$i<10;$i++) { $a[] = Mockery::mock(array('getTitle' => 'title'.$i, 'getChildPages' => array())); } return $a;
})
->shouldReceive('isActivePage')->andReturnUsing(function ($p) { return $p->getTitle() == 'title4'; })
->shouldReceive('isCompletedPage')->andReturnUsing(function ($p) { return $p->getTitle() < 'title4'; })
->shouldReceive('getActivePage')->andReturnUsing(function () {
return Mockery::mock(array('getTitle' => 'title4', '__toString' => 'teh form elements'));
});
$wizard = $this->createWizard();
$wizard->navigate(); // Needs to be called before isSubmittedAndValid() as this creates the form
if ($wizard->isSubmittedAndValid() && $wizard->isFinished()) {
// TODO: Run the installer (Who creates an installer? How do we handle module installers?)
$this->dropConfiguration(); // TODO: Should only be done if the installation has been successfully completed
$this->view->installer = '';
} else {
$this->storeConfiguration($wizard->getConfig());
}
$this->view->wizard = $wizard;
}
/**
* Create the wizard and register all pages
*
* @return Wizard
*/
protected function createWizard()
{
$wizard = new Wizard();
$wizard->setTitle('Web');
$wizard->setRequest($this->getRequest());
$wizard->setConfiguration($this->loadConfiguration());
$wizard->addPages(
array(
'1st step' => new Page(),
'2nd step' => new Page(),
'3rd step' => new Page(),
'a wizard' => array(
'4th step' => new Page(),
'5th step' => new Page()
),
'last step' => new Page()
)
);
return $wizard;
}
/**
* Store the given configuration values
*
* @param Zend_Config $config The configuration
*/
protected function storeConfiguration(Zend_Config $config)
{
$session = Session::getSession();
$session->getNamespace('WebWizard')->setAll($config->toArray(), true);
$session->write();
}
/**
* Load all configuration values
*
* @return Zend_Config
*/
protected function loadConfiguration()
{
return new Zend_Config(Session::getSession()->getNamespace('WebWizard')->getAll(), true);
}
/**
* Clear all stored configuration values
*/
protected function dropConfiguration()
{
$session = Session::getSession();
$session->removeNamespace('WebWizard');
$session->write();
}
}

View File

@ -6,17 +6,23 @@
<div class="sidebar">
<ul>
<?php foreach ($wizard->getPages() as $page): ?>
<li class="<?= $wizard->isActivePage($page) ? 'active' : (
$wizard->isCompletedPage($page) ? 'complete' : 'pending'
<li class="<?= $wizard->isCompletedPage($page) ? 'complete' : (
$wizard->isCurrentPage($page) ? 'active' : 'pending'
); ?>">
<?= $page->getTitle(); ?>
<?php if ($wizard->isWizard($page) && $wizard->isCurrentPage($page) && !$wizard->isCompletedPage($page)): ?>
<ul>
<?php foreach ($page->getChildPages() as $child): ?>
<li class="child<?= $page->isActiveChild($child) ? ' active' : ''; ?>">
<?= $child->getTitle(); ?>
<?php foreach ($page->getPages() as $subPage): ?>
<?php if ($page->isCurrentPage($subPage)): ?>
<li class="child active">
<?php else: ?>
<li class="child">
<?php endif ?>
<?= $subPage->getTitle(); ?>
</li>
<?php endforeach ?>
</ul>
<?php endif ?>
</li>
<?php endforeach ?>
<li class="install<?= $wizard->isFinished() ? ' active' : ''; ?>">
@ -28,7 +34,7 @@
<?php if ($wizard->isFinished()): ?>
<?= $this->partial('install/index/installog.phtml', array('installer' => $installer)); ?>
<?php else: ?>
<?= $wizard->getActivePage(); ?>
<?= $wizard; ?>
<?php endif ?>
</div>
</div>

View File

@ -0,0 +1,54 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Web\Wizard;
use Icinga\Web\Form;
class Page extends Form
{
/**
* Whether a CSRF token should not be added to this wizard page
*
* @var bool
*/
protected $tokenDisabled = true;
/**
* The title of this wizard page
*
* @var string
*/
protected $title = '';
/**
* Set the title for this wizard page
*
* @param string $title The title to set
*/
public function setTitle($title)
{
$this->title = $title;
}
/**
* Return the title of this wizard page
*
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Return a config containing all values of this wizard page
*
* @return Zend_Config
*/
public function getConfig()
{
return $this->getConfiguration();
}
}

View File

@ -0,0 +1,379 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Web\Wizard;
use Zend_Config;
use Icinga\Web\Form;
use Icinga\Exception\ProgrammingError;
class Wizard extends Page
{
/**
* Whether this wizard has been completed
*
* @var bool
*/
protected $finished = false;
/**
* The wizard pages
*
* @var array
*/
protected $pages = array();
/**
* Return the wizard pages
*
* @return array
*/
public function getPages()
{
return $this->pages;
}
/**
* Add a new page to this wizard
*
* @param Page $page The page to add
*/
public function addPage(Page $page)
{
$ident = $this->generatePageIdentifier($page);
$wizardConfig = $this->getConfig();
if ($wizardConfig->get($ident) === null) {
$wizardConfig->{$ident} = new Zend_Config(array(), true);
}
$page->setTokenDisabled(); // Usually default for pages, but not for wizards
$page->setConfiguration($wizardConfig->{$ident});
$page->setRequest($this->getRequest());
$page->setName($ident);
$this->pages[] = $page;
}
/**
* Add multiple pages to this wizard
*
* The given array's keys are titles and its values are class names to add
* as wizard pages. An array as value causes a sub-wizard being added.
*
* @param array $pages The pages to add to the wizard
*/
public function addPages(array $pages)
{
foreach ($pages as $title => $pageClassOrArray) {
if (is_array($pageClassOrArray)) {
$wizard = new static();
$wizard->setTitle($title);
$this->addPage($wizard);
$wizard->addPages($pageClassOrArray);
} elseif (is_string($pageClassOrArray)) {
$page = new $pageClassOrArray();
$page->setTitle($title);
$this->addPage($page);
} else {
$pageClassOrArray->setTitle($title);
$this->addPage($pageClassOrArray);
}
}
}
/**
* Return this wizard's progress
*
* @param int $default The step to return in case this wizard has no progress information yet
*
* @return int The current step
*/
public function getProgress($default = 0)
{
return $this->getConfig()->get('progress', $default);
}
/**
* Set this wizard's progress
*
* @param int $step The current step
*/
public function setProgress($step)
{
$config = $this->getConfig();
$config->progress = $step;
}
/**
* Return the current page
*
* @return Page
*
* @throws ProgrammingError In case there are not any pages registered
*/
public function getCurrentPage()
{
$pages = $this->getPages();
if (empty($pages)) {
throw new ProgrammingError('This wizard has no pages');
}
return $pages[$this->getProgress()];
}
/**
* Return whether the given page is the current page
*
* @param Page $page The page to check
*
* @return bool
*/
public function isCurrentPage(Page $page)
{
return $this->getCurrentPage() === $page;
}
/**
* Return whether the given page is the first page in the wizard
*
* @param Page $page The page to check
*
* @return bool
*
* @throws ProgrammingError In case there are not any pages registered
*/
public function isFirstPage(Page $page)
{
$pages = $this->getPages();
if (empty($pages)) {
throw new ProgrammingError('This wizard has no pages');
}
return $pages[0] === $page;
}
/**
* Return whether the given page has been completed
*
* @param Page $page The page to check
*
* @return bool
*
* @throws ProgrammingError In case there are not any pages registered
*/
public function isCompletedPage(Page $page)
{
$pages = $this->getPages();
if (empty($pages)) {
throw new ProgrammingError('This wizard has no pages');
}
return $this->isFinished() || array_search($page, $pages, true) < $this->getProgress();
}
/**
* Return whether the given page is the last page in the wizard
*
* @param Page $page The page to check
*
* @return bool
*
* @throws ProgrammingError In case there are not any pages registered
*/
public function isLastPage(Page $page)
{
$pages = $this->getPages();
if (empty($pages)) {
throw new ProgrammingError('This wizard has no pages');
}
return $pages[count($pages) - 1] === $page;
}
/**
* Return whether this wizard has been completed
*
* @return bool
*/
public function isFinished()
{
return $this->finished && $this->isLastPage($this->getCurrentPage());
}
/**
* Return whether the given page is a wizard
*
* @param Page $page The page to check
*
* @return bool
*/
public function isWizard(Page $page)
{
return $page instanceof self;
}
/**
* Return whether either the back- or next-button was clicked
*
* @see Form::isSubmitted()
*/
public function isSubmitted()
{
$checkData = $this->getRequest()->getParams();
return isset($checkData['btn_return']) || isset($checkData['btn_advance']);
}
/**
* Update the wizard's progress
*
* @param bool $lastStepIsLast Whether the last step of this wizard is actually the very last one
*/
public function navigate($lastStepIsLast = true)
{
$currentPage = $this->getCurrentPage();
if (($pageName = $this->getRequest()->getParam('btn_advance'))) {
if (!$this->isWizard($currentPage) || $currentPage->navigate(false) || $currentPage->isFinished()) {
if ($this->isLastPage($currentPage) && (!$lastStepIsLast || $pageName === 'install')) {
$this->finished = true;
} else {
$pages = $this->getPages();
$newStep = $this->getProgress() + 1;
if (isset($pages[$newStep]) && $pages[$newStep]->getName() === $pageName) {
$this->setProgress($newStep);
}
}
}
} elseif (($pageName = $this->getRequest()->getParam('btn_return'))) {
if ($this->isWizard($currentPage) && $currentPage->getProgress() > 0) {
$currentPage->navigate(false);
} elseif (!$this->isFirstPage($currentPage)) {
$pages = $this->getPages();
$newStep = $this->getProgress() - 1;
if ($pages[$newStep]->getName() === $pageName) {
$this->setProgress($newStep);
}
}
}
$config = $this->getConfig();
$config->{$currentPage->getName()} = $currentPage->getConfig();
}
/**
* Return a unique identifier for the given page
*
* @param Page $page The page for which to return the identifier
*
* @return string The page's unique identifier
*/
protected function generatePageIdentifier(Page $page)
{
if (($name = $page->getName())) {
return $name;
}
$pageClass = get_class($page);
$wizardConfig = $this->getConfig();
if ($wizardConfig->get('page_names') === null || $wizardConfig->page_names->get($pageClass) === null) {
$wizardConfig->page_names = $wizardConfig->get('page_names', new Zend_Config(array(), true));
$wizardConfig->page_names->{$pageClass} = sprintf('%06x', mt_rand(0, 0xffffff));
}
return $wizardConfig->page_names->{$pageClass};
}
/**
* Setup the current wizard page
*/
protected function create()
{
$currentPage = $this->getCurrentPage();
if ($this->isWizard($currentPage)) {
$this->createWizard($currentPage);
} else {
$this->createPage($currentPage);
}
}
/**
* Display the given page as this wizard's current page
*
* @param Page $page The page
*/
protected function createPage(Page $page)
{
$pages = $this->getPages();
$currentStep = $this->getProgress();
$this->addSubForm($page, $page->getName());
if (!$this->isFirstPage($page)) {
$this->addElement(
'button',
'btn_return',
array(
'type' => 'submit',
'label' => t('Previous'),
'value' => $pages[$currentStep - 1]->getName()
)
);
}
$this->addElement(
'button',
'btn_advance',
array(
'type' => 'submit',
'label' => $this->isLastPage($page) ? t('Install') : t('Next'),
'value' => $this->isLastPage($page) ? 'install' : $pages[$currentStep + 1]->getName()
)
);
}
/**
* Display the current page of the given wizard as this wizard's current page
*
* @param Wizard $wizard The wizard
*/
protected function createWizard(Wizard $wizard)
{
$isFirstPage = $this->isFirstPage($wizard);
$isLastPage = $this->isLastPage($wizard);
$currentSubPage = $wizard->getCurrentPage();
$isFirstSubPage = $wizard->isFirstPage($currentSubPage);
$isLastSubPage = $wizard->isLastPage($currentSubPage);
$this->addSubForm($currentSubPage, $currentSubPage->getName());
if (!$isFirstPage || !$isFirstSubPage) {
$pages = $isFirstSubPage ? $this->getPages() : $wizard->getPages();
$currentStep = $isFirstSubPage ? $this->getProgress() : $wizard->getProgress();
$this->addElement(
'button',
'btn_return',
array(
'type' => 'submit',
'label' => t('Previous'),
'value' => $pages[$currentStep - 1]->getName()
)
);
}
$pages = $isLastSubPage ? $this->getPages() : $wizard->getPages();
$currentStep = $isLastSubPage ? $this->getProgress() : $wizard->getProgress();
$this->addElement(
'button',
'btn_advance',
array(
'type' => 'submit',
'label' => $isLastPage && $isLastSubPage ? t('Install') : t('Next'),
'value' => $isLastPage && $isLastSubPage ? 'install' : $pages[$currentStep + 1]->getName()
)
);
}
}