Finished moving tag-manager from a vue to a component

Now tags load with the page, not via AJAX.
This commit is contained in:
Dan Brown
2020-06-29 22:11:03 +01:00
parent 4e107b9160
commit 573c4e26d5
21 changed files with 153 additions and 335 deletions

View File

@ -1,4 +1,5 @@
import {onChildEvent} from "../services/dom";
import {uniqueId} from "../services/util";
/**
* AddRemoveRows
@ -11,21 +12,43 @@ class AddRemoveRows {
this.modelRow = this.$refs.model;
this.addButton = this.$refs.add;
this.removeSelector = this.$opts.removeSelector;
this.rowSelector = this.$opts.rowSelector;
this.setupListeners();
}
setupListeners() {
this.addButton.addEventListener('click', e => {
const clone = this.modelRow.cloneNode(true);
clone.classList.remove('hidden');
this.modelRow.parentNode.insertBefore(clone, this.modelRow);
});
this.addButton.addEventListener('click', this.add.bind(this));
onChildEvent(this.$el, this.removeSelector, 'click', (e) => {
const row = e.target.closest('tr');
const row = e.target.closest(this.rowSelector);
row.remove();
});
}
// For external use
add() {
const clone = this.modelRow.cloneNode(true);
clone.classList.remove('hidden');
this.setClonedInputNames(clone);
this.modelRow.parentNode.insertBefore(clone, this.modelRow);
window.components.init(clone);
}
/**
* Update the HTML names of a clone to be unique if required.
* Names can use placeholder values. For exmaple, a model row
* may have name="tags[randrowid][name]".
* These are the available placeholder values:
* - randrowid - An random string ID, applied the same across the row.
* @param {HTMLElement} clone
*/
setClonedInputNames(clone) {
const rowId = uniqueId();
const randRowIdElems = clone.querySelectorAll(`[name*="randrowid"]`);
for (const elem of randRowIdElems) {
elem.name = elem.name.split('randrowid').join(rowId);
}
}
}
export default AddRemoveRows;

View File

@ -16,6 +16,7 @@ class AutoSuggest {
this.input = this.$refs.input;
this.list = this.$refs.list;
this.lastPopulated = 0;
this.setupListeners();
}
@ -44,7 +45,10 @@ class AutoSuggest {
selectSuggestion(value) {
this.input.value = value;
this.lastPopulated = Date.now();
this.input.focus();
this.input.dispatchEvent(new Event('input', {bubbles: true}));
this.input.dispatchEvent(new Event('change', {bubbles: true}));
this.hideSuggestions();
}
@ -79,8 +83,12 @@ class AutoSuggest {
}
async requestSuggestions() {
if (Date.now() - this.lastPopulated < 50) {
return;
}
const nameFilter = this.getNameFilterIfNeeded();
const search = this.input.value.slice(0, 3);
const search = this.input.value.slice(0, 3).toLowerCase();
const suggestions = await this.loadSuggestions(search, nameFilter);
let toShow = suggestions.slice(0, 6);
if (search.length > 0) {

View File

@ -37,7 +37,7 @@ class Collapsible {
}
openIfContainsError() {
const error = this.content.querySelector('.text-neg');
const error = this.content.querySelector('.text-neg.text-small');
if (error) {
this.open();
}

View File

@ -70,13 +70,20 @@ function initComponent(name, element) {
function parseRefs(name, element) {
const refs = {};
const manyRefs = {};
const prefix = `${name}@`
const refElems = element.querySelectorAll(`[refs*="${prefix}"]`);
const selector = `[refs*="${prefix}"]`;
const refElems = [...element.querySelectorAll(selector)];
if (element.matches(selector)) {
refElems.push(element);
}
for (const el of refElems) {
const refNames = el.getAttribute('refs')
.split(' ')
.filter(str => str.startsWith(prefix))
.map(str => str.replace(prefix, ''));
.map(str => str.replace(prefix, ''))
.map(kebabToCamel);
for (const ref of refNames) {
refs[ref] = el;
if (typeof manyRefs[ref] === 'undefined') {

View File

@ -0,0 +1,19 @@
import Sortable from "sortablejs";
/**
* SortableList
* @extends {Component}
*/
class SortableList {
setup() {
this.container = this.$el;
this.handleSelector = this.$opts.handleSelector;
new Sortable(this.container, {
handle: this.handleSelector,
animation: 150,
});
}
}
export default SortableList;

View File

@ -0,0 +1,32 @@
/**
* TagManager
* @extends {Component}
*/
class TagManager {
setup() {
this.addRemoveComponentEl = this.$refs.addRemove;
this.container = this.$el;
this.rowSelector = this.$opts.rowSelector;
this.setupListeners();
}
setupListeners() {
this.container.addEventListener('change', event => {
const addRemoveComponent = this.addRemoveComponentEl.components['add-remove-rows'];
if (!this.hasEmptyRows()) {
addRemoveComponent.add();
}
});
}
hasEmptyRows() {
const rows = this.container.querySelectorAll(this.rowSelector);
const firstEmpty = [...rows].find(row => {
return [...row.querySelectorAll('input')].filter(input => input.value).length === 0;
});
return firstEmpty !== undefined;
}
}
export default TagManager;