Skip to main content

Table

Struct Table 

Source
pub struct Table<M> { /* private fields */ }
Expand description

Table description of a Resource’s list view declaring columns and how they map to queries.

A clone shares every column, filter and projection.

The list keeps its whole state in the URL: ?tab= picks a tab, ?q= searches, ?sort= and ?dir= sort, ?page=, ?per_page=, ?after= and ?before= page, ?f.<name>= filters and ?group_by= groups. Each row is keyed by its record’s primary key, which the action URLs and bulk checkboxes carry; a composite key has no URL form, so its rows render without row actions or bulk selection.

  • Search matches a row any searchable column contains the term in. It escapes % and _, trims the term and caps it at 128 characters, and matches case-insensitively: PostgreSQL runs it as ILIKE, SQLite (for ASCII) and MySQL (under its default collations) as LIKE.
  • Without a ?sort= naming a sortable column, the rows sort by the default_sort, else by the first sortable column, ascending. The primary key breaks ties, so a sort over duplicate values pages deterministically, and a page number past the last page reads as the last.
  • A filter that cannot apply, an unknown name, a value the filter rejects or one past the limits, is never dropped silently: the list shows a warning banner naming it, and the export answers 400. At most 32 filters apply, each with at most 64 values and parts, each name, part and value at most 256 bytes. Active filters combine with AND.
  • ?group_by= groups the rows by one of the table’s groupings, which orders them by its field ahead of the sort, so a group runs on across pages; one the table does not declare is ignored.
  • The list updates in place: a search, filter, sort or page reruns the page through the runtime, keeping focus and scroll, and without JavaScript its links and its search form load the page. A table that fails to load shows an error state with a retry link in place of the rows.

When the policy allows DeleteAny, or RunAny for an action placed on the bulk bar, the list adds a checkbox column and a bulk bar; a row that no delete or bulk action allows gets no checkbox. A bulk delete takes at most Limits::bulk_records, 400 by default, runs in one transaction and skips the records the policy refuses Delete. Both deletes ask first through a confirmation dialog, which needs JavaScript; a POST not confirmed through it answers 400. Each lands back on the list as it was left.

§Export

GET {list}/-/export returns the list as a CSV file named {slug}.csv, with the current search, filters and sort applied and the relations the columns include loaded: the rows the list shows, each of them, whatever the policy answers View of it. It delivers at most Limits::export_rows, 10,000 by default, and answers 413 rather than a truncated file when more match. It writes each column’s formatted value, a date column’s stored value, and escapes a cell a spreadsheet would read as a formula; ?bom=1 prefixes the file with a byte-order mark for Excel.

Implementations§

Source§

impl<M> Table<M>

Source

pub fn group_by<T>(self, lens: Lens<M, T>) -> Self
where M: Model + Send + Sync + 'static, T: FormScalar + Send + Sync + 'static,

Offer grouping the rows by the field lens reads, under the field’s name. Call it once per field a reader may group by; ?group_by= picks one, and the list groups by none without it.

use tablo_core::{Table, TextColumn, lens};

Table::new(TextColumn::new(lens!(Post.title)))
    .group_by(lens!(Post.status))
    .group_by(lens!(Post.author));

A grouping orders the rows by its field ahead of the sort, so each group runs on across pages, and its header shows its field’s value, or “No value” over the rows holding none. Two groupings by one field are a declaration error.

Source§

impl<M> Table<M>

Source

pub fn row_color( self, color: impl Fn(&M) -> Option<Color> + Send + Sync + 'static, ) -> Self

Tint each row in color(row), such as an overdue invoice in Color::Danger; a row it gives None stays plain.

use tablo_core::{Color, Reads, Table, TextColumn, lens};

Table::new(TextColumn::new(lens!(Invoice.number)))
    .row_color(|invoice: &Invoice| invoice.overdue.then_some(Color::Danger))
    .row_color_reading(Reads::of(lens!(Invoice.overdue)));

The closure may read any field, so a resource that restricts a field refuses the table until row_color_reading declares the ones it reads, and a user who may not view one of them sees every row plain.

Source

pub fn row_color_reading(self, reads: Reads<M>) -> Self

Declare the fields the row_color reads. A color declaring it reads none of the row’s relations, beside a column loading one, reads a copy of the row without them, which ModelColumns builds: it then mounts beside a column loading a record of a model a resource restricts a field of.

Source§

impl<M> Table<M>

Add link to each row’s actions, before its custom actions, after the links already declared.

Source§

impl<M> Table<M>

Source

pub fn default_sort<T>(self, path: impl Into<Path<M, T>>) -> Self
where M: Model,

Sort by the field path names, smallest first, when the URL names no sortable column; by the first sortable column, ascending, by default. The field needs no column: the rows sort by it all the same, and no header marks the sort.

use tablo_core::{Table, TextColumn, lens};

Table::new(TextColumn::new(lens!(Post.title)).sortable()).default_sort(lens!(Post.title));
Source

pub fn default_sort_desc<T>(self, path: impl Into<Path<M, T>>) -> Self
where M: Model,

Sort by the field path names, largest first, when the URL names no sortable column, as default_sort does ascending.

use tablo_core::{DateColumn, Table, TextColumn, lens};

// The newest posts first.
Table::new((
    TextColumn::new(lens!(Post.title)).sortable(),
    DateColumn::new(lens!(Post.published_at)).sortable(),
))
.default_sort_desc(lens!(Post.published_at));
Source

pub fn default_sort_by(self, sort: Sort) -> Self

Sort by the sortable column sort names when the URL names none: the default for a column of your own, whose name no field path spells. A field’s column takes default_sort, whose path a rename fails to compile.

use tablo_core::{Sort, Table, TextColumn, lens};

// "revenue" is the name a column of your own returns.
Table::new(TextColumn::new(lens!(Post.title))).default_sort_by(Sort::desc("revenue"));

A name the table declares no sortable column for is a declaration error.

Source§

impl<M> Table<M>

Source

pub fn summary(self, summary: Summary<M>) -> Self

Show summary under the rows, after the declared ones.

Source§

impl<M> Table<M>

Source

pub fn tab(self, tab: ListTab<M>) -> Self

Offer tab above the rows, after the declared ones.

Source

pub fn option_tabs<T>(self, lens: impl Into<Path<M, T>>) -> Self
where T: Options + FormScalar + IntoExpr<T>,

Offer a tab admitting every row, keyed all, then a tab per option of the Options field lens binds, in declaration order, after the declared ones. An option’s tab is keyed by its value and labelled by its label, admits the rows holding it, and shows their count.

use tablo_core::{Table, TextColumn, lens};

// `?tab=all`, `?tab=draft` and `?tab=published`.
Table::new(TextColumn::new(lens!(Post.title))).option_tabs(lens!(Post.status));

An option whose value the field’s type does not parse is a declaration error, since its tab would admit no row.

Source§

impl<M> Table<M>

Source

pub fn new(cols: impl IntoColumns<M>) -> Self
where M: Model + IntoExpr<M>,

Declare a table of cols.

Each row is keyed by its record’s primary key, which the row’s action URLs carry.

Source

pub fn column(self, column: impl Column<M> + 'static) -> Self

Append column after the declared ones.

Source

pub fn filters(self, filters: impl IntoFilters<M>) -> Self
where M: Model,

Append filters after the declared ones, such as those a field’s #[columns(filter)] declares.

Source

pub fn pagination(self, pagination: Pagination) -> Self

Page through the rows as pagination says; Pagination::Pages by default.

Source

pub fn action_layout(self, layout: ActionLayout) -> Self

Put a row’s actions where layout says; ActionLayout::Inline by default.

Source

pub fn per_page_options(self, options: impl IntoIterator<Item = usize>) -> Self

The page sizes a reader may pick, beside the table’s own; none offers no choice. DEFAULT_PER_PAGE_OPTIONS by default. A cursor table offers none.

Source

pub fn page_size(self, size: usize) -> Self

Show size rows a page; DEFAULT_PAGE_SIZE by default.

Source

pub fn declaration_errors(&self) -> Vec<DeclarationErrorKind>

What is wrong with this declaration.

Source

pub fn per_page(&self) -> usize

The rows a page shows, as Self::page_size set them.

Render no search toolbar in the table.

Source

pub fn hide_filter_menu(self) -> Self

Render no filter menu in the table’s toolbar.

Source

pub fn deferred_filters(self) -> Self

Apply the filter menu’s controls only when its Apply button is pressed, rather than as each one changes, so a reader sets several filters before the list reruns once. A search, a sort or a tab applies the controls as they stand too, since the toolbar’s form carries them.

Trait Implementations§

Source§

impl<M> Clone for Table<M>

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<M> Debug for Table<M>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<M> !RefUnwindSafe for Table<M>

§

impl<M> !UnwindSafe for Table<M>

§

impl<M> Forget for Table<M>

§

impl<M> Freeze for Table<M>
where Vec<Arc<dyn Column<M>>>: Freeze, Vec<Arc<dyn Filter<M>>>: Freeze, Vec<GroupDef<M>>: Freeze, RowColor<M>: Freeze, Vec<RowLink<M>>: Freeze, Vec<ListTab<M>>: Freeze, Vec<Summary<M>>: Freeze, fn(&M) -> String: Freeze,

§

impl<M> Move for Table<M>

§

impl<M> Send for Table<M>
where Vec<Arc<dyn Column<M>>>: Send, Vec<Arc<dyn Filter<M>>>: Send, Vec<GroupDef<M>>: Send, RowColor<M>: Send, Vec<RowLink<M>>: Send, Vec<ListTab<M>>: Send, Vec<Summary<M>>: Send, fn(&M) -> String: Send,

§

impl<M> Sync for Table<M>
where Vec<Arc<dyn Column<M>>>: Sync, Vec<Arc<dyn Filter<M>>>: Sync, Vec<GroupDef<M>>: Sync, RowColor<M>: Sync, Vec<RowLink<M>>: Sync, Vec<ListTab<M>>: Sync, Vec<Summary<M>>: Sync, fn(&M) -> String: Sync,

§

impl<M> Unpin for Table<M>
where Vec<Arc<dyn Column<M>>>: Unpin, Vec<Arc<dyn Filter<M>>>: Unpin, Vec<GroupDef<M>>: Unpin, RowColor<M>: Unpin, Vec<RowLink<M>>: Unpin, Vec<ListTab<M>>: Unpin, Vec<Summary<M>>: Unpin, fn(&M) -> String: Unpin,

§

impl<M> UnsafeUnpin for Table<M>
where Vec<Arc<dyn Column<M>>>: UnsafeUnpin, Vec<Arc<dyn Filter<M>>>: UnsafeUnpin, Vec<GroupDef<M>>: UnsafeUnpin, RowColor<M>: UnsafeUnpin, Vec<RowLink<M>>: UnsafeUnpin, Vec<ListTab<M>>: UnsafeUnpin, Vec<Summary<M>>: UnsafeUnpin, fn(&M) -> String: UnsafeUnpin,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more