// 2024/04/16a - SharePoint document overwrite changes. // 2023/07/26 - From DoBC merged with latest DoBC updates // This JS file contains common functions to use on ADX Forms // Used to read Querystring params function getParameterByName(name, url) { if (!url) url = window.location.href; name = name.replace(/[\[\]]/g, "\\$&"); var regex = new RegExp("[?&]" + name + "(=([^]*)|&|#|$)"), results = regex.exec(url); if (!results) return null; if (!results[2]) return ''; return decodeURIComponent(results[2].replace(/\+/g, " ")); } // ####################################################################################### // Deselect selected Radio button by clicking again // ####################################################################################### // Find all radios in the form and attach an onclick handler so that if the radio is selected and is clicked again, it is deselected. function Deselect_AttachRadioHandlers() { var radios = $("input[type='radio']"); radios.mousedown(function () { Deselect(this); }); // If the radio is already checked, another click will deselect it } // Deselects a radio button if it is selected and clicked again. Must use a bit of a delay for this to work. function Deselect(radio) { if (radio.checked) { setTimeout(function () { radio.checked = false; // Uncheck this radio // Fire the onchange event manually if ("createEvent" in document) { var evt = document.createEvent("HTMLEvents"); evt.initEvent("change", false, true); radio.dispatchEvent(evt); } else { radio.fireEvent("onchange"); } // If this radio is deselected, we need to check if we need to re-perform show/hide of other controls. if (radio.parentElement.tagName == "SPAN") { // Regular select one item radio button ShowHide_Execute(radio.parentElement.id); } else { // Matrix radio button var parentTable = $(radio).closest("table")[0]; ShowHide_Execute(parentTable.id); } }, 200); } } // ####################################################################################### // End - Deselect selected Radio button by clicking again // ####################################################################################### // ####################################################################################### // Show/Hide fields based on other field values. Also Required Fields. // ####################################################################################### // Cycles through all of the show/hide definitions in the array and attaches the ShowHide onchange event handler function ShowHide_AttachOnChangeHandlers() { if (!_showHideArray) { return; } var prevSourceField = ""; for (var i = 0; i < _showHideArray.length; i++) { var defn = _showHideArray[i]; // e.g. dobc_xxx1:262720019|dobc_xxx2:262720019*dobc_div_sos_careproviders_other var sourceDefn = defn.split("*")[0]; // e.g. dobc_xxx1:262720019|dobc_xxx2:262720019 // Cycle though each potential field defined in the source definition (usually there is only one) for (var j = 0; j < sourceDefn.split("|").length; j++) { var fieldDefn = sourceDefn.split("|")[j]; // e.g. dobc_xxx1:262720019 var sourceField = fieldDefn.split(":")[0]; // Finally we have the field (e.g. dobc_xxx1) if (sourceField == prevSourceField) { continue; } // Use this to avoid attaching duplicate handlers prevSourceField = sourceField; var f = $("#" + sourceField); if (f.length) { f.change(function () { ShowHide_Execute(this.id); }); } else { console.log("Warning, from the Show/Hide definitions, the field '" + sourceField + "' was not found."); } } } } // Executes the show/hide definition for the provided field as defined in the array _showHideArray. An example show/hide definition is: // - dobc_commitnewpatients:262720000,262720002*dobc_commitnumberpatients // The above means for the "dobc_commitnewpatients" field, if the value is either (262720000,262720002), then show field "dobc_commitnumberpatients". Otherwise hide. // NOTE: If multiple definitions are for the same target (field/section/tab), then all conditions must evaluate to show for the target to be shown. // NOTE2: Use value of "#NOT-NULL#" to match any non-blank value function ShowHide_Execute(fieldname) { // Init/Validation if (typeof _showHideArray == "undefined") { return; } if (!fieldname) { return; } var subsetShowHideArray = new Array(); // Array that will hold only the definitions that we are interested in var targetsArray = new Array(); // Array that holds the targets that we are interested in because they are associated with the provided fieldname // Go through all of the definitions and pull out the ones where: // Pass 1 - The source field matches the provided fieldname // Pass 2 - The definition contains the same target as those determined from Pass 1 // We need to do this 2 pass so that we can handle the situation when multiple definitions point to the same target. In this case both definitions must result in true to display the target (these are "AND" conditions). // - Pass 1 for (var i = 0; i < _showHideArray.length; i++) { var defn = _showHideArray[i]; var sourceDefn = defn.split("*")[0]; // e.g. dobc_xxx1:262720019|dobc_xxx2:262720019 // Cycle though each potential field defined in the source definition (usually there is only one) for (var j = 0; j < sourceDefn.split("|").length; j++) { var fieldDefn = sourceDefn.split("|")[j]; // e.g. dobc_xxx1:262720019 var sourceField = fieldDefn.split(":")[0]; // Finally we have the field (e.g. dobc_xxx1) if (fieldname == sourceField) { subsetShowHideArray.push(defn); } } } // - Get the targets for (var j = 0; j < subsetShowHideArray.length; j++) { var defn = subsetShowHideArray[j]; var fieldSectionTab = defn.split("*")[1]; targetsArray.push(fieldSectionTab); } // - Pass 2 for (var j = 0; j < _showHideArray.length; j++) { var defn = _showHideArray[j]; var fieldSectionTab = defn.split("*")[1]; if (targetsArray.indexOf(fieldSectionTab) >= 0) { // Same target field/section/tab if (subsetShowHideArray.indexOf(defn) < 0) { // Has not already been added to the subset array subsetShowHideArray.push(defn); } } } // Now execute all of the definitions in the subset array. We will need to remember any field/section/tabs that are hidden var hiddenTargetsArray = new Array(); // This array stores any field/section/tab that has been hidden. Remember, since definitions are ANDed, if a definition hides a field, then any subsequent definitions that try to show the same field cannot. for (var j = 0; j < subsetShowHideArray.length; j++) { // Parse the definition var defn = subsetShowHideArray[j]; var sourceDefn = defn.split("*")[0]; // e.g. dobc_xxx1:262720019|dobc_xxx2:262720019 var fieldSectionTab = defn.split("*")[1]; var valueMatched = false; for (var k = 0; k < sourceDefn.split("|").length; k++) { // Loop in case we have multiple source fields and values to match (this is an OR condition) var fieldDefn = sourceDefn.split("|")[k]; var sourceField = fieldDefn.split(":")[0]; var sourceValues = fieldDefn.split(":")[1]; // May have multiple values separated by comma var currentValue = GetValueString(sourceField); var hasValue = HasValue(sourceField); // Show if any of the values match for (var i = 0; i < sourceValues.split(",").length; i++) { var valueToMatch = sourceValues.split(",")[i] + ""; // Convert to string if ((valueToMatch == currentValue) || ((valueToMatch == "#NOT-NULL#") && (hasValue))) { if (!(fieldSectionTab in hiddenTargetsArray)) { // Check that we haven't previously hidden this target. If so, skip the showing because these are AND conditions ShowFieldSectionTab(fieldSectionTab); } valueMatched = true; } } } // If no value matched, then hide perform hide. if (!valueMatched) { HideFieldSectionTab(fieldSectionTab); hiddenTargetsArray[fieldSectionTab] = true; // Remember that we have hidden this target } } } // Executes the show/hide on all fields as found in the array _showHideArray. This is useful for initializing the display of a form when it first loads. function ShowHide_Execute_All() { var prevSourceField = ""; for (var i = 0; i < _showHideArray.length; i++) { var defn = _showHideArray[i]; // e.g. dobc_xxx1:262720019|dobc_xxx2:262720019*dobc_div_sos_careproviders_other var sourceDefn = defn.split("*")[0]; // e.g. dobc_xxx1:262720019|dobc_xxx2:262720019 // Cycle though each potential field defined in the source definition (usually there is only one) for (var j = 0; j < sourceDefn.split("|").length; j++) { var fieldDefn = sourceDefn.split("|")[j]; // e.g. dobc_xxx1:262720019 var sourceField = fieldDefn.split(":")[0]; // Finally we have the field (e.g. dobc_xxx1) if (sourceField == prevSourceField) { continue; } // Use this to avoid attaching duplicate handlers prevSourceField = sourceField; ShowHide_Execute(sourceField); } } } // Sets a field to be required if the parent field(s) are of certain values // requiredField - the field which we want to (conditionally) make required // requiredFieldLabel - display label of the field in question // parentFieldDefn - (optional) parent field(s) and the conditional value(s) when we will make the field required (e.g. dobc_accountrolecode:262720000,262720001&dobc_ld_img:1) - if null, this means the field is always required // errorMsg - (optional) The custom error message to display. If not provided, then the standard error message will be used. // validationFailValue - (optional) If the field equals this value, the validation will fail. Used primarily for checkboxes where unchecked equates to a value of "0". function SetRequiredField(requiredField, requiredFieldLabel, parentFieldDefn, customErrorMessage, validationFailValue) { // Init/Validation if ($("#" + requiredField).length == 0) { return; // Field not found } // If there are no validations on the page, then Page_Validators will be empty. if (typeof (Page_Validators) == "undefined") { console.log("Page_Validators not found because there are no fields that require validation as defined in metadata. The field '" + requiredField + "' cannot be set to required."); return; } // Remove any required validators for this field (if any exists). This will avoid double validators for the same field. RemoveRequiredValidator(requiredField); // Create new validator var newValidator = document.createElement('span'); newValidator.style.display = "none"; newValidator.id = "RequiredFieldValidator" + requiredField; newValidator.controltovalidate = requiredField; newValidator.errormessage = "" + requiredFieldLabel + " is a required field."; if (customErrorMessage) { newValidator.errormessage = "" + customErrorMessage + ""; } newValidator.validationGroup = ""; // Set this if you have set ValidationGroup on the form newValidator.initialvalue = ""; newValidator.evaluationfunction = function () { // Check the parent fields to see if they are set to a value where we want to make a child field required if (parentFieldDefn) { var parentFields = parentFieldDefn.split("&"); for (var i = 0; i < parentFields.length; i++) { var parentFieldName = parentFields[i].split(":")[0]; // Parent field name var parentFieldValues = parentFields[i].split(":")[1].split(","); // Array of parent field values that if matches then we want to set the field requirement var currentParentValue = GetValueString(parentFieldName); if (parentFieldValues.indexOf(currentParentValue) < 0) { // Current parent field value is NOT in the list where we want to set the field requirement. Exit immediately. return true; } } } // At this point, the parent fields are all values that we want to check that the child field is required (or the parent fields is not defined) var reqValue = GetValueString(requiredField); if ((reqValue == null) || (reqValue == "") || (reqValue == "undefined") || (reqValue == validationFailValue)) { return false; } else { return true; } }; // Add the new validator to the page validators array: Page_Validators.push(newValidator); // Wire-up the click event handler of the validation summary link $("a[href='#" + requiredField + "_label']").on("click", function () { scrollToAndFocus(requiredField + '_label', requiredField); }); // Add red asterisk $("#" + requiredField + "_label").parent().addClass("required"); //$('#' + requiredField + "_label").after(' *'); //$("#spanRequired_" + requiredField).prev().css("float", "none"); // Remove float of previous label element so that the asterisk is on the same line // Add onchange event handler on the parent to hide/show the red asterisk (ONLY if one parent) if (parentFieldDefn) { if (parentFieldDefn.indexOf("&") < 0) { // Only one parent field var parent1 = parentFieldDefn.split(":")[0]; var parentValuesArray = parentFieldDefn.split(":")[1].split(","); $("#" + parent1).change( function () { // Get the value of the agree field var parentValue = GetValueString(parent1); if (parentValuesArray.indexOf(parentValue) < 0) { $("#" + requiredField + "_label").parent().removeClass("required"); } else { $("#" + requiredField + "_label").parent().addClass("required"); } } ); $("#" + parent1).change(); // Execute the change event so that the red asterisk is initialized to either visible or hidden to start } } } // Automatically moves to the next field if the max length is achieved. function AutoMoveNextField(obj, e, nextField) { // Init if (!obj) { return; } var value = obj.value; if (!value) { return; } var maxlength = obj.maxLength; // Exit if a letter or a number was NOT pressed var charCode = e.which || e.keyCode; var charStr = String.fromCharCode(charCode); if (!(/[a-z0-9]/i.test(charStr))) { // NOT letter or number pressed return; } // If the value hits the maximum length, then move to the next field if (value.length >= maxlength) { $("#" + nextField).focus().select(); return; } } // Removes the validator with the exact name provided function RemoveValidator(validatorName) { $.each(Page_Validators, function (index, validator) { if ((validator) && (validator.id == validatorName)) { Page_Validators.splice(index, 1); } }); } // Remove the required field validation for the provided field. //eg. RemoveRequiredValidator("customerid") function RemoveRequiredValidator(fieldName) { if (typeof (Page_Validators) == "undefined") { return; } // Make sure Page_Validators exists $.each(Page_Validators, function (index, validator) { if ((validator) && (validator.id == "RequiredFieldValidator" + fieldName)) { Page_Validators.splice(index, 1); } }); $("#" + fieldName + "_label").parent().removeClass("required"); } // Removes all required field validators currently on the form. Used primarily for Save as Draft, where we want the user to // save the record as draft even if there are some required fields unfilled on the current form. function RemoveAllRequiredValidators() { if (!Page_Validators) { return; } // Get a list of required field validators that we want to remove var arrayFieldsToRemoveValidator = new Array(); for (var i = 0; i < Page_Validators.length; i++) { var validatorId = Page_Validators[i].id; if (validatorId.indexOf("RequiredFieldValidator") >= 0) { // Determine if this is a required field validator. These will have IDs of "RequiredFieldValidatorXXX" var field = validatorId.replace("RequiredFieldValidator", ""); arrayFieldsToRemoveValidator.push(field); } } // Remove the validators for (var i = 0; i < arrayFieldsToRemoveValidator.length; i++) { var field = arrayFieldsToRemoveValidator[i]; RemoveRequiredValidator(field); } } // ####################################################################################### // End - Show/Hide fields based on other field values // ####################################################################################### // ####################################################################################### // Hide/Show helpers // ####################################################################################### // Setting to control whether or not we want to reset (blank) out field values when questions are hidden as part of show/hide. The page implementing this feature should set the _showHide_ResetInputFieldsOnHide to true to enable this. if (typeof _showHide_ResetInputFieldsOnHide === 'undefined') { var _showHide_ResetInputFieldsOnHide = false; } function HideField(fieldName) { var f = $("#" + fieldName); if (f.length) { f.closest("td").hide(); // Reset input fields within the container if set to if (_showHide_ResetInputFieldsOnHide) { ResetInputFields(f.closest("td")); } return true; } // If we get here, the field could not be found return false; } function ShowField(fieldName) { var f = $("#" + fieldName); if (f.length) { f.closest("td").show(); return true; } // If we get here, the field could not be found return false; } function ShowSection(sectionName) { var s = $(".section[data-name='" + sectionName + "']"); if (s.length) { s.closest("fieldset").show(); return true; } // If we get here, the section could not be found return false; } function HideSection(sectionName) { var s = $(".section[data-name='" + sectionName + "']"); if (s.length) { s.closest("fieldset").hide(); // Reset input fields within the container if set to if (_showHide_ResetInputFieldsOnHide) { ResetInputFields(s.closest("fieldset")); } return true; } // If we get here, the section could not be found return false; } // Resets (blanks) the value of any form elements found within the jQuery container provided. Used primarily with show/hide where if we are hiding // questions, we will want to blank out any values. function ResetInputFields($j) { // Uncheck any checked radio buttons or checkboxes and trigger the onchange $j.find("input:checked").prop('checked', false).change(); // Blank any input text $j.find("input:text").val("").change(); // Blank any textarea $j.find("textarea").val("").change(); // Reset any "select" dropdowns $j.find("select").prop("selectedIndex", 0).change(); } function ShowTab(tabName) { var t = $(".tab[data-name='" + tabName + "']"); if (t.length) { t.show(); t.prev().show(); // hides the tab's title if one is present return true; } // If we get here, the section could not be found return false; } function HideTab(tabName) { var t = $(".tab[data-name='" + tabName + "']"); if (t.length) { t.hide(); t.prev().hide(); // hides the tab's title if one is present return true; } // If we get here, the section could not be found return false; } // Make all tabs visible. Used to show all content on the web page (again). function ShowAllTabs() { for (var i = 0; i < _allTabs.length; i++) { var t = _allTabs[i]; ShowTab(t); } } // Shows the field, section, or tab in that order until one is found function ShowFieldSectionTab(name) { if (!ShowField(name)) { if (!ShowSection(name)) { ShowTab(name); } } } // Hides the field, section, or tab in that order until one is found function HideFieldSectionTab(name) { if (!HideField(name)) { if (!HideSection(name)) { HideTab(name); } } } // Reverses the order of answers of a question. Handles both single field questions or matrix questions. // REQUIREMENT: The question must be isolated in its own section. function ReverseAnswers(field) { var fieldset = $("#" + field).closest("fieldset"); var tableCount = $(fieldset).find("table").length; if (tableCount > 1) { // Matrix question var sectionTable = $(fieldset).find("table").first(); var subTables = sectionTable.find("table"); $.each(subTables, function (key, value) { var trs = $(value).find("tr"); $.each(trs, function (trKey, trValue) { $(trValue).append($(trValue).find("td").get().reverse()); }); }); } else { // Single field question - horizontal or vertical var inputs = $(fieldset).find("input"); var inputParent = $(inputs).first().parent(); $(inputParent).append($(inputParent).children().get().reverse()); // - Now move labels after the input $.each(inputs, function (inKey, inValue) { var inputId = $(inValue).attr("id"); var label = $("label[for='" + inputId + "']").get(); $(label).insertAfter($(label).next()); // Shift the label to the right one spot (will be to the right of the input) }); } } // Disables a portal subgrid: // (1) Hides any Create buttons // (2) Hides subgrid action buttons // (3) Converts any "Details" or column header links with plain text function DisableSubgrid(gridId) { $(`#${gridId}`).find("div.grid-actions").find("a.create-action").hide(); // Hide any Create buttons for subgrids $(`#${gridId}`).find("div.view-grid").find("div.action").hide(); // Hide any subgrid action buttons // Replace column headers with plain text (hide original link) $(`#${gridId}`).find("div.view-grid").find("thead").find("a").hide().after(function () { return `${this.childNodes[0].nodeValue}` }); // Replace Details links with plain text (hide original link) $(`#${gridId}`).find("div.view-grid").find("a.details-link").hide().after(function () { return `${this.childNodes[0].nodeValue}` }); } // Re-enables a subgrid function EnableSubgrid(gridId) { $(`#${gridId}`).find("div.grid-actions").find("a.create-action").show(); // Show any Create buttons for subgrids $(`#${gridId}`).find("div.view-grid").find("div.action").show(); // Show any subgrid action buttons // Show column headers links $(`#${gridId}`).find("div.view-grid").find("thead").find("a").show(); // Show Details links $(`#${gridId}`).find("div.view-grid").find("a.details-link").show(); // Remove any plain text "LinkReplacementText" $(".LinkReplacementText").remove(); } // Makes all form elements disabled except for the provided specific elements on the exclusions list function MakeFormReadOnly(exclusionsCsv) { // Remember form elements that were already disabled before this function was called $("input[disabled]").attr("disabledBeforeMakeFormReadOnly", true); $("textarea[readonly]").attr("disabledBeforeMakeFormReadOnly", true); $("select[disabled]").attr("disabledBeforeMakeFormReadOnly", true); $("button[disabled]").attr("disabledBeforeMakeFormReadOnly", true); // Make all form elements disabled $("input").prop("disabled", true); $("textarea").prop("readonly", true); $("select").prop("disabled", true); $("button").prop("disabled", true); // Undo the disabled property for each item on the exclusions list if (exclusionsCsv) { for (var i = 0; i < exclusionsCsv.split(",").length; i++) { var exclusion = exclusionsCsv.split(",")[i]; $("#" + exclusion).prop("disabled", false); $("#" + exclusion).prop("readonly", false); $("#" + exclusion).attr("disabledBeforeMakeFormReadOnly", null); } } // Disable all subgrids $("div.subgrid").each(function () { DisableSubgrid(this.id); }); // Exceptions $("button.close").prop("disabled", false); // Do not disable any "close" buttons (e.g. modal close buttons) } // Makes all form elements editable. Reverses what was done by MakeFormReadOnly. function MakeFormEditable() { // Make all disabled form elements now editable $("input[disabled]").prop("disabled", false); $("textarea[readonly]").prop("readonly", false); $("select[disabled]").prop("disabled", false); $("button[disabled]").prop("disabled", false); $("div.subgrid").find("a.btn.action").show(); // Subgrid buttons $("div.subgrid").find("div.dropdown.action").show() // Subgrid actions dropdown // If any elements were marked as disabled before the MakeFormReadOnly call, then make them disabled again $("input[disabledBeforeMakeFormReadOnly]").prop("disabled", true).attr("disabledBeforeMakeFormReadOnly", null); $("textarea[disabledBeforeMakeFormReadOnly]").prop("readonly", true).attr("disabledBeforeMakeFormReadOnly", null); $("select[disabledBeforeMakeFormReadOnly]").prop("disabled", true).attr("disabledBeforeMakeFormReadOnly", null); $("button[disabledBeforeMakeFormReadOnly]").prop("disabled", true).attr("disabledBeforeMakeFormReadOnly", null); $("div.subgrid").find("a.btn.action[disabledBeforeMakeFormReadOnly]").hide().attr("disabledBeforeMakeFormReadOnly", null); // Subgrid buttons $("div.subgrid").find("div.dropdown.action[disabledBeforeMakeFormReadOnly]").hide().attr("disabledBeforeMakeFormReadOnly", null); // Subgrid actions dropdown // Re-enable all subgrids $("div.subgrid").each(function () { EnableSubgrid(this.id); }); } // Returns whether or not the field given has a value that has been entered by the user. For example, a checkbox will have a value of "0" if it is // not selected, but we will return false because the user has not entered a value for this field. function HasValue(fieldname) { // Init var f = $("#" + fieldname); var value = GetValueString(fieldname); // Checkbox if (f.is(":checkbox")) { if (value == "0") { return false; } else { return true; } } // Return whether or not a value exists for this field if ((value != "") && (value != "undefined")) { return true; } else { return false; } } // Replaces text within the label of a field function ReplaceFieldLabelText(fieldname, sourceText, replacementText) { var label = $("#" + fieldname + "_label"); // Get the "label" element label.html(label.html().replace(sourceText, replacementText)); } // Sets text for the label of a field function SetFieldLabelText(fieldname, fieldLabel) { var label = $("#" + fieldname + "_label"); // Get the "label" element label.html(fieldLabel); } // Enables or disables a field // fieldName can be a comma separated list of fields // useReadOnly - whether to use readonly vs disabled. Disabled will not result in the value being sent to the server. // Updated 2024/03/07 function EnableDisableField(fieldName, isEnabled, useReadOnly) { var prop = (useReadOnly) ? "readonly" : "disabled"; // Check if we have a csv of fields to process if (fieldName.indexOf(",") > 0) { var fields = fieldName.split(","); for (var i = 0; i < fields.length; i++) { EnableDisableField(fields[i], isEnabled); } return; } // Special case for "select" if ($("#" + fieldName).is("select")) { var selectHiddenField = fieldName + "_hidden"; if (isEnabled) { // Remove the hidden input field $("#" + selectHiddenField).remove(); } else { if (useReadOnly) { // We should disable the field, but pass the value on to the server. Do this by adding a hidden input field. var hiddenSelectHtml = ""; $("#" + fieldName).after(hiddenSelectHtml); } } // Change the useReadOnly to false (so that the field will be set to "disabled") and continue on with the rest of the code useReadOnly = false; } // Enable/disable the field - use "readonly" if this is an "input" or a "textarea" if ((($("#" + fieldName).is("input")) && ($("#" + fieldName).is("not:input:checkbox"))) // Is "input" but not checkbox || ($("#" + fieldName).is("textarea"))) { // Use "readonly" for input and textareas - these values still get submitted $("#" + fieldName).prop("readonly", !isEnabled); } else { // Just disable the field - these values will not get submitted $("#" + fieldName).prop(prop, !isEnabled); } // For radio buttons, there will be _0, _1, _2 fields if ($("#" + fieldName + "_0[type=radio]").length > 0) { for (var i = 0; i < 10; i++) { var fieldName_Int = fieldName + "_" + i; EnableDisableField(fieldName_Int, isEnabled, useReadOnly); } } // Special case if this is a date field (we need to disable the display label and the calendar image) if ($("#" + fieldName + "[data-type='date']").length > 0) { $("#" + fieldName + "[data-type='date']").parent().find("input[data-date-format]").prop(prop, !isEnabled); if (isEnabled) { $("#" + fieldName + "[data-type='date']").parent().find("span.input-group-addon").on(); } else { $("#" + fieldName + "[data-type='date']").parent().find("span.input-group-addon").off(); } } // Special case for lookup fields (hide the "x" and hourglass buttons) if (($("input#" + fieldName + "_name").length > 0) && ($("input#" + fieldName + "_entityname").length > 0)) { // This is a lookup if there are "_name" and "_entityname" fields if (isEnabled) { $("#" + fieldName).closest("div.input-group").find("button").show(); // Show lookup related buttons $("input#" + fieldName + "_name").prop(prop, false); } else { $("#" + fieldName).closest("div.input-group").find("button").hide(); // Hide lookup related buttons $("input#" + fieldName + "_name").prop(prop, true); } } } // Shortcut function for enabling fields function EnableField(fieldName, useReadOnly) { EnableDisableField(fieldName, true, useReadOnly); } // Shortcut function for disabling fields function DisableField(fieldName, useReadOnly) { EnableDisableField(fieldName, false, useReadOnly); } // Enables or disables a button // The button can be either a regular Input button or an anchor button. For anchor buttons, provide the "title" of the button (not the display name of the button but the HTML "title"). function EnableDisableButton(nameOrTitle, isEnabled) { // Check if we have a csv of buttons to process if (nameOrTitle.indexOf(",") > 0) { var buttons = nameOrTitle.split(","); for (var i = 0; i < buttons.length; i++) { EnableDisableButton(buttons[i], isEnabled); } return; } // Enable/disable Anchor tag if (isEnabled) { // Enable anchor $("a[title='" + nameOrTitle + "']").removeAttr("disabled"); $("a[title='" + nameOrTitle + "']").on("click"); } else { // Disable anchor $("a[title='" + nameOrTitle + "']").attr("disabled", "disabled"); $("a[title='" + nameOrTitle + "']").off("click"); $("a[title='" + nameOrTitle + "']").click(function (e) { e.preventDefault(); }); // Disable clicking on the link (# - which may have jumped to the top of the screen) } // Enable/disable Input $("#" + nameOrTitle).prop("disabled", !isEnabled); } // Shortcut function for enabling buttons function EnableButton(nameOrTitle) { EnableDisableButton(nameOrTitle, true); } // Shortcut function for disabling buttons function DisableButton(nameOrTitle) { EnableDisableButton(nameOrTitle, false); } // Makes all form elements disabled except for the provided specific elements on the exclusions list function MakeFormReadOnly(exclusionsCsv) { // Make all form elements disabled $("input").prop("disabled", true); $("textarea").prop("readonly", true); $("select").prop("disabled", true); $("button").prop("disabled", true); // Undo the disabled property for each item on the exclusions list if (exclusionsCsv) { for (var i = 0; i < exclusionsCsv.split(",").length; i++) { var exclusion = exclusionsCsv.split(",")[i]; $("#" + exclusion).prop("disabled", false); $("#" + exclusion).prop("readonly", false); } } } // ####################################################################################### // End - Hide/Show helpers // ####################################################################################### // ####################################################################################### // Get/Set values // ####################################################################################### // Retrieves the value of the field in string format function GetValueString(fieldname) { // Init var f = $("#" + fieldname); // Checkbox if (f.is(":checkbox")) { if (f.is(":checked")) { return "1"; } else { return "0"; } } // Try to get the value directly if (f.length <= 0) { return ""; } var value = f.val() + ""; // Convert to string // Try as optionset if (value == "") { value = f.find("input:checked").val() + ""; } return value; } // Retrieves the label of the field's value function GetLabelString(fieldname) { // Init var f = $("#" + fieldname); // Checkbox if (f.is(":checkbox")) { if (f.is(":checked")) { return "Yes"; } else { return "No"; } } // Try to get the value directly if (f.length <= 0) { return ""; } var value = f.val() + ""; // Convert to string // Try as optionset if (value == "") { value = f.find("input:checked").text() + ""; } return value; } // fieldId: ID (CRM attribute name) of the field we are setting. // dateValue: The date-time value to set to. This should be of type Date. // From: https://bernado-nguyen-hoan.com/2018/07/24/how-to-set-value-for-date-time-field-by-javascript-in-crm-portal/ - but modified and fixed function SetDateTimeFieldValue(fieldId, dateValue) { //Get the submit field var $submitField = $("#" + fieldId); //Get the display field var $displayField = $submitField.nextAll(".datetimepicker").children("input"); //Get the display date format var dateFormat = $displayField.attr("data-date-format"); //Set the submit field. Remember this needs to be in UTC and the format must be exact. //$submitField.val(moment.utc(dateValue).format("YYYY-MM-DDTHH:mm:ss.SSSSSSS")); // This original code is missing the "Z" $submitField.val(dateValue.toISOString().replace("Z", "0000Z")); // Make sure the date has 7 digit milliseconds (the toISOString function only provides 3 digit milliseconds) //Set the display field using the page's date format $displayField.val(moment(dateValue).format(dateFormat)); } // ####################################################################################### // End - Get/Set helpers // ####################################################################################### // ####################################################################################### // Formatting Phone Numbers / Postal Code // ####################################################################################### // Formats a phone field value to the format (xxx) xxx-xxxx function FormatPhoneField(field) { var phoneValue = $("#" + field).val(); var formattedPhoneValue = FormatPhoneNumber(phoneValue); $("#" + field).val(formattedPhoneValue); } // Same method as alz.Phone.FormatPhoneNumber_General() as found in alz_XrmCore.js function FormatPhoneNumber(phoneNumber) { // Verify that the field is valid if (typeof (phoneNumber) != "undefined" && phoneNumber != null) { if (phoneNumber != "") { // Remove any special characters var sTmp = phoneNumber.replace(/[^0-9,A-Z,a-z]/g, ""); // If the number is a length we expect and support, // format the translated number switch (sTmp.length) { case 1: case 2: case 3: case 4: case 5: case 6: case 8: case 9: return sTmp; break; case 7: return (sTmp.substr(0, 3) + "-" + sTmp.substr(3, 4)); break; case 10: return "(" + sTmp.substr(0, 3) + ") " + sTmp.substr(3, 3) + "-" + sTmp.substr(6, 4); break; // *** Custom Alianz code start *** // 11 digits - assume first digit is country code case 11: return (sTmp.substr(0, 1) + " (" + sTmp.substr(1, 3) + ") " + sTmp.substr(4, 3) + "-" + sTmp.substr(7, 4)); break; // *** Custom Alianz code end *** default: return ("(" + sTmp.substr(0, 3) + ") " + sTmp.substr(3, 3) + "-" + sTmp.substr(6, 4) + " " + sTmp.substr(10, sTmp.length)); break; } } } } // Returns whether or not the postal code is of the format A1A 1A1 (with a space in the middle) function IsValidPostalCode(s) { // Init if (!s) { return false; } if (s.length != 7) { return false; } // Must be 7 characters (with a space in the middle) var regEx = /[a-zA-Z][0-9][a-zA-Z] [0-9][a-zA-Z][0-9]/; if (regEx.test(s)) { return true; } else { return false; } } // Formats a postal code value: // - Makes all capitals // - Adds a space in the middle function FormatPostalCode(s) { // Init if (!s) { return ""; } // Format postal code var postal = s.toUpperCase(); // Make uppercase postal = postal.replaceAll(" ", ""); // Remove spaces if (postal.length != 6) { return s; } postal = postal.substring(0, 3) + " " + postal.substring(3, 6); // Add a space in the middle // Return the newly formatted postal code if it is a valid postal code. Otherwise just return the original string. if (IsValidPostalCode(postal)) { return postal; } else { return s; } } // Adds replaceAll method for Javascript strings String.prototype.replaceAll = function (target, replacement) { return this.split(target).join(replacement); }; // Adds formatting and validation to a postal code field // (1) Add onchange to a postal code field to format it // (2) Add custom valiation so that the email and confirm email must match // - postalField - the postal code field // - validatorIdBefore - the ID of the validator field that should be BEFORE our validator that we will create. This is used for placing the error message in the correct order. // - errorMsg - error message to display when validation fails function AddFormatting_Validation_PostalCode(postalField, validatorIdBefore, errorMsg) { // (1) Add onchange to a postal code field to format it $("#" + postalField).change(function () { // Get the postal value and format var postal = GetValueString(postalField); if (!postal) { return; } // If we have a blank postal code, just exit var newPostal = FormatPostalCode(postal); // Check if valid and warn user if not var isValid = IsValidPostalCode(newPostal); if (!isValid) { //alert("Please enter the Postal Code in the format L#L #L# (Example: A1A 1A1)."); return; } // Set the postal to the newly formatting version if different if (postal != newPostal) { $("#" + postalField).val(newPostal); } }); // (2) Add custom valiation so that the email and confirm email must match if (typeof (Page_Validators) != 'undefined') { var newValidator = document.createElement('span'); newValidator.style.display = "none"; newValidator.id = "postalformat_" + postalField + "_validator"; newValidator.controltovalidate = postalField; newValidator.errormessage = "" + errorMsg + ""; newValidator.validationGroup = ""; // Set this if you have set ValidationGroup on the form newValidator.initialvalue = ""; newValidator.evaluationfunction = function () { var postalCode = $("#" + postalField).val(); return IsValidPostalCode(postalCode) || (!postalCode); // Blank postal will also pass validation }; // Add the new validator to the page validators array in correct position: var newIndex = 0; for (var i = 0; i < Page_Validators.length; i++) { var validator = Page_Validators[i]; if (validator.id == validatorIdBefore) { // e.g. "RequiredFieldValidatordobc_emailaddress" newIndex = i + 1; } } Page_Validators.splice(newIndex, 0, newValidator); // at index position "newIndex", remove 0 elements // Wire-up the click event handler of the validation summary link $("a[href='#" + postalField + "_label']").on("click", function () { scrollToAndFocus(postalField + '_label', postalField); }); } } // ####################################################################################### // End - Formatting Phone Numbers / Postal Code // ####################################################################################### // #region Field related // Sets the value of a field // If lookup, the value should be an object with the attributes: field, entityName, displayName, id function SetFieldValue(field, value) { // Boolean if ($("#" + field).hasClass("boolean-radio")) { if ((value == "1") || (value == true)) { $("#" + field + "_1").prop("checked", true); } if ((value == "0") || (value == false)) { $("#" + field + "_0").prop("checked", true); } return; } // Date if ($("#" + field).hasClass("datetime")) { var dateValue = value; var dateField = $("#" + field); var $displayField = dateField.nextAll(".datetimepicker").children("input"); var dateFormat = $displayField.attr("data-date-format"); dateField.val(moment.utc(dateValue).format("YYYY-MM-DDTHH:mm:ss.0000000\\Z")); $displayField.val(moment(dateValue).format(dateFormat)); // Note: portal uses Moment.js for Date operations return; } // Lookup if (value.entityName != undefined) { SetLookupValue(value.field, value.entityName, value.displayName, value.id); return; } // Picklist if ($("#" + field).hasClass("picklist")) { $("#" + field).find("input[value='" + value + "']").prop("checked", true); } // Radio // Radio inputs will have IDs with "_0", "_1" appended. (Note that _0, _1 does NOT correspond to the value) if ($("#" + field + "_0").prop("type") == "radio") { $("#" + field).find(`input[value='${value}']`).prop("checked", true); } // If we get here, then just set the value $("#" + field).val(value); } // Set the value for a lookup field function SetLookupValue(field, entityName, displayName, id) { $("#" + field + "_name").attr("value", displayName); $("#" + field).attr("value", id); $("#" + field + "_entityname").attr("value", entityName); } // Expands the width of a Multiple Choice Matrix question function ExpandMatrixQuestionWidth(field, width) { $("table[data-name='" + field + "']").find("div.info").width(width); } // Checks the list of fields to see if at least one field has a value. // Uses HasValue function from CometFormUtil.js function HasValueAny(fieldsCsv) { var arrayFields = fieldsCsv.split(","); for (var i = 0; i < arrayFields.length; i++) { var f = arrayFields[i]; if (!f) { continue; } var hasValue = HasValue(f); if (hasValue) { return true; } } // At this point no field had a value so return false return false; } // Checks the list of fields to see if ALL fields have a value. // Uses HasValue function from CometFormUtil.js function HasValueAll(fieldsCsv) { var arrayFields = fieldsCsv.split(","); for (var i = 0; i < arrayFields.length; i++) { var f = arrayFields[i]; if (!f) { continue; } var hasValue = HasValue(f); if (!hasValue) { return false; } } // At this point ALL fields have values so return true return true; } // Blanks out a field's value. Needs to be extended for non-text fields. function BlankFieldValue(field) { // Init var f = $("#" + field); if (f.length == 0) { return; } // Blank the field depending on the type of field var isMatrix = ($("#" + field + ".picklist.horizontal").length > 0); if (isMatrix) { for (var i = 0; i < 10; i++) { var radioName = field + "_" + i; $("#" + radioName).attr("checked", false); // Uncheck every radio button named _0 to _9 } } else { // Default $("#" + field).val(""); } } // Blanks out the depending field (field2) if it is hidden (based on what the master field's value is). function BlankIfHidden(field1, showValue, field2) { // Get the value of the master field var masterFieldValue = GetValueString(field1); // If the value is not the showValue, then dependent field will be hidden. Therefore blank the dependent field. if (masterFieldValue != showValue) { BlankFieldValue(field2); } } // Adds or Removes the red asterisk to make a field appear required. Separate validation code is needed to make the field required. // Use the AddRedAsterisk or RemoveRedAsterisk functions instead of this one. function AddRemoveRedAsterisk(addOrRemove, field, cssClass) { if (!cssClass) { cssClass = "required"; } // Default required css class if ($("#" + field).length == 0) { return; } var isAdd = (addOrRemove == "ADD"); var nodeName = $("#" + field)[0].nodeName; var inputType = $("#" + field).attr("type"); switch (nodeName) { case "TEXTAREA": (isAdd) ? $("#" + field + "_label").addClass(cssClass) : $("#" + field + "_label").removeClass(cssClass); break; case "TABLE": // Probably a matrix question (isAdd) ? $("#" + field).closest("fieldset").find("div.description").addClass(cssClass) : $("#" + field).closest("fieldset").find("div.description").removeClass(cssClass); break; case "P": // Just add directly to the P tag - this is not a field reference (isAdd) ? $("#" + field).addClass(cssClass) : $("#" + field).removeClass(cssClass); break; default: // Usually INPUT switch (inputType) { case "checkbox": (isAdd) ? $("#" + field).closest("fieldset").find("div.description").addClass(cssClass) : $("#" + field).closest("fieldset").find("div.description").removeClass(cssClass); break; default: (isAdd) ? $("#" + field + "_label").parent().addClass(cssClass) : $("#" + field + "_label").parent().removeClass(cssClass); break; } break; } } // Adds the red asterisk to make a field appear required. Separate validation code is needed to make the field required. function AddRedAsterisk(field, cssClass) { AddRemoveRedAsterisk("ADD", field, cssClass) } // Removes the red asterisk (but does not change validation code). function RemoveRedAsterisk(field, cssClass) { AddRemoveRedAsterisk("REMOVE", field, cssClass) } // Hides a field - used for the first option of a matrix question. For these questions, we want to hide the first option, // but we have to maintain the column headers (headers such as Not met, Partially Met, Met). function HideField_MatrixFirstItem(fieldName) { var f = $("#" + fieldName); if (f.length) { f.closest("td").find("div.info").css("visibility", "hidden"); // Hide the option text, but maintain the space it takes f.closest("td").find("div.info").css("height", "1px"); // Make it not take up too much vertical space f.closest("td").find("div.control").find("input[type='radio']").hide(); // Hide the radio buttons // Reset input fields within the container if set to if (_showHide_ResetInputFieldsOnHide) { ResetInputFields(f.closest("td")); } return true; } // If we get here, the field could not be found return false; } // Shows a field - used for the first option of a matrix question. For these questions, the first option was hidden specifically // for maintaining the column headers. function ShowField_MatrixFirstItem(fieldName) { var f = $("#" + fieldName); if (f.length) { f.closest("td").find("div.info").css("visibility", "visible"); // Show the option text f.closest("td").find("div.info").css("height", ""); f.closest("td").find("div.control").find("input[type='radio']").show(); // Show the radio buttons return true; } // If we get here, the field could not be found return false; } // OVERRIDE of CometFormUtil.js (now ensures no data-name) function HideField(fieldName) { var f = $("#" + fieldName + ":not([data-name])"); // Fields will not have a data-name if (f.length) { f.closest("td").hide(); // Reset input fields within the container if set to if (_showHide_ResetInputFieldsOnHide) { ResetInputFields(f.closest("td")); } return true; } // If we get here, the field could not be found return false; } // OVERRIDE of CometFormUtil.js (now ensures no data-name) function ShowField(fieldName) { var f = $("#" + fieldName + ":not([data-name])"); // Fields will not have a data-name if (f.length) { f.closest("td").show(); return true; } // If we get here, the field could not be found return false; } // OVERRIDE of CometFormUtil.js - Retrieves the label of the field's value function GetLabelString(fieldname) { // Init var f = $("#" + fieldname); // Checkbox if (f.is(":checkbox")) { if (f.is(":checked")) { return "Yes"; } else { return "No"; } } // Try as a lookup var label = ""; if ($("#" + fieldname + "_name").length > 0) { label = $("#" + fieldname + "_name").val(); } // Try as radio if (label == "") { label = f.find("input:checked").text() + ""; } // Try as an dropdown (select) if (label == "") { label = f.find("option:selected").text() + ""; } // Try to get the value directly if (label == "") { if (f.length <= 0) { return ""; } label = f.val() + ""; // Convert to string } return label; } // OVERRIDE of CometFormUtil.js - Retrieves the value of the field in string format function GetValueString(fieldname) { // Init var f = $("#" + fieldname); // Checkbox if (f.is(":checkbox")) { if (f.is(":checked")) { return "1"; } else { return "0"; } } // Try to get the value directly if (f.length <= 0) { return ""; } var value = f.val() + ""; // Convert to string // Try as optionset if ($("#" + fieldname + "_0").prop("type") == "radio") { value = $("#" + fieldname).find("input:checked").val() + ""; } // If value is "undefined", set it to blank if (value == "undefined") { value = ""; } return value; } // #endregion // #region Subgrid // Returns the number of records visible in a portal subgrid function CountSubgridRecords(subgrid) { var count = $("#" + subgrid + " table tbody tr").length; return count; } // Reloads a portal subgrid function ReloadSubgrid(subgridName) { $("#" + subgridName).find(".subgrid").trigger("refresh"); } // Checks to see if a subgrid failed to load. Sometimes it fails and a simple refresh of the page does the trick. We will first try reloading the subgrid. // If the reload doesn't work after a number of attempts, we will replace the unhelpful error message: // - We're sorry, but something went wrong. Error ID # [d7462b22-2aa8-4950-9e7b-7bceb013f0a6] function CheckForSubgridLoadError(gridId) { if ($("#" + gridId).find("div.alert-danger:contains('something went wrong')").length > 0) { console.log("Error message detected for grid " + gridId); // Check if we've already added an error message for this subgrid var grid = $("#" + gridId); var isErrorMsgAdded = (grid.attr("errorMessageAdded") == "1"); if (isErrorMsgAdded) { return; } // Exit if the error message has already been added for this subgrid // Check the number of times this grid has been attempted to be reloaded var reloadAttempts = grid.attr("reloadAttempts") - 0; if (isNaN(reloadAttempts)) { reloadAttempts = 0; } // Try to reload the grid unless we've already tried to reload if (reloadAttempts < 5) { // Try to reload reloadAttempts++; console.log("Subgrid reload attempt " + reloadAttempts + " for grid '" + gridId + "'."); grid.attr("reloadAttempts", reloadAttempts); // Remember that we've added the error message grid.find("div.alert-danger:contains('something went wrong')").remove(); // - Remove the grid's error message to avoid another reload attempt while a reload is still occurring. Error message: We're sorry, but something went wrong. Error ID # [d7462b22-2aa8-4950-9e7b-7bceb013f0a6] var reloadDelay = (reloadAttempts == 1) ? 100 : (reloadAttempts == 2) ? 500 : (reloadAttempts == 3) ? 1000 : (reloadAttempts == 4) ? 2000 : 3000; // Set increasing delays for further reload attempts setTimeout(function () { ReloadSubgrid(gridId); }, reloadDelay); } else { // Show error message console.log("Showing subgrid error message for grid " + gridId); // Too many reload attempts. There is a problem with this grid. Just show a better error message. // - Remember that we've added the error message grid.attr("errorMessageAdded", "1"); // Remember that we've added the error message // - Remove the grid's error message as it's not helpful: We're sorry, but something went wrong. Error ID # [d7462b22-2aa8-4950-9e7b-7bceb013f0a6] grid.find("div.alert-danger:contains('something went wrong')").remove(); // - Add the error message to the page grid.after("