diff --git a/jahia-test-module/src/react/server/views/testGetChildNodes/TestGetChildNodes.tsx b/jahia-test-module/src/react/server/views/testGetChildNodes/TestGetChildNodes.tsx index 7463eb2c..2c621591 100644 --- a/jahia-test-module/src/react/server/views/testGetChildNodes/TestGetChildNodes.tsx +++ b/jahia-test-module/src/react/server/views/testGetChildNodes/TestGetChildNodes.tsx @@ -2,6 +2,8 @@ import { AddContentButtons, getChildNodes, jahiaComponent, + RenderChild, + RenderChildren, } from "@jahia/javascript-modules-library"; import type { Node } from "javax.jcr"; @@ -88,7 +90,33 @@ jahiaComponent( /> + +

RenderChildren

+
+ +
+
+ node.getName().includes("filtered")} /> +
+
+ +
+ +

RenderChild

+
+ +
); }, ); + +jahiaComponent( + { + nodeType: "javascriptExample:testGetChildNodes", + name: "path", + displayName: "test getChildNodes", + componentType: "view", + }, + (_, { currentNode }) =>
{currentNode.getPath()}
, +); diff --git a/javascript-create-module/template/src/components/HelloWorld/default.server.tsx b/javascript-create-module/template/src/components/HelloWorld/default.server.tsx index fa9c794e..de3a9ab9 100644 --- a/javascript-create-module/template/src/components/HelloWorld/default.server.tsx +++ b/javascript-create-module/template/src/components/HelloWorld/default.server.tsx @@ -1,8 +1,6 @@ import { - AddContentButtons, HydrateInBrowser, - Render, - getChildNodes, + RenderChildren, jahiaComponent, useUrlBuilder, } from "@jahia/javascript-modules-library"; @@ -17,7 +15,7 @@ jahiaComponent( displayName: "Hello World Component", componentType: "view", }, - ({ name }: { name: string }, { renderContext, currentNode }) => { + ({ name }: { name: string }, { renderContext }) => { const { buildStaticUrl } = useUrlBuilder(); return ( <> @@ -44,13 +42,7 @@ jahiaComponent(

{t("7l9zetMbU4cKpL4NxSOtL")}

- {getChildNodes(currentNode, -1, 0, (node) => node.isNodeType("jnt:content")).map( - (node) => ( - // @ts-expect-error Fix the types - - ), - )} - +

diff --git a/javascript-modules-engine/tests/cypress/e2e/ui/getChildNodesTest.cy.ts b/javascript-modules-engine/tests/cypress/e2e/ui/getChildNodesTest.cy.ts index c631ff19..7bcfd55b 100644 --- a/javascript-modules-engine/tests/cypress/e2e/ui/getChildNodesTest.cy.ts +++ b/javascript-modules-engine/tests/cypress/e2e/ui/getChildNodesTest.cy.ts @@ -202,4 +202,42 @@ describe('getChildNodes function test', () => { cy.logout() }) + + it('Verify RenderChildren', function () { + cy.login() + + cy.visit('/cms/render/default/en/sites/javascriptTestSite/home/testGetChildNodes.html') + + for (const child of ['child1', 'child2', 'filtered', 'filtered2', 'filtered3']) { + cy.get('div[data-testid="renderAllChildren"]').contains( + `/sites/javascriptTestSite/home/testGetChildNodes/pagecontent/getChildNodesTest/${child}`, + ) + } + + for (const child of ['filtered', 'filtered2', 'filtered3']) { + cy.get('div[data-testid="renderFilteredChildren"]').contains( + `/sites/javascriptTestSite/home/testGetChildNodes/pagecontent/getChildNodesTest/${child}`, + ) + } + + for (const child of ['child2', 'filtered']) { + cy.get('div[data-testid="renderPaginatedChildren"]').contains( + `/sites/javascriptTestSite/home/testGetChildNodes/pagecontent/getChildNodesTest/${child}`, + ) + } + + cy.logout() + }) + + it('Verify RenderChild', function () { + cy.login() + + cy.visit('/cms/render/default/en/sites/javascriptTestSite/home/testGetChildNodes.html') + + cy.get('div[data-testid="renderChild"]').contains( + `/sites/javascriptTestSite/home/testGetChildNodes/pagecontent/getChildNodesTest/child1`, + ) + + cy.logout() + }) }) diff --git a/javascript-modules-engine/tests/package.json b/javascript-modules-engine/tests/package.json index 2ec1dc17..b1f8287f 100644 --- a/javascript-modules-engine/tests/package.json +++ b/javascript-modules-engine/tests/package.json @@ -20,6 +20,7 @@ "@jahia/cypress": "^4.2.0", "@jahia/jahia-reporter": "^1.0.30", "@jahia/jcontent-cypress": "^3.0.0-tests.8", + "@types/mocha": "^10.0.10", "@types/node": "^18.11.18", "cypress": "^14.0.0", "cypress-iframe": "^1.0.1", diff --git a/javascript-modules-engine/tests/yarn.lock b/javascript-modules-engine/tests/yarn.lock index a3757db1..800efc30 100644 --- a/javascript-modules-engine/tests/yarn.lock +++ b/javascript-modules-engine/tests/yarn.lock @@ -160,6 +160,7 @@ __metadata: "@jahia/cypress": "npm:^4.2.0" "@jahia/jahia-reporter": "npm:^1.0.30" "@jahia/jcontent-cypress": "npm:^3.0.0-tests.8" + "@types/mocha": "npm:^10.0.10" "@types/node": "npm:^18.11.18" cypress: "npm:^14.0.0" cypress-iframe: "npm:^1.0.1" @@ -404,6 +405,13 @@ __metadata: languageName: node linkType: hard +"@types/mocha@npm:^10.0.10": + version: 10.0.10 + resolution: "@types/mocha@npm:10.0.10" + checksum: 10c0/d2b8c48138cde6923493e42b38e839695eb42edd04629abe480a8f34c0e3f50dd82a55832c2e8d2b6e6f9e4deb492d7d733e600fbbdd5a0ceccbcfc6844ff9d5 + languageName: node + linkType: hard + "@types/node-fetch@npm:^2.5.7": version: 2.6.1 resolution: "@types/node-fetch@npm:2.6.1" diff --git a/javascript-modules-library/README.md b/javascript-modules-library/README.md index c6f05eb4..cd30d57b 100644 --- a/javascript-modules-library/README.md +++ b/javascript-modules-library/README.md @@ -36,6 +36,22 @@ This component renders a Jahia component out of a node or a JS object. ``` +### `RenderChild` + +This component renders a child node of the current node. It's a thin wrapper around `Render` and `AddContentButtons`. + +```tsx + +``` + +### `RenderChildren` + +This component renders all children of the current node. It's a thin wrapper around `Render`, `getChildNodes` and `AddContentButtons`. + +```tsx + +``` + ## Components ### `AbsoluteArea` diff --git a/javascript-modules-library/src/core/server/components/Area.tsx b/javascript-modules-library/src/core/server/components/Area.tsx index bb8381e9..f932a385 100644 --- a/javascript-modules-library/src/core/server/components/Area.tsx +++ b/javascript-modules-library/src/core/server/components/Area.tsx @@ -37,7 +37,11 @@ export function Area({ * @default false */ readOnly?: boolean; - /** Allow area to be stored as a subnode */ + /** + * Allow area to be stored as a subnode + * + * @deprecated Use child node(s) and `` instead + */ areaAsSubNode?: boolean; /** * Content type to be used to create the area diff --git a/javascript-modules-library/src/core/server/components/render/RenderChild.tsx b/javascript-modules-library/src/core/server/components/render/RenderChild.tsx new file mode 100644 index 00000000..280f46bd --- /dev/null +++ b/javascript-modules-library/src/core/server/components/render/RenderChild.tsx @@ -0,0 +1,29 @@ +import type { JSX } from "react"; +import { Render } from "./Render.js"; +import { useServerContext } from "../../hooks/useServerContext.js"; +import { AddContentButtons } from "../AddContentButtons.js"; + +/** + * Renders a child of the current node, designated by its name. + * + * If the child node does not exist, it will display the "Add content" buttons. + */ +export function RenderChild({ + name, + view, +}: { + /** + * The name of the child node to render. + * + * In the CND file, it's what's after the `+` in the child node definition: `+ name (type)`. + */ + name: string; + /** View to use when rendering the child. */ + view?: string | undefined; +}): JSX.Element { + const { currentNode } = useServerContext(); + if (currentNode.hasNode(name)) { + return ; + } + return ; +} diff --git a/javascript-modules-library/src/core/server/components/render/RenderChildren.tsx b/javascript-modules-library/src/core/server/components/render/RenderChildren.tsx new file mode 100644 index 00000000..d8cf6ab4 --- /dev/null +++ b/javascript-modules-library/src/core/server/components/render/RenderChildren.tsx @@ -0,0 +1,58 @@ +import type { JSX } from "react"; +import { Render } from "./Render.js"; +import { useServerContext } from "../../hooks/useServerContext.js"; +import { AddContentButtons } from "../AddContentButtons.js"; +import { getChildNodes } from "../../utils/jcr/getChildNodes.js"; +import type { JCRNodeWrapper } from "org.jahia.services.content"; + +/** Renders the children of the current node, and "Add content" buttons afterwards. */ +export function RenderChildren({ + view, + pagination, + filter = "jnt:content", +}: { + /** View to use when rendering the children. */ + view?: string | undefined; + /** + * Pagination parameters: + * + * - `{ count: number; start?: number }` to specify the number of children to display and the + * starting index (defaults to 0). + * - `{ count: number; page: number }` to specify the number of children to display and the page + * number. + * + * If not provided, all children will be displayed. + */ + pagination?: { count: number; start?: number } | { count: number; page: number }; + /** + * Filter to apply to the children: + * + * - A string to filter by node type. + * - A function to filter by custom logic. + * + * @default "jnt:content" + */ + filter?: string | ((node: JCRNodeWrapper) => boolean); +}): JSX.Element { + const { currentNode } = useServerContext(); + const offset = pagination + ? "page" in pagination + ? pagination.page * pagination.count + : (pagination.start ?? 0) + : 0; + const limit = pagination ? pagination.count : -1; + + return ( + <> + {getChildNodes( + currentNode, + limit, + offset, + typeof filter === "string" ? (node) => node.isNodeType(filter) : filter, + ).map((node) => ( + + ))} + + + ); +} diff --git a/javascript-modules-library/src/core/server/utils/jcr/getChildNodes.ts b/javascript-modules-library/src/core/server/utils/jcr/getChildNodes.ts index 6d7d698d..3d680c1d 100644 --- a/javascript-modules-library/src/core/server/utils/jcr/getChildNodes.ts +++ b/javascript-modules-library/src/core/server/utils/jcr/getChildNodes.ts @@ -1,4 +1,3 @@ -import type { Node } from "javax.jcr"; import type { JCRNodeWrapper } from "org.jahia.services.content"; /** @@ -16,9 +15,9 @@ export function getChildNodes( node: JCRNodeWrapper, limit: number | undefined = undefined, offset = 0, - filter: ((node: Node) => boolean) | undefined = undefined, -): Node[] { - const result: Node[] = []; + filter: ((node: JCRNodeWrapper) => boolean) | undefined = undefined, +): JCRNodeWrapper[] { + const result: JCRNodeWrapper[] = []; if (!node || !limit) { console.warn("Missing one or more mandatory parameters (node, limit) to getChildNodes"); @@ -32,15 +31,15 @@ export function getChildNodes( // Skip nodes until reaching the offset if (skipped < offset) { - if (!filter || filter(child)) { + if (!filter || filter(child as JCRNodeWrapper)) { skipped++; } continue; } - if (!filter || filter(child)) { - result.push(child); + if (!filter || filter(child as JCRNodeWrapper)) { + result.push(child as JCRNodeWrapper); if (limit > 0 && result.length >= limit) { break; } diff --git a/javascript-modules-library/src/index.ts b/javascript-modules-library/src/index.ts index 618f9a2a..f228161d 100644 --- a/javascript-modules-library/src/index.ts +++ b/javascript-modules-library/src/index.ts @@ -2,6 +2,8 @@ export { RenderInBrowser } from "./core/server/components/render/RenderInBrowser.js"; export { HydrateInBrowser } from "./core/server/components/render/HydrateInBrowser.js"; export { Render } from "./core/server/components/render/Render.js"; +export { RenderChild } from "./core/server/components/render/RenderChild.js"; +export { RenderChildren } from "./core/server/components/render/RenderChildren.js"; // Components export { AbsoluteArea } from "./core/server/components/AbsoluteArea.js";