From eea4e21d9bd7b89f7124fa1acbe3347d68db6d90 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 04 Jan 2026 19:35:27 +0000
Subject: [PATCH] =further taxonomyCreator.js debugging
---
assets/js/concise/View.js | 570 ++++++++++++++++++++++++++++++++++++++++++--------------
1 files changed, 429 insertions(+), 141 deletions(-)
diff --git a/assets/js/concise/View.js b/assets/js/concise/View.js
index b31b782..3f71831 100644
--- a/assets/js/concise/View.js
+++ b/assets/js/concise/View.js
@@ -3,21 +3,25 @@
*/
class ViewController {
constructor(container, store) {
- console.log(container);
this.a11y = window.jvbA11y;
this.error = window.jvbError;
this.container = container;
this.initElements();
+ this.settings = window.jvbUserSettings;
this.store = store;
+
+ this.isTimeline = !!document.querySelector('[data-timeline]');
+
this.items = {
list: new Map(),
grid: new Map(),
table: new Map(),
}
- this.currentView = 'grid';
+ this.currentView = this.container.dataset.view ?? 'grid';
this.selectedItems = new Set();
+ this.subscribers = new Set();
this.init();
}
@@ -26,8 +30,11 @@
this.selectors = {
grid: '.item-grid',
table: {
- table: 'table',
+ table: 'form.table',
+ form: 'table',
body: 'table body',
+ header: 'table thead',
+ footer: 'table tfoot',
selectedColumns: '.all-filters .multi-select',
columns: 'thead th'
},
@@ -40,19 +47,23 @@
}
this.ui = window.uiFromSelectors(this.selectors, this.container);
- console.log(this.ui);
}
init() {
// Subscribe to store updates
this.store.subscribe((event, data) => {
switch(event) {
- case 'data-fetched':
- case 'data-cached':
- this.handleDataUpdate(data);
+ case 'items-saved':
+ // this.handleDataUpdate(data);
break;
- case 'items-updated':
- this.handleItemsUpdate(data.items);
+ case 'data-loaded':
+ this.handleItemsUpdate();
+ break;
+ case 'item-saved':
+ // this.updateItem(data.item);
+ break;
+ case 'item-deleted':
+ // this.deleteItem(data.item);
break;
}
});
@@ -128,52 +139,48 @@
toggleColumns(column, show) {
let theColumn = this.ui.table.columns.filter(col => col.className === column);
- console.log(theColumn);
- console.log('Toggle Columns');
- console.log(column, show);
- console.log(this.ui.table.columns);
+ // console.log(theColumn);
+ // console.log('Toggle Columns');
+ // console.log(column, show);
+ // console.log(this.ui.table.columns);
}
setupViewSwitcher() {
document.querySelectorAll('[data-view]').forEach(btn => {
+ this.settings.addSetting(btn);
btn.addEventListener('click', () => {
this.currentView = btn.dataset.view;
this.render();
});
});
- }
- /**
- * Handle data updates from store
- */
- handleDataUpdate(data) {
- if (data.data && data.data.items) {
- this.render(data.data.items);
+ const checkedView = document.querySelector('[data-view]:checked');
+ if (checkedView) {
+ this.currentView = checkedView.dataset.view;
}
}
/**
* Handle items update
*/
- handleItemsUpdate(items) {
- this.render(items);
+ handleItemsUpdate() {
+ this.render();
}
- render(items = null) {
+ render() {
if (!this.store) {
console.error('No store connected to renderer');
return;
}
+ const items = this.store.getFiltered();
- // Get items from store if not provided
- if (!items) {
- const currentRequest = this.store.getCurrentRequest();
- if (currentRequest && currentRequest.data && currentRequest.data.items) {
- items = currentRequest.data.items;
- } else {
- return;
- }
+ // Handle empty state
+ if (items.length === 0) {
+ console.log('Nothing to show');
+ this.renderEmpty();
+ return;
}
+
switch(this.currentView) {
case 'grid':
this.renderGrid(items);
@@ -189,6 +196,17 @@
this.updateSelectionUI();
}
+ renderEmpty() {
+ this.toggleTable(false);
+ window.removeChildren(this.ui.grid);
+
+ const empty = window.getTemplate('emptyState');
+ if (empty) {
+ this.ui.grid.appendChild(empty);
+ this.a11y?.announce('No items found');
+ }
+ }
+
renderGrid(items) {
this.toggleGrid();
this.toggleTable(false);
@@ -200,19 +218,15 @@
const fragment = document.createDocumentFragment();
items.forEach(item => {
- let card;
- if (this.items.grid.has(item.id)) {
- card = this.items.grid.get(item.id);
- } else {
- card = this.store.renderOrRetrieve(item, 'grid', this.renderGridItem.bind(this));
- this.items.grid.set(item.id, card);
- }
-
+ let card = this.renderGridItem(item);
fragment.appendChild(card);
});
this.ui.grid.appendChild(fragment);
}
renderGridItem(item) {
+ if (this.items.grid.has(item.id)) {
+ return this.items.grid.get(item.id);
+ }
const card = window.getTemplate('gridView');
card.dataset.id = item.id;
@@ -237,8 +251,6 @@
checkbox.id,
checkbox.checked,
label.htmlFor,
- img.src,
- img.alt,
edit.dataset.id,
trash.dataset.id
] = [
@@ -246,28 +258,64 @@
`select-${item.id}`,
this.selectedItems.has(`${item.id}`),
`select-${item.id}`,
- item.images[item.fields.post_thumbnail]?.medium??'',
- item.images[item.fields.post_thumbnail]?.alt??'',
item.id,
item.id
];
+ // if (this.store.config.storeName === 'progress') {
+ // [
+ // img.src,
+ // img.alt,
+ // ] = [
+ // item.images[item.fields['timeline'][0].post_thumbnail]?.medium??'',
+ // item.images[item.fields['timeline'][0].post_thumbnail]?.alt??'',
+ // ];
+ // } else {
+ [
+ img.src,
+ img.alt,
+ ] = [
+ item.images[item.fields.post_thumbnail]?.medium??'',
+ item.images[item.fields.post_thumbnail]?.alt??'',
+ ];
+
+ // }
+
+ this.items.grid.set(item.id, card);
return card;
}
toggleTable(on) {
- this.ui.table.selectedColumns.hidden = !on;
+ if (this.ui.table.selectedColumns) {
+ this.ui.table.selectedColumns.hidden = !on;
+ }
+
if (on && !this.ui.table.table) {
let table = window.getTemplate('contentTable');
this.container.append(table);
this.ui.table.table = this.container.querySelector('form.table');
- this.ui.table.body = this.ui.table.table.querySelector('tbody');
+ this.ui.table.form = this.ui.table.table.querySelector('table');
+ this.ui.table.header = this.ui.table.form.querySelector('thead');
+ this.ui.table.footer = this.ui.table.form.querySelector('tfoot');
+ this.ui.table.body = this.ui.table.form.querySelector('tbody');
this.ui.table.columns = this.container.querySelectorAll(this.selectors.table.columns);
}
+
if (this.ui.table.table) {
this.ui.table.table.hidden = !on;
- window.removeChildren(this.ui.table.body);
+ if (on) {
+ this.notify('table-view', this.ui.table.table);
+ }else {
+ this.notify('not-table-view', this.ui.table.table);
+ }
+
+ if (this.ui.table.body){
+ window.removeChildren(this.ui.table.body);
+ }
}
- this.ui.table.selectedColumns.hidden = !on;
+
+ if (this.ui.table.selectedColumns) {
+ this.ui.table.selectedColumns.hidden = !on;
+ }
}
toggleGrid() {
@@ -279,22 +327,29 @@
this.toggleGrid();
items.forEach(item => {
- let row;
- if (this.items.table.has(item.id)) {
- row = this.items.table.get(item.id);
+
+ let row = (this.isTimeline) ? this.renderTimelineTableItem(item) : this.renderTableItem(item);
+
+ if (this.ui.table.body) {
+ this.ui.table.body.append(row);
} else {
- row = this.store.renderOrRetrieve(item, 'table', this.renderTableItem.bind(this));
- this.items.table.set(item.id, row);
+ if (!this.ui.table.footer) {
+ this.ui.table.footer = this.ui.table.table.querySelector('tfoot');
+ }
+ this.ui.table.form.insertBefore(row, this.ui.table.footer);
}
- this.ui.table.body.append(row);
});
window.jvbSelector.scanExistingFields();
+
}
renderTableItem(item) {
- let empty = ['',0];
+ if (this.items.table.has(item.id)) {
+ return this.items.table.get(item.id);
+ }
+
const row = window.getTemplate('tableView');
row.dataset.id = item.id;
@@ -303,96 +358,171 @@
row.querySelector('.select-item').value,
row.querySelector('.select-item').checked,
row.querySelector('.select-item + label').htmlFor,
- row.querySelector(`input[name="post_status"][value="${item.status}"]`).checked
] = [
item.id,
item.id,
this.selectedItems.has(`${item.id}`),
item.id,
- item.status
+ ];
+ let status = row.querySelector(`input[name="post_status"][value="${item.status}"]`);
+ if (status) {
+ status.checked = true;
+ }
+
+ if (Object.hasOwn(this.ui.table.table.dataset, 'edit')) {
+ new window.jvbPopulate(row, item);
+ } else {
+ for (let [key, value] of Object.entries(item)) {
+ let col = row.querySelector(`[data-field="${key}"]`);
+ if (col) {
+ let p = col.querySelector('p');
+ if (col.dataset.fieldType === 'date') {
+ value = window.formatTimeAgo(value);
+ }
+ p.textContent = value;
+ }
+ }
+ }
+
+
+ // Clean up after population
+ this.cleanupTableRow(row);
+
+ this.items.table.set(item.id, row);
+ return row;
+ }
+
+ renderTimelineTableItem(item) {
+ if (this.items.table.has(item.id)) {
+ return this.items.table.get(item.id);
+ }
+
+ const row = window.getTemplate('tableView');
+ row.dataset.id = item.id;
+
+ [
+ row.querySelector('.select-item').id,
+ row.querySelector('.select-item').value,
+ row.querySelector('.select-item').checked,
+ row.querySelector('.select-item + label').htmlFor,
+ ] = [
+ item.id,
+ item.id,
+ this.selectedItems.has(`${item.id}`),
+ item.id,
];
+ let timelinePoint = row.querySelector('.timeline-point');
+ let tbody = row;
- row.querySelectorAll('td[data-field]').forEach(field => {
- let value = item.fields[field.dataset.field];
- // field.querySelectorAll('label').forEach(label => {
- // label.hidden = true;
- // });
+ // Populate shared fields
+ let sharedRow = row.querySelector('tr.shared');
+ new window.jvbPopulate(sharedRow, item);
+ this.prefixTimelineFieldNames(sharedRow, item.id);
+ this.cleanupTableRow(sharedRow);
- let label = field.querySelector('label');
- let isEmpty = (empty.includes(value));
- let temp;
- switch (field.dataset.fieldType) {
- case 'text':
- case 'number':
- case 'url':
- case 'tel':
- case 'email':
- if (!isEmpty) {
- field.querySelector('input').value = value;
- }
- label.remove();
- break;
- case 'textarea':
- if (!isEmpty) {
- field.querySelector('textarea').value = value;
- }
- label.remove();
- break;
- case 'taxonomy':
- label.remove();
- if (!isEmpty) {
- temp = field.querySelector('input[type=hidden]');
- temp.value = value;
- }
- break;
- case 'image':
- if (!isEmpty) {
- let image = window.getTemplate('uploadItem');
- let img = image.querySelector('img');
- [
- img.src,
- img.alt
- ] = [
- item.images[value].medium??'',
- item.images[value].alt??'',
- ];
- field.querySelector('.item-grid').append(image);
- field.querySelector('input[type=hidden]').value = value;
- }
- field.querySelectorAll('.progress,label,.upload-select,.status,details').forEach(item => {
- item.remove();
- });
- break;
- case 'true_false':
- if (!isEmpty) {
- field.querySelector('input').checked = parseInt(value) === 1;
- }
- field.querySelector('.toggle-label').hidden = true;
- break;
- case 'select':
- label.remove();
- case 'radio':
- case 'checkbox':
- field.querySelector('.label')?.remove();
- if (!isEmpty) {
- value = value.split(',');
- value.forEach(v => {
- temp = field.querySelector(`[value="${v}"]`);
- if (temp) {
- temp.checked = true;
- }
- });
- }
- break;
- default:
- if (!isEmpty) {
- console.log(value);
- }
- break;
+ // Handle timeline points
+ if (item.fields.timeline && typeof item.fields.timeline === 'object') {
+ const timelineArray = Object.entries(item.fields.timeline);
+
+ timelineArray.forEach(([imgId, timeline], index) => {
+ let point = timelinePoint.cloneNode(true);
+ point.dataset.index = index;
+ point.dataset.imageId = imgId;
+
+ // NEW: Create item-like structure for timeline point
+ const timelineItem = {
+ fields: timeline,
+ images: item.images,
+ taxonomies: {} // Timeline points don't have taxonomies
+ };
+
+ new window.jvbPopulate(point, timelineItem);
+
+ this.cleanupTableRow(point);
+ let imgdata = item.images[timeline.post_thumbnail];
+ if (imgdata) {
+ point.querySelector('.field.upload').title = imgdata['image-title'];
+ }
+
+ this.prefixTimelineFieldNames(point, timeline.id);
+
+ tbody.insertBefore(point, timelinePoint);
+ });
+ }
+
+ timelinePoint.remove();
+
+ this.items.table.set(item.id, row);
+ return row;
+ }
+
+ /**
+ * Timeline uses bracket notation: [postId]fieldName
+ * This matches the collectTimeline() method in FormController
+ */
+ prefixTimelineFieldNames(row, postId) {
+ row.querySelectorAll('input, textarea, select').forEach(field => {
+ const currentName = field.name;
+
+ if (!currentName || currentName.startsWith('[') ||
+ currentName === 'form-id' || currentName.startsWith('_')) {
+ return;
+ }
+
+ // Use bracket notation for timeline
+
+ let label = field.nextElementSibling;
+ field.name = `[${postId}]${currentName}`;
+ if (label && label.tagName === 'LABEL') {
+ field.id = `[${postId}]${field.id}`;
+ label.htmlFor = field.id;
}
});
- return row;
+ }
+
+ cleanupTableRow(row) {
+ row.querySelectorAll('td[data-field]').forEach(field => {
+ // Remove labels (they're in the header)
+ field.querySelectorAll('label:not(.select-item-label,.radio-option,[for*="select-item"])').forEach(label => {
+ if (!label.closest('.radio-options')) {
+ label.remove();
+ }
+ });
+
+ // Special handling for image/upload fields
+ // if (field.dataset.fieldType === 'image' || field.dataset.fieldType === 'upload') {
+ // const itemGrid = field.querySelector('.item-grid');
+ // const uploadContainer = field.querySelector('.file-upload-container');
+ //
+ // // If grid has items (populated), just remove upload UI
+ // if (itemGrid && itemGrid.children.length > 0) {
+ // // Remove upload controls but keep the populated items
+ // field.querySelectorAll('.progress, .upload-select, .status, details:not(.item-grid details)').forEach(el => {
+ // el.remove();
+ // });
+ // // Keep upload container hidden if it was hidden
+ // if (uploadContainer && uploadContainer.hidden) {
+ // uploadContainer.hidden = true;
+ // }
+ // } else {
+ // // No items, remove all upload UI
+ // field.querySelectorAll('.file-upload-wrapper, .progress, .upload-select, .status, details').forEach(el => {
+ // el.remove();
+ // });
+ // }
+ // }
+
+ // Remove toggle labels for true_false fields
+ if (field.dataset.fieldType === 'true_false') {
+ field.querySelector('.toggle-label')?.remove();
+ }
+
+ // Remove field labels for checkbox/radio groups
+ if (['checkbox', 'radio', 'select'].includes(field.dataset.fieldType)) {
+ field.querySelector('.label')?.remove();
+ }
+ });
}
renderList(items) {
@@ -403,19 +533,15 @@
this.ui.grid.classList.add('list-view');
items.forEach(item => {
- let row;
- if (this.items.list.has(item.id)) {
- row = this.items.list.get(item.id);
- } else {
- row = this.store.renderOrRetrieve(item, 'list', this.renderListItem.bind(this));
- this.items.list.set(item.id, row);
- }
-
+ let row = this.renderListItem(item);
this.ui.grid.appendChild(row);
});
}
renderListItem(item) {
+ if (this.items.list.has(item.id)) {
+ return this.items.list.get(item.id);
+ }
const row = window.getTemplate('listView');
row.dataset.id = item.id;
@@ -464,9 +590,158 @@
item.images[item.fields.post_thumbnail]?.alt??'',
]
}
+ this.items.list.set(item.id, row);
return row;
}
+ setupTimelineDragHandler() {
+ if (!this.isTimeline || this.currentView !== 'table') return;
+
+ // Clean up existing handler if any
+ if (this.timelineDragHandler) {
+ this.timelineDragHandler.destroy();
+ }
+
+ this.timelineDragHandler = new window.jvbDragHandler({
+ draggableSelector: '.timeline-point',
+ dropTargetSelector: '.timeline-point',
+ handleSelector: '.drag-handle',
+
+ getItemId: (element) => {
+ return element.dataset.imageId;
+ },
+
+ getSelectedItems: () => {
+ return [];
+ },
+
+ validateDrop: (itemIds, dropTarget) => {
+ const draggedRow = document.querySelector(`.timeline-point[data-image-id="${itemIds[0]}"]`);
+ if (!draggedRow) return false;
+
+ const draggedTbody = draggedRow.closest('tbody');
+ const targetTbody = dropTarget.closest('tbody');
+
+ return draggedTbody === targetTbody;
+ },
+
+ onDragStart: (itemIds, element) => {
+ element.classList.add('is-dragging');
+ },
+
+ onDrop: (itemIds, dropTarget) => {
+ const draggedRow = document.querySelector(`.timeline-point[data-image-id="${itemIds[0]}"]`);
+ if (!draggedRow) return;
+
+ // Remove all drop indicators
+ document.querySelectorAll('.drop-above, .drop-below').forEach(el => {
+ el.classList.remove('drop-above', 'drop-below');
+ });
+
+ const tbody = draggedRow.closest('tbody');
+ const dropPosition = dropTarget.dataset.dropPosition;
+
+ // Insert based on drop position
+ if (dropPosition === 'above') {
+ tbody.insertBefore(draggedRow, dropTarget);
+ } else {
+ tbody.insertBefore(draggedRow, dropTarget.nextSibling);
+ }
+
+ draggedRow.classList.remove('is-dragging');
+ this.updateTimelineOrder(tbody);
+ },
+
+ onDragEnd: (itemIds, success) => {
+ // Clean up all drag classes
+ document.querySelectorAll('.is-dragging, .drop-above, .drop-below').forEach(el => {
+ el.classList.remove('is-dragging', 'drop-above', 'drop-below');
+ });
+ },
+
+ previewElement: '.drag-handle',
+ previewOptions: {
+ offset: { x: -20, y: -20 },
+ showCount: false
+ }
+ });
+
+ // Add custom hover logic for better drop positioning
+ this.addTimelineDragHoverLogic();
+ }
+
+ addTimelineDragHoverLogic() {
+ let currentHover = null;
+
+ document.addEventListener('pointermove', (e) => {
+ if (!document.querySelector('.timeline-point.is-dragging')) return;
+
+ const target = e.target.closest('.timeline-point:not(.is-dragging)');
+ if (!target) {
+ if (currentHover) {
+ currentHover.classList.remove('drop-above', 'drop-below');
+ delete currentHover.dataset.dropPosition;
+ currentHover = null;
+ }
+ return;
+ }
+
+ // Determine if we're in the top or bottom half
+ const rect = target.getBoundingClientRect();
+ const midpoint = rect.top + (rect.height / 2);
+ const isTopHalf = e.clientY < midpoint;
+
+ // Update classes
+ if (currentHover && currentHover !== target) {
+ currentHover.classList.remove('drop-above', 'drop-below');
+ delete currentHover.dataset.dropPosition;
+ }
+
+ target.classList.remove('drop-above', 'drop-below');
+ target.classList.add(isTopHalf ? 'drop-above' : 'drop-below');
+ target.dataset.dropPosition = isTopHalf ? 'above' : 'below';
+
+ currentHover = target;
+ });
+ }
+ updateTimelineOrder(tbody) {
+ const postId = parseInt(tbody.dataset.id);
+ const rows = Array.from(tbody.querySelectorAll('.timeline-point'));
+
+ const item = this.store.get(postId);
+ if (!item) return;
+
+ let timeline = {};
+ // Update menu_order for each timeline point
+ rows.forEach((row, index) => {
+ const imgID = row.dataset.imageId;
+ timeline[imgID] = item.fields.timeline[imgID];
+ });
+ item.fields.timeline = timeline;
+
+ // Update store (triggers autosave)
+ this.store.save(item);
+ this.notify('order-changed', postId);
+ this.a11y?.announce(`Timeline order updated. ${rows.length} steps reordered.`);
+ }
+
+ extractRowFields(row) {
+ const fields = {};
+ row.querySelectorAll('[data-field]').forEach(cell => {
+ const fieldName = cell.dataset.field;
+ const input = cell.querySelector('input, textarea, select');
+
+ if (input) {
+ if (input.type === 'checkbox') {
+ fields[fieldName] = input.checked;
+ } else {
+ fields[fieldName] = input.value;
+ }
+ }
+ });
+ return fields;
+ }
+
toggleSelection(id) {
if (this.selectedItems.has(id)) {
this.selectedItems.delete(id);
@@ -511,6 +786,19 @@
this.ui.bulk.count.textContent = count === 0 ? '' : `${count} ${item} selected`;
}
}
+
+
+ /**
+ * Event system
+ */
+ subscribe(callback) {
+ this.subscribers.add(callback);
+ return () => this.subscribers.delete(callback);
+ }
+
+ notify(event, data) {
+ this.subscribers.forEach(cb => cb(event, data));
+ }
}
window.jvbViews = ViewController;
--
Gitblit v1.10.0