PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.14.2
Independent Analytics – WordPress Analytics Plugin v2.14.2
2.15.0 2.14.10 trunk 1.1 1.10 1.10.1 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.17.1 1.17.2 1.17.3 1.17.4 1.18 1.18.1 1.19.0 1.19.1 1.2 1.20.0 1.21.0 1.22.0 1.22.1 1.23.0 1.23.1 1.24.0 1.24.1 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.28.1 1.28.2 1.28.3 1.29.0 1.3 1.30.0 1.30.1 1.4 1.5 1.6 1.7 1.8 1.9 2.0.0 2.0.1 2.1.4 2.1.5 2.1.6 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.10 2.11.2 2.11.3 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.11.9 2.12.0 2.12.1 2.12.2 2.13.1 2.13.2 2.13.5 2.13.6 2.14.0 2.14.1 2.14.2 2.14.4 2.14.6 2.14.7 2.14.8 2.14.9 2.2.0 2.2.1 2.3.1 2.3.2 2.4.2 2.4.3 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.7.1 2.7.2 2.7.3 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7
independent-analytics / javascript-source / controllers / overview / module_editor.js
independent-analytics / javascript-source / controllers / overview Last commit date
add_module.js 1 year ago checkbox_group.js 1 year ago module.js 1 year ago module_editor.js 1 year ago module_list.js 1 year ago module_picker.js 1 year ago reorder_modules.js 1 year ago
module_editor.js
455 lines
1 import {Controller} from "@hotwired/stimulus"
2
3 export default class extends Controller {
4 static targets = ['saveButton', 'cancelButton']
5
6 static values = {
7 reports: Array,
8 moduleId: String,
9 moduleToSwap: String
10 }
11
12 cachedModuleMarkup = null
13
14 connect() {
15 // React to changes in the selected report
16 if (this.element.querySelector('#report')) {
17 this.element.querySelector('#report').addEventListener('change', (event) => this.updateReport(event))
18 }
19
20 // Autofocus on the correct input. Since autofocus only works on page load, this needs to be
21 // done manually when editing an existing module.
22 this.element.querySelector('[autofocus]')?.focus()
23
24 // Cancel editing if escape is pressed
25 document.addEventListener('keydown', this.onKeydown)
26
27 if (this.moduleIdValue) {
28 this.cacheModuleMarkup()
29 }
30
31 this.signalEditingStarted()
32 }
33
34 disconnect() {
35 document.removeEventListener('keydown', this.onKeydown)
36 this.signalEditingFinished()
37 }
38
39 isEditingExistingModule() {
40 return !!this.moduleIdValue
41 }
42
43 cancel() {
44 if(this.cancelButtonTarget) {
45 this.cancelButtonTarget.disabled = true
46 }
47
48 if (this.isSwapping()) {
49 this.element.previousSibling.classList.remove('hidden')
50 this.element.remove()
51 return
52 }
53
54 if (!this.isEditingExistingModule()) {
55 this.signalEditingFinished()
56 this.element.outerHTML = document.getElementById('module-picker-template').innerHTML
57 } else if (this.cachedModuleMarkup) {
58 this.signalEditingFinished()
59 this.element.outerHTML = this.cachedModuleMarkup
60 } else {
61 this.cancelEditingExistingModule()
62 }
63 }
64
65 changeModuleType() {
66 const templateString = document.getElementById('module-picker-template').innerHTML
67 const templateDocument = new DOMParser().parseFromString(templateString, "text/html");
68 const modulePicker = templateDocument.body.firstElementChild
69
70 modulePicker.classList.remove('show-intro')
71 modulePicker.classList.add('show-list')
72 modulePicker.setAttribute('data-module-picker-module-to-swap-value', this.moduleIdValue)
73
74 this.element.classList.add('hidden')
75 this.element.insertAdjacentElement('afterend', modulePicker)
76 }
77
78 cacheModuleMarkup() {
79 const data = {
80 ...iawpActions.get_markup_for_module,
81 id: this.moduleIdValue
82 }
83
84 jQuery.post(ajaxurl, data, (response) => {
85 this.cachedModuleMarkup = response.data.module_html
86 })
87 }
88
89 cancelEditingExistingModule() {
90 const data = {
91 ...iawpActions.get_markup_for_module,
92 id: this.moduleIdValue
93 }
94
95 jQuery.post(ajaxurl, data, (response) => {
96 this.signalEditingFinished()
97 this.element.outerHTML = response.data.module_html
98 }).fail((error) => {
99 //
100 })
101 }
102
103 onKeydown = (event) => {
104 // Ignore non-escape keys
105 if (event.key !== "Escape") {
106 return;
107 }
108
109 // Cancel if the active element is in the editor
110 if (this.element.contains(document.activeElement)) {
111 this.cancel()
112 }
113 }
114
115 updateReport(event) {
116 const newReportId = event.target.value
117 const report = this.reportsValue.find((report) => report.id === newReportId || report.id === parseInt(newReportId))
118
119 if (report) {
120 this.updateGroupSelect(report.columns, this.element.querySelector('#sort_by'))
121 this.updateGroupSelect(report.aggregatable_columns, this.element.querySelector('#aggregatable_sort_by'))
122 this.updateGroupSelect(report.statistics, this.element.querySelector('#primary_metric'))
123 this.updateGroupSelect(report.statistics, this.element.querySelector('#secondary_metric'), true)
124 this.updateGroupCheckboxes(report.statistics, this.element.querySelector('#statistics'))
125 }
126 }
127
128 save(event) {
129 event.preventDefault()
130
131 if (this.isEditingExistingModule()) {
132 this.updateExistingModule(event)
133 } else {
134 this.saveNewModule(event)
135 }
136 }
137
138 updateExistingModule(event) {
139 const data = {
140 ...iawpActions.edit_module,
141 module_id: this.moduleIdValue,
142 fields: this.getFieldValues(event.target),
143 }
144
145 this.saveButtonTarget.setAttribute('disabled', 'disabled')
146 this.saveButtonTarget.classList.add('sending')
147
148 jQuery.post(ajaxurl, data, (response) => {
149 this.saveButtonTarget.classList.remove('sending')
150 this.saveButtonTarget.classList.add('sent')
151 setTimeout((controller) => {
152 controller.saveButtonTarget.removeAttribute('disabled')
153 controller.saveButtonTarget.classList.remove('sent')
154 controller.element.outerHTML = response.data.module_html
155 }, 500, this)
156 }).fail((error) => {
157 this.saveButtonTarget.removeAttribute('disabled')
158 this.saveButtonTarget.classList.remove('sending')
159 })
160 }
161
162 saveNewModule(event) {
163 const data = {
164 ...iawpActions.save_module,
165 module: this.getFieldValues(event.target),
166 moduleToSwap: this.moduleToSwapValue,
167 }
168
169 this.saveButtonTarget.setAttribute('disabled', 'disabled')
170 this.saveButtonTarget.classList.add('sending')
171
172 jQuery.post(ajaxurl, data, (response) => {
173 this.saveButtonTarget.classList.remove('sending')
174 this.saveButtonTarget.classList.add('sent')
175 setTimeout(() => {
176 this.saveButtonTarget.removeAttribute('disabled')
177 this.saveButtonTarget.classList.remove('sent')
178
179 if(this.isSwapping()) {
180 this.element.previousSibling.remove()
181 this.element.insertAdjacentHTML("beforebegin", response.data.module_html)
182 this.element.remove()
183 } else {
184 this.element.insertAdjacentHTML("beforebegin", response.data.module_html)
185 this.cancel()
186 }
187
188 // this.element.insertAdjacentHTML("beforebegin", response.data.module_html)
189 }, 500, this)
190 }).fail((error) => {
191 this.saveButtonTarget.removeAttribute('disabled')
192 this.saveButtonTarget.classList.remove('sending')
193 })
194 }
195
196 getFieldValues(formElement) {
197 const fields = {}
198 const formElements = Array.from(formElement.elements)
199
200 formElements.forEach((element) => {
201 if (element.tagName === 'BUTTON' || (element.tagName === 'INPUT' && ['button', 'submit'].includes(element.type))) {
202 // Skip buttons
203 } else if (element.type === 'radio') {
204 // Special case for radio buttons
205 if (element.checked) {
206 fields[element.name] = element.value
207 }
208 } else if (element.type === 'checkbox') {
209 // Special case for checkboxes
210 if (!fields[element.name]) {
211 fields[element.name] = []
212 }
213 if (element.checked) {
214 fields[element.name].push(element.value)
215 }
216 } else {
217 // Everything else
218 fields[element.name] = element.value
219 }
220 })
221
222 return fields
223 }
224
225 // /**
226 // * Rebuild the options for the sort by select box after the report is changed
227 // *
228 // * @param data
229 // * @param selectElement
230 // */
231 // updateSelect(data, selectElement) {
232 // if (!selectElement) {
233 // return
234 // }
235 //
236 // const previousValue = selectElement.value
237 //
238 // selectElement.innerHTML = ''
239 //
240 // data.forEach(([id, name]) => {
241 // const option = document.createElement('option')
242 // option.setAttribute('value', id)
243 // option.innerText = name
244 // selectElement.append(option)
245 // })
246 //
247 // if (data.some(([id, name]) => id === previousValue)) {
248 // selectElement.value = previousValue
249 // }
250 // }
251
252 /**
253 * Rebuild the options for the sort by select box after the report is changed
254 *
255 * @param data
256 * @param selectElement
257 */
258 updateGroupSelect(data, selectElement, addNoComparisonOption = false) {
259 if (!selectElement) {
260 return
261 }
262
263 const previousValue = selectElement.value
264
265 selectElement.innerHTML = ''
266
267 if (addNoComparisonOption) {
268 const optionGroup = document.createElement('optgroup')
269 optionGroup.setAttribute('label', iawpText.noComparison)
270
271 const option = document.createElement('option')
272 option.setAttribute('value', 'no_comparison')
273 option.innerText = iawpText.noComparison
274 optionGroup.append(option)
275
276 selectElement.appendChild(optionGroup)
277 }
278
279 const groups = []
280 data.forEach(([id, name, group]) => {
281 if (!groups.includes(group)) {
282 groups.push(group)
283 }
284 })
285
286 groups.forEach((group) => {
287 const optionGroup = document.createElement('optgroup');
288 optionGroup.setAttribute('label', group)
289
290 data.filter(([id, name, groupName]) => {
291 return groupName === group
292 }).forEach(([id, name]) => {
293 const option = document.createElement('option')
294 option.setAttribute('value', id)
295 option.innerText = name
296 optionGroup.append(option)
297 })
298
299 if (data.some(([id, name]) => id === previousValue)) {
300 selectElement.value = previousValue
301 }
302
303 selectElement.append(optionGroup)
304 })
305 }
306
307 // /**
308 // * Rebuild the statistics checkboxes after the report is changed
309 // *
310 // * @param data
311 // * @param container
312 // */
313 // updateCheckboxes(data, container) {
314 // if (!container) {
315 // return
316 // }
317 //
318 // const previousName = container.querySelector('input').name
319 // const previousValues = Array.from(container.querySelectorAll('input:checked')).map((input) => input.value)
320 //
321 // container.innerHTML = ''
322 //
323 // data.forEach(([id, name], index) => {
324 // // Create the label element
325 // const label = document.createElement('label')
326 //
327 // // Create the checkbox element
328 // const checkbox = document.createElement('input')
329 // checkbox.type = 'checkbox'
330 // checkbox.name = previousName
331 // checkbox.value = id
332 // checkbox.checked = previousValues.includes(id) || index === 0
333 //
334 // // Create the text node
335 // const text = document.createTextNode(' ' + name)
336 //
337 // // Append the checkbox and text to the label
338 // label.appendChild(checkbox)
339 // label.appendChild(text)
340 //
341 // container.append(label)
342 // })
343 // }
344
345 /**
346 * Rebuild the statistics checkboxes after the report is changed
347 *
348 * @param data
349 * @param container
350 */
351 updateGroupCheckboxes(data, container) {
352 if (!container) {
353 return
354 }
355
356 const previousName = container.querySelector('input').name
357 const previousValues = Array.from(container.querySelectorAll('input:checked')).map((input) => input.value)
358
359 container.innerHTML = ''
360
361 const checkboxGroup = document.createElement('div')
362 checkboxGroup.classList.add('checkbox-group-container')
363 checkboxGroup.dataset.controller = 'checkbox-group'
364
365 const tabContainer = document.createElement('div')
366 tabContainer.classList.add('tab-container')
367 const addedTabs = []
368
369 data.forEach(([id, name, group]) => {
370 if (!addedTabs.includes(group)) {
371 const tab = document.createElement('button')
372 tab.setAttribute('type', 'button')
373 tab.classList.add('checkbox-group-tab')
374 tab.dataset.groupName = group
375 tab.dataset.checkboxGroupTarget = 'groupTab'
376 tab.dataset.action = 'checkbox-group#changeTab'
377 tab.innerText = group
378
379 // First tab should be selected
380 if (addedTabs.length === 0) {
381 tab.classList.add('selected')
382 }
383
384 tabContainer.appendChild(tab)
385 tabContainer.append(' ')
386 addedTabs.push(group)
387 }
388 })
389
390 checkboxGroup.appendChild(tabContainer)
391
392
393 addedTabs.forEach((group, index) => {
394 const groupData = data.filter(([id, name, groupName]) => group === groupName)
395 const groupContainer = document.createElement('div');
396 groupContainer.dataset.checkboxGroupTarget = 'group'
397 groupContainer.dataset.groupName = group
398 groupContainer.classList.add('checkbox-group')
399
400 if (index === 0) {
401 groupContainer.classList.add('selected')
402 }
403
404 groupData.forEach(([id, name], index) => {
405 // Create the label element
406 const label = document.createElement('label')
407
408 // Create the checkbox element
409 const checkbox = document.createElement('input')
410 checkbox.type = 'checkbox'
411 checkbox.name = previousName
412 checkbox.value = id
413 checkbox.checked = previousValues.includes(id) || index === 0
414
415 // Create the text node
416 const text = document.createTextNode(' ' + name)
417
418 // Append the checkbox and text to the label
419 label.appendChild(checkbox)
420 label.appendChild(text)
421
422 groupContainer.appendChild(label)
423 })
424
425 checkboxGroup.appendChild(groupContainer)
426 })
427
428 container.appendChild(checkboxGroup)
429 }
430
431 signalEditingStarted() {
432 document.dispatchEvent(
433 new CustomEvent('iawp:moduleEditingStarted', {
434 detail: {
435 moduleId: this.moduleIdValue
436 }
437 })
438 )
439 }
440
441 signalEditingFinished() {
442 document.dispatchEvent(
443 new CustomEvent('iawp:moduleEditingFinished', {
444 detail: {
445 moduleId: this.moduleIdValue
446 }
447 })
448 )
449 }
450
451
452 isSwapping() {
453 return !!this.moduleToSwapValue
454 }
455 }