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
withemail_ok
/email_bad
classes).{{ dotbridge:on(click) }}
: Triggers bridge events with options likeoneTimeUse
,rateLimit
,regenerateId
, andurl
.data-dotapp-nojs
: Disablesdotapp.js
form handling, as DotBridge manages events directly.{{ enc(additionalKey): "AdminVal" }}
: Encrypts select option values using theadditionalKey
, 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:
This input, named<input type="text" {{ dotbridge:input="email.address(email, 6, email_ok, email_bad)" }} placeholder="Enter your EMAIL">
email.address
, uses theemail
filter, starts validation after 6 characters, and appliesemail_ok
(valid) oremail_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 nameduser.username
,email.address
, andjust.text
to themyBridge
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 thehtmljs
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.