-
+
Absolute Area with custom page (sub-level)
+
Non editable area
-
-
Absolute area level
-
Area type
Limited absolute area editing
Absolute Area parameters
>
diff --git a/jahia-test-module/src/react/server/views/testAreaColumns/TestAreaColumns.tsx b/jahia-test-module/src/react/server/views/testAreaColumns/TestAreaColumns.tsx
index cfd0f61d..4f18cb3e 100644
--- a/jahia-test-module/src/react/server/views/testAreaColumns/TestAreaColumns.tsx
+++ b/jahia-test-module/src/react/server/views/testAreaColumns/TestAreaColumns.tsx
@@ -10,10 +10,10 @@ jahiaComponent(
return (
);
diff --git a/jahia-test-module/src/react/server/views/testAreas/TestAreas.tsx b/jahia-test-module/src/react/server/views/testAreas/TestAreas.tsx
index 3cace9af..926b4e8d 100644
--- a/jahia-test-module/src/react/server/views/testAreas/TestAreas.tsx
+++ b/jahia-test-module/src/react/server/views/testAreas/TestAreas.tsx
@@ -18,7 +18,7 @@ jahiaComponent(
Area with allowed types
Area with number of items
@@ -28,17 +28,12 @@ jahiaComponent(
Area with areaView
-
-
Area with subNodesView
-
Area with path
-
-
+
Non editable area
@@ -46,21 +41,16 @@ jahiaComponent(
-
Area as sub node
-
-
Area type
Area parameters
${junit.version}
test
+
+ pl.pragmatists
+ JUnitParams
+ 1.1.1
+ test
+
diff --git a/javascript-modules-engine-java/src/main/java/org/jahia/modules/javascript/modules/engine/js/server/RenderHelper.java b/javascript-modules-engine-java/src/main/java/org/jahia/modules/javascript/modules/engine/js/server/RenderHelper.java
index a86493f4..82245b96 100644
--- a/javascript-modules-engine-java/src/main/java/org/jahia/modules/javascript/modules/engine/js/server/RenderHelper.java
+++ b/javascript-modules-engine-java/src/main/java/org/jahia/modules/javascript/modules/engine/js/server/RenderHelper.java
@@ -49,7 +49,6 @@
import javax.servlet.jsp.tagext.TagSupport;
import java.io.IOException;
import java.io.Serializable;
-import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URLDecoder;
import java.util.*;
@@ -60,9 +59,8 @@
*/
public class RenderHelper {
private static final Logger logger = LoggerFactory.getLogger(RenderHelper.class);
-
- private static final Set ABSOLUTEAREA_ALLOWED_ATTRIBUTES = Set.of("name", "path", "areaView", "allowedTypes", "numberOfItems", "subNodesView", "editable", "areaType", "level", "limitedAbsoluteAreaEdit", "parameters");
- private static final Set AREA_ALLOWED_ATTRIBUTES = Set.of("name", "path", "areaView", "allowedTypes", "numberOfItems", "subNodesView", "editable", "areaAsSubNode", "areaType", "parameters");
+ private static final Set ABSOLUTEAREA_ALLOWED_ATTRIBUTES = Set.of("name", "parent", "view", "allowedNodeTypes", "numberOfItems", "nodeType", "editable", "areaType", "limitedAbsoluteAreaEdit", "parameters");
+ private static final Set AREA_ALLOWED_ATTRIBUTES = Set.of("name", "view", "allowedNodeTypes", "numberOfItems", "nodeType", "editable", "parameters");
private JCRSessionFactory jcrSessionFactory;
private JCRTemplate jcrTemplate;
@@ -80,6 +78,7 @@ public ProxyObject transformToJsNode(JCRNodeWrapper node, boolean includeChildre
/**
* Get the render parameters for the given resource
+ *
* @param resource the resource for which to retrieve the render parameters
* @return a Map<String,Object> containing the render parameters
*/
@@ -93,6 +92,7 @@ public ProxyObject getRenderParameters(Resource resource) {
* don't need encoding are those defined 'unreserved' in section 2.3 of
* the 'URI generic syntax' RFC 2396. Not the entire path string is escaped,
* but every individual part (i.e. the slashes are not escaped).
+ *
* @param path the path to encode
* @return a String containing the escaped path
* @throws NullPointerException if path is null.
@@ -104,9 +104,10 @@ public String escapePath(String path) {
/**
* Find the first displayable node in the given node's hierarchy. A displayable node is a node that has a content
* or page template associated with it.
- * @param node the node at which to start the resolution
+ *
+ * @param node the node at which to start the resolution
* @param renderContext the current render context
- * @param contextSite the site in which to resolve the template
+ * @param contextSite the site in which to resolve the template
* @return the first displayable {@link JCRNodeWrapper} found in the hierarchy
*/
public JCRNodeWrapper findDisplayableNode(JCRNodeWrapper node, RenderContext renderContext, @Nullable JCRSiteNode contextSite) {
@@ -115,7 +116,8 @@ public JCRNodeWrapper findDisplayableNode(JCRNodeWrapper node, RenderContext ren
/**
* Returns the node which corresponds to the bound component of the j:bindedComponent property in the specified node.
- * @param node the node to get the bound component for
+ *
+ * @param node the node to get the bound component for
* @param context current render context
* @return the bound {@link JCRNodeWrapper}
*/
@@ -132,48 +134,48 @@ public String renderComponent(Map attr, RenderContext renderContext)
return jcrTemplate.doExecuteWithSystemSessionAsUser(user, renderContext.getWorkspace(),
renderContext.getMainResource().getLocale(), session -> {
- JCRNodeWrapper node = JSNodeMapper.toVirtualNode((Map) attr.get("content"), session, renderContext);
- String renderConfig = attr.get("advanceRenderingConfig") != null ? (String) attr.get("advanceRenderingConfig") : "";
- String templateType = attr.get("templateType") != null ? (String) attr.get("templateType") : "html";
-
- if ("OPTION".equals(renderConfig)) {
- Map optionAttr = new HashMap<>();
- optionAttr.put("node", node);
- optionAttr.put("templateType", templateType);
- optionAttr.put("view", attr.get("view"));
- optionAttr.put("parameters", attr.get("parameters"));
- optionAttr.put("nodetype", node.getPrimaryNodeType().getName());
- try {
- return renderTag(new OptionTag(), optionAttr, renderContext);
- } catch (IllegalAccessException | InvocationTargetException | JspException | IOException e) {
- throw new RepositoryException(e);
- }
- } else {
- Resource r = new Resource(node, templateType, (String) attr.get("view"),
- "INCLUDE".equals(renderConfig) ? "include" : "module");
- // TODO TECH-1335 use TO_CACHE_WITH_PARENT_FRAGMENT constant once minimal jahia version >= 8.2.0.0
- r.getModuleParams().put("toCacheWithParentFragment", true);
-
- try {
- // handle render parameters for JSON rendering
- Map renderParameters = (Map) attr.get("parameters");
- if (renderParameters != null && !renderParameters.isEmpty()) {
- String charset = renderContext.getResponse().getCharacterEncoding();
- for (Map.Entry renderParam : renderParameters.entrySet()) {
- // only allow String params for compatibility reasons due to JSP ParamParent parameters being a map
- if (renderParam.getValue() instanceof String) {
- r.getModuleParams().put(URLDecoder.decode(renderParam.getKey(), charset),
- URLDecoder.decode((String) renderParam.getValue(), charset));
+ JCRNodeWrapper node = JSNodeMapper.toVirtualNode((Map) attr.get("content"), session, renderContext);
+ String renderConfig = attr.get("advanceRenderingConfig") != null ? (String) attr.get("advanceRenderingConfig") : "";
+ String templateType = attr.get("templateType") != null ? (String) attr.get("templateType") : "html";
+
+ if ("OPTION".equals(renderConfig)) {
+ Map optionAttr = new HashMap<>();
+ optionAttr.put("node", node);
+ optionAttr.put("templateType", templateType);
+ optionAttr.put("view", attr.get("view"));
+ optionAttr.put("parameters", attr.get("parameters"));
+ optionAttr.put("nodetype", node.getPrimaryNodeType().getName());
+ try {
+ return renderTag(new OptionTag(), optionAttr, renderContext);
+ } catch (IllegalAccessException | InvocationTargetException | JspException | IOException e) {
+ throw new RepositoryException(e);
+ }
+ } else {
+ Resource r = new Resource(node, templateType, (String) attr.get("view"),
+ "INCLUDE".equals(renderConfig) ? "include" : "module");
+ // TODO TECH-1335 use TO_CACHE_WITH_PARENT_FRAGMENT constant once minimal jahia version >= 8.2.0.0
+ r.getModuleParams().put("toCacheWithParentFragment", true);
+
+ try {
+ // handle render parameters for JSON rendering
+ Map renderParameters = (Map) attr.get("parameters");
+ if (renderParameters != null && !renderParameters.isEmpty()) {
+ String charset = renderContext.getResponse().getCharacterEncoding();
+ for (Map.Entry renderParam : renderParameters.entrySet()) {
+ // only allow String params for compatibility reasons due to JSP ParamParent parameters being a map
+ if (renderParam.getValue() instanceof String) {
+ r.getModuleParams().put(URLDecoder.decode(renderParam.getKey(), charset),
+ URLDecoder.decode((String) renderParam.getValue(), charset));
+ }
+ }
}
+
+ return renderService.render(r, renderContext);
+ } catch (RenderException | IOException e) {
+ throw new RepositoryException(e);
}
}
-
- return renderService.render(r, renderContext);
- } catch (RenderException | IOException e) {
- throw new RepositoryException(e);
- }
- }
- });
+ });
}
public String createContentButtons(String childName, String nodeTypes, boolean editCheck, RenderContext renderContext, Resource currentResource) throws JspException, InvocationTargetException, IllegalAccessException, RepositoryException, IOException {
@@ -227,49 +229,50 @@ public String render(Map attr, RenderContext renderContext, Reso
/**
* Render a tag that adds resources to the page. Resources might for example be CSS files, Javascript files or inline
- * @param attr may contain the following:
- *
- * - insert (boolean) : If true, the resource will be inserted into the document. Typically used
- * for on-demand loading of resources.
- * - async (boolean) : If true, the resource will be loaded asynchronously. For scripts, this means
- * the script
- * will be executed as soon as it's available, without blocking the rest of the page.
- * - defer (boolean) : If true, the resource will be deferred, i.e., loaded after the document
- * has been parsed.
- * For scripts, this means the script will not be executed until after the page has loaded.
- * - type (string) : The type of the resource. This could be 'javascript' for .js files, 'css' for
- * .css files, etc.
- * The type will be used to resolve the directory in the module where the resources are located. For example
- * for the 'css' type it will look for the resources in the css directory of the module.
- * - resources (string) : The path to the resource file, relative to the module. It is also allowed to
- * specify multiple resources by separating them with commas. It is also allowed to use absolute URLs to
- * include remote resources.
- * - inlineResource (string) : Inline HTML that markup will be considered as a resource.
- * - title (string) : The title of the resource. This is typically not used for scripts or stylesheets,
- * but may be used for other types of resources.
- * - key (string) : A unique key for the resource. This could be used to prevent duplicate resources
- * from being added to the document.
- * - targetTag (string): The HTML tag where the resource should be added. This could be 'head' for
- * resources that should be added to the <head> tag, 'body' for resources that should be added to
- * the <body> tag, etc.
- * - rel (string) : The relationship of the resource to the document. This is typically 'stylesheet'
- * for CSS files.
- * - media (string) : The media for which the resource is intended. This is typically used for CSS
- * files, with values like 'screen', 'print', etc.
- * - condition (string) : A condition that must be met for the resource to be loaded. This could be
- * used for conditional comments in IE, for example.
- *
+ *
+ * @param attr may contain the following:
+ *
+ * - insert (boolean) : If true, the resource will be inserted into the document. Typically used
+ * for on-demand loading of resources.
+ * - async (boolean) : If true, the resource will be loaded asynchronously. For scripts, this means
+ * the script
+ * will be executed as soon as it's available, without blocking the rest of the page.
+ * - defer (boolean) : If true, the resource will be deferred, i.e., loaded after the document
+ * has been parsed.
+ * For scripts, this means the script will not be executed until after the page has loaded.
+ * - type (string) : The type of the resource. This could be 'javascript' for .js files, 'css' for
+ * .css files, etc.
+ * The type will be used to resolve the directory in the module where the resources are located. For example
+ * for the 'css' type it will look for the resources in the css directory of the module.
+ * - resources (string) : The path to the resource file, relative to the module. It is also allowed to
+ * specify multiple resources by separating them with commas. It is also allowed to use absolute URLs to
+ * include remote resources.
+ * - inlineResource (string) : Inline HTML that markup will be considered as a resource.
+ * - title (string) : The title of the resource. This is typically not used for scripts or stylesheets,
+ * but may be used for other types of resources.
+ * - key (string) : A unique key for the resource. This could be used to prevent duplicate resources
+ * from being added to the document.
+ * - targetTag (string): The HTML tag where the resource should be added. This could be 'head' for
+ * resources that should be added to the <head> tag, 'body' for resources that should be added to
+ * the <body> tag, etc.
+ * - rel (string) : The relationship of the resource to the document. This is typically 'stylesheet'
+ * for CSS files.
+ * - media (string) : The media for which the resource is intended. This is typically used for CSS
+ * files, with values like 'screen', 'print', etc.
+ * - condition (string) : A condition that must be met for the resource to be loaded. This could be
+ * used for conditional comments in IE, for example.
+ *
* @param renderContext the current rendering context
* @return a String containing the rendered HTML tags for the provided resources.
- * @throws IllegalAccessException if the underlying tag cannot be accessed
+ * @throws IllegalAccessException if the underlying tag cannot be accessed
* @throws InvocationTargetException if the underlying tag cannot be invoked
- * @throws JspException if the underlying tag throws a JSP exception
- * @throws IOException if the underlying tag throws an IO exception
+ * @throws JspException if the underlying tag throws a JSP exception
+ * @throws IOException if the underlying tag throws an IO exception
*/
public String addResources(Map attr, RenderContext renderContext) throws IllegalAccessException, InvocationTargetException, JspException, IOException {
Map ressourcesAttrs = new HashMap<>(attr);
- if (attr.containsKey("inlineResource")){
+ if (attr.containsKey("inlineResource")) {
ressourcesAttrs.put("body", attr.get("inlineResource").toString());
}
return renderTag(new AddResourcesTag(), ressourcesAttrs, renderContext);
@@ -278,61 +281,105 @@ public String addResources(Map attr, RenderContext renderContext
/**
* Add a cache dependency to the current resource. This will be used to flush the current resource when the
* dependencies are modified.
- * @param attr may be the following:
- *
- * - node (JCRNodeWrapper) : The node to add as a dependency.
- * - uuid (String) : The UUID of the node to add as a dependency.
- * - path (String) : The path of the node to add as a dependency.
- * - flushOnPathMatchingRegexp (String) : A regular expression that will be used to flush the cache
- * when the path of the modified nodes matches the regular expression.
- *
+ *
+ * @param attr may be the following:
+ *
+ * - node (JCRNodeWrapper) : The node to add as a dependency.
+ * - uuid (String) : The UUID of the node to add as a dependency.
+ * - path (String) : The path of the node to add as a dependency.
+ * - flushOnPathMatchingRegexp (String) : A regular expression that will be used to flush the cache
+ * when the path of the modified nodes matches the regular expression.
+ *
* @param renderContext the current rendering context
- * @throws IllegalAccessException if the underlying tag cannot be accessed
+ * @throws IllegalAccessException if the underlying tag cannot be accessed
* @throws InvocationTargetException if the underlying tag cannot be invoked
- * @throws JspException if the underlying tag throws a JSP exception
- * @throws IOException if the underlying tag throws an IO exception
+ * @throws JspException if the underlying tag throws a JSP exception
+ * @throws IOException if the underlying tag throws an IO exception
*/
public void addCacheDependency(Map attr, RenderContext renderContext) throws IllegalAccessException, InvocationTargetException, JspException, IOException {
renderTag(new AddCacheDependencyTag(), attr, renderContext);
}
+ /**
+ * Calculate the relative path from one node to another node.
+ *
+ * @param currentPath the path of the current node from which to calculate the relative path
+ * @param basePath the path of the base node to which to calculate the relative path
+ * @return the relative path from currentPath to basePath
+ */
+ static String calculateRelativePath(String currentPath, String basePath) {
+ // special case when the parent is the current node
+ if (currentPath.equals(basePath)) {
+ return ".";
+ }
+ String[] currentPathParts = currentPath.split("/");
+ String[] parentPathParts = basePath.split("/");
+
+ // Find the common prefix length
+ int commonLength = 0;
+ while (commonLength < currentPathParts.length && commonLength < parentPathParts.length
+ && currentPathParts[commonLength].equals(parentPathParts[commonLength])) {
+ commonLength++;
+ }
+
+ // Calculate the number of steps to go up from the current node to the common prefix
+ int stepsUp = currentPathParts.length - commonLength;
+
+ // Build the relative path
+ StringBuilder relativePath = new StringBuilder();
+ for (int i = 0; i < stepsUp; i++) {
+ relativePath.append("../");
+ }
+ for (int i = commonLength; i < parentPathParts.length; i++) {
+ relativePath.append(parentPathParts[i]);
+ if (i < parentPathParts.length - 1) {
+ relativePath.append("/");
+ }
+ }
+
+ return relativePath.toString();
+ }
+
+ private static T readMandatoryAttribute(Map attr, String attributeName) {
+ T name = (T) attr.remove(attributeName);
+ if (name == null) {
+ throw new IllegalStateException(String.format("Attribute '%s' is required", attributeName));
+ }
+ return name;
+ }
+
public String renderAbsoluteArea(Map attr, RenderContext renderContext) throws JspException, InvocationTargetException, IllegalAccessException, IOException {
checkAttributes(attr, ABSOLUTEAREA_ALLOWED_ATTRIBUTES);
+ // path handling
+ String name = readMandatoryAttribute(attr, "name");
+ JCRNodeWrapper parent = readMandatoryAttribute(attr, "parent");
+ String relativePath = calculateRelativePath(renderContext.getSite().getPath(), parent.getPath());
+ attr.put("path", relativePath + "/" + name);
return internalRenderArea(attr, "absoluteArea", renderContext);
}
public String renderArea(Map attr, RenderContext renderContext) throws IllegalAccessException, InvocationTargetException, JspException, IOException {
checkAttributes(attr, AREA_ALLOWED_ATTRIBUTES);
+ String name = readMandatoryAttribute(attr, "name");
+ attr.put("path", renderContext.getMainResource().getNode().getPath() + "/" + name);
return internalRenderArea(attr, "area", renderContext);
}
private String internalRenderArea(Map attr, String moduleType, RenderContext renderContext) throws IllegalAccessException, InvocationTargetException, JspException, IOException {
// We copy the attributes to avoid modifying the original map
- Map areaAttr = new HashMap<>(attr);
+ Map areaAttr = new HashMap<>(attr);
areaAttr.put("moduleType", moduleType);
// We now have to transform the attr properties into something that the AreaTag can understand
- if (areaAttr.get("path") != null && areaAttr.get("name") != null) {
- logger.warn("Both path and name are set on the area [ " + areaAttr.get("name") + "] tag, name will be ignored");
- areaAttr.remove("name");
- } else {
- if (areaAttr.get("name") != null) {
- areaAttr.put("path", areaAttr.get("name"));
- areaAttr.remove("name");
- }
- }
- if (areaAttr.get("areaView") != null) {
- areaAttr.put("view", areaAttr.get("areaView"));
- areaAttr.remove("areaView");
- }
- Object allowedTypes = areaAttr.get("allowedTypes");
+ Object allowedTypes = areaAttr.get("allowedNodeTypes");
if (allowedTypes != null) {
if (allowedTypes instanceof List) {
areaAttr.put("nodeTypes", StringUtils.join((List) allowedTypes, ' '));
- areaAttr.remove("allowedTypes");
- } if (allowedTypes.getClass().isArray()) {
+ areaAttr.remove("allowedNodeTypes");
+ }
+ if (allowedTypes.getClass().isArray()) {
Object[] allowedTypeArray = (Object[]) allowedTypes;
areaAttr.put("nodeTypes", StringUtils.join(allowedTypeArray, ' '));
- areaAttr.remove("allowedTypes");
+ areaAttr.remove("allowedNodeTypes");
}
}
@@ -340,21 +387,16 @@ private String internalRenderArea(Map attr, String moduleType, R
areaAttr.put("listLimit", areaAttr.get("numberOfItems"));
areaAttr.remove("numberOfItems");
}
-
- AreaTag areaTag = new AreaTag();
- // The subNodesView is an attribute that is passed as a tag parameter
- if (areaAttr.get("subNodesView") != null) {
- areaTag.addParameter("subNodesView", (String) areaAttr.get("subNodesView"));
- areaAttr.remove("subNodesView");
- }
+ areaAttr.put("areaType", areaAttr.remove("nodeType"));
+ areaAttr.put("level", -1); // force the "path" parameter to be computed against the root node of the site (ex: /sites/)
// Now we remove any null attribute to make sure they don't override default tag attributes
areaAttr = cleanupNullValues(areaAttr);
- return renderTag(areaTag, areaAttr, renderContext);
+ return renderTag(new AreaTag(), areaAttr, renderContext);
}
- private Map cleanupNullValues(Map mapToClean) {
+ private Map cleanupNullValues(Map mapToClean) {
return mapToClean.entrySet().stream()
.filter(entry -> entry.getValue() != null)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
@@ -372,8 +414,8 @@ private String renderTag(TagSupport tag, Map attr, RenderContext
}
String body = null;
- if (tag instanceof BodyTagSupport && attr.get("body") != null) {
- body = (String) attr.get("body");
+ if (tag instanceof BodyTagSupport && attr.get("body") != null) {
+ body = (String) attr.get("body");
}
safePopulate(tag, attr);
@@ -420,7 +462,7 @@ private void safePopulate(Object tag, Map attr) {
for (Map.Entry entry : attr.entrySet()) {
try {
BeanUtils.setProperty(tag, entry.getKey(), entry.getValue());
- } catch (Throwable e) {
+ } catch (Throwable e) {
// Ignore any errors
}
}
@@ -444,7 +486,7 @@ public void setRenderService(RenderService renderService) {
this.renderService = renderService;
}
- private void checkAttributes(Map attributes, Set allowedAttributes) {
+ private void checkAttributes(Map attributes, Set allowedAttributes) {
for (String attr : attributes.keySet()) {
if (!allowedAttributes.contains(attr)) {
throw new IllegalArgumentException("Attribute " + attr + " is not allowed");
diff --git a/javascript-modules-engine-java/src/test/java/org/jahia/modules/javascript/modules/engine/js/server/RenderHelperTest.java b/javascript-modules-engine-java/src/test/java/org/jahia/modules/javascript/modules/engine/js/server/RenderHelperTest.java
new file mode 100644
index 00000000..2687f198
--- /dev/null
+++ b/javascript-modules-engine-java/src/test/java/org/jahia/modules/javascript/modules/engine/js/server/RenderHelperTest.java
@@ -0,0 +1,31 @@
+package org.jahia.modules.javascript.modules.engine.js.server;
+
+import junit.framework.TestCase;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(JUnitParamsRunner.class)
+public class RenderHelperTest extends TestCase {
+
+ @Test
+ @Parameters({
+ // siblings:
+ "/home, /about, ../about",
+ "/sites/mysite/home, /sites/mysite/about, ../about",
+ // identical:
+ "/home, /home, .",
+ // parents:
+ "/home/sub-level-1, /home, ../",
+ "/home/sub-level-1/sub-level-2, /home, ../../",
+ "/home/sub-level-1/sub-level-2, /home/sub-level-1, ../",
+ // children:
+ "/home, /home/sub-level-1, sub-level-1",
+ "/home, /home/sub-level-1, sub-level-1",
+ "/home, /home/sub-level-1/sub-level-2, sub-level-1/sub-level-2"
+ })
+ public void testCalculateRelativePath(String currentPath, String basePath, String expectedRelativePath) {
+ assertEquals(expectedRelativePath, RenderHelper.calculateRelativePath(currentPath, basePath));
+ }
+}
\ No newline at end of file
diff --git a/javascript-modules-engine/tests/cypress.config.ts b/javascript-modules-engine/tests/cypress.config.ts
index 345eabb2..1f60d892 100644
--- a/javascript-modules-engine/tests/cypress.config.ts
+++ b/javascript-modules-engine/tests/cypress.config.ts
@@ -3,6 +3,7 @@ import { defineConfig } from 'cypress'
export default defineConfig({
chromeWebSecurity: false,
failOnStatusCode: false,
+ watchForFileChanges: false,
defaultCommandTimeout: 30000,
videoUploadOnPasses: false,
reporter: 'cypress-multi-reporters',
diff --git a/javascript-modules-engine/tests/cypress/e2e/ui/absoluteAreaTest.cy.ts b/javascript-modules-engine/tests/cypress/e2e/ui/absoluteAreaTest.cy.ts
index 4e74472e..2954b412 100644
--- a/javascript-modules-engine/tests/cypress/e2e/ui/absoluteAreaTest.cy.ts
+++ b/javascript-modules-engine/tests/cypress/e2e/ui/absoluteAreaTest.cy.ts
@@ -52,13 +52,21 @@ describe('Absolute Area test', () => {
name: 'pagecontent',
primaryNodeType: 'jnt:contentList',
},
- ]).then(() => {
- addNode({
- parentPathOrId: `/sites/javascriptTestSite/home/${pageName}/pagecontent`,
- name: 'test',
- primaryNodeType: 'javascriptExample:testAbsoluteAreas',
- })
+ ])
+ addNode({
+ parentPathOrId: `/sites/javascriptTestSite/home/${pageName}/pagecontent`,
+ name: 'test',
+ primaryNodeType: 'javascriptExample:testAbsoluteAreas',
+ })
+ addNode({
+ parentPathOrId: `/sites/javascriptTestSite/home/${pageName}/pagecontent/test`,
+ // the content node is expected to exist in TestAbsolutArea
+ name: 'basicArea',
+ primaryNodeType: 'jnt:contentList',
})
+ addSimplePage('/sites/javascriptTestSite', 'custom', 'Custom', 'en', 'simple').then(() =>
+ addSimplePage('/sites/javascriptTestSite/custom', 'sub-level', 'Sub level', 'en', 'simple'),
+ )
})
it(`${pageName}: Basic Area test`, () => {
@@ -117,35 +125,20 @@ describe('Absolute Area test', () => {
cy.logout()
})
- it(`${pageName}: subNodesView Area`, () => {
+ it(`${pageName}: absolute Area home page`, () => {
cy.login()
cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`)
- addNode({
- parentPathOrId: `/sites/javascriptTestSite/home/${pageName}/subNodesViewArea`,
- name: 'item1',
- primaryNodeType: 'jnt:bigText',
- })
- cy.reload()
cy.iframe('#page-builder-frame-1').within(() => {
- cy.get('div[data-testid="subNodesViewArea"]').find('a').contains('item1')
+ cy.get('div[data-testid="absoluteAreaHomePage"] div[data-testid="row-twoColumns"]').should('exist')
})
cy.logout()
})
- it(`${pageName}: path Area`, () => {
+ it(`${pageName}: absolute Area custom page (sub-level)`, () => {
cy.login()
cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`)
cy.iframe('#page-builder-frame-1').within(() => {
- cy.get('div[data-testid="pathArea"]').find('div[type="absoluteArea"]').should('exist')
- })
- cy.logout()
- })
-
- it(`${pageName}: absolute Area`, () => {
- cy.login()
- cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`)
- cy.iframe('#page-builder-frame-1').within(() => {
- cy.get('div[data-testid="absoluteArea"]').find('div[data-testid="row-twoColumns"]').should('exist')
+ cy.get('div[data-testid="absoluteAreaCustomPage"]').find('div[type="absoluteArea"]').should('be.visible')
})
cy.logout()
})
@@ -159,15 +152,6 @@ describe('Absolute Area test', () => {
cy.logout()
})
- it(`${pageName}: absolute Area level`, () => {
- cy.login()
- cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`)
- cy.iframe('#page-builder-frame-1').within(() => {
- cy.get('div[data-testid="absoluteAreaLevel"]').find('div[data-testid="row-twoColumns"]').should('exist')
- })
- cy.logout()
- })
-
it(`${pageName}: Area type`, () => {
cy.login()
cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`)
diff --git a/javascript-modules-engine/tests/cypress/e2e/ui/areaTest.cy.ts b/javascript-modules-engine/tests/cypress/e2e/ui/areaTest.cy.ts
index 75f24bfb..df28870a 100644
--- a/javascript-modules-engine/tests/cypress/e2e/ui/areaTest.cy.ts
+++ b/javascript-modules-engine/tests/cypress/e2e/ui/areaTest.cy.ts
@@ -78,26 +78,11 @@ describe('Area test', () => {
cy.logout()
})
- it(`${pageName}: subNodesView Area`, () => {
- cy.login()
- cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`)
- addNode({
- parentPathOrId: `/sites/javascriptTestSite/home/${pageName}/subNodesViewArea`,
- name: 'item1',
- primaryNodeType: 'jnt:bigText',
- })
- cy.reload()
- cy.iframe('#page-builder-frame-1').within(() => {
- cy.get('div[data-testid="subNodesViewArea"]').find('a').contains('item1')
- })
- cy.logout()
- })
-
it(`${pageName}: path Area`, () => {
cy.login()
cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`)
cy.iframe('#page-builder-frame-1').within(() => {
- cy.get('div[data-testid="pathArea"]').find('div[type="area"]').should('exist')
+ cy.get('div[data-testid="parentArea"]').find('div[type="area"]').should('exist')
})
cy.logout()
})
@@ -111,18 +96,6 @@ describe('Area test', () => {
cy.logout()
})
- it(`${pageName}: Area as sub node`, () => {
- cy.login()
- cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`)
- cy.iframe('#page-builder-frame-1').within(() => {
- cy.get('div[data-testid="areaAsSubNode"]')
- .find('div[type="area"]')
- .should('have.attr', 'path')
- .and('match', /\/pagecontent\/test\/areaAsSubNode$/)
- })
- cy.logout()
- })
-
it(`${pageName}: Area type`, () => {
cy.login()
cy.visit(`/jahia/jcontent/javascriptTestSite/en/pages/home/${pageName}`)
diff --git a/javascript-modules-library/README.md b/javascript-modules-library/README.md
index ca438ae7..4ed3c9f8 100644
--- a/javascript-modules-library/README.md
+++ b/javascript-modules-library/README.md
@@ -56,10 +56,10 @@ This component renders all children of the current node. It's a thin wrapper aro
### `AbsoluteArea`
-This component creates an absolute area in the page. It's an area of user-contributable content that is synchronized between all pages where it is present.
+This component creates an absolute area in the page. It's an area of user-contributable content, child of the node of your choice.
```tsx
-
+
```
### `Area`
diff --git a/javascript-modules-library/src/core/server/components/AbsoluteArea.tsx b/javascript-modules-library/src/core/server/components/AbsoluteArea.tsx
index b4784402..758b11e8 100644
--- a/javascript-modules-library/src/core/server/components/AbsoluteArea.tsx
+++ b/javascript-modules-library/src/core/server/components/AbsoluteArea.tsx
@@ -1,6 +1,7 @@
import type React from "react";
import { useServerContext } from "../hooks/useServerContext.js";
import server from "virtual:jahia-server";
+import type { JCRNodeWrapper } from "org.jahia.services.content";
/**
* Generates an absolute area in which editors may insert content objects.
@@ -9,28 +10,41 @@ import server from "virtual:jahia-server";
*/
export function AbsoluteArea({
name,
+ parent,
areaView,
+ view,
allowedTypes,
+ allowedNodeTypes,
numberOfItems,
- subNodesView,
- path,
readOnly = false,
- level,
areaType = "jnt:contentList",
+ nodeType = "jnt:contentList",
parameters,
}: Readonly<{
/** The name of the area. */
- name?: string;
- /** The view to use for the area. */
+ name: string;
+ /** Parent node where the area is stored in the JCR. The parent node must exist. */
+ parent: JCRNodeWrapper;
+
+ /**
+ * The view to use for the area.
+ *
+ * @deprecated Use {@link #view} instead
+ */
areaView?: string;
- /** The allowed types for the area. */
+ /** The view to use for the area. */
+ view?: string;
+ /**
+ * The allowed types for the area.
+ *
+ * @deprecated Use {@link #allowedNodeTypes} instead
+ */
allowedTypes?: string[];
+ /** The allowed types for the area. */
+ allowedNodeTypes?: string[];
/** The number of items to display in the area. */
numberOfItems?: number;
- /** The view to use for the subnodes. */
- subNodesView?: string;
- /** Relative (to the current node) or absolute path to the node to include. */
- path?: string;
+
/**
* Makes the area read-only.
*
@@ -42,15 +56,24 @@ export function AbsoluteArea({
* @default false
*/
readOnly?: boolean | "children";
- /** Ancestor level for absolute area - 0 is Home page, 1 first sub-pages, ... */
- level?: number;
/**
- * Content type to be used to create the area
+ * Content node type to be used to create the area (if the node does not exist yet)
*
+ * @deprecated Use {@link #nodeType} instead
* @default jnt:contentList
*/
areaType?: string;
- /** The parameters to pass to the absolute area */
+ /**
+ * Content node type to be used to create the area (if the node does not exist yet)
+ *
+ * @default jnt:contentList
+ */
+ nodeType?: string;
+ /**
+ * Map of custom parameters that can be passed to the backend engine for advanced logic.
+ *
+ * @deprecated Not recommended
+ */
parameters?: Record;
}>): React.JSX.Element {
const { renderContext } = useServerContext();
@@ -61,14 +84,12 @@ export function AbsoluteArea({
__html: server.render.renderAbsoluteArea(
{
name,
- areaView,
- allowedTypes,
+ parent: parent,
+ view: view ? view : areaView,
+ allowedNodeTypes: allowedNodeTypes ?? allowedTypes,
numberOfItems,
- subNodesView,
- path,
+ nodeType: nodeType ?? areaType,
editable: readOnly !== true,
- level,
- areaType,
limitedAbsoluteAreaEdit: readOnly === "children",
parameters,
},
diff --git a/javascript-modules-library/src/core/server/components/Area.tsx b/javascript-modules-library/src/core/server/components/Area.tsx
index f932a385..3a544d0a 100644
--- a/javascript-modules-library/src/core/server/components/Area.tsx
+++ b/javascript-modules-library/src/core/server/components/Area.tsx
@@ -10,46 +10,62 @@ import server from "virtual:jahia-server";
export function Area({
name,
areaView,
+ view,
allowedTypes,
+ allowedNodeTypes,
numberOfItems,
- subNodesView,
- path,
readOnly = false,
- areaAsSubNode,
areaType = "jnt:contentList",
+ nodeType = "jnt:contentList",
parameters,
}: Readonly<{
/** The name of the area. */
- name?: string;
- /** The view to use for the area. */
+ name: string;
+
+ /**
+ * The view to use for the area.
+ *
+ * @deprecated Use {@link #view} instead
+ */
areaView?: string;
- /** The allowed types for the area. */
+ /** The view to use for the area. */
+ view?: string;
+ /**
+ * The allowed types for the area.
+ *
+ * @deprecated Use {@link #allowedNodeTypes} instead
+ */
allowedTypes?: string[];
+ /** The allowed types for the area. */
+ allowedNodeTypes?: string[];
/** The number of items to display in the area. */
numberOfItems?: number;
- /** The view to use for the subnodes. */
- subNodesView?: string;
- /** Relative (to the current node) or absolute path to the node to include. */
- path?: string;
+
/**
* Makes the area read-only.
*
* @default false
*/
readOnly?: boolean;
+
/**
- * Allow area to be stored as a subnode
+ * Content node type to be used to create the area (if the node does not exist yet)
*
- * @deprecated Use child node(s) and `` instead
+ * @deprecated Use {@link #nodeType} instead
+ * @default jnt:contentList
*/
- areaAsSubNode?: boolean;
+ areaType?: string;
/**
- * Content type to be used to create the area
+ * Content node type to be used to create the area (if the node does not exist yet)
*
* @default jnt:contentList
*/
- areaType?: string;
- /** The parameters to pass to the area */
+ nodeType?: string;
+ /**
+ * Map of custom parameters that can be passed to the backend engine for advanced logic.
+ *
+ * @deprecated Not recommended
+ */
parameters?: Record;
}>): JSX.Element {
const { renderContext } = useServerContext();
@@ -60,14 +76,11 @@ export function Area({
__html: server.render.renderArea(
{
name,
- areaView,
- allowedTypes,
+ view: view ?? areaView,
+ allowedNodeTypes: allowedNodeTypes ?? allowedTypes,
numberOfItems,
- subNodesView,
- path,
+ nodeType: nodeType ?? areaType,
editable: !readOnly,
- areaAsSubNode,
- areaType,
parameters,
},
renderContext,