Closed simonw closed 4 days ago
I can use the actors_from_ids mechanism to show better actors, and I can update the design of the datasette_acl_actor_ids
plugin hook to return whole actors, not just IDs, and implement one of the JavaScript autocomplete things I considered in https://github.com/datasette/datasette-acl/issues/18#issuecomment-2323460110
From https://alphagov.github.io/accessible-autocomplete/#progressive-enhancement
If your autocomplete is meant to select from a small list of options (a few hundred), we strongly suggest that you render a
<select>
menu on the server, and use progressive enhancement.
Instances with more than a few hundred users will be rare, I'm going to do that.
I tried getting the alphagov one working - it's pretty glitchy for me:
I had to hack the source code and add the selected text here to disable the 1Password icon on it too:
Here's my prototype:
diff --git a/datasette_acl/templates/manage_acl_group.html b/datasette_acl/templates/manage_acl_group.html
index b293f42..529b6e8 100644
--- a/datasette_acl/templates/manage_acl_group.html
+++ b/datasette_acl/templates/manage_acl_group.html
@@ -3,6 +3,8 @@
{% block title %}{{ name }}{% endblock %}
{% block extra_head %}
+<script src="{{ urls.static_plugins("datasette-acl", "accessible-autocomplete.min.js") }}"></script>
+<link rel="stylesheet" href="{{ urls.static_plugins("datasette-acl", "accessible-autocomplete.min.css") }}">
<style>
.remove-button {
background-color: #fff;
@@ -75,14 +77,22 @@
<form action="{{ request.path }}" method="post">
<input type="hidden" name="csrftoken" value="{{ csrftoken() }}">
- <p><label>User ID <input type="text" data-1p-ignore name="add"{% if valid_actor_ids %} list="actor-ids"{% endif %}></label> <input type="submit" value="Add member"></p>
- {% if valid_actor_ids %}
- <datalist id="actor-ids">{% for actor_id in valid_actor_ids %}
- <option value="{{ actor_id }}"></option>
+ <p><label for="id_add">User ID</label> <select id="id_add" name="add">
+ {% for actor_id in valid_actor_ids %}
+ <option>{{ actor_id }}</option>
{% endfor %}
- </datalist>
- {% endif %}
+ </select>
</form>
+
+
+<script>
+const userSelect = document.querySelector('#id_add');
+
+accessibleAutocomplete.enhanceSelectElement({
+ selectElement: userSelect
+});
+document.getElementById('add').setAttribute('data-1p-ignore', '');
+</script>
{% endif %}
{% endif %}
Got this working with https://projects.verou.me/awesomplete/
I'd prefer it if selecting the item submitted the form, you have to enter twice right now.
Here's that prototype so far (the custom event thing doesn't work though):
diff --git a/datasette_acl/templates/manage_acl_group.html b/datasette_acl/templates/manage_acl_group.html
index b293f42..9bbf7f3 100644
--- a/datasette_acl/templates/manage_acl_group.html
+++ b/datasette_acl/templates/manage_acl_group.html
@@ -3,6 +3,8 @@
{% block title %}{{ name }}{% endblock %}
{% block extra_head %}
+<script src="{{ urls.static_plugins("datasette-acl", "awesomplete.min.js") }}"></script>
+<link rel="stylesheet" href="{{ urls.static_plugins("datasette-acl", "awesomplete.css") }}">
<style>
.remove-button {
background-color: #fff;
@@ -75,7 +77,7 @@
<form action="{{ request.path }}" method="post">
<input type="hidden" name="csrftoken" value="{{ csrftoken() }}">
- <p><label>User ID <input type="text" data-1p-ignore name="add"{% if valid_actor_ids %} list="actor-ids"{% endif %}></label> <input type="submit" value="Add member"></p>
+ <p><label>User ID <input id="id_add" type="text" data-minchars="1" data-1p-ignore name="add"{% if valid_actor_ids %} list="actor-ids"{% endif %}></label> <input type="submit" value="Add member"></p>
{% if valid_actor_ids %}
<datalist id="actor-ids">{% for actor_id in valid_actor_ids %}
<option value="{{ actor_id }}"></option>
@@ -118,10 +120,16 @@
{% endif %}
<script>
-// Focus on add input if we just added a member
-if (window.location.hash === '#focus-add') {
- document.querySelector('input[name="add"]').focus();
-}
+document.addEventListener('DOMContentLoaded', function() {
+ document.querySelector('#id_add').addEventListener('awesomplete-select', (ev) => {
+ console.log(ev);
+ // this.closest('form').submit();
+ });
+ // Focus on add input if we just added a member
+ if (window.location.hash === '#focus-add') {
+ document.querySelector('input[name="add"]').focus();
+ }
+});
</script>
{% endblock %}
I'm going to try https://choices-js.github.io/Choices/
I like Choices best:
That prototoype so far:
diff --git a/datasette_acl/templates/manage_acl_group.html b/datasette_acl/templates/manage_acl_group.html
index b293f42..12b1c0b 100644
--- a/datasette_acl/templates/manage_acl_group.html
+++ b/datasette_acl/templates/manage_acl_group.html
@@ -3,6 +3,8 @@
{% block title %}{{ name }}{% endblock %}
{% block extra_head %}
++<script src="{{ urls.static_plugins("datasette-acl", "choices-9.0.1.min.js") }}"></script>
++<link rel="stylesheet" href="{{ urls.static_plugins("datasette-acl", "choices-9.0.1.min.css") }}">
<style>
.remove-button {
background-color: #fff;
@@ -75,13 +77,17 @@
<form action="{{ request.path }}" method="post">
<input type="hidden" name="csrftoken" value="{{ csrftoken() }}">
- <p><label>User ID <input type="text" data-1p-ignore name="add"{% if valid_actor_ids %} list="actor-ids"{% endif %}></label> <input type="submit" value="Add member"></p>
- {% if valid_actor_ids %}
- <datalist id="actor-ids">{% for actor_id in valid_actor_ids %}
- <option value="{{ actor_id }}"></option>
- {% endfor %}
- </datalist>
- {% endif %}
+ <div style="display: flex; align-items: center; gap: 10px; max-width: 500px">
+ <label for="id_add" style="flex-shrink: 0;">User ID</label>
+ <div class="choices" data-type="select-one" tabindex="0" style="flex-grow: 1;">
+ <select id="id_add" name="add">
+ <option></option>
+ {% for actor_id in valid_actor_ids %}
+ <option>{{ actor_id }}</option>
+ {% endfor %}
+ </select>
+ </div>
+ </div>
</form>
{% endif %}
{% endif %}
@@ -118,10 +124,17 @@
{% endif %}
<script>
-// Focus on add input if we just added a member
-if (window.location.hash === '#focus-add') {
- document.querySelector('input[name="add"]').focus();
-}
+document.addEventListener('DOMContentLoaded', function() {
+ const select = document.querySelector('#id_add');
+ const choices = new Choices(select);
+ select.addEventListener('addItem', (ev) => {
+ ev.target.closest('form').submit()
+ });
+ // Focus on add input if we just added a member
+ if (window.location.hash === '#focus-add') {
+ choices.showDropdown();
+ }
+});
</script>
{% endblock %}
Claude artifact showing what it could look like if I use this rather than the table of checkboxes:
https://claude.site/artifacts/3b83782b-74d3-4759-ac68-523fe2a905eb
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Group Permissions UI</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/choices.js/10.2.0/choices.min.css">
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f0f0f0;
}
.container {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
h1 {
color: #333;
}
.group-row {
display: flex;
align-items: center;
margin-bottom: 10px;
padding: 10px;
background-color: #f9f9f9;
border-radius: 4px;
}
.group-name {
width: 150px;
font-weight: bold;
color: #4a4a4a;
}
select[multiple] {
min-width: 200px;
}
/* Choices.js custom styles */
.choices__inner {
min-height: 30px;
padding: 4px 7.5px 4px 3.75px;
}
.choices__list--multiple .choices__item {
font-size: 12px;
padding: 2px 5px;
margin-bottom: 0;
}
</style>
</head>
<body>
<div class="container">
<h1>Group Permissions</h1>
<div id="groups-container">
<div class="group-row">
<span class="group-name">staff (1)</span>
<select multiple id="select-staff">
<option value="insert-row">insert-row</option>
<option value="delete-row">delete-row</option>
<option value="update-row">update-row</option>
<option value="alter-table" selected>alter-table</option>
<option value="drop-table">drop-table</option>
</select>
</div>
<div class="group-row">
<span class="group-name">devs (5)</span>
<select multiple id="select-devs">
<option value="insert-row" selected>insert-row</option>
<option value="delete-row" selected>delete-row</option>
<option value="update-row" selected>update-row</option>
<option value="alter-table" selected>alter-table</option>
<option value="drop-table">drop-table</option>
</select>
</div>
<div class="group-row">
<span class="group-name">newgroup (0)</span>
<select multiple id="select-newgroup">
<option value="insert-row">insert-row</option>
<option value="delete-row">delete-row</option>
<option value="update-row">update-row</option>
<option value="alter-table" selected>alter-table</option>
<option value="drop-table">drop-table</option>
</select>
</div>
<div class="group-row">
<span class="group-name">muppets (5)</span>
<select multiple id="select-muppets">
<option value="insert-row">insert-row</option>
<option value="delete-row">delete-row</option>
<option value="update-row">update-row</option>
<option value="alter-table">alter-table</option>
<option value="drop-table">drop-table</option>
</select>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/choices.js/10.2.0/choices.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const selects = document.querySelectorAll('select[multiple]');
selects.forEach(select => {
new Choices(select, {
removeItemButton: true,
classNames: {
containerOuter: 'choices custom-choices',
}
});
});
});
</script>
</body>
</html>
Conversation transcript: https://gist.github.com/simonw/7b87b24cd53daf8ea05170c3c8013e3c
I implemented Choices for permission selection and user selection here:
Still need to differentiate user ID from user display though.
New datasette_acl_actor_ids
hook design: it can return a list of IDs, or it can return a list of dicts with "id"
and "display"
keys.
I'm going to rename datasette_acl_actor_ids
to datasette_acl_valid_actors
.
The autocomplete from this issue:
18
Isn't fit for purpose on Datasette Cloud, where user IDs are integers.
Need to be able to display their "display names" in lists of e.g. members of a group, and also autocomplete against those when adding users to groups or to table permissions.
Also: the
<datalist>
autocomplete really isn't very good - it still allows freeform text input and, at least on Firefox, shows a whole butch of irrelevant suggestions mixed in with the "valid" options: