PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 2.0.0
Subscriptions for WooCommerce with Stripe Recurring Payments v2.0.0
2.0.0 1.11.2 1.11.1 1.11.0 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.6 1.9.5 trunk 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 All 61 releases
subscription / assets / js / admin-components / editlist.js

editlist.js in Subscriptions for WooCommerce with Stripe Recurring Payments 2.0.0, at assets/js/admin-components/editlist.js

242 lines 8.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * WPSubsEditList — editable ordered list of text items.
3 *
4 * A reusable admin component: renders items in a reorderable list with per-row
5 * remove/move controls, plus an inline input + add button to append new items.
6 * The ordered list is serialized as JSON ([{ key, label }]) into a hidden input
7 * so it submits with the surrounding form. `key` is a slug derived from the label.
8 *
9 * Usage:
10 * PHP: SettingsHelper::render_editlist() / any markup with the classes below
11 * JS: WPSubsEditList.init() — auto-inits all .wpsubs-editlist elements
12 *
13 * Expected structure inside .wpsubs-editlist:
14 * input[type=hidden] — JSON store
15 * .wpsubs-editlist__items > .wpsubs-editlist__item[data-key]
16 * .wpsubs-editlist__label
17 * [data-editlist-up] [data-editlist-down] [data-editlist-remove]
18 * .wpsubs-editlist__empty — shown only when empty
19 * .wpsubs-editlist__input — inline text input
20 * [data-editlist-add] — add/confirm button
21 *
22 * Events fired on the root element (bubbles):
23 * wpsubs:change — { items } after any add/remove/reorder
24 */
25 (function () {
26 "use strict";
27
28 /**
29 * Derive a slug key from a label.
30 *
31 * @param {string} label
32 * @return {string}
33 */
34 function slugify(label) {
35 return String(label)
36 .toLowerCase()
37 .replace(/[^a-z0-9]+/g, "_")
38 .replace(/^_+|_+$/g, "");
39 }
40
41 var SVG_UP =
42 '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="18 15 12 9 6 15"/></svg>';
43 var SVG_DOWN =
44 '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>';
45 var SVG_TRASH =
46 '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>';
47
48 /**
49 * @param {HTMLElement} el Root .wpsubs-editlist element.
50 */
51 function WPSubsEditList(el) {
52 this.el = el;
53 this.hidden = el.querySelector('input[type="hidden"]');
54 this.list = el.querySelector(".wpsubs-editlist__items");
55 this.emptyEl = el.querySelector(".wpsubs-editlist__empty");
56 this.input = el.querySelector(".wpsubs-editlist__input");
57 // Optional live count badge (e.g. when the list lives inside a modal trigger).
58 this.countEl = el.querySelector(".wpsubs-editlist__count");
59 this._bind();
60 this._serialize();
61 }
62
63 WPSubsEditList.prototype._rows = function () {
64 return this.list ? this.list.querySelectorAll(".wpsubs-editlist__item") : [];
65 };
66
67 /** Serialize the current rows into the hidden input as JSON. */
68 WPSubsEditList.prototype._serialize = function () {
69 var items = [];
70 this._rows().forEach(function (row) {
71 var labelEl = row.querySelector(".wpsubs-editlist__label");
72 items.push({
73 key: row.getAttribute("data-key") || "",
74 label: labelEl ? labelEl.textContent : "",
75 });
76 });
77 if (this.hidden) this.hidden.value = JSON.stringify(items);
78 if (this.emptyEl) this.emptyEl.hidden = items.length > 0;
79 if (this.countEl) this.countEl.textContent = String(items.length);
80 this.el.dispatchEvent(new CustomEvent("wpsubs:change", { bubbles: true, detail: { items: items } }));
81 };
82
83 /**
84 * Append a new item row.
85 *
86 * @param {string} label
87 */
88 WPSubsEditList.prototype._addItem = function (label) {
89 var text = String(label).trim();
90 if (!text || !this.list) return;
91
92 var row = document.createElement("li");
93 row.className = "wpsubs-editlist__item";
94 row.setAttribute("data-key", slugify(text));
95
96 var handle = document.createElement("span");
97 handle.className = "wpsubs-editlist__handle";
98 handle.setAttribute("aria-hidden", "true");
99 handle.innerHTML = "&#8942;&#8942;";
100
101 var lab = document.createElement("span");
102 lab.className = "wpsubs-editlist__label";
103 lab.textContent = text;
104
105 var actions = document.createElement("span");
106 actions.className = "wpsubs-editlist__actions";
107 actions.innerHTML =
108 '<button type="button" class="wpsubs-editlist__btn" data-editlist-up>' +
109 SVG_UP +
110 "</button>" +
111 '<button type="button" class="wpsubs-editlist__btn" data-editlist-down>' +
112 SVG_DOWN +
113 "</button>" +
114 '<button type="button" class="wpsubs-editlist__btn wpsubs-editlist__btn--danger" data-editlist-remove>' +
115 SVG_TRASH +
116 "</button>";
117
118 row.appendChild(handle);
119 row.appendChild(lab);
120 row.appendChild(actions);
121 this.list.appendChild(row);
122 this._serialize();
123 };
124
125 /** Add the item currently typed in the inline input, then reset it. */
126 WPSubsEditList.prototype._commitInput = function () {
127 if (!this.input) return;
128 var val = this.input.value.trim();
129 if (!val) return;
130 this._addItem(val);
131 this.input.value = "";
132 this.input.focus();
133 };
134
135 WPSubsEditList.prototype._bind = function () {
136 var self = this;
137
138 this.el.addEventListener("click", function (e) {
139 if (e.target.closest("[data-editlist-add]")) {
140 self._commitInput();
141 return;
142 }
143 var row = e.target.closest(".wpsubs-editlist__item");
144 if (!row) return;
145
146 if (e.target.closest("[data-editlist-remove]")) {
147 row.parentNode.removeChild(row);
148 self._serialize();
149 } else if (e.target.closest("[data-editlist-up]")) {
150 if (row.previousElementSibling) {
151 row.parentNode.insertBefore(row, row.previousElementSibling);
152 self._serialize();
153 }
154 } else if (e.target.closest("[data-editlist-down]")) {
155 if (row.nextElementSibling) {
156 row.parentNode.insertBefore(row.nextElementSibling, row);
157 self._serialize();
158 }
159 }
160 });
161
162 if (this.input) {
163 this.input.addEventListener("keydown", function (e) {
164 if (e.key === "Enter") {
165 e.preventDefault();
166 self._commitInput();
167 }
168 });
169 }
170
171 // Drag to reorder — only starts from the handle.
172 this._dragRow = null;
173
174 this.el.addEventListener("mousedown", function (e) {
175 var handle = e.target.closest(".wpsubs-editlist__handle");
176 if (!handle) return;
177 var row = handle.closest(".wpsubs-editlist__item");
178 if (row) row.setAttribute("draggable", "true");
179 });
180
181 this.el.addEventListener("dragstart", function (e) {
182 var row = e.target.closest(".wpsubs-editlist__item");
183 if (!row || row.getAttribute("draggable") !== "true") return;
184 self._dragRow = row;
185 row.classList.add("wpsubs-editlist__item--dragging");
186 if (e.dataTransfer) {
187 e.dataTransfer.effectAllowed = "move";
188 try {
189 e.dataTransfer.setData("text/plain", "");
190 } catch (err) {
191 /* IE guard */
192 }
193 }
194 });
195
196 this.el.addEventListener("dragover", function (e) {
197 if (!self._dragRow || !self.list) return;
198 e.preventDefault();
199 var over = e.target.closest(".wpsubs-editlist__item");
200 if (!over || over === self._dragRow) return;
201 var rect = over.getBoundingClientRect();
202 var after = (e.clientY - rect.top) / rect.height > 0.5;
203 self.list.insertBefore(self._dragRow, after ? over.nextSibling : over);
204 });
205
206 this.el.addEventListener("drop", function (e) {
207 if (self._dragRow) e.preventDefault();
208 });
209
210 this.el.addEventListener("dragend", function () {
211 if (!self._dragRow) return;
212 self._dragRow.classList.remove("wpsubs-editlist__item--dragging");
213 self._dragRow.removeAttribute("draggable");
214 self._dragRow = null;
215 self._serialize();
216 });
217 };
218
219 /**
220 * Initialise all un-initialised .wpsubs-editlist elements under root.
221 *
222 * @param {Document|HTMLElement} [root]
223 */
224 function init(root) {
225 (root || document).querySelectorAll(".wpsubs-editlist:not([data-editlist-init])").forEach(function (el) {
226 el.setAttribute("data-editlist-init", "1");
227 new WPSubsEditList(el);
228 });
229 }
230
231 if (document.readyState === "loading") {
232 document.addEventListener("DOMContentLoaded", function () {
233 init();
234 });
235 } else {
236 init();
237 }
238
239 // Public API
240 window.WPSubsEditList = { init: init };
241 })();
242