PluginProbe
WebberZone Top 10 — Popular Posts / 4.3.2
WebberZone Top 10 — Popular Posts v4.3.2
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.3.2, at includes/admin/settings/js/tom-select-init.js

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