DotBridge Example
Open the live demo
at /documentation/examples/run/bridge
(also available at /documentation/examples/run/forms3).
Introduction
This example is the live Examples:Forms@index3! /
@submit3! page. Template tags call PHP over an encrypted AJAX POST
to the current page URL. Inputs can be filtered, encrypted, rate-limited, and
bound to a specific URL.
Prerequisites
Start from the named-forms example so the Examples module and
Forms controller already exist.
Creating the View
The live demo uses a standalone view at
/app/modules/Examples/views/bridge.view.php. The file is a complete
HTML document.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ var: $title }} — DotApp PHP Framework 2.0</title>
<link rel="stylesheet" href="/assets/modules/Examples/css/examples.css" />
</head>
<body class="ex-body">
<header class="ex-top">
<a href="{{ var: $docsUrl }}">Documentation</a>
<span>DotBridge</span>
</header>
<main class="ex-main">
<h1>DotBridge</h1>
<p class="ex-lead">Each button calls PHP over the current page URL.</p>
<form method="post" class="ex-card" data-dotapp-nojs>
<label for="email">Email</label>
<input type="text" id="email" {{ dotbridge:input="email.address(email, 6, email_ok, email_bad)" }} placeholder="Enter your email" />
<label for="category">Category</label>
<select id="category" {{ 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 class="ex-bridge-actions">
<div class="ex-btn" {{ dotbridge:on(click)="example.showEmailCategory(email.address, category)" oneTimeUse }}>Submit only once</div>
<div class="ex-btn" {{ dotbridge:on(click)="example.showEmailCategory2(email.address, category)" regenerateId rateLimit(60,2) rateLimit(3600,5) url(/documentation/examples/run/forms3/) }}>Submit 2× per minute</div>
<div class="ex-btn" {{ dotbridge:on(click)="example.showEmailCategory3(email.address, category)" regenerateId }}>Submit no limit</div>
</div>
</form>
<div class="output" id="output1">Submit with the first button to see the result</div>
<div class="output" id="output2">Submit with the second button to see the result</div>
<div class="output" id="output3">Submit with the third button to see the result</div>
</main>
<script src="/assets/dotapp/dotapp.js"></script>
<script src="/assets/modules/Examples/js/bridge.js"></script>
</body>
</html>
Adding Styles
Shared example chrome lives at
/app/modules/Examples/assets/css/examples.css and is served as
/assets/modules/Examples/css/examples.css.
Creating the Layout
setView('bridge') renders the full HTML document. Modules can also
wrap views with {{ layout:name }}. This demo keeps the view
self-contained.
DotBridge Inputs
Mark fields with {{ dotbridge:input="name" }} or the short form
dotbridge="name". Nested names such as email.address
arrive in PHP as $request->data(true)['data']['email.address'].
Built-in filters include email, url, phone,
password, date, time, creditcard,
username, and ipv4. Register more with
Bridge::addFilter($name, $callback).
DotBridge Filters
The email field uses
email.address(email, 6, email_ok, email_bad): the
email filter, a minimum length of 6, and optional success/failure
callbacks. Category options are encrypted in the template with
{{ enc(additionalKey): "AdminVal" }} and decrypted in PHP with
the same extra key.
DotBridge Events
Bind a click (or another event) with
{{ dotbridge:on(click)="functionName(arg1, arg2)" }}. Modifiers
on the same tag control reuse and limits:
oneTimeUse— the key is consumed after one successful call.regenerateId— a fresh id is issued after the call.rateLimit(seconds,count)— you can stack several windows, e.g.rateLimit(60,2) rateLimit(3600,5).url(/path)— POST target for that call. An invalid bound URL returns HTTP 403 witherror_code6.
DotBridge JavaScript Functions
Pair each template function name with $dotapp().bridge(name, event).
Hooks: before, onValueError, after,
and onResponseCode. Wait for the dotapp event if the
library is still loading.
Configuring Routes
Register the page GET and bind each bridge function to the same URLs in
initialize($dotApp):
$bridgePages = array_merge($pair($p . '/forms3'), $pair($p . '/bridge'));
Router::get($bridgePages, 'Examples:Forms@index3!', Router::STATIC_ROUTE);
foreach (['example.showEmailCategory', 'example.showEmailCategory2', 'example.showEmailCategory3'] as $fn) {
Router::bridge($bridgePages, $fn, 'Examples:Forms@submit3!', Router::STATIC_ROUTE);
}
Router::bridge($urls, $functionName, $handler, Router::STATIC_ROUTE)
scopes the handler to those page URLs. Closures use Bridge::listen() with the same argument order.
Updating the Controller
Methods are public static and take $request. The GET
action renders the view. The bridge action reads the payload and returns a
string (or an array), which becomes the JSON body.
public static function index3($request)
{
return self::view('bridge', [
'title' => 'DotBridge demo',
'docsUrl' => '/documentation/examples/dotbridge',
]);
}
public static function submit3($request)
{
$payload = $request->data(true)['data'] ?? [];
$email = (string) ($payload['email.address'] ?? '');
$category = Crypto::decrypt((string) ($payload['category'] ?? ''), 'additionalKey');
if ($category === false) {
$category = '(invalid category)';
}
return 'This is reply from submit3() function. Email: ' . $email . ', category: ' . $category;
}
Failed decryption returns false — compare with
=== false.
Implementing JavaScript
Live file: /app/modules/Examples/assets/js/bridge.js. The client
API is $dotapp.
(function () {
var runMe = function ($dotapp) {
function before(selector) {
var categoryInput = $dotapp('[dotbridge-input="category"]').val();
if (categoryInput === null || categoryInput === "") {
alert("Select a category.");
return $dotapp().halt();
}
$dotapp(selector).html("Loading...").removeClass("error").removeClass("success").addClass("loading");
}
function after(selector, body) {
var text = "";
if (typeof body === "string") text = body;
else if (body && body.body) text = body.body;
else if (body) text = String(body);
if (text) {
$dotapp(selector).html(text).removeClass("error").removeClass("loading").addClass("success");
}
}
function onResponse(selector, data) {
var reply = $dotapp().parseReply(data);
var text = (reply && reply.status_txt) ? reply.status_txt : "Request failed.";
$dotapp(selector).html(text).addClass("error").removeClass("loading").removeClass("success");
}
$dotapp()
.bridge("example.showEmailCategory", "click")
.before(function () { return before("#output1"); })
.onValueError(function (inputname) {
if (inputname == "email.address") alert("Enter a valid email address.");
})
.after(function (body) { after("#output1", body); })
.onResponseCode(function (status, text) { onResponse("#output1", text); }, 429);
};
if (window.$dotapp) runMe(window.$dotapp);
else window.addEventListener("dotapp", function () { runMe(window.$dotapp); }, { once: true });
})();
Repeat the same .bridge() chain for
example.showEmailCategory2 and
example.showEmailCategory3 with their output selectors.
Error Codes
Success is HTTP 200 with JSON { status: 1, body: <your return> }
and the header X-Answered-By: dotbridge.
- HTTP 400, error_code 1: CRC check failed.
- HTTP 403, error_code 2: bridge key mismatch.
- HTTP 404, error_code 3: function not registered or not callable.
- HTTP 429, error_code 4: rate limit, invalid id, or missing valid key.
- HTTP 403, error_code 5: CSRF referer mismatch.
- HTTP 403, error_code 6: invalid
dotbridge-url.
Live Demo
/documentation/examples/run/bridge and /documentation/examples/run/forms3 render the same page.