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