What's new?
The new iframe API gives users access to pre-existing sandbox API endpoints without the need for message handling. We've exposed a set of asynchronous functions that can be awaited, this ensures that HTML components can be more deterministic as a result is guaranteed. Buzzy handles the message handling, making your HTML code snippets simpler!
This feature is current opt-in and you must enable it through the HTML field settings under "Enable Async API ".
Script Placement
Warning
You must place the <script> inside the <body> of your custom HTML code.
This is important as the Buzzy Frame API is loaded in the head and may not be available due to loading order.
Available Asynchronous Functions
Data Management
Insert a Row
Usage
Copy insertMicroappRow({ body })
Return
Update a Row
Usage
Copy updateMicroappRow({ body })
Return
Copy {
body: { status: 'success', message: 'MicroApp Record Updated' },
}
Remove a Row
Usage
Copy removeMicroappRow({ rowID })
Return
None.
Fetch Embedded Row Data
Usage
Copy fetchEmbeddedRowData({
microAppID,
embeddingRowID,
subLimit,
viewFilters,
viewSort,
})
Return
Resource as JSON.
Fetch All Embedded Data
Usage
Copy fetchAllEmbeddedData(microAppID)
Return
Object with all data tables and sub tables resolves to N levels .
Fetch Data Table Rows 🆕
Usage
Copy fetchDataTableRows({
microAppID,
embeddingRowID,
viewFilters,
queryOptions,
viewSort,
})
Return
Array of data table rows as JSON.
Query Options (Pagination) and Filtering
When fetching embedded row data, you might to do pagination, filtering, and/or limit the number of results that you get. Buzzy uses MongoDB under the hood, as such, view filters follow the rules for MongoDB queries.
Query Options (Pagination and Data Limits)
Query options allow you to control the number of results you get back from the query, as well as allowing you to do pagination using the skip
parameter. Common supported MongoDB aggregation parameters:
More info on Sorting:
The data fetched from the Microapp can be sorted. The order
and field
parameters are used in the searchFilter to specify which Microapp sort field and if it is ascending or descending. The sort fields are configured in the Microapp Results Tab. Sort field 1 corresponds to the field
value 1 and so on. Where order
is either 1
= ascending or -1
= descending.
Example fetching microapp rows using Sort Field 1.
Copy queryOptions: [
{
"resourceID": "<microapp ID goes here>",
"order": 1,
"field": "1",
"skip": "5",
"limit": 100
}
]
View Filters (Filtering)
In cases where you might want to filter the results based on the value of a field, you can query based on the value of a field of the data table row. viewFilters
is an array of view filters to be used, as such you may filter many views as they are scoped to the data table ID (resourceID).
Combining the Above Concepts, see the following example with filtering and pagination included:
Copy const recipeData = await buzzyFrameAPI.fetchDataTableRows(
{
microAppID: recipeMetadata.id,
viewFilters:[ {sortVal: {$regex: 'Lasagna'}} ],
queryOptions:[{resourceID: recipeMetadata.id, limit: 1, skip: 0}]
}
);
In the example above, the query will fetch rows where the sortVal
contains the word "Lasagna", in this case, the sortVal
is referencing the data table row title (the recipe title). The query options them limit the rows to only 1 row, while skipping 0. If we were doing some pagination, when a user clicked a "Next Page" button, we could change the skip to 1 so it would get the next recipe that satisfies the filters.
MicroAppChild Management
createMicroappChild
Creates a new MicroAppChild entry.
Copy await buzzyFrameAPI.createMicroappChild({
microAppResourceID, // ID of the MicroApp resource (from rowJSON.parentResourceID)
appID, // ID of the parent app item (from rowJSON._id)
fieldID, // ID of the parent field the child is associated with
content // Content data for the child (e.g., file metadata, chart config)
});
Example usage for file upload:
Copy await buzzyFrameAPI.createMicroappChild({
microAppResourceID: rowJSON.parentResourceID,
appID: rowJSON._id,
fieldID: rowJSON.fieldID,
content: {
url: presignedReadUrl,
filename: file.name,
size: file.size,
type: file.type,
expiredAt: Date.now() + (7 * 24 * 60 * 60 * 1000) // 7 days
}
});
getChildItemsByField
Retrieves all MicroAppChild entries for a specific parent app item and field.
Copy const children = await buzzyFrameAPI.getChildItemsByField({
appID, // ID of the parent app item to get children for
fieldID // ID of the parent field to get children for
});
Example usage for fetching images:
Copy const images = await buzzyFrameAPI.getChildItemsByField({
appID: rowJSON._id,
fieldID: rowJSON.fieldID
});
updateMicroappChild
Updates the content of a MicroAppChild entry.
Copy await buzzyFrameAPI.updateMicroappChild({
childID, // ID of the child to update
content // New content data
});
Example usage for updating file URL:
Copy await buzzyFrameAPI.updateMicroappChild({
childID: file._id,
content: {
...file.content,
url: presignedReadUrl,
expiredAt: Date.now() + (7 * 24 * 60 * 60 * 1000)
}
});
removeMicroappChild
Deletes a MicroAppChild entry.
Copy await buzzyFrameAPI.removeMicroappChild({
childID // ID of the child to delete
});
Navigation
navigate
Navigates to a different screen within the same app.
Copy await buzzyFrameAPI.navigate({
screenID, // ID of the screen to navigate to (use getScreenID to obtain)
rowID // Optional: ID of the row to view on the screen
});
Example usage:
Copy const screenID = await buzzyFrameAPI.getScreenID({ screenName: 'Product Details' });
await buzzyFrameAPI.navigate({
screenID,
rowID: product._id
});
getScreenID
Helper method to get the screenID for navigation.
Copy const screenID = await buzzyFrameAPI.getScreenID({
screenName // Name of the screen to get ID for
});
Listen for Updates
Usage
Copy addMicroappListener({
microAppID,
listener
})
Register a listener function for a given microapp (aka data table).
Whenever data is added or updated that fits the query you have running on the given microapp, the supplied listener function will be executed. In the native app version, this function will also be run if the user initiates a refresh by dragging down on the screen.
Typically this would be used to re-run your query and set the results to a state variable, and thus trigger re-rendering of your fetched data whenever it is updated.
The listener will be passed on object with these properties:
microAppID - the id that passed into the listener
isUserRefresh - true if this is a user initiated refresh
timestamp - timestamp of the update that triggered the listener
Code Example
Preamble
Buzzy HTML Field Settings
Due to the usage of React, we want to disable handlebars and opt-in to the new Async API . This is easily accomplished in the settings panel of the HTML field definition:
Our Example Data table Layout
As a reference for the code snippet, the fields and set up of the data tables referenced in the code example are provided below:
Fruit Reports (Data table)
Fruit Counters (Sub table)
Graphic-HTML (HTML Field)
Fruit Counters (Sub table)
Code
Copy <!DOCTYPE html>
<html>
<head>
<title>HTML Example</title>
<script
src="https://unpkg.com/react@18/umd/react.production.min.js"
crossorigin
></script>
<script
src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
crossorigin
></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.3.3/dist/chart.umd.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel" data-presets="env,react">
// Initializing the global BuzzyFrameAPI class
// This gives us access to API methods to do CRUD operations
const buzzyFrameAPI = new BuzzyFrameAPI();
// React component
function App() {
const { useEffect, useState } = React;
const [frameToken, setFrameToken] = useState(null);
const [fruitData, setFruitData] = useState(null);
useEffect(() => {
let isUnmounted = false;
async function initData() {
try {
const initData = await buzzyFrameAPI.initialise();
const {
rowJSON = {},
frameToken,
id,
resolveData,
} = initData || {};
if (rowJSON?._id && rowJSON["Fruit Counters"]) {
const fruitTableRowData =
await buzzyFrameAPI.fetchDataTableRows({
microAppID: rowJSON["Fruit Counters"],
embeddingRowID: rowJSON?._id,
});
if (!isUnmounted) {
setFruitData(fruitTableRowData);
}
}
} catch (error) {
console.log("[IFRAME] Error occured in initData().", error);
}
}
initData();
return () => {
isUnmounted = true;
};
}, []);
const PieChart = ({ data }) => {
const labels = data.map((fruit) => fruit["Fruit Type"]);
const counts = data.map((fruit) => fruit["Count"]);
const chartData = {
labels,
datasets: [
{
data: counts,
backgroundColor: [
"#FF6384",
"#36A2EB",
"#FFCE56",
"#4CAF50",
// Add more colors if needed
],
},
],
};
const canvas = document.createElement("canvas");
document.getElementById("root").appendChild(canvas);
new Chart(canvas.getContext("2d"), {
type: "doughnut",
data: chartData,
});
};
return (
<div>
{fruitData ? <PieChart data={fruitData} /> : <p>Loading...</p>}
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
</script>
</body>
</html>
Result
With some data table rows, the above example provides the following output in Buzzy:\
Advanced Examples
Example 1: Course Enrollment with File/Image Copying
This example demonstrates how to create a course enrollment system that:
Creates a copy of a course template for a user
Copies all associated questionnaires, questions, and options
Handles file and image copying between templates and user instances
Uses navigation to guide the user through the process
Copy <!DOCTYPE html>
<html>
<head>
<title>Do Course with Images and Files</title>
<script
src="https://unpkg.com/react@18/umd/react.production.min.js"
crossorigin
></script>
<script
src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
crossorigin
></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500&display=swap" rel="stylesheet" />
<style>
html,
body {
margin: 0;
padding: 0;
overflow: hidden;
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
body::-webkit-scrollbar {
display: none;
}
button {
display: block;
width: 100%;
height: 40px;
font-family: "Poppins", serif;
font-weight: 500;
font-size: 16px;
line-height: 24px;
cursor: pointer;
background: transparent;
color: white;
border: none;
outline: none;
}
.spinner {
display: inline-block;
width: 1em;
height: 1em;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel" data-presets="env,react">
const buzzyFrameAPI = new BuzzyFrameAPI();
const IMAGE_EXPIRY_TIME = 604800; // 7 days seconds
const NUM_ITEMS = 100;
function App() {
const { useEffect, useState, useRef } = React;
const [loading, setLoading] = useState(false);
const [
studentQuitCourseProcessingScreenID,
setStudentQuitCourseProcessingScreenID,
] = useState(null);
const [studentCourseDetailsScreenID, setStudentCourseDetailsScreenID] =
useState(null);
const metadata = useRef([]);
const TEMPLATE_COURSE_MICROAPPID = useRef(null);
const TEMPLATE_QUESTIONNAIRE_MICROAPPID = useRef(null);
const TEMPLATE_QUESTION_MICROAPPID = useRef(null);
const TEMPLATE_QUESTION_OPTIONS_MICROAPPID = useRef(null);
const USER_COURSE_MICROAPPID = useRef(null);
const USER_QUESTIONNAIRE_MICROAPPID = useRef(null);
const USER_QUESTION_MICROAPPID = useRef(null);
const USER_QUESTION_OPTIONS_MICROAPPID = useRef(null);
const USER_MICROAPPID = useRef(null);
useEffect(() => {
async function initData() {
try {
const { rowJSON } = await buzzyFrameAPI.initialise();
metadata.current = await buzzyFrameAPI.fetchDatatableMetadata({
dataTableID: rowJSON?.parentResourceID,
});
TEMPLATE_COURSE_MICROAPPID.current = metadata.current.find(
(table) => table.dataTableName === "CourseTemplate"
)?.id;
TEMPLATE_QUESTIONNAIRE_MICROAPPID.current = metadata.current.find(
(table) => table.dataTableName === "QuestionnaireTemplate"
)?.id;
TEMPLATE_QUESTION_MICROAPPID.current = metadata.current.find(
(table) => table.dataTableName === "QuestionTemplate"
)?.id;
TEMPLATE_QUESTION_OPTIONS_MICROAPPID.current =
metadata.current.find(
(table) => table.dataTableName === "Option"
)?.id;
USER_COURSE_MICROAPPID.current = metadata.current.find(
(table) => table.dataTableName === "UserCourse"
)?.id;
USER_QUESTIONNAIRE_MICROAPPID.current = metadata.current.find(
(table) => table.dataTableName === "Questionnaire"
)?.id;
USER_QUESTION_MICROAPPID.current = metadata.current.find(
(table) => table.dataTableName === "Question"
)?.id;
USER_QUESTION_OPTIONS_MICROAPPID.current = metadata.current.find(
(table) => table.dataTableName === "OptionUser"
)?.id;
USER_MICROAPPID.current = metadata.current.find(
(table) => table.dataTableName === "User"
)?.id;
console.log("Metadata initialized:", {
TEMPLATE_COURSE_MICROAPPID: TEMPLATE_COURSE_MICROAPPID.current,
TEMPLATE_QUESTIONNAIRE_MICROAPPID:
TEMPLATE_QUESTIONNAIRE_MICROAPPID.current,
TEMPLATE_QUESTION_MICROAPPID:
TEMPLATE_QUESTION_MICROAPPID.current,
TEMPLATE_QUESTION_OPTIONS_MICROAPPID:
TEMPLATE_QUESTION_OPTIONS_MICROAPPID.current,
USER_COURSE_MICROAPPID: USER_COURSE_MICROAPPID.current,
USER_QUESTIONNAIRE_MICROAPPID:
USER_QUESTIONNAIRE_MICROAPPID.current,
USER_QUESTION_MICROAPPID: USER_QUESTION_MICROAPPID.current,
USER_QUESTION_OPTIONS_MICROAPPID:
USER_QUESTION_OPTIONS_MICROAPPID.current,
USER_MICROAPPID: USER_MICROAPPID.current,
});
} catch (error) {
console.error("Error initializing metadata:", error);
}
}
initData();
}, []);
async function handleDoCourse() {
setLoading(true);
try {
const initData = await buzzyFrameAPI.initialise();
const { rowJSON } = initData;
function generateRandomString(length) {
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
for (let i = 0; i < length; i++) {
result += characters.charAt(
Math.floor(Math.random() * characters.length)
);
}
return result;
}
const extractS3Keys = (url) => {
try {
// Extract the pathname from the URL
const pathname = new URL(url).pathname;
// Split the pathname into parts
const parts = pathname.split("/");
if (parts.length >= 3) {
const resourceID = parts[1]; // First part after the domain
const fileKey = parts[2]; // Second part after the domain
return { resourceID, fileKey };
} else {
throw new Error("URL does not have the expected structure.");
}
} catch (error) {
console.error("Error parsing URL:", error.message);
return null;
}
};
// Copy MicroAppChild data helper
async function copyMicroAppChildDataForRow({
sourceRowID,
targetRowID,
sourceMicroAppID,
targetMicroAppID,
}) {
const fieldsToCopy = metadata.current
.find((table) => table.id === sourceMicroAppID)
?.fields.filter(
(field) =>
field.fieldType === "images" || field.fieldType === "files"
);
console.log("Fields to copy:", fieldsToCopy);
if (!fieldsToCopy || fieldsToCopy.length === 0) {
console.log(
"No fields to copy for MicroApp:",
sourceMicroAppID
);
return;
}
for (const field of fieldsToCopy) {
const items = await buzzyFrameAPI.getChildItemsByField({
appID: sourceRowID,
fieldID: field.id,
});
console.log("Items to copy:", items);
for (const item of items) {
const targetFieldID = metadata.current
.find((table) => table.id === targetMicroAppID)
?.fields.find((f) => f.fieldName === field.fieldName)?.id;
console.log("Target field ID:", targetFieldID);
const { url } = item.content || {};
const extractedKeys = extractS3Keys(url);
const newFileKey = generateRandomString(10);
console.log("About to copy s3", {
url,
extractedKeys,
newFileKey,
});
const expiredAt =
new Date().getTime() + IMAGE_EXPIRY_TIME * 1000;
const newURL = await buzzyFrameAPI.copyS3File({
sourceResourceID: extractedKeys.resourceID,
destinationResourceID: extractedKeys.resourceID,
fileKey: `${extractedKeys.resourceID}/${extractedKeys.fileKey}`,
newFileKey: `${extractedKeys.resourceID}/${newFileKey}`,
});
console.log("COPIED New URL:", {
oldUrl: item.content.url,
newURL,
targetFieldID,
});
if (targetFieldID) {
const newContent = {
...item.content,
url: newURL,
expiredAt,
};
console.log("Copying item:", newContent);
await buzzyFrameAPI.createMicroappChild({
microAppResourceID: targetMicroAppID,
appID: targetRowID,
fieldID: targetFieldID,
content: newContent,
});
}
}
}
}
const courseTemplate = rowJSON;
console.log("About to nav Student Add Course Processing");
buzzyFrameAPI.navigate({
screenID: await buzzyFrameAPI.getScreenID({
screenName: "Student Add Course Processing",
}),
});
console.log("about to fetch user template:", {
courseTemplate,
USER_MICROAPPID: USER_MICROAPPID.current,
});
// Fetch the current user based on logged-in user ID
const currentUser = await buzzyFrameAPI.fetchDataTableRows({
microAppID: USER_MICROAPPID.current,
sortVal: courseTemplate.currentLoggedInUser,
subLimit: 1,
});
if (!currentUser || currentUser.length === 0) {
throw new Error("Current user not found.");
}
console.log("About to create UserCourse for user", currentUser);
// Create UserCourse and copy children
const userCourse = await buzzyFrameAPI.insertMicroappRow({
body: {
microAppID: USER_COURSE_MICROAPPID.current,
rowData: {
title: courseTemplate.name,
description: courseTemplate.description,
status: "Not Started",
},
embeddingRowID: currentUser[0]._id,
ignoreActionRules: true,
},
});
console.log("UserCourse created:", userCourse);
await copyMicroAppChildDataForRow({
sourceRowID: courseTemplate._id,
targetRowID: userCourse.rowID,
sourceMicroAppID: TEMPLATE_COURSE_MICROAPPID.current,
targetMicroAppID: USER_COURSE_MICROAPPID.current,
});
const questionnaireTemplates =
await buzzyFrameAPI.fetchDataTableRows({
microAppID: TEMPLATE_QUESTIONNAIRE_MICROAPPID.current,
embeddingRowID: courseTemplate._id,
subLimit: NUM_ITEMS,
});
console.log("Questionnaire templates:", questionnaireTemplates);
for (const questionnaireTemplate of questionnaireTemplates) {
const userQuestionnaire = await buzzyFrameAPI.insertMicroappRow({
body: {
microAppID: USER_QUESTIONNAIRE_MICROAPPID.current,
rowData: { title: questionnaireTemplate.name },
embeddingRowID: userCourse.rowID,
ignoreActionRules: true,
},
});
await copyMicroAppChildDataForRow({
sourceRowID: questionnaireTemplate._id,
targetRowID: userQuestionnaire.rowID,
sourceMicroAppID: TEMPLATE_QUESTION_MICROAPPID.current,
targetMicroAppID: USER_QUESTIONNAIRE_MICROAPPID.current,
});
const questionTemplates = await buzzyFrameAPI.fetchDataTableRows({
microAppID: TEMPLATE_QUESTION_MICROAPPID.current,
embeddingRowID: questionnaireTemplate._id,
subLimit: NUM_ITEMS,
});
for (const questionTemplate of questionTemplates) {
const userQuestion = await buzzyFrameAPI.insertMicroappRow({
body: {
microAppID: USER_QUESTION_MICROAPPID.current,
rowData: { ...questionTemplate, answer: "" },
embeddingRowID: userQuestionnaire.rowID,
ignoreActionRules: true,
},
});
await copyMicroAppChildDataForRow({
sourceRowID: questionTemplate._id,
targetRowID: userQuestion.rowID,
sourceMicroAppID: TEMPLATE_QUESTION_MICROAPPID.current,
targetMicroAppID: USER_QUESTION_MICROAPPID.current,
});
const options = await buzzyFrameAPI.fetchDataTableRows({
microAppID: TEMPLATE_QUESTION_OPTIONS_MICROAPPID.current,
embeddingRowID: questionTemplate._id,
subLimit: NUM_ITEMS,
});
for (const option of options) {
const userOption = await buzzyFrameAPI.insertMicroappRow({
body: {
microAppID: USER_QUESTION_OPTIONS_MICROAPPID.current,
rowData: { optionText: option.optionText },
embeddingRowID: userQuestion.rowID,
ignoreActionRules: true,
},
});
await copyMicroAppChildDataForRow({
sourceRowID: option._id,
targetRowID: userOption.rowID,
sourceMicroAppID:
TEMPLATE_QUESTION_OPTIONS_MICROAPPID.current,
targetMicroAppID: USER_QUESTION_OPTIONS_MICROAPPID.current,
});
}
}
}
console.log("Course data copied successfully.");
buzzyFrameAPI.navigate({
screenID: await buzzyFrameAPI.getScreenID({
screenName: "Student Add Course Success",
}),
rowID: userCourse.rowID,
});
} catch (error) {
console.error("Error during course copy:", error);
} finally {
setLoading(false);
}
}
return (
<div>
<button onClick={handleDoCourse} disabled={loading}>
{loading ? <span className="spinner"></span> : "Take this course"}
</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
</script>
</body>
</html>
This example demonstrates several key features:
Using getChildItemsByField
to fetch file and image attachments
Using createMicroappChild
to copy attachments to new rows
Using navigate
to guide users through the enrollment process
Handling complex data relationships and maintaining referential integrity
Example 2: Course Unenrollment with Cleanup
This example shows how to properly remove a course and all its associated data, maintaining referential integrity:
Copy <!DOCTYPE html>
<html>
<head>
<title>Manage Courses</title>
<script
src="https://unpkg.com/react@18/umd/react.production.min.js"
crossorigin
></script>
<script
src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
crossorigin
></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,200;0,300;0,400;0,500&display=swap"
rel="stylesheet"
/>
<style>
html,
body {
margin: 0;
padding: 0;
overflow: hidden;
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
body::-webkit-scrollbar {
display: none;
}
button {
display: block;
width: 140px;
height: 40px;
padding: 0px;
font-family: "Poppins", serif;
font-weight: 500;
font-size: 16px;
line-height: 40px;
text-align: center;
background: transparent;
cursor: pointer;
color: white;
border: none;
outline: none;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel" data-presets="env,react">
const DEFAULT_NUM_ROWS = 100;
let TEMPLATE_QUESTION_MICROAPPID,
TEMPLATE_QUESTION_OPTIONS_MICROAPPID,
USER_COURSE_MICROAPPID,
USER_QUESTIONNAIRE_MICROAPPID,
USER_QUESTION_MICROAPPID,
USER_QUESTION_OPTIONS_MICROAPPID,
USER_MICROAPPID;
let metadata = [];
const buzzyFrameAPI = new BuzzyFrameAPI();
function App() {
const { useEffect, useState } = React;
const [courseID, setCourseID] = useState(null);
useEffect(() => {
async function initData() {
try {
const { rowJSON } = await buzzyFrameAPI.initialise();
metadata = await buzzyFrameAPI.fetchDatatableMetadata({
dataTableID: rowJSON?.parentResourceID,
});
setCourseID(rowJSON._id);
USER_COURSE_MICROAPPID = metadata.find(
(table) => table.dataTableName === "UserCourse"
)?.id;
USER_QUESTIONNAIRE_MICROAPPID = metadata.find(
(table) => table.dataTableName === "Questionnaire"
)?.id;
USER_QUESTION_MICROAPPID = metadata.find(
(table) => table.dataTableName === "Question"
)?.id;
USER_QUESTION_OPTIONS_MICROAPPID = metadata.find(
(table) => table.dataTableName === "OptionUser"
)?.id;
USER_MICROAPPID = metadata.find(
(table) => table.dataTableName === "User"
)?.id;
} catch (error) {
console.error("[ERROR] Error during initialization:", error);
}
}
initData();
}, []);
const handleDeleteCourse = async () => {
if (!courseID) return;
try {
buzzyFrameAPI.navigate({
screenID: await buzzyFrameAPI.getScreenID({
screenName: "Student Quit Course Processing",
}),
});
// Fetch questionnaires linked to the course
const questionnaires = await buzzyFrameAPI.fetchDataTableRows({
microAppID: USER_QUESTIONNAIRE_MICROAPPID,
subLimit: DEFAULT_NUM_ROWS,
embeddingRowID: courseID,
});
console.log("QUESTIONNAIRES TO DELETE:", questionnaires);
for (const questionnaire of questionnaires) {
// Fetch questions linked to each questionnaire
const questions = await buzzyFrameAPI.fetchDataTableRows({
microAppID: USER_QUESTION_MICROAPPID,
subLimit: DEFAULT_NUM_ROWS,
embeddingRowID: questionnaire._id,
});
for (const question of questions) {
// Fetch options linked to each question
const options = await buzzyFrameAPI.fetchDataTableRows({
microAppID: USER_QUESTION_OPTIONS_MICROAPPID,
subLimit: DEFAULT_NUM_ROWS,
embeddingRowID: question._id,
});
// Delete each option
for (const option of options) {
await buzzyFrameAPI.removeMicroappRow({
body: { rowID: option._id, ignoreActionRules: true },
});
}
// Delete the question
await buzzyFrameAPI.removeMicroappRow({
body: { rowID: question._id, ignoreActionRules: true },
});
}
// Delete the questionnaire
await buzzyFrameAPI.removeMicroappRow({
body: { rowID: questionnaire._id, ignoreActionRules: true },
});
}
// Delete the course
await buzzyFrameAPI.removeMicroappRow({
body: { rowID: courseID, ignoreActionRules: true },
});
console.log(
"[DEBUG] Course and all its children deleted successfully."
);
buzzyFrameAPI.navigate({
screenID: await buzzyFrameAPI.getScreenID({
screenName: "Student Course List",
rowID: null,
}),
rowID: null,
});
} catch (error) {
console.error("[ERROR] Error during course deletion:", error);
}
};
return (
<div>
<button onClick={handleDeleteCourse}>Quit course</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
</script>
</body>
</html>
This example demonstrates:
Proper cleanup of nested data structures
Using navigate
to show processing and completion states
Maintaining referential integrity during deletion
Error handling and user feedback
Limitations
The current iteration of the new API is still under development. There are a number of key issue yet to be resolved: