| 
      
            Line 0
          
      
      
        Link Here
      
     | 
  
          
            
              | 0 | 
              -   | 
              1 | 
              /**  | 
            
            
               | 
               | 
              2 | 
               * AdditionalFields Module  | 
            
            
              | 3 | 
               *  | 
            
            
              | 4 | 
               * A reusable module for handling additional fields in Koha forms.  | 
            
            
              | 5 | 
               * This module provides functionality for managing extended attributes  | 
            
            
              | 6 | 
               * in a generic way that can be used across different Koha forms.  | 
            
            
              | 7 | 
               *  | 
            
            
              | 8 | 
               * @example  | 
            
            
              | 9 | 
               * Expected HTML structure:  | 
            
            
              | 10 | 
               * <fieldset class="brief" id="booking_extended_attributes">  | 
            
            
              | 11 | 
               *   <div class="form-group">  | 
            
            
              | 12 | 
               *     <label class="control-label">Field Name</label>  | 
            
            
              | 13 | 
               *     <input type="text" class="extended-attribute form-control form-control-sm w-50" />  | 
            
            
              | 14 | 
               *   </div>  | 
            
            
              | 15 | 
               * </fieldset>  | 
            
            
              | 16 | 
               *  | 
            
            
              | 17 | 
               *  | 
            
            
              | 18 | 
               * // Usage:  | 
            
            
              | 19 | 
               * const additionalFields = AdditionalFields.init({ | 
            
            
              | 20 | 
               *   containerId: 'booking_extended_attributes',  | 
            
            
              | 21 | 
               *   resourceType: 'booking',  | 
            
            
              | 22 | 
               *   onFieldsChanged: (values) => console.log('Fields changed:', values) | 
            
            
              | 23 | 
               * });  | 
            
            
              | 24 | 
               *  | 
            
            
              | 25 | 
               * // Fetch and render fields  | 
            
            
              | 26 | 
               * additionalFields.fetchExtendedAttributes('booking') | 
            
            
              | 27 | 
               *   .then(types => additionalFields.renderExtendedAttributes(types, []));  | 
            
            
              | 28 | 
               */  | 
            
            
              | 29 | 
               | 
            
            
              | 30 | 
              /**  | 
            
            
              | 31 | 
               * @typedef {Object} AdditionalFieldsConfig | 
            
            
              | 32 | 
               * @property {string} containerId - ID of the container element | 
            
            
              | 33 | 
               * @property {string} resourceType - Type of resource (e.g., 'booking', 'patron') | 
            
            
              | 34 | 
               * @property {Function} onFieldsChanged - Callback for field changes | 
            
            
              | 35 | 
               * @property {Object} selectors - Custom selectors | 
            
            
              | 36 | 
               * @property {string} selectors.repeatableFieldClass - Class for repeatable fields | 
            
            
              | 37 | 
               * @property {string} selectors.inputClass - Class for input elements | 
            
            
              | 38 | 
               * @property {string} selectors.fieldPrefix - Prefix for field names | 
            
            
              | 39 | 
               */  | 
            
            
              | 40 | 
               | 
            
            
              | 41 | 
              /**  | 
            
            
              | 42 | 
               * @typedef {Object} AuthorizedValue | 
            
            
              | 43 | 
               * @property {number} authorised_value_id - The ID of the authorized value | 
            
            
              | 44 | 
               * @property {string} category_name - The category name | 
            
            
              | 45 | 
               * @property {string} description - The description text | 
            
            
              | 46 | 
               * @property {string} image_url - The image URL, if any | 
            
            
              | 47 | 
               * @property {string|null} opac_description - The OPAC description, if any | 
            
            
              | 48 | 
               * @property {string} value - The value code | 
            
            
              | 49 | 
               */  | 
            
            
              | 50 | 
               | 
            
            
              | 51 | 
              /**  | 
            
            
              | 52 | 
               * @typedef {Object} ExtendedAttributeType | 
            
            
              | 53 | 
               * @property {number} extended_attribute_type_id - The ID of the extended attribute type | 
            
            
              | 54 | 
               * @property {string} name - The name of the extended attribute type | 
            
            
              | 55 | 
               * @property {string|null} authorised_value_category_name - The category name for authorized values, if any | 
            
            
              | 56 | 
               * @property {boolean} repeatable - Whether the attribute is repeatable | 
            
            
              | 57 | 
               * @property {string} resource_type - The resource type this attribute belongs to | 
            
            
              | 58 | 
               * @property {string} marc_field - The MARC field associated with this attribute | 
            
            
              | 59 | 
               * @property {string} marc_field_mode - The MARC field mode (get/set) | 
            
            
              | 60 | 
               * @property {boolean} searchable - Whether the attribute is searchable | 
            
            
              | 61 | 
               */  | 
            
            
              | 62 | 
               | 
            
            
              | 63 | 
              /**  | 
            
            
              | 64 | 
               * @typedef {Object} ExtendedAttribute | 
            
            
              | 65 | 
               * @property {number} field_id - The ID of the extended attribute | 
            
            
              | 66 | 
               * @property {string} id - The unique identifier | 
            
            
              | 67 | 
               * @property {string} record_id - The ID of the record this attribute belongs to | 
            
            
              | 68 | 
               * @property {string} value - The value of the extended attribute | 
            
            
              | 69 | 
               */  | 
            
            
              | 70 | 
               | 
            
            
              | 71 | 
              /**  | 
            
            
              | 72 | 
               * @typedef {Object} ExtendedAttributeValue | 
            
            
              | 73 | 
               * @property {number} field_id - The ID of the extended attribute type | 
            
            
              | 74 | 
               * @property {string} value - The value of the extended attribute | 
            
            
              | 75 | 
               */  | 
            
            
              | 76 | 
               | 
            
            
              | 77 | 
              const AdditionalFields = (function () { | 
            
            
              | 78 | 
                  // Constants for class names and selectors  | 
            
            
              | 79 | 
                  const CLASS_NAMES = { | 
            
            
              | 80 | 
                      // Layout components  | 
            
            
              | 81 | 
                      CONTAINER: { | 
            
            
              | 82 | 
                          FLEX: "d-flex",  | 
            
            
              | 83 | 
                          FLEX_ALIGN_CENTER: "align-items-center",  | 
            
            
              | 84 | 
                          MARGIN: { | 
            
            
              | 85 | 
                              BOTTOM: "mb-2",  | 
            
            
              | 86 | 
                              START: "ms-2",  | 
            
            
              | 87 | 
                              START_AUTO: "ms-auto",  | 
            
            
              | 88 | 
                          },  | 
            
            
              | 89 | 
                          WIDTH: { | 
            
            
              | 90 | 
                              HALF: "w-50",  | 
            
            
              | 91 | 
                          },  | 
            
            
              | 92 | 
                      },  | 
            
            
              | 93 | 
               | 
            
            
              | 94 | 
                      // Form components  | 
            
            
              | 95 | 
                      FORM: { | 
            
            
              | 96 | 
                          GROUP: "form-group",  | 
            
            
              | 97 | 
                          LABEL: "control-label",  | 
            
            
              | 98 | 
                          INPUT: { | 
            
            
              | 99 | 
                              BASE: "form-control",  | 
            
            
              | 100 | 
                              SELECT: "form-select",  | 
            
            
              | 101 | 
                              SMALL: "form-control-sm",  | 
            
            
              | 102 | 
                          },  | 
            
            
              | 103 | 
                      },  | 
            
            
              | 104 | 
               | 
            
            
              | 105 | 
                      // Extended attributes components  | 
            
            
              | 106 | 
                      EXTENDED_ATTRIBUTE: { | 
            
            
              | 107 | 
                          BASE: "extended-attribute",  | 
            
            
              | 108 | 
                          REPEATABLE: { | 
            
            
              | 109 | 
                              CONTAINER: "repeatable-field",  | 
            
            
              | 110 | 
                              ADD_BUTTON: "add-repeatable",  | 
            
            
              | 111 | 
                              REMOVE_BUTTON: "remove-repeatable",  | 
            
            
              | 112 | 
                          },  | 
            
            
              | 113 | 
                      },  | 
            
            
              | 114 | 
               | 
            
            
              | 115 | 
                      // State classes  | 
            
            
              | 116 | 
                      STATE: { | 
            
            
              | 117 | 
                          HIDDEN: "d-none",  | 
            
            
              | 118 | 
                          VISUALLY_HIDDEN: "visually-hidden",  | 
            
            
              | 119 | 
                          FADE: "fade",  | 
            
            
              | 120 | 
                          SHOW: "show",  | 
            
            
              | 121 | 
                      },  | 
            
            
              | 122 | 
               | 
            
            
              | 123 | 
                      // Button components  | 
            
            
              | 124 | 
                      BUTTON: { | 
            
            
              | 125 | 
                          BASE: "btn",  | 
            
            
              | 126 | 
                          SMALL: "btn-sm",  | 
            
            
              | 127 | 
                          LINK: "btn-link",  | 
            
            
              | 128 | 
                          PRIMARY: "text-primary",  | 
            
            
              | 129 | 
                      },  | 
            
            
              | 130 | 
               | 
            
            
              | 131 | 
                      // Icon components  | 
            
            
              | 132 | 
                      ICON: { | 
            
            
              | 133 | 
                          BASE: "fa",  | 
            
            
              | 134 | 
                          ADD: "fa-plus",  | 
            
            
              | 135 | 
                          REMOVE: "fa-trash",  | 
            
            
              | 136 | 
                      },  | 
            
            
              | 137 | 
                  };  | 
            
            
              | 138 | 
               | 
            
            
              | 139 | 
                  const SELECTORS = { | 
            
            
              | 140 | 
                      REPEATABLE_FIELD: `.${CLASS_NAMES.EXTENDED_ATTRIBUTE.REPEATABLE.CONTAINER}`, | 
            
            
              | 141 | 
                      EXTENDED_ATTRIBUTE: `.${CLASS_NAMES.EXTENDED_ATTRIBUTE.BASE}`, | 
            
            
              | 142 | 
                      FORM_GROUP: `li.${CLASS_NAMES.FORM.GROUP}`, | 
            
            
              | 143 | 
                      INPUT: "input, select",  | 
            
            
              | 144 | 
                  };  | 
            
            
              | 145 | 
               | 
            
            
              | 146 | 
                  const NAMES = { | 
            
            
              | 147 | 
                      EXTENDED_ATTRIBUTE: (fieldId, index) =>  | 
            
            
              | 148 | 
                          `extended_attributes.${fieldId}${index !== null ? `[${index}]` : ""}`, | 
            
            
              | 149 | 
                  };  | 
            
            
              | 150 | 
               | 
            
            
              | 151 | 
                  const TEXTS = { | 
            
            
              | 152 | 
                      EXTENDED_ATTRIBUTES: __("Additional Fields"), | 
            
            
              | 153 | 
                      LOADING: __("Loading"), | 
            
            
              | 154 | 
                      SELECT_AN_OPTION: __("Select an option"), | 
            
            
              | 155 | 
                      ADD: __("New"), | 
            
            
              | 156 | 
                      REMOVE: __("Remove"), | 
            
            
              | 157 | 
                  };  | 
            
            
              | 158 | 
               | 
            
            
              | 159 | 
                  // Helper functions  | 
            
            
              | 160 | 
                  const Helpers = { | 
            
            
              | 161 | 
                      /**  | 
            
            
              | 162 | 
                       * Get container element or return null if not found  | 
            
            
              | 163 | 
                       * @param {string} containerId - ID of the container element | 
            
            
              | 164 | 
                       * @returns {HTMLElement|null} Container element or null if not found | 
            
            
              | 165 | 
                       */  | 
            
            
              | 166 | 
                      getContainer: containerId => { | 
            
            
              | 167 | 
                          return document.getElementById(containerId);  | 
            
            
              | 168 | 
                      },  | 
            
            
              | 169 | 
               | 
            
            
              | 170 | 
                      /**  | 
            
            
              | 171 | 
                       * Check if the given element is an input or select element  | 
            
            
              | 172 | 
                       * @param {Element} element - The element to check | 
            
            
              | 173 | 
                       * @returns {element is HTMLInputElement|HTMLSelectElement} True if the element is an instance of HTMLInputElement or HTMLSelectElement, otherwise false. | 
            
            
              | 174 | 
                       */  | 
            
            
              | 175 | 
                      isInputOrSelect(element) { | 
            
            
              | 176 | 
                          return (  | 
            
            
              | 177 | 
                              element instanceof HTMLInputElement ||  | 
            
            
              | 178 | 
                              element instanceof HTMLSelectElement  | 
            
            
              | 179 | 
                          );  | 
            
            
              | 180 | 
                      },  | 
            
            
              | 181 | 
               | 
            
            
              | 182 | 
                      /**  | 
            
            
              | 183 | 
                       * Extracts the field ID from an input element's name attribute  | 
            
            
              | 184 | 
                       * @param {Element} input - The input element | 
            
            
              | 185 | 
                       * @param {string} fieldPrefix - The prefix for field names | 
            
            
              | 186 | 
                       * @returns {number} The extracted field ID | 
            
            
              | 187 | 
                       */  | 
            
            
              | 188 | 
                      getFieldIdFromInputName(input, fieldPrefix) { | 
            
            
              | 189 | 
                          if (!Helpers.isInputOrSelect(input)) { | 
            
            
              | 190 | 
                              return 0;  | 
            
            
              | 191 | 
                          }  | 
            
            
              | 192 | 
               | 
            
            
              | 193 | 
                          const name = input.name.replace(`${fieldPrefix}.`, ""); | 
            
            
              | 194 | 
                          const fieldId = name.replace(/\[.*\]/, "");  | 
            
            
              | 195 | 
                          return parseInt(fieldId, 10) || 0;  | 
            
            
              | 196 | 
                      },  | 
            
            
              | 197 | 
               | 
            
            
              | 198 | 
                      /**  | 
            
            
              | 199 | 
                       * Create element with classes and optional attributes  | 
            
            
              | 200 | 
                       * @template {keyof HTMLElementTagNameMap} T | 
            
            
              | 201 | 
                       * @param {T} tagName - HTML tag name | 
            
            
              | 202 | 
                       * @param {string[]} classes - Array of class names | 
            
            
              | 203 | 
                       * @param {Object.<string, string>} [attributes] - Optional attributes to set | 
            
            
              | 204 | 
                       * @returns {HTMLElementTagNameMap[T]} Created element | 
            
            
              | 205 | 
                       */  | 
            
            
              | 206 | 
                      createElement: (tagName, classes = [], attributes = {}) => { | 
            
            
              | 207 | 
                          const element = document.createElement(tagName);  | 
            
            
              | 208 | 
                          if (classes.length) { | 
            
            
              | 209 | 
                              element.className = classes.join(" "); | 
            
            
              | 210 | 
                          }  | 
            
            
              | 211 | 
                          Object.entries(attributes).forEach(([key, value]) => { | 
            
            
              | 212 | 
                              element.setAttribute(key, value);  | 
            
            
              | 213 | 
                          });  | 
            
            
              | 214 | 
                          return element;  | 
            
            
              | 215 | 
                      },  | 
            
            
              | 216 | 
               | 
            
            
              | 217 | 
                      /**  | 
            
            
              | 218 | 
                       * Create button with icon and text  | 
            
            
              | 219 | 
                       * @param {string} text - Button text | 
            
            
              | 220 | 
                       * @param {string} iconClass - Icon class | 
            
            
              | 221 | 
                       * @param {string[]} classes - Additional classes | 
            
            
              | 222 | 
                       * @param {Object.<string, string>} [attributes] - Optional attributes | 
            
            
              | 223 | 
                       * @returns {HTMLButtonElement} Button element | 
            
            
              | 224 | 
                       */  | 
            
            
              | 225 | 
                      createButton: (text, iconClass, classes = [], attributes = {}) => { | 
            
            
              | 226 | 
                          const button = Helpers.createElement(  | 
            
            
              | 227 | 
                              "button",  | 
            
            
              | 228 | 
                              [  | 
            
            
              | 229 | 
                                  CLASS_NAMES.BUTTON.BASE,  | 
            
            
              | 230 | 
                                  CLASS_NAMES.BUTTON.SMALL,  | 
            
            
              | 231 | 
                                  CLASS_NAMES.BUTTON.LINK,  | 
            
            
              | 232 | 
                                  ...classes,  | 
            
            
              | 233 | 
                              ],  | 
            
            
              | 234 | 
                              { type: "button", ...attributes } | 
            
            
              | 235 | 
                          );  | 
            
            
              | 236 | 
               | 
            
            
              | 237 | 
                          const icon = Helpers.createElement("i", [ | 
            
            
              | 238 | 
                              CLASS_NAMES.ICON.BASE,  | 
            
            
              | 239 | 
                              iconClass,  | 
            
            
              | 240 | 
                          ]);  | 
            
            
              | 241 | 
                          button.appendChild(icon);  | 
            
            
              | 242 | 
               | 
            
            
              | 243 | 
                          const textNode = document.createTextNode(text);  | 
            
            
              | 244 | 
                          button.appendChild(textNode);  | 
            
            
              | 245 | 
               | 
            
            
              | 246 | 
                          return button;  | 
            
            
              | 247 | 
                      },  | 
            
            
              | 248 | 
               | 
            
            
              | 249 | 
                      /**  | 
            
            
              | 250 | 
                       * Group values by field_id  | 
            
            
              | 251 | 
                       * @param {Array<ExtendedAttributeValue>} values - Array of extended attribute values | 
            
            
              | 252 | 
                       * @returns {Object.<number, string[]>} Grouped values | 
            
            
              | 253 | 
                       * @todo use Object.groupBy once it is widely available  | 
            
            
              | 254 | 
                       */  | 
            
            
              | 255 | 
                      groupValues: values => { | 
            
            
              | 256 | 
                          if (!values) { | 
            
            
              | 257 | 
                              return {}; | 
            
            
              | 258 | 
                          }  | 
            
            
              | 259 | 
               | 
            
            
              | 260 | 
                          return values.reduce((acc, { field_id, value }) => { | 
            
            
              | 261 | 
                              if (!acc[field_id]) { | 
            
            
              | 262 | 
                                  acc[field_id] = [];  | 
            
            
              | 263 | 
                              }  | 
            
            
              | 264 | 
                              acc[field_id].push(value);  | 
            
            
              | 265 | 
                              return acc;  | 
            
            
              | 266 | 
                          }, {}); | 
            
            
              | 267 | 
                      },  | 
            
            
              | 268 | 
               | 
            
            
              | 269 | 
                      /**  | 
            
            
              | 270 | 
                       * Get unique categories from field types  | 
            
            
              | 271 | 
                       * @param {Array<ExtendedAttributeType>} types - Array of extended attribute types | 
            
            
              | 272 | 
                       * @returns {string[]} Unique categories | 
            
            
              | 273 | 
                       */  | 
            
            
              | 274 | 
                      getUniqueCategories: types => { | 
            
            
              | 275 | 
                          return [  | 
            
            
              | 276 | 
                              ...new Set(  | 
            
            
              | 277 | 
                                  types  | 
            
            
              | 278 | 
                                      .map(type => type.authorised_value_category_name)  | 
            
            
              | 279 | 
                                      .filter(category => category !== null)  | 
            
            
              | 280 | 
                              ),  | 
            
            
              | 281 | 
                          ];  | 
            
            
              | 282 | 
                      },  | 
            
            
              | 283 | 
               | 
            
            
              | 284 | 
                      /**  | 
            
            
              | 285 | 
                       * Convert array of {category, values} to object | 
            
            
              | 286 | 
                       * @param {Array<{category: string, values: AuthorizedValue[]}>} results - Array of results | 
            
            
              | 287 | 
                       * @returns {Object.<string, AuthorizedValue[]>} Object of authorized values by category | 
            
            
              | 288 | 
                       */  | 
            
            
              | 289 | 
                      convertToAuthorizedValues: results => { | 
            
            
              | 290 | 
                          return results.reduce((acc, { category, values }) => { | 
            
            
              | 291 | 
                              acc[category] = values;  | 
            
            
              | 292 | 
                              return acc;  | 
            
            
              | 293 | 
                          }, {}); | 
            
            
              | 294 | 
                      },  | 
            
            
              | 295 | 
               | 
            
            
              | 296 | 
                      /**  | 
            
            
              | 297 | 
                       * Handle adding a new repeatable field  | 
            
            
              | 298 | 
                       * @param {Event} event - The click event | 
            
            
              | 299 | 
                       * @param {ExtendedAttributeType} type - The field type | 
            
            
              | 300 | 
                       * @param {string|string[]|null} currentValues - Current values | 
            
            
              | 301 | 
                       * @param {Object.<string, Array<AuthorizedValue>>} authorizedValues - Authorized values by category | 
            
            
              | 302 | 
                       */  | 
            
            
              | 303 | 
                      handleAddRepeatable: (event, type, currentValues, authorizedValues) => { | 
            
            
              | 304 | 
                          const button = /** @type {HTMLButtonElement} */ ( | 
            
            
              | 305 | 
                              event.currentTarget  | 
            
            
              | 306 | 
                          );  | 
            
            
              | 307 | 
                          const repeatableContainer = button.previousElementSibling;  | 
            
            
              | 308 | 
                          if (!repeatableContainer) { | 
            
            
              | 309 | 
                              return;  | 
            
            
              | 310 | 
                          }  | 
            
            
              | 311 | 
               | 
            
            
              | 312 | 
                          const values =  | 
            
            
              | 313 | 
                              typeof currentValues === "string"  | 
            
            
              | 314 | 
                                  ? [currentValues]  | 
            
            
              | 315 | 
                                  : Array.isArray(currentValues)  | 
            
            
              | 316 | 
                                    ? currentValues  | 
            
            
              | 317 | 
                                    : [];  | 
            
            
              | 318 | 
                          const newInput = createInput(  | 
            
            
              | 319 | 
                              type,  | 
            
            
              | 320 | 
                              "",  | 
            
            
              | 321 | 
                              values.length,  | 
            
            
              | 322 | 
                              authorizedValues  | 
            
            
              | 323 | 
                          );  | 
            
            
              | 324 | 
                          if (newInput && repeatableContainer) { | 
            
            
              | 325 | 
                              repeatableContainer.appendChild(newInput);  | 
            
            
              | 326 | 
                          }  | 
            
            
              | 327 | 
                          if (typeof config.onFieldsChanged === "function") { | 
            
            
              | 328 | 
                              config.onFieldsChanged(getValues());  | 
            
            
              | 329 | 
                          }  | 
            
            
              | 330 | 
                      },  | 
            
            
              | 331 | 
               | 
            
            
              | 332 | 
                      /**  | 
            
            
              | 333 | 
                       * Handle removing a repeatable field  | 
            
            
              | 334 | 
                       * @param {Event} event - The click event | 
            
            
              | 335 | 
                       */  | 
            
            
              | 336 | 
                      handleRemoveRepeatable: event => { | 
            
            
              | 337 | 
                          const button = /** @type {HTMLButtonElement} */ ( | 
            
            
              | 338 | 
                              event.currentTarget  | 
            
            
              | 339 | 
                          );  | 
            
            
              | 340 | 
                          const wrapper = button.parentElement;  | 
            
            
              | 341 | 
                          if (wrapper) { | 
            
            
              | 342 | 
                              wrapper.remove();  | 
            
            
              | 343 | 
                              if (typeof config.onFieldsChanged === "function") { | 
            
            
              | 344 | 
                                  config.onFieldsChanged(getValues());  | 
            
            
              | 345 | 
                              }  | 
            
            
              | 346 | 
                          }  | 
            
            
              | 347 | 
                      },  | 
            
            
              | 348 | 
                  };  | 
            
            
              | 349 | 
               | 
            
            
              | 350 | 
                  /** @type {AdditionalFieldsConfig} */ | 
            
            
              | 351 | 
                  let config = { | 
            
            
              | 352 | 
                      containerId: "",  | 
            
            
              | 353 | 
                      resourceType: "",  | 
            
            
              | 354 | 
                      onFieldsChanged: () => {}, | 
            
            
              | 355 | 
                      selectors: { | 
            
            
              | 356 | 
                          repeatableFieldClass:  | 
            
            
              | 357 | 
                              CLASS_NAMES.EXTENDED_ATTRIBUTE.REPEATABLE.CONTAINER,  | 
            
            
              | 358 | 
                          inputClass: CLASS_NAMES.EXTENDED_ATTRIBUTE.BASE,  | 
            
            
              | 359 | 
                          fieldPrefix: "extended_attributes",  | 
            
            
              | 360 | 
                      },  | 
            
            
              | 361 | 
                  };  | 
            
            
              | 362 | 
               | 
            
            
              | 363 | 
                  /**  | 
            
            
              | 364 | 
                   * Initialize the module with configuration options  | 
            
            
              | 365 | 
                   * @param {Object} options - Configuration options | 
            
            
              | 366 | 
                   * @param {string} options.containerId - ID of the container element | 
            
            
              | 367 | 
                   * @param {string} options.resourceType - Type of resource (e.g., 'booking', 'patron') | 
            
            
              | 368 | 
                   * @param {Function} options.onFieldsChanged - Callback for field changes | 
            
            
              | 369 | 
                   * @param {Object} options.selectors - Custom selectors | 
            
            
              | 370 | 
                   * @param {string} options.selectors.repeatableFieldClass - Class for repeatable fields | 
            
            
              | 371 | 
                   * @param {string} options.selectors.inputClass - Class for input elements | 
            
            
              | 372 | 
                   * @param {string} options.selectors.fieldPrefix - Prefix for field names | 
            
            
              | 373 | 
                   */  | 
            
            
              | 374 | 
                  function init(options) { | 
            
            
              | 375 | 
                      config = { | 
            
            
              | 376 | 
                          ...config,  | 
            
            
              | 377 | 
                          ...options,  | 
            
            
              | 378 | 
                          selectors: { | 
            
            
              | 379 | 
                              ...config.selectors,  | 
            
            
              | 380 | 
                              ...(options.selectors || {}), | 
            
            
              | 381 | 
                          },  | 
            
            
              | 382 | 
                      };  | 
            
            
              | 383 | 
               | 
            
            
              | 384 | 
                      return { | 
            
            
              | 385 | 
                          init,  | 
            
            
              | 386 | 
                          getValues,  | 
            
            
              | 387 | 
                          setValues,  | 
            
            
              | 388 | 
                          clear,  | 
            
            
              | 389 | 
                          renderExtendedAttributes,  | 
            
            
              | 390 | 
                          fetchExtendedAttributes,  | 
            
            
              | 391 | 
                          fetchAndProcessExtendedAttributes,  | 
            
            
              | 392 | 
                          fetchAndProcessAuthorizedValues,  | 
            
            
              | 393 | 
                          renderExtendedAttributesValues,  | 
            
            
              | 394 | 
                      };  | 
            
            
              | 395 | 
                  }  | 
            
            
              | 396 | 
               | 
            
            
              | 397 | 
                  /**  | 
            
            
              | 398 | 
                   * Get all field values from the form  | 
            
            
              | 399 | 
                   * @returns {ExtendedAttributeValue[]} Array of extended attribute values | 
            
            
              | 400 | 
                   */  | 
            
            
              | 401 | 
                  function getValues() { | 
            
            
              | 402 | 
                      const container = Helpers.getContainer(config.containerId);  | 
            
            
              | 403 | 
                      if (!container) { | 
            
            
              | 404 | 
                          return [];  | 
            
            
              | 405 | 
                      }  | 
            
            
              | 406 | 
               | 
            
            
              | 407 | 
                      const values = [];  | 
            
            
              | 408 | 
                      const inputs = container.querySelectorAll(SELECTORS.INPUT);  | 
            
            
              | 409 | 
               | 
            
            
              | 410 | 
                      inputs.forEach(input => { | 
            
            
              | 411 | 
                          if (!Helpers.isInputOrSelect(input)) { | 
            
            
              | 412 | 
                              return;  | 
            
            
              | 413 | 
                          }  | 
            
            
              | 414 | 
               | 
            
            
              | 415 | 
                          const fieldId = Helpers.getFieldIdFromInputName(  | 
            
            
              | 416 | 
                              input,  | 
            
            
              | 417 | 
                              config.selectors.fieldPrefix  | 
            
            
              | 418 | 
                          );  | 
            
            
              | 419 | 
               | 
            
            
              | 420 | 
                          values.push({ | 
            
            
              | 421 | 
                              field_id: fieldId,  | 
            
            
              | 422 | 
                              value: input.value,  | 
            
            
              | 423 | 
                          });  | 
            
            
              | 424 | 
                      });  | 
            
            
              | 425 | 
               | 
            
            
              | 426 | 
                      return values;  | 
            
            
              | 427 | 
                  }  | 
            
            
              | 428 | 
               | 
            
            
              | 429 | 
                  /**  | 
            
            
              | 430 | 
                   * Set values for all extended attribute fields  | 
            
            
              | 431 | 
                   * @param {Array<ExtendedAttributeValue>} values - Array of extended attribute values | 
            
            
              | 432 | 
                   * @param {Object.<string, Array<AuthorizedValue>>} [authorizedValues] - Optional authorized values by category | 
            
            
              | 433 | 
                   */  | 
            
            
              | 434 | 
                  function setValues(values, authorizedValues = {}) { | 
            
            
              | 435 | 
                      const container = Helpers.getContainer(config.containerId);  | 
            
            
              | 436 | 
                      if (!container) { | 
            
            
              | 437 | 
                          return;  | 
            
            
              | 438 | 
                      }  | 
            
            
              | 439 | 
               | 
            
            
              | 440 | 
                      // Get all field types from the container  | 
            
            
              | 441 | 
                      const fieldTypes = Array.from(  | 
            
            
              | 442 | 
                          container?.querySelectorAll(SELECTORS.FORM_GROUP) ?? []  | 
            
            
              | 443 | 
                      )  | 
            
            
              | 444 | 
                          .map(li => { | 
            
            
              | 445 | 
                              /** @type {HTMLInputElement|HTMLSelectElement|null} */ | 
            
            
              | 446 | 
                              const input = li.querySelector(SELECTORS.INPUT);  | 
            
            
              | 447 | 
                              if (!input) return null;  | 
            
            
              | 448 | 
               | 
            
            
              | 449 | 
                              const fieldId = Helpers.getFieldIdFromInputName(  | 
            
            
              | 450 | 
                                  input,  | 
            
            
              | 451 | 
                                  config.selectors.fieldPrefix  | 
            
            
              | 452 | 
                              );  | 
            
            
              | 453 | 
               | 
            
            
              | 454 | 
                              return { | 
            
            
              | 455 | 
                                  extended_attribute_type_id: fieldId,  | 
            
            
              | 456 | 
                                  name: li.querySelector("label")?.textContent || "", | 
            
            
              | 457 | 
                                  authorised_value_category_name:  | 
            
            
              | 458 | 
                                      input.tagName === "SELECT"  | 
            
            
              | 459 | 
                                          ? (input.dataset.category ?? null)  | 
            
            
              | 460 | 
                                          : null,  | 
            
            
              | 461 | 
                                  repeatable:  | 
            
            
              | 462 | 
                                      li.querySelector(SELECTORS.REPEATABLE_FIELD) !== null,  | 
            
            
              | 463 | 
                                  resource_type: config.resourceType,  | 
            
            
              | 464 | 
                                  marc_field: "",  | 
            
            
              | 465 | 
                                  marc_field_mode: "",  | 
            
            
              | 466 | 
                                  searchable: false,  | 
            
            
              | 467 | 
                              };  | 
            
            
              | 468 | 
                          })  | 
            
            
              | 469 | 
                          .filter(type => type !== null);  | 
            
            
              | 470 | 
               | 
            
            
              | 471 | 
                      // Group values by field_id  | 
            
            
              | 472 | 
                      const groupedValues = Helpers.groupValues(values);  | 
            
            
              | 473 | 
               | 
            
            
              | 474 | 
                      // Get existing fields  | 
            
            
              | 475 | 
                      const existingFields = new Map(  | 
            
            
              | 476 | 
                          Array.from(  | 
            
            
              | 477 | 
                              container?.querySelectorAll(SELECTORS.FORM_GROUP) || []  | 
            
            
              | 478 | 
                          ).map(field => { | 
            
            
              | 479 | 
                              const input = field.querySelector("input, select"); | 
            
            
              | 480 | 
                              if (!input) { | 
            
            
              | 481 | 
                                  return [, field];  | 
            
            
              | 482 | 
                              }  | 
            
            
              | 483 | 
               | 
            
            
              | 484 | 
                              const fieldId = Helpers.getFieldIdFromInputName(  | 
            
            
              | 485 | 
                                  input,  | 
            
            
              | 486 | 
                                  config.selectors.fieldPrefix  | 
            
            
              | 487 | 
                              );  | 
            
            
              | 488 | 
               | 
            
            
              | 489 | 
                              return [Number(fieldId), field];  | 
            
            
              | 490 | 
                          })  | 
            
            
              | 491 | 
                      );  | 
            
            
              | 492 | 
               | 
            
            
              | 493 | 
                      // Check if we need to fetch authorized values  | 
            
            
              | 494 | 
                      const hasSelectFields = fieldTypes.some(  | 
            
            
              | 495 | 
                          type => type.authorised_value_category_name  | 
            
            
              | 496 | 
                      );  | 
            
            
              | 497 | 
               | 
            
            
              | 498 | 
                      const needsAuthorizedValues =  | 
            
            
              | 499 | 
                          hasSelectFields && Object.keys(authorizedValues).length === 0;  | 
            
            
              | 500 | 
                      if (!needsAuthorizedValues) { | 
            
          
            
              | 501 | 
                          // Track which fields we've processed  | 
            
            
              | 502 | 
                          const processedFields = new Set();  | 
            
            
              | 503 | 
               | 
            
            
              | 504 | 
                          // Update or create fields  | 
            
            
              | 505 | 
                          fieldTypes.forEach(type => { | 
            
            
              | 506 | 
                              const fieldId = type.extended_attribute_type_id;  | 
            
            
              | 507 | 
                              processedFields.add(fieldId);  | 
            
            
              | 508 | 
               | 
            
            
              | 509 | 
                              const fieldValues =  | 
            
            
              | 510 | 
                                  groupedValues[type.extended_attribute_type_id] || [];  | 
            
            
              | 511 | 
                              const currentValues = type.repeatable  | 
            
            
              | 512 | 
                                  ? fieldValues  | 
            
            
              | 513 | 
                                  : [fieldValues[0] || ""];  | 
            
            
              | 514 | 
               | 
            
            
              | 515 | 
                              const existingField = existingFields.get(fieldId);  | 
            
            
              | 516 | 
                              if (!existingField) { | 
            
            
              | 517 | 
                                  // Create new field  | 
            
            
              | 518 | 
                                  const field = createField(  | 
            
            
              | 519 | 
                                      type,  | 
            
            
              | 520 | 
                                      currentValues,  | 
            
            
              | 521 | 
                                      authorizedValues  | 
            
            
              | 522 | 
                                  );  | 
            
            
              | 523 | 
                                  if (field && container) container.appendChild(field);  | 
            
            
              | 524 | 
                                  return;  | 
            
            
              | 525 | 
                              }  | 
            
            
              | 526 | 
               | 
            
            
              | 527 | 
                              // Update existing field  | 
            
            
              | 528 | 
                              updateExistingField(  | 
            
            
              | 529 | 
                                  existingField,  | 
            
            
              | 530 | 
                                  type,  | 
            
            
              | 531 | 
                                  currentValues,  | 
            
            
              | 532 | 
                                  authorizedValues  | 
            
            
              | 533 | 
                              );  | 
            
            
              | 534 | 
                          });  | 
            
            
              | 535 | 
               | 
            
            
              | 536 | 
                          // Remove fields that no longer exist  | 
            
            
              | 537 | 
                          existingFields.forEach((field, fieldId) => { | 
            
            
              | 538 | 
                              if (!processedFields.has(fieldId)) { | 
            
            
              | 539 | 
                                  field.remove();  | 
            
            
              | 540 | 
                              }  | 
            
            
              | 541 | 
                          });  | 
            
            
              | 542 | 
                          return;  | 
            
            
              | 543 | 
                      }  | 
            
            
              | 544 | 
               | 
            
            
              | 545 | 
                      // Get unique categories  | 
            
            
              | 546 | 
                      const categories = Helpers.getUniqueCategories(fieldTypes);  | 
            
            
              | 547 | 
               | 
            
            
              | 548 | 
                      // Fetch authorized values for each category  | 
            
            
              | 549 | 
                      Promise.all(  | 
            
            
              | 550 | 
                          categories.map(category =>  | 
            
            
              | 551 | 
                              fetchAuthorizedValues(category).then(values => ({ | 
            
            
              | 552 | 
                                  category,  | 
            
            
              | 553 | 
                                  values,  | 
            
            
              | 554 | 
                              }))  | 
            
            
              | 555 | 
                          )  | 
            
            
              | 556 | 
                      )  | 
            
            
              | 557 | 
                          .then(results => { | 
            
            
              | 558 | 
                              // Convert array of {category, values} to object | 
            
            
              | 559 | 
                              const fetchedValues =  | 
            
            
              | 560 | 
                                  Helpers.convertToAuthorizedValues(results);  | 
            
            
              | 561 | 
               | 
            
            
              | 562 | 
                              // Re-render with fetched values  | 
            
            
              | 563 | 
                              renderExtendedAttributes(fieldTypes, values, fetchedValues);  | 
            
            
              | 564 | 
                          })  | 
            
            
              | 565 | 
                          .catch(error => { | 
            
            
              | 566 | 
                              console.error("Error fetching authorized values:", error); | 
            
            
              | 567 | 
                          });  | 
            
            
              | 568 | 
                  }  | 
            
            
              | 569 | 
               | 
            
            
              | 570 | 
                  /**  | 
            
            
              | 571 | 
                   * Clear all field values  | 
            
            
              | 572 | 
                   */  | 
            
            
              | 573 | 
                  function clear() { | 
            
            
              | 574 | 
                      const container = Helpers.getContainer(config.containerId);  | 
            
            
              | 575 | 
                      if (!container) { | 
            
            
              | 576 | 
                          return;  | 
            
            
              | 577 | 
                      }  | 
            
            
              | 578 | 
               | 
            
            
              | 579 | 
                      const inputs = container.querySelectorAll(SELECTORS.INPUT);  | 
            
            
              | 580 | 
                      inputs.forEach(input => { | 
            
            
              | 581 | 
                          if (!Helpers.isInputOrSelect(input)) { | 
            
            
              | 582 | 
                              return;  | 
            
            
              | 583 | 
                          }  | 
            
            
              | 584 | 
               | 
            
            
              | 585 | 
                          input.value = "";  | 
            
            
              | 586 | 
                      });  | 
            
            
              | 587 | 
                  }  | 
            
            
              | 588 | 
               | 
            
            
              | 589 | 
                  /**  | 
            
            
              | 590 | 
                   * Update a single input field  | 
            
            
              | 591 | 
                   * @param {Element} field - The field element | 
            
            
              | 592 | 
                   * @param {string} value - The new value | 
            
            
              | 593 | 
                   */  | 
            
            
              | 594 | 
                  function updateSingleField(field, value) { | 
            
            
              | 595 | 
                      const input = field.querySelector("input, select"); | 
            
            
              | 596 | 
                      if (!input || !Helpers.isInputOrSelect(input)) { | 
            
            
              | 597 | 
                          return;  | 
            
            
              | 598 | 
                      }  | 
            
            
              | 599 | 
               | 
            
            
              | 600 | 
                      input.value = value || "";  | 
            
            
              | 601 | 
                  }  | 
            
            
              | 602 | 
               | 
            
            
              | 603 | 
                  /**  | 
            
            
              | 604 | 
                   * Update a repeatable field  | 
            
            
              | 605 | 
                   * @param {Element} field - The field element | 
            
            
              | 606 | 
                   * @param {ExtendedAttributeType} type - The field type | 
            
            
              | 607 | 
                   * @param {string[]} values - The new values | 
            
            
              | 608 | 
                   * @param {Object.<string, Array<AuthorizedValue>>} authorizedValues - Authorized values by category | 
            
            
              | 609 | 
                   */  | 
            
            
              | 610 | 
                  function updateRepeatableField(field, type, values, authorizedValues) { | 
            
            
              | 611 | 
                      const repeatableContainer = field.querySelector(  | 
            
            
              | 612 | 
                          SELECTORS.REPEATABLE_FIELD  | 
            
            
              | 613 | 
                      );  | 
            
            
              | 614 | 
                      if (!repeatableContainer) { | 
            
            
              | 615 | 
                          return;  | 
            
            
              | 616 | 
                      }  | 
            
            
              | 617 | 
               | 
            
            
              | 618 | 
                      const existingInputs = Array.from(  | 
            
            
              | 619 | 
                          repeatableContainer.querySelectorAll("input, select") | 
            
            
              | 620 | 
                      );  | 
            
            
              | 621 | 
               | 
            
            
              | 622 | 
                      // Update or add inputs  | 
            
            
              | 623 | 
                      values.forEach((value, index) => { | 
            
            
              | 624 | 
                          const input = existingInputs[index];  | 
            
            
              | 625 | 
                          if (!Helpers.isInputOrSelect(input)) { | 
            
            
              | 626 | 
                              const newInput = createInput(  | 
            
            
              | 627 | 
                                  type,  | 
            
            
              | 628 | 
                                  value,  | 
            
            
              | 629 | 
                                  index,  | 
            
            
              | 630 | 
                                  authorizedValues  | 
            
            
              | 631 | 
                              );  | 
            
            
              | 632 | 
                              if (newInput) repeatableContainer.appendChild(newInput);  | 
            
            
              | 633 | 
                              return;  | 
            
            
              | 634 | 
                          }  | 
            
            
              | 635 | 
               | 
            
            
              | 636 | 
                          input.value = value;  | 
            
            
              | 637 | 
                      });  | 
            
            
              | 638 | 
               | 
            
            
              | 639 | 
                      // Remove excess inputs  | 
            
            
              | 640 | 
                      while (existingInputs.length > values.length) { | 
            
            
              | 641 | 
                          existingInputs.pop()?.parentElement?.remove();  | 
            
            
              | 642 | 
                      }  | 
            
            
              | 643 | 
                  }  | 
            
            
              | 644 | 
               | 
            
            
              | 645 | 
                  /**  | 
            
            
              | 646 | 
                   * Update an existing field  | 
            
            
              | 647 | 
                   * @param {Element} field - The field element | 
            
            
              | 648 | 
                   * @param {ExtendedAttributeType} type - The field type | 
            
            
              | 649 | 
                   * @param {string[]} values - The new values | 
            
            
              | 650 | 
                   * @param {Object.<string, Array<AuthorizedValue>>} authorizedValues - Authorized values by category | 
            
            
              | 651 | 
                   */  | 
            
            
              | 652 | 
                  function updateExistingField(field, type, values, authorizedValues) { | 
            
            
              | 653 | 
                      if (!type.repeatable) { | 
            
            
              | 654 | 
                          updateSingleField(field, values[0]);  | 
            
            
              | 655 | 
                          return;  | 
            
            
              | 656 | 
                      }  | 
            
            
              | 657 | 
               | 
            
            
              | 658 | 
                      updateRepeatableField(field, type, values, authorizedValues);  | 
            
            
              | 659 | 
                  }  | 
            
            
              | 660 | 
               | 
            
            
              | 661 | 
                  /**  | 
            
            
              | 662 | 
                   * Render extended attributes in the form  | 
            
            
              | 663 | 
                   * @param {Array<ExtendedAttributeType>} types - Extended attribute types | 
            
            
              | 664 | 
                   * @param {Array<ExtendedAttributeValue>} values - Current values | 
            
            
              | 665 | 
                   * @param {Object.<string, Array<AuthorizedValue>>} [authorizedValues] - Optional authorized values by category | 
            
            
              | 666 | 
                   */  | 
            
            
              | 667 | 
                  function renderExtendedAttributes(types, values, authorizedValues = {}) { | 
            
            
              | 668 | 
                      const container = Helpers.getContainer(config.containerId);  | 
            
            
              | 669 | 
                      if (!container) { | 
            
            
              | 670 | 
                          return;  | 
            
            
              | 671 | 
                      }  | 
            
            
              | 672 | 
               | 
            
            
              | 673 | 
                      if (!types || types.length === 0) { | 
            
            
              | 674 | 
                          container.innerHTML = "";  | 
            
            
              | 675 | 
                          return;  | 
            
            
              | 676 | 
                      }  | 
            
            
              | 677 | 
               | 
            
            
              | 678 | 
                      // Group values by field_id to handle repeatable fields  | 
            
            
              | 679 | 
                      const groupedValues = Helpers.groupValues(values);  | 
            
            
              | 680 | 
               | 
            
            
              | 681 | 
                      // Get existing fields  | 
            
            
              | 682 | 
                      const existingFields = new Map(  | 
            
            
              | 683 | 
                          Array.from(  | 
            
            
              | 684 | 
                              container?.querySelectorAll(SELECTORS.FORM_GROUP) || []  | 
            
            
              | 685 | 
                          ).map(field => { | 
            
            
              | 686 | 
                              const input = field.querySelector("input, select"); | 
            
            
              | 687 | 
                              if (!input) { | 
            
            
              | 688 | 
                                  return [, field];  | 
            
            
              | 689 | 
                              }  | 
            
            
              | 690 | 
               | 
            
            
              | 691 | 
                              const fieldId = Helpers.isInputOrSelect(input)  | 
            
            
              | 692 | 
                                  ? input.name.split(".")[1]?.split("[")[0] | 
            
            
              | 693 | 
                                  : undefined;  | 
            
            
              | 694 | 
                              return [fieldId, field];  | 
            
            
              | 695 | 
                          })  | 
            
            
              | 696 | 
                      );  | 
            
            
              | 697 | 
               | 
            
            
              | 698 | 
                      // Check if we need to fetch authorized values  | 
            
            
              | 699 | 
                      const hasSelectFields = types.some(  | 
            
            
              | 700 | 
                          type => type.authorised_value_category_name  | 
            
            
              | 701 | 
                      );  | 
            
            
              | 702 | 
               | 
            
            
              | 703 | 
                      const needsAuthorizedValues =  | 
            
            
              | 704 | 
                          hasSelectFields && Object.keys(authorizedValues).length === 0;  | 
            
            
              | 705 | 
                      if (!needsAuthorizedValues) { | 
            
            
              | 706 | 
                          // Track which fields we've processed  | 
            
            
              | 707 | 
                          const processedFields = new Set();  | 
            
            
              | 708 | 
               | 
            
            
              | 709 | 
                          // Update or create fields  | 
            
            
              | 710 | 
                          types.forEach(type => { | 
            
            
              | 711 | 
                              const fieldId = type.extended_attribute_type_id.toString();  | 
            
            
              | 712 | 
                              processedFields.add(fieldId);  | 
            
            
              | 713 | 
               | 
            
            
              | 714 | 
                              const fieldValues =  | 
            
            
              | 715 | 
                                  groupedValues[type.extended_attribute_type_id] || [];  | 
            
            
              | 716 | 
                              const currentValues = type.repeatable  | 
            
            
              | 717 | 
                                  ? fieldValues  | 
            
            
              | 718 | 
                                  : [fieldValues[0] || ""];  | 
            
            
              | 719 | 
               | 
            
            
              | 720 | 
                              const existingField = existingFields.get(fieldId);  | 
            
            
              | 721 | 
                              if (!existingField) { | 
            
            
              | 722 | 
                                  // Create new field  | 
            
            
              | 723 | 
                                  const field = createField(  | 
            
            
              | 724 | 
                                      type,  | 
            
            
              | 725 | 
                                      currentValues,  | 
            
            
              | 726 | 
                                      authorizedValues  | 
            
            
              | 727 | 
                                  );  | 
            
            
              | 728 | 
                                  if (field && container) container.appendChild(field);  | 
            
            
              | 729 | 
                                  return;  | 
            
            
              | 730 | 
                              }  | 
            
            
              | 731 | 
               | 
            
            
              | 732 | 
                              // Update existing field  | 
            
            
              | 733 | 
                              updateExistingField(  | 
            
            
              | 734 | 
                                  existingField,  | 
            
            
              | 735 | 
                                  type,  | 
            
            
              | 736 | 
                                  currentValues,  | 
            
            
              | 737 | 
                                  authorizedValues  | 
            
            
              | 738 | 
                              );  | 
            
            
              | 739 | 
                          });  | 
            
            
              | 740 | 
               | 
            
            
              | 741 | 
                          // Remove fields that no longer exist  | 
            
            
              | 742 | 
                          existingFields.forEach((field, fieldId) => { | 
            
            
              | 743 | 
                              if (!processedFields.has(fieldId)) { | 
            
            
              | 744 | 
                                  field.remove();  | 
            
            
              | 745 | 
                              }  | 
            
            
              | 746 | 
                          });  | 
            
            
              | 747 | 
                          return;  | 
            
            
              | 748 | 
                      }  | 
            
            
              | 749 | 
               | 
            
            
              | 750 | 
                      // Get unique categories  | 
            
            
              | 751 | 
                      const categories = Helpers.getUniqueCategories(types);  | 
            
            
              | 752 | 
               | 
            
            
              | 753 | 
                      // Fetch authorized values for each category  | 
            
            
              | 754 | 
                      Promise.all(  | 
            
            
              | 755 | 
                          categories.map(category =>  | 
            
            
              | 756 | 
                              fetchAuthorizedValues(category).then(values => ({ | 
            
            
              | 757 | 
                                  category,  | 
            
            
              | 758 | 
                                  values,  | 
            
            
              | 759 | 
                              }))  | 
            
            
              | 760 | 
                          )  | 
            
            
              | 761 | 
                      )  | 
            
            
              | 762 | 
                          .then(results => { | 
            
            
              | 763 | 
                              // Convert array of {category, values} to object | 
            
            
              | 764 | 
                              const fetchedValues =  | 
            
            
              | 765 | 
                                  Helpers.convertToAuthorizedValues(results);  | 
            
            
              | 766 | 
               | 
            
            
              | 767 | 
                              // Re-render with fetched values  | 
            
            
              | 768 | 
                              renderExtendedAttributes(types, values, fetchedValues);  | 
            
            
              | 769 | 
                          })  | 
            
            
              | 770 | 
                          .catch(error => { | 
            
            
              | 771 | 
                              console.error("Error fetching authorized values:", error); | 
            
            
              | 772 | 
                          });  | 
            
            
              | 773 | 
                  }  | 
            
            
              | 774 | 
               | 
            
            
              | 775 | 
                  /**  | 
            
            
              | 776 | 
                   * Create a field element for an extended attribute type  | 
            
            
              | 777 | 
                   * @param {ExtendedAttributeType} type - Extended attribute type | 
            
            
              | 778 | 
                   * @param {string|string[]|null} values - Current value | 
            
            
              | 779 | 
                   * @param {Object.<string, Array<AuthorizedValue>>} [authorizedValues] - Optional authorized values by category | 
            
            
              | 780 | 
                   * @returns {HTMLElement} Field element | 
            
            
              | 781 | 
                   */  | 
            
            
              | 782 | 
                  function createField(type, values, authorizedValues = {}) { | 
            
            
              | 783 | 
                      const field = Helpers.createElement("li", [ | 
            
            
              | 784 | 
                          CLASS_NAMES.FORM.GROUP,  | 
            
            
              | 785 | 
                          CLASS_NAMES.STATE.FADE,  | 
            
            
              | 786 | 
                          CLASS_NAMES.STATE.SHOW,  | 
            
            
              | 787 | 
                      ]);  | 
            
            
              | 788 | 
               | 
            
            
              | 789 | 
                      const label = Helpers.createElement("label", [CLASS_NAMES.FORM.LABEL]); | 
            
            
              | 790 | 
                      label.setAttribute(  | 
            
            
              | 791 | 
                          "for",  | 
            
            
              | 792 | 
                          `extended_attribute_${type.extended_attribute_type_id}` | 
            
            
              | 793 | 
                      );  | 
            
            
              | 794 | 
                      label.textContent = type.name + ":";  | 
            
            
              | 795 | 
                      field.appendChild(label);  | 
            
            
              | 796 | 
               | 
            
            
              | 797 | 
                      if (type.repeatable) { | 
            
            
              | 798 | 
                          const repeatableContainer = Helpers.createElement("div", [ | 
            
            
              | 799 | 
                              config.selectors.repeatableFieldClass,  | 
            
            
              | 800 | 
                          ]);  | 
            
            
              | 801 | 
               | 
            
            
              | 802 | 
                          // Always create at least one input field for repeatable fields  | 
            
            
              | 803 | 
                          values = Array.isArray(values) && values.length > 0 ? values : [""];  | 
            
            
              | 804 | 
                          values.forEach((value, index) => { | 
            
            
              | 805 | 
                              const input = createInput(type, value, index, authorizedValues);  | 
            
            
              | 806 | 
                              if (input) { | 
            
            
              | 807 | 
                                  repeatableContainer.appendChild(input);  | 
            
            
              | 808 | 
                              }  | 
            
            
              | 809 | 
                          });  | 
            
            
              | 810 | 
                          field.appendChild(repeatableContainer);  | 
            
            
              | 811 | 
               | 
            
            
              | 812 | 
                          const addButton = Helpers.createButton(  | 
            
            
              | 813 | 
                              TEXTS.ADD,  | 
            
            
              | 814 | 
                              CLASS_NAMES.ICON.ADD,  | 
            
            
              | 815 | 
                              [CLASS_NAMES.EXTENDED_ATTRIBUTE.REPEATABLE.ADD_BUTTON],  | 
            
            
              | 816 | 
                              { | 
            
            
              | 817 | 
                                  "data-attribute-id": `extended_attribute_${type.extended_attribute_type_id}`, | 
            
            
              | 818 | 
                              }  | 
            
            
              | 819 | 
                          );  | 
            
            
              | 820 | 
               | 
            
            
              | 821 | 
                          addButton.addEventListener("click", event => { | 
            
            
              | 822 | 
                              Helpers.handleAddRepeatable(  | 
            
            
              | 823 | 
                                  event,  | 
            
            
              | 824 | 
                                  type,  | 
            
            
              | 825 | 
                                  values,  | 
            
            
              | 826 | 
                                  authorizedValues  | 
            
            
              | 827 | 
                              );  | 
            
            
              | 828 | 
                          });  | 
            
            
              | 829 | 
               | 
            
            
              | 830 | 
                          field.appendChild(addButton);  | 
            
            
              | 831 | 
                      } else { | 
            
            
              | 832 | 
                          const input = createInput(  | 
            
            
              | 833 | 
                              type,  | 
            
            
              | 834 | 
                              values?.[0] || "",  | 
            
            
              | 835 | 
                              0,  | 
            
            
              | 836 | 
                              authorizedValues  | 
            
            
              | 837 | 
                          );  | 
            
            
              | 838 | 
                          if (input) { | 
            
            
              | 839 | 
                              field.appendChild(input);  | 
            
            
              | 840 | 
                          }  | 
            
            
              | 841 | 
                      }  | 
            
            
              | 842 | 
               | 
            
            
              | 843 | 
                      return field;  | 
            
            
              | 844 | 
                  }  | 
            
            
              | 845 | 
               | 
            
            
              | 846 | 
                  /**  | 
            
            
              | 847 | 
                   * Create an input element for an extended attribute type  | 
            
            
              | 848 | 
                   * @param {ExtendedAttributeType} type - Extended attribute type | 
            
            
              | 849 | 
                   * @param {string} value - Current value | 
            
            
              | 850 | 
                   * @param {number} index - Index for repeatable fields | 
            
            
              | 851 | 
                   * @param {Object.<string, Array<AuthorizedValue>>} [authorizedValues] - Optional authorized values by category | 
            
            
              | 852 | 
                   * @returns {HTMLElement} Input element | 
            
            
              | 853 | 
                   */  | 
            
            
              | 854 | 
                  function createInput(type, value, index = 0, authorizedValues = {}) { | 
            
            
              | 855 | 
                      const wrapper = Helpers.createElement("div", [ | 
            
            
              | 856 | 
                          CLASS_NAMES.CONTAINER.FLEX,  | 
            
            
              | 857 | 
                          CLASS_NAMES.CONTAINER.FLEX_ALIGN_CENTER,  | 
            
            
              | 858 | 
                          CLASS_NAMES.CONTAINER.MARGIN.BOTTOM,  | 
            
            
              | 859 | 
                      ]);  | 
            
            
              | 860 | 
               | 
            
            
              | 861 | 
                      let input;  | 
            
            
              | 862 | 
                      if (type.authorised_value_category_name) { | 
            
            
              | 863 | 
                          input = Helpers.createElement("select", [ | 
            
            
              | 864 | 
                              config.selectors.inputClass,  | 
            
            
              | 865 | 
                              CLASS_NAMES.FORM.INPUT.SELECT,  | 
            
            
              | 866 | 
                              CLASS_NAMES.CONTAINER.WIDTH.HALF,  | 
            
            
              | 867 | 
                          ]);  | 
            
            
              | 868 | 
                          input.id = `extended_attribute_${type.extended_attribute_type_id}${type.repeatable ? `_${index}` : ""}`; | 
            
            
              | 869 | 
                          input.name = NAMES.EXTENDED_ATTRIBUTE(  | 
            
            
              | 870 | 
                              type.extended_attribute_type_id,  | 
            
            
              | 871 | 
                              type.repeatable ? index : null  | 
            
            
              | 872 | 
                          );  | 
            
            
              | 873 | 
                          input.dataset.category = type.authorised_value_category_name;  | 
            
            
              | 874 | 
               | 
            
            
              | 875 | 
                          // Add default option  | 
            
            
              | 876 | 
                          const defaultOption = Helpers.createElement("option"); | 
            
            
              | 877 | 
                          defaultOption.value = "";  | 
            
            
              | 878 | 
                          defaultOption.textContent = TEXTS.SELECT_AN_OPTION;  | 
            
            
              | 879 | 
                          input.appendChild(defaultOption);  | 
            
            
              | 880 | 
               | 
            
            
              | 881 | 
                          // Add authorized values from the correct category  | 
            
            
              | 882 | 
                          const categoryValues =  | 
            
            
              | 883 | 
                              authorizedValues[type.authorised_value_category_name] || [];  | 
            
            
              | 884 | 
                          if (categoryValues.length > 0) { | 
            
            
              | 885 | 
                              categoryValues.forEach(val => { | 
            
            
              | 886 | 
                                  const option = Helpers.createElement("option"); | 
            
            
              | 887 | 
                                  option.value = val.value;  | 
            
            
              | 888 | 
                                  option.textContent = val.description;  | 
            
            
              | 889 | 
                                  input.appendChild(option);  | 
            
            
              | 890 | 
                              });  | 
            
            
              | 891 | 
                          }  | 
            
            
              | 892 | 
               | 
            
            
              | 893 | 
                          // Set the value after all options are added  | 
            
            
              | 894 | 
                          if (value) { | 
            
            
              | 895 | 
                              input.value = value;  | 
            
            
              | 896 | 
                          }  | 
            
            
              | 897 | 
                      } else { | 
            
            
              | 898 | 
                          input = Helpers.createElement("input", [ | 
            
            
              | 899 | 
                              config.selectors.inputClass,  | 
            
            
              | 900 | 
                              CLASS_NAMES.FORM.INPUT.BASE,  | 
            
            
              | 901 | 
                              CLASS_NAMES.CONTAINER.WIDTH.HALF,  | 
            
            
              | 902 | 
                          ]);  | 
            
            
              | 903 | 
                          input.type = "text";  | 
            
            
              | 904 | 
                          input.id = `extended_attribute_${type.extended_attribute_type_id}${type.repeatable ? `_${index}` : ""}`; | 
            
            
              | 905 | 
                          input.name = NAMES.EXTENDED_ATTRIBUTE(  | 
            
            
              | 906 | 
                              type.extended_attribute_type_id,  | 
            
            
              | 907 | 
                              type.repeatable ? index : null  | 
            
            
              | 908 | 
                          );  | 
            
            
              | 909 | 
                          input.value = value || "";  | 
            
            
              | 910 | 
                      }  | 
            
            
              | 911 | 
               | 
            
            
              | 912 | 
                      // Add change event listener to notify of field changes  | 
            
            
              | 913 | 
                      input.addEventListener("change", () => { | 
            
            
              | 914 | 
                          if (typeof config.onFieldsChanged === "function") { | 
            
            
              | 915 | 
                              config.onFieldsChanged(getValues());  | 
            
            
              | 916 | 
                          }  | 
            
            
              | 917 | 
                      });  | 
            
            
              | 918 | 
               | 
            
            
              | 919 | 
                      wrapper.appendChild(input);  | 
            
            
              | 920 | 
               | 
            
            
              | 921 | 
                      if (type.repeatable) { | 
            
            
              | 922 | 
                          const removeButton = Helpers.createButton(  | 
            
            
              | 923 | 
                              TEXTS.REMOVE,  | 
            
            
              | 924 | 
                              CLASS_NAMES.ICON.REMOVE,  | 
            
            
              | 925 | 
                              [  | 
            
            
              | 926 | 
                                  CLASS_NAMES.EXTENDED_ATTRIBUTE.REPEATABLE.REMOVE_BUTTON,  | 
            
            
              | 927 | 
                                  CLASS_NAMES.CONTAINER.MARGIN.START,  | 
            
            
              | 928 | 
                              ],  | 
            
            
              | 929 | 
                              { | 
            
            
              | 930 | 
                                  "data-attribute-id": `extended_attribute_${type.extended_attribute_type_id}`, | 
            
            
              | 931 | 
                              }  | 
            
            
              | 932 | 
                          );  | 
            
            
              | 933 | 
                          removeButton.addEventListener(  | 
            
            
              | 934 | 
                              "click",  | 
            
            
              | 935 | 
                              Helpers.handleRemoveRepeatable  | 
            
            
              | 936 | 
                          );  | 
            
            
              | 937 | 
                          wrapper.appendChild(removeButton);  | 
            
            
              | 938 | 
                      }  | 
            
            
              | 939 | 
               | 
            
            
              | 940 | 
                      return wrapper;  | 
            
            
              | 941 | 
                  }  | 
            
            
              | 942 | 
               | 
            
            
              | 943 | 
                  /**  | 
            
            
              | 944 | 
                   * Fetch authorized values for a category  | 
            
            
              | 945 | 
                   * @param {string} category - The category to fetch values for | 
            
            
              | 946 | 
                   * @returns {Promise<AuthorizedValue[]>} - The authorized values | 
            
            
              | 947 | 
                   */  | 
            
            
              | 948 | 
                  async function fetchAuthorizedValues(category) { | 
            
            
              | 949 | 
                      try { | 
            
            
              | 950 | 
                          const authorisedValuesClient =  | 
            
            
              | 951 | 
                              window["APIClient"].authorised_values;  | 
            
            
              | 952 | 
                          return authorisedValuesClient.values.get(category);  | 
            
            
              | 953 | 
                      } catch (error) { | 
            
            
              | 954 | 
                          console.error(  | 
            
            
              | 955 | 
                              `Error fetching authorized values for category ${category}:`, | 
            
            
              | 956 | 
                              error  | 
            
            
              | 957 | 
                          );  | 
            
            
              | 958 | 
                          return [];  | 
            
            
              | 959 | 
                      }  | 
            
            
              | 960 | 
                  }  | 
            
            
              | 961 | 
               | 
            
            
              | 962 | 
                  /**  | 
            
            
              | 963 | 
                   * Fetch extended attribute types for a resource type  | 
            
            
              | 964 | 
                   * @param {string} resourceType - Type of resource | 
            
            
              | 965 | 
                   * @returns {Promise<ExtendedAttributeType[]>} - The extended attribute types | 
            
            
              | 966 | 
                   */  | 
            
            
              | 967 | 
                  async function fetchExtendedAttributes(resourceType) { | 
            
            
              | 968 | 
                      try { | 
            
            
              | 969 | 
                          const additionalFieldsClient =  | 
            
            
              | 970 | 
                              window["APIClient"].additional_fields;  | 
            
            
              | 971 | 
                          return additionalFieldsClient.additional_fields.getAll(  | 
            
            
              | 972 | 
                              resourceType  | 
            
            
              | 973 | 
                          );  | 
            
            
              | 974 | 
                      } catch (error) { | 
            
            
              | 975 | 
                          console.error(  | 
            
            
              | 976 | 
                              `Error fetching extended attributes for resource type ${resourceType}:`, | 
            
            
              | 977 | 
                              error  | 
            
            
              | 978 | 
                          );  | 
            
            
              | 979 | 
                          return [];  | 
            
            
              | 980 | 
                      }  | 
            
            
              | 981 | 
                  }  | 
            
            
              | 982 | 
               | 
            
            
              | 983 | 
                  /**  | 
            
            
              | 984 | 
                   * Fetch and process extended attribute types  | 
            
            
              | 985 | 
                   * @param {string} resourceType - Type of resource (e.g., 'booking') | 
            
            
              | 986 | 
                   * @returns {Promise<Object.<string, Pick<ExtendedAttributeType, "authorised_value_category_name" | "name">>>} Processed extended attribute types | 
            
            
              | 987 | 
                   */  | 
            
            
              | 988 | 
                  async function fetchAndProcessExtendedAttributes(resourceType) { | 
            
            
              | 989 | 
                      try { | 
            
            
              | 990 | 
                          const additionalFieldsClient =  | 
            
            
              | 991 | 
                              window["APIClient"].additional_fields;  | 
            
            
              | 992 | 
                          const response =  | 
            
            
              | 993 | 
                              await additionalFieldsClient.additional_fields.getAll(  | 
            
            
              | 994 | 
                                  resourceType  | 
            
            
              | 995 | 
                              );  | 
            
            
              | 996 | 
               | 
            
            
              | 997 | 
                          return response.reduce(  | 
            
            
              | 998 | 
                              (  | 
            
            
              | 999 | 
                                  acc,  | 
            
            
              | 1000 | 
                                  { | 
            
          
            
              | 1001 | 
                                      extended_attribute_type_id,  | 
            
            
              | 1002 | 
                                      authorised_value_category_name,  | 
            
            
              | 1003 | 
                                      name,  | 
            
            
              | 1004 | 
                                  }  | 
            
            
              | 1005 | 
                              ) => { | 
            
            
              | 1006 | 
                                  acc[extended_attribute_type_id] = { | 
            
            
              | 1007 | 
                                      authorised_value_category_name,  | 
            
            
              | 1008 | 
                                      name,  | 
            
            
              | 1009 | 
                                  };  | 
            
            
              | 1010 | 
                                  return acc;  | 
            
            
              | 1011 | 
                              },  | 
            
            
              | 1012 | 
                              {} | 
            
            
              | 1013 | 
                          );  | 
            
            
              | 1014 | 
                      } catch (error) { | 
            
            
              | 1015 | 
                          console.error(  | 
            
            
              | 1016 | 
                              `Error fetching extended attributes for resource type ${resourceType}:`, | 
            
            
              | 1017 | 
                              error  | 
            
            
              | 1018 | 
                          );  | 
            
            
              | 1019 | 
                          return {}; | 
            
            
              | 1020 | 
                      }  | 
            
            
              | 1021 | 
                  }  | 
            
            
              | 1022 | 
               | 
            
            
              | 1023 | 
                  /**  | 
            
            
              | 1024 | 
                   * Fetch and process authorized values for categories  | 
            
            
              | 1025 | 
                   * @param {string[]} categories - Array of category names | 
            
            
              | 1026 | 
                   * @returns {Promise<Object.<string, Pick<AuthorizedValue, "description">>>} Processed authorized values | 
            
            
              | 1027 | 
                   */  | 
            
            
              | 1028 | 
                  async function fetchAndProcessAuthorizedValues(categories) { | 
            
            
              | 1029 | 
                      try { | 
            
            
              | 1030 | 
                          const authorisedValuesClient =  | 
            
            
              | 1031 | 
                              window["APIClient"].authorised_values;  | 
            
            
              | 1032 | 
                          const response =  | 
            
            
              | 1033 | 
                              await authorisedValuesClient.values.getCategoriesWithValues([  | 
            
            
              | 1034 | 
                                  JSON.stringify(categories),  | 
            
            
              | 1035 | 
                              ]);  | 
            
            
              | 1036 | 
               | 
            
            
              | 1037 | 
                          return response.reduce((acc, item) => { | 
            
            
              | 1038 | 
                              const { category_name, authorised_values } = item; | 
            
            
              | 1039 | 
                              acc[category_name] = acc[category_name] || {}; | 
            
            
              | 1040 | 
                              authorised_values.forEach(({ value, description }) => { | 
            
            
              | 1041 | 
                                  acc[category_name][value] = description;  | 
            
            
              | 1042 | 
                              });  | 
            
            
              | 1043 | 
                              return acc;  | 
            
            
              | 1044 | 
                          }, {}); | 
            
            
              | 1045 | 
                      } catch (error) { | 
            
            
              | 1046 | 
                          console.error("Error fetching authorized values:", error); | 
            
            
              | 1047 | 
                          return {}; | 
            
            
              | 1048 | 
                      }  | 
            
            
              | 1049 | 
                  }  | 
            
            
              | 1050 | 
               | 
            
            
              | 1051 | 
                  /**  | 
            
            
              | 1052 | 
                   * Render extended attributes values with their names and authorized values  | 
            
            
              | 1053 | 
                   * @param {Array<ExtendedAttribute>} attributes - Array of extended attributes | 
            
            
              | 1054 | 
                   * @param {Object.<string, Pick<ExtendedAttributeType, "name" | "authorised_value_category_name">>} types - Extended attribute types | 
            
            
              | 1055 | 
                   * @param {Object.<string, Object.<string, string>>} authorizedValues - Authorized values by category | 
            
            
              | 1056 | 
                   * @param {string} recordId - The record ID to filter attributes by | 
            
            
              | 1057 | 
                   * @returns {string[]} Array of strings of rendered attributes | 
            
            
              | 1058 | 
                   */  | 
            
            
              | 1059 | 
                  function renderExtendedAttributesValues(  | 
            
            
              | 1060 | 
                      attributes,  | 
            
            
              | 1061 | 
                      types = {}, | 
            
            
              | 1062 | 
                      authorizedValues = {}, | 
            
            
              | 1063 | 
                      recordId  | 
            
            
              | 1064 | 
                  ) { | 
            
            
              | 1065 | 
                      if (!attributes || attributes.length === 0) return [""];  | 
            
            
              | 1066 | 
                      return attributes  | 
            
            
              | 1067 | 
                          .filter(attribute => attribute.record_id == recordId)  | 
            
            
              | 1068 | 
                          .map(attribute => { | 
            
            
              | 1069 | 
                              const fieldId = attribute.field_id;  | 
            
            
              | 1070 | 
                              const typeInfo = types[fieldId] || {}; | 
            
            
              | 1071 | 
                              const name = typeInfo.name || fieldId;  | 
            
            
              | 1072 | 
                              const categoryName =  | 
            
            
              | 1073 | 
                                  typeInfo.authorised_value_category_name || "";  | 
            
            
              | 1074 | 
                              const valueDescription =  | 
            
            
              | 1075 | 
                                  authorizedValues[categoryName]?.[attribute.value] ||  | 
            
            
              | 1076 | 
                                  attribute.value;  | 
            
            
              | 1077 | 
               | 
            
            
              | 1078 | 
                              return [name, valueDescription].join(": "); | 
            
            
              | 1079 | 
                          });  | 
            
            
              | 1080 | 
                  }  | 
            
            
              | 1081 | 
               | 
            
            
              | 1082 | 
                  return { | 
            
            
              | 1083 | 
                      init,  | 
            
            
              | 1084 | 
                      getValues,  | 
            
            
              | 1085 | 
                      setValues,  | 
            
            
              | 1086 | 
                      clear,  | 
            
            
              | 1087 | 
                      renderExtendedAttributes,  | 
            
            
              | 1088 | 
                      fetchExtendedAttributes,  | 
            
            
              | 1089 | 
                      fetchAndProcessExtendedAttributes,  | 
            
            
              | 1090 | 
                      fetchAndProcessAuthorizedValues,  | 
            
            
              | 1091 | 
                      renderExtendedAttributesValues,  | 
            
            
              | 1092 | 
                  };  | 
            
            
              | 1093 | 
              })();  | 
            
            
              | 1094 | 
               | 
            
            
              | 1095 | 
              window["AdditionalFields"] = AdditionalFields;  |