import $ from 'jquery'; import Sortable from 'sortablejs'; import '../../utils/jquery-utils'; export default class CollectionsField { constructor() { this.lists = $(); const body = $('body'); $('[data-type="collection"]').each((index, list) => this.addList(list)); body.on('mutation._grav', this._onAddedNodes.bind(this)); body.on('click', (event) => { const target = $(event.target); if (!(target.is('[data-action="confirm"], [data-action="delete"]') || target.closest('[data-action="confirm"], [data-action="delete"]').length)) { CollectionsField.closeConfirmations(); } }); } addList(list) { list = $(list); this.lists = this.lists.add(list); list.on('click', '> .collection-actions [data-action="add"]', (event) => this.addItem(event)); list.on('click', '> ul > li > .item-actions [data-action="confirm"]', (event) => this.confirmRemove(event)); list.on('click', '> ul > li > .item-actions [data-action="delete"]', (event) => this.removeItem(event)); list.on('click', '> ul > li > .item-actions [data-action="collapse"]', (event) => this.collapseItem(event)); list.on('click', '> ul > li > .item-actions [data-action="expand"]', (event) => this.expandItem(event)); list.on('click', '> .collection-actions [data-action-sort="date"]', (event) => this.sortItems(event)); list.on('click', '> .collection-actions [data-action="collapse_all"]', (event) => this.collapseItems(event)); list.on('click', '> .collection-actions [data-action="expand_all"]', (event) => this.expandItems(event)); list.on('input change', '[data-key-observe]', (event) => this.observeKey(event)); list.find('[data-collection-holder]').each((index, container) => { container = $(container); if (container.data('collection-sort') || container[0].hasAttribute('data-collection-nosort')) { return; } container.data('collection-sort', new Sortable(container.get(0), { forceFallback: false, handle: '.collection-sort', animation: 150, onUpdate: () => this.reindex(container) })); }); this._updateActionsStateBasedOnMinMax(list); } addItem(event) { let button = $(event.currentTarget); let position = button.data('action-add') || 'bottom'; let list = $(button.closest('[data-type="collection"]')); let template = $(list.find('> [data-collection-template="new"]').data('collection-template-html')); this._updateActionsStateBasedOnMinMax(list); let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]'); let maxItems = list.data('max'); if (typeof maxItems !== 'undefined' && items.length >= maxItems) { return; } list.find('> [data-collection-holder]')[position === 'top' ? 'prepend' : 'append'](template); this.reindex(list); items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]'); let topAction = list.closest('[data-type="collection"]').find('[data-action-add="top"]'); let sortAction = list.closest('[data-type="collection"]').find('[data-action="sort"]'); if (items.length) { if (topAction.length) { topAction.parent().removeClass('hidden'); } if (sortAction.length && items.length > 1) { sortAction.removeClass('hidden'); } } // refresh toggleables in a list $('[data-grav-field="toggleable"] input[type="checkbox"]').trigger('change'); } static closeConfirmations() { $('.list-confirm-deletion[data-action="delete"]').addClass('hidden'); } confirmRemove(event) { const button = $(event.currentTarget); const list = $(button.closest('.item-actions')); const action = list.find('.list-confirm-deletion[data-action="delete"]'); const isHidden = action.hasClass('hidden'); CollectionsField.closeConfirmations(); action[isHidden ? 'removeClass' : 'addClass']('hidden'); } removeItem(event) { let button = $(event.currentTarget); let item = button.closest('[data-collection-item]'); let list = $(button.closest('[data-type="collection"]')); let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]'); let minItems = list.data('min'); if (typeof minItems !== 'undefined' && items.length <= minItems) { return; } item.remove(); this.reindex(list); items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]'); let topAction = list.closest('[data-type="collection"]').find('[data-action-add="top"]'); let sortAction = list.closest('[data-type="collection"]').find('[data-action="sort"]'); if (!items.length) { if (topAction.length) { topAction.parent().addClass('hidden'); } } if (sortAction.length && items.length <= 1) { sortAction.addClass('hidden'); } this._updateActionsStateBasedOnMinMax(list); } collapseItems(event) { let button = $(event.currentTarget); let items = $(button.closest('[data-type="collection"]')).find('> ul > [data-collection-item] > .item-actions [data-action="collapse"]'); items.click(); } collapseItem(event) { let button = $(event.currentTarget); let item = button.closest('[data-collection-item]'); button.attr('data-action', 'expand').removeClass('fa-chevron-circle-down').addClass('fa-chevron-circle-right'); item.addClass('collection-collapsed'); } expandItems(event) { let button = $(event.currentTarget); let items = $(button.closest('[data-type="collection"]')).find('> ul > [data-collection-item] > .item-actions [data-action="expand"]'); items.click(); } expandItem(event) { let button = $(event.currentTarget); let item = button.closest('[data-collection-item]'); button.attr('data-action', 'collapse').removeClass('fa-chevron-circle-right').addClass('fa-chevron-circle-down'); item.removeClass('collection-collapsed'); } sortItems(event) { let button = $(event.currentTarget); let sortby = button.data('action-sort'); let sortby_dir = button.data('action-sort-dir') || 'asc'; let list = $(button.closest('[data-type="collection"]')); let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]'); items.sort((a, b) => { let A = $(a).find('[name$="[' + sortby + ']"]'); let B = $(b).find('[name$="[' + sortby + ']"]'); let sort; if (sortby_dir === 'asc') { sort = (A.val() < B.val()) ? -1 : (A.val() > B.val()) ? 1 : 0; } else { sort = (A.val() > B.val()) ? -1 : (A.val() < B.val()) ? 1 : 0; } return sort; }).each((_, container) => { $(container).parent().append(container); }); this.reindex(list); } observeKey(event) { let input = $(event.target); let value = input.val(); let item = input.closest('[data-collection-key]'); item.data('collection-key-backup', item.data('collection-key')).data('collection-key', value); this.reindex(null, item); } reindex(list, items) { items = items || $(list).closest('[data-type="collection"]').find('> ul > [data-collection-item]'); items.each((index, item) => { item = $(item); let observed = item.find('[data-key-observe]'); let observedValue = observed.val(); let hasCustomKey = observed.length; let currentKey = item.data('collection-key-backup'); item.attr('data-collection-key', hasCustomKey ? observedValue : index); ['name', 'data-grav-field-name', 'for', 'id', 'data-grav-file-settings', 'data-file-post-add', 'data-file-post-remove', 'data-grav-array-name', 'data-grav-elements'].forEach((prop) => { item.find('[' + prop + '], [_' + prop + ']').each(function() { let element = $(this); let indexes = []; let array_index = null; let regexps = [ new RegExp('\\[(\\d+|\\*|' + currentKey + ')\\]', 'g'), new RegExp('\\.(\\d+|\\*|' + currentKey + ')\\.', 'g') ]; // special case to preserve array field index keys if (prop === 'name' && element.data('gravArrayType')) { const match_index = element.attr(prop).match(/\[[0-9]{1,}\]$/); const pattern = element[0].closest('[data-grav-array-name]').dataset.gravArrayName; if (match_index && pattern) { array_index = match_index[0]; element.attr(prop, `${pattern}${match_index[0]}`); return; } } if (hasCustomKey && !observedValue) { element.attr(`_${prop}`, element.attr(prop)); element.attr(prop, null); return; } if (element.attr(`_${prop}`)) { element.attr(prop, element.attr(`_${prop}`)); element.attr(`_${prop}`, null); } element.parents('[data-collection-key]').map((idx, parent) => indexes.push($(parent).attr('data-collection-key'))); indexes.reverse(); let matchedKey = currentKey; let replaced = element.attr(prop).replace(regexps[0], (/* str, p1, offset */) => { let extras = ''; if (array_index) { extras = array_index; console.log(indexes, extras); } matchedKey = indexes.shift() || matchedKey; return `[${matchedKey}]${extras}`; }); replaced = replaced.replace(regexps[1], (/* str, p1, offset */) => { matchedKey = indexes.shift() || matchedKey; return `.${matchedKey}.`; }); element.attr(prop, replaced); }); }); }); } _onAddedNodes(event, target/* , record, instance */) { let collections = $(target).find('[data-type="collection"]'); if (!collections.length) { return; } collections.each((index, collection) => { collection = $(collection); if (!~this.lists.index(collection)) { this.addList(collection); } }); } _updateActionsStateBasedOnMinMax(list) { let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]'); let minItems = list.data('min'); let maxItems = list.data('max'); list.find('> .collection-actions [data-action="add"]').attr('disabled', false); list.find('> ul > li > .item-actions [data-action="delete"]').attr('disabled', false); if (typeof minItems !== 'undefined' && items.length <= minItems) { list.find('> ul > li > .item-actions [data-action="delete"]').attr('disabled', true); } if (typeof maxItems !== 'undefined' && items.length >= maxItems) { list.find('> .collection-actions [data-action="add"]').attr('disabled', true); } } } export let Instance = new CollectionsField();