Add dynamic tag creation

This commit is contained in:
Paul Konstantin Gerke 2022-02-18 00:22:01 +01:00
parent 5046b754e5
commit c397836e68
3 changed files with 122 additions and 11 deletions

View File

@ -92,9 +92,9 @@ class BMIViewSet(viewsets.ModelViewSet):
serializer_class = serializers.BMISerializer serializer_class = serializers.BMISerializer
filterset_fields = ("child", "date") filterset_fields = ("child", "date")
class TagsViewSet(viewsets.ModelViewSet): class TagsViewSet(viewsets.ModelViewSet):
queryset = models.BabyBuddyTag.objects.all() queryset = models.BabyBuddyTag.objects.all()
serializer_class = serializers.TagsSerializer serializer_class = serializers.TagsSerializer
lookup_field = "slug" lookup_field = "slug"
filterset_fields = ("last_used",) filterset_fields = ("last_used", "name")

View File

@ -2,17 +2,18 @@
{{ widget }} {{ widget }}
</div> </div>
<div class="form_control" <div class="form_control" data-tags-url="{% url 'api:api-root' %}tags/"
{% for k, v in widget.attrs.items %} {% for k, v in widget.attrs.items %}
{{ k }}={{ v }} {{ k }}={{ v }}
{% endfor %}> {% endfor %}>
<span class="prototype-tag btn badge badge-pill cursor-pointer" style="display: none;"> {% csrf_token %}
<span class="prototype-tag btn badge badge-pill cursor-pointer mr-1" style="display: none;">
PROTOTYPE PROTOTYPE
<span class="add-remove-icon pl-1 pr-1">+ or -</span> <span class="add-remove-icon pl-1 pr-1">+ or -</span>
</span> </span>
<div class="current_tags" style="min-height: 2em;"> <div class="current_tags" style="min-height: 2em;">
{% for t in widget.value %} {% for t in widget.value %}
<span data-value="{{ t.name }}" data-color="{{ t.color }}" class="tag btn badge badge-pill cursor-pointer" style="background-color: {{ t.color }};"> <span data-value="{{ t.name }}" data-color="{{ t.color }}" class="tag btn badge badge-pill cursor-pointer mr-1" style="background-color: {{ t.color }};">
{{ t.name }} {{ t.name }}
<span class="add-remove-icon pl-1 pr-1">-</span> <span class="add-remove-icon pl-1 pr-1">-</span>
</span> </span>
@ -21,11 +22,20 @@
<div class="new-tags"> <div class="new-tags">
<span>Suggestions:</span> <span>Suggestions:</span>
{% for t in widget.tag_suggestions.quick %} {% for t in widget.tag_suggestions.quick %}
<span data-value="{{ t.name }}" data-color="{{ t.color }}" class="tag btn badge badge-pill cursor-pointer" style="background-color: {{ t.color }};"> <span data-value="{{ t.name }}" data-color="{{ t.color }}" class="tag btn badge badge-pill cursor-pointer mr-1" style="background-color: {{ t.color }};">
{{ t.name }} {{ t.name }}
<span class="add-remove-icon pl-1 pr-1">+</span> <span class="add-remove-icon pl-1 pr-1">+</span>
</span> </span>
{% endfor %} {% endfor %}
<div class="createtag btn badge badge-pill cursor-pointer border">
<span class="plus">+</span>
<div class="create-tag-inputs input-group ml-3 mr-3 d-none">
<input class="form-control" type="text" name="" placeholder="Tag name">
<div class=="input-group-append">
<button class="btn btn-outline-primary btn-add-new-tag" type="button">Add</button>
</div>
</div>
</div>
</div> </div>
<input <input
type="hidden" type="hidden"
@ -35,12 +45,102 @@
<script> <script>
window.addEventListener('load', () => { window.addEventListener('load', () => {
const widget = document.getElementById('{{ widget.attrs.id }}'); const widget = document.getElementById('{{ widget.attrs.id }}');
const csrfToken = document.querySelector('input[name="csrfmiddlewaretoken"]').value;
const prototype = widget.querySelector('.prototype-tag'); const prototype = widget.querySelector('.prototype-tag');
const currentTags = widget.querySelector('.current_tags'); const currentTags = widget.querySelector('.current_tags');
const newTags = widget.querySelector('.new-tags'); const newTags = widget.querySelector('.new-tags');
const inputElement = widget.querySelector('input'); const inputElement = widget.querySelector('input[type="hidden"]');
const apiTagsUrl = widget.getAttribute('data-tags-url');
const createTagButton = widget.querySelector('.createtag');
const addTagInput = createTagButton.querySelector('input[type="text"]');
const addTagButton = createTagButton.querySelector('.btn-add-new-tag');
function showCreateTag() {
createTagButton.removeEventListener('click', showCreateTag);
createTagButton.classList.remove('btn');
createTagButton.querySelector(".plus").classList.add("d-none");
createTagButton.querySelector(".create-tag-inputs").classList.remove("d-none");
addTagInput.value = "";
}
createTagButton.addEventListener('click', showCreateTag);
function doReq(method, uri, data, success, fail) {
const req = new XMLHttpRequest();
req.addEventListener('load', () => {
if ((req.status >= 200) && (req.status < 300)) {
success(req.responseText, req);
} else {
fail(req.responseText, req);
}
});
for (const name of ["error", "timeout", "abort"]) {
req.addEventListener(name, () => {
fail(req.responseText, req);
});
}
req.timeout = 20000;
req.open(method, uri);
req.setRequestHeader("Content-Type", "application/json");
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("X-CSRFTOKEN", csrfToken);
req.send(data);
}
function createTag() {
const tagName = addTagInput.value.trim();
const uriTagName = encodeURIComponent(tagName);
const data = JSON.stringify({
'name': addTagInput.value
});
function close() {
createTagButton.addEventListener('click', showCreateTag);
createTagButton.classList.add('btn');
createTagButton.querySelector(".plus").classList.remove("d-none");
createTagButton.querySelector(".create-tag-inputs").classList.add("d-none");
}
function fail() {
close();
alert("Error creating tag");
}
function addTag(name, color) {
const foundTag = widget.querySelector(`span[data-value="${name}"]`);
if (foundTag) {
foundTag.parentNode.removeChild(foundTag);
}
const tag = createNewTag(name, color, "-");
insertTag(currentTags, tag);
removeTagCallback(tag);
close();
}
doReq("GET", `${apiTagsUrl}?name=${uriTagName}`, null,
(text) => {
const json = JSON.parse(text);
if (json.count) {
const tagJson = json.results[0];
addTag(tagJson.name, tagJson.color);
} else {
doReq("POST", apiTagsUrl, data,
(text) => {
const tagJson = JSON.parse(text);
addTag(tagJson.name, tagJson.color);
}, fail
);
}
}, fail
);
}
addTagButton.addEventListener('click', createTag);
function hexParse(x) { function hexParse(x) {
return parseInt(x, 16); return parseInt(x, 16);
@ -52,8 +152,6 @@
avgColor += hexParse(colorStr.substring(3, 5)) * 1.5; avgColor += hexParse(colorStr.substring(3, 5)) * 1.5;
avgColor += hexParse(colorStr.substring(5, 7)) * 1.0; avgColor += hexParse(colorStr.substring(5, 7)) * 1.0;
console.log(`C ${colorStr} -> ${avgColor}`);
if (avgColor > 200) { if (avgColor > 200) {
return "#101010"; return "#101010";
} else { } else {
@ -79,10 +177,22 @@
function createNewTag(name, color, actionSymbol) { function createNewTag(name, color, actionSymbol) {
const tag = prototype.cloneNode(true); const tag = prototype.cloneNode(true);
tag.classList.remove("prototype-tag");
tag.classList.add("tag");
updateTag(tag, name, color, actionSymbol); updateTag(tag, name, color, actionSymbol);
return tag; return tag;
} }
function insertTag(list, tag) {
const createTag = list.querySelector(".createtag");
if (createTag) {
list.insertBefore(tag, createTag);
} else {
list.appendChild(tag);
}
updateInputList();
}
function registerNewCallback(tag, newParent, newSymbol, onClicked) { function registerNewCallback(tag, newParent, newSymbol, onClicked) {
function callback(event) { function callback(event) {
tag.parentNode.removeChild(tag); tag.parentNode.removeChild(tag);
@ -92,7 +202,8 @@
tag.getAttribute("data-color"), tag.getAttribute("data-color"),
newSymbol newSymbol
); );
newParent.appendChild(tag);
insertTag(newParent, tag);
tag.removeEventListener('click', callback); tag.removeEventListener('click', callback);
onClicked(tag); onClicked(tag);

View File

@ -26,7 +26,7 @@ class TagsEditor(Widget):
result = super().get_context(name, value, attrs) result = super().get_context(name, value, attrs)
tag_names = set(x['name'] for x in result['widget']['value']) tag_names = set(x['name'] for x in (result.get('widget', {}).get('value', None) or []))
quick_suggestion_tags = [ quick_suggestion_tags = [
t for t in quick_suggestion_tags t for t in quick_suggestion_tags
if t.name not in tag_names if t.name not in tag_names