Earth Charity Logo Earth Charity

A donation page with a form

Imagine this was a page on your main website.

Your supporters could start filling out the form here. When they click the 'Donate' button, an Impact Stack page will open with the form prefilled with the data from the form here.

Feel free to try it out. It'll open an Impact Stack form with the whatever data you add here already pre-filled.

Working demo ⤵︎


How to do it

Here's some basic code to get you started. Most organisations would need a web developer to pick this up and adapt it for your needs and your website technology. If you put it on your main website it would likely inherit a lot of the existing styling, but we'd still expect you to need to do some work to make sure it looked on-brand for you.

Javascript

You would need something like this Javascript which looks for the form and intercepts the submission. Then it opens an Impact Stack page with the fields prefilled.

See the Form Javascript Code
Form Javascript
<!--
This will look for any <form class="impact_stack"> on your page
It will intercept the submission of the form, then make an Impact Stack prepopulation link out of the data in the form
-->
<script>
document.addEventListener("DOMContentLoaded", () => {
// You could have multiple forms on the page with the way this is set up.
// Feel free to choose another selector to find the forms on your page
const forms = Array.from(document.querySelectorAll("form.impact_stack"));
if (forms.length === 0) {
console.error("No forms were found.");
return;
}
for (const form of forms) {
if (form === null || !(form instanceof HTMLFormElement)) {
return;
}
form.addEventListener("submit", (event) => {
event.preventDefault();
if (
event.target === null ||
!(event.target instanceof HTMLFormElement)
) {
return;
}
try {
// Get the data from the form fields
const formData = new FormData(event.target);
// Get the Impact Stack form URL from the action attribute of the form
const actionURL = new URL(event.target.action);
// This makes a pre-population link for Impact Stack:
// https://support.impact-stack.org/hc/en-us/articles/115001868166-How-to-pre-populate-forms
//
// The 'keys' here are from the 'name' attribute on the form inputs.
// They should match the 'form key' on your Impact Stack form
actionURL.hash = Array.from(formData.entries())
.filter(([, value]) => typeof value === "string")
.map(([key, value]) => {
//
// Validation checks.
//
// You could adjust these or add any other checks you need here
// Donation interval must be 1, m or y
// Donation amount must be an integer greater than 0
if (
key === "donation_interval" &&
!["1", "m", "y"].includes(value)
) {
throw new Error(`Invalid donation_interval value: ${value}`);
}
if (
key === "donation_amount" &&
(!Number.isInteger(Number(value)) || Number(value) <= 0)
) {
throw new Error(`Invalid donation_amount value: ${value}`);
}
return `p:${encodeURIComponent(key)}=${encodeURIComponent(
value
)}`;
})
.join("&");
// Use _blank instead of _self if you want to open in a new tab
window.open(actionURL.toString(), "_self");
} catch (error) {
console.error("Form submission error: ", error.message);
// You might want to show an error message on the page here
}
});
}
});
</script>

HTML

Then you would need some HTML for the form itself. You can edit the fields to be whatever you want. Edit the 'action' attribute on the form to be the URL of your Impact Stack form. The donation amount is a 'radio group' which is complex to create and understand.

See the Donation Form Code
Donation form HTML
<!--
This example uses a lot of classes like form-item and form-type-radios to reuse Earth Charity styles. This will need likely need quite a lot of customisation to make it match your brand.
-->
<form class="impact_stack" action="https://demo2.impact-stack.org/donation-2">
<div class="form-item form-type-select">
<label for="donation-interval">Donation Interval</label>
<select id="donation-interval" name="donation_interval">
<option value="1">One-time</option>
<option value="m">Monthly</option>
<option value="y">Yearly</option>
</select>
</div>
<div class="form-item form-type-radios">
<label for="donation-amount">Donation Amount</label>
<div class="form-radios donation-amount-buttons select-or-other-select" role="radiogroup"
aria-label="Select donation amount">
<div class="form-item form-type-radio">
<input type="radio" id="donation-10" name="amount_radio" value="10"
class="form-radio donation-amount-buttons select-or-other-select" tabindex="0">
<label class="button option" for="donation-10"><span>£10</span></label>
</div>
<div class="form-item form-type-radio">
<input type="radio" id="donation-20" name="amount_radio" value="20"
class="form-radio donation-amount-buttons select-or-other-select" tabindex="0">
<label class="button option" for="donation-20"><span>£20</span></label>
</div>
<div class="form-item form-type-radio">
<input type="radio" id="donation-30" name="amount_radio" value="30"
class="form-radio donation-amount-buttons select-or-other-select" tabindex="0">
<label class="button option" for="donation-30"><span>£30</span></label>
</div>
</div>
</div>
<!-- This is a hidden input that is filled by the radio group above -->
<input hidden id="donation-amount" name="donation_amount" min="1" placeholder="Donation amount">
<div class="mt-4"><button type="submit" class="button">Donate <span class="arrow">→</span></button></div>
</form>
<script>
const radioGroup = document.querySelector('.form-radios');
const donation_amount = document.getElementById('donation-amount');
const radioInputs = document.querySelectorAll('.form-radio');
let lastChecked = null;
radioInputs.forEach(radio => {
radio.addEventListener('click', (e) => {
if (lastChecked === radio) {
radio.checked = false;
lastChecked = null;
donation_amount.value = '';
} else {
lastChecked = radio;
donation_amount.value = radio.value;
}
});
});
radioGroup.addEventListener('keydown', (e) => {
const options = [...document.querySelectorAll('.form-radio')];
const currentIndex = options.indexOf(document.activeElement);
switch (e.key) {
case 'ArrowRight':
case 'ArrowDown':
e.preventDefault();
const nextIndex = (currentIndex + 1) % options.length;
options[nextIndex].focus();
options[nextIndex].checked = true;
lastChecked = options[nextIndex];
donation_amount.value = options[nextIndex].value;
break;
case 'ArrowLeft':
case 'ArrowUp':
e.preventDefault();
const prevIndex = (currentIndex - 1 + options.length) % options.length;
options[prevIndex].focus();
options[prevIndex].checked = true;
lastChecked = options[prevIndex];
donation_amount.value = options[prevIndex].value;
break;
case 'Space':
e.preventDefault();
const currentRadio = document.activeElement;
if (lastChecked === currentRadio) {
currentRadio.checked = false;
lastChecked = null;
donation_amount.value = '';
} else {
currentRadio.checked = true;
lastChecked = currentRadio;
donation_amount.value = currentRadio.value;
}
break;
}
});
</script>
<style>
form {
display: flex;
flex-direction: column;
row-gap: 1rem;
}
.form-radios {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
margin-top: 0 !important;
}
.form-type-radio label {
width: 100%;
}
@media (min-width: 640px) {
.form-radios {
grid-template-columns: repeat(3, 100px);
}
}
.form-radio:focus+label {
outline: 2px solid #90c92a;
outline-offset: 2px;
}
</style>