Skip to main content

tablo_core/table/
row_link.rs

1//! [`Table::row_link`]: a link among a row's actions.
2
3use std::sync::Arc;
4
5use derive_where::derive_where;
6use topcoat::icon::IconData;
7
8use super::Table;
9use crate::{Reads, Text};
10
11/// A link among a row's actions, such as a product's storefront page: unlike an
12/// [`Action`](crate::Action), it posts nothing and runs no policy check, so it suits only a page
13/// the user may already open.
14///
15/// ```rust
16/// # #[derive(Debug, Clone, toasty::Model, tablo_core::ModelColumns)]
17/// # struct Product { #[key] #[auto] id: uuid::Uuid, name: String, slug: String }
18/// use tablo_core::{Reads, RowLink, Table, TextColumn, lens};
19///
20/// Table::new(TextColumn::new(lens!(Product.name))).row_link(
21///     RowLink::new("Open product", |product: &Product| {
22///         Some(format!("/shop/{}", product.slug))
23///     })
24///     .new_tab()
25///     .reading(Reads::of(lens!(Product.slug))),
26/// );
27/// ```
28#[derive_where(Clone)]
29pub struct RowLink<M> {
30    pub(crate) label: Text,
31    url: crate::column::ReadFn<M, Option<String>>,
32    pub(crate) icon: Option<IconData>,
33    pub(crate) new_tab: bool,
34    /// The fields `url` reads, when declared.
35    pub(super) reads: Option<Reads<M>>,
36    /// Whether `url` reads a copy of the row without its relations.
37    bare: bool,
38}
39
40impl<M> RowLink<M> {
41    /// A link reading `label` to `url(row)`; a row it gives `None` shows no link. The URL is
42    /// relative, or absolute over `http`, `https`, `mailto` or `tel`: a row whose URL has any
43    /// other scheme, such as `javascript:`, shows no link either.
44    pub fn new(
45        label: impl Into<Text>,
46        url: impl Fn(&M) -> Option<String> + Send + Sync + 'static,
47    ) -> Self {
48        Self {
49            label: label.into(),
50            url: Arc::new(url),
51            icon: None,
52            new_tab: false,
53            reads: None,
54            bare: false,
55        }
56    }
57
58    /// Show `icon` before the label.
59    pub fn icon(mut self, icon: IconData) -> Self {
60        self.icon = Some(icon);
61        self
62    }
63
64    /// Open the link in a new tab.
65    pub fn new_tab(mut self) -> Self {
66        self.new_tab = true;
67        self
68    }
69
70    /// Declare the fields the URL reads. A resource that
71    /// [restricts a field](crate::ResourceDef::field_access) refuses the table until each of its
72    /// links declares them, and a user who may not view one of them sees no such link.
73    pub fn reading(mut self, reads: Reads<M>) -> Self {
74        self.reads = Some(reads);
75        self
76    }
77
78    /// Where the link leads from `row`, when it shows there.
79    pub(crate) fn url_of(&self, row: &M) -> Option<String> {
80        (self.url)(row).filter(|url| crate::column::link_href(url).is_some())
81    }
82}
83
84impl<M> Table<M> {
85    /// Add `link` to each row's actions, before its custom actions, after the links already
86    /// declared.
87    pub fn row_link(mut self, link: RowLink<M>) -> Self {
88        self.row_links.push(link);
89        self
90    }
91
92    /// The links each row shows.
93    pub(crate) fn row_links(&self) -> &[RowLink<M>] {
94        &self.row_links
95    }
96
97    /// Whether a row link reads the row's relations as loaded.
98    pub(crate) fn row_link_reads_row(&self) -> bool {
99        self.row_links.iter().any(|link| !link.bare)
100    }
101
102    /// Have each row link declaring it reads none of the row's relations read `bare(row)`.
103    pub(super) fn bare_row_links(&mut self, bare: fn(&M) -> M)
104    where
105        M: toasty::schema::Model + 'static,
106    {
107        for link in &mut self.row_links {
108            if crate::column::reads_own_fields(link.reads.as_ref()) {
109                let url = link.url.clone();
110                link.url = Arc::new(move |row: &M| url(&bare(row)));
111                link.bare = true;
112            }
113        }
114    }
115
116    /// Whether a row link does not declare what its URL reads.
117    pub(crate) fn row_link_reads_undeclared(&self) -> bool {
118        self.row_links.iter().any(|link| link.reads.is_none())
119    }
120}