Skip to content

Commit 9934d0e

Browse files
author
David Connelly
committed
Introduced 'mx-during-request' and 'mx-after-swap' attributes to enhance user experience in Trongate MX. The 'mx-during-request' controls the appearance of elements during AJAX requests, while 'mx-after-swap' allows executing JavaScript functions after content swaps. Refactored app.js to add a feature that closes open modals when clicking outside of them (which was surprisingly tricky!). Special thanks to our very own Godfather of Speed Coding, Dafa, for fixing the recent glitch with the form_submit() helper method. Everything's smooth and running perfectly now!
1 parent 4fe2e54 commit 9934d0e

File tree

4 files changed

+116
-50
lines changed

4 files changed

+116
-50
lines changed

engine/license.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* An open source PHP framework for web developers who like to break the rules
55
*
6-
* Version: 1.3.3055
6+
* Version: 1.3.3056
77
*
88
* This product is released under the MIT License (MIT)
99
*

license.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* An open source PHP framework for web developers who like to break the rules
55
*
6-
* Version: 1.3.3055
6+
* Version: 1.3.3056
77
*
88
* This product is released under the MIT License (MIT)
99
*

public/js/app.js

100755100644
File mode changed.

public/js/trongate-mx.js

+114-48
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,33 @@ function setMXHandlers(http, element) {
119119
};
120120
}
121121

122+
function handleMxDuringRequest(element, targetElement) {
123+
const mxDuringRequest = element.getAttribute('mx-during-request');
124+
if (mxDuringRequest) {
125+
if (mxDuringRequest === 'cloak') {
126+
targetElement.style.setProperty('opacity', '0', 'important');
127+
targetElement.classList.add('mx-cloak');
128+
} else {
129+
const placeholder = document.querySelector(mxDuringRequest);
130+
if (placeholder) {
131+
targetElement.dataset.mxOriginalContent = targetElement.innerHTML;
132+
targetElement.innerHTML = placeholder.innerHTML;
133+
targetElement.classList.add('mx-placeholder-active');
134+
}
135+
}
136+
}
137+
138+
// Check if the original element is a form and disable its elements
139+
if (element.tagName.toLowerCase() === 'form') {
140+
Array.from(element.elements).forEach(formElement => {
141+
if (!formElement.disabled) {
142+
formElement.disabled = true;
143+
formElement.classList.add('mx-temp-disabled');
144+
}
145+
});
146+
}
147+
}
148+
122149
function invokeFormPost(containingForm, triggerEvent, httpMethodAttribute) {
123150
const http = setupHttpRequest(containingForm, httpMethodAttribute);
124151
setMXHeaders(http, containingForm);
@@ -142,6 +169,9 @@ function invokeFormPost(containingForm, triggerEvent, httpMethodAttribute) {
142169
formData.append(key, value);
143170
});
144171

172+
const targetElement = establishTargetElement(containingForm);
173+
handleMxDuringRequest(containingForm, targetElement);
174+
145175
http.onload = function() {
146176
attemptHideLoader(containingForm);
147177
if (http.status >= 200 && http.status < 300) {
@@ -166,6 +196,9 @@ function invokeHttpRequest(element, httpMethodAttribute) {
166196
setMXHeaders(http, element);
167197
setMXHandlers(http, element);
168198

199+
const targetElement = establishTargetElement(element);
200+
handleMxDuringRequest(element, targetElement);
201+
169202
let formData = new FormData();
170203
const mxValsStr = element.getAttribute('mx-vals');
171204
if (mxValsStr) {
@@ -222,23 +255,16 @@ function mxSubmitForm(element, triggerEvent, httpMethodAttribute) {
222255
return;
223256
}
224257

225-
const submitButton = containingForm.querySelector('button[type="submit"]');
226-
if (submitButton) {
227-
// Clear existing validation errors
228-
clearExistingValidationErrors(containingForm);
258+
// Clear existing validation errors
259+
clearExistingValidationErrors(containingForm);
229260

230-
submitButton.disabled = true; // Disable submit button
261+
// The following three attribute types require an attempt to collect form data.
262+
const requiresDataAttributes = ['mx-post', 'mx-put', 'mx-patch'];
231263

232-
// The following three attribute types require an attempt to collect form data.
233-
const requiresDataAttributes = ['mx-post', 'mx-put', 'mx-patch'];
234-
235-
if (requiresDataAttributes.includes(httpMethodAttribute)) {
236-
invokeFormPost(containingForm, triggerEvent, httpMethodAttribute);
237-
} else {
238-
initInvokeHttpRequest(containingForm, httpMethodAttribute);
239-
}
264+
if (requiresDataAttributes.includes(httpMethodAttribute)) {
265+
invokeFormPost(containingForm, triggerEvent, httpMethodAttribute);
240266
} else {
241-
console.log('no submit button found');
267+
initInvokeHttpRequest(containingForm, httpMethodAttribute);
242268
}
243269
}
244270

@@ -362,6 +388,10 @@ function populateTargetEl(targetEl, http, element) {
362388

363389
// Attempt add modal buttons
364390
attemptAddModalButtons(targetEl, element);
391+
392+
// Execute after-swap function if specified
393+
executeAfterSwap(element);
394+
365395
} catch (error) {
366396
console.error('Error in populateTargetEl:', error);
367397
} finally {
@@ -371,6 +401,24 @@ function populateTargetEl(targetEl, http, element) {
371401
}
372402
}
373403

404+
function executeAfterSwap(element) {
405+
const functionName = element.getAttribute('mx-after-swap');
406+
if (functionName) {
407+
// Remove parentheses if present
408+
const cleanFunctionName = functionName.replace(/\(\)$/, '');
409+
410+
if (typeof window[cleanFunctionName] === 'function') {
411+
try {
412+
window[cleanFunctionName]();
413+
} catch (error) {
414+
console.error(`Error executing ${cleanFunctionName}:`, error);
415+
}
416+
} else {
417+
console.warn(`Function ${cleanFunctionName} not found`);
418+
}
419+
}
420+
}
421+
374422
function handleMainSwaps(targetEl, tempFragment, selectStr, mxSwapStr) {
375423
let contents = selectStr ? tempFragment.querySelectorAll(selectStr) : [tempFragment.firstChild];
376424
contents.forEach(content => {
@@ -564,48 +612,65 @@ function attemptAddModalButtons(targetEl, element) {
564612
}
565613
}
566614

615+
function establishTargetElement(element) {
616+
const mxTargetStr = getAttributeValue(element, 'mx-target');
617+
618+
if (!mxTargetStr || mxTargetStr === 'this') {
619+
return element;
620+
}
621+
622+
if (mxTargetStr === 'none') {
623+
return null;
624+
}
625+
626+
if (mxTargetStr === 'body') {
627+
return document.body;
628+
}
629+
630+
if (mxTargetStr.startsWith('closest ')) {
631+
const selector = mxTargetStr.replace('closest ', '');
632+
return element.closest(selector);
633+
}
634+
635+
if (mxTargetStr.startsWith('find ')) {
636+
const selector = mxTargetStr.replace('find ', '');
637+
return element.querySelector(selector);
638+
}
639+
640+
// If a valid CSS selector is provided
641+
return document.querySelector(mxTargetStr);
642+
}
643+
567644
function handleHttpResponse(http, element) {
568-
const containingForm = element.closest('form');
569645

570-
if (containingForm) {
571-
const submitButton = containingForm.querySelector('button[type="submit"]');
572-
if (submitButton) {
573-
submitButton.removeAttribute('disabled');
646+
// Remove cloaking from all elements
647+
document.querySelectorAll('.mx-cloak').forEach(el => {
648+
el.classList.remove('mx-cloak');
649+
if (el.style.opacity === '0') {
650+
el.style.removeProperty('opacity');
574651
}
575-
}
652+
});
653+
654+
// Restore original content for elements with placeholders
655+
document.querySelectorAll('.mx-placeholder-active').forEach(el => {
656+
if (el.dataset.mxOriginalContent) {
657+
el.innerHTML = el.dataset.mxOriginalContent;
658+
delete el.dataset.mxOriginalContent;
659+
}
660+
el.classList.remove('mx-placeholder-active');
661+
});
662+
663+
// Re-enable temporarily disabled elements
664+
document.querySelectorAll('.mx-temp-disabled').forEach(element => {
665+
element.disabled = false;
666+
element.classList.remove('mx-temp-disabled');
667+
});
576668

577669
element.classList.remove('blink');
578670

579671
if (http.status >= 200 && http.status < 300) {
580672
if (http.getResponseHeader('Content-Type').includes('text/html')) {
581-
const mxTargetStr = getAttributeValue(element, 'mx-target');
582-
583-
let targetEl;
584-
585-
if (mxTargetStr === 'none') {
586-
// If mx-target is 'none', do not replace any content
587-
targetEl = null;
588-
} else if (mxTargetStr === 'this') {
589-
// Target the element that triggered the request
590-
targetEl = element;
591-
} else if (mxTargetStr && mxTargetStr.startsWith('closest ')) {
592-
// Find the closest ancestor matching the selector
593-
const selector = mxTargetStr.replace('closest ', '');
594-
targetEl = element.closest(selector);
595-
} else if (mxTargetStr && mxTargetStr.startsWith('find ')) {
596-
// Find the first descendant matching the selector
597-
const selector = mxTargetStr.replace('find ', '');
598-
targetEl = element.querySelector(selector);
599-
} else if (mxTargetStr === 'body') {
600-
// Target the body element
601-
targetEl = document.body;
602-
} else if (mxTargetStr) {
603-
// If a valid CSS selector is provided
604-
targetEl = document.querySelector(mxTargetStr);
605-
} else {
606-
// If no mx-target is specified, use the invoking element as the target
607-
targetEl = element;
608-
}
673+
const targetEl = establishTargetElement(element);
609674

610675
if (targetEl) {
611676
// Check to see if we are required to do a success animation.
@@ -641,6 +706,7 @@ function handleHttpResponse(http, element) {
641706
console.error('An error occurred');
642707
}
643708

709+
const containingForm = element.closest('form');
644710
if (containingForm) {
645711
attemptDisplayValidationErrors(http, element, containingForm);
646712
}

0 commit comments

Comments
 (0)