Secure Forms with dotapp.js

This example demonstrates advanced form handling in the DotApp PHP Framework using the dotapp.js frontend library. It covers secure form submissions with encrypted select options, CRC validation, dynamic template rendering with {{ foreach }}, and the use of the Crypto facade for data decryption.

Prerequisites

Important: This example builds on the basic forms example. Please review the following documentation before proceeding:

These resources cover the creation of the Examples module, route configuration in initializeRoutes, and the Forms controller setup, which are reused in this example.

Creating the View

Create a new view file at /app/modules/Examples/views/htmljs.view.php to serve as the base template for this example:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ var: $seo['title'] }}</title>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="description" content="{{ var: $seo['description'] }}"/>
    <meta name="keywords" content="{{ var: $seo['keywords'] }}"/>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- CSS file -->
    <link rel="stylesheet" href="/assets/modules/Examples/css/example2.css">
</head>
<body>
    {{ content }}
    <script src="/assets/dotapp/dotapp.js"></script>
    <script src="/assets/modules/Examples/js/example2.js"></script>
</body>
</html>
        

Note: The dotapp.js script is automatically available at /assets/dotapp/dotapp.js and includes a public encryption key, managed by the framework. The key may change per request, but this is handled transparently. The CSS file at /assets/modules/Examples/css/example2.css maps to /app/modules/Examples/assets/css/example2.css.

Adding Styles

Create a CSS file at /app/modules/Examples/assets/css/example2.css to style the form:


* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Arial', sans-serif;
    background-color: #f4f7fa;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    padding: 20px;
}

.form-container {
    background-color: #ffffff;
    padding: 30px;
    border-radius: 12px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
    width: 100%;
    max-width: 400px;
}

h2 {
    text-align: center;
    margin-bottom: 25px;
    color: #333;
    font-size: 24px;
    font-weight: 600;
}

.form-group {
    margin-bottom: 20px;
}

label {
    display: block;
    font-size: 14px;
    color: #555;
    margin-bottom: 8px;
    font-weight: 500;
}

input[type="text"],
select {
    width: 100%;
    padding: 12px;
    border: 1px solid #ddd;
    border-radius: 8px;
    font-size: 16px;
    color: #333;
    background-color: #f9f9f9;
    transition: border-color 0.3s, box-shadow 0.3s;
}

input[type="text"]:focus,
select:focus {
    outline: none;
    border-color: #007bff;
    box-shadow: 0 0 8px rgba(0, 123, 255, 0.2);
    background-color: #fff;
}

select {
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23333' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14L2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    cursor: pointer;
}

button {
    width: 100%;
    padding: 12px;
    background-color: #007bff;
    border: none;
    border-radius: 8px;
    color: #fff;
    font-size: 16px;
    font-weight: 600;
    cursor: pointer;
    transition: background-color 0.3s, transform 0.2s;
}

button:hover {
    background-color: #0056b3;
    transform: translateY(-2px);
}

button:active {
    transform: translateY(0);
}

.output {
    margin-top: 20px;
    padding: 15px;
    border-radius: 8px;
    font-size: 14px;
    line-height: 1.5;
    transition: all 0.3s ease;
    min-height: 60px;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
}

.output.loading {
    background-color: #e9f7ff;
    color: #007bff;
    border: 1px solid #007bff;
}

.output.success {
    background-color: #e6ffed;
    color: #28a745;
    border: 1px solid #28a745;
}

.output.error {
    background-color: #ffe6e6;
    color: #dc3545;
    border: 1px solid #dc3545;
}

/* Responsive design */
@media (max-width: 480px) {
    .form-container {
        padding: 20px;
        max-width: 100%;
    }

    h2 {
        font-size: 20px;
    }
}
        

This CSS ensures a modern, responsive form design with loading, success, and error states for the output display.

Creating the Layout

Create a layout file at /app/modules/Examples/views/layouts/example2.forms.layout.php to define the secure form:


<div class="form-container">
    <h2>Contact Form</h2>
    <form method="post" id="example2">
        <div class="form-group">
            <label for="name">Name</label>
            <input type="text" id="name" name="name" placeholder="Enter your name" required>
        </div>
        <div class="form-group">
            <label for="category">Category</label>
            <select id="category" name="category" required>
                <option value="" disabled selected>Select a category</option>
                <option value="{{ enc(additionalKey): "SupportVal" }}">Support</option>
                <option value="{{ enc(additionalKey): "FeedbackVal" }}">Feedback</option>
                <option value="{{ enc(additionalKey): "OtherVal" }}">Other</option>
            </select>
        </div>
        <div class="form-group">
            <label for="foreach">Select created by ForEach</label>
            <select id="foreach" name="foreach" required>
                <option value="" disabled selected>Select a category again</option>
                {{ foreach $items as $item }}
                    <option value="{{ enc(additionalKey2): $item['value'] }}">{{ var: $item['text'] }}</option>
                {{ /foreach }}
            </select>
        </div>
        {{ formName(CSRF) }}
        <button type="submit">Submit</button>
    </form>
    <div class="output">Submit the form to see the result</div>
</div>
        

Key Features:

  • {{ enc(key): value }}: Encrypts select option values (e.g., SupportVal) using the specified key (additionalKey or additionalKey2).
  • {{ foreach $items as $item }}: Dynamically generates select options from the $items array passed by the controller.
  • {{ formName(CSRF) }}: Assigns a unique form name for security, stronger than traditional CSRF tokens.
  • dotapp.js: Automatically handles form submission unless data-dotapp-nojs is added.

Configuring Routes

Update /app/modules/Examples/module.init.php to define routes for displaying and submitting the secure form:


public function initialize($dotApp) {
    // Display the secure forms page
    Router::get(['/documentation/examples/run/forms2', '/documentation/examples/run/forms2/'], "Examples:Forms@index2", Router::STATIC_ROUTE);

    // Handle secure form submission
    Router::post(['/documentation/examples/run/forms2', '/documentation/examples/run/forms2/'], "Examples:Forms@submit2", Router::STATIC_ROUTE);
}
        

These routes map /documentation/examples/run/forms2 to the index2 method for displaying the form and the submit2 method for processing submissions.

Updating the Controller

Modify /app/modules/Examples/Controllers/Forms.php to include the Crypto facade and handle form display and submission. Add the following at the top of the file:


use Dotsystems\App\Parts\Crypto;
        

Then, add the following methods:


private static function seoVar2() {
    $seo = [];
    $seo['title'] = "Advanced Example: Secure Form Handling with dotapp.js and Template Features";
    $seo['description'] = "This advanced example demonstrates secure form handling using the dotapp.js frontend library, including encryption, CRC validation, and dynamic templates with foreach loops.";
    $seo['keywords'] = "dotapp.js, secure forms, encryption, CRC validation, PHP templates, foreach, Crypto facade, advanced form handling";
    return $seo;
}

public static function index2($request, Renderer $renderer) {
    $button = "Submit";
    $items = [];
    $items[] = ['value' => 'ValueItem1', 'text' => 'Text item 1'];
    $items[] = ['value' => 'ValueItem2', 'text' => 'Text item 2'];
    $items[] = ['value' => 'ValueItem3', 'text' => 'Text item 3'];
    $viewcode = $renderer->module(self::modulename())
        ->setView("htmljs")
        ->setViewVar("seo", static::seoVar2())
        ->setViewVar("items", $items)
        ->setViewVar("btnName", $button)
        ->setLayout("example2.forms")
        ->renderView();
    return $viewcode;
}

public static function submit2($request, Renderer $renderer) {
            if ($request->crcCheck()) {
                $request->form(['POST'],"CSRF", function($request) {
                    $categoryVal = Crypto::decrypt($request->data()['data']['category'],"additionalKey");
                    $foreachVal = Crypto::decrypt($request->data()['data']['foreach'],"additionalKey2");
                    if ($categoryVal === false || $foreachVal === false) {
                        $answer = [];
                        $answer['error'] = 1;
                        $answer['error_txt'] = "Data manipulation detected!";
                        echo DotApp::DotApp()->ajaxReply($answer, 403);
                        return;
                    } else {
                        $answer = [];
                        $answer['error'] = 0;
                        $answer['text'] = "Category: ". $categoryVal. ", Foreach: ". $foreachVal;
                        echo DotApp::DotApp()->ajaxReply($answer, 200);
                        return;
                    }
                }, function() {
                    $answer = [];
                    $answer['error'] = 1;
                    $answer['error_txt'] = "CSRF check failed!";
                    echo DotApp::DotApp()->ajaxReply($answer, 403);
                    return;
                });
            } else {
                $answer = [];
                $answer['error'] = 1;
                $answer['error_txt'] = "CRC check failed!";
                echo DotApp::DotApp()->ajaxReply($answer, 403);
                return;
            }
       }
        

Explanation:

  • seoVar2(): Defines SEO metadata for the page.
  • index2(): Renders the form page with the htmljs view, passing an array of items for the foreach loop, a button name, and SEO variables.
  • submit2(): Validates the CRC using crcCheck(), decrypts form data with the Crypto facade, and returns an AJAX response indicating success or failure.

Implementing JavaScript

Create a JavaScript file at /app/modules/Examples/assets/js/example2.js to handle form submission with dotapp.js:


(function() {
    var runMe = function($dotapp) {
        $dotapp()
            .form('#example2')
            .before((data, form) => {
                $dotapp(".output")
                    .removeClass("error")
                    .removeClass("success")
                    .addClass("loading")
                    .html("Loading...");
            })
            .after((data, response, form) => {
                setTimeout(function() {
                    try {
                        var answer = JSON.parse(atob(response));
                        if ($dotapp().isSet(answer.error)) {
                            if (answer.error == 0) {
                                $dotapp(".output")
                                    .removeClass("error")
                                    .removeClass("loading")
                                    .addClass("success")
                                    .html("Form submitted successfully! <br>" + answer.text);
                            }
                        }
                    } catch (err) {
                        $dotapp(".output")
                            .removeClass("loading")
                            .removeClass("success")
                            .addClass("error")
                            .html("Error while parsing response data!");
                    }
                }, 1000);
            })
            .onError((data, status, error, form) => {
                setTimeout(function() {
                    try {
                        var answer = JSON.parse(atob(error));
                        if ($dotapp().isSet(answer.error)) {
                            if (answer.error == 1) {
                                $dotapp(".output")
                                    .removeClass("loading")
                                    .removeClass("success")
                                    .addClass("error")
                                    .html("There was an error! <br>" + answer.error_txt);
                            }
                        }
                    } catch (err) {
                        $dotapp(".output")
                            .removeClass("loading")
                            .removeClass("success")
                            .addClass("error")
                            .html("Unknown error! <br>" + error);
                    }
                }, 1000);
            });
    };

    if (window.$dotapp) {
        runMe(window.$dotapp);
    } else {
        window.addEventListener('dotapp', function() {
            runMe(window.$dotapp);
        }, { once: true });
    }
})();
        

Key Points:

  • The script waits for $dotapp to be available, ensuring proper initialization.
  • .before(): Displays a loading state before form submission.
  • .after(): Handles successful responses, parsing the base64-encoded JSON and displaying the result.
  • .onError(): Handles errors, displaying appropriate messages.
  • A 1-second delay simulates server response time for better UX.

Using the Crypto Facade

The Crypto facade is used to decrypt form data securely. The {{ enc(key): value }} template function encrypts select option values, and the controller decrypts them using:


Crypto::decrypt($request->data()['data']['category'], "additionalKey");
        

CRC validation is enforced with $request->crcCheck(), ensuring only forms submitted via dotapp.js are processed, as raw submissions lack the dynamic CRC key.

Live Demo

Try the live demo of this secure forms example at /documentation/examples/run/forms2. This interactive example lets you test encrypted form submissions, CRC validation, and dynamic rendering.