Code Monkey home page Code Monkey logo

stepformbuilder's Introduction

StepFormBuilder

2017-08-22 -> 2021-03-05

A helper to implement a form composed of multiple steps.

This is part of the universe framework.

step-form-builder.png

Install

Using the planet installer via light-cli

lt install Ling.StepFormBuilder

Using the uni command.

uni import Ling/StepFormBuilder

Or just download it and place it where you want otherwise.

Why?

I created this planet because while implementing our own e-commerce solution, in order to help with the checkout process.

Do you know the one page checkout? You have the following steps:

  • login
  • shipping
  • billing

And some steps might contain other steps, for instance in our company we had an extra training step composed of three forms, so our scheme was more like this:

  • login
  • (training)
    • training-step-1
    • training-step-2
    • training-step-3
  • shipping
  • billing

I already had a MVC form logic with the help of the OnTheFlyForm planet, but I wanted to delegate the navigation part of the checkout process to an external object: the StepFormBuilder.

The Rules

The StepFormBuilder's behaviour strictly conforms to a set of rules:

  • Let P mean previous button
  • Let N mean next button
  • we use only static (not ajax) form in this implementation
  • All steps are registered in the order in which they should appear
  • P.click goes to the previous step unconditionally (if there is no previous step it remains on the same step)
  • N.click post the form; if the form is valid: - it stores the data in the pool - it assign the "done" state to this step - it goes to the next step If there is no next step, then it means the StepFormBuilder has been successfully completed.
  • the "done" state can only be removed using the reset method
  • Steps can be grouped together (like training-step-1, training-step-2 and training-step-3 in the above example)
  • click on a step's title goes directly to the step, but only if the step is already done; otherwise it doesn't do anything
  • when you land on a step group by clicking on the step's title, you always land on the first step

How to

That's all for the theory, and now the above example in code. Don't mind the first two require_once, they belong to the kamille framework (they basically initialize the autoloader)

<?php

use Ling\Authenticate\SessionUser\SessionUser;
use Ling\Bat\SessionTool;
use Module\Ekom\Api\EkomApi;
use Module\Ekom\Utils\OrderBuilder\OrderBuilder;
use Module\Ekom\Utils\OrderBuilder\Step\MockOrderBuilderStep;
use Module\EkomUserProductHistory\Api\EkomUserProductHistoryApi;
use Module\EkomUserProductHistory\UserProductHistory\Entry\Entry;
use Module\EkomUserProductHistory\UserProductHistory\FileSystemUserProductHistory;
use Ling\OnTheFlyForm\Helper\OnTheFlyFormHelper;
use Ling\OnTheFlyForm\OnTheFlyForm;
use Ling\StepFormBuilder\PrototypeStepFormBuilder;
use Ling\StepFormBuilder\Step\OnTheFlyFormStep;
use Ling\StepFormBuilder\StepFormBuilderInterface;


require_once __DIR__ . "/../boot.php";
require_once __DIR__ . "/../init.php";





$builder = new PrototypeStepFormBuilder();
$builder->registerStep('login', OnTheFlyFormStep::create()->setForm(OnTheFlyForm::create([
    "login",
], 'login-key')
    ->setValidationRules([
        'login' => ['required'],
    ])
));


$builder->registerStep('training1', OnTheFlyFormStep::create()->setForm(OnTheFlyForm::create([
    "motivation",
], 'motivation-key')
    ->setValidationRules([
        'motivation' => ['required'],
    ])
));
$builder->registerStep('training2', OnTheFlyFormStep::create()->setForm(OnTheFlyForm::create([
    "provenance",
], 'provenance-key')
    ->setValidationRules([
        'provenance' => ['required'],
    ])
));
$builder->registerStep('training3', OnTheFlyFormStep::create()->setForm(OnTheFlyForm::create([
    "explanations",
], 'explanations-key')
    ->setValidationRules([
        'explanations' => ['required'],
    ])
));


$builder->registerStep('shipping', OnTheFlyFormStep::create()->setForm(OnTheFlyForm::create([
    "shipping_mode",
], 'shipping-mode-key')
    ->setValidationRules([
        'shipping_mode' => ['required'],
    ])
));

$builder->registerStep('payment', OnTheFlyFormStep::create()->setForm(OnTheFlyForm::create([
    "payment_mode",
], 'payment-mode-key')->setValidationRules([
    'payment_mode' => ['required'],
])));



$builder->addGroup(['training1', 'training2', 'training3']);

class StepRenderer
{

    protected $title;

    /**
     * @var StepFormBuilderInterface
     */
    protected $builder;

    protected $id;


    public function __construct()
    {
        $this->title = "no title";
        $this->id = null;
        $this->builder = null;
    }


    public static function create(StepFormBuilderInterface $builder)
    {
        $o = new static();
        $o->builder = $builder;
        return $o;
    }

    public function setId($id)
    {
        $this->id = $id;
        return $this;
    }


    public function getBuilder()
    {
        return $this->builder;
    }

    public function displayBarFormLink(StepFormBuilderInterface $builder)
    {
        $id = $this->getId();
        $stepKey = $builder->getStepKey();
        ?>
        <form method="post" action="">
            <a href="#">Go to this step</a>
            <input type="hidden" name="<?php echo $stepKey; ?>" value="<?php echo $id; ?>">
        </form>
        <?php
    }


    public function displayBar(StepFormBuilderInterface $builder)
    {
        $id = $this->getId();
        ?>
        <div class="bar"><?php echo $this->title; ?> (<?php echo $builder->getStepState($id); ?>)
            <?php $this->displayBarFormLink($builder); ?>
        </div>
        <?php
        return $this;
    }

    public function displayModel(array $model)
    {
        ?>Default display<?php
        return $this;
    }


    public function displayButtons(array $model, $prev = true, $next = true)
    {
        if (true === $prev) {
            ?>
            <button type="submit" name="<?php echo $this->getBuilder()->getPrevStepKey(); ?>" value="1">Previous
            </button>
            <?php
        }
        if (true === $next) {
            ?>
            <button type="submit">Next</button>
            <?php
        }

        OnTheFlyFormHelper::generateKey($model);
    }

    private function getId()
    {
        if (null === $this->id) {
            $class = get_called_class();
            $class = str_replace('StepRenderer', '', $class);
            $this->id = strtolower($class);
        }
        return $this->id;
    }

}

class LoginStepRenderer extends StepRenderer
{
    public function __construct()
    {
        parent::__construct();
        $this->title = "login";
    }

    public function displayModel(array $model)
    {
        $m = $model;
        ?>
        <form method="post" action="">
            Login: <input name="<?php echo $m['nameLogin']; ?>" value="<?php echo $m['valueLogin']; ?>">
            <?php $this->displayButtons($model); ?>
        </form>
        <?php
        return $this;
    }
}

class TrainingStepRenderer extends StepRenderer
{
    protected $steps;

    public function __construct()
    {
        parent::__construct();
        $this->title = "training";
        $this->steps = [];
    }

    public function displayBar(StepFormBuilderInterface $builder)
    {
        ?>
        <div class="bar"><?php echo $this->title; ?> (composed)
            <?php $this->displayBarFormLink($builder); ?>
        </div>
        <?php
        return $this;
    }

    public function setSteps($steps)
    {
        $this->steps = $steps;
        return $this;
    }


    public function render(StepFormBuilderInterface $builder)
    {
        $trainingRenderer1 = TrainingStep1Renderer::create($builder);
        $trainingRenderer2 = TrainingStep2Renderer::create($builder);
        $trainingRenderer3 = TrainingStep3Renderer::create($builder);
        //
        if ('active' === $builder->getStepState('training1')) {
            $model = $builder->getStepModel('training1');
            $trainingRenderer1->displayModel($model);
        } elseif ('active' === $builder->getStepState('training2')) {
            $model = $builder->getStepModel('training2');
            $trainingRenderer2->displayModel($model);
        } elseif ('active' === $builder->getStepState('training3')) {
            $model = $builder->getStepModel('training3');
            $trainingRenderer3->displayModel($model);
        }
    }

}

class TrainingStep1Renderer extends StepRenderer
{

    public function displayModel(array $model)
    {
        $m = $model;
        ?>
        <form method="post" action="">
            Training - motivation: <input name="<?php echo $m['nameMotivation']; ?>"
                                          value="<?php echo $m['valueMotivation']; ?>">
            <?php $this->displayButtons($model); ?>
        </form>
        <?php
        return $this;
    }
}

class TrainingStep2Renderer extends StepRenderer
{

    public function displayModel(array $model)
    {
        $m = $model;
        ?>
        <form method="post" action="">
            Training - provenance: <input name="<?php echo $m['nameProvenance']; ?>"
                                          value="<?php echo $m['valueProvenance']; ?>">
            <?php $this->displayButtons($model); ?>
        </form>
        <?php
        return $this;
    }
}

class TrainingStep3Renderer extends StepRenderer
{

    public function displayModel(array $model)
    {
        $m = $model;
        ?>
        <form method="post" action="">
            Training - explanations: <input name="<?php echo $m['nameExplanations']; ?>"
                                            value="<?php echo $m['valueExplanations']; ?>">
            <?php $this->displayButtons($model); ?>
        </form>
        <?php
        return $this;
    }
}


class ShippingStepRenderer extends StepRenderer
{
    public function __construct()
    {
        parent::__construct();
        $this->title = "shipping";
    }

    public function displayModel(array $model)
    {
        $m = $model;
        ?>
        <form method="post" action="">
            Shipping mode: <input name="<?php echo $m['nameShippingMode']; ?>"
                                  value="<?php echo $m['valueShippingMode']; ?>">
            <?php $this->displayButtons($model); ?>
        </form>
        <?php
        return $this;
    }
}

class PaymentStepRenderer extends StepRenderer
{
    public function __construct()
    {
        parent::__construct();
        $this->title = "payment";
    }

    public function displayModel(array $model)
    {
        $m = $model;
        ?>
        <form method="post" action="">
            Payment mode: <input name="<?php echo $m['namePaymentMode']; ?>"
                                 value="<?php echo $m['valuePaymentMode']; ?>">
            <?php $this->displayButtons($model); ?>
        </form>
        <?php
        return $this;
    }
}




?>
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script
                src="https://code.jquery.com/jquery-3.2.1.js"
                integrity="sha256-DZAnKJ/6XZ9si04Hgrsxu/8s717jcIzLy3oi35EouyE="
                crossorigin="anonymous">
        </script>
        <style>
            .bar {
                background: #eee;
                margin-bottom: 5px;
            }

            .bar form {
                display: inline-block;
                margin-left: 100px;
            }
        </style>
    </head>

    <body>
    <?php
    $loginRenderer = LoginStepRenderer::create($builder)->displayBar($builder);
    if ('active' === $builder->getStepState('login')) {
        $model = $builder->getStepModel('login');
        $loginRenderer->displayModel($model);
    }
    $trainingRenderer = TrainingStepRenderer::create($builder)->setId("training1")->displayBar($builder)->setSteps([
        TrainingStep1Renderer::create($builder),
        TrainingStep2Renderer::create($builder),
        TrainingStep3Renderer::create($builder),
    ]);

    if (
        'active' === $builder->getStepState('training1') ||
        'active' === $builder->getStepState('training2') ||
        'active' === $builder->getStepState('training3')
    ) {
        $trainingRenderer->render($builder);
    }

    $shippingRenderer = ShippingStepRenderer::create($builder)->displayBar($builder);
    if ('active' === $builder->getStepState('shipping')) {
        $model = $builder->getStepModel('shipping');
        $shippingRenderer->displayModel($model);
    }
    $paymentRenderer = PaymentStepRenderer::create($builder)->displayBar($builder);
    if ('active' === $builder->getStepState('payment')) {
        $model = $builder->getStepModel('payment');
        $paymentRenderer->displayModel($model);
    }
    ?>

    <form method="post" action="">
        <input type="hidden" name="<?php echo $builder->getResetKey(); ?>" value="1">
        <button type="submit" id="reset">Reset</button>
    </form>

    <script>
        document.addEventListener("DOMContentLoaded", function (event) {
            $(document).ready(function () {
                $('a').on('click', function () {
                    $(this).parent().submit();
                    return false;
                });

                $('#reset').on('click', function () {
                    $(this).parent().submit();
                    return false;
                });
            });
        });
    </script>
    </body>
    </html>
<?php

$builder->debug();

History Log

  • 1.1.4 -- 2021-05-31

    • Removing trailing plus in lpi-deps file (to work with Light_PlanetInstaller:2.0.0 api
  • 1.1.3 -- 2021-03-05

    • update README.md, add install alternative
  • 1.1.2 -- 2020-12-08

    • Fix lpi-deps not using natsort.
  • 1.1.1 -- 2020-12-04

    • Add lpi-deps.byml file
  • 1.1.0 -- 2017-08-22

    • update OnTheFlyFormStep now accepts callback as form
    • add StepFormBuilderInterface.setActiveStep method
  • 1.0.2 -- 2017-08-22

    • removed debug strings in StepFormBuilder
  • 1.0.1 -- 2017-08-22

    • add OnTheFlyFormStep.onSuccessfulValidateAfter protected method
  • 1.0.0 -- 2017-08-22

    • initial commit

stepformbuilder's People

Contributors

lingtalfi avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.