312 lines
11 KiB
JavaScript
312 lines
11 KiB
JavaScript
|
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();
|