DotBridge Example

This example demonstrates how to use DotBridge in the DotApp PHP Framework to create a secure, interactive form that connects the frontend and backend seamlessly. DotBridge, integrated into the templating system and the dotapp.js library, enables secure AJAX requests, input validation with built-in and custom filters, and event handling with features like rate limiting and one-time use. With minimal code, developers can build dynamic forms that validate inputs, handle events, and provide visual feedback, as shown in this example with email and category inputs.

Prerequisites

Important: This example builds on the secure forms example. To understand the context and setup, please review the following documentation before proceeding:

The secure forms example covers the creation of the Examples module, route configuration, and the Forms controller, which are reused here. It is critical to follow the examples in order, starting from the first, to grasp the full context. The Examples module was created using the DotApper CLI with the command:


php dotapper.php --create-module=Examples
        

The Forms controller was created with:


php dotapper.php --module=Examples --create-controller=Forms
        

Each example adds new functionality to the Examples module without causing naming conflicts. If you’re unfamiliar with these steps, refer to the secure forms example linked above.

Creating the View

Reuse or create a view file at /app/modules/Examples/views/htmljs.view.php to serve as the base template:


<!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 files -->
    {{ var: $css }}
</head>
<body>
    {{ content }}
    <script src="/assets/dotapp/dotapp.js"></script>
    {{ var: $js }}
</body>
</html>
        

Note: As explained in the secure forms example, the dotapp.js script is automatically installed by the framework and available at /assets/dotapp/dotapp.js. Developers only need to include the script tag, and the framework handles the rest. The $css and $js variables dynamically include styles and scripts passed from the controller.

Adding Styles

Create a CSS file at /app/modules/Examples/assets/css/example3.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);
}

.email_ok {
    border: 1px solid rgb(32, 170, 20) !important;
}

.email_bad {
    border: 1px solid red !important;
}

.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 provides a modern, responsive design with visual feedback for valid (email_ok) and invalid (email_bad) inputs, as well as loading, success, and error states for output elements.

Creating the Layout

Create a layout file at /app/modules/Examples/views/layouts/3.formbridge/form.layout.php to define the DotBridge form:


<div class="form-container">
    <h2>DotBridge Form</h2>
    <form method="post" data-dotapp-nojs>
        <div class="form-group">
            <label for="name">Email</label>
            <input type="text" {{ dotbridge:input="email.address(email, 6, email_ok, email_bad)" }} placeholder="Enter your EMAIL">
        </div>
        <div class="form-group">
            <label for="category">Category</label>
            <select {{ dotbridge:input="category" }}>
                <option value="" disabled selected>Select a category</option>
                <option value="{{ enc(additionalKey): "AdminVal" }}">Admin</option>
                <option value="{{ enc(additionalKey): "EditorVal" }}">Editor</option>
                <option value="{{ enc(additionalKey): "OtherVal" }}">Other</option>
            </select>
        </div>
        <div class="form-group">
            <label for="category">OneTime USE button</label>
            <div class="button" {{ dotbridge:on(click)="example.showEmailCategory(email.address, category)" oneTimeUse }}>Submit OnlyOnce</div>
            <br><br>
            <label for="category">2 x Clicks per minute, 5 x Clicks per hour</label>
            <div class="button" {{ dotbridge:on(click)="example.showEmailCategory2(email.address, category)" regenerateId rateLimit(60,2) rateLimit(3600,5) url(/documentation/examples/run/forms3/) }}>Submit 2xper minute</div>
            <br><br>
            <label for="category">No limit button</label>
            <div class="button" {{ dotbridge:on(click)="example.showEmailCategory3(email.address, category)" regenerateId }}>Submit NoLimit</div>
        </div>
    </form>
    <div class="output" id="output1">Submit the form using first button to see the result</div>
    <div class="output" id="output2">Submit the form using second button to see the result</div>
    <div class="output" id="output3">Submit the form using third button to see the result</div>
</div>
        

Key Features:

  • {{ dotbridge:input }}: Tracks inputs (e.g., category) or applies validation filters (e.g., email with email_ok/email_bad classes).
  • {{ dotbridge:on(click) }}: Triggers bridge events with options like oneTimeUse, rateLimit, regenerateId, and url.
  • data-dotapp-nojs: Disables dotapp.js form handling, as DotBridge manages events directly.
  • {{ enc(additionalKey): "AdminVal" }}: Encrypts select option values using the additionalKey, as explained in the secure forms example.

DotBridge Inputs

DotBridge inputs are defined using the {{ dotbridge:input }} tag in the templating system. They allow tracking of input values or applying validation filters. Inputs can be used in two ways:

  • Without Filter: Tracks the input value without validation. Example:
    
    <select {{ dotbridge:input="category" }}>
                    
  • With Filter: Applies a validation filter with visual feedback. Example:
    
    <input type="text" {{ dotbridge:input="email.address(email, 6, email_ok, email_bad)" }} placeholder="Enter your EMAIL">
                    
    This input, named email.address, uses the email filter, starts validation after 6 characters, and applies email_ok (valid) or email_bad (invalid) CSS classes.

Filters provide client-side validation, enhancing user experience by visually indicating valid or invalid inputs before submission. Single quotes are allowed for class names (e.g., 'email_ok'), but double quotes are not.

DotBridge Filters

DotBridge supports built-in filters for client-side input validation and allows developers to create custom filters for specialized validation needs.

Built-in Filters

The following filters are available for validating inputs:

  • Email Filter

    Description: Validates whether the input is a correctly formatted email address and applies CSS classes for visual feedback.

    Syntax: {{ dotbridge:input="name.email(email, start_checking_length, class_ok, class_bad)" }}

    Arguments:

    • email: Client-side email validation filter.
    • start_checking_length: Minimum characters to start validation (-1 disables validation).
    • class_ok: CSS class for valid input (e.g., email_ok).
    • class_bad: CSS class for invalid input (e.g., email_bad).

    Example:

    
    <input type="text" {{ dotbridge:input="newsletter.email(email, 5, 'valid-email', 'invalid-email')" }}>
                    
  • URL Filter

    Description: Validates whether the input is a correctly formatted URL.

    Syntax: {{ dotbridge:input="name.url(url, start_checking_length, class_ok, class_bad)" }}

    Example: {{ dotbridge:input="web.url(url, 5, 'valid-url', 'invalid-url')" }}

  • Phone Number Filter

    Description: Validates phone number formats.

    Syntax: {{ dotbridge:input="name.phone(phone, start_checking_length, class_ok, class_bad)" }}

    Example: {{ dotbridge:input="contact.phone(phone, 5, 'valid-phone', 'invalid-phone')" }}

  • Password Filter

    Description: Ensures the input has at least 8 characters, one uppercase letter, one lowercase letter, and one number.

    Syntax: {{ dotbridge:input="name.password(password, start_checking_length, class_ok, class_bad)" }}

    Example: {{ dotbridge:input="user.password(password, 8, 'valid-password', 'invalid-password')" }}

  • Date Filter

    Description: Validates dates in YYYY-MM-DD format.

    Syntax: {{ dotbridge:input="name.date(date, start_checking_length, class_ok, class_bad)" }}

    Example: {{ dotbridge:input="event.date(date, 10, 'valid-date', 'invalid-date')" }}

  • Time Filter

    Description: Validates time in 24-hour HH:MM format.

    Syntax: {{ dotbridge:input="name.time(time, start_checking_length, class_ok, class_bad)" }}

    Example: {{ dotbridge:input="event.time(time, 5, 'valid-time', 'invalid-time')" }}

  • Credit Card Filter

    Description: Validates major credit card formats using Luhn’s algorithm.

    Syntax: {{ dotbridge:input="name.card(creditcard, start_checking_length, class_ok, class_bad)" }}

    Example: {{ dotbridge:input="payment.card(creditcard, 16, 'valid-card', 'invalid-card')" }}

  • Username Filter

    Description: Validates usernames (3-16 alphanumeric characters, underscores, or hyphens).

    Syntax: {{ dotbridge:input="name.username(username, start_checking_length, class_ok, class_bad)" }}

    Example: {{ dotbridge:input="user.username(username, 3, 'valid-username', 'invalid-username')" }}

  • IPv4 Filter

    Description: Validates IPv4 addresses (X.X.X.X, where X is 0-255).

    Syntax: {{ dotbridge:input="name.ipv4(ipv4, start_checking_length, class_ok, class_bad)" }}

    Example: {{ dotbridge:input="network.ipv4(ipv4, 7, 'valid-ip', 'invalid-ip')" }}

Custom Filters

Developers can create custom filters using Bridge::addFilter($name, $callback). The callback receives parameters from the dotbridge:input tag. Example:


Bridge::addFilter("customFilter", function ($params) {
    /*$params[0] -> customFilter
      $params[1] -> 7
      $params[2] -> 'param1'
      $params[3] -> param2*/
    $pattern = '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$';
    $replacement = "";
    $replacement .= ' dotbridge-pattern="'.base64_encode($pattern).'"';
    if (isset($params[1])) $replacement .= ' dotbridge-min="'.intval($params[1]).'"';
    if (isset($params[2])) {
        $params[2] = trim(str_replace("'","",$params[2]));
        $replacement .= ' dotbridge-ok="'.$params[2].'"';
    }
    if (isset($params[3])) {
        $params[3] = trim(str_replace("'","",$params[3]));
        $replacement .= ' dotbridge-bad="'.$params[3].'"';
    }
    return($replacement);
});
        

Explanation: This filter validates input against a regex pattern (e.g., for emails) and adds attributes like dotbridge-pattern, dotbridge-min, dotbridge-ok, and dotbridge-bad. The regex is base64-encoded and sent to the frontend, where dotapp.js evaluates it after the input reaches the specified length. Developers can customize the logic to add any attribute, which can be processed in a custom frontend script.

Example Usage:


<input type="text" {{ dotbridge:input="test.custom(customFilter, 7, 'param1', param2)" }}>
        

This allows developers to define reusable filters that integrate with frontend scripts for consistent validation across inputs.

DotBridge Events

DotBridge events are defined using {{ dotbridge:on(event) }} to trigger backend functions via secure AJAX requests. They support standard JavaScript events (e.g., click, keyUp, keyDown) and offer advanced options for security and control.

Basic Example:


<div class="btn" {{ dotbridge:on(click)="callThis" }}>Callme</div>
        

This triggers a POST request to the current URL (e.g., /customTest/) with the bridge function callThis. The backend route is defined as:


Router::bridge(['/customTest/'], "callThis", function ($request) {
    return "You called callThis bridge!";
}, Router::STATIC_ROUTE);
        

The response is sent to the frontend in the request body.

Advanced Example:


<button {{ dotbridge:on(click)="myBridge(user.username, email.address, just.text)" url(/customurl/test) regenerateId oneTimeUse rateLimit(60,10) rateLimit(3600,100) expireAt(1458874) internalID(MyInternalID) }}>Login</button>
        

Options Explained:

  • myBridge(user.username, email.address, just.text): Sends values of inputs named user.username, email.address, and just.text to the myBridge function.
  • url(/customurl/test): Overrides the default URL to /customurl/test.
  • regenerateId: Generates a unique ID per request, preventing replay attacks.
  • oneTimeUse: Limits the button to a single use per session, even after page refresh.
  • rateLimit(seconds, count): Restricts event frequency (e.g., rateLimit(60,10) allows 10 events in 60 seconds, rateLimit(3600,100) allows 100 events in an hour).
  • expireAt(timestamp): Disables the button after the specified timestamp.
  • internalID(name): Sets a custom ID for debugging (e.g., MyInternalID).

Concrete Example:


<button {{ dotbridge:on(click)="myBridge(user.username, email.address, just.text)" url(/customurl/test) regenerateId rateLimit(60,2) }}>Call My Bridge</button>
        

Backend route:


Router::bridge(['/customurl/test'], "myBridge", "Examples:Forms@myCustomBridge", Router::STATIC_ROUTE);
        

This button triggers the myBridge function at /customurl/test, sending input values with a rate limit of 2 clicks per minute and a unique ID per request.

DotBridge JavaScript Functions

The dotapp.js library provides chainable methods for handling DotBridge events in the frontend, allowing developers to manage the lifecycle of a bridge request and handle responses or errors.

  • .bridge(name, event)

    Initializes a DotBridge event listener for the specified bridge name and JavaScript event (e.g., click).

    Example:

    
    $dotapp().bridge("example.showEmailCategory", "click")
                    
  • .before(data, element, event)

    Description: A callback executed before sending the request to the server. Use this to modify data, add variables, or update the UI (e.g., show a loading state). If the function returns nothing, the original data is sent unchanged. If it returns modified data, that data is sent instead. To stop further processing (e.g., if validation fails), return $dotapp().halt().

    Example:

    
    .before(function(data, element, event) {
        const categoryInput = $dotapp('[dotbridge-input="category"]').val();
        if (categoryInput === null) {
            alert("Select category !");
            return $dotapp().halt();
        }
        $dotapp("#output1")
            .html("Loading...")
            .removeClass("error")
            .removeClass("success")
            .addClass("loading");
    })
                    

    This checks if a category is selected, halting the request if not, and sets a loading state otherwise.

  • .after(data, element, event)

    Description: A callback executed after a successful response from the server. It processes the response data for display or further action.

    Example:

    
    .after(function(data, element, event) {
        if (reply = $dotapp().parseReply(data)) {
            $dotapp("#output1")
                .html(reply)
                .removeClass("error")
                .removeClass("loading")
                .addClass("success");
        }
    })
                    

    This displays the server response with a success state.

  • .onValueError(inputname, element, event)

    Description: A callback triggered for each input that fails validation (e.g., an invalid email). The request is halted, and the developer can display error messages or highlight invalid inputs. This callback is called separately for each invalid input.

    Example:

    
    .onValueError(function(inputname, element, e) {
        if (inputname == "email.address") alert("Enter correct email address !");
    })
                    

    This alerts the user if the email input is invalid.

  • .onError(data, status, error, element)

    Description: A callback for handling network or server errors (e.g., connection issues), not tied to specific HTTP status codes.

    Example:

    
    .onError(function(data, status, error, element) {
        $dotapp("#output1")
            .html("Network error occurred!")
            .addClass("error")
            .removeClass("loading")
            .removeClass("success");
    })
                    

    This displays a generic error message for network failures.

  • .onResponseCode(callback, code)

    Description: A callback for handling specific HTTP status codes, independent of .after. For example, 429 indicates a rate limit exceeded error.

    Example:

    
    .onResponseCode(function(status, text, element, e) {
        if (reply = $dotapp().parseReply(text)) {
            $dotapp("#output1")
                .html(reply.status_txt)
                .addClass("error")
                .removeClass("loading")
                .removeClass("success");
        }
    }, 429)
                    

    This handles the 429 status code, displaying the error message.

Configuring Routes

Update /app/modules/Examples/module.init.php to include routes for the DotBridge example. Add the Bridge facade at the top:


use \Dotsystems\App\Parts\Bridge;
        

Then, update the initialize function:


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

    // Handle form submission using bridge
    Router::bridge(['/documentation/examples/run/forms3', '/documentation/examples/run/forms3/'], "example.showEmailCategory", "Examples:Forms@submit3", Router::STATIC_ROUTE);
    Router::bridge(['/documentation/examples/run/forms3', '/documentation/examples/run/forms3/'], "example.showEmailCategory2", "Examples:Forms@submit3", Router::STATIC_ROUTE);
    Router::bridge(['/documentation/examples/run/forms3', '/documentation/examples/run/forms3/'], "example.showEmailCategory3", "Examples:Forms@submit3", Router::STATIC_ROUTE);
}
        

These routes map the page display to index3 and handle three bridge events (example.showEmailCategory, example.showEmailCategory2, example.showEmailCategory3) via the submit3 method.

Updating the Controller

Modify /app/modules/Examples/Controllers/Forms.php to include the Crypto facade and new methods:


use Dotsystems\App\Parts\Crypto;

private static function seoVar3() {
    $seo = [];
    $seo['title'] = "DotBridge Example: Seamless Backend-to-Frontend Integration with DotApp Framework";
    $seo['description'] = "This example showcases how to use DotBridge in the DotApp Framework to effortlessly connect PHP backend with JavaScript frontend using secure AJAX requests. Learn how to implement simple tags like {{ dotbridge:on(click) }} for dynamic, secure, and efficient form handling.";
    $seo['keywords'] = "DotBridge, DotApp Framework, secure AJAX, PHP JavaScript integration, dynamic forms, dotapp.js, secure communication, template tags, backend frontend bridge";
    return $seo;
}

public static function index3($request, Renderer $renderer) {
    $js = '<script src="/assets/modules/Examples/js/example3.js"></script>';
    $css = '<link rel="stylesheet" href="/assets/modules/Examples/css/example3.css">';
    $items = [];
    $items[] = array('value' => 'ValueItem1', 'text' => 'Text item 1');
    $items[] = array('value' => 'ValueItem2', 'text' => 'Text item 2');
    $items[] = array('value' => 'ValueItem3', 'text' => 'Text item 3');
    $viewcode = $renderer->module(self::modulename())
        ->setView("htmljs")
        ->setViewVar("seo", static::seoVar3())
        ->setViewVar("items", $items)
        ->setViewVar("js", $js)
        ->setViewVar("css", $css)
        ->setLayout("3.formbridge/form")
        ->renderView();
    return $viewcode;
}

public static function submit3($request, Renderer $renderer) {
    return "This is reply from submit3() function. Email: ".$request->data()['data']['email.address'].", category: ".Crypto::decrypt($request->data()['data']['category'], "additionalKey");
}
        

Explanation:

  • seoVar3(): Defines SEO metadata tailored to DotBridge and secure AJAX.
  • index3(): Renders the form with the htmljs view, 3.formbridge/form layout, and variables for CSS, JavaScript, and select options.
  • submit3(): Processes bridge events, returning the email input and decrypted category value.

Implementing JavaScript

Create a JavaScript file at /app/modules/Examples/assets/js/example3.js to handle DotBridge events:


(function() {
    // Definícia funkcie, ktorá sa má spustiť
    var runMe = function($dotapp) {
        function before(element) {
            const categoryInput = $dotapp('[dotbridge-input="category"]').val();
            if (categoryInput === null) {
                alert("Select category !");
                return $dotapp().halt();
            }
            $dotapp(element)
                .html("Loading...")
                .removeClass("error")
                .removeClass("success")
                .addClass("loading");
        }

        function after(element, data) {
            if (reply = $dotapp().parseReply(data)) {
                $dotapp(element)
                    .html(reply)
                    .removeClass("error")
                    .removeClass("loading")
                    .addClass("success");
            }
        }

        function onResponse(element, data) {
            if (reply = $dotapp().parseReply(data)) {
                $dotapp(element)
                    .html(reply.status_txt)
                    .addClass("error")
                    .removeClass("loading")
                    .removeClass("success");
            }
        }

        $dotapp()
            .bridge("example.showEmailCategory", "click")
            .before(function(data, element, event) {
                return before("#output1");
            })
            .onValueError(function(inputname, element, e) {
                if (inputname == "email.address") alert("Enter correct email address !");
            })
            .after(function(data, element, event) {
                after("#output1", data);
            })
            .onResponseCode(function(status, text, element, e) {
                onResponse("#output1", text);
            }, 429);

        $dotapp()
            .bridge("example.showEmailCategory2", "click")
            .before(function(data, element, event) {
                return before("#output2");
            })
            .onValueError(function(inputname, element, e) {
                if (inputname == "email.address") alert("Enter correct email address !");
            })
            .after(function(data, element, event) {
                after("#output2", data);
            })
            .onResponseCode(function(status, text, element, e) {
                onResponse("#output2", text);
            }, 429);

        $dotapp()
            .bridge("example.showEmailCategory3", "click")
            .before(function(data, element, event) {
                return before("#output3");
            })
            .onValueError(function(inputname, element, e) {
                if (inputname == "email.address") alert("Enter correct email address !");
            })
            .after(function(data, element, event) {
                after("#output3", data);
            })
            .onResponseCode(function(status, text, element, e) {
                onResponse("#output3", text);
            }, 429);
    };

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

Explanation: This script ensures $dotapp is available before executing, using the recommended pattern to avoid dependency issues. The before function validates the category input, halting the request if none is selected. The after function handles successful responses, and onResponse handles rate limit errors (429). The onValueError function alerts for invalid email inputs.

Error Codes

The .onResponseCode function handles specific HTTP status codes returned by the backend. Below is a table of possible error codes and their meanings:

Error Code HTTP Status Status Text
1 400 CRC check failed!
2 403 Bridge key does not match!
3 404 Function not found!
4 429 Rate limit exceeded!
5 403 CSRF check failed!
6 403 Invalid URL!

Live Demo

Try the live demo of this DotBridge example at /documentation/examples/run/forms3. This interactive example lets you test secure AJAX interactions, input validation with filters, event handling with rate limiting, and dynamic frontend validation.