Skip to content
Snippets Groups Projects
Commit f5e01de7 authored by Vitaliy Bohomazyuk's avatar Vitaliy Bohomazyuk
Browse files

Dev version

parent e753ed92
Branches 8.x 8.x-1.0-alpha1
Tags 8.x-1.0-alpha1 8.x-1.0-alpha2
No related merge requests found
.multi-steps-label {
}
.multi-steps-label .step-label {
display: inline-block;
padding-right: 20px;
padding-top: 10px;
padding-bottom: 10px;
}
.multi-steps-label .step-label.active {
color: #0074bd;
}
name: 'simple_multistep'
type: module
description: 'Provide simple multi step form'
core: 8.x
package: 'Custom'
dependencies:
- field_group
\ No newline at end of file
simple_multistep:
css:
theme:
css/main.css: {}
\ No newline at end of file
<?php
/**
* @file
* Contains simple_multistep.module.
*/
use Drupal\Core\Form\FormStateInterface;
use Drupal\simple_multistep\MultistepController;
/**
* Implements hook_form_alter().
*/
function simple_multistep_form_alter(&$form, FormStateInterface &$form_state) {
// Check if form using form group multi step field.
if (_check_form_multistep($form)) {
/** @var \Drupal\simple_multistep\MultistepController $multiStep */
if ($multiStep = $form_state->get('multistep_controller')) {
$multiStep->updateStepInfo();
}
else {
$multiStep = new MultistepController($form, $form_state);
}
$multiStep->rebuildForm($form);
$form_state->set('multistep_controller', $multiStep);
// Attach style library.
$form['#attached']['library'][] = 'simple_multistep/simple_multistep';
}
}
/**
* Validator handler for next button.
*/
function simple_multistep_register_next_step(&$form, FormStateInterface $form_state) {
/** @var \Drupal\simple_multistep\MultistepController $multiStep */
$multiStep = $form_state->get('multistep_controller');
// Need update form state after submission.
$multiStep->setFormState($form_state);
$multiStep->saveInputValues();
$multiStep->saveStoredValues();
$multiStep->increaseStep();
// Fill field value previous step.
$stored_input = $multiStep->getInputValues();
$current_step = $multiStep->getCurrentStep();
if (isset($stored_input[$current_step]) && !empty($stored_input[$current_step])) {
$form_state->setUserInput($stored_input[$current_step]);
}
$form_state->set('multistep_controller', $multiStep);
$form_state->setRebuild();
}
/**
* Validator handler for back button.
*/
function simple_multistep_register_back(&$form, FormStateInterface $form_state) {
/** @var \Drupal\simple_multistep\MultistepController $multiStep */
$multiStep = $form_state->get('multistep_controller');
// Need update form state after submission.
$multiStep->setFormState($form_state);
// If current_step more than 0.
if ($multiStep->getCurrentStep()) {
$multiStep->reduceStep();
// Fill field value previous step.
$stored_input = $multiStep->getInputValues();
$current_step = $multiStep->getCurrentStep();
if (isset($stored_input[$current_step]) && !empty($stored_input[$current_step])) {
$form_state->setUserInput($stored_input[$current_step]);
}
$form_state->set('multistep_controller', $multiStep);
$form_state->setRebuild();
}
}
/**
* Validation handler.
*/
function simple_multistep_multistep_validate(&$form, FormStateInterface &$form_state) {
/** @var \Drupal\simple_multistep\MultistepController $multiStep */
$multiStep = $form_state->get('multistep_controller');
// Need update form state after submission.
$multiStep->setFormState($form_state);
$stored_values = $multiStep->getStoredValues();
if (!empty($stored_values)) {
foreach ($stored_values as $step => $value_list) {
foreach ($value_list as $field_name => $field_value) {
$form_state->setValue($field_name, $field_value);
}
}
}
$form_state->set('multistep_controller', $multiStep);
}
/**
* Check if valid multi step form.
*
* @param array $form
* Form array.
*
* @return bool
* TRUE if form multi step.
*/
function _check_form_multistep(array $form) {
if (isset($form['#fieldgroups']) && !empty($form['#fieldgroups'])) {
foreach ($form['#fieldgroups'] as $fieldgroup) {
if (is_object($fieldgroup) && $fieldgroup->format_type == 'form_step') {
return TRUE;
}
}
}
return FALSE;
}
\ No newline at end of file
<?php
namespace Drupal\simple_multistep;
use Drupal\Core\Form\FormStateInterface;
/**
* Class FormButton.
*
* @package Drupal\simple_multistep
*/
class FormButton extends FormStep {
/**
* Constructor.
*
* @param array $form
* Form settings.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state object.
* @param int $current_step
* Current step.
*/
public function __construct(array $form, FormStateInterface $form_state, $current_step) {
parent::__construct($form, $form_state);
$this->currentStep = $current_step;
$this->fetchStepSettings();
}
/**
* Show back button.
*
* @param array $form
* Reference to form array.
*/
private function showBackButton(array &$form) {
$step_format_settings = $this->stepSettings->format_settings;
if ($this->currentStep != 0 && !empty($step_format_settings['back_button_show'])) {
// Add back button and remove validation.
$form['actions']['back_button'] = array(
'#type' => 'button',
'#value' => $step_format_settings['back_button_text'],
'#validate' => array('simple_multistep_register_back'),
'#submit' => array(),
'#limit_validation_errors' => array(),
);
}
}
/**
* Show next button.
*
* @param array $form
* Reference to form array.
*/
private function showNextButton(array &$form) {
$step_format_settings = $this->stepSettings->format_settings;
if (count($this->steps) - 1 != $this->currentStep) {
$form['actions']['next'] = array(
'#type' => 'button',
'#value' => $step_format_settings['next_button_text'],
'#validate' => array('simple_multistep_register_next_step'),
'#submit' => array(),
);
$form['actions']['submit']['#access'] = FALSE;
}
// On last step hide next button and show save button.
else {
$form['actions']['submit']['#access'] = TRUE;
$form['#validate'][] = 'simple_multistep_multistep_validate';
}
}
/**
* Render form button.
*
* @param array $form
* Form array.
*/
public function render(array &$form) {
$this->showNextButton($form);
$this->showBackButton($form);
}
}
<?php
namespace Drupal\simple_multistep;
use Drupal\Core\Form\FormStateInterface;
/**
* Class FormStep.
*
* @package Drupal\simple_multistep
*/
class FormStep {
/**
* Form array.
*
* @var array
*/
protected $form;
/**
* Form state.
*
* @var \Drupal\Core\Form\FormStateInterface
*/
protected $formState;
/**
* Current step.
*
* @var int
*/
protected $currentStep;
/**
* Steps.
*
* @var array
*/
protected $steps;
/**
* Step settings.
*
* @var object
*/
protected $stepSettings;
/**
* FormStepController constructor.
*
* @param array $form
* Form settings.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state object.
*/
public function __construct(array &$form, FormStateInterface $form_state) {
$this->form = $form;
$this->formState = $form_state;
$this->currentStep = 0;
$this->updateStepInfo();
}
/**
* Update step info.
*/
public function updateStepInfo() {
// Fetch list all steps.
$this->fetchSteps();
// Fetch current step settings.
$this->fetchStepSettings();
}
/**
* Get current step.
*
* @return int
* Current step.
*/
public function getCurrentStep() {
return $this->currentStep;
}
/**
* Increase step number.
*/
public function increaseStep() {
$this->currentStep++;
}
/**
* Reduce step number.
*/
public function reduceStep() {
$this->currentStep--;
}
/**
* Set current step.
*/
protected function setCurrentStep() {
$this->currentStep = 0;
$this->currentStep = empty($this->formState->get('step')) ? 0 : $this->formState->get('step');
}
/**
* Get all form steps.
*/
public function getSteps() {
return $this->steps;
}
/**
* Get array with form steps.
*/
protected function fetchSteps() {
$steps = array();
if (isset($this->form['#fieldgroups']) && is_array($this->form['#fieldgroups'])) {
foreach ($this->form['#fieldgroups'] as $field_group) {
if ($field_group->format_type == 'form_step') {
$steps[] = $field_group;
}
}
usort($steps, array($this, 'sortStep'));
}
$this->steps = $steps;
}
/**
* Sort array by object property.
*
* @param object $first_object
* First object.
* @param object $second_object
* Second object.
*
* @return int
* Indicator.
*/
protected static function sortStep($first_object, $second_object) {
if ($first_object->weight == $second_object->weight) {
return 0;
}
return ($first_object->weight < $second_object->weight) ? -1 : 1;
}
/**
* Get form step settings.
*/
public function getStepSettings() {
return $this->stepSettings;
}
/**
* Fetch form step settings by current step.
*/
protected function fetchStepSettings() {
$step_settings = array();
if (isset($this->form['#fieldgroups'])) {
$form_steps = $this->getSteps();
if (!empty($form_steps) && isset($form_steps[$this->currentStep])) {
$step_settings = $form_steps[$this->currentStep];
}
}
$this->stepSettings = $step_settings;
}
/**
* Set $form_state.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state.
*/
public function setFormState(FormStateInterface $form_state) {
$this->formState = $form_state;
}
/**
* Get submission values for current step.
*
* @param object $step
* Step option.
*
* @return array
* Array with step values.
*/
public function getStepValues($step) {
$list_value = array();
$all_children = $this->getAllChildren($step);
$current_user_input = $this->formState->getValues();
foreach ($all_children as $field_name) {
if (isset($current_user_input[$field_name])) {
$list_value[$field_name] = $current_user_input[$field_name];
}
}
return $list_value;
}
/**
* Get all child from field group.
*
* @param object $fieldgroup
* Field group object.
* @param array $child
* Array with existing child.
*
* @return array
* Return array with child.
*/
protected function getAllChildren($fieldgroup, array $child = []) {
if ($group_children = $fieldgroup->children) {
foreach ($group_children as $form_element_id) {
if (isset($this->form[$form_element_id])) {
$child[] = $form_element_id;
}
elseif (isset($this->form['#fieldgroups'][$form_element_id]->children)) {
$child = $this->getAllChildren($this->form['#fieldgroups'][$form_element_id], $child);
}
}
}
return $child;
}
}
<?php
namespace Drupal\simple_multistep;
use Drupal\Core\Form\FormStateInterface;
/**
* Class MultistepController.
*
* @package Drupal\simple_multistep
*/
class MultistepController extends FormStep {
/**
* Steps indicator.
*
* @var StepIndicator
*/
public $stepIndicator;
/**
* Form button.
*
* @var FormButton
*/
public $formButton;
/**
* Stored values from $form_state.
*
* @var array
*/
protected $storedValues;
/**
* Input values from $form_state.
*
* @var array
*/
protected $inputValues;
/**
* MultistepController constructor.
*
* @param array $form
* Form settings.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state object.
*/
public function __construct(array &$form, FormStateInterface $form_state) {
parent::__construct($form, $form_state);
// Initialize empty storage.
$this->inputValues = [];
$this->storedValues = [];
}
/**
* Save input values from current step.
*/
public function saveInputValues() {
$stored_input = $this->inputValues;
$stored_input[$this->currentStep] = $this->formState->getUserInput();
$this->inputValues = $stored_input;
}
/**
* Get input values.
*/
public function getInputValues() {
return $this->inputValues;
}
/**
* Save stored values from current step.
*/
public function saveStoredValues() {
$stored_values = $this->storedValues;
$stored_values[$this->currentStep] = $this->getStepValues($this->steps[$this->currentStep]);
$this->storedValues = $stored_values;
}
/**
* Get stored values.
*/
public function getStoredValues() {
return $this->storedValues;
}
/**
* Prepare Multistep Form.
*
* @param array $form
* Reference to form.
*/
public function rebuildForm(array &$form) {
// Add step indicator.
$this->stepIndicator = new StepIndicator($form, $this->formState, $this->currentStep);
$this->stepIndicator->render($form);
unset($form['actions']['next']['#limit_validation_errors']);
foreach ($this->steps as $key => $step) {
$all_children = $this->getAllChildren($step);
if (!empty($all_children)) {
// Another step.
if ($key != $this->currentStep) {
foreach ($all_children as $child_id) {
if (isset($form[$child_id])) {
$form[$child_id]['#access'] = FALSE;
}
}
}
else {
foreach ($all_children as $child_id) {
if (isset($form[$child_id])) {
$form['actions']['next']['#limit_validation_errors'][] = array($child_id);
}
}
}
}
}
// Last step.
if ($this->currentStep == count($this->steps) - 1) {
foreach ($form as $element_key => $form_element) {
if (is_array($form_element) && isset($form_element['#type'])) {
if (isset($form['actions']['next']['#limit_validation_errors'])) {
unset($form['actions']['next']['#limit_validation_errors']);
}
}
}
}
// Add additional button for form.
$this->formButton = new FormButton($form, $this->formState, $this->currentStep);
$this->formButton->render($form);
}
}
<?php
namespace Drupal\simple_multistep\Plugin\field_group\FieldGroupFormatter;
use Drupal\field_group\FieldGroupFormatterBase;
/**
* Plugin implementation of the 'form_step' formatter.
*
* @FieldGroupFormatter(
* id = "form_step",
* label = @Translation("Form step"),
* description = @Translation("Provide simple form step"),
* supported_contexts = {
* "form",
* }
* )
*/
class FormStep extends FieldGroupFormatterBase {
/**
* {@inheritdoc}
*/
public function preRender(&$element, $rendering_object) {
$element += array(
'#type' => 'container',
'#pre_render' => array(),
'#attributes' => array(),
);
if ($this->getSetting('id')) {
$element['#attributes']['id'] = $this->getSetting('id');
}
$classes = $this->getClasses();
if (!empty($classes)) {
$element['#attributes']['class'] = $classes;
}
if ($this->getSetting('required_fields')) {
$element['#attached']['library'][] = 'field_group/formatter.fieldset';
$element['#attached']['library'][] = 'field_group/core';
}
}
/**
* {@inheritdoc}
*/
public function settingsForm() {
$form = parent::settingsForm();
$form['label']['#title'] = $this->t('Step title');
$form['show_step_title'] = array(
'#title' => $this->t('Show step title'),
'#type' => 'checkbox',
'#default_value' => $this->getSetting('show_step_title'),
'#description' => $this->t('Show step title'),
'#weight' => 1,
);
$form['back_button_show'] = array(
'#title' => $this->t('Show back button'),
'#type' => 'checkbox',
'#default_value' => $this->getSetting('back_button_show'),
'#description' => $this->t('Back button for form. Don`t show on first step'),
'#weight' => 2,
);
$form['back_button_text'] = array(
'#title' => $this->t('Text for back button'),
'#type' => 'textfield',
'#default_value' => $this->getSetting('back_button_text'),
'#description' => $this->t('Text which will be show on back button'),
'#weight' => $form['back_button_show']['#weight'] + 0.1,
);
$form['next_button_text'] = array(
'#title' => $this->t('Text for next button'),
'#type' => 'textfield',
'#default_value' => $this->getSetting('next_button_text'),
'#description' => $this->t('Text which will be show on next button'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
$summary[] = $this->t('Back button: @element',
array('@element' => $this->getSetting('back_button_show') ? 'Show' : 'Hide')
);
$summary[] = $this->t('Show title: @element',
array('@element' => $this->getSetting('show_step_title') ? 'Show' : 'Hide')
);
return $summary;
}
/**
* {@inheritdoc}
*/
public static function defaultContextSettings($context) {
$defaults = array(
'back_button_show' => FALSE,
'back_button_text' => t('Back'),
'next_button_text' => t('Next'),
'show_step_title' => TRUE,
) + parent::defaultSettings($context);
if ($context == 'form') {
$defaults['required_fields'] = 1;
}
return $defaults;
}
}
<?php
namespace Drupal\simple_multistep;
use Drupal\Core\Form\FormStateInterface;
/**
* Class StepIndicator.
*
* @package Drupal\simple_multistep
*/
class StepIndicator extends FormStep {
/**
* Constructor.
*
* @param array $form
* Form settings.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state object.
* @param int $current_step
* Current step.
*/
public function __construct(array $form, FormStateInterface $form_state, $current_step) {
parent::__construct($form, $form_state);
$this->currentStep = $current_step;
}
/**
* Create indicator.
*/
private function createIndicator() {
$steps_label = array(
'#type' => 'item',
'#weight' => -1,
);
$markup = '<div class="multi-steps-label">';
foreach ($this->steps as $step_number => $step) {
$format_settings = $step->format_settings;
if ($format_settings['show_step_title']) {
$active = $this->currentStep == $step_number ? ' active' : '';
$markup .= '<div class="step-label' . $active . '">';
$markup .= $step->label;
$markup .= '</div>';
}
}
$markup .= '</div>';
$steps_label['#markup'] = $markup;
return $steps_label;
}
/**
* Get Indicator.
*
* @param array $form
* Reference to form.
*/
public function render(array &$form) {
$form['steps_label'] = $this->createIndicator();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment