Skip to content

Commit 5967b45

Browse files
authored
v1.1 Release (#1)
1 parent 303bbe6 commit 5967b45

28 files changed

+279
-129
lines changed

content/globals/formatic.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
title: Formatic
1+
title: Formatic

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@alpinejs/focus": "^3.13.0",
1919
"alpinejs": "^3.13.0",
2020
"dropzone": "^6.0.0-beta.2",
21-
"flatpickr": "^4.6.13"
21+
"flatpickr": "^4.6.13",
22+
"laravel-precognition-alpine": "^0.5.2"
2223
}
2324
}

public/build/assets/site-6726e1ae.js

+26
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/build/assets/site-d811f542.css

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/build/manifest.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"resources/css/site.css": {
3-
"file": "assets/site-b3ba1d08.css",
3+
"file": "assets/site-d811f542.css",
44
"isEntry": true,
55
"src": "resources/css/site.css"
66
},
77
"resources/js/site.js": {
8-
"file": "assets/site-328ce870.js",
8+
"file": "assets/site-6726e1ae.js",
99
"isEntry": true,
1010
"src": "resources/js/site.js"
1111
}

resources/blueprints/forms/employment_application.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -259,4 +259,4 @@ tabs:
259259
listable: hidden
260260
instructions_position: above
261261
visibility: visible
262-
hide_display: false
262+
hide_display: false

resources/js/form/DatePicker.js

+24
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,29 @@ export const DatePicker = (config) => ({
1616
instance.altInput.id = config.id;
1717
},
1818
});
19+
20+
this.$watch("form.errors", () => {
21+
this.handleChange();
22+
});
23+
},
24+
25+
handleChange() {
26+
const instance = this.picker;
27+
const fieldName = instance.altInput.id;
28+
const errors = this.form.errors[fieldName];
29+
30+
instance.altInput.classList.remove(
31+
"border-red-600",
32+
"focus-visible:border-red-600"
33+
);
34+
35+
if (errors && errors.length > 0) {
36+
instance.altInput.classList.add(
37+
"border-red-600",
38+
"focus-visible:border-red-600"
39+
);
40+
}
41+
42+
this.form.validate(fieldName);
1943
},
2044
});

resources/js/form/FileViewer.js

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ export const FileViewer = () => ({
1010
size: this.formatBytes(file.size),
1111
};
1212
});
13+
14+
this.form[event.target.id] = Object.values(event.target.files);
15+
this.form.validate(event.target.id);
1316
},
1417
formatBytes(x) {
1518
let l = 0,

resources/js/form/Formatic.js

+81-64
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
export const Formatic = ({ validate_url }) => ({
2-
error: false,
3-
errors: [],
4-
sending: false,
5-
success: false,
1+
export const Formatic = () => ({
62
steps: [],
73
currentStep: 1,
84
redirect_url: null,
95
submit_url: null,
6+
success: false,
7+
form: "",
8+
109
init() {
1110
this.$nextTick(() => {
1211
if (
@@ -19,74 +18,92 @@ export const Formatic = ({ validate_url }) => ({
1918
this.steps = JSON.parse(window.requiredSteps);
2019
});
2120

22-
this.submit_url = validate_url;
21+
this.submit_url = window.location.href;
22+
23+
this.form = this.$form(
24+
"post",
25+
this.$refs.form.getAttribute("action"),
26+
JSON.parse(this.$refs.form.getAttribute("x-data")).dynamic_form
27+
);
2328
},
24-
sendForm: async function () {
25-
this.sending = true;
26-
const formData = new FormData(this.$refs.form);
27-
28-
// Post the form.
29-
return fetch(this.resolveFormUrl(), {
30-
headers: {
31-
"X-Requested-With": "XMLHttpRequest",
32-
},
33-
method: "POST",
34-
body: new FormData(this.$refs.form),
35-
})
36-
.then((res) => res.json())
37-
.then((json) => {
38-
if (
39-
json["success"] &&
40-
this.isMultiStep() &&
41-
!this.isLastStep()
42-
) {
43-
this.errors = [];
44-
this.error = false;
45-
this.sending = false;
46-
47-
return;
48-
}
49-
50-
if (json["success"]) {
51-
this.errors = [];
52-
this.success = true;
53-
this.error = false;
54-
this.sending = false;
55-
this.$refs.form.reset();
56-
57-
setTimeout(() => {
58-
this.success = false;
59-
60-
if (this.redirect_url) {
61-
window.location.href = this.redirect_url;
62-
}
63-
}, 1000);
64-
}
65-
66-
if (json["error"]) {
67-
this.sending = false;
68-
this.error = true;
69-
this.success = false;
70-
this.errors = json["error"];
71-
}
72-
})
73-
.catch((err) => {
74-
console.log("err", err);
75-
this.sending = false;
29+
30+
submit: async function () {
31+
try {
32+
const response = await this.form.submit({
33+
headers: { "Content-Type": "multipart/form-data" },
7634
});
35+
36+
if (
37+
response.status === 200 &&
38+
this.isMultiStep() &&
39+
!this.isLastStep()
40+
) {
41+
this.form.errors = {};
42+
43+
return;
44+
}
45+
46+
if (response.status === 200) {
47+
this.form.reset();
48+
this.form.errors = {};
49+
this.success = true;
50+
51+
setTimeout(() => {
52+
this.success = false;
53+
54+
if (this.redirect_url) {
55+
window.location.href = this.redirect_url;
56+
}
57+
58+
if (this.isMultiStep()) {
59+
location.reload();
60+
}
61+
}, 1000);
62+
}
63+
} catch (error) {
64+
if (error.response && error.response.data.errors) {
65+
this.form.errors = error.response.data.errors;
66+
}
67+
}
7768
},
69+
7870
prev() {
7971
this.currentStep--;
8072
},
8173
next: async function () {
82-
await this.sendForm();
74+
const isValid = this.validateFields();
75+
76+
// Move to the next step only if the form is valid
77+
if (isValid) {
78+
if (!(this.form.errors && this.hasRequiredFields())) {
79+
this.currentStep++;
80+
this.form.errors = {};
81+
}
8382

84-
if (!(this.error && this.hasRequiredFields())) {
85-
this.currentStep++;
86-
this.errors = [];
83+
window.scrollTo({ top: 0, behavior: "smooth" });
8784
}
85+
},
86+
validateFields() {
87+
this.form.errors = {};
88+
89+
const requiredFields = Object.values(this.steps[this.currentStep - 1]);
90+
91+
requiredFields.forEach((field) => {
92+
// Grab inputs
93+
const input = document.querySelector(`[name="${field}"]`);
94+
95+
// Format field's error message
96+
let formattedErrorMessage = `The ${field
97+
.replace(/_/g, " ")
98+
.replace(/\b\w/g, (c) => c.toUpperCase())} field is required.`;
99+
100+
// Add error to errors object (if input is empty)
101+
if (input && input.offsetParent && !this.form[field]) {
102+
this.form.errors[field] = formattedErrorMessage;
103+
}
104+
});
88105

89-
window.scrollTo({ top: 0, behavior: "smooth" });
106+
return !Object.keys(this.form.errors).length;
90107
},
91108
isMultiStep() {
92109
return this.steps.length > 0;
@@ -103,7 +120,7 @@ export const Formatic = ({ validate_url }) => ({
103120
},
104121
hasRequiredFields() {
105122
const fields = Object.values(this.steps[this.currentStep - 1]).filter(
106-
(field) => (this.errors.hasOwnProperty(field) ? true : false)
123+
(field) => (this.form.errors.hasOwnProperty(field) ? true : false)
107124
);
108125

109126
return fields.length > 0;

resources/js/site.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// This is all you.
22
import Alpine from "alpinejs";
33
import focus from "@alpinejs/focus";
4+
import precognition from "laravel-precognition-alpine";
45

56
import { ListInput } from "./form/ListInput";
67
import { DatePicker } from "./form/DatePicker";
@@ -11,7 +12,7 @@ import { Theme } from "./ui/Theme";
1112
window.Alpine = Alpine;
1213

1314
// Plugins
14-
Alpine.plugin(focus);
15+
Alpine.plugin([focus, precognition]);
1516

1617
// Form Components
1718
Alpine.data("formatic", Formatic);

resources/views/partials/sets/formatic.antlers.html

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<x-block form_size="{form_size}">
2-
<div x-data="formatic({ validate_url: '{{ route:forms.validate :form="selected_form:handle" }}' })">
3-
{{ form handle="{selected_form:handle}" js="alpine" x-ref="form" @submit.prevent="sendForm()" }}
2+
{{ form:create :in="selected_form:handle" js="alpine:dynamic_form" attr:x-ref="form" files="true" }}
3+
<div x-data="formatic()">
44
<template x-if="success">
55
{{ if submit_options === 'thankyou_page' }}
66
<x-alert class="mb-5">
@@ -28,15 +28,15 @@
2828
{{ /if }}
2929
{{ if required_fields_text }}
3030
<div class="my-4">
31-
<p class="text-destructive text-xs font-semibold">
31+
<p class="text-xs font-semibold text-destructive">
3232
{{ required_fields_text }}
3333
</p>
3434
</div>
3535
{{ /if }}
3636
{{ if !enable_multistep }}
3737
<div class="sm:col-span-12">
38-
<x-button x-bind:disabled="sending" type="submit">
39-
<template x-if="sending">
38+
<x-button x-bind:disabled="form.processing" @click.prevent="submit" type="submit">
39+
<template x-if="form.processing">
4040
<x-spinner />
4141
</template>
4242
Submit
@@ -46,6 +46,6 @@
4646
<input type="text" class="hidden" name="{{ honeypot ?? 'honeypot' }}">
4747
</div>
4848
</template>
49-
{{ /form }}
50-
</div>
49+
</div>
50+
{{ /form:create }}
5151
</x-block>

resources/views/partials/sets/formatic/_form_grid.antlers.html

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<div class="grid md:grid-cols-12 gap-4">
1+
<div class="grid gap-4 md:grid-cols-12">
22
{{ fields }}
33
{{ _widthClass = switch(
44
(width == '100') => 'sm:col-span-12',
@@ -30,8 +30,8 @@
3030
{{ instructions }}
3131
</p>
3232
{{ /if }}
33-
<template x-if="errors.{{ handle }}">
34-
<p class="text-destructive" x-text="errors.{{ handle }}"></p>
33+
<template x-if="form.invalid('{{ handle }}')">
34+
<p class="text-sm text-destructive" x-text="form.errors.{{ handle }}"></p>
3535
</template>
3636
</div>
3737
</template>

resources/views/partials/sets/formatic/_form_multistep.antlers.html

+19-8
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,27 @@
2929
</x-button>
3030
</template>
3131
32-
<x-button x-bind:class="currentStep === 1 && 'ml-auto'" x-bind:disabled="sending" type="button"
33-
@click="next()">
34-
<template x-if="sending">
35-
<x-spinner />
36-
</template>
37-
{{ last ? 'Submit' : 'Next' }}
38-
</x-button>
32+
<template x-if="!isLastStep()">
33+
<x-button x-bind:class="currentStep === 1 && 'ml-auto'" x-bind:disabled="form.processing"
34+
type="submit" @click="next()">
35+
<template x-if="form.processing">
36+
<x-spinner />
37+
</template>
38+
Next
39+
</x-button>
40+
</template>
41+
42+
<template x-if="isLastStep()">
43+
<x-button x-bind:disabled="form.processing" type="submit" @click="submit()">
44+
<template x-if="form.processing">
45+
<x-spinner />
46+
</template>
47+
Submit
48+
</x-button>
49+
</template>
3950
</div>
4051
</x-card.footer>
4152
</x-card>
4253
</x-steps.content>
4354
{{ /sections }}
44-
</x-steps>
55+
</x-steps>

0 commit comments

Comments
 (0)