@tableflow/js
Version:
The JavaScript SDK for TableFlow. Embed an importer to collect and transform CSV files in your application.
196 lines (167 loc) • 6.25 kB
text/typescript
import { TableFlowImporterProps } from "./types";
import "./index.css";
import postMessage from "./utils/postMessage";
let postMessages: string[] = [];
type MessageType = "start" | "complete" | "close" | "reload" | "postMessageSubscribe";
type MessageData = {
source: string;
id: string;
type: MessageType;
importerId: string;
data?: any;
};
export default function createTableFlowImporter({
elementId = "tableflow-importer",
importerId,
hostUrl,
isModal = true,
modalOnCloseTriggered = (importComplete?: boolean) => null,
modalCloseOnOutsideClick,
template,
darkMode = false,
primaryColor = "#7a5ef8",
metadata = "{}",
onComplete,
waitOnComplete,
customStyles,
className,
showDownloadTemplateButton,
skipHeaderRowSelection,
cssOverrides,
schemaless,
schemalessReadOnly,
schemalessDataTypes,
trimSpaces,
language,
customTranslations,
showUploadAnotherFileButton,
}: TableFlowImporterProps) {
// CSS classes
const baseClass = "TableFlowImporter";
const themeClass = darkMode && `${baseClass}-dark`;
const domElementClass = [`${baseClass}-${isModal ? "dialog" : "div"}`, themeClass, className].filter((i) => i).join(" ");
// domElement element
let domElement = document.getElementById(elementId) as HTMLDialogElement | HTMLDivElement;
let isImportComplete = false;
const backdropClick = () => {
if (modalCloseOnOutsideClick) {
modalOnCloseTriggered(isImportComplete);
isImportComplete = false;
}
};
if (domElement === null) {
domElement = isModal ? (document.createElement("dialog") as HTMLDialogElement) : (document.createElement("div") as HTMLDivElement);
document.body.appendChild(domElement);
domElement.setAttribute("id", elementId);
if (isModal) domElement.addEventListener("click", backdropClick);
}
domElement.setAttribute("class", domElementClass);
importerId = importerId === undefined || importerId?.trim() === "" ? "0" : importerId;
// iframe element
let urlParams = {};
let messageParams = {
importerId,
isModal: isModal ? "true" : "false",
modalIsOpen: "true",
template: parseObjectOrStringJSON("template", template),
darkMode: darkMode.toString(),
primaryColor,
metadata: parseObjectOrStringJSON("metadata", metadata),
onComplete: onComplete ? "true" : "false",
waitOnComplete: parseOptionalBoolean(waitOnComplete),
customStyles: parseObjectOrStringJSON("customStyles", customStyles),
cssOverrides: parseObjectOrStringJSON("cssOverrides", cssOverrides),
showDownloadTemplateButton: parseOptionalBoolean(showDownloadTemplateButton),
skipHeaderRowSelection: parseOptionalBoolean(skipHeaderRowSelection),
schemaless: parseOptionalBoolean(schemaless),
schemalessReadOnly: parseOptionalBoolean(schemalessReadOnly),
schemalessDataTypes: parseOptionalBoolean(schemalessDataTypes),
trimSpaces: parseOptionalBoolean(trimSpaces),
language: language || "en",
customTranslations: parseObjectOrStringJSON("customTranslations", customTranslations),
showUploadAnotherFileButton: parseOptionalBoolean(showUploadAnotherFileButton),
};
const uploaderUrl = getUploaderUrl(urlParams, hostUrl);
const postHandlers = {
start: (messageData: MessageData) => {
if (messageParams.modalIsOpen !== "true") {
isImportComplete = false;
const iframe = domElement.querySelector("iframe");
postMessage(iframe, { ...messageParams, modalIsOpen: "true" });
}
},
complete: (messageData: MessageData) => {
isImportComplete = true;
onComplete?.(messageData?.data);
postMessages.push(messageData?.id);
},
close: (messageData: MessageData) => {
modalOnCloseTriggered?.(isImportComplete);
postMessages.push(messageData?.id);
if (messageParams.modalIsOpen !== "false") {
const iframe = domElement.querySelector("iframe");
postMessage(iframe, { ...messageParams, modalIsOpen: "false" });
}
},
reload: (messageData: MessageData) => {
isImportComplete = false;
postMessages.push(messageData?.id);
},
postMessageSubscribe: () => {
const iframe = domElement.querySelector("iframe");
postMessage(iframe, { params: messageParams });
},
};
function messageListener(e: { data: MessageData } | MessageEvent<MessageData> | null) {
if (!e || !e?.data) return;
const messageData = e.data;
if (
!messageData ||
messageData?.source !== "tableflow-importer" ||
// checks if there is importerId and if it is different from the current importerId
(messageData?.importerId && messageData?.importerId !== importerId) ||
!messageData?.id ||
postMessages.includes(messageData.id)
) {
return;
}
postHandlers[messageData.type]?.(messageData);
}
window.addEventListener("message", messageListener);
domElement.innerHTML = `<iframe src="${uploaderUrl}" />`;
return domElement;
}
function getUploaderUrl(urlParams: any, hostUrl?: string) {
const searchParams = new URLSearchParams(urlParams);
const defaultImporterUrl = "https://importer.tableflow.com";
return `${hostUrl ? hostUrl : defaultImporterUrl}?${searchParams}`;
}
// Allows for the user to pass in JSON as either an object or a string
const parseObjectOrStringJSON = (name: string, param?: Record<string, unknown> | string): string => {
if (typeof param === "undefined") {
return "";
}
let parsedObj: Record<string, unknown> = {};
if (typeof param === "string") {
try {
parsedObj = JSON.parse(param);
} catch (e) {
console.error(
`The '${name}' prop is not a valid JSON string. This prop can either be a JSON string or JSON object. Please check the documentation for more details.`
);
return "";
}
} else {
parsedObj = param;
}
// Replace % symbols with %25
for (const key in parsedObj) {
if (typeof parsedObj[key] === "string") {
parsedObj[key] = (parsedObj[key] as string).replace(/%(?!25)/g, "%25");
}
}
return JSON.stringify(parsedObj);
};
const parseOptionalBoolean = (val?: boolean) => {
return typeof val === "undefined" || val === null ? "" : val ? "true" : "false";
};