Chandra

 view release on metacpan or  search on metacpan

lib/Chandra/Table.pm  view on Meta::CPAN


    my $wrap = Chandra::Element->new({ tag => 'div', class => 'chandra-table-wrap' });

    # ── Filter row (never re-rendered by _update_body) ────
    my $has_filters = grep { $_->{filterable} } @$columns;
    if ($has_filters) {
        my $filter_div = Chandra::Element->new({ tag => 'div', class => 'chandra-table-filters' });
        for my $col (@$columns) {
            next unless $col->{filterable};
            my $key = $col->{key};
            my $current = $filters->{$key} // '';
            if ($col->{filter_options}) {
                my $select = Chandra::Element->new({
                    tag => 'select', class => 'chandra-table-filter',
                    'data-action' => "filter:$key",
                });
                $select->add_child(Chandra::Element->new({
                    tag => 'option', data => "All $col->{label}",
                })->attribute('value', ''));
                for my $opt (@{$col->{filter_options}}) {
                    my $option = Chandra::Element->new({ tag => 'option', data => $opt });
                    $option->attribute('value', $opt);
                    $option->attribute('selected', 'selected') if $current eq $opt;
                    $select->add_child($option);
                }
                $filter_div->add_child($select);
            } else {
                my $input = Chandra::Element->new({
                    tag => 'input', class => 'chandra-table-filter',
                    'data-action' => "filter:$key",
                });
                $input->attribute('type', 'text');
                $input->attribute('placeholder', "Filter $col->{label}...");
                $input->attribute('value', $current);
                $filter_div->add_child($input);
            }
        }
        $wrap->add_child($filter_div);
    }

    # ── Table with thead + tbody ──────────────────────────
    my $table = Chandra::Element->new({ tag => 'table', class => 'chandra-table' });
    $table->add_child($self->_render_thead);
    $table->add_child($self->_render_tbody);
    $wrap->add_child($table);

    # ── Pagination ────────────────────────────────────────
    $wrap->add_child($self->_render_pagination);

    return $wrap->render;
}

# ── CSS ────────────────────────────────────────────────────

sub css {
    return <<'CSS';
.chandra-table-wrap { font-family: system-ui, -apple-system, sans-serif; }
.chandra-table { width: 100%; border-collapse: collapse; }
.chandra-table th, .chandra-table td { padding: 8px 12px; text-align: left; border-bottom: 1px solid #e0e0e0; }
.chandra-table th { background: #f5f5f5; font-weight: 600; user-select: none; }
.chandra-table-sortable { cursor: pointer; }
.chandra-table-sortable:hover { background: #e8e8e8; }
.chandra-table-stripe { background: #fafafa; }
.chandra-table-selected { background: #e3f2fd !important; }
.chandra-table-select { width: 40px; text-align: center; }
.chandra-table-empty, .chandra-table-loading { text-align: center; padding: 24px; color: #999; }
.chandra-table-filters { padding: 8px 0; display: flex; gap: 8px; flex-wrap: wrap; }
.chandra-table-filter { padding: 4px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; }
.chandra-table-pagination { display: flex; align-items: center; justify-content: center; gap: 4px; padding: 12px 0; }
.chandra-table-page-btn { padding: 4px 10px; border: 1px solid #ddd; border-radius: 4px; background: #fff; cursor: pointer; font-size: 13px; }
.chandra-table-page-btn:hover { background: #f0f0f0; }
.chandra-table-page-active { background: #2196F3 !important; color: #fff; border-color: #2196F3; }
.chandra-table-info { margin-right: 12px; font-size: 13px; color: #666; }
CSS
}

1;

__END__

=head1 NAME

Chandra::Table - Sortable, filterable, paginated data grid component

=head1 SYNOPSIS

    use Chandra::Table;

    my $table = Chandra::Table->new(
        columns => [
            { key => 'name',  label => 'Name',  sortable => 1 },
            { key => 'email', label => 'Email', sortable => 1 },
            { key => 'role',  label => 'Role',  filterable => 1,
              filter_options => [qw(admin user guest)] },
        ],
        data      => \@users,
        page_size => 10,
        selectable => 'multi',
        on_row_click => sub { my ($row) = @_; print $row->{name} },
    );

    $app->css(Chandra::Table->css);
    $table->mount($app, '#content');

=head1 DESCRIPTION

C<Chandra::Table> is a L<Chandra::Component> subclass providing a
data grid. Renders using L<Chandra::Element> for proper event wiring.

=head1 SEE ALSO

L<Chandra::Component>, L<Chandra::Element>, L<Chandra::App>

=cut



( run in 0.862 second using v1.01-cache-2.11-cpan-140bd7fdf52 )