PageSourceSearch

https://www.orchardortho.com/js/file-upload.js

js orchardortho.com collected 2026-10-04 13:25:18 UTC 5,012 bytes, 136 lines download raw bytes

1/**
2 * File Upload for ajax_form file pickers (dentist referral x-rays/photos).
3 *
4 * Config-driven (reads window.__PRM_CONFIG__) so it works UNCHANGED in both
5 * direct mode (posts to prm.tio.work with TDS-API-KEY header) and proxied mode
6 * (posts same-origin to /api/prm/v2/upload-file/, no key — the proxy injects it).
7 *
8 * Replaces the dead C5 plupload + /themes/_uploads.php flow:
9 *  - #pick_files (span) triggers a hidden #file_input (created if absent)
10 *  - each file is POSTed to the upload endpoint (multipart)
11 *  - the returned URL is written into the next free #doc_N hidden slot, which
12 *    is re-enabled so it serializes; empty slots stay DISABLED so no blank
13 *    attachment reaches PRM
14 *
15 * Slot fields MUST be scalar names (gdpr-X-Ray-01..NN), not name="…[]" arrays
16 * (PRM's note builder 500s on arrays). Requires jQuery (loaded beforeInteractive).
17 */
18(function () {
19  if (typeof jQuery === "undefined") return;
20
21  jQuery(document).ready(function ($) {
22    var $pick = $("#pick_files");
23    var $doc = $("#doc_files");
24    if (!$pick.length || !$doc.length) return; // no upload UI on this page
25
26    var cfg = window.__PRM_CONFIG__ || {};
27    var UPLOAD_ENDPOINT = cfg.uploadEndpoint || "https://prm.tio.work/api/v2/upload-file";
28    var TDS_API_KEY = cfg.tdsApiKey || "";
29    var ACCOUNT_ID = cfg.accountId || "";
30    var MAX_BYTES = 10 * 1024 * 1024;
31
32    // create the hidden file input if the C5 markup didn't carry one (plupload used to)
33    var $input = $("#file_input");
34    if (!$input.length) {
35      $input = $(
36        '<input type="file" id="file_input" multiple accept=".jpg,.jpeg,.png,.gif,.pdf,.heic" style="display:none">'
37      );
38      $pick.before($input);
39    }
40
41    // discover slots #doc_1..#doc_N and disable them (empty slots must not serialize)
42    var slots = [];
43    for (var n = 1; $("#doc_" + n).length; n++) slots.push($("#doc_" + n));
44    if (!slots.length) return;
45    var MAX = slots.length;
46    slots.forEach(function ($s) { $s.prop("disabled", true); });
47
48    var $names = $doc.find(".file_names").first();
49    var $del = $doc.find(".btn_del, .btn_del_one").first();
50    var $loader = $doc.find(".loader, .loader_one").first();
51
52    var files = [];
53    var inflight = 0;
54
55    $pick.on("click keypress", function (e) {
56      if (e.type === "keypress" && e.which !== 13 && e.which !== 32) return;
57      e.preventDefault();
58      if (files.length >= MAX) { alert("Maximum " + MAX + " files allowed."); return; }
59      $input.trigger("click");
60    });
61
62    $input.on("change", function () {
63      var f = this.files;
64      if (!f || !f.length) return;
65      var remaining = MAX - files.length;
66      if (remaining <= 0) { alert("Maximum " + MAX + " files allowed."); return; }
67      var count = Math.min(f.length, remaining);
68      if (f.length > remaining) alert("Only " + remaining + " more file(s) allowed.");
69      for (var i = 0; i < count; i++) uploadFile(f[i]);
70      this.value = "";
71    });
72
73    $del.on("click", function (e) {
74      e.preventDefault();
75      e.stopPropagation();
76      files = [];
77      slots.forEach(function ($s) { $s.val("").prop("disabled", true); });
78      if ($names.length) $names.empty();
79      $(this).hide();
80    });
81
82    function uploadFile(file) {
83      if (file.size > MAX_BYTES) { alert(file.name + " is too large. Maximum 10MB per file."); return; }
84      var fd = new FormData();
85      fd.append("file", file);
86      if (ACCOUNT_ID) fd.append("accountid", ACCOUNT_ID);
87      var headers = {};
88      if (TDS_API_KEY) headers["TDS-API-KEY"] = TDS_API_KEY; // omitted in proxied mode
89
90      inflight++; status();
91      $.ajax({
92        type: "POST",
93        url: UPLOAD_ENDPOINT,
94        data: fd,
95        processData: false,
96        contentType: false,
97        headers: headers,
98        dataType: "json",
99        success: function (r) {
100          inflight--;
101          if (r && r.success && r.url) {
102            files.push({ url: r.url, name: r.name || file.name });
103            var idx = files.length;
104            if (idx <= MAX) slots[idx - 1].val(r.url).prop("disabled", false);
105            render();
106          } else {
107            alert("Failed to upload " + file.name + ". Please try again.");
108          }
109          status();
110        },
111        error: function () {
112          inflight--;
113          alert("Failed to upload " + file.name + ". Please try again.");
114          status();
115        }
116      });
117    }
118
119    function render() {
120      if (!$names.length) return;
121      if (!files.length) { $names.empty(); if ($del.length) $del.hide(); return; }
122      $names.text(files.map(function (f) { return f.name; }).join(", "));
123      if ($del.length) $del.show();
124    }
125
126    function status() {
127      if (inflight > 0) {
128        if ($loader.length) $loader.css("display", "inline");
129        $('.ajax_form button[type="submit"]').prop("disabled", true);
130      } else {
131        if ($loader.length) $loader.hide();
132        $('.ajax_form button[type="submit"]').prop("disabled", false);
133      }
134    }
135  });
136})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.