UNPKG

@natlibfi/melinda-ui-commons

Version:
189 lines (151 loc) 7.72 kB
/* eslint-disable max-params */ /* eslint-disable no-mixed-operators */ /* eslint-disable max-statements */ import {isDataFieldTag, marcFieldToDiv, stringToMarcField} from './editorUtils.js'; //****************************************************************************// // // // MARC record editor // // // //****************************************************************************// // NV's comments about Settings: // - decorateField: function,no idea what this is used for, maybe someone uses this, predates me (=NV), // - editableRecord: undefined/function(record), by default record is *NOT* editable. // - editableField: undefined/function(field, boolean = false), by default field is *NOT* editable. // - focusHandler // - inputHandler // - keyDownHandler // - onClick: add eventListerer to a field. NOT used by me (NV) on editors. As my editor uses way more listeners, I'm currently keeping them on the app side. // - pasteHandler: undefined/function // - subfieldCodePrefix: undefined/string, default is nothing, editor needs a non-empty value. NV uses '$$' as Aleph converts '$$' to a subfield separator anyways. // - uneditableFieldBackgroundColor: undefined/string-that-specifies-colour, undefined changes nothing // window.activeFieldElement = undefined; // Global variable for determining the row/field that last had focus. DO this is app, breaks tests... export function showRecord(record, dest, settings = {}, recordDivName = 'muuntaja', logRecord = true) { // Check modern muuntaja. If not needed, then remove this function. if (logRecord) { console.log('Show Record:', record); // eslint-disable-line no-console } console.log('showRecord() is deprecated. Use showRecordInDiv() instead!'); // Get div to fill in the fields // NV: NB! mere '#Record' might not work. I've seen colleagues using same #Record ID twice. // Also `#${recordDivName} .record-merge-panel #${dest} #Record` is just terrible hard-coding. const recordDiv = document.querySelector(`#${recordDivName} .record-merge-panel #${dest} #Record`); // To alleviate the problem I (NV) have split the function into two parts (the latter half being more generic showRecordInDiv()) return showRecordInDiv(record, recordDiv, settings); } export function showRecordInDiv(record, recordDiv, settings = {}) { if (!recordDiv) { return; } recordDiv.innerHTML = ''; if (!record) { return; } const recordIsEditable = settings?.editableRecord ? settings.editableRecord(record) : false; if (record.error) { const error = document.createElement('div'); error.classList.add('error'); error.textContent = getHumanReadableErrorMessage(record.error); console.error(record.error); /* eslint-disable-line no-console */ recordDiv.appendChild(error); } if (record.notes) { const notes = document.createElement('div'); notes.classList.add('notes'); notes.textContent = record.notes; recordDiv.appendChild(notes); } if (record.leader) { const leaderAsField = {tag: 'LDR', value: record.leader}; const leaderIsEditable = settings?.editableField ? settings.editableField(leaderAsField, recordIsEditable) : false; marcFieldToDiv(recordDiv, undefined, leaderAsField, settings, leaderIsEditable); } if (record.fields) { for (const field of record.fields) { const fieldIsEditable = settings?.editableField ? settings.editableField(field, recordIsEditable) : false; const content = settings?.getContent ? settings.getContent(field) : field; marcFieldToDiv(recordDiv, undefined, content, settings, fieldIsEditable); } } function getHumanReadableErrorMessage(errorMessage) { if (errorMessage.includes('Record is invalid')) { return 'Tietueen validointi ei onnistunut. Tarkistathan merkatut kentät.'; } return 'Tapahtui virhe'; } } // Read field divs and convert them to marc fields (leader is converted into a LDR field) export function getEditorFields(editorElementId = 'Record', subfieldCodePrefix = '$$') { const parentElem = document.getElementById(editorElementId); if (!parentElem) { console.log(`WARNING: getEditorFields(): no element '${editorElementId}' found!`); return []; } return [...parentElem.children].map(div => stringToMarcField(div.textContent, subfieldCodePrefix)); // [].map() converts children into an editable array } export function filterField(field) { // Field contains field.error (for debugging and error messages) and control fields have fake indicators. // Copy and return relevant fields when a) converting the whole record, or b) testing. if (isDataFieldTag(field.tag)) { return {tag: field.tag, ind1: field.ind1, ind2: field.ind2, subfields: field.subfields.map(sf => filterSubfields(sf))}; } return {tag: field.tag, value: field.value}; function filterSubfields(subfield) { return {code: subfield.code, value: subfield.value}; } } export function convertFieldsToRecord(fields, settings = {}) { // this should go to melinda-ui-commons... if (fields == undefined) { fields = getEditorFields(settings.editorDivId, settings.subfieldCodePrefix); // Get default fields } const [leader, ...otherFields] = fields //const validationErrors = extractErrorsFromFields(fields); // Validate? if (otherFields.length < 1) { return {error: 'no fields'}; } const filteredFields = otherFields.map(f => filterField(f)); // Drop errors and other extra data if (leader.tag !== 'LDR') { return {error: 'first field should be leader'}; } return { leader: leader.value, fields: filteredFields } } function getNonRepeatableDuplicateErrors(fields, tags, result = []) { return innerLoop(tags, result); function innerLoop(tags, result) { const [currTag, ...remaingingTags] = tags; if (!currTag) { return result; } // NB! "tag" value '1' can be used to see how many '1XX' fields there are. const relevantFields = fields.filter(f => f.tag.substring(0, currTag.length) === currTag); console.log(`${currTag} must be non-repeatable; got ${relevantFields.length} instance(s)`); const currentError = relevantFields.length > 1 ? [`Non-repeatable ${normalizeTag(currTag)} appears more than once!`] : []; return innerLoop(remaingingTags, [...result, ...currentError]); } function normalizeTag(tag) { if (tag.length == 1) { // '1' => '1XX' return `${tag}XX`; } return tag; } } export function extractErrors(settings, defaultFields = []) { // 2025-03-20: we are now only returning errors for fields that are editable, and thus fixable. (Should we parameterize this?) // 2025-04-29: added defaultFields. It's used only by tests (to smuggle fields for tests) const fields = defaultFields.length === 0 ? getEditorFields(settings.editorDivId, settings.subfieldCodePrefix).filter(settings.editableField) : defaultFields; if (fields.length === 0) { return [`No input data found (ref: ${settings.editorDivId})`]; } const fieldInternalErrors = fields.filter(f => f.error).map(f => f.error); const recordLevelErrors = getRecordLevelErrors(fields); return [...recordLevelErrors, ...fieldInternalErrors]; function getRecordLevelErrors(fields) { const nonRepeatables = getNonRepeatableDuplicateErrors(fields, ['LDR', '001', '003', '008']); console.log(`GOT ${nonRepeatables.length} NON-REPEATABILITY ERROR(S)!`); // Should we check 00X lengths? // What else? return [...nonRepeatables]; } }