mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Forms improvements
Summary: Forms improvements and following new design - New headers - New UI - New right panel options Test Plan: Tests updated Reviewers: georgegevoian, dsagal Reviewed By: georgegevoian Subscribers: dsagal, paulfitz Differential Revision: https://phab.getgrist.com/D4158
This commit is contained in:
434
static/forms/form.css
Normal file
434
static/forms/form.css
Normal file
@@ -0,0 +1,434 @@
|
||||
html,
|
||||
body {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
background-color: #f7f7f7;
|
||||
line-height: 1.42857143;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.grist-form-container {
|
||||
--icon-Tick: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTExLjYxODMwNjksNC42NzcwMjg0NyBDMTEuNzk2Njc4OSw0LjQ2NjIyNTE3IDEyLjExMjE2NzgsNC40Mzk5MzQ0MyAxMi4zMjI5NzExLDQuNjE4MzA2NDUgQzEyLjUzMzc3NDQsNC43OTY2Nzg0OCAxMi41NjAwNjUyLDUuMTEyMTY3NDEgMTIuMzgxNjkzMSw1LjMyMjk3MDcxIEw2LjUzMDY4ODI3LDEyLjIzNzc5NDYgTDMuNjQ2NDQ2NjEsOS4zNTM1NTI5OCBDMy40NTExODQ0Niw5LjE1ODI5MDg0IDMuNDUxMTg0NDYsOC44NDE3MDgzNSAzLjY0NjQ0NjYxLDguNjQ2NDQ2MiBDMy44NDE3MDg3Niw4LjQ1MTE4NDA2IDQuMTU4MjkxMjQsOC40NTExODQwNiA0LjM1MzU1MzM5LDguNjQ2NDQ2MiBMNi40NjkzMTE3MywxMC43NjIyMDQ1IEwxMS42MTgzMDY5LDQuNjc3MDI4NDcgWiIgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJub256ZXJvIi8+PC9zdmc+);
|
||||
--icon-Minus: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHJlY3QgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJub256ZXJvIiB4PSIyIiB5PSI3LjUiIHdpZHRoPSIxMiIgaGVpZ2h0PSIxIiByeD0iLjUiLz48L3N2Zz4=);
|
||||
--primary: #16b378;
|
||||
--primary-dark: #009058;
|
||||
--dark-gray: #D9D9D9;
|
||||
--light-gray: #bfbfbf;
|
||||
--light: white;
|
||||
|
||||
color: #262633;
|
||||
background-color: #f7f7f7;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
padding-top: 52px;
|
||||
font-size: 15px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Liberation Sans", Helvetica, Arial, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
|
||||
|
||||
.grist-form-container .grist-form-confirm {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.grist-form {
|
||||
margin: 0px auto;
|
||||
background-color: white;
|
||||
border: 1px solid #E8E8E8;
|
||||
width: 600px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: calc(100% - 32px);
|
||||
margin-bottom: 16px;
|
||||
padding-top: 20px;
|
||||
--grist-form-padding: 48px;
|
||||
padding-left: var(--grist-form-padding);
|
||||
padding-right: var(--grist-form-padding);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.grist-form-container {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.grist-form {
|
||||
--grist-form-padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.grist-form > div + div {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.grist-form .grist-section {
|
||||
border-radius: 3px;
|
||||
border: 1px solid #D9D9D9;
|
||||
padding: 16px 24px;
|
||||
padding: 24px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.grist-form .grist-section > div + div {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.grist-form input[type="text"],
|
||||
.grist-form input[type="date"],
|
||||
.grist-form input[type="datetime-local"],
|
||||
.grist-form input[type="number"] {
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #D9D9D9;
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.grist-form .grist-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.grist-form .grist-field .grist-field-description {
|
||||
color: #222;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-top: 4px;
|
||||
white-space: pre-wrap;
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.grist-form .grist-field input[type="text"] {
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #D9D9D9;
|
||||
font-size: 13px;
|
||||
outline-color: #16b378;
|
||||
outline-width: 1px;
|
||||
line-height: inherit;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.grist-form .grist-submit, .grist-form-container button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.grist-form input[type="submit"], .grist-form-container button {
|
||||
background-color: #16b378;
|
||||
border: 1px solid #16b378;
|
||||
color: white;
|
||||
padding: 10px 24px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.grist-form input[type="datetime-local"] {
|
||||
width: 100%;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.grist-form input[type="date"] {
|
||||
width: 100%;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
|
||||
.grist-form input[type="checkbox"] {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.grist-form .grist-columns {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--grist-columns-count), 1fr);
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.grist-form select {
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #D9D9D9;
|
||||
font-size: 13px;
|
||||
outline-color: #16b378;
|
||||
outline-width: 1px;
|
||||
background: white;
|
||||
line-height: inherit;
|
||||
flex: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.grist-form .grist-choice-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.grist-form .grist-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
--color: var(--dark-gray);
|
||||
}
|
||||
.grist-form .grist-checkbox:hover {
|
||||
--color: var(--light-gray);
|
||||
}
|
||||
|
||||
.grist-form input[type="checkbox"] {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
outline: none !important;
|
||||
--radius: 3px;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
margin-right: 4px;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.grist-form input[type="checkbox"]:checked:enabled, .grist-form input[type="checkbox"]:indeterminate:enabled {
|
||||
--color: var(--primary);
|
||||
}
|
||||
|
||||
.grist-form input[type="checkbox"]:disabled {
|
||||
--color: var(--dark-gray);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.grist-form input[type="checkbox"]::before, .grist-form input[type="checkbox"]::after {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--color, var(--dark-gray));
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.grist-form input[type="checkbox"]:checked::before, .grist-form input[type="checkbox"]:disabled::before, .grist-form input[type="checkbox"]:indeterminate::before {
|
||||
background-color: var(--color);
|
||||
}
|
||||
|
||||
.grist-form input[type="checkbox"]:not(:checked):indeterminate::after {
|
||||
-webkit-mask-image: var(--icon-Minus);
|
||||
}
|
||||
|
||||
.grist-form input[type="checkbox"]:not(:disabled)::after {
|
||||
background-color: var(--light);
|
||||
}
|
||||
|
||||
.grist-form input[type="checkbox"]:checked::after, .grist-form input[type="checkbox"]:indeterminate::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
-webkit-mask-image: var(--icon-Tick);
|
||||
-webkit-mask-size: contain;
|
||||
-webkit-mask-position: center;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
background-color: var(--light);
|
||||
}
|
||||
|
||||
|
||||
.grist-form .grist-submit input[type="submit"]:hover, .grist-form-container button:hover {
|
||||
border-color: var(--primary-dark);
|
||||
background-color: var(--primary-dark);
|
||||
}
|
||||
|
||||
.grist-power-by {
|
||||
margin-top: 24px;
|
||||
color: var(--dark-text, #494949);
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-top: 1px solid var(--dark-gray);
|
||||
padding: 10px;
|
||||
margin-left: calc(-1 * var(--grist-form-padding));
|
||||
margin-right: calc(-1 * var(--grist-form-padding));
|
||||
}
|
||||
|
||||
.grist-power-by a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
color: var(--dark-text, #494949);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.grist-logo {
|
||||
width: 58px;
|
||||
height: 20.416px;
|
||||
flex-shrink: 0;
|
||||
background: url(logo.png);
|
||||
background-position: 0 0;
|
||||
background-size: contain;
|
||||
background-color: transparent;
|
||||
background-repeat: no-repeat;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.grist-question > .grist-label {
|
||||
color: var(--dark, #262633);
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 16px; /* 145.455% */
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/* Markdown reset */
|
||||
|
||||
.grist-form h1,
|
||||
.grist-form h2,
|
||||
.grist-form h3,
|
||||
.grist-form h4,
|
||||
.grist-form h5,
|
||||
.grist-form h6 {
|
||||
margin: 4px 0px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.grist-form h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
.grist-form h2 {
|
||||
font-size: 22px;
|
||||
}
|
||||
.grist-form h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
.grist-form h4 {
|
||||
font-size: 13px;
|
||||
}
|
||||
.grist-form h5 {
|
||||
font-size: 11px;
|
||||
}
|
||||
.grist-form h6 {
|
||||
font-size: 10px;
|
||||
}
|
||||
.grist-form p {
|
||||
margin: 0px;
|
||||
}
|
||||
.grist-form strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
.grist-form hr {
|
||||
border: 0px;
|
||||
border-top: 1px solid var(--dark-gray);
|
||||
margin: 4px 0px;
|
||||
}
|
||||
|
||||
.grist-text-left {
|
||||
text-align: left;
|
||||
}
|
||||
.grist-text-right {
|
||||
text-align: right;
|
||||
}
|
||||
.grist-text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.grist-switch {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.grist-switch input[type='checkbox']::after {
|
||||
content: none;
|
||||
}
|
||||
.grist-switch input[type='checkbox']::before {
|
||||
content: none;
|
||||
}
|
||||
.grist-switch input[type='checkbox'] {
|
||||
position: absolute;
|
||||
}
|
||||
.grist-switch > span {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* Slider component */
|
||||
.grist-widget_switch {
|
||||
position: relative;
|
||||
width: 30px;
|
||||
height: 17px;
|
||||
display: inline-block;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.grist-switch_slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--grist-theme-switch-slider-fg, #ccc);
|
||||
border-radius: 17px;
|
||||
}
|
||||
|
||||
.grist-switch_slider:hover {
|
||||
box-shadow: 0 0 1px #2196F3;
|
||||
}
|
||||
|
||||
.grist-switch_circle {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
content: "";
|
||||
height: 13px;
|
||||
width: 13px;
|
||||
left: 2px;
|
||||
bottom: 2px;
|
||||
background-color: var(--grist-theme-switch-circle-fg, white);
|
||||
border-radius: 17px;
|
||||
}
|
||||
|
||||
input:checked + .grist-switch_transition > .grist-switch_slider {
|
||||
background-color: var(--primary, #16b378);
|
||||
}
|
||||
|
||||
input:checked + .grist-switch_transition > .grist-switch_circle {
|
||||
-webkit-transform: translateX(13px);
|
||||
-ms-transform: translateX(13px);
|
||||
transform: translateX(13px);
|
||||
}
|
||||
|
||||
.grist-switch_on > .grist-switch_slider {
|
||||
background-color: var(--grist-actual-cell-color, #2CB0AF);
|
||||
}
|
||||
|
||||
.grist-switch_on > .grist-switch_circle {
|
||||
-webkit-transform: translateX(13px);
|
||||
-ms-transform: translateX(13px);
|
||||
transform: translateX(13px);
|
||||
}
|
||||
|
||||
.grist-switch_transition > .grist-switch_slider, .grist-switch_transition > .grist-switch_circle {
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
@@ -1,156 +1,64 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<!-- INSERT BASE -->
|
||||
{{#if BASE}}
|
||||
<base href="{{ BASE }}">
|
||||
{{/if}}
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
background-color: #f7f7f7;
|
||||
line-height: 1.42857143;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
<script src="forms/grist-form-submit.js"></script>
|
||||
<script src="forms/purify.min.js"></script>
|
||||
<style>
|
||||
.grist-form-container {
|
||||
color: #262633;
|
||||
background-color: #f7f7f7;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
padding-top: 52px;
|
||||
padding-bottom: 32px;
|
||||
font-size: 13px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Liberation Sans", Helvetica, Arial, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
.grist-form-container .grist-form-confirm {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
form.grist-form {
|
||||
padding: 32px;
|
||||
margin: 0px auto;
|
||||
background-color: white;
|
||||
border: 1px solid #E8E8E8;
|
||||
width: 640px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
max-width: calc(100% - 32px);
|
||||
}
|
||||
|
||||
form.grist-form .grist-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
form.grist-form .grist-field label {
|
||||
font-size: 15px;
|
||||
margin-bottom: 8px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
form.grist-form .grist-field .grist-field-description {
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
margin-top: 4px;
|
||||
color: #929299;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
form.grist-form .grist-field input[type="text"] {
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #D9D9D9;
|
||||
font-size: 13px;
|
||||
outline-color: #16b378;
|
||||
outline-width: 1px;
|
||||
line-height: inherit;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
form.grist-form input[type="submit"] {
|
||||
background-color: #16b378;
|
||||
border: 1px solid #16b378;
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
form.grist-form input[type="datetime-local"] {
|
||||
width: 100%;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
form.grist-form input[type="date"] {
|
||||
width: 100%;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
form.grist-form input[type="submit"]:hover {
|
||||
border-color: #009058;
|
||||
background-color: #009058;
|
||||
}
|
||||
|
||||
form.grist-form input[type="checkbox"] {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
form.grist-form .grist-columns {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--grist-columns-count), 1fr);
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
form.grist-form select {
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #D9D9D9;
|
||||
font-size: 13px;
|
||||
outline-color: #16b378;
|
||||
outline-width: 1px;
|
||||
background: white;
|
||||
line-height: inherit;
|
||||
flex: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
form.grist-form .grist-choice-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<link rel="stylesheet" href="forms/form.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main class='grist-form-container'>
|
||||
<form class='grist-form'
|
||||
onsubmit="event.target.parentElement.querySelector('.grist-form-confirm').style.display = 'block', event.target.style.display = 'none'"
|
||||
data-grist-doc="<!-- INSERT DOC URL -->"
|
||||
data-grist-table="<!-- INSERT TABLE ID -->">
|
||||
<script>
|
||||
document.write(DOMPurify.sanitize(`<!-- INSERT CONTENT -->`));
|
||||
</script>
|
||||
onsubmit="event.target.parentElement.querySelector('.grist-form-confirm').style.display = 'flex', event.target.style.display = 'none'"
|
||||
data-grist-doc="{{ DOC_URL }}"
|
||||
data-grist-table="{{ TABLE_ID }}"
|
||||
data-grist-success-url="{{ SUCCESS_URL }}"
|
||||
>
|
||||
{{ dompurify CONTENT }}
|
||||
<div class="grist-power-by">
|
||||
<a href="https://getgrist.com" target="_blank">
|
||||
<div>Powered by</div>
|
||||
<div class="grist-logo"></div>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
<div class='grist-form-confirm' style='display: none'>
|
||||
Thank you! Your response has been recorded.
|
||||
<div>
|
||||
{{ SUCCESS_TEXT }}
|
||||
</div>
|
||||
{{#if ANOTHER_RESPONSE }}
|
||||
<button onclick="window.location.reload()">Submit another response</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
// Validate choice list on submit
|
||||
document.querySelector('.grist-form input[type="submit"]').addEventListener('click', function(event) {
|
||||
// When submit is pressed make sure that all choice lists that are required
|
||||
// have at least one option selected
|
||||
const choiceLists = document.querySelectorAll('.grist-choice-list.required:not(:has(input:checked))');
|
||||
Array.from(choiceLists).forEach(function(choiceList) {
|
||||
// If the form has at least one checkbox make it required
|
||||
const firstCheckbox = choiceList.querySelector('input[type="checkbox"]');
|
||||
firstCheckbox?.setAttribute('required', 'required');
|
||||
});
|
||||
|
||||
// All other required choice lists with at least one option selected are no longer required
|
||||
const choiceListsRequired = document.querySelectorAll('.grist-choice-list.required:has(input:checked)');
|
||||
Array.from(choiceListsRequired).forEach(function(choiceList) {
|
||||
// If the form has at least one checkbox make it required
|
||||
const firstCheckbox = choiceList.querySelector('input[type="checkbox"]');
|
||||
firstCheckbox?.removeAttribute('required');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -9,12 +9,13 @@ if (!window.gristFormSubmit) {
|
||||
* - `formData` should be a [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData)
|
||||
* object, typically obtained as `new FormData(formElement)`. Inside the `submit` event handler, it
|
||||
* can be convenient to use `new FormData(event.target)`.
|
||||
* - formElement is the form element that was submitted.
|
||||
*
|
||||
* This function sends values from `formData` to add a new record in the specified Grist table. It
|
||||
* returns a promise for the result of the add-record API call. In case of an error, the promise
|
||||
* will be rejected with an error message.
|
||||
*/
|
||||
async function gristFormSubmit(docUrl, tableId, formData) {
|
||||
async function gristFormSubmit(docUrl, tableId, formData, formElement) {
|
||||
// Pick out the server and docId from the docUrl.
|
||||
const match = /^(https?:\/\/[^\/]+(?:\/o\/[^\/]+)?)\/(?:doc\/([^\/?#]+)|([^\/?#]{12,})\/)/.exec(docUrl);
|
||||
if (!match) { throw new Error("Invalid Grist doc URL " + docUrl); }
|
||||
@@ -24,7 +25,7 @@ async function gristFormSubmit(docUrl, tableId, formData) {
|
||||
// Construct the URL to use for the add-record API endpoint.
|
||||
const destUrl = server + "/api/docs/" + docId + "/tables/" + tableId + "/records";
|
||||
|
||||
const payload = {records: [{fields: formDataToJson(formData)}]};
|
||||
const payload = {records: [{fields: formDataToJson(formData, formElement)}]};
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
@@ -58,6 +59,35 @@ function formDataToJson(f) {
|
||||
k.endsWith('[]') ? [k.slice(0, -2), ['L', ...f.getAll(k)]] : [k, f.get(k)]));
|
||||
}
|
||||
|
||||
/**
|
||||
* TypedFormData is a wrapper around FormData that provides type information for the fields.
|
||||
*/
|
||||
class TypedFormData {
|
||||
constructor(formElement, formData) {
|
||||
if (!(formElement instanceof HTMLFormElement)) throw new Error("formElement must be a form");
|
||||
if (formData && !(formData instanceof FormData)) throw new Error("formData must be a FormData");
|
||||
this._formData = formData ?? new FormData(formElement);
|
||||
this._formElement = formElement;
|
||||
}
|
||||
keys() { return this._formData.keys(); }
|
||||
type(key) {
|
||||
return this._formElement?.querySelector(`[name="${key}"]`)?.getAttribute('data-grist-type');
|
||||
}
|
||||
get(key) {
|
||||
const value = this._formData.get(key);
|
||||
if (value === null) { return null; }
|
||||
const type = this.type(key);
|
||||
return type === 'Ref' || type === 'RefList' ? Number(value) : value;
|
||||
}
|
||||
getAll(key) {
|
||||
const values = Array.from(this._formData.getAll(key));
|
||||
if (['Ref', 'RefList'].includes(this.type(key))) {
|
||||
return values.map(v => Number(v));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Handle submissions for plain forms that include special data-grist-* attributes.
|
||||
async function handleSubmitPlainForm(ev) {
|
||||
@@ -76,7 +106,7 @@ async function handleSubmitPlainForm(ev) {
|
||||
|
||||
const successUrl = ev.target.getAttribute('data-grist-success-url');
|
||||
|
||||
await gristFormSubmit(docUrl, tableId, new FormData(ev.target));
|
||||
await gristFormSubmit(docUrl, tableId, new TypedFormData(ev.target));
|
||||
|
||||
// On success, redirect to the requested URL.
|
||||
if (successUrl) {
|
||||
@@ -111,7 +141,7 @@ async function handleSubmitWPCF7(ev) {
|
||||
if (!docUrl) { throw new Error("Missing attribute data-grist-doc='GRIST_DOC_URL'"); }
|
||||
if (!tableId) { throw new Error("Missing attribute data-grist-table='GRIST_TABLE_ID'"); }
|
||||
|
||||
await gristFormSubmit(docUrl, tableId, new FormData(ev.target));
|
||||
await gristFormSubmit(docUrl, tableId, new TypedFormData(ev.target));
|
||||
console.log("grist-form-submit WPCF7 Form %s: Added record", formId);
|
||||
|
||||
} catch (err) {
|
||||
@@ -135,7 +165,7 @@ async function handleSubmitGravityForm(ev, options) {
|
||||
if (!docUrl) { throw new Error("setUpGravityForm: missing docUrl option"); }
|
||||
if (!tableId) { throw new Error("setUpGravityForm: missing tableId option"); }
|
||||
|
||||
const f = new FormData(ev.target);
|
||||
const f = new TypedFormData(ev.target);
|
||||
for (const key of Array.from(f.keys())) {
|
||||
// Skip fields other than input fields.
|
||||
if (!key.startsWith("input_")) {
|
||||
|
||||
BIN
static/forms/logo.png
Normal file
BIN
static/forms/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
Reference in New Issue
Block a user