/**
|
* Manages view states: grid, list, or table
|
*/
|
class ViewController {
|
constructor(container, store) {
|
this.a11y = window.jvbA11y;
|
this.error = window.jvbError;
|
|
this.container = container;
|
this.initElements();
|
|
this.store = store;
|
this.items = {
|
list: new Map(),
|
grid: new Map(),
|
table: new Map(),
|
}
|
this.currentView = 'grid';
|
this.selectedItems = new Set();
|
|
this.init();
|
}
|
|
initElements() {
|
this.selectors = {
|
grid: '.item-grid',
|
table: {
|
table: 'table',
|
body: 'table body',
|
selectedColumns: '.all-filters .multi-select',
|
columns: 'thead th'
|
},
|
bulk: {
|
count: '.bulk-controls .selected-count',
|
control: '.bulk-controls .bulk-actions',
|
select: '.bulk-controls select',
|
selectAll: '.select-all'
|
}
|
}
|
|
this.ui = window.uiFromSelectors(this.selectors, this.container);
|
}
|
|
init() {
|
// Subscribe to store updates
|
this.store.subscribe((event, data) => {
|
switch(event) {
|
case 'data-loaded':
|
case 'items-saved':
|
this.handleDataUpdate(data);
|
break;
|
case 'items-updated':
|
this.handleItemsUpdate(data.items);
|
break;
|
case 'item-saved':
|
// this.updateItem(data.item);
|
break;
|
case 'item-deleted':
|
// this.deleteItem(data.item);
|
break;
|
}
|
});
|
|
// Set up view switcher
|
this.setupViewSwitcher();
|
this.changeHandler = this.handleChange.bind(this);
|
this.clickHandler = this.handleClick.bind(this);
|
this.lastSelected = null;
|
|
document.addEventListener('change', this.changeHandler);
|
document.addEventListener('click', this.clickHandler);
|
}
|
|
handleClick(e) {
|
let select = e.target.closest('.select-item-label');
|
if (select) {
|
if (e.shiftKey) {
|
e.preventDefault();
|
this.handleRangeSelection(e.target);
|
} else {
|
this.lastSelected = e.target.closest('.item');
|
}
|
}
|
}
|
|
handleRangeSelection(target) {
|
if (!this.lastSelected) {
|
this.lastSelected = target.closest('.item');
|
return;
|
}
|
|
const current = target.closest('.item');
|
const all = Array.from(this.container.querySelectorAll('.item'));
|
|
const lastIndex = all.indexOf(this.lastSelected);
|
const currentIndex = all.indexOf(current);
|
|
if (lastIndex === -1 || currentIndex === -1) {
|
this.lastSelected = current;
|
return;
|
}
|
|
const start = Math.min(lastIndex, currentIndex);
|
const end = Math.max(lastIndex, currentIndex);
|
|
let newSelections = 0;
|
for (let i = start; i <= end; i++) {
|
let item = all[i];
|
this.selectedItems.add(item.dataset.id);
|
let checkbox = item.querySelector('.select-item');
|
if (checkbox && !checkbox.checked) {
|
checkbox.checked = true;
|
newSelections++;
|
}
|
}
|
|
this.updateSelectionUI();
|
window.jvbA11y.announce(`Selected ${newSelections} items in range.`);
|
|
|
}
|
|
handleChange(e) {
|
if (e.target.closest('.select-all')) {
|
this.selectAll(e.target.checked);
|
} else if (e.target.closest('.select-item')) {
|
this.toggleSelection(e.target.closest('.item').dataset.id);
|
} else if (e.target.closest('details.multi-select')) {
|
this.toggleColumns(e.target.id, e.target.checked);
|
}
|
}
|
|
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);
|
}
|
|
setupViewSwitcher() {
|
document.querySelectorAll('[data-view]').forEach(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);
|
}
|
}
|
|
/**
|
* Handle items update
|
*/
|
handleItemsUpdate(items) {
|
this.render(items);
|
}
|
|
render(items = null) {
|
if (!this.store) {
|
console.error('No store connected to renderer');
|
return;
|
}
|
|
// 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;
|
}
|
}
|
switch(this.currentView) {
|
case 'grid':
|
this.renderGrid(items);
|
break;
|
case 'table':
|
this.renderTable(items);
|
break;
|
case 'list':
|
this.renderList(items);
|
break;
|
}
|
|
this.updateSelectionUI();
|
}
|
|
renderGrid(items) {
|
this.toggleGrid();
|
this.toggleTable(false);
|
|
this.ui.grid.classList.remove('list-view');
|
this.ui.grid.classList.add('grid-view');
|
|
|
const fragment = document.createDocumentFragment();
|
|
items.forEach(item => {
|
let card;
|
if (this.store.renderOrRetrieve) {
|
card = this.store.renderOrRetrieve(item, 'grid', this.renderGridItem.bind(this));
|
} else {
|
// Fallback to local cache
|
if (this.items.grid.has(item.id)) {
|
card = this.items.grid.get(item.id);
|
} else {
|
card = this.renderGridItem(item);
|
this.items.grid.set(item.id, card);
|
}
|
}
|
|
fragment.appendChild(card);
|
});
|
this.ui.grid.appendChild(fragment);
|
}
|
renderGridItem(item) {
|
const card = window.getTemplate('gridView');
|
card.dataset.id = item.id;
|
|
if (item._pending) card.classList.add('pending');
|
|
let [
|
checkbox,
|
label,
|
img,
|
edit,
|
trash
|
] = [
|
card.querySelector('input'),
|
card.querySelector('label'),
|
card.querySelector('img'),
|
card.querySelector('[data-action="edit"]'),
|
card.querySelector('[data-action="trash"]'),
|
];
|
|
[
|
checkbox.value,
|
checkbox.id,
|
checkbox.checked,
|
label.htmlFor,
|
img.src,
|
img.alt,
|
edit.dataset.id,
|
trash.dataset.id
|
] = [
|
item.id,
|
`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
|
];
|
return card;
|
}
|
|
toggleTable(on) {
|
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.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);
|
}
|
this.ui.table.selectedColumns.hidden = !on;
|
}
|
|
toggleGrid() {
|
window.removeChildren(this.ui.grid);
|
}
|
|
renderTable(items) {
|
this.toggleTable(true);
|
this.toggleGrid();
|
|
items.forEach(item => {
|
let row;
|
if (this.items.table.has(item.id)) {
|
row = this.items.table.get(item.id);
|
} else {
|
row = this.store.renderOrRetrieve(item, 'table', this.renderTableItem.bind(this));
|
this.items.table.set(item.id, row);
|
}
|
|
this.ui.table.body.append(row);
|
});
|
|
window.jvbSelector.scanExistingFields();
|
}
|
|
renderTableItem(item) {
|
let empty = ['',0];
|
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,
|
row.querySelector(`input[name="post_status"][value="${item.status}"]`).checked
|
] = [
|
item.id,
|
item.id,
|
this.selectedItems.has(`${item.id}`),
|
item.id,
|
item.status
|
];
|
|
|
row.querySelectorAll('td[data-field]').forEach(field => {
|
let value = item.fields[field.dataset.field];
|
// field.querySelectorAll('label').forEach(label => {
|
// label.hidden = true;
|
// });
|
|
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')?.remove();
|
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;
|
}
|
});
|
return row;
|
}
|
|
renderList(items) {
|
this.toggleGrid();
|
this.toggleTable(false);
|
|
this.ui.grid.classList.remove('grid-view');
|
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);
|
}
|
|
this.ui.grid.appendChild(row);
|
});
|
}
|
|
renderListItem(item) {
|
const row = window.getTemplate('listView');
|
row.dataset.id = item.id;
|
|
if (item._pending) row.classList.add('pending');
|
let select = row.querySelector('.select-item');
|
let label = row.querySelector('.select-item + label');
|
[
|
select.id,
|
select.value,
|
select.checked,
|
label.htmlFor
|
] = [
|
item.id,
|
item.id,
|
this.selectedItems.has(`${item.id}`),
|
item.id,
|
];
|
|
row.querySelectorAll('[data-attr]').forEach(attr => {
|
if (item[attr.dataset['attr']] !== ''){
|
attr.textContent = item[attr.dataset['attr']];
|
} else {
|
attr.remove();
|
}
|
});
|
row.querySelectorAll('[data-field]').forEach(field => {
|
let value = item.fields[field.dataset['field']];
|
if (value !== '') {
|
if (field.tagName === 'DIV') {
|
field.innerHTML = value;
|
} else {
|
field.textContent = value;
|
}
|
} else {
|
field.remove();
|
}
|
});
|
|
let img = row.querySelector('img');
|
if (img) {
|
[
|
img.src,
|
img.alt
|
] = [
|
item.images[item.fields.post_thumbnail]?.medium??'',
|
item.images[item.fields.post_thumbnail]?.alt??'',
|
]
|
}
|
return row;
|
}
|
|
toggleSelection(id) {
|
if (this.selectedItems.has(id)) {
|
this.selectedItems.delete(id);
|
} else {
|
this.selectedItems.add(id);
|
}
|
this.updateSelectionUI();
|
}
|
|
selectAll(check) {
|
const items = this.container.querySelectorAll('.item');
|
if (!check) {
|
this.selectedItems.clear();
|
this.ui.bulk.selectAll.checked = false;
|
this.ui.bulk.select.value = '';
|
}
|
items.forEach(item => {
|
if (check) {
|
this.selectedItems.add(item.dataset.id)
|
}
|
|
item.querySelector('.select-item').checked = check;
|
});
|
|
this.updateSelectionUI();
|
}
|
|
clearSelection() {
|
this.selectAll(false);
|
this.ui.bulk.select.value = '';
|
}
|
|
updateSelectionUI() {
|
const count = this.selectedItems.size;
|
|
if (this.ui.bulk.control) {
|
this.ui.bulk.control.hidden = count === 0;
|
}
|
if (this.ui.bulk.count) {
|
let item = count === 1 ? 'item' : 'items';
|
this.ui.bulk.count.hidden = count === 0;
|
this.ui.bulk.count.textContent = count === 0 ? '' : `${count} ${item} selected`;
|
}
|
}
|
}
|
|
window.jvbViews = ViewController;
|