PluginProbe
WebberZone Top 10 — Popular Posts / 4.5.1
WebberZone Top 10 — Popular Posts v4.5.1
4.5.1 4.5.0 4.4.3 4.4.2 4.4.1 4.4.0 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 trunk 1.0 1.0.1 1.1 1.2 1.3 1.4 1.4.1 1.5 1.5.1 1.5.2 1.5.3 1.6 1.6.1 All 117 releases
top-10 / includes / admin / settings / js / tom-select-init.js

tom-select-init.js in WebberZone Top 10 — Popular Posts 4.5.1, at includes/admin/settings/js/tom-select-init.js

265 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* global ajaxurl, WebberTomSelectSettings, jQuery, TomSelect */
2
3 (function ($) {
4 'use strict';
5
6 function normalizeOption(item) {
7 if (!item || typeof item !== 'object') {
8 return null;
9 }
10
11 const value = item.id || item.value || '';
12 const text = item.name || item.text || value;
13
14 if (!value && !text) {
15 return null;
16 }
17
18 return { value: String(value), text: String(text) };
19 }
20
21 function getEndpointOptions(settings, endpoint) {
22 if (!settings || !endpoint) {
23 return [];
24 }
25
26 const endpointOptions = settings[endpoint];
27
28 if (!Array.isArray(endpointOptions)) {
29 return [];
30 }
31
32 return endpointOptions
33 .map(normalizeOption)
34 .filter(Boolean);
35 }
36
37 function isTaxonomyEndpoint(endpoint) {
38 const key = typeof endpoint === 'string' ? endpoint : '';
39 return key === 'category' || key === 'post_tag' || key.includes('tax');
40 }
41
42 function initTomSelect(root) {
43 const scope = (root && typeof root.querySelectorAll === 'function') ? root : document;
44 const elements = scope.querySelectorAll('.ts_autocomplete');
45
46 elements.forEach(function (element) {
47 if (element.tomselect) {
48 return;
49 }
50
51 const tagName = (element.tagName || '').toUpperCase();
52 if (tagName !== 'INPUT' && tagName !== 'SELECT' && tagName !== 'TEXTAREA') {
53 return;
54 }
55
56 const prefix = element.getAttribute('data-wp-prefix') || 'Webber';
57 const settingsKey = `${prefix}TomSelectSettings`;
58 const settings = window[settingsKey]
59 || window.WebberTomSelectSettings
60 || {};
61
62 if (!settings || typeof settings !== 'object') {
63 return;
64 }
65
66 const action = element.getAttribute('data-wp-action') || settings.action;
67 const nonce = element.getAttribute('data-wp-nonce') || settings.nonce;
68 const endpoint = element.getAttribute('data-wp-endpoint') || settings.endpoint;
69 const strings = settings.strings || {};
70
71 const formattedOptions = getEndpointOptions(settings, endpoint);
72
73 let rawValue = '';
74
75 if (tagName === 'SELECT' && element.multiple && element.selectedOptions) {
76 rawValue = Array.from(element.selectedOptions)
77 .map(option => String(option.value || '').trim())
78 .filter(Boolean)
79 .join(',');
80 } else if (typeof element.value === 'string') {
81 rawValue = element.value;
82 } else {
83 const valueAttribute = element.getAttribute('value');
84 rawValue = typeof valueAttribute === 'string' ? valueAttribute : '';
85 }
86
87 const savedIds = rawValue.split(',').map(id => id.trim()).filter(Boolean);
88 const taxonomyEndpoint = isTaxonomyEndpoint(endpoint);
89
90 // For taxonomy endpoints, add saved values as options so Tom Select can display them
91 if (taxonomyEndpoint && savedIds.length > 0) {
92 const savedOptions = savedIds.map(savedValue => {
93 // Extract term name from formatted string "Name (taxonomy:id)"
94 const match = savedValue.match(/^(.*)\s+\(.*:\d+\)$/);
95 const termName = match ? match[1] : savedValue;
96 return { value: savedValue, text: termName };
97 });
98
99 // Merge saved options with existing options, avoiding duplicates
100 const allOptions = [...formattedOptions];
101 savedOptions.forEach(savedOption => {
102 if (!allOptions.some(opt => opt.value === savedOption.value)) {
103 allOptions.push(savedOption);
104 }
105 });
106
107 // Replace formattedOptions with merged options
108 formattedOptions.length = 0;
109 formattedOptions.push(...allOptions);
110 }
111
112 // For non-taxonomy endpoints, add saved values as options so Tom Select can display them.
113 if (!taxonomyEndpoint && savedIds.length > 0) {
114 savedIds.forEach(savedValue => {
115 if (!formattedOptions.some(opt => opt.value === savedValue)) {
116 formattedOptions.push({ value: savedValue, text: savedValue });
117 }
118 });
119 }
120
121 // Get any custom config from data attributes
122 let customConfig = {};
123 const configAttr = element.getAttribute('data-ts-config');
124
125 if (configAttr) {
126 try {
127 customConfig = JSON.parse(configAttr);
128 } catch (e) {
129 console.error('Error parsing custom config:', configAttr, e);
130 }
131 }
132
133 // Default config
134 const defaultConfig = {
135 plugins: ['dropdown_input', 'clear_button', 'remove_button'],
136 valueField: 'value',
137 labelField: 'text',
138 searchField: ['text', 'value'],
139 options: formattedOptions,
140 items: savedIds,
141 persist: true,
142 createOnBlur: false,
143 create: false,
144 render: {
145 no_results: (data, escape) => {
146 const template = strings.no_results || 'No results for "%s"';
147 return `<div class="no-results">${template.replace('%s', escape(data.input))}</div>`;
148 },
149 option: (data, escape) => {
150 // For taxonomy endpoints, display only the formatted value to avoid duplication
151 if (taxonomyEndpoint) {
152 return `<div>${escape(data.value)}</div>`;
153 }
154 // Avoid showing "value (value)" when value and text are identical.
155 if (data.text === data.value) {
156 return `<div>${escape(data.value)}</div>`;
157 }
158 return `<div>${escape(data.text)} (${escape(data.value)})</div>`;
159 },
160 item: (data, escape) => {
161 // For taxonomy endpoints, display only the formatted value to avoid duplication
162 if (taxonomyEndpoint) {
163 return `<div>${escape(data.value)}</div>`;
164 }
165 // Avoid showing "value (value)" when value and text are identical.
166 if (data.text === data.value) {
167 return `<div>${escape(data.value)}</div>`;
168 }
169 return `<div>${escape(data.text)} (${escape(data.value)})</div>`;
170 }
171 },
172 load: function (query, callback) {
173 if (!query.length) {
174 callback();
175 return;
176 }
177
178 // Build base payload.
179 const payload = {
180 action: action,
181 nonce: nonce,
182 q: query,
183 endpoint: endpoint
184 };
185
186 // Optional: harvest sibling fields from the closest repeater
187 // row and merge them into the payload. Declared on the input
188 // as data-wp-extra-fields='{"row_pat":"pat","row_id":"row_id"}'
189 // — meaning send the closest row's `[pat]` input as `row_pat`
190 // and `[row_id]` as `row_id`.
191 const extraAttr = element.getAttribute('data-wp-extra-fields');
192 if (extraAttr) {
193 try {
194 const map = JSON.parse(extraAttr);
195 const row = element.closest('.wz-repeater-item');
196 if (row && map && typeof map === 'object') {
197 Object.keys(map).forEach(function (key) {
198 const suffix = map[key];
199 if (!suffix) {
200 return;
201 }
202 const sibling = row.querySelector('[name$="[' + suffix + ']"]');
203 if (sibling && typeof sibling.value === 'string') {
204 payload[key] = sibling.value;
205 }
206 });
207 }
208 } catch (e) {
209 console.error('Error parsing data-wp-extra-fields:', extraAttr, e);
210 }
211 }
212
213 $.ajax({
214 url: ajaxurl,
215 type: 'POST',
216 dataType: 'json',
217 data: payload,
218 error: function () {
219 callback();
220 },
221 success: function (res) {
222 if (res.success && res.data && Array.isArray(res.data.items)) {
223 callback(
224 res.data.items
225 .map(normalizeOption)
226 .filter(Boolean)
227 );
228 } else if (res.success && Array.isArray(res.data)) {
229 callback(
230 res.data
231 .map(normalizeOption)
232 .filter(Boolean)
233 );
234 } else {
235 callback();
236 }
237 }
238 });
239 }
240 };
241
242 // Merge default config with custom config
243 const finalConfig = { ...defaultConfig, ...customConfig };
244
245 // Initialize Tom Select with merged config
246 try {
247 new TomSelect(element, finalConfig);
248 } catch (error) {
249 console.error('Tom Select initialization error:', error);
250 }
251 });
252 }
253
254 window.WebberInitTomSelect = initTomSelect;
255
256 document.addEventListener('wz:repeater-item-added', function (event) {
257 const detail = event && event.detail ? event.detail : {};
258 initTomSelect(detail.container || document);
259 });
260
261 $(document).ready(function () {
262 initTomSelect(document);
263 });
264 })(jQuery);
265