From dece4d8886dffb9b1f900f115b64fd0203f3c130 Mon Sep 17 00:00:00 2001 From: Yawar Amin Date: Sat, 27 Jan 2024 01:10:39 -0500 Subject: [PATCH 1/2] Minimize re-rendering of 'bulk update users' table And use ARIA to announce a summary of the bulk update. By using a checkbox input to represent the activation status, we don't need to re-render it when it's changed, because the checkbox itself manages that state. Re: #1431 --- www/content/examples/bulk-update.md | 198 ++++++++++++++++------------ 1 file changed, 112 insertions(+), 86 deletions(-) diff --git a/www/content/examples/bulk-update.md b/www/content/examples/bulk-update.md index 9e3433977..4f0066e4e 100644 --- a/www/content/examples/bulk-update.md +++ b/www/content/examples/bulk-update.md @@ -5,65 +5,64 @@ template = "demo.html" This demo shows how to implement a common pattern where rows are selected and then bulk updated. This is accomplished by putting a form around a table, with checkboxes in the table, and then including the checked -values in `PUT`'s to two different endpoints: `activate` and `deactivate`: +values in the form submission (`POST` request): ```html -
- - -
-
- - + - - + - + ...
Name EmailStatusActive
Joe Smith joe@smith.orgActive
+ +
``` -The server will either activate or deactivate the checked users and then rerender the `tbody` tag with -updated rows. It will apply the class `activate` or `deactivate` to rows that have been mutated. This allows -us to use a bit of CSS to flash a color helping the user see what happened: +The server will bulk-update the statuses based on the values of the checkboxes. +We respond with a small toast message about the update to inform the user, and +use ARIA to politely announce the update for accessibility. ```css - .htmx-settling tr.deactivate td { - background: lightcoral; - } - .htmx-settling tr.activate td { - background: darkseagreen; - } - tr td { - transition: all 1.2s; - } +#toast.htmx-settling { + opacity: 100; +} + +#toast { + background: #E1F0DA; + opacity: 0; + transition: opacity 3s ease-out; +} ``` +The cool thing is that, because HTML form inputs already manage their own state, +we don't need to re-render any part of the users table. The active users are +already checked and the inactive ones unchecked! + You can see a working example of this code below. {{ demoenv() }} @@ -73,91 +72,118 @@ You can see a working example of this code below. // Fake Server Side Code //========================================================================= - // data - var dataStore = function(){ - var data = [ - { name: "Joe Smith", email: "joe@smith.org", status: "Active" }, - { name: "Angie MacDowell", email: "angie@macdowell.org", status: "Active" }, - { name: "Fuqua Tarkenton", email: "fuqua@tarkenton.org", status: "Active" }, - { name: "Kim Yee", email: "kim@yee.org", status: "Inactive" } - ]; + const dataStore = (() => { + const data = { + "joe@smith.org": {name: 'Joe Smith', status: 'Active'}, + "angie@macdowell.org": {name: 'Angie MacDowell', status: 'Active'}, + "fuqua@tarkenton.org": {name: 'Fuqua Tarkenton', status: 'Active'}, + "kim@yee.org": {name: 'Kim Yee', status: 'Inactive'}, + }; + return { - findContactById : function(id) { - return data[id]; - }, - allContacts : function() { + all() { return data; - } - } - }() + }, - function getIds(params) { - if(params['ids']) { - if(Array.isArray(params['ids'])) { - return params['ids'].map(x => parseInt(x)) - } else { - return [parseInt(params['ids'])]; - } - } else { - return []; - } - } + activate(email) { + if (data[email].status === 'Active') { + return 0; + } else { + data[email].status = 'Active'; + return 1; + } + }, + + deactivate(email) { + if (data[email].status === 'Inactive') { + return 0; + } else { + data[email].status = 'Inactive'; + return 1; + } + }, + }; + })(); // routes init("/demo", function(request){ - return displayUI(dataStore.allContacts()); + return displayUI(dataStore.all()); }); - onPut("/activate", function(request, params){ - var ids = getIds(params); - for (var i = 0; i < ids.length; i++) { - dataStore.findContactById(ids[i])['status'] = 'Active'; + /* + Params look like: + {"active:joe@smith.org":"on","active:angie@macdowell.org":"on","active:fuqua@tarkenton.org":"on"} + */ + onPost("/users", function (req, params) { + const actives = {}; + let activated = 0; + let deactivated = 0; + + // Build a set of active users for efficient lookup + for (const param of Object.keys(params)) { + const nameEmail = param.split(':'); + if (nameEmail[0] === 'active') { + actives[nameEmail[1]] = true; } - return displayTable(ids, dataStore.allContacts(), 'activate'); - }); + } - onPut("/deactivate", function (req, params) { - var ids = getIds(params); - for (var i = 0; i < ids.length; i++) { - dataStore.findContactById(ids[i])['status'] = 'Inactive'; + // Activate or deactivate users based on the lookup + for (const email of Object.keys(dataStore.all())) { + if (actives[email]) { + activated += dataStore.activate(email); + } else { + deactivated += dataStore.deactivate(email); } - return displayTable(ids, dataStore.allContacts(), 'deactivate'); + } + + return `Activated ${activated} and deactivated ${deactivated} user(s)`; }); // templates function displayUI(contacts) { return `

Select Rows And Activate Or Deactivate Below

-
+ - - + - ${displayTable([], contacts, "")} + ${displayTable(contacts)}
Name EmailStatusActive
+ +
-
-
-
- - -
` +
`; } - function displayTable(ids, contacts, action) { + function displayTable(contacts) { var txt = ""; - for (var i = 0; i < contacts.length; i++) { - var c = contacts[i]; - txt += `\n - ${c.name}${c.email}${c.status} - ` + + for (email of Object.keys(contacts)) { + txt += ` + + ${contacts[email].name} + ${email} + + + + +`; } + return txt; } From 556fad83893b0eb8a86f37242d330624239ab206 Mon Sep 17 00:00:00 2001 From: Yawar Amin Date: Sat, 27 Jan 2024 01:42:04 -0500 Subject: [PATCH 2/2] Improve screen reader pronunciation --- www/content/examples/bulk-update.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/content/examples/bulk-update.md b/www/content/examples/bulk-update.md index 4f0066e4e..972d63721 100644 --- a/www/content/examples/bulk-update.md +++ b/www/content/examples/bulk-update.md @@ -136,7 +136,7 @@ You can see a working example of this code below. } } - return `Activated ${activated} and deactivated ${deactivated} user(s)`; + return `Activated ${activated} and deactivated ${deactivated} users`; }); // templates