| Line 0
          
      
      
        Link Here | 
            
              |  |  | 1 | (function () { | 
            
              | 2 |     window.addEventListener('load', onload); | 
            
              | 3 |  | 
            
              | 4 |     // Delay between API requests | 
            
              | 5 |     var debounceDelay = 1000; | 
            
              | 6 |  | 
            
              | 7 |     // Elements we work frequently with | 
            
              | 8 |     var textarea = document.getElementById("identifiers_input"); | 
            
              | 9 |     var nameInput = document.getElementById("name"); | 
            
              | 10 |     var cardnumberInput = document.getElementById("cardnumber"); | 
            
              | 11 |     var branchcodeSelect = document.getElementById("branchcode"); | 
            
              | 12 |     var processButton = document.getElementById("process_button"); | 
            
              | 13 |     var createButton = document.getElementById("button_create_batch"); | 
            
              | 14 |     var finishButton = document.getElementById("button_finish"); | 
            
              | 15 |     var batchItemsDisplay = document.getElementById("add_batch_items"); | 
            
              | 16 |     var createProgressTotal = document.getElementById("processed_total"); | 
            
              | 17 |     var createProgressCount = document.getElementById("processed_count"); | 
            
              | 18 |     var createProgressFailed = document.getElementById("processed_failed"); | 
            
              | 19 |     var createProgressBar = document.getElementById("processed_progress_bar"); | 
            
              | 20 |     var identifierTable = document.getElementById('identifier-table'); | 
            
              | 21 |     var createRequestsButton = document.getElementById('create-requests-button'); | 
            
              | 22 |  | 
            
              | 23 |  | 
            
              | 24 |     // We need a data structure keyed on identifier type, which tells us how to parse that | 
            
              | 25 |     // identifier type and what services can get its metadata. We receive an array of | 
            
              | 26 |     // available services | 
            
              | 27 |     var supportedIdentifiers = {}; | 
            
              | 28 |     metadata_enrichment_services.forEach(function (service) { | 
            
              | 29 |         // Iterate the identifiers that this service supports | 
            
              | 30 |         Object.keys(service.identifiers_supported).forEach(function (idType) { | 
            
              | 31 |             if (!supportedIdentifiers[idType]) { | 
            
              | 32 |                 supportedIdentifiers[idType] = []; | 
            
              | 33 |             } | 
            
              | 34 |             supportedIdentifiers[idType].push(service); | 
            
              | 35 |         }); | 
            
              | 36 |     }); | 
            
              | 37 |  | 
            
              | 38 |     // An object for when we're creating a new batch | 
            
              | 39 |     var emptyBatch = { | 
            
              | 40 |         name: '', | 
            
              | 41 |         backend: null, | 
            
              | 42 |         cardnumber: '', | 
            
              | 43 |         branchcode: '' | 
            
              | 44 |     }; | 
            
              | 45 |  | 
            
              | 46 |     // The object that holds the batch we're working with | 
            
              | 47 |     // It's a proxy so we can update portions of the UI | 
            
              | 48 |     // upon changes | 
            
              | 49 |     var batch = new Proxy( | 
            
              | 50 |         { data: {} }, | 
            
              | 51 |         { | 
            
              | 52 |             get: function (obj, prop) { | 
            
              | 53 |                 return obj[prop]; | 
            
              | 54 |             }, | 
            
              | 55 |             set: function (obj, prop, value) { | 
            
              | 56 |                 obj[prop] = value; | 
            
              | 57 |                 manageBatchItemsDisplay(); | 
            
              | 58 |                 updateBatchInputs(); | 
            
              | 59 |                 setFinishButton(); | 
            
              | 60 |                 disableCardnumberInput(); | 
            
              | 61 |                 displayPatronName(); | 
            
              | 62 |             } | 
            
              | 63 |         } | 
            
              | 64 |     ); | 
            
              | 65 |  | 
            
              | 66 |     // The object that holds the contents of the table | 
            
              | 67 |     // It's a proxy so we can make it automatically redraw the | 
            
              | 68 |     // table upon changes | 
            
              | 69 |     var tableContent = new Proxy( | 
            
              | 70 |         { data: [] }, | 
            
              | 71 |         { | 
            
              | 72 |             get: function (obj, prop) { | 
            
              | 73 |                 return obj[prop]; | 
            
              | 74 |             }, | 
            
              | 75 |             set: function (obj, prop, value) { | 
            
              | 76 |                 obj[prop] = value; | 
            
              | 77 |                 updateTable(); | 
            
              | 78 |                 updateRowCount(); | 
            
              | 79 |                 updateProcessTotals(); | 
            
              | 80 |                 checkAvailability(); | 
            
              | 81 |             } | 
            
              | 82 |         } | 
            
              | 83 |     ); | 
            
              | 84 |  | 
            
              | 85 |     var progressTotals = new Proxy( | 
            
              | 86 |         { | 
            
              | 87 |             data: {} | 
            
              | 88 |         }, | 
            
              | 89 |         { | 
            
              | 90 |             get: function (obj, prop) { | 
            
              | 91 |                 return obj[prop]; | 
            
              | 92 |             }, | 
            
              | 93 |             set: function (obj, prop, value) { | 
            
              | 94 |                 obj[prop] = value; | 
            
              | 95 |                 showCreateRequestsButton(); | 
            
              | 96 |             } | 
            
              | 97 |         } | 
            
              | 98 |     ); | 
            
              | 99 |  | 
            
              | 100 |     // Keep track of submission API calls that are in progress | 
            
              | 101 |     // so we don't duplicate them | 
            
              | 102 |     var submissionSent = {}; | 
            
              | 103 |  | 
            
              | 104 |     // Keep track of availability API calls that are in progress | 
            
              | 105 |     // so we don't duplicate them | 
            
              | 106 |     var availabilitySent = {}; | 
            
              | 107 |  | 
            
              | 108 |     // The datatable | 
            
              | 109 |     var table; | 
            
              | 110 |     var tableEl = document.getElementById('identifier-table'); | 
            
              | 111 |  | 
            
              | 112 |     // The element that potentially holds the ID of the batch | 
            
              | 113 |     // we're working with | 
            
              | 114 |     var idEl = document.getElementById('ill-batch-details'); | 
            
              | 115 |     var batchId = null; | 
            
              | 116 |     var backend = null; | 
            
              | 117 |  | 
            
              | 118 |     function onload() { | 
            
              | 119 |         $('#ill-batch-modal').on('shown.bs.modal', function () { | 
            
              | 120 |             init(); | 
            
              | 121 |             patronAutocomplete(); | 
            
              | 122 |             batchInputsEventListeners(); | 
            
              | 123 |             createButtonEventListener(); | 
            
              | 124 |             createRequestsButtonEventListener(); | 
            
              | 125 |             moreLessEventListener(); | 
            
              | 126 |             removeRowEventListener(); | 
            
              | 127 |         }); | 
            
              | 128 |         $('#ill-batch-modal').on('hidden.bs.modal', function () { | 
            
              | 129 |             // Reset our state when we close the modal | 
            
              | 130 |             delete idEl.dataset.batchId; | 
            
              | 131 |             delete idEl.dataset.backend; | 
            
              | 132 |             batchId = null; | 
            
              | 133 |             tableEl.style.display = 'none'; | 
            
              | 134 |             tableContent.data = []; | 
            
              | 135 |             progressTotals.data = { | 
            
              | 136 |                 total: 0, | 
            
              | 137 |                 count: 0, | 
            
              | 138 |                 failed: 0 | 
            
              | 139 |             }; | 
            
              | 140 |             textarea.value = ''; | 
            
              | 141 |             batch.data = {}; | 
            
              | 142 |             // Remove event listeners we created | 
            
              | 143 |             removeEventListeners(); | 
            
              | 144 |         }); | 
            
              | 145 |     }; | 
            
              | 146 |  | 
            
              | 147 |     function init() { | 
            
              | 148 |         batchId = idEl.dataset.batchId; | 
            
              | 149 |         backend = idEl.dataset.backend; | 
            
              | 150 |         emptyBatch.backend = backend; | 
            
              | 151 |         progressTotals.data = { | 
            
              | 152 |             total: 0, | 
            
              | 153 |             count: 0, | 
            
              | 154 |             failed: 0 | 
            
              | 155 |         }; | 
            
              | 156 |         if (batchId) { | 
            
              | 157 |             fetchBatch(); | 
            
              | 158 |             setModalHeading(true); | 
            
              | 159 |         } else { | 
            
              | 160 |             batch.data = emptyBatch; | 
            
              | 161 |             setModalHeading(); | 
            
              | 162 |         } | 
            
              | 163 |         finishButtonEventListener(); | 
            
              | 164 |         processButtonEventListener(); | 
            
              | 165 |         identifierTextareaEventListener(); | 
            
              | 166 |         displaySupportedIdentifiers(); | 
            
              | 167 |         createButtonEventListener(); | 
            
              | 168 |         updateRowCount(); | 
            
              | 169 |     }; | 
            
              | 170 |  | 
            
              | 171 |     function initPostCreate() { | 
            
              | 172 |         disableCreateButton(); | 
            
              | 173 |     }; | 
            
              | 174 |  | 
            
              | 175 |     function setFinishButton() { | 
            
              | 176 |         if (batch.data.patron) { | 
            
              | 177 |             finishButton.removeAttribute('disabled'); | 
            
              | 178 |         } | 
            
              | 179 |     }; | 
            
              | 180 |  | 
            
              | 181 |     function setModalHeading(isUpdate) { | 
            
              | 182 |         var heading = document.getElementById('ill-batch-modal-label'); | 
            
              | 183 |         heading.textContent = isUpdate ? ill_batch_update : ill_batch_add; | 
            
              | 184 |     } | 
            
              | 185 |  | 
            
              | 186 |     // Identify items that have metadata and therefore can have a local request | 
            
              | 187 |     // created, and do so | 
            
              | 188 |     function requestRequestable() { | 
            
              | 189 |         createRequestsButton.setAttribute('disabled', true); | 
            
              | 190 |         var toCheck = tableContent.data; | 
            
              | 191 |         toCheck.forEach(function (row) { | 
            
              | 192 |             if ( | 
            
              | 193 |                 !row.requestId && | 
            
              | 194 |                 Object.keys(row.metadata).length > 0 && | 
            
              | 195 |                 !submissionSent[row.value] | 
            
              | 196 |             ) { | 
            
              | 197 |                 submissionSent[row.value] = 1; | 
            
              | 198 |                 makeLocalSubmission(row.value, row.metadata); | 
            
              | 199 |             } | 
            
              | 200 |         }); | 
            
              | 201 |     }; | 
            
              | 202 |  | 
            
              | 203 |     // Identify items that can have their availability checked, and do it | 
            
              | 204 |     function checkAvailability() { | 
            
              | 205 |         // Only proceed if we've got services that can check availability | 
            
              | 206 |         if (!batch_availability_services || batch_availability_services.length === 0) return; | 
            
              | 207 |         var toCheck = tableContent.data; | 
            
              | 208 |         toCheck.forEach(function (row) { | 
            
              | 209 |             if ( | 
            
              | 210 |                 !row.url && | 
            
              | 211 |                 Object.keys(row.metadata).length > 0 && | 
            
              | 212 |                 !availabilitySent[row.value] | 
            
              | 213 |             ) { | 
            
              | 214 |                 availabilitySent[row.value] = 1; | 
            
              | 215 |                 getAvailability(row.value, row.metadata); | 
            
              | 216 |             } | 
            
              | 217 |         }); | 
            
              | 218 |     }; | 
            
              | 219 |  | 
            
              | 220 |     // Check availability services for immediate availability, if found, | 
            
              | 221 |     // create a link in the table linking to the item | 
            
              | 222 |     function getAvailability(identifier, metadata) { | 
            
              | 223 |         // Prep the metadata for passing to the availability plugins | 
            
              | 224 |         var prepped = encodeURIComponent(base64EncodeUnicode(JSON.stringify(metadata))); | 
            
              | 225 |         for (i = 0; i < batch_availability_services.length; i++) { | 
            
              | 226 |             var service = batch_availability_services[i]; | 
            
              | 227 |             window.doApiRequest( | 
            
              | 228 |                 service.endpoint + prepped | 
            
              | 229 |             ) | 
            
              | 230 |                 .then(function (response) { | 
            
              | 231 |                     return response.json(); | 
            
              | 232 |                 }) | 
            
              | 233 |                 .then(function (data) { | 
            
              | 234 |                     if (data.results.search_results && data.results.search_results.length > 0) { | 
            
              | 235 |                         var result = data.results.search_results[0]; | 
            
              | 236 |                         tableContent.data = tableContent.data.map(function (row) { | 
            
              | 237 |                             if (row.value === identifier) { | 
            
              | 238 |                                 row.url = result.url; | 
            
              | 239 |                                 row.availabilitySupplier = service.name; | 
            
              | 240 |                             } | 
            
              | 241 |                             return row; | 
            
              | 242 |                         }); | 
            
              | 243 |                     } | 
            
              | 244 |                 }); | 
            
              | 245 |         } | 
            
              | 246 |     }; | 
            
              | 247 |  | 
            
              | 248 |     // Help btoa with > 8 bit strings | 
            
              | 249 |     // Shamelessly grabbed from: https://www.base64encoder.io/javascript/ | 
            
              | 250 |     function base64EncodeUnicode(str) { | 
            
              | 251 |         // First we escape the string using encodeURIComponent to get the UTF-8 encoding of the characters, | 
            
              | 252 |         // then we convert the percent encodings into raw bytes, and finally feed it to btoa() function. | 
            
              | 253 |         utf8Bytes = encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) { | 
            
              | 254 |                 return String.fromCharCode('0x' + p1); | 
            
              | 255 |         }); | 
            
              | 256 |  | 
            
              | 257 |         return btoa(utf8Bytes); | 
            
              | 258 |     }; | 
            
              | 259 |  | 
            
              | 260 |     // Create a local submission and update our local state | 
            
              | 261 |     // upon success | 
            
              | 262 |     function makeLocalSubmission(identifier, metadata) { | 
            
              | 263 |         var payload = { | 
            
              | 264 |             batch_id: batchId, | 
            
              | 265 |             backend: batch.data.backend, | 
            
              | 266 |             borrowernumber: batch.data.patron.borrowernumber, | 
            
              | 267 |             branchcode: batch.data.branchcode, | 
            
              | 268 |             metadata: metadata | 
            
              | 269 |         }; | 
            
              | 270 |         window.doCreateSubmission(payload) | 
            
              | 271 |             .then(function (response) { | 
            
              | 272 |                 return response.json(); | 
            
              | 273 |             }) | 
            
              | 274 |             .then(function (data) { | 
            
              | 275 |                 tableContent.data = tableContent.data.map(function (row) { | 
            
              | 276 |                     if (row.value === identifier) { | 
            
              | 277 |                         row.requestId = data.illrequest_id; | 
            
              | 278 |                     } | 
            
              | 279 |                     return row; | 
            
              | 280 |                 }); | 
            
              | 281 |             }) | 
            
              | 282 |             .catch(function () { | 
            
              | 283 |                 window.handleApiError(ill_batch_api_request_fail); | 
            
              | 284 |             }); | 
            
              | 285 |     }; | 
            
              | 286 |  | 
            
              | 287 |     function updateProcessTotals() { | 
            
              | 288 |         var init = { | 
            
              | 289 |             total: 0, | 
            
              | 290 |             count: 0, | 
            
              | 291 |             failed: 0 | 
            
              | 292 |         }; | 
            
              | 293 |         progressTotals.data = init; | 
            
              | 294 |         var toUpdate = progressTotals.data; | 
            
              | 295 |         tableContent.data.forEach(function (row) { | 
            
              | 296 |             toUpdate.total++; | 
            
              | 297 |             if (Object.keys(row.metadata).length > 0 || row.failed.length > 0) { | 
            
              | 298 |                 toUpdate.count++; | 
            
              | 299 |             } | 
            
              | 300 |             if (Object.keys(row.failed).length > 0) { | 
            
              | 301 |                 toUpdate.failed++; | 
            
              | 302 |             } | 
            
              | 303 |         }); | 
            
              | 304 |         createProgressTotal.innerHTML = toUpdate.total; | 
            
              | 305 |         createProgressCount.innerHTML = toUpdate.count; | 
            
              | 306 |         createProgressFailed.innerHTML = toUpdate.failed; | 
            
              | 307 |         var percentDone = Math.ceil((toUpdate.count / toUpdate.total) * 100); | 
            
              | 308 |         createProgressBar.setAttribute('aria-valuenow', percentDone); | 
            
              | 309 |         createProgressBar.innerHTML = percentDone + '%'; | 
            
              | 310 |         createProgressBar.style.width = percentDone + '%'; | 
            
              | 311 |         progressTotals.data = toUpdate; | 
            
              | 312 |     }; | 
            
              | 313 |  | 
            
              | 314 |     function displayPatronName() { | 
            
              | 315 |         var span = document.getElementById('patron_link'); | 
            
              | 316 |         if (batch.data.patron) { | 
            
              | 317 |             var link = createPatronLink(); | 
            
              | 318 |             span.appendChild(link); | 
            
              | 319 |         } else { | 
            
              | 320 |             if (span.children.length > 0) { | 
            
              | 321 |                 span.removeChild(span.firstChild); | 
            
              | 322 |             } | 
            
              | 323 |         } | 
            
              | 324 |     }; | 
            
              | 325 |  | 
            
              | 326 |     function removeEventListeners() { | 
            
              | 327 |         textarea.removeEventListener('paste', processButtonState); | 
            
              | 328 |         textarea.removeEventListener('keyup', processButtonState); | 
            
              | 329 |         processButton.removeEventListener('click', processIdentifiers); | 
            
              | 330 |         nameInput.removeEventListener('keyup', createButtonState); | 
            
              | 331 |         cardnumberInput.removeEventListener('keyup', createButtonState); | 
            
              | 332 |         branchcodeSelect.removeEventListener('change', createButtonState); | 
            
              | 333 |         createButton.removeEventListener('click', createBatch); | 
            
              | 334 |         identifierTable.removeEventListener('click', toggleMetadata); | 
            
              | 335 |         identifierTable.removeEventListener('click', removeRow); | 
            
              | 336 |         createRequestsButton.remove('click', requestRequestable); | 
            
              | 337 |     }; | 
            
              | 338 |  | 
            
              | 339 |     function finishButtonEventListener() { | 
            
              | 340 |         finishButton.addEventListener('click', doFinish); | 
            
              | 341 |     }; | 
            
              | 342 |  | 
            
              | 343 |     function identifierTextareaEventListener() { | 
            
              | 344 |         textarea.addEventListener('paste', textareaUpdate); | 
            
              | 345 |         textarea.addEventListener('keyup', textareaUpdate); | 
            
              | 346 |     }; | 
            
              | 347 |  | 
            
              | 348 |     function processButtonEventListener() { | 
            
              | 349 |         processButton.addEventListener('click', processIdentifiers); | 
            
              | 350 |     }; | 
            
              | 351 |  | 
            
              | 352 |     function createRequestsButtonEventListener() { | 
            
              | 353 |         createRequestsButton.addEventListener('click', requestRequestable); | 
            
              | 354 |     }; | 
            
              | 355 |  | 
            
              | 356 |     function createButtonEventListener() { | 
            
              | 357 |         createButton.addEventListener('click', createBatch); | 
            
              | 358 |     }; | 
            
              | 359 |  | 
            
              | 360 |     function batchInputsEventListeners() { | 
            
              | 361 |         nameInput.addEventListener('keyup', createButtonState); | 
            
              | 362 |         cardnumberInput.addEventListener('keyup', createButtonState); | 
            
              | 363 |         branchcodeSelect.addEventListener('change', createButtonState); | 
            
              | 364 |     }; | 
            
              | 365 |  | 
            
              | 366 |     function moreLessEventListener() { | 
            
              | 367 |         identifierTable.addEventListener('click', toggleMetadata); | 
            
              | 368 |     }; | 
            
              | 369 |  | 
            
              | 370 |     function removeRowEventListener() { | 
            
              | 371 |         identifierTable.addEventListener('click', removeRow); | 
            
              | 372 |     }; | 
            
              | 373 |  | 
            
              | 374 |     function textareaUpdate() { | 
            
              | 375 |         processButtonState(); | 
            
              | 376 |         updateRowCount(); | 
            
              | 377 |     }; | 
            
              | 378 |  | 
            
              | 379 |     function processButtonState() { | 
            
              | 380 |         if (textarea.value.length > 0) { | 
            
              | 381 |             processButton.removeAttribute('disabled'); | 
            
              | 382 |         } else { | 
            
              | 383 |             processButton.setAttribute('disabled', 1); | 
            
              | 384 |         } | 
            
              | 385 |     }; | 
            
              | 386 |  | 
            
              | 387 |     function disableCardnumberInput() { | 
            
              | 388 |         if (batch.data.patron) { | 
            
              | 389 |             cardnumberInput.setAttribute('disabled', true); | 
            
              | 390 |         } else { | 
            
              | 391 |             cardnumberInput.removeAttribute('disabled'); | 
            
              | 392 |         } | 
            
              | 393 |     }; | 
            
              | 394 |  | 
            
              | 395 |     function createButtonState() { | 
            
              | 396 |         if ( | 
            
              | 397 |             nameInput.value.length > 0 && | 
            
              | 398 |             cardnumberInput.value.length > 0 && | 
            
              | 399 |             branchcodeSelect.selectedOptions.length === 1 | 
            
              | 400 |         ) { | 
            
              | 401 |             createButton.removeAttribute('disabled'); | 
            
              | 402 |             createButton.setAttribute('display', 'inline-block'); | 
            
              | 403 |         } else { | 
            
              | 404 |             createButton.setAttribute('disabled', 1); | 
            
              | 405 |             createButton.setAttribute('display', 'none'); | 
            
              | 406 |         } | 
            
              | 407 |     }; | 
            
              | 408 |  | 
            
              | 409 |     function doFinish() { | 
            
              | 410 |         updateBatch() | 
            
              | 411 |             .then(function () { | 
            
              | 412 |                 $('#ill-batch-modal').modal({ show: false }); | 
            
              | 413 |                 location.href = '/cgi-bin/koha/ill/ill-requests.pl?batch_id=' + batch.data.id; | 
            
              | 414 |             }); | 
            
              | 415 |     }; | 
            
              | 416 |  | 
            
              | 417 |     // Get the batch | 
            
              | 418 |     function fetchBatch() { | 
            
              | 419 |         window.doBatchApiRequest("/" + batchId) | 
            
              | 420 |             .then(function (response) { | 
            
              | 421 |                 return response.json(); | 
            
              | 422 |             }) | 
            
              | 423 |             .then(function (jsoned) { | 
            
              | 424 |                 batch.data = { | 
            
              | 425 |                     id: jsoned.id, | 
            
              | 426 |                     name: jsoned.name, | 
            
              | 427 |                     backend: jsoned.backend, | 
            
              | 428 |                     cardnumber: jsoned.cardnumber, | 
            
              | 429 |                     branchcode: jsoned.branchcode | 
            
              | 430 |                 } | 
            
              | 431 |                 return jsoned; | 
            
              | 432 |             }) | 
            
              | 433 |             .then(function (data) { | 
            
              | 434 |                 batch.data = data; | 
            
              | 435 |             }) | 
            
              | 436 |             .catch(function () { | 
            
              | 437 |                 window.handleApiError(ill_batch_api_fail); | 
            
              | 438 |             }); | 
            
              | 439 |  | 
            
              | 440 |     }; | 
            
              | 441 |  | 
            
              | 442 |     function createBatch() { | 
            
              | 443 |         var selectedBranchcode = branchcodeSelect.selectedOptions[0].value; | 
            
              | 444 |         return doBatchApiRequest('', { | 
            
              | 445 |             method: 'POST', | 
            
              | 446 |             headers: { | 
            
              | 447 |                 'Content-type': 'application/json' | 
            
              | 448 |             }, | 
            
              | 449 |             body: JSON.stringify({ | 
            
              | 450 |                 name: nameInput.value, | 
            
              | 451 |                 backend: backend, | 
            
              | 452 |                 cardnumber: cardnumberInput.value, | 
            
              | 453 |                 branchcode: selectedBranchcode | 
            
              | 454 |             }) | 
            
              | 455 |         }) | 
            
              | 456 |             .then(function (response) { | 
            
              | 457 |                 return response.json(); | 
            
              | 458 |             }) | 
            
              | 459 |             .then(function (body) { | 
            
              | 460 |                 batchId = body.id; | 
            
              | 461 |                 batch.data = { | 
            
              | 462 |                     id: body.id, | 
            
              | 463 |                     name: body.name, | 
            
              | 464 |                     backend: body.backend, | 
            
              | 465 |                     cardnumber: body.patron.cardnumber, | 
            
              | 466 |                     branchcode: body.branchcode, | 
            
              | 467 |                     patron: body.patron | 
            
              | 468 |                 }; | 
            
              | 469 |                 initPostCreate(); | 
            
              | 470 |             }) | 
            
              | 471 |             .catch(function () { | 
            
              | 472 |                 handleApiError(ill_batch_create_api_fail); | 
            
              | 473 |             }); | 
            
              | 474 |     }; | 
            
              | 475 |  | 
            
              | 476 |     function updateBatch() { | 
            
              | 477 |         var selectedBranchcode = branchcodeSelect.selectedOptions[0].value; | 
            
              | 478 |         return doBatchApiRequest('/' + batch.data.id, { | 
            
              | 479 |             method: 'PUT', | 
            
              | 480 |             headers: { | 
            
              | 481 |                 'Content-type': 'application/json' | 
            
              | 482 |             }, | 
            
              | 483 |             body: JSON.stringify({ | 
            
              | 484 |                 name: nameInput.value, | 
            
              | 485 |                 backend: batch.data.backend, | 
            
              | 486 |                 cardnumber: batch.data.patron.cardnumber, | 
            
              | 487 |                 branchcode: selectedBranchcode | 
            
              | 488 |             }) | 
            
              | 489 |         }) | 
            
              | 490 |             .catch(function () { | 
            
              | 491 |                 handleApiError(ill_batch_update_api_fail); | 
            
              | 492 |             }); | 
            
              | 493 |     }; | 
            
              | 494 |  | 
            
              | 495 |     function displaySupportedIdentifiers() { | 
            
              | 496 |         var names = Object.keys(supportedIdentifiers).map(function (identifier) { | 
            
              | 497 |             return window['ill_batch_' + identifier]; | 
            
              | 498 |         }); | 
            
              | 499 |         var displayEl = document.getElementById('supported_identifiers'); | 
            
              | 500 |         displayEl.textContent = names.length > 0 ? names.join(', ') : ill_batch_none; | 
            
              | 501 |     } | 
            
              | 502 |  | 
            
              | 503 |     function updateRowCount() { | 
            
              | 504 |         var textEl = document.getElementById('row_count_value'); | 
            
              | 505 |         var val = textarea.value.trim(); | 
            
              | 506 |         var cnt = 0; | 
            
              | 507 |         if (val.length > 0) { | 
            
              | 508 |             cnt = val.split(/\n/).length; | 
            
              | 509 |         } | 
            
              | 510 |         textEl.textContent = cnt; | 
            
              | 511 |     } | 
            
              | 512 |  | 
            
              | 513 |     function showProgress() { | 
            
              | 514 |         var el = document.getElementById('create-progress'); | 
            
              | 515 |         el.style.display = 'block'; | 
            
              | 516 |     } | 
            
              | 517 |  | 
            
              | 518 |     function showCreateRequestsButton() { | 
            
              | 519 |         var data = progressTotals.data; | 
            
              | 520 |         var el = document.getElementById('create-requests'); | 
            
              | 521 |         el.style.display = (data.total > 0 && data.count === data.total) ? 'flex' : 'none'; | 
            
              | 522 |     } | 
            
              | 523 |  | 
            
              | 524 |     async function processIdentifiers() { | 
            
              | 525 |         var content = textarea.value; | 
            
              | 526 |         hideErrors(); | 
            
              | 527 |         if (content.length === 0) return; | 
            
              | 528 |  | 
            
              | 529 |         disableProcessButton(); | 
            
              | 530 |         var label = document.getElementById('progress-label').firstChild; | 
            
              | 531 |         label.innerHTML = ill_batch_retrieving_metadata; | 
            
              | 532 |         showProgress(); | 
            
              | 533 |  | 
            
              | 534 |         // Errors encountered when processing | 
            
              | 535 |         var processErrors = {}; | 
            
              | 536 |  | 
            
              | 537 |         // Prepare the content, including trimming each row | 
            
              | 538 |         var contentArr = content.split(/\n/); | 
            
              | 539 |         var trimmed = contentArr.map(function (row) { | 
            
              | 540 |             return row.trim(); | 
            
              | 541 |         }); | 
            
              | 542 |  | 
            
              | 543 |         var parsed = []; | 
            
              | 544 |  | 
            
              | 545 |         trimmed.forEach(function (identifier) { | 
            
              | 546 |             var match = identifyIdentifier(identifier); | 
            
              | 547 |             // If this identifier is not identifiable or | 
            
              | 548 |             // looks like more than one type, we can't be sure | 
            
              | 549 |             // what it is | 
            
              | 550 |             if (match.length != 1) { | 
            
              | 551 |                 parsed.push({ | 
            
              | 552 |                     type: 'unknown', | 
            
              | 553 |                     value: identifier | 
            
              | 554 |                 }); | 
            
              | 555 |             } else { | 
            
              | 556 |                 parsed.push(match[0]); | 
            
              | 557 |             } | 
            
              | 558 |         }); | 
            
              | 559 |  | 
            
              | 560 |         var unknownIdentifiers = parsed | 
            
              | 561 |             .filter(function (parse) { | 
            
              | 562 |                 if (parse.type == 'unknown') { | 
            
              | 563 |                     return parse; | 
            
              | 564 |                 } | 
            
              | 565 |             }) | 
            
              | 566 |             .map(function (filtered) { | 
            
              | 567 |                 return filtered.value; | 
            
              | 568 |             }); | 
            
              | 569 |  | 
            
              | 570 |         if (unknownIdentifiers.length > 0) { | 
            
              | 571 |             processErrors.badidentifiers = { | 
            
              | 572 |                 element: 'badids', | 
            
              | 573 |                 values: unknownIdentifiers.join(', ') | 
            
              | 574 |             }; | 
            
              | 575 |         }; | 
            
              | 576 |  | 
            
              | 577 |         // Deduping | 
            
              | 578 |         var deduped = []; | 
            
              | 579 |         var dupes = {}; | 
            
              | 580 |         parsed.forEach(function (row) { | 
            
              | 581 |             var value = row.value; | 
            
              | 582 |             var alreadyInDeduped = deduped.filter(function (d) { | 
            
              | 583 |                 return d.value === value; | 
            
              | 584 |             }); | 
            
              | 585 |             if (alreadyInDeduped.length > 0 && !dupes[value]) { | 
            
              | 586 |                 dupes[value] = 1; | 
            
              | 587 |             } else if (alreadyInDeduped.length === 0) { | 
            
              | 588 |                 row.metadata = {}; | 
            
              | 589 |                 row.failed = {}; | 
            
              | 590 |                 row.requestId = null; | 
            
              | 591 |                 deduped.push(row); | 
            
              | 592 |             } | 
            
              | 593 |         }); | 
            
              | 594 |         // Update duplicate error if dupes were found | 
            
              | 595 |         if (Object.keys(dupes).length > 0) { | 
            
              | 596 |             processErrors.duplicates = { | 
            
              | 597 |                 element: 'dupelist', | 
            
              | 598 |                 values: Object.keys(dupes).join(', ') | 
            
              | 599 |             }; | 
            
              | 600 |         } | 
            
              | 601 |  | 
            
              | 602 |         // Display any errors | 
            
              | 603 |         displayErrors(processErrors); | 
            
              | 604 |  | 
            
              | 605 |         // Now build and display the table | 
            
              | 606 |         if (!table) { | 
            
              | 607 |             buildTable(); | 
            
              | 608 |         } | 
            
              | 609 |  | 
            
              | 610 |         // We may be appending new values to an existing table, | 
            
              | 611 |         // in which case, ensure we don't create duplicates | 
            
              | 612 |         var tabIdentifiers = tableContent.data.map(function (tabId) { | 
            
              | 613 |             return tabId.value; | 
            
              | 614 |         }); | 
            
              | 615 |         var notInTable = deduped.filter(function (ded) { | 
            
              | 616 |             if (!tabIdentifiers.includes(ded.value)) { | 
            
              | 617 |                 return ded; | 
            
              | 618 |            } | 
            
              | 619 |         }); | 
            
              | 620 |         if (notInTable.length > 0) { | 
            
              | 621 |             tableContent.data = tableContent.data.concat(notInTable); | 
            
              | 622 |         } | 
            
              | 623 |  | 
            
              | 624 |         // Populate metadata for those records that need it | 
            
              | 625 |         var newData = tableContent.data; | 
            
              | 626 |         for (var i = 0; i < tableContent.data.length; i++) { | 
            
              | 627 |             var row = tableContent.data[i]; | 
            
              | 628 |             // Skip rows that don't need populating | 
            
              | 629 |             if ( | 
            
              | 630 |                 Object.keys(tableContent.data[i].metadata).length > 0 || | 
            
              | 631 |                 Object.keys(tableContent.data[i].failed).length > 0 | 
            
              | 632 |             ) continue; | 
            
              | 633 |             var identifier = { type: row.type, value: row.value }; | 
            
              | 634 |             try { | 
            
              | 635 |                 var populated = await populateMetadata(identifier); | 
            
              | 636 |                 row.metadata = populated.results.result || {}; | 
            
              | 637 |             } catch (e) { | 
            
              | 638 |                 row.failed = ill_populate_failed; | 
            
              | 639 |             } | 
            
              | 640 |             newData[i] = row; | 
            
              | 641 |             tableContent.data = newData; | 
            
              | 642 |         } | 
            
              | 643 |     } | 
            
              | 644 |  | 
            
              | 645 |     function disableProcessButton() { | 
            
              | 646 |         processButton.setAttribute('disabled', true); | 
            
              | 647 |     } | 
            
              | 648 |  | 
            
              | 649 |     function disableCreateButton() { | 
            
              | 650 |         createButton.setAttribute('disabled', true); | 
            
              | 651 |     } | 
            
              | 652 |  | 
            
              | 653 |     function disableRemoveRowButtons() { | 
            
              | 654 |         var buttons = document.getElementsByClassName('remove-row'); | 
            
              | 655 |         for (var button of buttons) { | 
            
              | 656 |             button.setAttribute('disabled', true); | 
            
              | 657 |         } | 
            
              | 658 |     } | 
            
              | 659 |  | 
            
              | 660 |     async function populateMetadata(identifier) { | 
            
              | 661 |         // All services that support this identifier type | 
            
              | 662 |         var services = supportedIdentifiers[identifier.type]; | 
            
              | 663 |         // Check each service and use the first results we get, if any | 
            
              | 664 |         for (var i = 0; i < services.length; i++) { | 
            
              | 665 |             var service = services[i]; | 
            
              | 666 |             var endpoint = '/api/v1/contrib/' + service.api_namespace + service.search_endpoint + '?' + identifier.type + '=' + identifier.value; | 
            
              | 667 |             var metadata = await getMetadata(endpoint); | 
            
              | 668 |             if (metadata.errors.length === 0) { | 
            
              | 669 |                 var parsed = await parseMetadata(metadata, service); | 
            
              | 670 |                 if (parsed.errors.length > 0) { | 
            
              | 671 |                     throw Error(metadata.errors.map(function (error) { | 
            
              | 672 |                         return error.message; | 
            
              | 673 |                     }).join(', ')); | 
            
              | 674 |                 } | 
            
              | 675 |                 return parsed; | 
            
              | 676 |             } | 
            
              | 677 |         } | 
            
              | 678 |     }; | 
            
              | 679 |  | 
            
              | 680 |     async function getMetadata(endpoint) { | 
            
              | 681 |         var response = await debounce(doApiRequest)(endpoint); | 
            
              | 682 |         return response.json(); | 
            
              | 683 |     }; | 
            
              | 684 |  | 
            
              | 685 |     async function parseMetadata(metadata, service) { | 
            
              | 686 |         var endpoint = '/api/v1/contrib/' + service.api_namespace + service.ill_parse_endpoint; | 
            
              | 687 |         var response = await doApiRequest(endpoint, { | 
            
              | 688 |             method: 'POST', | 
            
              | 689 |             headers: { | 
            
              | 690 |                 'Content-type': 'application/json' | 
            
              | 691 |             }, | 
            
              | 692 |             body: JSON.stringify(metadata) | 
            
              | 693 |         }); | 
            
              | 694 |         return response.json(); | 
            
              | 695 |     } | 
            
              | 696 |  | 
            
              | 697 |     // A render function for identifier type | 
            
              | 698 |     function createIdentifierType(data) { | 
            
              | 699 |         return window['ill_batch_' + data]; | 
            
              | 700 |     }; | 
            
              | 701 |  | 
            
              | 702 |     // Get an item's title | 
            
              | 703 |     function getTitle(meta) { | 
            
              | 704 |         if (meta.article_title && meta.article_title.length > 0) { | 
            
              | 705 |             return { | 
            
              | 706 |                 prop: 'article_title', | 
            
              | 707 |                 value: meta.article_title | 
            
              | 708 |             }; | 
            
              | 709 |         } else if (meta.title && meta.title.length > 0) { | 
            
              | 710 |             return { | 
            
              | 711 |                 prop: 'title', | 
            
              | 712 |                 value: meta.title | 
            
              | 713 |             }; | 
            
              | 714 |         } | 
            
              | 715 |     }; | 
            
              | 716 |  | 
            
              | 717 |     // Create a metadata row | 
            
              | 718 |     function createMetadataRow(data, meta, prop) { | 
            
              | 719 |         if (!meta[prop]) return; | 
            
              | 720 |  | 
            
              | 721 |         var div = document.createElement('div'); | 
            
              | 722 |         div.classList.add('metadata-row'); | 
            
              | 723 |         var label = document.createElement('span'); | 
            
              | 724 |         label.classList.add('metadata-label'); | 
            
              | 725 |         label.innerText = ill_batch_metadata[prop] + ': '; | 
            
              | 726 |  | 
            
              | 727 |         // Add a link to the availability URL if appropriate | 
            
              | 728 |         var value; | 
            
              | 729 |         if (!data.url) { | 
            
              | 730 |             value = document.createElement('span'); | 
            
              | 731 |         } else { | 
            
              | 732 |             value = document.createElement('a'); | 
            
              | 733 |             value.setAttribute('href', data.url); | 
            
              | 734 |             value.setAttribute('target', '_blank'); | 
            
              | 735 |             value.setAttribute('title', ill_batch_available_via + ' ' + data.availabilitySupplier); | 
            
              | 736 |         } | 
            
              | 737 |         value.classList.add('metadata-value'); | 
            
              | 738 |         value.innerText = meta[prop]; | 
            
              | 739 |         div.appendChild(label); | 
            
              | 740 |         div.appendChild(value); | 
            
              | 741 |  | 
            
              | 742 |         return div; | 
            
              | 743 |     } | 
            
              | 744 |  | 
            
              | 745 |     // A render function for displaying metadata | 
            
              | 746 |     function createMetadata(x, y, data) { | 
            
              | 747 |         // If the fetch failed | 
            
              | 748 |         if (data.failed.length > 0) { | 
            
              | 749 |             return data.failed; | 
            
              | 750 |         } | 
            
              | 751 |  | 
            
              | 752 |         // If we've not yet got any metadata back | 
            
              | 753 |         if (Object.keys(data.metadata).length === 0) { | 
            
              | 754 |             return ill_populate_waiting; | 
            
              | 755 |         } | 
            
              | 756 |  | 
            
              | 757 |         var core = ['doi', 'pmid', 'issn', 'title', 'year', 'issue', 'pages', 'publisher', 'article_title', 'article_author', 'volume']; | 
            
              | 758 |         var meta = data.metadata; | 
            
              | 759 |  | 
            
              | 760 |         var container = document.createElement('div'); | 
            
              | 761 |         container.classList.add('metadata-container'); | 
            
              | 762 |  | 
            
              | 763 |         // Create the title row | 
            
              | 764 |         var title = getTitle(meta); | 
            
              | 765 |         if (title) { | 
            
              | 766 |             // Remove the title element from the props | 
            
              | 767 |             // we're about to iterate | 
            
              | 768 |             core = core.filter(function (i) { | 
            
              | 769 |                 return i !== title.prop; | 
            
              | 770 |             }); | 
            
              | 771 |             var titleRow = createMetadataRow(data, meta, title.prop); | 
            
              | 772 |             container.appendChild(titleRow); | 
            
              | 773 |         } | 
            
              | 774 |  | 
            
              | 775 |         var remainder = document.createElement('div'); | 
            
              | 776 |         remainder.classList.add('metadata-remainder'); | 
            
              | 777 |         remainder.style.display = 'none'; | 
            
              | 778 |         // Create the remaining rows | 
            
              | 779 |         core.sort().forEach(function (prop) { | 
            
              | 780 |             var div = createMetadataRow(data, meta, prop); | 
            
              | 781 |             if (div) { | 
            
              | 782 |                 remainder.appendChild(div); | 
            
              | 783 |             } | 
            
              | 784 |         }); | 
            
              | 785 |         container.appendChild(remainder); | 
            
              | 786 |  | 
            
              | 787 |         // Add a more/less toggle | 
            
              | 788 |         var firstField = container.firstChild; | 
            
              | 789 |         var moreLess = document.createElement('div'); | 
            
              | 790 |         moreLess.classList.add('more-less'); | 
            
              | 791 |         var moreLessLink = document.createElement('a'); | 
            
              | 792 |         moreLessLink.setAttribute('href', '#'); | 
            
              | 793 |         moreLessLink.classList.add('more-less-link'); | 
            
              | 794 |         moreLessLink.innerText = ' [' + ill_batch_metadata_more + ']'; | 
            
              | 795 |         moreLess.appendChild(moreLessLink); | 
            
              | 796 |         firstField.appendChild(moreLess); | 
            
              | 797 |  | 
            
              | 798 |         return container.outerHTML; | 
            
              | 799 |     }; | 
            
              | 800 |  | 
            
              | 801 |     function removeRow(ev) { | 
            
              | 802 |         if (ev.target.className.includes('remove-row')) { | 
            
              | 803 |             if (!confirm(ill_batch_item_remove)) return; | 
            
              | 804 |             // Find the parent row | 
            
              | 805 |             var ancestor = ev.target.closest('tr'); | 
            
              | 806 |             var identifier = ancestor.querySelector('.identifier').innerText; | 
            
              | 807 |             tableContent.data = tableContent.data.filter(function (row) { | 
            
              | 808 |                 return row.value !== identifier; | 
            
              | 809 |             }); | 
            
              | 810 |         } | 
            
              | 811 |     } | 
            
              | 812 |  | 
            
              | 813 |     function toggleMetadata(ev) { | 
            
              | 814 |         if (ev.target.className === 'more-less-link') { | 
            
              | 815 |             // Find the element we need to show | 
            
              | 816 |             var ancestor = ev.target.closest('.metadata-container'); | 
            
              | 817 |             var meta = ancestor.querySelector('.metadata-remainder'); | 
            
              | 818 |  | 
            
              | 819 |             // Display or hide based on its current state | 
            
              | 820 |             var display = window.getComputedStyle(meta).display; | 
            
              | 821 |  | 
            
              | 822 |             meta.style.display = display === 'block' ? 'none' : 'block'; | 
            
              | 823 |  | 
            
              | 824 |             // Update the More / Less text | 
            
              | 825 |             ev.target.innerText = ' [ ' + (display === 'none' ? ill_batch_metadata_less : ill_batch_metadata_more) + ' ]'; | 
            
              | 826 |         } | 
            
              | 827 |     } | 
            
              | 828 |  | 
            
              | 829 |     // A render function for the link to a request ID | 
            
              | 830 |     function createRequestId(x, y, data) { | 
            
              | 831 |         return data.requestId || '-'; | 
            
              | 832 |     } | 
            
              | 833 |  | 
            
              | 834 |     function buildTable(identifiers) { | 
            
              | 835 |         table = KohaTable('identifier-table', { | 
            
              | 836 |             processing: true, | 
            
              | 837 |             deferRender: true, | 
            
              | 838 |             ordering: false, | 
            
              | 839 |             paging: false, | 
            
              | 840 |             searching: false, | 
            
              | 841 |             autoWidth: false, | 
            
              | 842 |             columns: [ | 
            
              | 843 |                 { | 
            
              | 844 |                     data: 'type', | 
            
              | 845 |                     width: '13%', | 
            
              | 846 |                     render: createIdentifierType | 
            
              | 847 |                 }, | 
            
              | 848 |                 { | 
            
              | 849 |                     data: 'value', | 
            
              | 850 |                     width: '25%', | 
            
              | 851 |                     className: 'identifier' | 
            
              | 852 |                 }, | 
            
              | 853 |                 { | 
            
              | 854 |                     data: 'metadata', | 
            
              | 855 |                     render: createMetadata | 
            
              | 856 |                 }, | 
            
              | 857 |                 { | 
            
              | 858 |                     data: 'requestId', | 
            
              | 859 |                     width: '13%', | 
            
              | 860 |                     render: createRequestId | 
            
              | 861 |                 }, | 
            
              | 862 |                 { | 
            
              | 863 |                     width: '18%', | 
            
              | 864 |                     render: createActions, | 
            
              | 865 |                     className: 'action-column' | 
            
              | 866 |                 } | 
            
              | 867 |             ], | 
            
              | 868 |             createdRow: function (row, data) { | 
            
              | 869 |                 if (data.failed.length > 0) { | 
            
              | 870 |                     row.classList.add('fetch-failed'); | 
            
              | 871 |                 } | 
            
              | 872 |             } | 
            
              | 873 |         }); | 
            
              | 874 |     } | 
            
              | 875 |  | 
            
              | 876 |     function createActions(x, y, data) { | 
            
              | 877 |         return '<button type="button"' + (data.requestId ? ' disabled' : '') + ' class="btn btn-xs btn-danger remove-row">' + ill_button_remove + '</button>'; | 
            
              | 878 |     } | 
            
              | 879 |  | 
            
              | 880 |     // Redraw the table | 
            
              | 881 |     function updateTable() { | 
            
              | 882 |         if (!table) return; | 
            
              | 883 |         tableEl.style.display = tableContent.data.length > 0 ? 'table' : 'none'; | 
            
              | 884 |         tableEl.style.width = '100%'; | 
            
              | 885 |         table.api() | 
            
              | 886 |             .clear() | 
            
              | 887 |             .rows.add(tableContent.data) | 
            
              | 888 |             .draw(); | 
            
              | 889 |     }; | 
            
              | 890 |  | 
            
              | 891 |     function identifyIdentifier(identifier) { | 
            
              | 892 |         var matches = []; | 
            
              | 893 |  | 
            
              | 894 |         // Iterate our available services to see if any can identify this identifier | 
            
              | 895 |         Object.keys(supportedIdentifiers).forEach(function (identifierType) { | 
            
              | 896 |             // Since all the services supporting this identifier type should use the same | 
            
              | 897 |             // regex to identify it, we can just use the first | 
            
              | 898 |             var service = supportedIdentifiers[identifierType][0]; | 
            
              | 899 |             var regex = new RegExp(service.identifiers_supported[identifierType].regex); | 
            
              | 900 |             var match = identifier.match(regex); | 
            
              | 901 |             if (match && match.groups && match.groups.identifier) { | 
            
              | 902 |                 matches.push({ | 
            
              | 903 |                     type: identifierType, | 
            
              | 904 |                     value: match.groups.identifier | 
            
              | 905 |                 }); | 
            
              | 906 |             } | 
            
              | 907 |         }); | 
            
              | 908 |         return matches; | 
            
              | 909 |     } | 
            
              | 910 |  | 
            
              | 911 |     function displayErrors(errors) { | 
            
              | 912 |         var keys = Object.keys(errors); | 
            
              | 913 |         if (keys.length > 0) { | 
            
              | 914 |             keys.forEach(function (key) { | 
            
              | 915 |                 var el = document.getElementById(errors[key].element); | 
            
              | 916 |                 el.textContent = errors[key].values; | 
            
              | 917 |                 el.style.display = 'inline'; | 
            
              | 918 |                 var container = document.getElementById(key); | 
            
              | 919 |                 container.style.display = 'block'; | 
            
              | 920 |             }); | 
            
              | 921 |             var el = document.getElementById('textarea-errors'); | 
            
              | 922 |             el.style.display = 'flex'; | 
            
              | 923 |         } | 
            
              | 924 |     } | 
            
              | 925 |  | 
            
              | 926 |     function hideErrors() { | 
            
              | 927 |         var dupelist = document.getElementById('dupelist'); | 
            
              | 928 |         var badids = document.getElementById('badids'); | 
            
              | 929 |         dupelist.textContent = ''; | 
            
              | 930 |         dupelist.parentElement.style.display = 'none'; | 
            
              | 931 |         badids.textContent = ''; | 
            
              | 932 |         badids.parentElement.style.display = 'none'; | 
            
              | 933 |         var tae = document.getElementById('textarea-errors'); | 
            
              | 934 |         tae.style.display = 'none'; | 
            
              | 935 |     } | 
            
              | 936 |  | 
            
              | 937 |     function manageBatchItemsDisplay() { | 
            
              | 938 |         batchItemsDisplay.style.display = batch.data.id ? 'block' : 'none' | 
            
              | 939 |     }; | 
            
              | 940 |  | 
            
              | 941 |     function updateBatchInputs() { | 
            
              | 942 |         nameInput.value = batch.data.name || ''; | 
            
              | 943 |         cardnumberInput.value = batch.data.cardnumber || ''; | 
            
              | 944 |         branchcodeSelect.value = batch.data.branchcode || ''; | 
            
              | 945 |     } | 
            
              | 946 |  | 
            
              | 947 |     function debounce(func) { | 
            
              | 948 |         var timeout; | 
            
              | 949 |         return function (...args) { | 
            
              | 950 |             return new Promise(function (resolve) { | 
            
              | 951 |                 if (timeout) { | 
            
              | 952 |                     clearTimeout(timeout); | 
            
              | 953 |                 } | 
            
              | 954 |                 timeout = setTimeout(function () { | 
            
              | 955 |                     return resolve(func(...args)); | 
            
              | 956 |                 }, debounceDelay); | 
            
              | 957 |             }); | 
            
              | 958 |         } | 
            
              | 959 |     } | 
            
              | 960 |  | 
            
              | 961 |     function patronAutocomplete() { | 
            
              | 962 |         // Add autocomplete for patron selection | 
            
              | 963 |         $('#batch-form #cardnumber').autocomplete({ | 
            
              | 964 |             appendTo: '#batch-form', | 
            
              | 965 |             source: "/cgi-bin/koha/circ/ysearch.pl", | 
            
              | 966 |             minLength: 3, | 
            
              | 967 |             select: function (event, ui) { | 
            
              | 968 |                 var field = ui.item.cardnumber; | 
            
              | 969 |                 $('#batch-form #cardnumber').val(field) | 
            
              | 970 |                 return false; | 
            
              | 971 |             } | 
            
              | 972 |         }) | 
            
              | 973 |             .data("ui-autocomplete")._renderItem = function (ul, item) { | 
            
              | 974 |                 return $("<li></li>") | 
            
              | 975 |                     .data("ui-autocomplete-item", item) | 
            
              | 976 |                     .append("<a>" + item.surname + ", " + item.firstname + " (" + item.cardnumber + ") <small>" + item.address + " " + item.city + " " + item.zipcode + " " + item.country + "</small></a>") | 
            
              | 977 |                     .appendTo(ul); | 
            
              | 978 |             }; | 
            
              | 979 |     }; | 
            
              | 980 |  | 
            
              | 981 |     function createPatronLink() { | 
            
              | 982 |         if (!batch.data.patron) return; | 
            
              | 983 |         var patron = batch.data.patron; | 
            
              | 984 |         var a = document.createElement('a'); | 
            
              | 985 |         var href = '/cgi-bin/koha/members/moremember.pl?borrowernumber=' + patron.borrowernumber; | 
            
              | 986 |         var text = patron.surname + ' (' + patron.cardnumber + ')'; | 
            
              | 987 |         a.setAttribute('title', ill_borrower_details); | 
            
              | 988 |         a.setAttribute('href', href); | 
            
              | 989 |         a.textContent = text; | 
            
              | 990 |         return a; | 
            
              | 991 |     }; | 
            
              | 992 |  | 
            
              | 993 | })(); |