@@ -119,6 +119,33 @@ function setMXHandlers(http, element) {
119
119
} ;
120
120
}
121
121
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
+
122
149
function invokeFormPost ( containingForm , triggerEvent , httpMethodAttribute ) {
123
150
const http = setupHttpRequest ( containingForm , httpMethodAttribute ) ;
124
151
setMXHeaders ( http , containingForm ) ;
@@ -142,6 +169,9 @@ function invokeFormPost(containingForm, triggerEvent, httpMethodAttribute) {
142
169
formData . append ( key , value ) ;
143
170
} ) ;
144
171
172
+ const targetElement = establishTargetElement ( containingForm ) ;
173
+ handleMxDuringRequest ( containingForm , targetElement ) ;
174
+
145
175
http . onload = function ( ) {
146
176
attemptHideLoader ( containingForm ) ;
147
177
if ( http . status >= 200 && http . status < 300 ) {
@@ -166,6 +196,9 @@ function invokeHttpRequest(element, httpMethodAttribute) {
166
196
setMXHeaders ( http , element ) ;
167
197
setMXHandlers ( http , element ) ;
168
198
199
+ const targetElement = establishTargetElement ( element ) ;
200
+ handleMxDuringRequest ( element , targetElement ) ;
201
+
169
202
let formData = new FormData ( ) ;
170
203
const mxValsStr = element . getAttribute ( 'mx-vals' ) ;
171
204
if ( mxValsStr ) {
@@ -222,23 +255,16 @@ function mxSubmitForm(element, triggerEvent, httpMethodAttribute) {
222
255
return ;
223
256
}
224
257
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 ) ;
229
260
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' ] ;
231
263
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 ) ;
240
266
} else {
241
- console . log ( 'no submit button found' ) ;
267
+ initInvokeHttpRequest ( containingForm , httpMethodAttribute ) ;
242
268
}
243
269
}
244
270
@@ -362,6 +388,10 @@ function populateTargetEl(targetEl, http, element) {
362
388
363
389
// Attempt add modal buttons
364
390
attemptAddModalButtons ( targetEl , element ) ;
391
+
392
+ // Execute after-swap function if specified
393
+ executeAfterSwap ( element ) ;
394
+
365
395
} catch ( error ) {
366
396
console . error ( 'Error in populateTargetEl:' , error ) ;
367
397
} finally {
@@ -371,6 +401,24 @@ function populateTargetEl(targetEl, http, element) {
371
401
}
372
402
}
373
403
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
+
374
422
function handleMainSwaps ( targetEl , tempFragment , selectStr , mxSwapStr ) {
375
423
let contents = selectStr ? tempFragment . querySelectorAll ( selectStr ) : [ tempFragment . firstChild ] ;
376
424
contents . forEach ( content => {
@@ -564,48 +612,65 @@ function attemptAddModalButtons(targetEl, element) {
564
612
}
565
613
}
566
614
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
+
567
644
function handleHttpResponse ( http , element ) {
568
- const containingForm = element . closest ( 'form' ) ;
569
645
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' ) ;
574
651
}
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
+ } ) ;
576
668
577
669
element . classList . remove ( 'blink' ) ;
578
670
579
671
if ( http . status >= 200 && http . status < 300 ) {
580
672
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 ) ;
609
674
610
675
if ( targetEl ) {
611
676
// Check to see if we are required to do a success animation.
@@ -641,6 +706,7 @@ function handleHttpResponse(http, element) {
641
706
console . error ( 'An error occurred' ) ;
642
707
}
643
708
709
+ const containingForm = element . closest ( 'form' ) ;
644
710
if ( containingForm ) {
645
711
attemptDisplayValidationErrors ( http , element , containingForm ) ;
646
712
}
0 commit comments