diff --git a/src/model/metaData/Collapsible.ts b/src/model/metaData/Collapsible.ts
new file mode 100644
index 0000000..ea86695
--- /dev/null
+++ b/src/model/metaData/Collapsible.ts
@@ -0,0 +1,51 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+/*******************************************************************************
+ ** Meta-Data to define Collapsible state, e.g., for a section or widget
+ **
+ *******************************************************************************/
+export class Collapsible
+{
+ isCollapsible: boolean;
+ initiallyOpen: boolean;
+
+ constructor(object: any)
+ {
+ ////////////////////////////////////////////////////////////////////////////
+ // use value === true to let undefined (or any other) values become false //
+ ////////////////////////////////////////////////////////////////////////////
+ this.isCollapsible = object.isCollapsible === true;
+ this.initiallyOpen = object.initiallyOpen === true;
+ }
+
+
+ /***************************************************************************
+ *
+ ***************************************************************************/
+ public clone(): Collapsible
+ {
+ const clone = new Collapsible({
+ ...this
+ });
+ return (clone);
+ }
+}
diff --git a/src/model/metaData/QTableSection.ts b/src/model/metaData/QTableSection.ts
index b427fa4..9657741 100644
--- a/src/model/metaData/QTableSection.ts
+++ b/src/model/metaData/QTableSection.ts
@@ -19,6 +19,7 @@
* along with this program. If not, see .
*/
+import {Collapsible} from "./Collapsible";
import {QHelpContent} from "./QHelpContent";
/*******************************************************************************
@@ -37,6 +38,7 @@ export class QTableSection
gridColumns?: number;
helpContents?: QHelpContent[];
alternatives?: Map;
+ collapsible?: Collapsible;
/*******************************************************************************
@@ -68,6 +70,11 @@ export class QTableSection
this.alternatives.set(type, new QTableSection(object.alternatives[type]));
}
}
+
+ if (object.collapsible)
+ {
+ this.collapsible = new Collapsible(object.collapsible);
+ }
}
/***************************************************************************
@@ -97,11 +104,14 @@ export class QTableSection
alternativesClone.set(key, value.clone()));
}
+ const collapsibleClone: Collapsible | undefined = (this.collapsible ? this.collapsible.clone() : undefined);
+
const clone = new QTableSection({...this});
clone.fieldNames = fieldNamesClone;
clone.helpContents = helpContentsClone;
clone.alternatives = alternativesClone;
+ clone.collapsible = collapsibleClone;
return (clone);
}
diff --git a/src/model/metaData/QWidgetMetaData.ts b/src/model/metaData/QWidgetMetaData.ts
index 17c6b78..6b910ad 100644
--- a/src/model/metaData/QWidgetMetaData.ts
+++ b/src/model/metaData/QWidgetMetaData.ts
@@ -19,6 +19,7 @@
* along with this program. If not, see .
*/
+import {Collapsible} from "./Collapsible";
import {QHelpContent} from "./QHelpContent";
import {QIcon} from "./QIcon";
@@ -51,6 +52,7 @@ export class QWidgetMetaData
helpContent?: Map;
defaultValues?: Map;
+ collapsible?: Collapsible;
constructor(object: any)
{
@@ -89,5 +91,9 @@ export class QWidgetMetaData
}
}
+ if (object.collapsible)
+ {
+ this.collapsible = new Collapsible(object.collapsible);
+ }
}
}
diff --git a/tests/model/metaData/Collapsible.test.ts b/tests/model/metaData/Collapsible.test.ts
new file mode 100644
index 0000000..c1655c6
--- /dev/null
+++ b/tests/model/metaData/Collapsible.test.ts
@@ -0,0 +1,64 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2025. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import {Collapsible} from "../../../src/model/metaData/Collapsible";
+
+describe("Collapsible", () =>
+{
+ describe("constructor", () =>
+ {
+ it("should construct with all properties from object", () =>
+ {
+ const collapsible = new Collapsible({
+ isCollapsible: true,
+ initiallyOpen: true
+ });
+
+ expect(collapsible.isCollapsible).toBe(true);
+ expect(collapsible.initiallyOpen).toBe(true);
+ });
+
+ it("should handle empty object", () =>
+ {
+ const collapsible = new Collapsible({});
+
+ //////////////////////////////////////
+ // both attributes default to false //
+ //////////////////////////////////////
+ expect(collapsible.isCollapsible).toBe(false);
+ expect(collapsible.initiallyOpen).toBe(false);
+ });
+
+ it("should treat non-true values as false", () =>
+ {
+ const collapsible = new Collapsible({
+ isCollapsible: 1,
+ initiallyOpen: "true"
+ });
+
+ //////////////////////////////////////
+ // both attributes default to false //
+ //////////////////////////////////////
+ expect(collapsible.isCollapsible).toBe(false);
+ expect(collapsible.initiallyOpen).toBe(false);
+ });
+ });
+});
diff --git a/tests/model/metaData/QTableSection.test.ts b/tests/model/metaData/QTableSection.test.ts
new file mode 100644
index 0000000..38b94d2
--- /dev/null
+++ b/tests/model/metaData/QTableSection.test.ts
@@ -0,0 +1,105 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2025. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import {QTableSection} from "../../../src/model/metaData/QTableSection";
+
+describe("QTableSection", () =>
+{
+ describe("constructor", () =>
+ {
+ it("should construct with collapsible object if set in source object", () =>
+ {
+ const section = new QTableSection({
+ name: "section1",
+ collapsible: {
+ isCollapsible: true,
+ initiallyOpen: false
+ }
+ });
+
+ expect(section.name).toBe("section1");
+ expect(section.collapsible?.isCollapsible).toBe(true);
+ expect(section.collapsible?.initiallyOpen).toBe(false);
+ });
+
+ it("should construct with undefined collapsible object if not present in source object", () =>
+ {
+ const section = new QTableSection({
+ name: "section2"
+ });
+
+ expect(section.name).toBe("section2");
+ expect(section.collapsible).toBeUndefined();
+ });
+
+ it("should construct with collapsible object with null values if empty collapsible object is in source", () =>
+ {
+ const section = new QTableSection({
+ name: "section3",
+ collapsible: {}
+ });
+
+ expect(section.name).toBe("section3");
+ expect(section.collapsible?.isCollapsible).toBe(false);
+ expect(section.collapsible?.initiallyOpen).toBe(false);
+ });
+
+ it("should deeply clone a collapsible", () =>
+ {
+ const section = new QTableSection({
+ name: "section4",
+ collapsible: {
+ isCollapsible: true,
+ initiallyOpen: true
+ }
+ });
+
+ expect(section.name).toBe("section4");
+ expect(section.collapsible?.isCollapsible).toBe(true);
+ expect(section.collapsible?.initiallyOpen).toBe(true);
+
+ const clone = section.clone();
+ expect(clone.collapsible?.isCollapsible).toBe(true);
+ expect(clone.collapsible?.initiallyOpen).toBe(true);
+
+ if(clone.collapsible)
+ {
+ clone.collapsible.initiallyOpen = false;
+ }
+ expect(section.collapsible?.initiallyOpen).toBe(true);
+ expect(clone.collapsible?.initiallyOpen).toBe(false);
+ });
+
+ it("should handle an undefined collapsible during cloning", () =>
+ {
+ const section = new QTableSection({
+ name: "section5"
+ });
+
+ expect(section.name).toBe("section5");
+ expect(section.collapsible).toBeUndefined();
+
+ const clone = section.clone();
+ expect(clone.collapsible).toBeUndefined();
+ });
+ });
+
+});
diff --git a/tests/model/metaData/QWidgetMetaData.test.ts b/tests/model/metaData/QWidgetMetaData.test.ts
new file mode 100644
index 0000000..32c9440
--- /dev/null
+++ b/tests/model/metaData/QWidgetMetaData.test.ts
@@ -0,0 +1,67 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2025. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import {QWidgetMetaData} from "../../../src/model/metaData/QWidgetMetaData";
+
+describe("QWidgetMetaData", () =>
+{
+ describe("constructor", () =>
+ {
+ it("should construct with collapsible object if set in source object", () =>
+ {
+ const widget = new QWidgetMetaData({
+ name: "widget1",
+ collapsible: {
+ isCollapsible: true,
+ initiallyOpen: false
+ }
+ });
+
+ expect(widget.name).toBe("widget1");
+ expect(widget.collapsible?.isCollapsible).toBe(true);
+ expect(widget.collapsible?.initiallyOpen).toBe(false);
+ });
+
+ it("should construct with undefined collapsible object if not present in source object", () =>
+ {
+ const widget = new QWidgetMetaData({
+ name: "widget2"
+ });
+
+ expect(widget.name).toBe("widget2");
+ expect(widget.collapsible).toBeUndefined();
+ });
+
+ it("should construct with collapsible object with null values if empty collapsible object is in source", () =>
+ {
+ const widget = new QWidgetMetaData({
+ name: "widget3",
+ collapsible: {}
+ });
+
+ expect(widget.name).toBe("widget3");
+ expect(widget.collapsible?.isCollapsible).toBe(false);
+ expect(widget.collapsible?.initiallyOpen).toBe(false);
+ });
+
+ });
+
+});