You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

593 lines
26 KiB

<?php
/**
* Handles the editing, updating, creating, and deleting of the address list datatype in LBF. Reuses several LBF components
* inside of it. Also functions as a repeater for address items. Saving of these properties are handled by the scripts
* that call the LBF form. The current example can be found in interface/patient_file/summary/demographics_save.php
* and in interface/new/new_comprehensive_save.php
*
* @package OpenEMR
* @link https://www.open-emr.org
*
* @author David Eschelbacher <psoas@tampabay.rr.com>
* @author Stephen Nielson <snielson@discoverandchange.com>
* @copyright Copyright (c) 2022 David Eschelbacher <psoas@tampabay.rr.com>
* @copyright Copyright (c) 2022 Discover and Change, Inc. <snielson@discoverandchange.com>
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
*/
use OpenEMR\Services\ContactService;
global $pid; // we need to grab our pid from our global settings.
$pid = ($frow['blank_form'] ?? null) ? 0 : $pid;
$contactService = new ContactService();
$addresses = $contactService->getContactsForPatient($pid);
$table_id = uniqid("table_edit_addresses_");
// should always be set, but just in case we will set it to 0 so we can grab it
$field_id_esc = $field_id_esc ?? '0';
$addresses = $addresses ?? [];
$name_field_id = "form_" . $field_id_esc;
$smallform = $smallform ?? '';
$widgetConstants = [
'listWithAddButton' => 26
,'textDate' => 4
,'textbox' => 2
];
// TODO: @adunsulag could we actually design out an actual layout and then just generate/display it in here? Seems like that would provide the most extensible option?
// TODO: @adunsulag the repeating nature of this as a layout display would be problematic... we'd need some kind of repeater widget, would be a fun project.
?>
<style>
div.table_edit_addresses div.label_custom, div.form_addresses div.label_custom {
text-align: left !important;
}
</style>
<div id="<?php echo attr($table_id); ?>" class="row mt-3">
<div class ="table_edit_addresses col-12">
<div class="display_addresses_header pl-1" style="display: flex; line-height: 1.5; padding-top: 0.1rem; background-color: var(--gray300)">
<div class="label_custom mb-0"><?php echo xlt("Additional Addresses"); ?></div>
<div class="fas fa-plus-square text-primary pl-3 pb-1" style="display: inline-block; line-height: 1.5;"onclick="addAddress(event);return false"></div>
</div>
<div class="d-none no_addresses">
<span class="label_custom pl-1" style="line-height: 2.0;"><?php echo xlt("NONE"); ?></span>
<hr class="m-0 p-0" style="border-top-width: 2px; border-color: var(--gray300)" />
</div>
</div>
</div>
<template class="template_add_address">
<div class="addresses_group col-12 pl-0">
<div class="display_addresses form-row no-gutters justify-content-between pl-1">
<div class="display_addresses_use_column px-1" style="flex: 0 0 7em;">
<i class="fas fa-solid fa-caret-right fa-lg text-primary btn-edit-address" style="width:10px; line-height: 1.2;"></i>
<span class="display_addresses_use label_custom px-0" style="vertical-align: 0.1rem;"></span>
</div>
<div class="col-6 px-0">
<span class="display_addresses_full_address label_custom" style="vertical-align: -0.1rem;"></span>
</div>
<div class="col-3 display_addresses_period_column px-0">
<span class="display_addresses_period label_custom" style="vertical-align: -0.1rem;"></span>
</div>
<div class="fas fa-fw fa-trash-alt text-danger btn-delete-address text-center" role="button" style="flex: 0 0 2em; line-height: 1.5;">
</div>
</div>
<div class='d-none form_addresses form-row mx-3 my-2'>
<input type="hidden" class="form_addresses_data_action" name="<?php echo attr($name_field_id); ?>[data_action][]" value="<?php echo xla("No Change"); ?>" />
<input type="hidden" class="form_addresses_id" name="<?php echo attr($name_field_id); ?>[id][]" value="" />
<input type="hidden" class="form_addresses_foreign_id" name="<?php echo attr($name_field_id); ?>[foreign_id][]" value="" />
<div class="col-12">
<!-- Header -->
<div class="form-row">
<div class="col-3 label_custom">
<?php echo xlt("Address Use"); ?>
</div>
<div class="col-3 label_custom">
<?php echo xlt("Address Type"); ?>
</div>
<div class="col-3 label_custom">
<?php echo xlt("Start Date"); ?>
</div>
<div class="col-3 label_custom">
<?php echo xlt("End Date"); ?>
</div>
</div>
<!-- Values -->
<div class="form-row">
<div class="col-3">
<?php
generate_form_field([
'data_type' => $widgetConstants['listWithAddButton']
,'field_id' => $field_id_esc . "[use][]"
,'smallform' => ($smallform ?? '') . ' form_addresses_uses'
,'list_id' => 'address-uses'
,'empty_name' => 'Unassigned'
,'edit_options' => $edit_options ?? null
], '');
?>
</div>
<div class="col-3">
<?php
generate_form_field([
'data_type' => $widgetConstants['listWithAddButton']
,'field_id' => $field_id_esc . "[type][]"
,'smallform' => ($smallform ?? '') . ' form_addresses_types'
,'list_id' => 'address-types'
,'empty_name' => 'Unassigned'
,'edit_options' => $edit_options ?? null
], '');
?>
</div>
<div class="col-3">
<?php
generate_form_field([
'data_type' => $widgetConstants['textDate']
,'field_id' => $field_id_esc . "[period_start][]"
,'smallform' => ($smallform ?? '') . ' form_addresses_period_start'
], date("Y-m-d"));
?>
</div>
<div class="col-3">
<?php
generate_form_field([
'data_type' => $widgetConstants['textDate']
,'field_id' => $field_id_esc . "[period_end][]"
,'smallform' => ($smallform ?? '') . ' form_addresses_period_end'
], '');
?>
</div>
</div>
<!-- Header -->
<div class="form-row">
<div class="col-3 label_custom">
<?php echo xlt("Address"); ?>
</div>
<div class="col-3 label_custom">
<?php echo xlt("Address Line 2"); ?>
</div>
<div class="col-3 label_custom">
<?php echo xlt("City"); ?>
</div>
<div class="col-3 label_custom">
<?php echo xlt("County/District"); ?>
</div>
</div>
<!-- VALUES -->
<div class="form-row">
<div class="col-3">
<?php
generate_form_field([
'data_type' => $widgetConstants['textbox']
,'field_id' => $field_id_esc . "[line_1][]"
,'smallform' => ($smallform ?? '') . ' form_addresses_line1'
], '');
?>
</div>
<div class="col-3">
<?php
generate_form_field([
'data_type' => $widgetConstants['textbox']
,'field_id' => $field_id_esc . "[line_2][]"
,'smallform' => ($smallform ?? '') . ' form_addresses_line2'
], '');
?>
</div>
<div class="col-3">
<?php
generate_form_field([
'data_type' => $widgetConstants['textbox']
,'field_id' => $field_id_esc . "[city][]"
,'smallform' => ($smallform ?? '') . ' form_addresses_city'
], '');
?>
</div>
<div class="col-3">
<?php
generate_form_field([
'data_type' => $widgetConstants['textbox']
,'field_id' => $field_id_esc . "[district][]"
,'smallform' => ($smallform ?? '') . ' form_addresses_district'
], '');
?>
</div>
</div>
<!-- Header -->
<div class="form-row">
<div class="col-3 label_custom">
<?php echo xlt("State"); ?>
</div>
<div class="col-3 label_custom">
<?php echo xlt("Postal Code"); ?>
</div>
<div class="col-3 label_custom">
<?php echo xlt("Country"); ?>
</div>
</div>
<!-- Header -->
<div class="form-row">
<div class="col-3">
<?php
generate_form_field([
'data_type' => $widgetConstants['listWithAddButton']
,'field_id' => $field_id_esc . "[state][]"
,'smallform' => ($smallform ?? '') . ' form_addresses_state'
,'list_id' => 'state'
,'empty_name' => 'Unassigned'
,'edit_options' => $edit_options ?? null
], '');
?>
</div>
<div class="col-3">
<?php
generate_form_field([
'data_type' => $widgetConstants['textbox']
,'field_id' => $field_id_esc . "[postalcode][]"
,'smallform' => ($smallform ?? '') . ' form_addresses_postalcode'
], '');
?>
</div>
<div class="col-3">
<?php
generate_form_field([
'data_type' => $widgetConstants['listWithAddButton']
,'field_id' => $field_id_esc . "[country][]"
,'smallform' => ($smallform ?? '') . ' form_addresses_country'
,'list_id' => 'country'
,'empty_name' => 'Unassigned'
,'edit_options' => $edit_options ?? null
], '');
?>
</div>
</div>
</div>
</div>
<hr class="m-0 p-0" style="border-top-width: 2px; border-color: var(--gray300)" />
</div>
</template>
<script type="text/javascript">
let addressWidgets = [];
let ADDRESS_ACTION_VALUES = {
'DELETE': 'INACTIVATE'
,'ADD': 'ADD'
,'UPDATE': 'UPDATE'
};
let datePickerSettings = {
<?php $datetimepicker_timepicker = false; ?>
<?php $datetimepicker_showseconds = false; ?>
<?php $datetimepicker_formatInput = true; ?>
<?php require $GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'; ?>
};
// IFF to trap anything in our variables here
(function() {
// make sure we don't leak anything
let addresses = <?php echo json_encode($addresses); ?>;
let tableId = <?php echo js_escape($table_id); ?>;
init(tableId, addresses);
})();
function init(tableId, addresses){
//IE Compatibility
// TODO: @adunsulag, not sure how much this matters as IE compatability is so very small of a userbase...
if (!Element.prototype.matches) {
Element.prototype.matches =
Element.prototype.msMatchesSelector ||
Element.prototype.webkitMatchesSelector;
}
if (!Element.prototype.closest) {
Element.prototype.closest = function(s) {
var el = this;
do {
if (Element.prototype.matches.call(el, s)) return el;
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
};
}
// go through and populate our addresses from our JSON array
if (addresses && addresses.length && addresses.forEach) {
addresses.forEach(function(record) {
createEditAddressRowFromRecord(tableId, record);
});
} else {
showElement(document.querySelector(".no_addresses"));
}
//Event Listener to Update Full Address on Data Entry
document.getElementById(tableId).addEventListener('keyup', changeEdit);
document.getElementById(tableId).addEventListener('mouseup', changeEdit);
document.getElementById(tableId).addEventListener('touchend', changeEdit);
// TODO: @adunsulag need to handle the onkeyup event.
}
function createEditAddressRowFromRecord(tableId, record) {
const row_address_template = document.querySelector(".template_add_address");
var row_address_clone = row_address_template.content.cloneNode(true);
setInputValue(row_address_clone, "input.form_addresses_id", record.id || "");
setInputValue(row_address_clone, "input.form_addresses_line1", record.line1 || "");
setInputValue(row_address_clone, "input.form_addresses_line2", record.line2 || "");
setInputValue(row_address_clone, "input.form_addresses_city", record.city || "");
setInputValue(row_address_clone, "input.form_addresses_postalcode", record.postalcode || "");
setInputValue(row_address_clone, "input.form_addresses_data_action", ADDRESS_ACTION_VALUES.UPDATE);
setInputValue(row_address_clone, "input.form_addresses_district", record.district || "");
setInputValue(row_address_clone, "select.form_addresses_country", record.country || "");
setInputValue(row_address_clone, "select.form_addresses_state", record.state || "");
setInputValue(row_address_clone, "select.form_addresses_uses", record.use || "");
setInputValue(row_address_clone, "select.form_addresses_types", record.type || "");
setInputValue(row_address_clone, "input.form_addresses_period_start", record.period_start || "");
setInputValue(row_address_clone, "input.form_addresses_period_end", record.period_end || "");
let useDisplay = getSelectDisplay(row_address_clone, "select.form_addresses_uses");
let fullAddress = fullAddressFromRecord(record, 1);
let period = PeriodFromRecord(record);
row_address_clone.querySelector(".display_addresses_full_address").innerHTML = fullAddress[0];
row_address_clone.querySelector(".display_addresses_use").innerHTML = useDisplay;
row_address_clone.querySelector(".display_addresses_period").innerHTML = period;
let form_row = row_address_clone.querySelector(".form_addresses");
let display_row = row_address_clone.querySelector(".display_addresses");
document.getElementById(tableId).querySelector(".table_edit_addresses").appendChild(row_address_clone);
setupAddressesRowButtonEventListeners(display_row);
setupContainerDatePickers(form_row);
setupContainerListAddButtons(form_row);
}
function addAddress(event){
if (!document.querySelector(".no_addresses").classList.contains('d-none')) {
hideElement(document.querySelector(".no_addresses"));
}
let target = event.currentTarget;
let container = target.closest(".table_edit_addresses")
const row_address_template = document.querySelector(".template_add_address");
var row_address_clone = row_address_template.content.cloneNode(true);
let row_form_addresses = row_address_clone.querySelector(".form_addresses");
let row_display_addresses = row_address_clone.querySelector(".display_addresses");
container.appendChild(row_address_clone);
row_form_addresses.querySelector("input.form_addresses_data_action").value = ADDRESS_ACTION_VALUES.ADD;
row_display_addresses.querySelector(".display_addresses_full_address").innerHTML = "";
row_display_addresses.querySelector(".btn-edit-address").className = "fas fa-solid fa-caret-down fa-lg text-primary btn-edit-address";
setupAddressesRowButtonEventListeners(row_display_addresses);
setupContainerDatePickers(row_form_addresses);
setupContainerListAddButtons(row_form_addresses)
// expand the element and put the cursor focus in the first element of the address
showElement(row_form_addresses);
row_form_addresses.querySelector('input.form_addresses_line1').focus();
}
function changeEdit(){
// note we intentially keep this to be event.target as we have the event at the overarching container level
// and need to work on the input level.
var row_form_addresses = event.target.closest('.form_addresses');
if (!row_form_addresses) {
console.error("Failed to find element with class .form_addresses");
return;
}
var row_display_addresses = row_form_addresses.previousElementSibling;
if (!row_display_addresses) {
console.error("Failed to find previousElementSibling for .form_addresses");
return;
}
let record = createRecordFromInput(row_form_addresses);
let useDisplay = getSelectDisplay(row_form_addresses, "select.form_addresses_uses");
let typeDisplay = getSelectDisplay(row_form_addresses, "select.form_addresses_types");
let fullAddress = fullAddressFromRecord(record, 1);
let period = PeriodFromRecord(record);
row_display_addresses.querySelector(".display_addresses_full_address").innerHTML = fullAddress[0];
row_display_addresses.querySelector(".display_addresses_use").innerHTML = useDisplay;
row_display_addresses.querySelector(".display_addresses_period").innerHTML = period;
}
function editAddress(evt){
evt.preventDefault();
let element = evt.currentTarget;
if (element.className == "fas fa-solid fa-caret-right fa-lg text-primary btn-edit-address") {
let toggleElement = element.closest('.addresses_group').querySelector('.form_addresses');
toggleElement.classList.remove('d-none');
element.className = "fas fa-solid fa-caret-down fa-lg text-primary btn-edit-address";
} else {
let toggleElement = element.closest('.addresses_group').querySelector('.form_addresses');
toggleElement.classList.add('d-none');
element.className = "fas fa-solid fa-caret-right fa-lg text-primary btn-edit-address";
}
}
function deleteAddress(evt){
evt.preventDefault();
var row_display_addresses = evt.currentTarget.closest('.display_addresses');
if (!row_display_addresses) {
console.error("Failed to find element with class .display_addresses");
return;
}
var row_form_addresses = row_display_addresses.nextElementSibling;
//var row_display_addresses = element.closest('.display_addresses');
//var row_form_addresses = row_display_addresses.nextElementSibling;
let prompt = window.xl("Are you sure you wish to delete this address?");
if (confirm(prompt)) {
// seems odd to hide these when we can just remove them...
hideElement(row_display_addresses);
hideElement(row_form_addresses);
// set the action to be delete so we can remove this index
setInputValue(row_form_addresses, 'input.form_addresses_data_action', ADDRESS_ACTION_VALUES.DELETE)
}
}
function setupAddressesRowButtonEventListeners(row) {
row.querySelector(".btn-edit-address").addEventListener('click', editAddress);
row.querySelector(".btn-delete-address").addEventListener('click', deleteAddress);
}
function setupContainerDatePickers(container) {
let datepickers = container.querySelectorAll(".datepicker");
// let's setup our date selectors now using our jquery plugin
$(datepickers).datetimepicker(datePickerSettings);
}
function setupContainerListAddButtons(container) {
if (window.oeUI && window.oeUI.optionWidgets) {
$(container).find(".addtolist").on("click", function (evt) {
window.oeUI.optionWidgets.AddToList(this, evt);
});
}
}
function createRecordFromInput(row_form_addresses) {
var record = [];
record.line1 = getInputValue(row_form_addresses, "input.form_addresses_line1");
record.line2 = getInputValue(row_form_addresses, "input.form_addresses_line2");
record.city = getInputValue(row_form_addresses, "input.form_addresses_city");
record.full_zip = getInputValue(row_form_addresses, "input.form_addresses_postalcode");
record.zip = getInputValue(row_form_addresses, "input.form_addresses_postalcode");
record.state = getSelectValue(row_form_addresses, "select.form_addresses_state");
record.country = getSelectValue(row_form_addresses, "select.form_addresses_country");
record.district = getInputValue(row_form_addresses, "input.form_addresses_district");
record.use = getSelectValue(row_form_addresses, "select.form_addresses_uses");
record.useDisplay = getSelectDisplay(row_form_addresses, "select.form_addresses_uses");
record.type = getSelectValue(row_form_addresses, "select.form_addresses_types");
record.typeDisplay = getSelectDisplay(row_form_addresses, "select.form_addresses_types");
record.period_start = getInputValue(row_form_addresses, "input.form_addresses_period_start");
record.period_end = getInputValue(row_form_addresses, "input.form_addresses_period_end");
return record;
}
function fullAddressFromRecord(record, lines) {
var fullAddress = [];
var i = 0;
fullAddress[0] = record.line1 + (isBlank(record.line1) ? "": ", ");
fullAddress[0] += record.line2 + ((isBlank(record.line2) || isBlank(fullAddress[0])) ? "": ", ");
fullAddress[1] = "";
if (lines > 1) {
let i = 1;
fullAddress[0] = fullAddress[0].replace(/(,\s*$)/g, "");
}
fullAddress[i] += record.city + ((isBlank(record.city) && isBlank(fullAddress[i])) ? "": ", ");
fullAddress[i] += record.state + ((isBlank(record.state) && isBlank(fullAddress[i]) && (!isBlank(record.full_zip))) ? "": " ");
let full_zip = record.zip;
fullAddress[i] += full_zip + ((isBlank(full_zip) && isBlank(fullAddress[i]) && isBlank(record.country)) ? "": ", ");
fullAddress[i] += record.country;
fullAddress[i] = fullAddress[i].replace(/(,\s*$)/g, "");
return ((isBlank(fullAddress[0]) && isBlank(fullAddress[1])) ? "None": fullAddress);
}
function PeriodFromRecord(record) {
let period = [];
let periodStart = record.period_start;
let periodEnd = record.period_end;
if (!isBlank(periodStart) || !isBlank(periodEnd)) {
if (!isBlank(periodStart) && isBlank(periodEnd)) {
period.push(periodStart + " to " + window.xl('Current'));
} else if (isBlank(periodStart) && !isBlank(periodEnd)) {
period.push(window.xl('Expired')+ ":" + periodEnd);
} else {
period.push(periodStart + " to " + periodEnd);
}
}
return period;
}
function setInputValue(root, selector, value) {
let node = root.querySelector(selector);
if (!node) {
console.error("Failed to find DOM node with selector ", selector, ' in node ', root);
return;
}
node.value = value;
}
function getInputValue(root, selector) {
let node = root.querySelector(selector);
if (!node) {
console.error("Failed to find DOM node with selector ", selector, ' in node ', root);
return;
}
return node.value;
}
function getSelectDisplay(root, selector) {
let node = root.querySelector(selector);
if (!node) {
console.error("Failed to find DOM node with selector ", selector, ' in node ', root);
return;
}
let options = node.selectedOptions;
if (options && options.length) {
return options[0].textContent;
}
return "";
}
function getSelectValue(root, selector) {
let node = root.querySelector(selector);
if (!node) {
console.error("Failed to find DOM node with selector ", selector, ' in node ', root);
return;
}
let options = node.selectedOptions;
if (options && options.length) {
return options[0].value;
}
return "";
}
function hideElement(element){
element.classList.add('d-none');
}
function showElement(element){
element.classList.remove('d-none');
}
function isBlank(str) {
if (!str) { return true; }
return str.trim().length === 0;
}
</script>