//This is a hacked in fix for the bug with the multiple select fields in the form
//the issue is that on page load, that field gets focussed and the page scrolls to it
//this is a workaround to prevent that from happening
// Store scroll position immediately
let initialScrollTop = 0;
let scrollHandled = false;
window.addEventListener("DOMContentLoaded", function () {
initialScrollTop = window.pageYOffset || document.documentElement.scrollTop;
// Immediately set up scroll listener
window.addEventListener("scroll", function onScroll() {
if (!scrollHandled) {
scrollHandled = true;
window.scrollTo(0, initialScrollTop);
window.removeEventListener("scroll", onScroll);
}
}, { passive: true });
// Defer field fix just a little
setTimeout(function () {
const offending = document.querySelector("select[id='crcd2_disabilties_1']");
if (offending) {
offending.blur();
const chosenInput = offending.nextElementSibling?.querySelector("input");
if (chosenInput) chosenInput.blur();
// Focus your intended first field
const fields = Array.from(document.querySelectorAll("form input:not([type=hidden]):not([disabled]):not([readonly]), form select:not([disabled]):not([readonly])"));
const firstField = fields.find(el => el.offsetParent !== null); // check it's visible
if (firstField) firstField.focus();
}
}, 90); // shorter delay to beat the layout flicker
});
//Web API ajax wrapper
(function(webapi, $) {
function safeAjax(ajaxOptions) {
var deferredAjax = $.Deferred();
shell.getTokenDeferred().done(function(token) {
// Add headers for ajax
if (!ajaxOptions.headers) {
$.extend(ajaxOptions, {
headers: {
"__RequestVerificationToken": token
}
});
} else {
ajaxOptions.headers["__RequestVerificationToken"] = token;
}
$.ajax(ajaxOptions)
.done(function(data, textStatus, jqXHR) {
validateLoginSession(data, textStatus, jqXHR, deferredAjax.resolve);
}).fail(deferredAjax.reject); //ajax
}).fail(function() {
deferredAjax.rejectWith(this, arguments); // On token failure pass the token ajax and args
});
return deferredAjax.promise();
}
webapi.safeAjax = safeAjax;
})(window.webapi = window.webapi || {}, jQuery)
//START the actual logic for navigation of form steps
//and final submission to add the referral to Dynamics
document.addEventListener("DOMContentLoaded", function () {
let referralData = {};
const referralPaths = {
'whoareyou': '/',
'carer': '/referral-about-the-carer/',
'cared_for': '/referral-about-the-cared-for/',
'gp_practice': '/referral-gp-practice/',
'parent_or_guardian': '/referral-parent-or-guardian/', //only if < 18
'second_parent_or_guardian': '/referral-parent-or-guardian/referral-second-parent-or-guardian/', //only if < 18 and ticked the second parent box
'school': '/referral-school/', //only if <18
'referrer': '/referral-about-referrer/',
'info': '/referral-info/',
'gp_doctor': '/referral-gp-doctor/',
};
//=========================== START - GET form step from temporary storage and set values ==========================//
async function getReferralFromTempStorage() {
const storageId = sessionStorage.getItem("referralStorageId");
console.log('storageId', storageId, window.location.pathname)
if (!storageId) {
//alert("Session expired. Please restart the referral.");
if(window.location.pathname !== '/') {
window.location.href = '/'; // Redirect to the first page
}
return;
}
referralData = {};
// Step 1: Get full data from temp storage
try {
const result = await webapi.safeAjax({
type: "GET",
url: `/_api/crcd2_referralformtempdatas(crcd2_id='${storageId}')?$select=crcd2_tempdata`
});
referralData = JSON.parse(result.crcd2_tempdata || "{}");
} catch (error) {
//if no referral data, delete the storage id
console.log('Loaded referral data from API:', referralData);
if (!referralData || Object.keys(referralData).length === 0) {
console.log('No referral data found in temp storage. Deleting storage ID.');
sessionStorage.removeItem("referralStorageId");
return;
}
}
try {
// Step 2: Get the current form name from the URL
const currentFormName = getCurrentFormName();
console.log('currentFormName', currentFormName);
// Step 3: Get the specific form data for the current form
const currentFormData = referralData[currentFormName] || {};
console.log('referralData for current form', currentFormData);
//Step 4: do all the changes to the form before we're setting the values
changeNumberOnlyFields();
//create multiselect fields from the conditions
renderGroupedConditionMultiselect(currentFormData);
// Step 5: Set the form values
const form = document.getElementById("liquid_form");
if (!form) {
console.warn("Form not found in the DOM. Cannot set values.");
return currentFormData;
}
const inputs = form.querySelectorAll("input, select, textarea");
inputs.forEach(input => {
const fieldName = input.getAttribute("id");
if (fieldName && currentFormData[fieldName] !== undefined) {
input.value = currentFormData[fieldName];
}
});
// Handle radio buttons
const radioGroups = {};
inputs.forEach(input => {
if (input.type === "radio") {
const groupName = input.name;
if (!radioGroups[groupName]) {
radioGroups[groupName] = [];
}
radioGroups[groupName].push(input);
}
});
// Set the value for each radio group
for (const groupName in radioGroups) {
const group = radioGroups[groupName];
// Extract cleaned key from full group name
const cleanedKey = groupName.split('$').pop();
let checkedValue = currentFormData[cleanedKey];
if (checkedValue === "true") {
checkedValue = 1;
} else if (checkedValue === "false") {
checkedValue = 0;
}
console.debug('radio group', groupName, cleanedKey, checkedValue);
group.forEach(input => {
console.debug('Setting radio input', input.value, 'to checked:', input.value == checkedValue);
input.checked = (input.value == checkedValue);
});
}
// Handle checkboxes
inputs.forEach(input => {
if (input.type === "checkbox") {
console.log('checkbox input', input)
const fieldName = input.getAttribute("id");
console.log('checkbox fieldName', fieldName, currentFormData[fieldName])
if (fieldName && currentFormData[fieldName] !== undefined) {
input.checked = currentFormData[fieldName];
}
}
});
//Handle birth dates
//check if we have to set a default date
if(referralData && referralData[currentFormName]?.birthdate) {
const dobInput = document.getElementById('birthdate_datepicker_description');
//format the date to the format expected by the input
dobInput.value = moment(referralData[currentFormName].birthdate).format("MMMM DD, YYYY");
}
// Handle custom toggle (e.g., crcd2_doyouliveinsurrey)
const toggleWrapper = document.querySelector("#crcd2_doyouliveinsurrey_Container .custom-toggle-select input[type='checkbox']");
if (toggleWrapper && currentFormData["crcd2_doyouliveinsurrey"]) {
toggleWrapper.checked = currentFormData["crcd2_doyouliveinsurrey"] === "684750000"; // yes value
}
//Step 6:
//Now set all the other page changes based on the referral data
hideReferrerOrganisation();
setPageTitle();
setToggle();
toggleTextareasWithToggles();
handleCarerAgeFieldVisibility({
dobFieldId: 'birthdate_datepicker_description',
fieldsToToggle: [ ]
});
}
catch (err) {
console.error("Failed to load referral data from API");
console.error("Error message:", err?.message || err);
console.error("Full error object:", err);
alert("There was an error retrieving your referral data. Please restart.");
return;
}
}
//if this isn't the thank you page, we need to get the referral data from the temp storage
if (!window.location.pathname.includes('/thank-you/')) {
getReferralFromTempStorage();
}
//=========================== END - GET form step from temporary storage and set values ==========================//
//========================== START - If the referrer TYpe is school, don't show org ==========================//
function hideReferrerOrganisation() {
const refererType = getRefererType(referralData);
if(refererType === '684750004') { // School
const orgContainer = document.getElementById("crcd2_organisationtemp");
//get closest tr
const orgRow = orgContainer?.closest("tr");
if (orgRow) {
orgRow.style.display = "none";
}
}
}
//========================== END - If the referrer TYpe is school, don't show org ==========================//
//========================== START - Set dynamic page title based on who is filling in the form ==========================//
function setPageTitle() {
//check if ths is the carers page and the user who fills in the form is a carer
const path = window.location.pathname;
const refererType = getRefererType(referralData);
let titleElement = document.querySelector(".tab-title");
//get disability and conditions title via aria label = "Disabilty or Condition Information" and then get the .section-title and inside that the h3
const disabilityTitle = document.querySelector('fieldset[aria-label="Disabilty or Condition Information"] .section-title h3');
if (titleElement) {
/*'684750000': general, // Carer
'684750001': general, // Cared For
'684750002': general, // Parent
'684750004': general, // School
'684750005': [ // GP Doctor
'684750003': agencyOrOther, // Agency
'684750006': agencyOrOther // Other*/
if (path.contains(referralPaths['carer']) && refererType === '684750000') {
// Logic specific to carer users on the carer page - self referral
titleElement.textContent = "About You, the Carer";
if (disabilityTitle) {
disabilityTitle.textContent = "Your Disability or Condition Information";
}
//get fieldset with aria label "Carer Details" and then .section-title h3
const carerDetailsTitle = document.querySelector('fieldset[aria-label="Carer Details"] .section-title h3');
//change to 'Your Details'
if (carerDetailsTitle) {
carerDetailsTitle.textContent = "Your Details";
}
//Does the carer or the person they care for live in Surrey?
const surreyLabel = document.getElementById("crcd2_doyouliveinsurrey_label");
surreyLabel.textContent = surreyLabel.textContent.replace('Does the carer', 'Do you').replace('they', 'you');
//Also tick this if the person you care for is currently in a Surrey Hospital
const surreyDescription = document.querySelector('#crcd2_doyouliveinsurrey_label').closest('tr').querySelector('.description.above');
if (surreyDescription) {
//Change from: "Also tick this if the person you care for is currently in a Surrey Hospital"
surreyDescription.textContent = surreyDescription.textContent.replace('the carer', 'you').replace('they', 'you');
}
}
else if (path.contains(referralPaths['carer'])) {
// Cares page, but not a carer user
if (disabilityTitle) {
disabilityTitle.textContent = "Carers Disability or Condition Information";
}
}
if (path.contains(referralPaths['cared_for']) && refererType === '684750000') {
// Logic specific to carer users on the cared for page
titleElement.textContent = "About Who You Care For";
}
if (path.contains(referralPaths['cared_for']) && refererType === '684750001') {
// Logic specific to carer users on the cared_for page
titleElement.textContent = "About You, the Cared For Person";
}
if (path.contains(referralPaths['gp_doctor']) && refererType === '684750005') {
// Logic specific to GP users on the GP Doctor page
titleElement.textContent = "About You, the GP";
}
if (path.contains(referralPaths['parent_or_guardian']) && refererType === '684750002') {
// Logic specific to parent or guardian users on the parent or guardian page
titleElement.textContent = "About You, the Parent or Guardian of the Carer";
}
if (path.contains(referralPaths['second_parent_or_guardian'])) {
// Logic specific to parent or guardian users on the second parent or guardian page
titleElement.textContent = "Second Parent or Guardian Info";
}
if (path.contains(referralPaths['school']) && refererType === '684750004') {
// Logic specific to school users on the school page
titleElement.textContent = "About You, the School";
}
if (path.contains(referralPaths['referrer']) && (refererType === '684750003' || refererType === '684750006' || refererType === '684750004')) {
// Logic specific to referrer users on the referrer page
titleElement.textContent = "About You, the Referrer";
}
}
console.log('INFO PAGE - CARER filling in the info page', path, referralPaths['info'], refererType);
if(path.contains(referralPaths['info']) && refererType === '684750000') {
//carer filling in the info page - SELF REFERRAL
//
const serviceLabel = document.getElementById("crcd2_servicerequested_label");
serviceLabel.textContent = "Which Services do you require (if known)?";
//remove this option
const personalHealthBudgetOption = document.getElementById("crcd2_servicerequested_item4");
if (personalHealthBudgetOption) {
const personalHealthBudgetLabel = personalHealthBudgetOption?.closest('label');
personalHealthBudgetLabel.style.display = "none";
}
//get all labels and replace 'the Carer' with 'you' and 'Person Cared For' with 'the person you care for'
//also take care of 'Does the Carer' and 'Do you'
const allLabels = document.querySelectorAll("label.field-label");
allLabels.forEach(label => {
label.textContent = label.textContent
.replace('Does the carer', 'Do you')
.replace('Does the Carer', 'Do you')
.replace('Is the Carer', 'Are you')
.replace('the Carer', 'you')
.replace('the carer', 'you')
.replace('they', 'you')
.replace('Person Cared For', 'the person you care for');
});
}
}
//========================== END - Set dynamic page title based on who is filling in the form ==========================//
//========================== START - Second parent or guardian page ==========================/
function adjustParentPageForSecondEntry() {
const pathToMatch = referralPaths['second_parent_or_guardian'];
if (!window.location.pathname.endsWith(pathToMatch)) return;
const interval = setInterval(() => {
const fieldset = document.querySelector('fieldset[aria-label="Additional Parent or Guardian"]');
const heading = fieldset?.querySelector('h3');
if (fieldset) {
fieldset.style.display = 'none';
}
if (heading && heading.textContent !== 'Second Parent or Guardian Info') {
heading.textContent = 'Second Parent or Guardian Info';
}
if (fieldset && heading) clearInterval(interval);
}, 300);
}
adjustParentPageForSecondEntry();
//========================== END - Second parent or guardian page ==========================/
//========================== START - Change surrey tickbox to toggle ==========================//
function setToggle() {
//all yes/no radios:
// Inject CSS for all toggles
const radioToggleStyle = document.createElement("style");
radioToggleStyle.textContent = `
.toggle-wrapper,
.toggle-label-wrapper {
display: flex;
align-items: center;
gap: 8px;
margin-top: 6px;
}
.toggle-label {
font-size: 0.9rem;
color: #333;
}
.custom-toggle-select input[type="checkbox"],
.custom-toggle input[type="checkbox"] {
opacity: 0;
width: 0;
height: 0;
position: absolute;
}
.custom-toggle ,
.custom-toggle-select {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
.custom-slider,
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: 0.4s;
border-radius: 24px;
}
.custom-slider::before,
.toggle-slider::before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
transition: 0.4s;
border-radius: 50%;
}
.custom-toggle input:checked + .custom-slider,
.custom-toggle-select input:checked + .toggle-slider {
background-color: #2196F3;
}
.custom-toggle input:checked + .custom-slider::before,
.custom-toggle-select input:checked + .toggle-slider::before {
transform: translateX(26px);
}
`;
document.head.appendChild(radioToggleStyle);
// Convert each .boolean-radio into a toggle
document.querySelectorAll(".boolean-radio").forEach(function (radioGroup) {
const radios = radioGroup.querySelectorAll('input[type="radio"]');
if (radios.length !== 2) return; // skip if not Yes/No pair
const name = radios[0].name;
const checkedValue = [...radios].find(r => r.checked)?.value;
const isYes = checkedValue === "1" || checkedValue === "true" || checkedValue === 1 || checkedValue === true;
console.debug('Converting radio group', name, 'to toggle with checked value:', checkedValue, 'isYes:', isYes);
// Remove original radio buttons
radioGroup.innerHTML = "";
// Create toggle container
const wrapper = document.createElement("div");
wrapper.className = "toggle-wrapper";
const labelNo = document.createElement("span");
labelNo.textContent = "No";
labelNo.className = "toggle-label";
const labelYes = document.createElement("span");
labelYes.textContent = "Yes";
labelYes.className = "toggle-label";
const toggleLabel = document.createElement("label");
toggleLabel.className = "custom-toggle";
const toggleInput = document.createElement("input");
toggleInput.type = "checkbox";
toggleInput.dataset.trueValue = true;
toggleInput.dataset.falseValue = false;
toggleInput.checked = isYes;
const slider = document.createElement("span");
slider.className = "custom-slider";
toggleLabel.appendChild(toggleInput);
toggleLabel.appendChild(slider);
wrapper.appendChild(labelNo);
wrapper.appendChild(toggleLabel);
wrapper.appendChild(labelYes);
radioGroup.appendChild(wrapper);
// Create hidden real radio inputs (preserves original name)
const hiddenNo = document.createElement("input");
hiddenNo.type = "radio";
hiddenNo.name = name;
hiddenNo.value = false;
hiddenNo.style.display = "none";
const hiddenYes = document.createElement("input");
hiddenYes.type = "radio";
hiddenYes.name = name;
hiddenYes.value = true;
hiddenYes.style.display = "none";
// Sync toggle state with hidden radios
toggleInput.addEventListener("change", function () {
if (toggleInput.checked) {
hiddenYes.checked = true;
hiddenYes.dispatchEvent(new Event("change"));
} else {
hiddenNo.checked = true;
hiddenNo.dispatchEvent(new Event("change"));
}
});
// Set initial checked radio
if (isYes) {
hiddenYes.checked = true;
hiddenYes.dispatchEvent(new Event("change"));
} else {
hiddenNo.checked = true;
hiddenNo.dispatchEvent(new Event("change"));
}
radioGroup.appendChild(hiddenNo);
radioGroup.appendChild(hiddenYes);
});
//just the 'do you live in surrey' tickbox
const select = document.getElementById("crcd2_doyouliveinsurrey");
if (!select) return;
select.style.display = "none";
const container = document.createElement("div");
container.className = "toggle-label-wrapper";
const labelNo = document.createElement("span");
labelNo.textContent = "No";
labelNo.className = "toggle-label toggle-label-left";
const labelYes = document.createElement("span");
labelYes.textContent = "Yes";
labelYes.className = "toggle-label toggle-label-right";
const toggleWrapper = document.createElement("label");
toggleWrapper.className = "custom-toggle-select";
const toggleInput = document.createElement("input");
toggleInput.type = "checkbox";
const slider = document.createElement("span");
slider.className = "toggle-slider";
toggleWrapper.appendChild(toggleInput);
toggleWrapper.appendChild(slider);
container.appendChild(labelNo);
container.appendChild(toggleWrapper);
container.appendChild(labelYes);
select.parentNode.insertBefore(container, select.nextSibling);
toggleInput.checked = select.value !== "";
toggleInput.addEventListener("change", function () {
select.value = toggleInput.checked ? "684750000" : "";
select.dispatchEvent(new Event("change"));
});
}
//========================== END - Change surrey tickbox to toggle ==========================//
function changeNumberOnlyFields() {
const numberFieldIds = [
"crcd2_nhsnumber",
"crcd2_household",
];
numberFieldIds.forEach(id => {
const input = document.getElementById(id);
if (input) {
input.setAttribute("type", "number");
input.setAttribute("min", "0"); // only allow positive numbers
input.setAttribute("step", "1"); // only allow whole numbers
}
});
}
//========================== START - change number only fields ==========================//
//========================== END - change number only fields ==========================//
//========================== START - set multiselect fields ==========================//
function replaceAllMSOSWithCheckboxes() {
const lists = document.querySelectorAll("ul.msos-selection");
console.debug("Found MSOS lists:", lists.length);
lists.forEach((list) => {
const containerDiv = list.closest("div.msos-selection-container");
if (!containerDiv) return;
const inputCheckboxes = list.querySelectorAll("input[type='checkbox']");
if (!inputCheckboxes.length) return;
const firstCheckbox = inputCheckboxes[0];
const idMatch = firstCheckbox.id.match(/^(.+)_item\d+$/);
if (!idMatch) return;
const baseId = idMatch[1];
const hiddenInput = document.getElementById(baseId + "_0");
if (!hiddenInput) return;
const fullContainer = document.getElementById(baseId + "_Container");
if (!fullContainer) return;
// Build checkbox wrapper
const wrapper = document.createElement("div");
wrapper.id = baseId + "-checkboxes";
wrapper.className = "msos-checkbox-wrapper";
wrapper.style.marginBottom = "1em";
inputCheckboxes.forEach((cb) => {
const value = cb.value;
console.log("CHECKBOX VALUE:", value);
const labelText = cb.getAttribute("aria-label") || cb.parentElement?.title || value;
const originalId = cb.id;
const label = document.createElement("label");
label.style.display = "block";
label.style.marginBottom = "4px";
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.value = value;
checkbox.id = originalId; // <-- preserve original ID
checkbox.style.marginRight = "5px";
label.appendChild(checkbox);
label.appendChild(document.createTextNode(labelText));
wrapper.appendChild(label);
});
// Replace full field container with new checkboxes
const parent = fullContainer.parentElement;
fullContainer.remove();
parent.appendChild(wrapper);
});
}
setTimeout(replaceAllMSOSWithCheckboxes, 500);
//=========================== END - set multiselect fields ==========================//
//========================== START - Toggle more info fields ==========================//
const hiddenToggleFields = [
{
toggleName: "ctl00$ContentContainer$EntityFormControl_354ecd05d592439fb598b3a11e71f4c5$EntityFormControl_354ecd05d592439fb598b3a11e71f4c5_EntityFormView$crcd2_behaviouremotionaldisability",
showElementId: "crcd2_behaviouremotionaldisabilityneeds"
},
{
toggleName: "ctl00$ContentContainer$EntityFormControl_354ecd05d592439fb598b3a11e71f4c5$EntityFormControl_354ecd05d592439fb598b3a11e71f4c5_EntityFormView$crcd2_medialneeds",
showElementId: "crcd2_medicalneeds"
},
{
toggleName: "ctl00$ContentContainer$EntityFormControl_354ecd05d592439fb598b3a11e71f4c5$EntityFormControl_354ecd05d592439fb598b3a11e71f4c5_EntityFormView$crcd2_extrasupport",
showElementId: "crcd2_additionalcomments"
},
{
toggleName: "ctl00$ContentContainer$EntityFormControl_7cda038b6d9c4d03bef00a285e36b9d5$EntityFormControl_7cda038b6d9c4d03bef00a285e36b9d5_EntityFormView$crcd2_isinhospital",
showElementId: "crcd2_hospital"
},
{
toggleName: "ctl00$ContentContainer$EntityFormControl_7cda038b6d9c4d03bef00a285e36b9d5$EntityFormControl_7cda038b6d9c4d03bef00a285e36b9d5_EntityFormView$crcd2_otheragenciesinvolved",
showElementId: "crcd2_otheragenciesdetails"
}
];
function toggleTextareasWithToggles() {
hiddenToggleFields.forEach(({ toggleName, showElementId }) => {
const yesRadio = document.querySelector(`input[type="radio"][name="${toggleName}"][value="true"]`);
const noRadio = document.querySelector(`input[type="radio"][name="${toggleName}"][value="false"]`);
const textareaRow = document.getElementById(showElementId)?.closest("tr");
if (!yesRadio || !noRadio || !textareaRow) return;
const updateVisibility = () => {
textareaRow.style.display = yesRadio.checked ? "" : "none";
};
// Initial hide/show
updateVisibility();
// Event listeners
yesRadio.addEventListener("change", updateVisibility);
noRadio.addEventListener("change", updateVisibility);
});
}
function stripHiddenToggleFields(data) {
hiddenToggleFields.forEach(({ toggleName, showElementId }) => {
const toggle = document.querySelector(`input[name="${toggleName}"]:checked`);
const isYes = toggle?.value === "true" || toggle?.value === "1";
if (!isYes && data.hasOwnProperty(showElementId)) {
delete data[showElementId]; // remove unwanted field from submission
}
});
return data;
}
//============================ END - Toggle more info fields ==========================//
//========================== START - Filter Conditions Multiselect ==========================//
async function renderGroupedConditionMultiselect(currentFormData) {
const targetRow = document.querySelector("#crcd2_disabilitydetail")?.closest("tr");
if (!targetRow) return;
// Fetch conditions from Dataverse
let result;
try {
result = await webapi.safeAjax({
type: "GET",
url: "/_api/crcd2_conditions?$select=crcd2_conditionid,crcd2_conditionname,crcd2_primarycondition"
});
} catch (e) {
console.error("Failed to fetch conditions", e);
return;
}
const conditions = result?.value || [];
if (!conditions.length) return;
// Group by formatted label
const groups = {};
conditions.forEach(c => {
const value = c.crcd2_primarycondition || 0;
const label = c["crcd2_primarycondition@OData.Community.Display.V1.FormattedValue"] || "Other";
if (!groups[value]) groups[value] = { label, items: [] };
groups[value].items.push(c);
});
// Insert before the target row
const wrapper = document.createElement("tr");
wrapper.innerHTML = `
`;
targetRow.parentNode.insertBefore(wrapper, targetRow);
// CSS styles
const style = document.createElement("style");
style.innerHTML = `
.condition-group {
margin-bottom: 2rem;
}
.condition-group-title {
font-weight: 600;
margin-bottom: 0.5rem;
}
.condition-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 0.4rem 1.5rem;
}
.condition-option {
display: flex;
align-items: start;
gap: 0.4rem;
font-size: 0.95rem;
line-height: 1.3;
}
.condition-option input[type="checkbox"] {
width: 14px;
height: 14px;
margin-top: 0.2rem;
}
`;
document.head.appendChild(style);
const wrapperDiv = document.getElementById("conditionGroupWrapper");
const checkboxElements = [];
// Build UI
Object.entries(groups).sort((a, b) =>
a[1].label.localeCompare(b[1].label)
).forEach(([value, group]) => {
const groupDiv = document.createElement("div");
groupDiv.className = "condition-group";
groupDiv.dataset.groupId = value;
const groupTitle = document.createElement("div");
groupTitle.className = "condition-group-title";
groupTitle.textContent = group.label;
const grid = document.createElement("div");
grid.className = "condition-grid";
const sortedItems = group.items.sort((a, b) =>
a.crcd2_conditionname.localeCompare(b.crcd2_conditionname)
);
sortedItems.forEach(item => {
const id = `cond_${item.crcd2_conditionid}`;
const option = document.createElement("div");
option.className = "condition-option";
option.dataset.label = item.crcd2_conditionname.toLowerCase();
option.dataset.group = value;
const isChecked = currentFormData[id] ? true : false;
option.innerHTML = `
`;
checkboxElements.push(option);
grid.appendChild(option);
});
groupDiv.appendChild(groupTitle);
groupDiv.appendChild(grid);
wrapperDiv.appendChild(groupDiv);
});
// Update hidden field
const updateHiddenField = () => {
const selected = Array.from(document.querySelectorAll(".condition-checkbox:checked"))
.map(cb => cb.value);
document.getElementById("crcd2_selectedconditions").value = JSON.stringify(selected);
};
document.querySelectorAll(".condition-checkbox").forEach(cb =>
cb.addEventListener("change", updateHiddenField)
);
// Search filter
const filterInput = document.getElementById("conditionFilter");
filterInput.addEventListener("input", e => {
const query = e.target.value.toLowerCase().trim();
// Show/hide individual options
const visibleGroups = {};
checkboxElements.forEach(opt => {
const match = opt.dataset.label.includes(query);
opt.style.display = match ? "" : "none";
if (match) visibleGroups[opt.dataset.group] = true;
});
// Show/hide group wrappers
document.querySelectorAll(".condition-group").forEach(group => {
const groupId = group.dataset.groupId;
group.style.display = visibleGroups[groupId] ? "" : "none";
});
});
}
//========================== END - Filter Conditions Multiselect ==========================/
//========================== START - Handle hospital field visibility ==========================/
function setupHospitalToggle() {
const toggle = document.querySelector('#crcd2_isinhospital input[type="checkbox"]');
const hospitalCell = document.getElementById('crcd2_hospital')?.closest('td');
if (!toggle || !hospitalCell) return;
function updateVisibility() {
hospitalCell.style.display = toggle.checked ? '' : 'none';
}
toggle.addEventListener('change', updateVisibility);
updateVisibility(); // run on page load
}
// Run this after the DOM is ready
setupHospitalToggle();
//========================== START - Handle carer age field visibility - Young vs Adult Carers ==========================/
function handleCarerAgeFieldVisibility(options) {
console.debug('AGE CHECK handleCarerAgeFieldVisibility', options)
// Default options
const {
dobFieldId = 'birthdate_datepicker_description',
fieldsToToggle = [],
threshold = 18
} = options;
const toggleFields = (age) => {
console.debug('AGE CHECK toggleFields', age, fieldsToToggle)
fieldsToToggle.forEach(fieldId => {
const row = document.getElementById(fieldId)?.closest('tr');
if (!row) return;
row.style.display = age < threshold ? '' : 'none';
});
};
const toggleSection = (age) => {
console.debug('AGE CHECK toggleSelection - hide the complete Young Carer section?', age, age >= threshold)
const fieldset = document.querySelector('fieldset[aria-label="Young Carer Information"]');
if (!fieldset) return;
fieldset.style.display = age < threshold ? '' : 'none';
};
const calculateAge = (dateStr) => {
const birthDate = new Date(dateStr);
if (isNaN(birthDate)) return null;
const today = new Date();
let age = today.getFullYear() - birthDate.getFullYear();
const m = today.getMonth() - birthDate.getMonth();
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) age--;
return age;
};
const applyLogic = () => {
const dobInput = document.getElementById(dobFieldId);
if (!dobInput) return;
const age = calculateAge(dobInput.value);
console.debug('AGE CHECK age', age, dobInput.value);
if (age !== null) {
toggleFields(age);
toggleSection(age);
}
};
const waitForDOBInput = async () => {
console.debug('AGE CHECK waitForDOBInput')
let retries = 0;
while (retries < 30) {
const dobInput = document.getElementById(dobFieldId);
console.debug('AGE CHECK dobinput', dobInput)
if (dobInput) {
applyLogic();
dobInput.addEventListener('input', applyLogic);
dobInput.addEventListener('blur', applyLogic);
dobInput.addEventListener('change', applyLogic);
let lastValue = dobInput.value;
setInterval(() => {
if (dobInput.value !== lastValue) {
lastValue = dobInput.value;
applyLogic();
}
}, 500);
break;
}
await new Promise(resolve => setTimeout(resolve, 300));
retries++;
}
if (retries >= 30) console.warn('DOB input not found after waiting');
};
//hide to start with
toggleSection(100);
waitForDOBInput();
}
function disableRecentDatesInCalendarPopup(minAge = 5, defaultBackYears = 20) {
const observer = new MutationObserver(() => {
const today = new Date();
const cutoff = new Date(today);
cutoff.setFullYear(today.getFullYear() - minAge);
const cells = document.querySelectorAll('.bootstrap-datetimepicker-widget td.day');
cells.forEach(cell => {
const dayStr = cell.getAttribute('data-day');
if (!dayStr) return;
const cellDate = new Date(dayStr);
if (cellDate > cutoff) {
cell.classList.add('disabled');
cell.querySelector('button')?.setAttribute('disabled', 'true');
}
});
const yearButton = document.querySelector('.bootstrap-datetimepicker-widget .picker-switch button[title="Select month"]');
const $input = $('input[aria-labelledby="birthdate_label"]');
if (yearButton && $input.length && !$input.data('viewAdjusted')) {
const currentVal = $input.val()?.trim();
const enteredDate = new Date(currentVal);
const isEmptyOrToday =
!currentVal ||
isNaN(enteredDate) ||
enteredDate.toDateString() === today.toDateString();
if (isEmptyOrToday) {
const jumpDate = new Date();
jumpDate.setFullYear(jumpDate.getFullYear() - defaultBackYears);
$input.val(moment(jumpDate).format("MMMM DD, YYYY"));
$input.trigger("change");
$input.data('viewAdjusted', true);
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
// Also prevent manual typing of too-recent dates
function enforceMinAgeOnManualInput(minAge = 5) {
const cutoff = new Date();
cutoff.setFullYear(cutoff.getFullYear() - minAge);
document.addEventListener("blur", function (e) {
if (e.target.id === "birthdate_datepicker_description") {
const entered = new Date(e.target.value);
if (entered > cutoff) {
e.target.value = "";
alert("Date must be at least 5 years ago.");
}
}
}, true);
}
// Call both on DOM ready
disableRecentDatesInCalendarPopup();
enforceMinAgeOnManualInput();
//========================== END - Handle carer age field visibility - Young vs Adult Carers ==========================/
//========================== START - Format mandatory fields question ==========================/
function formatMandatoryFieldsQuestion() {
//Questions marked with * are required fields.
const infoMandatory = Array.from(
document.querySelectorAll('.section-title *')
).find(el =>
el.textContent.includes('Questions marked with * are required fields.')
);
if (!infoMandatory) return;
infoMandatory.style.fontStyle = "italic";
infoMandatory.style.setProperty('color', '#000', 'important');
infoMandatory.style.setProperty(
'font-size',
'var(--bs-body-font-size)',
'important'
);
infoMandatory.textContent = '';
const redStar = document.createElement('span');
redStar.textContent = '*';
redStar.style.color = 'red';
infoMandatory.appendChild(
document.createTextNode('Questions marked with ')
);
infoMandatory.appendChild(redStar);
infoMandatory.appendChild(
document.createTextNode(' are required fields.')
);
}
formatMandatoryFieldsQuestion();
//========================== END - Format mandatory fields question ==========================/
//Just some sanitisation of the data
//check if we have all the data we need to submit the form - if no session started, go back to first page
const path = window.location.pathname;
const isFirstPage = path === '/';
const storageId = sessionStorage.getItem("referralStorageId");
// Redirect if not on first page and missing data
if (!isFirstPage && (!storageId)) {
console.warn("Missing session or referrer type. Redirecting to first step.");
window.location.href = '/';
return;
}
// Override WebForm_OnSubmit to force submission blocking
window.WebForm_OnSubmit = function () {
console.warn("WebForm_OnSubmit blocked form submission.");
return false;
};
function waitForWebApi(callback) {
if (typeof window.webapi !== "undefined" && typeof window.webapi.safeAjax === "function") {
console.log("webapi is available, executing script...");
callback();
} else {
console.log("webapi not available yet, retrying...");
setTimeout(function () { waitForWebApi(callback); }, 500); // Check every 500ms
}
}
function getCurrentFormName() {
let form = document.getElementById("liquid_form");
//now find child with id EntityFormPanel and then get its parent id - get that value as the currentForm name
let currentFormName = form.querySelector('#EntityFormPanel').parentElement.id;
//hack to make sure we store both parent and second parent data because its the same form with the same parent element id
if (path.endsWith(referralPaths['second_parent_or_guardian'])) {
currentFormName = currentFormName + '-second-parent';
}
return currentFormName;
}
waitForWebApi(function () {
let form = document.getElementById("liquid_form"); // Target form by ID
let submitButton = document.getElementById("InsertButton"); // Power Pages Submit Button
if (form && submitButton) {
form.submit = function () {
console.log("Form submission intercepted. Preventing submission.");
};
form.addEventListener("submit", async function (event) {
event.preventDefault(); // Stop default submission
event.stopImmediatePropagation();
// 1️⃣ Check if Power Pages validation failed
let validationSummary = document.querySelector(".validation-summary.alert-danger");
console.log("Checking for validation errors...", validationSummary.style.display);
if (validationSummary && validationSummary.style.display !== "none") {
console.warn("Power Pages validation failed. Errors detected.");
// alert("Please correct the errors before submitting.");
// 2️⃣ Check for invalid fields in the form
let invalidFields = form.querySelectorAll("[aria-invalid='true']");
if (invalidFields.length > 0) {
console.warn("Power Pages validation failed. Fix errors before submitting.");
invalidFields.forEach(field => {
console.warn("Invalid field:", field.name || field.id, "Value:", field.value);
});
// alert("Please fix the required fields before submitting.");
return; // Stop execution if validation failed
}
return; // Stop submission if validation summary is visible
}
console.log("No validation errors. Proceeding with API call...");
try {
//now find child with id EntityFormPanel and then get its parent id - get that value as the currentForm name
const currentFormName = getCurrentFormName();
console.log('currentForm', currentFormName)
let formData = getFormDataFromDynamicsForm(currentFormName);
console.log('get referral data 2', formData);
console.log(formData);
//store the data in the temporary table
const allTempReferralData = await saveFormStepToTempStorage(currentFormName, formData);
//then go to the next step
navigateToPage(currentFormName, allTempReferralData);
return false;
} catch (error) {
console.error("Error:", error);
alert("Something went wrong with the form submission. Please try again.");
}
}, true);
// Intercept Power Pages-specific Submit Button Click
submitButton.addEventListener("click", function (event) {
event.preventDefault(); // Stop default behavior
event.stopImmediatePropagation(); // Prevent Power Pages overrides
console.log('Prevent default click?');
form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));
});
}
});
//=========================== START - Save form step to temporary storage and go to next step ==========================//
function getRefererType(referralData) {
console.debug('getRefererType', referralData);
if (!referralData || !referralData['EntityFormControl_f188b40bbf5d4afd92023a1f77a97afc']?.['crcd2_referrertype']) {
console.log('ARE WE THERE?');
//do we have an element with id crcd2_referrertype_X a radio control, where we can get the value from? X is a number and there are multiple because its a radio control
const referrerTypeElement = document.querySelector('input[id^="crcd2_referrertype_"][type="radio"]:checked');
if (referrerTypeElement) {
return referrerTypeElement.value;
}
}
return referralData['EntityFormControl_f188b40bbf5d4afd92023a1f77a97afc']?.['crcd2_referrertype'];
}
async function navigateToPage(currentForm, allTempReferralData) {
console.log('navigateToPage', currentForm, allTempReferralData)
const general = [
referralPaths['whoareyou'],
referralPaths['carer'],
referralPaths['cared_for'],
referralPaths['info'],
referralPaths['gp_practice'],
referralPaths['parent_or_guardian'], //only if < 18
referralPaths['second_parent_or_guardian'], //only if <18 and ticked the second parten box
referralPaths['school'] //only if <18 AND the carer is in school - crcd2_schoolstatus == 684750000
];
const agencyOrOther = [...general, referralPaths['referrer']];
const school = [
referralPaths['whoareyou'],
referralPaths['carer'],
referralPaths['cared_for'],
referralPaths['info'],
referralPaths['parent_or_guardian'], //only if < 18
referralPaths['second_parent_or_guardian'], //only if <18 and ticked the second parten box
referralPaths['school'],
referralPaths['referrer']
]
const formSequences = {
'684750000': general, // Carer
'684750001': general, // Cared For
'684750002': general, // Parent
'684750004': school, // School
'684750005': [ // GP Doctor
'/',
referralPaths['carer'],
referralPaths['cared_for'],
referralPaths['info'],
referralPaths['gp_practice'],
referralPaths['gp_doctor'],
referralPaths['school'], //only if <18 AND the carer is in school - crcd2_schoolstatus == 684750000
],
'684750003': agencyOrOther, // Agency
'684750006': agencyOrOther // Other
};
const path = window.location.pathname;
console.log('Whats the referral data', referralData);
const refererType = getRefererType(referralData);
let sequence = formSequences[refererType];
if (!sequence) {
console.warn('No sequence defined for referrer type', refererType);
return;
}
// Remove /referral-parent-or-guardian/ and /referral-school/ if DOB >= 18 AND the carer is in school - crcd2_schoolstatus == 684750000
const birthdateStr = allTempReferralData["EntityFormControl_354ecd05d592439fb598b3a11e71f4c5"]?.['birthdate'];
const isInSchool = allTempReferralData["EntityFormControl_354ecd05d592439fb598b3a11e71f4c5"]?.['crcd2_schoolstatus'] === '684750000'; // crcd2_schoolstatus == 684750000
console.debug('schoolStatus - is in school:', isInSchool);
if (birthdateStr) {
const birthdate = new Date(birthdateStr);
const today = new Date();
const age = today.getFullYear() - birthdate.getFullYear();
const monthDiff = today.getMonth() - birthdate.getMonth();
const isBirthdayPassed = monthDiff > 0 || (monthDiff === 0 && today.getDate() >= birthdate.getDate());
const actualAge = isBirthdayPassed ? age : age - 1;
console.log('actualAge', actualAge, birthdateStr);
// Remove school page if not referred by a school and not in school
const refererTypeIsSchool = refererType === '684750004';
sequence = sequence.filter(step => {
const isSchoolPage = step === '/referral-school/';
const isParentPage = step === '/referral-parent-or-guardian/' ||
step === '/referral-parent-or-guardian/referral-second-parent-or-guardian/';
const shouldRemoveSchool = isSchoolPage && !refererTypeIsSchool && !isInSchool;
if (actualAge >= 18) {
// Remove parent pages and school page if not referred by school or in school
return !isParentPage && !shouldRemoveSchool;
} else {
// For under 18s, remove school page only if required
return !shouldRemoveSchool;
}
});
}
// Conditionally remove or keep second parent page
if (path === referralPaths['parent_or_guardian']) {
const toggle = document.querySelector('#crcd2_secondparent input[type="checkbox"]');
const isSecondParent = toggle?.checked;
if (!isSecondParent) {
sequence = sequence.filter(step => step !== referralPaths['second_parent_or_guardian']);
}
}
const currentIndex = sequence.indexOf(path);
console.log('GOING TO NEXT STEP', currentIndex, sequence, path);
if (currentIndex === -1 || currentIndex === sequence.length - 1) {
console.log('No next step found or already at the last step.');
// Last step, trigger referral creation
createReferralFromTempStorage();
return;
}
const nextPath = sequence[currentIndex + 1];
window.location.href = nextPath;
}
async function saveFormStepToTempStorage(currentFormName, formData) {
try {
// Clean up formData before saving
const cleanedFormData = {};
document.querySelectorAll("input[type='checkbox'][id^='crcd2_'][id*='_item']").forEach(cb => {
if (cb.checked) {
cleanedFormData[cb.id] = cb.value;
}
});
// Save values from radio groups transformed into toggles
document.querySelectorAll(".boolean-radio").forEach(function (radioGroup) {
const selectedRadio = radioGroup.querySelector("input[type='radio']:checked");
if (!selectedRadio) return;
const cleanedKey = selectedRadio.name.split('$').pop();
if (cleanedKey) {
cleanedFormData[cleanedKey] = selectedRadio.value;
}
});
for (let key in formData) {
let val = formData[key];
console.log('-------------CLEAN DATA START key and val', key, val);
// Check if the key indicates a radio input (i.e., contains 'ctl00$ContentContainer$')
// and the value is "0" or "1" -> convert to boolean true or false
if (key.includes('ctl00$ContentContainer$') && (val === "0" || val === "1")) {
// Get the cleaned key by removing the prefix (after the last '$')
const cleanedKey = key.split('$').pop();
// Convert "0" to false, "1" to true
cleanedFormData[cleanedKey] = (val === "0") ? false : true;
console.log('-------------CLEAN DATA END radio', key, cleanedKey, cleanedFormData[cleanedKey]);
continue; // Skip to the next field after processing the radio input
}
// Remove the prefix (anything before and including the last dollar sign)
const cleanedKey = key.split('$').pop(); // This will get the part after the last dollar sign
// Skip all datepicker helper fields
if (cleanedKey.endsWith('_datepicker_description')) continue;
if (
typeof val === "undefined" ||
cleanedKey === null ||
val === null ||
val === "" ||
cleanedKey.includes("_selectAll") ||
cleanedKey.toLowerCase().includes("button") ||
cleanedKey.match(/^crcd2_.*_item\d+$/)
) {
console.log('-------------CLEAN DATA NONE', cleanedKey);
continue;
}
// Convert string "on"/"true"/"false" to actual booleans
if (typeof val === "string") {
if (val === "on" || val === "true") {
cleanedFormData[cleanedKey] = true;
continue;
}
if (val === "off" || val === "false") {
cleanedFormData[cleanedKey] = false;
continue;
}
}
// Clean up data formats, like birthdate
if (typeof val === "string" && /^\d{4}-\d{2}-\d{2}T/.test(val)) {
console.log('-------------CLEAN DATA END 0 key and val', cleanedKey, val);
cleanedFormData[cleanedKey] = val.split("T")[0]; // Keep only the date part
continue;
}
// Only store checked condition checkboxes
// Get only checked condition checkboxes from the DOM
const checkedConditions = Array.from(
document.querySelectorAll("input[type='checkbox'][id^='cond_']:checked")
).map(input => [input.id, input.value]); // [key, value]
if (cleanedKey.startsWith("cond_")) {
const isChecked = checkedConditions.some(([id]) => id.endsWith(cleanedKey));
if (!isChecked) {
console.log('-------------SKIP UNCHECKED CONDITION', cleanedKey);
continue;
}
}
cleanedFormData[cleanedKey] = val;
console.log('-------------CLEAN DATA END 2 key and val', cleanedKey, val);
}
//now get the curren session id from storage:
let storageId = sessionStorage.getItem("referralStorageId");
if (!storageId) {
const generatedId = crypto.randomUUID();
const payload = {
"crcd2_id": generatedId,
"crcd2_tempdata": JSON.stringify({ [currentFormName]: cleanedFormData })
};
await webapi.safeAjax({
type: "POST",
url: "/_api/crcd2_referralformtempdatas",
contentType: "application/json",
data: JSON.stringify(payload)
});
sessionStorage.setItem("referralStorageId", generatedId);
console.log("Created new temp storage with ID:", generatedId);
return payload;
} else {
console.log("Updating existing temp storage record:", storageId);
const existing = await webapi.safeAjax({
type: "GET",
url: `/_api/crcd2_referralformtempdatas?$filter=crcd2_id eq '${storageId}'&$top=1&$select=crcd2_tempdata`
});
const value = existing.value?.[0];
const sysId = value?.crcd2_referralformtempdataid;
if (!sysId) {
//temp data doesn't exist any longer on the server, so start again, clear session storage and call the function agaain
sessionStorage.removeItem("referralStorageId");
console.warn("Temp data not found on server. Starting fresh.");
//TODO saveFormStepToTempStorage(currentFormName, formData);
return;
}
let fullData = {};
try {
fullData = value?.crcd2_tempdata ? JSON.parse(value.crcd2_tempdata) : {};
} catch {
console.warn("Could not parse existing form content, starting fresh.");
}
fullData[currentFormName] = cleanedFormData;
console.log("Full data to be saved:", fullData);
await webapi.safeAjax({
type: "PATCH",
url: `/_api/crcd2_referralformtempdatas(${sysId})`,
contentType: "application/json",
data: JSON.stringify({
"crcd2_tempdata": JSON.stringify(fullData)
})
});
console.log("Updated temp storage record:", storageId, sysId, fullData);
return fullData;
}
} catch (error) {
console.error("Error saving form step to temp storage:", error);
alert("There was an error saving your progress. Please try again.");
}
}
//=========================== END - Save form step to temporary storage and go to next step ==========================//
async function findPersonByNameOrEmail(data) {
const email = data.emailaddress1 || "";
const name = data.firstname || "";
if (!email && !name) return null;
const filter = email
? `$filter=emailaddress1 eq '${email}'`
: `$filter=firstname eq '${name}'`;
const result = await webapi.safeAjax({
type: "GET",
url: `/_api/contacts?${filter}&$select=contactid`
});
return result.value?.[0]?.contactid || null;
}
//=========================== START - collect all form data from the forms so we can store it ==========================//
function getFormDataFromDynamicsForm() {
console.log('getFormDataFromDynamicsForm')
let formData = {};
// Select all input, select, and textarea fields inside the form
let inputs = document.querySelectorAll("#liquid_form input, #liquid_form select, #liquid_form textarea");
console.log('inputs', inputs)
inputs.forEach(input => {
let fieldName = input.getAttribute("id");
console.log('fieldName', fieldName, input.type, input.value)
if(fieldName === null || fieldName === undefined) {
console.warn('Field name is null or undefined, skipping input:', input);
return; // Skip if fieldName is not defined
}
if (input.type === "radio") {
//radios don't have an aria-label, so we need to get the name of the radio group
fieldName = input.name;
if (input.checked) {
formData[fieldName] = input.value;
}
} else if (input.type === "checkbox" && !input.classList.contains('msos-label') && input.checked) { //and does not have class msos-label
formData[fieldName] = input.checked;
// formData[fieldName] = input.value;
} else if (input.type === "hidden" && input.value.startsWith('[{')) {
// this is to get fancy multi choice options in a comma separated string
// If the hidden input contains a JSON array (multi-select)
try {
let valueArray = JSON.parse(input.value); // Parse the JSON string
let valueString = valueArray.map(item => item.Value).join(','); // Get the Value properties and join them
formData[fieldName] = valueString; // Store the comma-separated string in formData
} catch (error) {
console.error('Failed to parse hidden field value:', input.value, error);
}
} else if(input.type !== "hidden") {
formData[fieldName] = input.value.trim(); // Store input value
}
console.log('collected field:', fieldName, formData[fieldName])
});
console.log("Collected Form Data:", formData);
return formData;
}
//=========================== END - collect all form data from the forms so we can store it ==========================//
//=========================== START - We're done, create the referral ==========================//
async function createReferralFromTempStorage() {
// Step 1: Get the referral data from the temp table in dynamics via the api
const storageId = sessionStorage.getItem("referralStorageId");
if (!storageId) {
console.warn("Session expired. Please restart the referral.");
if(window.location.pathname !== '/') {
window.location.href = '/'; // Redirect to the first page
}
return;
}
try {
const tempData = await webapi.safeAjax({
type: "GET",
url: `/_api/crcd2_referralformtempdatas(crcd2_id='${storageId}')?$select=crcd2_tempdata,crcd2_referralformtempdataid`
});
const referralData = cleanReferralForCreation(JSON.parse(tempData.crcd2_tempdata || "{}"));
console.debug('referralData for creation', referralData)
// Step 2: Submit the referral using the data
const gpPracticeId = await createGPPractice(referralData);
console.log(gpPracticeId, 'gpPracticeId created')
const carerId = await createCarer(referralData, gpPracticeId);
console.log(carerId, 'carerId created')
const caredForId = await createCaredFor(referralData);
console.log(caredForId, 'caredForId created')
const parentId = await createParent(referralData, carerId);
console.log(parentId, 'parentId created')
const schoolId = await createSchool(referralData);
console.log(schoolId, 'schoolId created')
const gpDoctorId = await createGPDoctor(referralData);
console.log(gpDoctorId, 'gpDoctorId created')
const otherReferrerId = await createOtherReferrer(referralData);
console.log(otherReferrerId, 'otherReferrerId created')
// Create the referral with all the collected IDs
const referralId = await createReferral(referralData, carerId, caredForId, parentId, schoolId, gpPracticeId, gpDoctorId, otherReferrerId);
console.log('referralId', referralId)
//now Clean up the temp data
if (!referralId) {
console.warn("Referral creation failed. Please try again.");
alert("There was an error creating your referral. Please try again.");
return;
}
//TEMP TODO UNCOMMENT AGAIN
await deleteTempStorage(tempData.crcd2_referralformtempdataid);
//no redirect to the thank you page
window.location.href = '/thank-you/';
} catch (error) {
console.error("Error fetching referral data:", error.message);
alert("There was an error fetching your referral data. Please try again.");
}
}
function cleanReferralForCreation(referralData) {
let cleanedData = {};
// Loop through each form block
for (const formKey in referralData) {
const section = referralData[formKey];
const sectionCleaned = { ...section };
const keysToJoin = new Set();
// Step 1: Combine _itemX fields into arrays
for (const key in sectionCleaned) {
if (key.includes("_item")) {
const baseKey = key.split("_item")[0];
if (!Array.isArray(sectionCleaned[baseKey])) {
sectionCleaned[baseKey] = [];
}
sectionCleaned[baseKey].push(sectionCleaned[key]);
keysToJoin.add(baseKey);
}
}
// Step 2: Join only combined keys
keysToJoin.forEach((key) => {
sectionCleaned[key] = sectionCleaned[key].join(",");
});
// Step 3: Remove all *_itemX keys
Object.keys(sectionCleaned).forEach((key) => {
if (key.includes("_item")) {
delete sectionCleaned[key];
}
});
cleanedData[formKey] = sectionCleaned;
//Remove text content for toggle fields that are set to NO/false
cleanedData = stripHiddenToggleFields(cleanedData);
}
console.debug("Cleaned referral data for creation:", cleanedData);
return cleanedData;
}
async function deleteTempStorage(tempDataId) {
// Clean up temp data from Dynamics
webapi.safeAjax({
type: "DELETE",
url: `/_api/crcd2_referralformtempdatas(${tempDataId})`,
success: function () {
console.log("Temp data deleted successfully.");
sessionStorage.removeItem("referralStorageId");
},
error: function (error) {
console.warn("Failed to delete temp data:", error);
}
});
}
async function createPerson(referralData, personData, personTypeValue) {
console.log('createPerson', referralData, personData, personTypeValue)
if (!personData) {
console.log(`No data found for formKey ${personTypeValue}. Skipping person creation.`);
return null;
}
// Extract cond_ values
const conditionIds = Object.keys(personData)
.filter(k => k.startsWith('cond_') && personData[k])
.map(k => k.replace('cond_', ''));
if ('conditionFilter' in personData) {
delete personData.conditionFilter;
}
console.debug('conditionIds', conditionIds);
const existing = await webapi.safeAjax({
type: "GET",
url: `/_api/contacts?$filter=firstname eq '${personData.firstname}' and lastname eq '${personData.lastname}' and emailaddress1 eq '${personData.emailaddress1}'&$top=1&$select=contactid,crcd2_persontype`,
contentType: "application/json"
});
if (existing.value.length > 0) {
const contact = existing.value[0];
const id = contact.contactid;
let currentTypes = contact.crcd2_persontype?.split(",").map(v => parseInt(v)) || [];
if (!currentTypes.includes(personTypeValue)) {
currentTypes.push(personTypeValue);
await webapi.safeAjax({
type: "PATCH",
url: `/_api/contacts(${id})`,
contentType: "application/json",
data: JSON.stringify({
crcd2_persontype: currentTypes.join(",")
})
});
console.log("Updated existing person with new persontype value");
}
if (conditionIds.length) {
await linkConditionsToContact(id, conditionIds);
}
return id;
}
let cleanedData = { ...personData, crcd2_persontype: `${personTypeValue}` };
//remove the condition ids from the cleanedData object
Object.keys(cleanedData).forEach(k => {
if (k.startsWith('cond_')) delete cleanedData[k];
});
console.debug('cleanedData for person creation');
console.debug(cleanedData);
const newId = await new Promise((resolve, reject) => {
webapi.safeAjax({
type: "POST",
url: "/_api/contacts",
contentType: "application/json",
data: JSON.stringify(cleanedData),
success: function (data, textStatus, xhr) {
const entityId = xhr.getResponseHeader("entityid");
console.log("New person ID:", entityId);
resolve(entityId);
},
error: function (xhr, status, error) {
console.error("Error creating person:", error);
reject(error);
}
});
});
if (conditionIds.length) {
await linkConditionsToContact(newId, conditionIds);
}
return newId;
}
async function linkConditionsToContact(contactId, conditionIds) {
if (!contactId || !conditionIds?.length) return;
const baseUrl = window.location.origin;
for (const conditionId of conditionIds) {
try {
await webapi.safeAjax({
type: "POST",
url: `/_api/contacts(${contactId})/crcd2_person_condition/$ref`,
contentType: "application/json",
data: JSON.stringify({
"@odata.id": `${baseUrl}/_api/crcd2_conditions(${conditionId})`
})
});
console.log(`Linked condition ${conditionId} to contact ${contactId}`);
} catch (err) {
console.error(`Error linking condition ${conditionId} to contact ${contactId}`, err);
}
}
}
async function createOrganisation(organisationData) {
console.log('createOrganisation', organisationData)
// Check if organisation exists by name
let filter = `name eq '${organisationData.name.replace(/'/g, "''")}'`;
const existing = await webapi.safeAjax({
type: "GET",
url: `/_api/accounts?$filter=${filter}&$top=1&$select=accountid`,
contentType: "application/json"
});
if (existing.value.length > 0) {
return existing.value[0].accountid;
}
// Create new organisation
const newOrgId = await new Promise((resolve, reject) => {
webapi.safeAjax({
type: "POST",
url: "/_api/accounts",
contentType: "application/json",
data: JSON.stringify(organisationData),
success: function (data, textStatus, xhr) {
const entityId = xhr.getResponseHeader("entityid");
console.log("New organisation created with ID:", entityId);
resolve(entityId);
},
error: function (xhr, status, error) {
console.error("Error creating organisation:", error);
reject(error);
}
});
});
return newOrgId;
}
async function createConnection(fromContactId, toContactId, role1Id, role2Id) {
const payload = {
"record1id_contact@odata.bind": `/contacts(${fromContactId})`,
"record1roleid@odata.bind": `/connectionroles(${role1Id})`,
"record2id_contact@odata.bind": `/contacts(${toContactId})`,
"record2roleid@odata.bind": `/connectionroles(${role2Id})`
};
try {
await webapi.safeAjax({
type: "POST",
url: "/_api/connections",
contentType: "application/json",
data: JSON.stringify(payload)
});
console.log(`Created connection from ${fromContactId} to ${toContactId} using role ${role1Id}`);
} catch (error) {
console.error("Error creating connection:", error);
}
}
async function createCarer(referralData, gpPracticeId) {
console.log('createCarer', referralData);
const personData = referralData['EntityFormControl_354ecd05d592439fb598b3a11e71f4c5'];
const persontypeValue = 684750000; // 'Carer'
//if we have a GP Practice Id, connect as GP Practice to the Carer
if(gpPracticeId === null || gpPracticeId === undefined) {
console.warn("No GP Practice ID provided, skipping connection to GP Practice.");
} else {
personData["crcd2_GPPractice@odata.bind"] = `/accounts(${gpPracticeId})`;
}
return await createPerson(referralData, personData, persontypeValue);
}
async function createCaredFor(referralData) {
const personData = referralData['EntityFormControl_5a7b8afdbbf248bcbe560b0f9010e47e'];
const persontypeValue = 684750001; // Cared For
return await createPerson(referralData, personData, persontypeValue);
}
async function createParent(referralData, carerId) {
const firstParentData = referralData['EntityFormControl_47e9a6105fd54bdd954a27194448a2d3'];
//check if the key of the second parent exists in the referralData array
console.log('firstParentData', firstParentData);
if (firstParentData &&
firstParentData['crcd2_secondparent'] !== 'false' &&
referralData['EntityFormControl_47e9a6105fd54bdd954a27194448a2d3-second-parent']) {
const secondParentData = referralData['EntityFormControl_47e9a6105fd54bdd954a27194448a2d3-second-parent'];
await actuallyCreateParent(secondParentData, carerId);
}
return await actuallyCreateParent(firstParentData, carerId);
}
async function actuallyCreateParent(personData, carerId) {
const persontypeValue = 684750003; // Other
if (!personData) {
console.log("No parent form data found, skipping parent creation.");
return null;
}
const parentId = await createPerson(referralData, personData, persontypeValue);
const PARENT_ROLE_ID = "EDA69FC6-0B5F-44FB-B584-7DFEB8A925AF";
const CHILD_ROLE_ID = "DF0BF69F-333C-4E9B-86E7-4FF737BC9343";
if(!parentId) {
console.warn("Parent ID is not available. Skipping connection creation.");
} else {
// Parent → Carer
await createConnection(parentId, carerId, PARENT_ROLE_ID, CHILD_ROLE_ID);
// Carer → Parent (reciprocal)
// await createConnection(carerId, parentId, CHILD_ROLE_ID);
}
return parentId;
}
async function createSchool(referralData) {
// Check if the key exists in referralData
if (!referralData['EntityFormControl_ade9444c5dd345eea53e3222227250a9']) {
console.warn("School data not found in referralData.");
return null; // Return null if the key doesn't exist
}
//first check if the school already exists
//if not, create the school
//then return the school id
const organisationData = {
...referralData['EntityFormControl_ade9444c5dd345eea53e3222227250a9'],
businesstypecode: 1 // School
};
const schoolId = await createOrganisation(organisationData);
return schoolId;
}
async function createGPPractice(referralData) {
if (!referralData['EntityFormControl_97836fa7f3ca4775a82800f6bff81262']) {
console.warn("GP practice data not found in referralData.");
return null; // Return null if the key doesn't exist
}
//first check if the GP practice already exists
//if not, create the GP practice
//then return the GP practice id
console.log('createGPPractice', referralData)
const organisationData = {
...referralData['EntityFormControl_97836fa7f3ca4775a82800f6bff81262'],
businesstypecode: 684750002 // GP Practice
};
console.log('createGPPractice', organisationData)
const gpPracticeId = await createOrganisation(organisationData);
return gpPracticeId;
}
async function createGPDoctor(referralData) {
//first check if the GP doctor already exists
//if not, create the GP doctor
//then return the GP doctor id
const personData = referralData['EntityFormControl_26623ae5580b4cbe8b24e63087bd2ea0']; // update with actual key
const persontypeValue = 684750003; // GP Doctor
return await createPerson(referralData, personData, persontypeValue);
}
async function createOtherReferrer(referralData) {
//first check if the other referrer already exists
//if not, create the other referrer
//then return the other referrer id
const personData = referralData['EntityFormControl_a65cf9e8449a4e3989a4af8a4c14dfd3']; // update with actual key
const persontypeValue = 684750004; // Other professional
//if the Organisation crcd2_organisationtemp has a value, then we need to create an organisation and connect it to the person
if(personData?.crcd2_organisationtemp) {
const organisationData = {
name: personData.crcd2_organisationtemp,
businesstypecode: personData?.crcd2_organisationtype || 684750003 // Other
};
const organisationId = await createOrganisation(organisationData);
if(organisationId) {
personData["crcd2_Organisation@odata.bind"] = `/accounts(${organisationId})`;
}
}
return await createPerson(referralData, personData, persontypeValue);
}
async function createReferral(referralData, carerId, caredForId, parentId, schoolId, gpPracticeId, gpDoctorId, otherReferrerId) {
const referralInfoData = referralData['EntityFormControl_7cda038b6d9c4d03bef00a285e36b9d5'];
const refererType = getRefererType(referralData);
const payload = {
"crcd2_CaredForPerson@odata.bind": `/contacts(${caredForId})`,
"customerid_contact@odata.bind": `/contacts(${carerId})`, //Carer
"crcd2_referralentrymethod": "684750001", //Entry Method - Web Form
"crcd2_referralsource": mapReferralSource(referralData), //Referral Source
...referralInfoData
};
//connect the GP practice crcd2_GPPractice if we have a GP practice id
if (gpPracticeId) {
payload["crcd2_GPPractice@odata.bind"] = `/accounts(${gpPracticeId})`;
}
//connect the school crcd2_school
if (schoolId) {
payload["crcd2_School@odata.bind"] = `/accounts(${schoolId})`;
}
// Map referrer to contact or account
//set refferal source
switch (refererType) {
case '684750004': // School
payload["crcd2_Referrer@odata.bind"] = `/contacts(${otherReferrerId})`;
//set as referring organisation
payload["crcd2_ReferrerOrganisation@odata.bind"] = `/accounts(${schoolId})`;
//if referrer is school, set 'is school aware' to true crcd2_schoolawareofreferral
payload["crcd2_schoolawareofreferral"] = true;
payload.crcd2_referralsource = 684750003; //School
break;
case '684750005': // GP Person
payload.crcd2_referralsource = 684750001; //Agency/Professional
payload["crcd2_Referrer@odata.bind"] = `/contacts(${gpDoctorId})`;
//set GP practice as referring organisation
payload["crcd2_ReferrerOrganisation@odata.bind"] = `/accounts(${gpPracticeId})`;
break;
case '684750003': // Agency
case '684750006': // Other
payload.crcd2_referralsource = 684750001; //Agency/Professional
payload["crcd2_Referrer@odata.bind"] = `/contacts(${otherReferrerId})`;
break;
case '684750002': // Parent or Guardian
payload.crcd2_referralsource = 684750000; //parent or family etc.
payload["crcd2_Referrer@odata.bind"] = `/contacts(${parentId})`;
break;
case '684750000': // Carer
payload.crcd2_referralsource = 684750002; // Self
payload["crcd2_Referrer@odata.bind"] = `/contacts(${carerId})`;
break;
case '684750001': // Cared For
payload.crcd2_referralsource = 684750000; //parent or family etc.
payload["crcd2_Referrer@odata.bind"] = `/contacts(${carerId})`;
break;
}
// Submit referral
const referralId = await new Promise((resolve, reject) => {
webapi.safeAjax({
type: "POST",
url: "/_api/incidents",
contentType: "application/json",
data: JSON.stringify(payload),
success: function (data, textStatus, xhr) {
const entityId = xhr.getResponseHeader("entityid");
console.log("Referral created with ID:", entityId);
resolve(entityId);
},
error: function (xhr, status, error) {
console.error("Error creating referral:", error);
reject(error);
}
});
});
return referralId;
}
function mapReferralSource(referralData) {
//set the field 'referralSource' based on the refererType
const refererType = getRefererType(referralData);
const referrerMapping = {
'684750000': '684750002', // Carer -> Self
'684750001': '684750005', // Cared For -> Cared for person
'684750002': '684750000', // Parent/Guardian -> Parent/Guardian, other Family Member
'684750004': '684750003', // School -> School
'684750005': '684750006', // GP Doctor -> GP
'684750003': '684750001', // Agency -> Agency/Professional
'684750006': '684750001' // Other -> Agency/Professional
};
return referrerMapping[refererType] || '684750001'; // Default to Self if not found, but that shouldn't happen
}
//=========================== END - We're done, create the referral ==========================//
});