class FieldBuilder { constructor(updateButtonRef) { this.UpdateButton = updateButtonRef; } // 🔧 Shared Utilities _setStatus(element, type, message) { const icon = element.querySelector(".file-status-icon, .choice-status-icon"); const text = element.querySelector(".file-status-text, .choice-status-text"); if (!icon || !text) return; icon.style.display = "inline-block"; text.textContent = message; switch (type) { case "loading": icon.className = "file-status-icon fa fa-spinner fa-spin"; break; case "success": icon.className = "file-status-icon fa fa-check text-success"; break; case "error": icon.className = "file-status-icon fa fa-exclamation-triangle text-danger"; break; case "info": icon.className = "file-status-icon fa fa-info-circle text-info"; break; default: icon.style.display = "none"; icon.className = "file-status-icon"; text.textContent = ""; } } _toggleUpdateButton(disabled) { if (this.UpdateButton) { this.UpdateButton.disabled = disabled; } } // 📄 Virtual File Upload Field createVirtualFileUploadField({ entitySetName, logicalName, recordId, id, label, description = "", currentFileName = "", accepts = "application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,text/plain,application/pdf", postUploadCallback = null } = {}) { const template = `
${description}
`; const temp = document.createElement("tbody"); temp.innerHTML = template.trim(); const element = temp.firstChild; const fileInput = element.querySelector(`#${logicalName}_input_file`); let lastUploadedFile = null; function attachEmptyFile(filename, fileInput) { const emptyFile = new File([''], filename, { type: 'application/octet-stream', lastModified: Date.now() }); const dt = new DataTransfer(); dt.items.add(emptyFile); fileInput.files = dt.files; return emptyFile; } if (currentFileName !== "") { lastUploadedFile = attachEmptyFile(currentFileName, fileInput); } initField(fileInput); if (fileInput) { fileInput.addEventListener("click", e => { if (fileInput.hasAttribute("readonly")) { e.preventDefault(); // stop file picker e.stopImmediatePropagation(); // block other handlers } }); fileInput.addEventListener("change", async () => { const file = fileInput.files[0]; if (!file) { if (lastUploadedFile) { attachEmptyFile(lastUploadedFile.name, fileInput); } this._setStatus(element, "default", ""); return; } fileInput.disabled = true; this._toggleUpdateButton(true); this._setStatus(element, "loading", "Uploading file"); try { await _api.upload(entitySetName, recordId, logicalName, file); this._setStatus(element, "success", "File uploaded successfully"); lastUploadedFile = file; if (typeof postUploadCallback === 'function') { try { postUploadCallback(file); } catch (cbErr) { console.error(cbErr); } } } catch (err) { console.error('Upload failed', err); this._setStatus(element, "error", err?.message || "Unknown error"); } finally { fileInput.disabled = false; this._toggleUpdateButton(false); } }, { passive: true }); } element.resetFileStatus = () => this._setStatus(element, "default", ""); return element; } // 📑 Dataverse Choice Field createDataverseChoiceField({ entitySetName, recordId, logicalName, dataType = "Choice", // "Choice" or "Lookup" targetTableSetName = "", // Required if dataType is "Lookup" relationshipName = "", // Required if dataType is "Lookup" id, label = "Select an option", description = "", options = [], selectedValue = "", postUpdateCallback = null, showStatus = false, allowClear = false } = {}) { const template = `
${description}
`; const statusTranslations = { loading: { en: "Saving...", fr: "Enregistrement..." }, saved: { en: "Saved successfully", fr: "Enregistré avec succès" } }; const temp = document.createElement("tbody"); temp.innerHTML = template.trim(); const element = temp.firstChild; const select = element.querySelector(`#${id}`); if (select) { initField(select); //let prevValue = selectedValue ?? ""; select.addEventListener("change", async (e) => { if (select.hasAttribute("readonly")) { this._setStatus(element, "default", "Field is read-only"); return; } const newValue = e.target.value; // do not allow blank/empty removal for now //if (!allowClear && (newValue === "" || newValue === null || newValue === undefined)) { // // Restore previous value visually without network call // select.value = prevValue; // // Provide gentle feedback // this._setStatus( // element, // "info", // "Clearing this field is not allowed." // ); // return; //} this._toggleUpdateButton(true); if (showStatus) this._setStatus(element, "loading", statusTranslations["loading"][LANG]); try { if (dataType === "Lookup") { if (!targetTableSetName || !relationshipName) { throw new Error("Missing targetTableSetName or relationshipName for lookup field"); } await _api.associate(entitySetName, recordId, relationshipName, targetTableSetName, newValue); } else { await _api.update(entitySetName, recordId, { [logicalName]: newValue }); } if (showStatus) this._setStatus(element, "success", statusTranslations["saved"][LANG]); if (typeof postUpdateCallback === "function") { try { postUpdateCallback(newValue); } catch (cbErr) { console.error(cbErr); } } } catch (err) { console.error("Update failed", err); this._setStatus(element, "error", err?.message || "Unknown error"); } finally { this._toggleUpdateButton(false); } }, { passive: true }); } return element; } }