// 2024/02/12a - Fixed Get/Set field value for radios. // #region Field Related // 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; } // 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`).attr("checked", false); // Remove the "checked" attribute on any other radios $("#" + 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); } // 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. function EnableDisableField(fieldName, isEnabled, useReadOnly) { // 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 var prop = (useReadOnly) ? "readonly" : "disabled"; $("#" + 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) { var prop = (useReadOnly) ? "readonly" : "disabled"; $("#" + 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 $("#" + fieldName).closest("div.input-group").find("div.input-group-btn").hide(); // Hide lookup related buttons } } // 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); } // 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; } } // 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); } } // 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; } // 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)); } // Validates that the provided phone number is 10 digits. If it is valid, it will be reformatted to (xxx) yyy-zzzz. // Will return an array consisting of // - (0) isValid // - (1) msg // - (2) formatted number function ValidateAndFormatPhoneNumber(phoneNumber) { var isValid = false; var msg = ""; if (phoneNumber == null) { phoneNumber = ""; } var phoneNumberClean = phoneNumber.replace(/[^0-9,]/g, ""); var phoneNumberReturn = phoneNumberClean; // If first number is 1, ignore it (1 is country code) if ((phoneNumberClean.length == 11) && (phoneNumberClean.substr(0, 1) == "1")) { phoneNumberClean = phoneNumberClean.substr(1, 10); } // Format the phone number if (phoneNumberClean.length == 10) { isValid = true; msg = `Phone number '${phoneNumber}' has been reformatted to '${phoneNumberReturn}'.`; phoneNumberReturn = `(${phoneNumberClean.substr(0, 3)}) ${phoneNumberClean.substr(3, 3)}-${phoneNumberClean.substr(6, 4)}`; } else { if (phoneNumberClean.length < 10) { isValid = false; msg = `Phone number '${phoneNumber}' must contain at least 10 digits.`; phoneNumberReturn = phoneNumberClean; } else { // More than 10 digits - ok, but do not reformat isValid = true; msg = ``; phoneNumberReturn = phoneNumber; } } return [isValid, msg, phoneNumberReturn]; } // #endregion // #region Hide/Show // Hides a field (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(); return true; } // If we get here, the field could not be found return false; } // Shows a field (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; } 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; } 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.name); } } // 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); } } } // 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); } } } // #endregion // #region Required Field related // 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 } } } // 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); } } // 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) } // #endregion // #region Custom Actions // Executes an ITA Custom Action to run custom business logic on the server. JSON results will be returned and will be passed // to either successCallback function or errorCallback function. In the case of an HTTP error response (400 - 600), a custom // callback can be used to handle this. function ExecuteCustomAction(action, params, successCallback, errorCallback, httpErrorCallback, method) { // Init if (!action) { alert("No Action provided."); return; } if (!params) { params = ""; } if ((!method) || (method != "POST")) { method = "GET"; } //console.log("METHOD: " + method); params = encodeURIComponent(params); // Encode for URL (replacements will be "%3D" for "=" and "%26" for "&") var paramsString = "parameters=" + params; var cacheId = Math.random() + ""; // Used for uniqueness so to circumvent portal caching. Should also add Booking Code to this cacheId to guarantee uniqueness var customActionUrl = window.location.origin + "/customaction-execute/?action=" + action; if (method == "GET") { customActionUrl += "&" + paramsString; } customActionUrl += "&cache_id=" + cacheId; var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function () { //console.log("readyState: " + this.readyState + ", status: " + this.status); if (this.readyState == 4) { if (this.status == 200) { try { // Read the JSON results from the Custom Action portal page var result = JSON.parse(this.responseText); //console.log("console log of the result"); //console.log(result); var successMessage = result[0].success_message; // If this is not an empty string, we have a successful result. This will usually be something like "SUCCESS". var errorMessage = result[0].error_message; // If there was an error, the details will be in here. var responseData = result[0].response_data; // The response data in JSON. var responseData2 = result[0].response_data_2; // This is if we need a second result set. if (successMessage != "") { // Success successCallback(result); } else { // Must be an error // Error errorCallback(result); } } catch (err) { // It's possible that the custom action page will not return a proper JSON object - for example if the page times out. In this case, // just send the raw responseText to the error function. // NOTE: The portal seems to time out after around 30 seconds (if a fetch takes too long for example). This timeout occurs even if the fetch succeeds. errorCallback(this.responseText); } } else if ((this.status >= 400) && (this.status <= 600)) { if (httpErrorCallback) { httpErrorCallback(this.status, this.responseText); } } } }; // Send the request if (method == "GET") { //console.log("Custom Action - GET: " + customActionUrl); xhttp.open("GET", customActionUrl, true); xhttp.send(); } else { // POST //console.log("Custom Action - POST: " + customActionUrl); //console.log("POST params: " + paramsString); xhttp.open("POST", customActionUrl, true); xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); // Send the proper header information along with the request xhttp.send(paramsString); } } // Converts a base64 string to an array of bytes function ConvertBase64ToArrayBuffer(_base64Str) { var binaryString = window.atob(_base64Str); var binaryLen = binaryString.length; var bytes = new Uint8Array(binaryLen); for (var i = 0; i < binaryLen; i++) { var ascii = binaryString.charCodeAt(i); bytes[i] = ascii; } return bytes; } // Opens base64 content into a new window via the creation of a Blob. This technique will bypass // Chrome's error of "Not allowed to navigate top frame to data URL" when a data is directly opened // in a URL function OpenBase64InNewWindow(_base64Str, _contentType) { var byte = ConvertBase64ToArrayBuffer(_base64Str); var blob = new Blob([byte], { type: _contentType }); window.open(URL.createObjectURL(blob), "_blank"); } // #endregion // #region Content Related / Helpers // Adjusts GMT datetime values to the current timezone offset. // Usage: add the class "adjustTimezone" to each datetime value. Also add the attribute "adjustTimezoneFormat" to define the format of the datetime (default is MM/DD/YYYY). // Once the datetime has been adjusted, the html object will be marked as "adjustTimezoneCOMPLETE" so that it cannot be adjusted a second time. function AdjustTimeZoneValues() { $(".adjustTimezone:not(.adjustTimezoneCOMPLETE)").each(function () { try { // Get the datetime value var val = $(this)[0].innerHTML; // Datetime value (string) // Define the moment object and adjust the offset var m = moment(val); var offset = (new Date()).getTimezoneOffset() / 60; // Determine the current offset in hours m = m.subtract(offset, "hours"); // Determine datetime format and write new value out var format = $(this).attr("adjustTimezoneFormat"); if (!format) { format = "MM/DD/YYYY"; } // Set a default datetime format if none provided in the HTML attribute "adjustTimezoneFormat" $(this)[0].innerHTML = m.format(format); // Mark the datetime as adjusted $(this).addClass("adjustTimezoneCOMPLETE"); // Add a class that the timezone adjustment on the value has already been completed. If this function is run again, this value will not get adjusted a second time. } catch (err) { console.log("Error in AdjustTimeZoneValues: " + err.message); } }); } // Add HTML before a field function AddHtmlBeforeField(field, html) { $("#" + field).closest("td").prepend(html); } // Add HTML after a field function AddHtmlAfterField(field, html) { $("#" + field).closest("td").append(html); } // Show all tabs and debugging iframe function ShowDebug() { $(`#${_iframeName}`).show(); for (var i = 0; i < _allTabs.length; i++) { var t = _allTabs[i]; ShowTab(t.name); } } // #endregion // #region Subgrid // Counts the rows in a subgrid function CountSubgridRows(subgridName) { var count = $("#" + subgridName + " table tbody tr").length; return count; } // Reloads a portal subgrid function ReloadSubgrid(subgridName) { $("#" + subgridName).find(".entity-grid.subgrid").trigger("refresh"); } // Hides a column of a subgrid matching by the column header's text. This should be called on a subgrid's onload. function HideColumnByHeaderText(grid, headerText) { $(`#${grid}`).find("th").filter(function () { let thisText = ($(this).find("a").length > 0) ? $(this).find("a")[0].childNodes[0].nodeValue : $(this).text(); // Get the text of the th, but exclude any text in child span elements return (thisText == headerText); }).hide(); $(`#${grid}`).find("td[data-th='" + headerText + "']").hide(); } // Shows a column of a subgrid matching by the column header's text. function ShowColumnByHeaderText(grid, headerText) { $(`#${grid}`).find("th").filter(function () { let thisText = ($(this).find("a").length > 0) ? $(this).find("a")[0].childNodes[0].nodeValue : $(this).text(); // Get the text of the th, but exclude any text in child span elements return (thisText == headerText); }).show(); $(`#${grid}`).find("td[data-th='" + headerText + "']").show(); } // Attaches an onload function to the provided subgrid function AddSubgridOnload(gridId, loadFn) { $(`#${gridId}`).find("div.subgrid").on("loaded", loadFn); } // #endregion // #region Misc // 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, " ")); } // Wrapper AJAX function // From: https://docs.microsoft.com/en-us/powerapps/maker/portals/write-update-delete-operations#wrapper-ajax-function (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) // #endregion