Skip to main content

tablo_core/resource/def/
header_actions.rs

1//! The header actions a [`ResourceDef`] declares, each with the badge its button may show.
2
3use toasty::stmt::Expr;
4
5use crate::{HeaderAction, Resource, ResourceDef};
6
7impl<R: Resource> ResourceDef<R> {
8    /// Adds the [`HeaderAction`] `A`, which acts on no record, to the list page's header, after
9    /// the ones already declared.
10    ///
11    /// The policy decides who may run it, through [`Ability::ViewAny`](crate::Ability::ViewAny)
12    /// and [`Ability::RunHeader`](crate::Ability::RunHeader) with its name, which no other action
13    /// of the resource may share. An `A::NAME` that is not a single URL path segment does not
14    /// compile.
15    ///
16    /// ```rust,compile_fail,E0080
17    /// # use tablo_core::{HeaderAction, NoForm, Resource, ResourceDef};
18    /// # use topcoat::{Result, context::Cx};
19    /// # #[derive(Debug, Clone, toasty::Model, tablo_core::ModelColumns)]
20    /// # struct Post { #[key] #[auto] id: uuid::Uuid, title: String }
21    /// # struct PostResource;
22    /// # impl Resource for PostResource {
23    /// #     type Model = Post;
24    /// #     type Form = NoForm<Post>;
25    /// # }
26    /// struct RecountTags;
27    ///
28    /// impl HeaderAction for RecountTags {
29    ///     type Input = ();
30    ///     const NAME: &'static str = "recount/tags";
31    ///
32    ///     async fn run(_: &Cx, _: (), _: &mut dyn toasty::Executor) -> Result<tablo_core::Outcome, tablo_core::WriteError<std::convert::Infallible>> {
33    ///         Ok(tablo_core::Outcome::done())
34    ///     }
35    /// }
36    ///
37    /// let _def = ResourceDef::<PostResource>::new().header_action::<RecountTags>();
38    /// ```
39    #[must_use]
40    pub fn header_action<A: HeaderAction>(mut self) -> Self {
41        self.header_actions = self.header_actions.add::<A>();
42        self
43    }
44
45    /// Declares the header action `A`, as [`header_action`](Self::header_action) does, and badges
46    /// its button with the number of rows of [`scoped_query`](crate::scoped_query) that `filter`
47    /// matches, as [`navigation_badge`](Self::navigation_badge) badges the sidebar. The count runs
48    /// each time the list renders, and a zero shows no badge.
49    ///
50    /// ```rust
51    /// # use tablo_core::{HeaderAction, NoForm, Resource, ResourceDef};
52    /// # use topcoat::{Result, context::Cx};
53    /// # #[derive(Debug, Clone, toasty::Model, tablo_core::ModelColumns)]
54    /// # struct Post { #[key] #[auto] id: uuid::Uuid, title: String, draft: bool }
55    /// # struct PostResource;
56    /// # impl Resource for PostResource {
57    /// #     type Model = Post;
58    /// #     type Form = NoForm<Post>;
59    /// # }
60    /// # struct PublishDrafts;
61    /// # impl HeaderAction for PublishDrafts {
62    /// #     type Input = ();
63    /// #     const NAME: &'static str = "publish-drafts";
64    /// #     async fn run(_: &Cx, _: (), _: &mut dyn toasty::Executor) -> Result<tablo_core::Outcome, tablo_core::WriteError<std::convert::Infallible>> {
65    /// #         Ok(tablo_core::Outcome::done())
66    /// #     }
67    /// # }
68    /// // "Publish drafts 3" while three drafts wait.
69    /// let _def = ResourceDef::<PostResource>::new()
70    ///     .header_action_with_badge::<PublishDrafts>(Post::fields().draft().eq(true));
71    /// ```
72    #[must_use]
73    pub fn header_action_with_badge<A: HeaderAction>(mut self, filter: Expr<bool>) -> Self {
74        self.header_badges.push((A::NAME, filter));
75        self.header_action::<A>()
76    }
77}