From 747b398bd5987ad95fd36d4f5d347a7744f1d35a Mon Sep 17 00:00:00 2001 From: Paul Konstantin Gerke Date: Tue, 22 Feb 2022 19:40:27 +0100 Subject: [PATCH] Create new javascript file for tags editor --- babybuddy/static_src/js/tags_editor.js | 206 +++++++++++++++++++++ babybuddy/templates/babybuddy/form.html | 3 + core/templates/core/widget_tag_editor.html | 201 +------------------- core/widgets.py | 16 +- gulpfile.config.js | 3 + gulpfile.js | 6 + 6 files changed, 233 insertions(+), 202 deletions(-) create mode 100644 babybuddy/static_src/js/tags_editor.js diff --git a/babybuddy/static_src/js/tags_editor.js b/babybuddy/static_src/js/tags_editor.js new file mode 100644 index 00000000..e6fb0db4 --- /dev/null +++ b/babybuddy/static_src/js/tags_editor.js @@ -0,0 +1,206 @@ +(function() { + class TagsEditor { + constructor(tagEditorRoot) { + this.tagEditorRoot = tagEditorRoot; + } + }; + + window.addEventListener('load', () => { + for (const el of document.querySelectorAll('.babybuddy-tags-editor')) { + new TagsEditor(el); + } + + return; + const widget = document.getElementById('{{ widget.attrs.id }}'); + const csrfToken = document.querySelector('input[name="csrfmiddlewaretoken"]').value; + + const prototype = widget.querySelector('.prototype-tag'); + const currentTags = widget.querySelector('.current_tags'); + const newTags = widget.querySelector('.new-tags'); + + const inputElement = widget.querySelector('input[type="hidden"]'); + + const apiTagsUrl = widget.getAttribute('data-tags-url'); + const createTagInputs = widget.querySelector('.create-tag-inputs'); + const addTagInput = createTagInputs.querySelector('input[type="text"]'); + const addTagButton = createTagInputs.querySelector('.btn-add-new-tag'); + + 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 createTagClicked() { + const tagName = addTagInput.value.trim(); + const uriTagName = encodeURIComponent(tagName); + + function success() { + addTagInput.value = ""; + } + + function fail(msg) { + msg = msg || "Error creating tag"; + + addTagInput.select() + alert(msg); + } + + if (!tagName) { + fail('Not a valid tag name'); + return; + } + + const data = JSON.stringify({ + 'name': addTagInput.value + }); + + + 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); + success(); + } + + 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', createTagClicked); + addTagInput.addEventListener('keydown', (e) => { + const key = e.key.toLowerCase(); + if (key === "enter") { + e.preventDefault(); + createTagClicked(); + } + }); + + function hexParse(x) { + return parseInt(x, 16); + } + + function computeComplementaryColor(colorStr) { + let avgColor = 0.0; + avgColor += hexParse(colorStr.substring(1, 3)) * -0.5; + avgColor += hexParse(colorStr.substring(3, 5)) * 1.5; + avgColor += hexParse(colorStr.substring(5, 7)) * 1.0; + + if (avgColor > 200) { + return "#101010"; + } else { + return "#E0E0E0"; + } + } + + function updateTag(tag, name, color, actionSymbol) { + const actionTextNode = tag.querySelector('.add-remove-icon').childNodes[0]; + + name = name || tag.getAttribute("data-value"); + color = color || tag.getAttribute("data-color"); + actionSymbol = actionSymbol || actionTextNode.textContent; + + tag.childNodes[0].textContent = name; + tag.setAttribute("data-value", name); + tag.setAttribute("data-color", color); + + const textColor = computeComplementaryColor(color); + tag.setAttribute('style', `background-color: ${color}; color: ${textColor};`); + actionTextNode.textContent = actionSymbol; + } + + function createNewTag(name, color, actionSymbol) { + const tag = prototype.cloneNode(true); + tag.classList.remove("prototype-tag"); + tag.classList.add("tag"); + updateTag(tag, name, color, actionSymbol); + return tag; + } + + function insertTag(list, tag) { + list.appendChild(tag); + updateInputList(); + } + + function registerNewCallback(tag, newParent, newSymbol, onClicked) { + function callback(event) { + tag.parentNode.removeChild(tag); + updateTag( + tag, + null, + tag.getAttribute("data-color"), + newSymbol + ); + + insertTag(newParent, tag); + + tag.removeEventListener('click', callback); + onClicked(tag); + } + tag.addEventListener('click', callback); + } + + function updateInputList() { + const names = []; + for (const tag of currentTags.querySelectorAll(".tag")) { + const name = tag.getAttribute("data-value"); + names.push(`"${name}"`); + } + inputElement.value = names.join(","); + } + + function addTagCallback(tag) { + registerNewCallback(tag, currentTags, "-", removeTagCallback); + updateInputList(); + } + + function removeTagCallback(tag) { + registerNewCallback(tag, newTags, "+", addTagCallback); + updateInputList(); + } + + for (const tag of newTags.querySelectorAll(".tag")) { + updateTag(tag); + addTagCallback(tag); + } + for (const tag of currentTags.querySelectorAll(".tag")) { + updateTag(tag); + removeTagCallback(tag); + } + }); +})(); \ No newline at end of file diff --git a/babybuddy/templates/babybuddy/form.html b/babybuddy/templates/babybuddy/form.html index 8768a4a8..91ff6ce3 100644 --- a/babybuddy/templates/babybuddy/form.html +++ b/babybuddy/templates/babybuddy/form.html @@ -1,9 +1,12 @@ {% load i18n widget_tweaks %} +{# Load any form-javascript files #} +{{ form.media.js }}
{% csrf_token %} {% for field in form %} + {{ field.widget }}
{% include 'babybuddy/form_field.html' %}
diff --git a/core/templates/core/widget_tag_editor.html b/core/templates/core/widget_tag_editor.html index bc2299e0..4ec7c2fd 100644 --- a/core/templates/core/widget_tag_editor.html +++ b/core/templates/core/widget_tag_editor.html @@ -1,6 +1,8 @@ -
{% csrf_token %}
diff --git a/core/widgets.py b/core/widgets.py index a5436bdf..ffb2cb64 100644 --- a/core/widgets.py +++ b/core/widgets.py @@ -1,7 +1,11 @@ +from django.forms import Media from typing import Any, Dict, Optional from django.forms import Widget class TagsEditor(Widget): + class Media: + js = ("babybuddy/js/tags_editor.js",) + input_type = 'hidden' template_name = 'core/widget_tag_editor.html' @@ -14,21 +18,23 @@ class TagsEditor(Widget): value = [self.__unpack_tag(tag) for tag in value] return value + def build_attrs(self, base_attrs, extra_attrs=None): + attrs = super().build_attrs(base_attrs, extra_attrs) + attrs['class'] = attrs.get('class', '') + ' babybuddy-tags-editor' + return attrs + def get_context(self, name: str, value: Any, attrs) -> Dict[str, Any]: from . import models most_tags = models.BabyBuddyTag.objects.order_by( '-last_used' ).all()[:256] - quick_suggestion_tags = models.BabyBuddyTag.objects.order_by( - '-last_used' - ).all() result = super().get_context(name, value, attrs) tag_names = set(x['name'] for x in (result.get('widget', {}).get('value', None) or [])) quick_suggestion_tags = [ - t for t in quick_suggestion_tags + t for t in most_tags if t.name not in tag_names ][:5] @@ -41,4 +47,4 @@ class TagsEditor(Widget): self.__unpack_tag(t) for t in most_tags ] } - return result \ No newline at end of file + return result diff --git a/gulpfile.config.js b/gulpfile.config.js index aef9af87..b5584be6 100644 --- a/gulpfile.config.js +++ b/gulpfile.config.js @@ -66,6 +66,9 @@ module.exports = { 'api/static_src/js/*.js', 'core/static_src/js/*.js', 'dashboard/static_src/js/*.js' + ], + tags_editor: [ + 'babybuddy/static_src/js/tags_editor.js' ] }, stylesConfig: { diff --git a/gulpfile.js b/gulpfile.js index 2b7d9cc1..16e34b28 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -195,6 +195,12 @@ function scripts(cb) { concat('app.js'), gulp.dest(config.scriptsConfig.dest) ], cb); + + pump([ + gulp.src(config.scriptsConfig.tags_editor), + concat('tags_editor.js'), + gulp.dest(config.scriptsConfig.dest) + ], cb); } /**