max / makenotwork
| 1 | //! The Askama entry point for a described Export CSV button. |
| 2 | //! |
| 3 | //! The first act taken through the pattern `27d5e5b8` ruled on: a described |
| 4 | //! fragment inside an Askama page, at the granularity of one primitive, in a |
| 5 | //! glue module that is a sibling of `widgets/` rather than a member of it. The |
| 6 | //! whole of the reasoning is in [`super::widgets`]; what is here is the first |
| 7 | //! thing it buys. |
| 8 | //! |
| 9 | //! # What it replaces |
| 10 | //! |
| 11 | //! `data-action="exportCsvButton" data-arg="/api/export/sales" |
| 12 | //! data-arg2="sales.csv"` — a class naming a behaviour plus the two things the |
| 13 | //! behaviour needed, positionally, repeated at five sites in four templates, |
| 14 | //! backed by a `window.exportCsvButton` global that fetched the URL, built an |
| 15 | //! anchor, set `download`, clicked it, and put the button's own label back |
| 16 | //! afterwards. |
| 17 | //! |
| 18 | //! [`Action::saving`] says all of it in one sentence: the answer to this call |
| 19 | //! is a file the reader keeps rather than a view. `htmx-glue.ts` performs it |
| 20 | //! once for everything that says so, which it already did for |
| 21 | //! [`super::buyer_contacts`], the one screen described before this. A terminal |
| 22 | //! renderer writes the file to disk from the same sentence without being told |
| 23 | //! which button this is. |
| 24 | //! |
| 25 | //! # One act, five call sites, and why the URL is a parameter |
| 26 | //! |
| 27 | //! The five exports differ in exactly two things: where the rows come from and |
| 28 | //! what the file is called. Both are facts about the caller rather than about |
| 29 | //! the control, so they are arguments and there is one function. Compare |
| 30 | //! `upload_field`, where the accept list is a fact about MNW and belongs to the |
| 31 | //! module: the test is whether a template would be writing the same literal |
| 32 | //! twice if it were not here. |
| 33 | //! |
| 34 | //! # The visual change, which is the ruling's accepted cost |
| 35 | //! |
| 36 | //! The templates wrote `class="btn-secondary text-sm"`. A described act emits |
| 37 | //! `class="button"`, so these five land on the design system's button. The |
| 38 | //! layout-only classes a caller needs (`broadcast-export`, `nowrap`) stay in |
| 39 | //! Askama on the element around it, where they were always a fact about that |
| 40 | //! page's layout rather than about the control. |
| 41 | |
| 42 | use declare; |
| 43 | |
| 44 | declare! |
| 45 | /// The route each export posts to, and the name the file is kept under. |
| 46 | /// |
| 47 | /// A read would be a link the browser downloads by itself; these are posts, |
| 48 | /// because assembling the file is work the server does rather than a document |
| 49 | /// it already has, and `Action::saves` is independent of the verb for exactly |
| 50 | /// that reason. |
| 51 | /// |
| 52 | /// `awaiting` because the whole file is assembled before any of it comes back. |
| 53 | /// Nothing countable to say about the wait: the row count is often known at the |
| 54 | /// call site and the byte count never is, and `layout::Awaiting` takes a |
| 55 | /// measurement rather than a stand-in for one. |
| 56 | |
| 57 | |
| 58 | pub shape act ; |
| 59 | |
| 60 | include control; |
| 61 | |
| 62 | |
| 63 | declare! |
| 64 | /// The same sentence under a caller's own label. |
| 65 | /// |
| 66 | /// `Export CSV` is right for a control standing alone among others, which is |
| 67 | /// every site [`act`] serves. It is wrong on the export portal, where six cards |
| 68 | /// each name their own subject and the format is already in the card's meta |
| 69 | /// line, so the control there reads `Download`. The label is the only thing |
| 70 | /// that varies: what a caller must not restate is the sentence, which is here |
| 71 | /// once and is what this module exists for. |
| 72 | /// |
| 73 | /// Returns the [`Act`] rather than a [`Node`] because the portal's controls sit |
| 74 | /// in a [`quasi_router::Row`], which holds acts rather than nodes. |
| 75 | |
| 76 | |
| 77 | pub shape control ; |
| 78 | |
| 79 | act label to post route saving filename awaiting; |
| 80 | |
| 81 | |
| 82 | /// The same act as a fragment, for a template to drop in place. |
| 83 | /// |
| 84 | /// No shell and no region: this lands inside a document Askama has already |
| 85 | /// built, which is what every glue module here does. |
| 86 | |
| 87 | |
| 88 | use Serves as _; |
| 89 | |
| 90 | new.fragment |
| 91 | |
| 92 | |
| 93 | |
| 94 | |
| 95 | /// The three things `data-action` + two positional arguments used to say, |
| 96 | /// said once. `data-saves` is the whole of the host's instruction. |
| 97 | |
| 98 | |
| 99 | let html = html; |
| 100 | |
| 101 | assert!; |
| 102 | assert!; |
| 103 | assert!; |
| 104 | |
| 105 | |
| 106 | /// The mark that says the call waits. A file the server assembles is the |
| 107 | /// report case `Action::awaiting` exists for, and the button locking itself |
| 108 | /// is what the deleted global did by hand. |
| 109 | |
| 110 | |
| 111 | let html = html; |
| 112 | |
| 113 | assert!; |
| 114 | |
| 115 | |
| 116 | /// Nothing positional survives. The failure this replaced was a second |
| 117 | /// argument drifting from the first at one of five sites, silently. |
| 118 | |
| 119 | |
| 120 | let html = html; |
| 121 | |
| 122 | assert!; |
| 123 | assert!; |
| 124 | |
| 125 | |
| 126 |