| | |
| | | class CRUDManager { |
| | | constructor(config) { |
| | | this.queue = window.jvbQueue; |
| | | console.log(this.queue); |
| | | this.config = config; |
| | | this.content = config.content || false; |
| | | this.settings = window.jvbUserSettings; |
| | |
| | | return; |
| | | } |
| | | this.isTimeline = false; |
| | | this.currentItemID = null; |
| | | |
| | | this.initElements(); |
| | | this.updateBulkOptions(); |
| | | |
| | | // Initialize components |
| | | this.store = new window.jvbStore({ |
| | | name: this.content, |
| | | storeName: this.content, |
| | | endpoint: 'content', |
| | | headers: { |
| | | 'action_nonce': jvbSettings.dash, |
| | | }, |
| | | indexes: [ |
| | | { name: 'status', keyPath: 'post_status'}, |
| | | { name: 'modified', keyPath: 'modified'}, |
| | | ], |
| | | filters: { |
| | | content: this.content, |
| | | user: jvbSettings.currentUser, |
| | | page: 1, |
| | | status: 'all' |
| | | }, |
| | | TTL: 3600000, |
| | | cacheDOM: true |
| | | }); |
| | | this.store = window.jvbStore.register( |
| | | this.content, |
| | | { |
| | | keyPath: 'id', |
| | | endpoint: 'content', |
| | | headers: { |
| | | 'action_nonce': jvbSettings.dash, |
| | | }, |
| | | indexes: [ |
| | | {name: 'id', keyPath: 'id'}, |
| | | { name: 'status', keyPath: 'status'}, |
| | | { name: 'date', keyPath: 'date'}, |
| | | { name: 'modified', keyPath: 'modified'}, |
| | | { name: 'title', keyPath: 'title'} |
| | | ], |
| | | filters: { |
| | | content: this.content, |
| | | user: jvbSettings.currentUser, |
| | | page: 1, |
| | | status: 'all', |
| | | orderby: 'modified', //or title |
| | | order: 'desc' |
| | | }, |
| | | TTL: 30 * 60 * 1000, //30 minutes cache |
| | | }); |
| | | |
| | | this.status = 'all'; |
| | | this.filterTimeout = null; |
| | | |
| | | this.viewController = new window.jvbViews(this.ui.container, this.store); |
| | | this.formController = new window.jvbForm(); |
| | | this.tableForm = null; |
| | | this.tableChanges = new Map(); |
| | | |
| | | |
| | | this.formController = (this.isTimeline) ? new window.jvbForm({collectFormData: () => this.collectTimelineData.bind(this)}) : new window.jvbForm(); |
| | | this.viewController.subscribe((event, form) => { |
| | | if (event === 'table-view' && !this.tableForm) { |
| | | if (!this.tableForm) { |
| | | this.tableForm = this.formController.registerForm(form, { |
| | | autosave: false, |
| | | formStatus: false, |
| | | isTable: true, |
| | | }); |
| | | } |
| | | |
| | | } else if (event === 'not-table-view') { |
| | | if (this.tableForm) { |
| | | |
| | | } |
| | | } else if (event === 'order-changed') { |
| | | let data = this.store.get(form); |
| | | if (!data) { |
| | | return; |
| | | } |
| | | let changes = {}; |
| | | changes[form] = data; |
| | | this.savePosts(changes, `Updating progression order`); |
| | | } |
| | | }); |
| | | |
| | | this.formController.subscribe((event, data) => { |
| | | switch(event) { |
| | |
| | | } |
| | | }); |
| | | |
| | | if (window.jvbQueue) { |
| | | window.jvbQueue.subscribe((event, data) => { |
| | | if (event === 'operation-completed' && data.source === 'form') { |
| | | this.handleQueueSuccess(event, data); |
| | | } else if (event === 'operation-failed-permanent' && data.source === 'form') { |
| | | this.handleQueueFailure(event, data); |
| | | } |
| | | }); |
| | | } |
| | | this.queue.subscribe((event, data) => { |
| | | if (!Object.hasOwn(data, 'endpoint') || data.endpoint !== 'content') return; |
| | | console.log('Queue Subscription in CRUD.js: ', data); |
| | | if (event === 'operation-completed') { |
| | | this.handleQueueSuccess(event, data); |
| | | } else if (event === 'operation-failed-permanent') { |
| | | this.handleQueueFailure(event, data); |
| | | } |
| | | }); |
| | | |
| | | |
| | | // Track initialization |
| | | this.initialized = false; |
| | |
| | | this.init(); |
| | | } |
| | | handleFormChange(event, data) { |
| | | let title = data.fullData.post_title; |
| | | let changes = (Object.hasOwn(data, 'changes')) ? data.changes : data.fullData; |
| | | |
| | | let theChanges = {}; |
| | | if (this.isTimeline) { |
| | | data.changes.content = this.content; |
| | | let changes = {}; |
| | | let postID = data.config.id.replace('edit-', ''); |
| | | changes[postID] = data.changes; |
| | | this.savePosts(changes, `Saving ${data.fullData.post_title}'s Changes`); |
| | | theChanges[this.currentItemID] = changes; |
| | | this.savePosts(theChanges, title); |
| | | return; |
| | | } |
| | | data.changes.content = this.content; |
| | | let changes = {}; |
| | | let title = ''; |
| | | |
| | | let itemsToRemove = []; |
| | | switch (true) { |
| | | case data.config.element === this.ui.forms.edit: |
| | | let postID = data.config.id.replace('edit-', ''); |
| | | console.log(postID); |
| | | changes[postID] = data.changes; |
| | | title = `Saving ${data.fullData['post_title']} Changes`; |
| | | theChanges[this.currentItemID] = changes; |
| | | title = `Saving ${title} Changes`; |
| | | // Check if status change requires removal |
| | | if (data.changes.post_status && this.shouldRemoveItem(data.changes.post_status)) { |
| | | itemsToRemove.push(postID); |
| | | if (changes.post_status && this.shouldRemoveItem(changes.post_status)) { |
| | | itemsToRemove.push(this.currentItemID); |
| | | } |
| | | break; |
| | | case data.config.element === this.ui.forms.bulkEdit: |
| | | let selected = data.config.element.querySelectorAll('.selected input:checked'); |
| | | selected.forEach(sel => { |
| | | changes[sel.value] = data.changes; |
| | | theChanges[sel.value] = changes; |
| | | // Check if status change requires removal |
| | | if (data.changes.post_status && this.shouldRemoveItem(data.changes.post_status)) { |
| | | if (changes.post_status && this.shouldRemoveItem(changes.post_status)) { |
| | | itemsToRemove.push(sel.value); |
| | | } |
| | | }); |
| | |
| | | break; |
| | | case data.config.element === this.ui.forms.create: |
| | | if (event === 'form-submit') { |
| | | changes[data.config.data['form-id']] = data.fullData; |
| | | title = `Saving ${data.fullData['post_title']} Changes`; |
| | | theChanges[data.config.data['form-id']] = changes; |
| | | title = `Saving ${title} Changes`; |
| | | } |
| | | break; |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | if (window.isEmptyObject(changes)) { |
| | | if (window.isEmptyObject(theChanges)) { |
| | | return; |
| | | } |
| | | |
| | | this.savePosts(changes, title); |
| | | this.savePosts(theChanges, title); |
| | | } |
| | | |
| | | shouldRemoveItem(newStatus) { |
| | |
| | | if (window.isEmptyObject(changes)) { |
| | | return; |
| | | } |
| | | |
| | | //ensure content is in each post |
| | | for (let postId in changes) { |
| | | if (!changes[postId]['content']) { |
| | | changes[postId]['content'] = this.content; |
| | | } |
| | | } |
| | | let operation = { |
| | | endpoint: 'content', |
| | | headers: { |
| | |
| | | this.queue.addToQueue(operation); |
| | | |
| | | } |
| | | handleQueueSuccess(event, data) { |
| | | console.log('Handling queue success...'); |
| | | console.log('Event', event); |
| | | console.log('Data', data); |
| | | async handleQueueSuccess(event, data) { |
| | | this.store.clearCache(); |
| | | this.store.clearHttpHeaders(); |
| | | this.store.fetch(); |
| | | } |
| | | handleQueueFailure(event, data) { |
| | | console.log('Handling queue failure...'); |
| | | console.log('Event', event); |
| | | console.log('Data', data); |
| | | console.error('Operation failed permanently:', data); |
| | | // Optionally show error notification to user |
| | | this.a11y?.announce(`Operation failed: ${data.error_message || 'Unknown error'}`); |
| | | } |
| | | |
| | | initElements() { |
| | |
| | | uploader: 'details.uploader' |
| | | }; |
| | | this.ui = window.uiFromSelectors(this.elements); |
| | | this.isTimeline = Object.hasOwn(this.ui.forms.edit.dataset, 'timeline'); |
| | | this.isTimeline = !!document.querySelector('[data-timeline]'); |
| | | } |
| | | init() { |
| | | this.settings.addSetting(this.ui.uploader, 'open'); |
| | | this.ui.uploader.addEventListener('toggle', (e) =>{ |
| | | console.log(e); |
| | | console.log('Is Open: ', this.ui.uploader.open); |
| | | console.log(this.ui.uploader.open ? 'on' : 'off'); |
| | | this.settings.saveSetting('open', this.ui.uploader.open ? 'on' : 'off'); |
| | | }); |
| | | |
| | |
| | | this.modals[name].subscribe((event, data) => { |
| | | switch (event) { |
| | | case 'modal-close': |
| | | this.currentItemID = null; |
| | | this.formController.cleanupForm(this.modals[name].modal.querySelector('form').dataset.formId); |
| | | //double check we have finished saving |
| | | console.log('Data on modal close: ', data); |
| | | break; |
| | | case 'modal-open': |
| | | //probably not needed in this class |
| | |
| | | |
| | | this.setupFilters(); |
| | | |
| | | |
| | | this.queue.subscribe((event, data) => { |
| | | switch (event) { |
| | | case 'operation-status': |
| | | //update items? |
| | | break; |
| | | } |
| | | }); |
| | | |
| | | this.initialized = true; |
| | | } |
| | | |
| | |
| | | }); |
| | | } |
| | | handleChange(e) { |
| | | if (e.target.closest('[data-id]')) { |
| | | if (this.isTimeline) { |
| | | this.handleTimelineTableChange(e); |
| | | } else { |
| | | this.handleTableChange(e); |
| | | } |
| | | return; |
| | | } |
| | | if (e.target.classList.contains('bulk-action-select')) { |
| | | if (e.target.value.startsWith('tax-')) { |
| | | const taxonomy = e.target.value.replace('tax-', ''); |
| | |
| | | } |
| | | } |
| | | } |
| | | handleTableChange(e) { |
| | | const row = e.target.closest('tr[data-id]'); |
| | | if (!row) return; |
| | | |
| | | const input = e.target; |
| | | const postID = parseInt(row.dataset.id); |
| | | const fieldName = input.closest(['data-field'])?.dataset.field; |
| | | if (!fieldName) return; |
| | | |
| | | const item = this.store.get(postID); |
| | | if (!item) return; |
| | | |
| | | item.fields[fieldName] = this.getInputValue(input); |
| | | |
| | | this.store.save(item); |
| | | |
| | | let post = {}; |
| | | post[postID] = item.fields; |
| | | this.savePosts(post, `Saving changes to ${this.content}`); |
| | | } |
| | | handleTimelineTableChange(e) { |
| | | const tbody = e.target.closest('tbody[data-id]'); |
| | | if (!tbody) return; |
| | | |
| | | const input = e.target; |
| | | const fieldName = input.closest('[data-field]')?.dataset.field; |
| | | |
| | | if (!fieldName) return; |
| | | |
| | | const parentID = parseInt(tbody.dataset.id); |
| | | const timelinePoint = input.closest('tr.timeline-point'); |
| | | |
| | | const item = this.store.get(parentID); |
| | | if (!item) return; |
| | | |
| | | const value = this.getInputValue(input); |
| | | |
| | | // Check if this is a specific point, or a shared value |
| | | if (timelinePoint) { |
| | | const imgID = timelinePoint.dataset.imageId; |
| | | if (!item.fields.timeline) { |
| | | item.fields.timeline = {}; |
| | | } |
| | | if (!item.fields.timeline[imgID]) { |
| | | item.fields.timeline[imgID] = {}; |
| | | } |
| | | item.fields.timeline[imgID][fieldName] = value; |
| | | } else { |
| | | item.fields[fieldName] = value; |
| | | } |
| | | |
| | | //Update store directly |
| | | this.store.save(item); |
| | | |
| | | let changes = {}; |
| | | changes[parentID] = item.fields; |
| | | this.savePosts(changes, 'Updating progress post'); |
| | | } |
| | | getInputValue(input) { |
| | | if (input.type === 'checkbox') { |
| | | return input.checked ? (input.value || '1') : ''; |
| | | } |
| | | if (input.type === 'radio') { |
| | | return input.checked ? input.value : null; |
| | | } |
| | | return input.value; |
| | | } |
| | | |
| | | openTaxonomyModal(taxonomy) { |
| | | // Check if jvbSelector exists |
| | |
| | | ); |
| | | } |
| | | handleBulkTaxonomy(selectedIds, taxonomy) { |
| | | console.log(taxonomy, selectedIds); |
| | | // Callback when terms are selected |
| | | if (selectedIds.length > 0) { |
| | | selectedIds = selectedIds.join(','); |
| | | let changes = {}; |
| | | let selected = Array.from(this.viewController.selectedItems); |
| | | |
| | | console.log('selected',selected); |
| | | |
| | | selected.forEach(sel => { |
| | | changes[sel] = { |
| | | content: this.content |
| | |
| | | changes[sel][taxonomy] = selectedIds; |
| | | }); |
| | | |
| | | console.log('Taxonomy changes: ', changes); |
| | | |
| | | let title = `Adding ${selected.length} ${this.config.plural??'posts'} to ${selectedIds.length} ${jvbSettings.labels[taxonomy].plural}`; |
| | | this.viewController.clearSelection(); |
| | |
| | | if (!['publish', 'draft', 'trash', 'delete'].includes(status)){ |
| | | return; |
| | | } |
| | | console.log(`Setting status: ${status}`); |
| | | |
| | | let changes = {}; |
| | | for (let selected of this.viewController.selectedItems) { |
| | | changes[selected] = { |
| | |
| | | default: |
| | | title = window.uppercaseFirst(status)+'ing'; |
| | | } |
| | | console.log(this.status); |
| | | |
| | | if ((this.status === 'all' && !['publish', 'draft'].includes(status)) || status !== this.status) { |
| | | let delay = 0; |
| | | for (let selected of this.viewController.selectedItems) { |
| | |
| | | |
| | | window.removeChildren(container); |
| | | for (let selected of this.viewController.selectedItems) { |
| | | console.log(selected); |
| | | let item = this.store.get(selected); |
| | | console.log(item); |
| | | |
| | | const img = window.getTemplate('bulkItem'); |
| | | if (!img) return; |
| | | |
| | |
| | | ]; |
| | | |
| | | this.formController.registerForm(this.ui.forms.bulkEdit); |
| | | console.log('Bulk Edit form registered'); |
| | | } |
| | | |
| | | populateEditForm(itemID) { |
| | | console.log(itemID); |
| | | this.currentItemID = itemID; |
| | | |
| | | let item = this.store.get(parseInt(itemID)); |
| | | console.log(item); |
| | | if (item) { |
| | | this.ui.modals.edit.dataset.itemID = itemID; |
| | | this.ui.modals.edit.dataset.content = this.content; |
| | |
| | | `Editing ${item.fields.post_title}` |
| | | ]; |
| | | form.dataset.formId = `edit-${itemID}`; |
| | | console.log(form.dataset.formId); |
| | | new window.jvbPopulate(form, item.fields, item.images); |
| | | this.formController.registerForm(this.ui.forms.edit); |
| | | console.log('Edit form registered'); |
| | | } |
| | | } |
| | | |
| | |
| | | document.querySelectorAll('[data-filter]').forEach(filter => { |
| | | filter.removeEventListener('change', this.filterHandler); |
| | | }); |
| | | this.store.subscribers.clear(); |
| | | } |
| | | } |
| | | |