1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
|
+++
title = "Bulk Update"
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 the form submission (`POST` request):
```html
<form id="checked-contacts"
hx-post="/users"
hx-swap="innerHTML settle:3s"
hx-target="#toast">
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Active</th>
</tr>
</thead>
<tbody id="tbody">
<tr>
<td>Joe Smith</td>
<td>joe@smith.org</td>
<td><input type="checkbox" name="active:joe@smith.org"></td>
</tr>
...
</tbody>
</table>
<input type="submit" value="Bulk Update" class="btn primary">
<output id="toast"></output>
</form>
```
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 an `<output>` element to politely announce the update for accessibility. Note
that the `<output>` element is appropriate for announcing the result of an action
in a specific form, but if you need to announce general-purpose messages that are
not connected to a form it would make sense to use an ARIA live region, eg
`<p id="toast" aria-live="polite"></p>`.
```css
#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.
<style scoped="">
#toast.htmx-settling {
opacity: 100;
}
#toast {
background: #E1F0DA;
opacity: 0;
transition: opacity 3s ease-out;
}
</style>
{{ demoenv() }}
<script>
//=========================================================================
// Fake Server Side Code
//=========================================================================
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 {
all() {
return data;
},
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.all());
});
/*
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;
}
}
// 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 `Activated ${activated} and deactivated ${deactivated} users`;
});
// templates
function displayUI(contacts) {
return `<h3>Select Rows And Activate Or Deactivate Below</h3>
<form
id="checked-contacts"
hx-post="/users"
hx-swap="innerHTML settle:3s"
hx-target="#toast"
>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Active</th>
</tr>
</thead>
<tbody id="tbody">
${displayTable(contacts)}
</tbody>
</table>
<input type="submit" value="Bulk Update" class="btn primary">
<output id="toast"></output>
</form>
<br>`;
}
function displayTable(contacts) {
var txt = "";
for (email of Object.keys(contacts)) {
txt += `
<tr>
<td>${contacts[email].name}</td>
<td>${email}</td>
<td>
<input
type="checkbox"
name="active:${email}"
${contacts[email].status === 'Active' ? 'checked' : ''}>
</td>
</tr>
`;
}
return txt;
}
</script>
|