Chandra
view release on metacpan or search on metacpan
examples/bridge_extension_example.pl view on Meta::CPAN
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;
}
ul#tasks li .text { flex: 1; }
ul#tasks li .time { font-size: 11px; color: #888; }
ul#tasks li button {
padding: 4px 12px; border-radius: 6px; border: none;
background: #2ecc71; color: #fff; cursor: pointer; font-size: 12px;
}
ul#tasks li button:disabled { background: #555; cursor: default; }
ul#tasks li.done .text { text-decoration: line-through; color: #666; }
#toasts {
position: fixed; bottom: 12px; right: 12px; z-index: 999;
display: flex; flex-direction: column; gap: 6px;
}
.toast {
padding: 8px 16px; border-radius: 8px; font-size: 13px;
animation: fadeIn 0.3s ease;
}
.toast-info { background: #2980b9; color: #fff; }
.toast-success { background: #27ae60; color: #fff; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; } }
#stats { text-align: center; margin-top: 14px; font-size: 13px; color: #888; }
#stats-btn { margin-top: 10px; display: block; margin-left: auto; margin-right: auto; }
</style>
</head>
<body>
<h1>Task Manager</h1>
<div class="input-row">
<input id="inp" placeholder="What needs doing?" onkeydown="if(event.key==='Enter') addTask()">
<button onclick="addTask()">Add</button>
</div>
<ul id="tasks"></ul>
<div id="stats"></div>
<button id="stats-btn" onclick="showStats()">Show Stats</button>
<div id="toasts"></div>
<script>
function addTask() {
var inp = document.getElementById('inp');
window.chandra.taskui.addTask(inp.value);
inp.value = '';
inp.focus();
}
function completeTask(id) {
window.chandra.taskui.completeTask(id);
}
function showStats() {
window.chandra.invoke('get_stats', []).then(function(s) {
document.getElementById('stats').textContent = s;
});
}
</script>
</body>
</html>
HTML
print "Starting task managerâ¦\n";
print "Extensions loaded: ", join(', ', Chandra::Bridge::Extension->list), "\n";
$app->run;
print "Done.\n";
=head1 NAME
Bridge Extension Example - Task Manager with inline and file-based JS extensions
=head1 DESCRIPTION
Demonstrates all three ways to register bridge extensions:
( run in 0.535 second using v1.01-cache-2.11-cpan-140bd7fdf52 )