Skip to content
This repository was archived by the owner on Oct 20, 2025. It is now read-only.

Commit 5ac3bd4

Browse files
committed
Submit form in background
1 parent a371aeb commit 5ac3bd4

File tree

7 files changed

+127
-8
lines changed

7 files changed

+127
-8
lines changed

app/app/Http/Controllers/TwoFieldsFormController.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ public function __invoke(Request $request)
1313
'email' => ['required', 'email'],
1414
]);
1515

16+
if ($request->query('redirect')) {
17+
return redirect()->route('form.simple');
18+
}
19+
1620
return redirect()->back();
1721
}
1822
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
@extends('layout')
2+
3+
@section('content')
4+
5+
FormBackground
6+
7+
<x-splade-form dusk="form-background" action="/form/twoFields?redirect=1" submit-on-change background debounce="500">
8+
<input v-model="form.name" dusk="name" />
9+
<p v-text="form.errors.name" />
10+
11+
<input v-model="form.email" dusk="email" />
12+
<p v-text="form.errors.email" />
13+
14+
<p v-if="form.processingInBackground">Form is processing</p>
15+
<p v-if="form.wasUnsuccessful">Form was unsuccessful</p>
16+
<p v-if="form.recentlyUnsuccessful">Form recently unsuccessful</p>
17+
<p v-if="form.wasSuccessful">Form was successful</p>
18+
<p v-if="form.recentlySuccessful">Form recently successful</p>
19+
20+
<x-splade-submit />
21+
</x-splade-form>
22+
23+
@endsection

app/routes/web.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
Route::post('form/twoFields', TwoFieldsFormController::class)->name('form.twoFields.submit');
104104

105105
Route::view('form/submitOnChange', 'form.submitOnChange')->name('form.submitOnChange');
106+
Route::view('form/background', 'form.background')->name('form.background');
106107

107108
Route::view('form/confirm', 'form.confirm')->name('form.confirm');
108109
Route::view('form/customConfirm', 'form.customConfirm')->name('form.customConfirm');

app/tests/Browser/Form/SubmitOnChangeTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,28 @@ public function it_can_submit_the_form_by_watching_an_array_of_attributes()
6767
});
6868
});
6969
}
70+
71+
/** @test */
72+
public function it_can_submit_the_form_by_in_the_background()
73+
{
74+
$this->browse(function (Browser $browser) {
75+
$browser->visit('/form/background')
76+
->waitForText('FormBackground')
77+
->within('@form-background', function (Browser $browser) {
78+
$browser->type('@name', 'Name')
79+
->waitForText('Form is processing')
80+
->waitForText('The email field is required.')
81+
->waitForText('Form was unsuccessful')
82+
->assertDontSee('Form is processing')
83+
->type('@email', 'e')
84+
->waitForText('The email must be a valid email address.')
85+
->type('@email', '[email protected]')
86+
->waitForText('Form was successful')
87+
->assertDontSee('Form was unsuccessful')
88+
->assertDontSee('Form recently unsuccessful')
89+
->press('Submit')
90+
->waitForRoute('form.simple');
91+
});
92+
});
93+
}
7094
}

lib/Components/Form.vue

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script>
22
import { objectToFormData } from "./FormHelpers.js";
33
import { Splade } from "../Splade.js";
4+
import debounce from "lodash-es/debounce";
45
import find from "lodash-es/find";
56
import get from "lodash-es/get";
67
import has from "lodash-es/has";
@@ -77,10 +78,16 @@ export default {
7778
default: false,
7879
},
7980
81+
background: {
82+
type: Boolean,
83+
required: false,
84+
default: false
85+
},
86+
8087
stay: {
8188
type: Boolean,
8289
require: false,
83-
default: false
90+
default: false,
8491
},
8592
8693
restoreOnSuccess: {
@@ -117,7 +124,13 @@ export default {
117124
type: Boolean,
118125
required: false,
119126
default: false
120-
}
127+
},
128+
129+
debounce: {
130+
type: Number,
131+
required: false,
132+
default: 0
133+
},
121134
},
122135
123136
emits: ["success", "error", "reset", "restored"],
@@ -128,12 +141,17 @@ export default {
128141
missingAttributes: [],
129142
values: Object.assign({}, { ...this.default }),
130143
processing: false,
144+
processingInBackground: false,
131145
wasSuccessful: false,
132146
recentlySuccessful: false,
133147
recentlySuccessfulTimeoutId: null,
148+
wasUnsuccessful: false,
149+
recentlyUnsuccessful: false,
150+
recentlyUnsuccessfulTimeoutId: null,
134151
formElement: null,
135152
elementsUploading: [],
136153
fileponds: {},
154+
debounceFunction: null,
137155
};
138156
},
139157
@@ -164,6 +182,12 @@ export default {
164182
},
165183
},
166184
185+
created() {
186+
this.debounceFunction = debounce(() => {
187+
this.request(this.background);
188+
}, this.debounce);
189+
},
190+
167191
/*
168192
* It loops through all missing attributes and tries to
169193
* find a sensible default for that attribute.
@@ -198,12 +222,20 @@ export default {
198222
// Create watchers
199223
if(this.submitOnChange === true) {
200224
this.$watch("values", () => {
201-
this.$nextTick(() => this.request());
225+
if(this.background) {
226+
this.processingInBackground = true;
227+
}
228+
229+
this.$nextTick(() => this.debounce ? this.debounceFunction() : this.request(this.background));
202230
}, { deep: true });
203231
}else if(isArray(this.submitOnChange)) {
204232
this.submitOnChange.forEach((key) => {
205233
this.$watch(`values.${key}`, () => {
206-
this.$nextTick(() => this.request());
234+
if(this.background) {
235+
this.processingInBackground = true;
236+
}
237+
238+
this.$nextTick(() => this.debounce ? this.debounceFunction() : this.request(this.background));
207239
}, { deep: true });
208240
});
209241
}
@@ -379,25 +411,35 @@ export default {
379411
* Maps the values into a FormData instance and then
380412
* performs an async request.
381413
*/
382-
async request() {
414+
async request(forceStay) {
415+
if(typeof forceStay === "undefined") {
416+
forceStay = false;
417+
}
418+
383419
if(this.$uploading) {
384420
return;
385421
}
386422
387423
await this.$nextTick();
388424
389-
this.processing = true;
425+
if(this.background) {
426+
this.processingInBackground = true;
427+
} else {
428+
this.processing = true;
429+
}
430+
390431
this.wasSuccessful = false;
391432
this.recentlySuccessful = false;
392433
clearTimeout(this.recentlySuccessfulTimeoutId);
434+
clearTimeout(this.recentlyUnsuccessfulTimeoutId);
393435
394436
const data = (this.values instanceof FormData)
395437
? this.values
396438
: objectToFormData(this.values);
397439
398440
const headers = { Accept: "application/json" };
399441
400-
if(this.stay) {
442+
if(this.stay || forceStay) {
401443
headers["X-Splade-Prevent-Refresh"] = true;
402444
}
403445
@@ -424,6 +466,11 @@ export default {
424466
}
425467
426468
this.processing = false;
469+
this.processingInBackground = false;
470+
471+
this.wasUnsuccessful = false;
472+
this.recentlyUnsuccessful = false;
473+
427474
this.wasSuccessful = true;
428475
this.recentlySuccessful = true;
429476
this.recentlySuccessfulTimeoutId = setTimeout(() => this.recentlySuccessful = false, 2000);
@@ -437,6 +484,15 @@ export default {
437484
.then(successCallback)
438485
.catch(async (error) => {
439486
this.processing = false;
487+
this.processingInBackground = false;
488+
489+
this.wasSuccessful = false;
490+
this.recentlySuccessful = false;
491+
492+
this.wasUnsuccessful = true;
493+
this.recentlyUnsuccessful = true;
494+
this.recentlyUnsuccessfulTimeoutId = setTimeout(() => this.recentlyUnsuccessful = false, 2000);
495+
440496
this.$emit("error", error);
441497
442498
if(!this.scrollOnError) {
@@ -476,7 +532,6 @@ export default {
476532
"$put",
477533
"$startUploading",
478534
"$stopUploading",
479-
"$processing",
480535
"$uploading",
481536
"$errorAttributes",
482537
"$registerFilepond",
@@ -488,10 +543,13 @@ export default {
488543
"reset",
489544
"hasError",
490545
"processing",
546+
"processingInBackground",
491547
"rawErrors",
492548
"submit",
493549
"wasSuccessful",
494550
"recentlySuccessful",
551+
"wasUnsuccessful",
552+
"recentlyUnsuccessful",
495553
];
496554
497555
if(preservedKeys.includes(name)) {

resources/views/functional/form.blade.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
:submit-on-change="@js($submitOnChange)"
88
:escape-validation-messages="@js($escapeValidationMessages)"
99
:preserve-scroll="@js($preserveScroll)"
10+
:background="@js($background)"
11+
:debounce="@js($debounce)"
1012
>
1113
<template #default="{!! $scope !!}">
1214
<form data-splade-id="{{ $spladeId }}" v-bind="form.$attrs" @submit.prevent="form.submit" {{ $attributes->only(['action', 'method'])->merge(['method' => 'POST']) }}>

src/Components/Form.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ public function __construct(
5151
public bool $scrollOnError = true,
5252
public array|bool|string $submitOnChange = false,
5353
public bool $preserveScroll = false,
54+
public bool $background = false,
55+
public int $debounce = 0,
5456
) {
5557
// We'll use this instance in the static 'selected()' method,
5658
// which is a workaround for a Vue bug. Later, when the
@@ -71,6 +73,11 @@ public function __construct(
7173
if (is_string($submitOnChange)) {
7274
$this->submitOnChange = static::splitByComma($submitOnChange);
7375
}
76+
77+
if ($background) {
78+
$this->scrollOnError = false;
79+
$this->preserveScroll = true;
80+
}
7481
}
7582

7683
/**

0 commit comments

Comments
 (0)