Adding/removing tags works again

This commit is contained in:
Paul Konstantin Gerke 2022-02-26 15:50:38 +01:00
parent 747b398bd5
commit 332a5999bc
3 changed files with 152 additions and 123 deletions

View File

@ -1,55 +1,125 @@
(function() {
class TagsEditor {
constructor(tagEditorRoot) {
this.tagEditorRoot = tagEditorRoot;
/**
* Parse a string as hexadecimal number
*/
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";
}
}
const CSRF_TOKEN = document.querySelector('input[name="csrfmiddlewaretoken"]').value;
function doReq(method, uri, data, success, fail) {
// TODO: prefer jQuery based requests for now
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", CSRF_TOKEN);
req.send(data);
}
class TaggingBase {
constructor(widget) {
this.prototype = widget.querySelector('.prototype-tag');
this.listeners = [];
}
addTagListUpdatedListener(c) {
this.listeners.push(c);
}
callTagListUpdatedListeners() {
for (const l of this.listeners) {
l();
}
}
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;
}
createNewTag(name, color, actionSymbol) {
const tag = this.prototype.cloneNode(true);
tag.classList.remove("prototype-tag");
tag.classList.add("tag");
updateTag(tag, name, color, actionSymbol);
return tag;
}
insertTag(list, tag) {
list.appendChild(tag);
}
};
window.addEventListener('load', () => {
for (const el of document.querySelectorAll('.babybuddy-tags-editor')) {
new TagsEditor(el);
}
class AddNewTagControl {
constructor(widget, taggingBase) {
this.taggingBase = taggingBase;
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');
this.apiTagsUrl = widget.getAttribute('data-tags-url');
this.createTagInputs = widget.querySelector('.create-tag-inputs');
this.addTagInput = this.createTagInputs.querySelector('input[type="text"]');
this.addTagButton = this.createTagInputs.querySelector('.btn-add-new-tag');
const inputElement = widget.querySelector('input[type="hidden"]');
this.addTagInput.value = "";
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);
this.addTagButton.addEventListener('click', () => this.createTagClicked);
this.addTagInput.addEventListener('keydown', (e) => {
const key = e.key.toLowerCase();
if (key === "enter") {
e.preventDefault();
this.createTagClicked();
}
});
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();
/**
* Callback called when the the "Add" button of the add-tag input is
* clicked or enter is pressed in the editor.
*/
onCreateTagClicked() {
// TODO: Make promise based
const tagName = this.addTagInput.value.trim();
const uriTagName = encodeURIComponent(tagName);
function success() {
@ -79,8 +149,8 @@
foundTag.parentNode.removeChild(foundTag);
}
const tag = createNewTag(name, color, "-");
insertTag(currentTags, tag);
const tag = this.taggingBase.createNewTag(name, color, "-");
this.taggingBase.insertTag(currentTags, tag);
removeTagCallback(tag);
success();
}
@ -102,105 +172,64 @@
}, fail
);
}
addTagButton.addEventListener('click', createTagClicked);
addTagInput.addEventListener('keydown', (e) => {
const key = e.key.toLowerCase();
if (key === "enter") {
e.preventDefault();
createTagClicked();
};
class TagsEditor {
constructor(tagEditorRoot) {
this.widget = tagEditorRoot;
this.taggingBase = new TaggingBase(this.widget);
this.addTagControl = new AddNewTagControl(
this.widget, this.taggingBase
);
this.currentTags = this.widget.querySelector('.current_tags');
this.newTags = this.widget.querySelector('.new-tags');
this.inputElement = this.widget.querySelector('input[type="hidden"]');
for (const tag of this.newTags.querySelectorAll(".tag")) {
this.configureAddTag(tag);
}
});
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";
for (const tag of this.currentTags.querySelectorAll(".tag")) {
this.configureRemoveTag(tag);
}
}
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) {
registerNewCallback(tag, newParent, onClicked) {
function callback(event) {
tag.parentNode.removeChild(tag);
updateTag(
tag,
null,
tag.getAttribute("data-color"),
newSymbol
);
insertTag(newParent, tag);
this.taggingBase.insertTag(newParent, tag);
tag.removeEventListener('click', callback);
onClicked(tag);
}
tag.addEventListener('click', callback);
tag.addEventListener('click', callback.bind(this));
}
function updateInputList() {
updateInputList() {
const names = [];
for (const tag of currentTags.querySelectorAll(".tag")) {
for (const tag of this.currentTags.querySelectorAll(".tag")) {
const name = tag.getAttribute("data-value");
names.push(`"${name}"`);
}
inputElement.value = names.join(",");
this.inputElement.value = names.join(",");
}
function addTagCallback(tag) {
registerNewCallback(tag, currentTags, "-", removeTagCallback);
updateInputList();
configureAddTag(tag) {
this.taggingBase.updateTag(tag, null, null, "+");
this.registerNewCallback(tag, this.currentTags, () => this.configureRemoveTag(tag));
this.updateInputList();
}
function removeTagCallback(tag) {
registerNewCallback(tag, newTags, "+", addTagCallback);
updateInputList();
configureRemoveTag(tag) {
this.taggingBase.updateTag(tag, null, null, "-");
this.registerNewCallback(tag, this.newTags, () => this.configureAddTag(tag));
this.updateInputList();
}
};
for (const tag of newTags.querySelectorAll(".tag")) {
updateTag(tag);
addTagCallback(tag);
}
for (const tag of currentTags.querySelectorAll(".tag")) {
updateTag(tag);
removeTagCallback(tag);
window.addEventListener('load', () => {
for (const el of document.querySelectorAll('.babybuddy-tags-editor')) {
new TagsEditor(el);
}
});
})();

View File

@ -1,5 +1,3 @@
{{ widget }}
<div data-tags-url="{% url 'api:api-root' %}tags/"
{% for k, v in widget.attrs.items %}
{{ k }}="{{ v }}"

View File

@ -20,7 +20,9 @@ class TagsEditor(Widget):
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'
class_string = attrs.get('class', '')
class_string = class_string.replace("form-control", "")
attrs['class'] = class_string + ' babybuddy-tags-editor'
return attrs
def get_context(self, name: str, value: Any, attrs) -> Dict[str, Any]: