multilevel.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import $ from 'jquery';
  2. $(function() {
  3. const getField = function getField(level, name) {
  4. let levelMargin = level * 50;
  5. let top = (level === 0 ? 'top' : '');
  6. let the_name = 'name="' + name + '"';
  7. if (level === 0) {
  8. // top
  9. the_name = 'data-attr-name="' + name + '"';
  10. }
  11. let field = `
  12. <div class="element-wrapper">
  13. <div class="form-row array-field-value_only js__multilevel-field ${top}"
  14. data-grav-array-type="row">
  15. <input
  16. type="text"
  17. ${the_name}
  18. placeholder="Enter value"
  19. style="margin-left: ${levelMargin}px"
  20. value="" />
  21. <span class="fa fa-minus js__remove-item"></span>
  22. <span class="fa fa-plus js__add-sibling hidden" data-level="${level}" ></span>
  23. <span class="fa fa-plus-circle js__add-children hidden" data-level="${level}"></span>
  24. </div>
  25. </div>
  26. `;
  27. return field;
  28. };
  29. const hasChildInputs = function hasChildInputs($element) {
  30. if ($element.attr('name')) {
  31. return false;
  32. }
  33. return true;
  34. };
  35. const getTopItems = function getTopItems(element) {
  36. return $(element + ' .js__multilevel-field.top');
  37. };
  38. const refreshControls = function refreshControls(unique_identifier) {
  39. let element = '[data-grav-multilevel-field]';
  40. if (unique_identifier) {
  41. element = '[data-grav-multilevel-field][data-id="' + unique_identifier + '"]';
  42. }
  43. const hideButtons = function hideButtons() {
  44. $(element + ' .js__add-sibling').addClass('hidden');
  45. $(element + ' .js__add-children').addClass('hidden');
  46. };
  47. const restoreAddSiblingButtons = function restoreAddSiblingButtons() {
  48. $(element + ' .children-wrapper').each(function() {
  49. let elements = $(this).children();
  50. elements.last().each(function() {
  51. let field = $(this);
  52. if (!$(this).hasClass('js__multilevel-field')) {
  53. field = $(this).find('.js__multilevel-field').first();
  54. }
  55. field.find('.js__add-sibling').removeClass('hidden');
  56. });
  57. });
  58. // add sibling to the last top element
  59. $(element + ' .js__multilevel-field.top').last().find('.js__add-sibling').removeClass('hidden');
  60. };
  61. const restoreAddChildrenButtons = function restoreAddChildrenButtons() {
  62. $(element + ' .js__multilevel-field').each(function() {
  63. if ($(this).siblings('.children-wrapper').length === 0 || $(this).siblings('.children-wrapper').find('.js__multilevel-field').length === 0) {
  64. $(this).find('.js__add-children').removeClass('hidden');
  65. }
  66. });
  67. };
  68. const preventRemovingLastTopItem = function preventRemovingLastTopItem() {
  69. let top_items = getTopItems(element);
  70. if (top_items.length === 1) {
  71. top_items.first().find('.js__remove-item').addClass('hidden');
  72. }
  73. };
  74. hideButtons();
  75. restoreAddSiblingButtons();
  76. restoreAddChildrenButtons();
  77. preventRemovingLastTopItem();
  78. };
  79. const changeAllOccurrencesInTree = function($el, current_name, new_name) {
  80. $el.parents('[data-grav-multilevel-field]').find('input').each(function() {
  81. let $input = $(this);
  82. if ($input.attr('name')) {
  83. $input.attr('name', $input.attr('name').replace(current_name, new_name));
  84. }
  85. if ($input.attr('data-attr-name')) {
  86. $input.attr('data-attr-name', $input.attr('data-attr-name').replace(current_name, new_name));
  87. }
  88. });
  89. };
  90. $(document).ready(function() {
  91. refreshControls();
  92. });
  93. $(document).on('mouseleave', '[data-grav-multilevel-field]', function(event) {
  94. let top_items = getTopItems('[data-id="' + $(this).data('id') + '"]');
  95. let has_top_items_without_children = false;
  96. let element_content = '';
  97. top_items.each(function() {
  98. let item = $(this);
  99. if ($(item).siblings('.children-wrapper').find('input').length === 0) {
  100. has_top_items_without_children = true;
  101. element_content = item.find('input').val();
  102. }
  103. });
  104. if (has_top_items_without_children) {
  105. if (element_content) {
  106. alert('Warning: if you save now, the element ' + element_content + ', without children, will be removed, because it\'s invalid YAML');
  107. } else {
  108. alert('Warning: if you save now, the top elements without children will be removed, because it\'s invalid YAML');
  109. }
  110. }
  111. });
  112. $(document).on('click', '[data-grav-multilevel-field] .js__add-children', function(event) {
  113. let element = $(this);
  114. let unique_container_id = element.closest('.js__multilevel-field').data('id');
  115. let level = element.data('level') + 1;
  116. const getParentOfElement = function getParentOfElement(element) {
  117. let parent = element.closest('.js__multilevel-field').parent().first();
  118. if (parent.find('.children-wrapper').length === 0) {
  119. $(parent).append('<div class="children-wrapper"></div>');
  120. }
  121. parent = parent.find('.children-wrapper').first();
  122. return parent;
  123. };
  124. const getNameFromParentInput = function getNameFromParentInput(parentInput, attr) {
  125. if (parentInput.hasClass('children-wrapper')) {
  126. parentInput = parentInput.siblings('.js__multilevel-field').first().find('input');
  127. }
  128. return parentInput.attr(attr) + '[' + parentInput.val() + ']';
  129. };
  130. const getInputFromChildrenWrapper = function getInputFromChildrenWrapper(parentChildrenWrapper) {
  131. return parentChildrenWrapper.siblings('.js__multilevel-field').first().find('input');
  132. };
  133. let parentChildrenWrapper = getParentOfElement(element);
  134. let parentInput = getInputFromChildrenWrapper(parentChildrenWrapper);
  135. let attr = 'name';
  136. if (parentInput.closest('.js__multilevel-field').hasClass('top')) {
  137. attr = 'data-attr-name';
  138. }
  139. parentInput.attr(attr, parentInput.attr(attr).replace('[]', ''));
  140. let name = getNameFromParentInput(parentInput, attr);
  141. let field = getField(level, name);
  142. $(parentChildrenWrapper).append(field);
  143. refreshControls(unique_container_id);
  144. });
  145. $(document).on('click', '[data-grav-multilevel-field] .js__add-sibling', function(event) {
  146. let element = $(this);
  147. let unique_container_id = element.closest('.js__multilevel-field').data('id');
  148. let level = element.data('level');
  149. element.closest('.children-wrapper').find('.js__add-sibling').addClass('hidden');
  150. let sibling = null;
  151. let is_top = false;
  152. if (element.closest('.js__multilevel-field').hasClass('top')) {
  153. is_top = true;
  154. }
  155. if (is_top) {
  156. sibling = element.closest('.js__multilevel-field').first().find('input').last();
  157. } else {
  158. sibling = element.siblings('input').first();
  159. if (!sibling) {
  160. sibling = element.closest('.children-wrapper').first().find('input').last();
  161. }
  162. }
  163. const getParentOfElement = function getParentOfElement(element) {
  164. let parent = element.closest('.js__multilevel-field').parent().first();
  165. if (!parent.hasClass('element-wrapper')) {
  166. if (parent.find('.element-wrapper').length === 0) {
  167. $(parent).append('<div class="element-wrapper"></div>');
  168. }
  169. parent = parent.find('.element-wrapper').first();
  170. }
  171. return parent;
  172. };
  173. const getNameFromSibling = function getNameFromSibling(parent, sibling, is_top = false) {
  174. let name = sibling.attr('name');
  175. if (hasChildInputs(sibling)) {
  176. let val = sibling.data('attr-name') + '[]';
  177. sibling.removeAttr('name');
  178. return val;
  179. }
  180. let last_index = name.lastIndexOf('[');
  181. let almost_there = name.substr(last_index + 1);
  182. let last_tag = almost_there.substr(0, almost_there.length - 1);
  183. if ($.isNumeric(last_tag)) {
  184. name = name.replace('[' + last_tag + ']', '[' + (parseInt(last_tag, 10) + 1) + ']');
  185. } else {
  186. if (is_top) {
  187. name = name.replace('[' + last_tag + ']', '');
  188. } else {
  189. name = name + '[1]';
  190. // change sibling name attr if necessary
  191. if (sibling.attr('name').slice('-2') !== '[0]') {
  192. changeAllOccurrencesInTree(sibling, sibling.attr('name'), sibling.attr('name') + '[0]');
  193. }
  194. }
  195. }
  196. return name;
  197. };
  198. let parent = getParentOfElement(element);
  199. let name = getNameFromSibling(parent, sibling, is_top);
  200. let field = getField(level, name);
  201. $(field).insertAfter(parent);
  202. refreshControls(unique_container_id);
  203. });
  204. $(document).on('click', '[data-grav-multilevel-field] .js__remove-item', function(event) {
  205. $(this).parents('.element-wrapper').first().remove();
  206. let unique_container_id = $(this).closest('.js__multilevel-field').data('id');
  207. refreshControls(unique_container_id);
  208. });
  209. // Store old value before editing a field
  210. $(document).on('focusin', '[data-grav-multilevel-field] input', function(event) {
  211. $(this).data('current-value', $(this).val());
  212. });
  213. // Handle field edited event
  214. $(document).on('change', '[data-grav-multilevel-field] input', function(event) {
  215. let $el = $(this);
  216. let old_value = $el.data('current-value');
  217. let new_value = $el.val();
  218. let full_name = $el.attr('name') || $el.attr('data-attr-name'); // first-level items have `data-attr-name` instead of `name`
  219. let old_name_attr = full_name + '[' + old_value + ']';
  220. let new_name_attr = full_name + '[' + new_value + ']';
  221. changeAllOccurrencesInTree($el, old_name_attr, new_name_attr);
  222. });
  223. });