Chandra
view release on metacpan or search on metacpan
examples/bridge_extension_example.pl view on Meta::CPAN
JS
# ââ 2. Register from a file: "toast" (depends on utils) âââââââââââââ
my ($fh, $toast_file) = tempfile(SUFFIX => '.js', UNLINK => 1);
print $fh <<'TOAST_JS';
var utils = window.chandra.utils;
return {
show: function(msg, type) {
type = type || 'info';
var el = document.createElement('div');
el.className = 'toast toast-' + type;
el.textContent = '[' + utils.timestamp() + '] ' + msg;
document.getElementById('toasts').appendChild(el);
setTimeout(function() { el.remove(); }, 4000);
}
};
TOAST_JS
close $fh;
Chandra::Bridge::Extension->register_file('toast', $toast_file,
depends => ['utils']);
# ââ 3. Create the app and use extend_bridge for "taskui" ââââââââââââ
my $app = Chandra::App->new(
title => 'Task Manager',
width => 520,
height => 620,
debug => 1,
);
$app->extend_bridge('taskui', <<'JS', depends => ['utils', 'toast']);
var utils = window.chandra.utils;
var toast = window.chandra.toast;
return {
addTask: function(text) {
if (!text) return;
var id = utils.uid();
var safe = utils.escapeHtml(text);
var li = document.createElement('li');
li.id = 'task-' + id;
li.innerHTML =
'<span class="text">' + safe + '</span>' +
'<span class="time">' + utils.timestamp() + '</span>' +
'<button onclick="completeTask(' + id + ')">Done</button>';
document.getElementById('tasks').appendChild(li);
toast.show('Added: ' + text, 'info');
/* also tell Perl */
window.chandra.invoke('task_added', [id, text]);
},
completeTask: function(id) {
var el = document.getElementById('task-' + id);
if (!el) return;
el.classList.add('done');
el.querySelector('button').disabled = true;
toast.show('Completed task #' + id, 'success');
window.chandra.invoke('task_done', [id]);
}
};
JS
# ââ 4. Perl-side callbacks ââââââââââââââââââââââââââââââââââââââââââ
my %tasks;
$app->bind('task_added', sub {
my ($id, $text) = @_;
$tasks{$id} = { text => $text, done => 0 };
printf "[Perl] task #%d added: %s (total: %d)\n", $id, $text, scalar keys %tasks;
return 1;
});
$app->bind('task_done', sub {
my ($id) = @_;
if ($tasks{$id}) {
$tasks{$id}{done} = 1;
printf "[Perl] task #%d completed\n", $id;
}
return 1;
});
$app->bind('get_stats', sub {
my $total = scalar keys %tasks;
my $done = grep { $_->{done} } values %tasks;
return "$done / $total completed";
});
# ââ 5. HTML content âââââââââââââââââââââââââââââââââââââââââââââââââ
$app->set_content(<<'HTML');
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
color: #e0e0e0;
min-height: 100vh;
padding: 20px;
}
h1 { text-align: center; margin-bottom: 16px; font-size: 22px; color: #e94560; }
.input-row {
display: flex; gap: 8px; margin-bottom: 16px;
}
.input-row input {
flex: 1; padding: 10px 14px; border-radius: 8px; border: 1px solid #333;
background: #1a1a2e; color: #fff; font-size: 14px; outline: none;
}
.input-row input:focus { border-color: #e94560; }
.input-row button, #stats-btn {
padding: 10px 18px; border-radius: 8px; border: none; cursor: pointer;
background: #e94560; color: #fff; font-size: 14px; font-weight: 600;
transition: background 0.2s;
}
.input-row button:hover, #stats-btn:hover { background: #c73652; }
ul#tasks { list-style: none; }
ul#tasks li {
display: flex; align-items: center; gap: 8px;
padding: 10px 14px; margin-bottom: 6px; border-radius: 8px;
background: rgba(255,255,255,0.06); transition: opacity 0.3s;
}
( run in 2.825 seconds using v1.01-cache-2.11-cpan-cdf2f3d4e48 )