Skip to content

Commit 9f14038

Browse files
authored
feat(insurance): client-side phone/fax validation on insurance + pharmacy edit forms (openemr#12534)
## Summary Follow-up to openemr#12529. That PR stopped the `TypeError` when a user typed a phone the legacy `phone_numbers` schema couldn't represent (anything that isn't a 10-digit NANP number), but the user still got no feedback — the bad value was silently dropped on save. This PR adds client-side validation so the *Practice Settings → Insurance Company* and *Procedures → Pharmacies* edit forms reject bad input before submit, with the browser's native error bubble pointing at the offending field. ## Scope (narrow on purpose) Two forms, four fields. These are the **only** two places in the codebase whose phone setters silently drop on `TypedPhoneNumber::tryCreate()` returning null (the same survey I did before openemr#12529). Other phone-bearing forms (patient demographics telecom, etc.) store the raw string verbatim — a different failure mode that needs a different fix in a separate PR. ## Changes For both `templates/insurance_companies/general_edit.html` and `templates/pharmacies/general_edit.html`: - **`type=\"text\"` → `type=\"tel\"`** on the phone and fax inputs — semantic, and triggers the numeric soft keyboard on mobile. - **`pattern=\"\\(?\\d\\d\\d\\)?[\\s.\\-]?\\d\\d\\d[\\s.\\-]?\\d\\d\\d\\d\"`** matching the same 10-digit NANP shapes the server already accepts (`5551234567`, `555-123-4567`, `(555) 123-4567`, `555.123.4567`, etc.). - **`title=\"{xla t='Enter a 10-digit phone number, e.g. 555-123-4567'}\"`** so the browser's validation bubble explains the expected format (translated). - **`submit_insurancecompany()` / `submit_pharmacy()`** now call `form.reportValidity()` before submit. Without this the JS-driven Save anchor bypasses HTML5 validation entirely. ## Why not `\\d{3}`? Smarty 4.5 treats `{3}` and `{4}` as tag delimiters and silently strips them during template compilation, so the rendered pattern becomes `\\(?\\d3\\)?[\\s.\\-]?\\d3[\\s.\\-]?\\d4` — a regex that rejects every plausible phone number. The explicit `\\d\\d\\d` form sidesteps Smarty and is short enough not to be unreadable. ## What this does not address - **Server-side coverage is unchanged.** A programmatic POST (curl, API client, anything outside a browser) still hits the legacy `set_number()` silent-drop path. openemr#12529's persist null-guard still carries that defense; this PR is the UX layer on top. - **No new error-display mechanism** in the legacy `C_InsuranceCompany` / `C_Pharmacy` controllers. The browser's native bubble is the entire validation surface — proportional for the cheap tier; a server-side validation error page would be a larger UX scope. - **NANP-only.** The underlying schema is still 10-digit US/Canada. International formats (`+44 …`) deserve a schema-and-storage change, not a regex tweak here. ## Verification Drove both forms through the dev stack's Selenium grid (script in scratch, not committed). Captured per-input behaviour against `form.checkValidity()`, `phone.checkValidity()`, and the rendered `pattern` attribute, then exercised the actual `submit_*` JS handlers and observed whether the page stayed on the edit form or navigated away. | Input | Insurance | Pharmacy | |---|---|---| | `555-1234` (7 digits) | ✗ blocked | ✗ blocked | | `555-123-45678` (11 digits) | ✗ blocked | ✗ blocked | | `garbage` | ✗ blocked | ✗ blocked | | `555-123-4567` | ✓ passes | ✓ passes | | `(555) 123-4567` | ✓ passes | ✓ passes | | `5551234567` | ✓ passes | ✓ passes | | (empty) | ✓ passes (optional) | ✓ passes (optional) | ## Test plan - [x] On *Practice Settings → Insurance Company → Add*, type `555-1234` in **Phone**, click **Save** → expect the browser's native error bubble next to the Phone field, form stays on the edit page. - [x] Same form, type `555-123-4567` in **Phone** + a name, click **Save** → expect successful save with phone written to the row. - [x] Same for **Fax** field. - [x] On *Procedures → Pharmacies → Add* (or Edit), repeat the three checks above. - [x] Empty Phone/Fax fields still save (they remain optional). - [ ] On a mobile device, the numeric keyboard pops up when focusing the Phone/Fax fields (bonus, from `type=\"tel\"`).
1 parent 27e8a2a commit 9f14038

2 files changed

Lines changed: 29 additions & 19 deletions

File tree

templates/insurance_companies/general_edit.html

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,13 @@
7474
<div class="form-row my-sm-2">
7575
<label for="phone" class="col-form-label col-sm-2">{xlt t='Phone'}</label>
7676
<div class="col-sm-8">
77-
<input type="text" id="phone" name="phone" class="form-control" value="{$insurancecompany->get_phone()|attr}" onKeyDown="PreventIt(event)">
77+
<input type="tel" id="phone" name="phone" class="form-control" value="{$insurancecompany->get_phone()|attr}" pattern="(?:\(\d\d\d\)|\d\d\d)[\s.\-]?\d\d\d[\s.\-]?\d\d\d\d" title="{xla t='Enter a 10-digit phone number, e.g. 555-123-4567'}" onKeyDown="PreventIt(event)">
7878
</div>
7979
</div>
8080
<div class="form-row my-sm-2">
8181
<label for="fax" class="col-form-label col-sm-2">{xlt t='Fax'}</label>
8282
<div class="col-sm-8">
83-
<input type="text" id="fax" name="fax" class="form-control" value="{$insurancecompany->get_fax()|attr}" onKeyDown="PreventIt(event)">
83+
<input type="tel" id="fax" name="fax" class="form-control" value="{$insurancecompany->get_fax()|attr}" pattern="(?:\(\d\d\d\)|\d\d\d)[\s.\-]?\d\d\d[\s.\-]?\d\d\d\d" title="{xla t='Enter a 10-digit fax number, e.g. 555-123-4567'}" onKeyDown="PreventIt(event)">
8484
</div>
8585
</div>
8686
<div class="form-row my-sm-2">
@@ -153,14 +153,21 @@
153153

154154
<script>
155155
function submit_insurancecompany() {
156-
if(document.insurancecompany.name.value.length>0) {
157-
top.restoreSession();
158-
document.insurancecompany.submit();
159-
//Z&H Removed redirection
160-
} else{
161-
document.insurancecompany.name.style.backgroundColor="red";
156+
if (document.insurancecompany.name.value.length === 0) {
157+
document.insurancecompany.name.style.backgroundColor = "red";
162158
document.insurancecompany.name.focus();
159+
return;
163160
}
161+
// Honor the pattern= constraints on phone/fax (and anything else added
162+
// later). reportValidity() shows the browser's native error bubble
163+
// pointing at the first invalid field; without this the JS submit()
164+
// bypasses HTML5 validation entirely.
165+
if (!document.insurancecompany.reportValidity()) {
166+
return;
167+
}
168+
top.restoreSession();
169+
document.insurancecompany.submit();
170+
//Z&H Removed redirection
164171
}
165172

166173
function jsWaitForDelay(delay) {

templates/pharmacies/general_edit.html

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@
5757
<div class="form-row py-sm-2">
5858
<label for="phone" class="col-form-label col-sm-2">{xlt t='Phone'}</label>
5959
<div class="col-sm-8">
60-
<input type="text" id="phone" name="phone" class="form-control" value="{$pharmacy->get_phone()|attr}" onKeyDown="PreventIt(event)" />
60+
<input type="tel" id="phone" name="phone" class="form-control" value="{$pharmacy->get_phone()|attr}" pattern="(?:\(\d\d\d\)|\d\d\d)[\s.\-]?\d\d\d[\s.\-]?\d\d\d\d" title="{xla t='Enter a 10-digit phone number, e.g. 555-123-4567'}" onKeyDown="PreventIt(event)" />
6161
</div>
6262
</div>
6363
<div class="form-row py-sm-2">
6464
<label for="fax" class="col-form-label col-sm-2">{xlt t='Fax'}</label>
6565
<div class="col-sm-8">
66-
<input type="text" id="fax" name="fax" class="form-control" value="{$pharmacy->get_fax()|attr}" onKeyDown="PreventIt(event)" />
66+
<input type="tel" id="fax" name="fax" class="form-control" value="{$pharmacy->get_fax()|attr}" pattern="(?:\(\d\d\d\)|\d\d\d)[\s.\-]?\d\d\d[\s.\-]?\d\d\d\d" title="{xla t='Enter a 10-digit fax number, e.g. 555-123-4567'}" onKeyDown="PreventIt(event)" />
6767
</div>
6868
</div>
6969
<div class="form-row py-sm-2">
@@ -101,17 +101,20 @@
101101
<script>
102102
function submit_pharmacy()
103103
{
104-
if(document.pharmacy.name.value.length>0)
105-
{
106-
top.restoreSession();
107-
document.pharmacy.submit();
108-
//Z&H Removed redirection
109-
}
110-
else
111-
{
112-
document.pharmacy.name.style.backgroundColor="red";
104+
if (document.pharmacy.name.value.length === 0) {
105+
document.pharmacy.name.style.backgroundColor = "red";
113106
document.pharmacy.name.focus();
107+
return;
108+
}
109+
// Honor the pattern= constraints on phone/fax. reportValidity() shows the
110+
// browser's native error bubble pointing at the first invalid field;
111+
// without this the JS submit() bypasses HTML5 validation entirely.
112+
if (!document.pharmacy.reportValidity()) {
113+
return;
114114
}
115+
top.restoreSession();
116+
document.pharmacy.submit();
117+
//Z&H Removed redirection
115118
}
116119

117120
function Waittoredirect(delaymsec) {

0 commit comments

Comments
 (0)