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 )