init-repeater.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import { contractData } from "../../data/contract-data.js"
  2. import { addEvent, debounce } from "../utils.js"
  3. export default function repeater(repeater = ".repeater-form", output = "output", addBtn = ".add-btn") {
  4. // Cache frequently used elements
  5. const repeaterForm = document.querySelector(repeater);
  6. const repeaterOutput = repeaterForm.querySelector(output);
  7. const addItemButton = repeaterForm.querySelector(addBtn);
  8. // Save data + form submit Event Listener
  9. const saveData = () => {
  10. const items = [];
  11. const seenNames = new Set();
  12. const repeaterItems = repeaterForm.querySelectorAll(".repeater-item");
  13. repeaterItems.forEach((item) => {
  14. const nameInput = item.querySelector(".repeater-item-name");
  15. const valueInput = item.querySelector(".repeater-item-value");
  16. const name = nameInput.value.trim();
  17. const value = valueInput.value.trim();
  18. if (name !== "" && value !== "") {
  19. if (seenNames.add(name)) {
  20. items.push({ name, value });
  21. }
  22. }
  23. });
  24. localStorage.setItem("repeaterData", JSON.stringify(items));
  25. console.log("Saved.");
  26. console.log(JSON.stringify(items));
  27. };
  28. addEvent(repeaterForm, (event) => {
  29. console.log("Form submitted");
  30. saveData();
  31. event.preventDefault();
  32. }, "submit");
  33. // Add new item + Event Listener
  34. const insertAddNewInputs = () => {
  35. const template = repeaterForm.querySelector(".repeater-item-template");
  36. const clone = template.content.cloneNode(true);
  37. repeaterOutput.appendChild(clone);
  38. };
  39. const addItemHandler = (event) => {
  40. // remove empty last-child (only if both fields are empty)
  41. repeaterForm.querySelector(".repeater-item:last-child:has(.repeater-item-name:placeholder-shown):has(.repeater-item-value:placeholder-shown)")?.remove()
  42. insertAddNewInputs();
  43. event.preventDefault();
  44. // console.log(event.target)
  45. repeaterForm.querySelector(".repeater-item:last-child input").focus();
  46. };
  47. addEvent(addItemButton, addItemHandler);
  48. addEvent(addItemButton, addItemHandler, "mousedown");
  49. // Remove item + Event Listener
  50. const removeItem = (item) => {
  51. item.remove();
  52. saveData();
  53. if (repeaterOutput.children.length === 0) {
  54. insertAddNewInputs();
  55. }
  56. };
  57. addEvent(repeaterForm, (event) => {
  58. if (event.target.classList.contains("remove-btn")) {
  59. const removedItem = event.target.closest(".repeater-item");
  60. removeItem(removedItem);
  61. event.preventDefault();
  62. }
  63. }, "click");
  64. // Debounced save + Event Listener
  65. const debouncedSave = debounce(saveData, 200);
  66. addEvent(repeaterForm, (event) => {
  67. const input = event.target;
  68. if (
  69. input.classList.contains("repeater-item-name") ||
  70. input.classList.contains("repeater-item-value")
  71. ) {
  72. debouncedSave();
  73. }
  74. }, "input");
  75. // init
  76. const init = () => {
  77. console.clear();
  78. repeaterForm.querySelector(".save-btn").style.display = "none";
  79. // repeaterForm.querySelector(".add-btn").style.cssText = "opacity: 0; overflow: hidden; pointer-events: none;";
  80. // ! BUG: doesn't default from json if ls empty
  81. // Load data from localStorage on page load
  82. const savedData = localStorage.getItem("repeaterData");
  83. if (savedData) {
  84. const parsedData = JSON.parse(savedData) ?? [];
  85. if (parsedData.length > 0) {
  86. repeaterOutput.innerHTML = ""; // Clear existing content
  87. } else {
  88. const shortcodes = contractData.shortcodes || [];
  89. shortcodes.forEach((shortcode) => {
  90. const template = repeaterForm.querySelector(".repeater-item-template");
  91. const clone = template.content.cloneNode(true);
  92. clone.querySelector(".repeater-item-name").setAttribute("value", shortcode.name);
  93. clone.querySelector(".repeater-item-value").setAttribute("value", shortcode.value);
  94. repeaterOutput.appendChild(clone);
  95. saveData();
  96. });
  97. }
  98. parsedData.forEach((item) => {
  99. const template = repeaterForm.querySelector(".repeater-item-template");
  100. const clone = template.content.cloneNode(true);
  101. const nameInput = clone.querySelector(".repeater-item-name");
  102. const valueInput = clone.querySelector(".repeater-item-value");
  103. nameInput.value = item.name;
  104. valueInput.value = item.value;
  105. repeaterOutput.appendChild(clone);
  106. });
  107. }
  108. // display input if empty
  109. if (repeaterOutput.children.length === 0) {
  110. insertAddNewInputs(); // Call the function to add a new item on page load
  111. }
  112. };
  113. init();
  114. }
  115. ///
  116. // Toggle overflow:visible when details finishes opening
  117. const initialOpenDetailsElements = document.querySelectorAll("details[open]");
  118. initialOpenDetailsElements.forEach(item => { item.style.overflow = "visible"; });
  119. const detailsElements = document.querySelectorAll("details");
  120. detailsElements.forEach(item => {
  121. item.addEventListener("transitionend", (event) => {
  122. const element = event.target;
  123. if (element.open === true) {
  124. element.style.overflow = "visible";
  125. // element.style.maxHeight = "300vh";
  126. } else {
  127. // if (!element.querySelector("&:is(summary)"))
  128. element.style.overflow = "hidden";
  129. // element.style.maxHeight = "90vh";
  130. // element.querySelector("& > *:not(summary)").style.height = "0";
  131. // alert("hidden")
  132. }
  133. });
  134. });
  135. ///
  136. // repeater(".repeater-form", "output", ".add-btn");
  137. repeater();
  138. ///
  139. // // Utils
  140. // // wrapper around querySelector
  141. // // select() usage: const [sel1, sel2 ] = select(".class1", ".class2")
  142. // // [repeaterForm, repeaterOutput, addItemButton] = select( ".repeater-form", "output", ".add-btn");
  143. // function select(...selectors) {
  144. // return selectors.map(selector => document.querySelector(selector));
  145. // };
  146. // function selectAll(...selectors) {
  147. // return selectors.map(selector => document.querySelectorAll(selector));
  148. // };
  149. // // wrapper around addEventListener with optional event type at the end
  150. // function addEvent(element, handler, eventType="click") {
  151. // element.addEventListener(eventType, handler);
  152. // }
  153. // // debounce() usage: const debouncedMyFunc = debounce(myFunc, 200);
  154. // // or function debouncedMyFunc() { return debounce(myFunc, 200); }
  155. // function debounce(callback, wait=200) {
  156. // let timeoutId = null;
  157. // return function (...args) {
  158. // window.clearTimeout(timeoutId);
  159. // timeoutId = window.setTimeout(() => {
  160. // callback.apply(null, args);
  161. // }, wait);
  162. // };
  163. // };
  164. // function addBrackets(element){
  165. // // Trim the value
  166. // let trimmedValue = element.value.trim();
  167. // // Check if it begins with "[", add if not
  168. // if (!trimmedValue.startsWith('[')) {
  169. // trimmedValue = '[' + trimmedValue;
  170. // }
  171. // // Check if it ends with "]", add if not
  172. // if (!trimmedValue.endsWith(']')) {
  173. // trimmedValue = trimmedValue + ']';
  174. // }
  175. // // Update the element value
  176. // element.value = trimmedValue;
  177. // }