collections.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. import $ from 'jquery';
  2. import Sortable from 'sortablejs';
  3. import '../../utils/jquery-utils';
  4. export default class CollectionsField {
  5. constructor() {
  6. this.lists = $();
  7. $('[data-type="collection"]').each((index, list) => this.addList(list));
  8. $('body').on('mutation._grav', this._onAddedNodes.bind(this));
  9. }
  10. addList(list) {
  11. list = $(list);
  12. this.lists = this.lists.add(list);
  13. list.on('click', '> .collection-actions [data-action="add"]', (event) => this.addItem(event));
  14. list.on('click', '> ul > li > .item-actions [data-action="delete"]', (event) => this.removeItem(event));
  15. list.on('click', '> ul > li > .item-actions [data-action="collapse"]', (event) => this.collapseItem(event));
  16. list.on('click', '> ul > li > .item-actions [data-action="expand"]', (event) => this.expandItem(event));
  17. list.on('click', '> .collection-actions [data-action-sort="date"]', (event) => this.sortItems(event));
  18. list.on('click', '> .collection-actions [data-action="collapse_all"]', (event) => this.collapseItems(event));
  19. list.on('click', '> .collection-actions [data-action="expand_all"]', (event) => this.expandItems(event));
  20. list.on('input change', '[data-key-observe]', (event) => this.observeKey(event));
  21. list.find('[data-collection-holder]').each((index, container) => {
  22. container = $(container);
  23. if (container.data('collection-sort') || container[0].hasAttribute('data-collection-nosort')) { return; }
  24. container.data('collection-sort', new Sortable(container.get(0), {
  25. forceFallback: false,
  26. handle: '.collection-sort',
  27. animation: 150,
  28. onUpdate: () => this.reindex(container)
  29. }));
  30. });
  31. this._updateActionsStateBasedOnMinMax(list);
  32. }
  33. addItem(event) {
  34. let button = $(event.currentTarget);
  35. let position = button.data('action-add') || 'bottom';
  36. let list = $(button.closest('[data-type="collection"]'));
  37. let template = $(list.find('> [data-collection-template="new"]').data('collection-template-html'));
  38. this._updateActionsStateBasedOnMinMax(list);
  39. let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
  40. let maxItems = list.data('max');
  41. if (typeof maxItems !== 'undefined' && items.length >= maxItems) {
  42. return;
  43. }
  44. list.find('> [data-collection-holder]')[position === 'top' ? 'prepend' : 'append'](template);
  45. this.reindex(list);
  46. items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
  47. let topAction = list.closest('[data-type="collection"]').find('[data-action-add="top"]');
  48. let sortAction = list.closest('[data-type="collection"]').find('[data-action="sort"]');
  49. if (items.length) {
  50. if (topAction.length) { topAction.parent().removeClass('hidden'); }
  51. if (sortAction.length && items.length > 1) { sortAction.removeClass('hidden'); }
  52. }
  53. // refresh toggleables in a list
  54. $('[data-grav-field="toggleable"] input[type="checkbox"]').trigger('change');
  55. }
  56. removeItem(event) {
  57. let button = $(event.currentTarget);
  58. let item = button.closest('[data-collection-item]');
  59. let list = $(button.closest('[data-type="collection"]'));
  60. let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
  61. let minItems = list.data('min');
  62. if (typeof minItems !== 'undefined' && items.length <= minItems) {
  63. return;
  64. }
  65. item.remove();
  66. this.reindex(list);
  67. items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
  68. let topAction = list.closest('[data-type="collection"]').find('[data-action-add="top"]');
  69. let sortAction = list.closest('[data-type="collection"]').find('[data-action="sort"]');
  70. if (!items.length) {
  71. if (topAction.length) { topAction.parent().addClass('hidden'); }
  72. }
  73. if (sortAction.length && items.length <= 1) { sortAction.addClass('hidden'); }
  74. this._updateActionsStateBasedOnMinMax(list);
  75. }
  76. collapseItems(event) {
  77. let button = $(event.currentTarget);
  78. let items = $(button.closest('[data-type="collection"]')).find('> ul > [data-collection-item] > .item-actions [data-action="collapse"]');
  79. items.click();
  80. }
  81. collapseItem(event) {
  82. let button = $(event.currentTarget);
  83. let item = button.closest('[data-collection-item]');
  84. button.attr('data-action', 'expand').removeClass('fa-chevron-circle-down').addClass('fa-chevron-circle-right');
  85. item.addClass('collection-collapsed');
  86. }
  87. expandItems(event) {
  88. let button = $(event.currentTarget);
  89. let items = $(button.closest('[data-type="collection"]')).find('> ul > [data-collection-item] > .item-actions [data-action="expand"]');
  90. items.click();
  91. }
  92. expandItem(event) {
  93. let button = $(event.currentTarget);
  94. let item = button.closest('[data-collection-item]');
  95. button.attr('data-action', 'collapse').removeClass('fa-chevron-circle-right').addClass('fa-chevron-circle-down');
  96. item.removeClass('collection-collapsed');
  97. }
  98. sortItems(event) {
  99. let button = $(event.currentTarget);
  100. let sortby = button.data('action-sort');
  101. let sortby_dir = button.data('action-sort-dir') || 'asc';
  102. let list = $(button.closest('[data-type="collection"]'));
  103. let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
  104. items.sort((a, b) => {
  105. let A = $(a).find('[name$="[' + sortby + ']"]');
  106. let B = $(b).find('[name$="[' + sortby + ']"]');
  107. let sort;
  108. if (sortby_dir === 'asc') {
  109. sort = (A.val() < B.val()) ? -1 : (A.val() > B.val()) ? 1 : 0;
  110. } else {
  111. sort = (A.val() > B.val()) ? -1 : (A.val() < B.val()) ? 1 : 0;
  112. }
  113. return sort;
  114. }).each((_, container) => {
  115. $(container).parent().append(container);
  116. });
  117. this.reindex(list);
  118. }
  119. observeKey(event) {
  120. let input = $(event.target);
  121. let value = input.val();
  122. let item = input.closest('[data-collection-key]');
  123. item.data('collection-key-backup', item.data('collection-key')).data('collection-key', value);
  124. this.reindex(null, item);
  125. }
  126. reindex(list, items) {
  127. items = items || $(list).closest('[data-type="collection"]').find('> ul > [data-collection-item]');
  128. items.each((index, item) => {
  129. item = $(item);
  130. let observed = item.find('[data-key-observe]');
  131. let observedValue = observed.val();
  132. let hasCustomKey = observed.length;
  133. let currentKey = item.data('collection-key-backup');
  134. item.attr('data-collection-key', hasCustomKey ? observedValue : index);
  135. ['name', 'data-grav-field-name', 'for', 'id', 'data-grav-file-settings', 'data-grav-array-name'].forEach((prop) => {
  136. item.find('[' + prop + '], [_' + prop + ']').each(function() {
  137. let element = $(this);
  138. let indexes = [];
  139. let array_index = null;
  140. let regexps = [
  141. new RegExp('\\[(\\d+|\\*|' + currentKey + ')\\]', 'g'),
  142. new RegExp('\\.(\\d+|\\*|' + currentKey + ')\\.', 'g')
  143. ];
  144. // special case to preserve array field index keys
  145. if (prop === 'name' && element.data('gravArrayType')) {
  146. const match_index = element.attr(prop).match(/\[[0-9]{1,}\]$/);
  147. const pattern = element.closest('[data-grav-array-name]').data('gravArrayName');
  148. if (match_index && pattern) {
  149. array_index = match_index[0];
  150. element.attr(prop, `${pattern}${match_index[0]}`);
  151. return;
  152. }
  153. }
  154. if (hasCustomKey && !observedValue) {
  155. element.attr(`_${prop}`, element.attr(prop));
  156. element.attr(prop, null);
  157. return;
  158. }
  159. if (element.attr(`_${prop}`)) {
  160. element.attr(prop, element.attr(`_${prop}`));
  161. element.attr(`_${prop}`, null);
  162. }
  163. element.parents('[data-collection-key]').map((idx, parent) => indexes.push($(parent).attr('data-collection-key')));
  164. indexes.reverse();
  165. let matchedKey = currentKey;
  166. let replaced = element.attr(prop).replace(regexps[0], (/* str, p1, offset */) => {
  167. let extras = '';
  168. if (array_index) { extras = array_index; console.log(indexes, extras); }
  169. matchedKey = indexes.shift() || matchedKey;
  170. return `[${matchedKey}]${extras}`;
  171. });
  172. replaced = replaced.replace(regexps[1], (/* str, p1, offset */) => {
  173. matchedKey = indexes.shift() || matchedKey;
  174. return `.${matchedKey}.`;
  175. });
  176. element.attr(prop, replaced);
  177. });
  178. });
  179. });
  180. }
  181. _onAddedNodes(event, target/* , record, instance */) {
  182. let collections = $(target).find('[data-type="collection"]');
  183. if (!collections.length) { return; }
  184. collections.each((index, collection) => {
  185. collection = $(collection);
  186. if (!~this.lists.index(collection)) {
  187. this.addList(collection);
  188. }
  189. });
  190. }
  191. _updateActionsStateBasedOnMinMax(list) {
  192. let items = list.closest('[data-type="collection"]').find('> ul > [data-collection-item]');
  193. let minItems = list.data('min');
  194. let maxItems = list.data('max');
  195. list.find('> .collection-actions [data-action="add"]').attr('disabled', false);
  196. list.find('> ul > li > .item-actions [data-action="delete"]').attr('disabled', false);
  197. if (typeof minItems !== 'undefined' && items.length <= minItems) {
  198. list.find('> ul > li > .item-actions [data-action="delete"]').attr('disabled', true);
  199. }
  200. if (typeof maxItems !== 'undefined' && items.length >= maxItems) {
  201. list.find('> .collection-actions [data-action="add"]').attr('disabled', true);
  202. }
  203. }
  204. }
  205. export let Instance = new CollectionsField();