You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by nm...@apache.org on 2021/01/12 16:51:39 UTC

[ofbiz-framework] branch trunk updated: Implemented: Access to the current screen from any widget element (OFBIZ-11809)

This is an automated email from the ASF dual-hosted git repository.

nmalin pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git


The following commit(s) were added to refs/heads/trunk by this push:
     new e0a840a  Implemented: Access to the current screen from any widget element (OFBIZ-11809)
e0a840a is described below

commit e0a840a0c4928080f7a3e3bdf3a42e327483939c
Author: Nicolas Malin <ni...@nereide.fr>
AuthorDate: Tue Jan 12 17:47:51 2021 +0100

    Implemented: Access to the current screen from any widget element
    (OFBIZ-11809)
    
    During the rendering process, an element have no information about the screen which is dependent from. This not really help to automate some refresh treatment and force the developer to know the screen structure where is the element.
    
    To solve this situation we implement a new class, ScreenStack that allow anytime for any element to identify its direct dependency. This help to resolve the unique Id that the screen take and can be used by the theme.
    
    At any time a developer can retrieve the screen reference with uel function screen:id(screenStack)
    
    example:
         <container id="${screen:id(screenStack)}">
    
    The object screenStack is always present during a rendering process.
---
 .../ofbiz/base/util/string/UelFunctions.java       | 15 +++++
 .../java/org/apache/ofbiz/widget/WidgetWorker.java | 14 ++++
 .../org/apache/ofbiz/widget/model/ModelScreen.java |  2 +
 .../ofbiz/widget/renderer/ScreenRenderer.java      | 74 ++++++++++++++++++++++
 .../widget/renderer/ScreenStringRenderer.java      |  7 +-
 .../widget/renderer/macro/MacroScreenRenderer.java | 48 +++++++++-----
 .../renderer/macro/MacroScreenViewHandler.java     |  4 +-
 .../template/macro/CsvScreenMacroLibrary.ftl       | 16 ++---
 .../template/macro/FoScreenMacroLibrary.ftl        |  8 ++-
 .../template/macro/HtmlScreenMacroLibrary.ftl      |  8 ++-
 .../template/macro/TextScreenMacroLibrary.ftl      | 10 +--
 .../template/macro/XlsScreenMacroLibrary.ftl       |  4 ++
 .../template/macro/XmlScreenMacroLibrary.ftl       | 10 ++-
 13 files changed, 185 insertions(+), 35 deletions(-)

diff --git a/framework/base/src/main/java/org/apache/ofbiz/base/util/string/UelFunctions.java b/framework/base/src/main/java/org/apache/ofbiz/base/util/string/UelFunctions.java
index 9d6ffd2..62b8e8a 100644
--- a/framework/base/src/main/java/org/apache/ofbiz/base/util/string/UelFunctions.java
+++ b/framework/base/src/main/java/org/apache/ofbiz/base/util/string/UelFunctions.java
@@ -48,6 +48,7 @@ import org.apache.ofbiz.base.util.FileUtil;
 import org.apache.ofbiz.base.util.UtilDateTime;
 import org.apache.ofbiz.base.util.UtilProperties;
 import org.apache.ofbiz.base.util.UtilXml;
+import org.apache.ofbiz.widget.renderer.ScreenRenderer;
 import org.cyberneko.html.parsers.DOMParser;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
@@ -179,6 +180,7 @@ import org.xml.sax.SAXException;
  * <tr><td><code>util:defaultTimeZone()</code></td><td>Returns the default <code>TimeZone</code>.</td></tr>
  * <tr><td><code>util:label(String, String, Locale)</code></td><td>Return the label present in ressource on the given locale.</td></tr>
  * <tr><td><code>util:size(Object)</code></td><td>Returns the size of <code>Maps</code>,
+ * <tr><td><code>screen:id(ScreenStack)</code></td><td>Returns the id of the current screen,
  * <code>Collections</code>, and <code>Strings</code>. Invalid <code>Object</code> types return -1.</td></tr>
  * <tr><td><code>util:urlExists(String)</code></td><td>Returns <code>true</code> if the specified URL exists.</td></tr>
  * <tr><td colspan="2"><b><code>dom:</code> contains <code>org.w3c.dom.*</code> functions</b></td></tr>
@@ -347,6 +349,18 @@ public class UelFunctions {
         return label;
     }
 
+    /**
+     * Returns the id of the current screen identified on the screen stack
+     * @param screenStack
+     * @return
+     */
+    public static String resolveCurrentScreenId(ScreenRenderer.ScreenStack screenStack) {
+        if (screenStack != null) {
+            return screenStack.resolveCurrentScreenId();
+        }
+        return null;
+    }
+
     public static Document readHtmlDocument(String str) {
         Document document = null;
         try {
@@ -536,6 +550,7 @@ public class UelFunctions {
                 this.functionMap.put("util:defaultLocale", Locale.class.getMethod("getDefault"));
                 this.functionMap.put("util:defaultTimeZone", TimeZone.class.getMethod("getDefault"));
                 this.functionMap.put("util:label", UelFunctions.class.getMethod("label", String.class, String.class, Locale.class));
+                this.functionMap.put("screen:id", UelFunctions.class.getMethod("resolveCurrentScreenId", ScreenRenderer.ScreenStack.class));
                 this.functionMap.put("dom:readHtmlDocument", UelFunctions.class.getMethod("readHtmlDocument", String.class));
                 this.functionMap.put("dom:readXmlDocument", UelFunctions.class.getMethod("readXmlDocument", String.class));
                 this.functionMap.put("dom:toHtmlString", UelFunctions.class.getMethod("toHtmlString", Node.class, String.class, boolean.class,
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/WidgetWorker.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/WidgetWorker.java
index c1d7bb4..57a3e3b 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/WidgetWorker.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/WidgetWorker.java
@@ -37,6 +37,7 @@ import org.apache.ofbiz.webapp.control.RequestHandler;
 import org.apache.ofbiz.webapp.taglib.ContentUrlTag;
 import org.apache.ofbiz.widget.model.ModelForm;
 import org.apache.ofbiz.widget.model.ModelFormField;
+import org.apache.ofbiz.widget.renderer.ScreenRenderer;
 import org.jsoup.nodes.Element;
 import org.jsoup.nodes.FormElement;
 import org.jsoup.parser.Parser;
@@ -256,6 +257,19 @@ public final class WidgetWorker {
         return combinedName.substring(pos + 1);
     }
 
+    /**
+     * Returns the ScreenStack from the context.
+     * If none, init new one and return it.
+     * @param context
+     * @return
+     */
+    public static ScreenRenderer.ScreenStack getScreenStack(Map<String, Object> context) {
+        if (! context.containsKey("screenStack")) {
+            context.put("screenStack", new ScreenRenderer.ScreenStack());
+        }
+        return (ScreenRenderer.ScreenStack) context.get("screenStack");
+    }
+
     public static int getPaginatorNumber(Map<String, Object> context) {
         int paginatorNumber = 0;
         if (context != null) {
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreen.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreen.java
index 057e7b8..136c7df 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreen.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreen.java
@@ -154,7 +154,9 @@ public class ModelScreen extends ModelWidget {
             }
 
             // render the screen, starting with the top-level section
+            screenStringRenderer.renderScreenBegin(writer, context, this);
             this.section.renderWidgetString(writer, context, screenStringRenderer);
+            screenStringRenderer.renderScreenEnd(writer, context, this);
             TransactionUtil.commit(beganTransaction);
         } catch (RuntimeException e) {
             throw e;
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenRenderer.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenRenderer.java
index bcb1118..64a518d 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenRenderer.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenRenderer.java
@@ -28,6 +28,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.Stack;
 
+import java.util.UUID;
 import javax.servlet.ServletContext;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -185,6 +186,9 @@ public class ScreenRenderer {
         // include an object to more easily render screens
         context.put("screens", screens);
 
+        // include an object to follow the screen stack during the screen rendering process
+        context.put("screenStack", new ScreenRenderer.ScreenStack());
+
         // make a reference for high level variables, a global context
         context.put("globalContext", context.standAloneStack());
 
@@ -376,4 +380,74 @@ public class ScreenRenderer {
         this.populateBasicContext(serviceContext, dctx.getDelegator(), dctx.getDispatcher(),
                 dctx.getSecurity(), (Locale) serviceContext.get("locale"), (GenericValue) serviceContext.get("userLogin"));
     }
+
+    /**
+     * Contains the stack of screen area ids that are generated during screen rendering
+     * This allow inherent refreshment of the parent screen, when using callback feature
+     * */
+    public static class ScreenStack {
+        LinkedList<Map<String, Object>> visitedScreens;
+
+        public ScreenStack() {
+            visitedScreens = new LinkedList<>();
+        }
+
+        /**
+         * Push a screen id upon the stack
+         * @param modelScreen
+         */
+        public void push(ModelScreen modelScreen) {
+            if (modelScreen != null) {
+                Map<String, Object> screenAreaAssociation = UtilMisc.toMap(
+                        "modelScreen", modelScreen,
+                        "areaId", modelScreen.getSection().getName() + UUID.randomUUID().toString());
+                 visitedScreens.addLast(screenAreaAssociation);
+            }
+        }
+
+        /**
+         * Remove the last visited screen from the stack
+         */
+        public void drop() {
+            visitedScreens.removeLast();
+        }
+
+        /**
+         * Return a map with the modelScreen and the unique areaId related to the current screen from the stack
+         *
+         * @return ["modelScreen": {@link ModelScreen}, "areaId": {@link String}]
+         */
+        private Map<String, Object> resolveCurrentScreenMap() {
+            return visitedScreens.getLast();
+        }
+
+        /**
+         * Return the {@link ModelScreen} of the current screen from the stack
+         *
+         * @return {@link ModelScreen}
+         */
+        public ModelScreen resolveCurrentModelScreen() {
+            if (visitedScreens.isEmpty()) return null;
+            return (ModelScreen) resolveCurrentScreenMap().get("modelScreen");
+        }
+
+        /**
+         * Return the area id reference of the current screen on the screen stack
+         * @return
+         */
+        public String resolveCurrentScreenId() {
+            if (visitedScreens.isEmpty()) return null;
+            return (String) resolveCurrentScreenMap().get("areaId");
+        }
+
+        /**
+         * If the given areaId have not consistency, return the current screen
+         * area id on the stack
+         * @return
+         */
+        public String resolveScreenAreaId(String areaId) {
+            if (UtilValidate.isNotEmpty(areaId)) return areaId;
+            return resolveCurrentScreenId();
+        }
+    }
 }
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenStringRenderer.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenStringRenderer.java
index 9b309f2..353eac6 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenStringRenderer.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/ScreenStringRenderer.java
@@ -23,6 +23,7 @@ import java.util.Map;
 
 import org.apache.ofbiz.base.util.GeneralException;
 import org.apache.ofbiz.entity.GenericValue;
+import org.apache.ofbiz.widget.model.ModelScreen;
 import org.apache.ofbiz.widget.model.ModelScreenWidget;
 
 /**
@@ -30,8 +31,10 @@ import org.apache.ofbiz.widget.model.ModelScreenWidget;
  */
 public interface ScreenStringRenderer {
     String getRendererName();
-    void renderScreenBegin(Appendable writer, Map<String, Object> context) throws IOException;
-    void renderScreenEnd(Appendable writer, Map<String, Object> context) throws IOException;
+    void renderBegin(Appendable writer, Map<String, Object> context) throws IOException;
+    void renderEnd(Appendable writer, Map<String, Object> context) throws IOException;
+    void renderScreenBegin(Appendable writer, Map<String, Object> context, ModelScreen modelScreen) throws IOException;
+    void renderScreenEnd(Appendable writer, Map<String, Object> context, ModelScreen modelScreen) throws IOException;
     void renderSectionBegin(Appendable writer, Map<String, Object> context, ModelScreenWidget.Section section) throws IOException;
     void renderSectionEnd(Appendable writer, Map<String, Object> context, ModelScreenWidget.Section section) throws IOException;
     void renderColumnContainer(Appendable writer, Map<String, Object> context, ModelScreenWidget.ColumnContainer columnContainer) throws IOException;
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenRenderer.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenRenderer.java
index ce1c3a8..e4ed5b2 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenRenderer.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenRenderer.java
@@ -62,6 +62,7 @@ import org.apache.ofbiz.widget.model.ScreenFactory;
 import org.apache.ofbiz.widget.renderer.FormStringRenderer;
 import org.apache.ofbiz.widget.renderer.MenuStringRenderer;
 import org.apache.ofbiz.widget.renderer.Paginator;
+import org.apache.ofbiz.widget.renderer.ScreenRenderer;
 import org.apache.ofbiz.widget.renderer.ScreenStringRenderer;
 import org.apache.ofbiz.widget.renderer.VisualTheme;
 import org.apache.ofbiz.widget.renderer.html.HtmlWidgetRenderer;
@@ -151,12 +152,26 @@ public class MacroScreenRenderer implements ScreenStringRenderer {
     }
 
     @Override
-    public void renderScreenBegin(Appendable writer, Map<String, Object> context) throws IOException {
+    public void renderBegin(Appendable writer, Map<String, Object> context) throws IOException {
+        executeMacro(writer, "renderBegin", null);
+    }
+
+    @Override
+    public void renderEnd(Appendable writer, Map<String, Object> context) throws IOException {
+        executeMacro(writer, "renderEnd", null);
+    }
+
+    @Override
+    public void renderScreenBegin(Appendable writer, Map<String, Object> context, ModelScreen modelScreen) throws IOException {
+        ScreenRenderer.ScreenStack screenStack = WidgetWorker.getScreenStack(context);
+        screenStack.push(modelScreen);
         executeMacro(writer, "renderScreenBegin", null);
     }
 
     @Override
-    public void renderScreenEnd(Appendable writer, Map<String, Object> context) throws IOException {
+    public void renderScreenEnd(Appendable writer, Map<String, Object> context, ModelScreen modelScreen) throws IOException {
+        ScreenRenderer.ScreenStack screenStack = WidgetWorker.getScreenStack(context);
+        screenStack.drop();
         executeMacro(writer, "renderScreenEnd", null);
     }
 
@@ -166,12 +181,14 @@ public class MacroScreenRenderer implements ScreenStringRenderer {
             this.widgetCommentsEnabled = ModelWidget.widgetBoundaryCommentsEnabled(context);
         }
         if (this.widgetCommentsEnabled) {
-            Map<String, Object> parameters = new HashMap<>();
-            StringBuilder sb = new StringBuilder("Begin ");
-            sb.append(section.isMainSection() ? "Screen " : "Section Widget ");
-            sb.append(section.getBoundaryCommentName());
-            parameters.put("boundaryComment", sb.toString());
-            executeMacro(writer, "renderSectionBegin", parameters);
+            StringBuilder sb = new StringBuilder("Begin section widget")
+                    .append(section.getBoundaryCommentName());
+            if (section.isMainSection()) {
+                ScreenRenderer.ScreenStack screenStack = WidgetWorker.getScreenStack(context);
+                sb.append(" id ")
+                        .append(screenStack.resolveCurrentScreenId());
+            }
+            executeMacro(writer, "renderSectionBegin", UtilMisc.toMap("boundaryComment", sb.toString()));
         }
         if (HtmlWidgetRenderer.NAMED_BORDER_TYPE != ModelWidget.NamedBorderType.NONE && section.isMainSection()) {
             // render start of named border for screen
@@ -186,13 +203,14 @@ public class MacroScreenRenderer implements ScreenStringRenderer {
             writer.append(HtmlWidgetRenderer.endNamedBorder("Screen", section.getBoundaryCommentName()));
         }
         if (this.widgetCommentsEnabled) {
-            Map<String, Object> parameters = new HashMap<>();
-            StringBuilder sb = new StringBuilder();
-            sb.append("End ");
-            sb.append(section.isMainSection() ? "Screen " : "Section Widget ");
-            sb.append(section.getBoundaryCommentName());
-            parameters.put("boundaryComment", sb.toString());
-            executeMacro(writer, "renderSectionEnd", parameters);
+            StringBuilder sb = new StringBuilder("End section Widget ")
+                    .append(section.getBoundaryCommentName());
+            if (section.isMainSection()) {
+                ScreenRenderer.ScreenStack screenStack = WidgetWorker.getScreenStack(context);
+                sb.append(" id ")
+                        .append(screenStack.resolveCurrentScreenId());
+            }
+            executeMacro(writer, "renderSectionEnd", UtilMisc.toMap("boundaryComment", sb.toString()));
         }
     }
 
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenViewHandler.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenViewHandler.java
index 6972238..4365a7b 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenViewHandler.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroScreenViewHandler.java
@@ -112,9 +112,9 @@ public class MacroScreenViewHandler extends AbstractViewHandler {
             ScreenRenderer screens = new ScreenRenderer(writer, context, screenStringRenderer);
             context.put("screens", screens);
             context.put("simpleEncoder", UtilCodec.getEncoder(visualTheme.getModelTheme().getEncoder(getName())));
-            screenStringRenderer.renderScreenBegin(writer, context);
+            screenStringRenderer.renderBegin(writer, context);
             screens.render(page);
-            screenStringRenderer.renderScreenEnd(writer, context);
+            screenStringRenderer.renderEnd(writer, context);
             writer.flush();
         } catch (TemplateException e) {
             Debug.logError(e, "Error initializing screen renderer", MODULE);
diff --git a/themes/common-theme/template/macro/CsvScreenMacroLibrary.ftl b/themes/common-theme/template/macro/CsvScreenMacroLibrary.ftl
index eb77b7a..2d18464 100644
--- a/themes/common-theme/template/macro/CsvScreenMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/CsvScreenMacroLibrary.ftl
@@ -17,17 +17,17 @@ specific language governing permissions and limitations
 under the License.
 -->
 
-<#macro renderScreenBegin>
-</#macro>
+<#macro renderBegin></#macro>
 
-<#macro renderScreenEnd>
-</#macro>
+<#macro renderEnd></#macro>
 
-<#macro renderSectionBegin boundaryComment>
-</#macro>
+<#macro renderScreenBegin></#macro>
 
-<#macro renderSectionEnd boundaryComment>
-</#macro>
+<#macro renderScreenEnd></#macro>
+
+<#macro renderSectionBegin boundaryComment></#macro>
+
+<#macro renderSectionEnd boundaryComment></#macro>
 
 <#macro renderContainerBegin id style autoUpdateLink type autoUpdateInterval></#macro>
 <#macro renderContainerEnd type></#macro>
diff --git a/themes/common-theme/template/macro/FoScreenMacroLibrary.ftl b/themes/common-theme/template/macro/FoScreenMacroLibrary.ftl
index f5f4fe5..cf84e11 100644
--- a/themes/common-theme/template/macro/FoScreenMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/FoScreenMacroLibrary.ftl
@@ -39,10 +39,16 @@ under the License.
 
 <#escape x as x?xml>
 
-<#macro renderScreenBegin>
+<#macro renderBegin>
 <?xml version="1.0" encoding="UTF-8"?>
 </#macro>
 
+<#macro renderEnd>
+</#macro>
+
+<#macro renderScreenBegin>
+</#macro>
+
 <#macro renderScreenEnd>
 </#macro>
 
diff --git a/themes/common-theme/template/macro/HtmlScreenMacroLibrary.ftl b/themes/common-theme/template/macro/HtmlScreenMacroLibrary.ftl
index 8a2a5eb..2d5b65e 100644
--- a/themes/common-theme/template/macro/HtmlScreenMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/HtmlScreenMacroLibrary.ftl
@@ -16,10 +16,16 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
 -->
-<#macro renderScreenBegin>
+<#macro renderBegin>
 <!DOCTYPE html>
 </#macro>
 
+<#macro renderEnd>
+</#macro>
+
+<#macro renderScreenBegin>
+</#macro>
+
 <#macro renderScreenEnd>
 </#macro>
 
diff --git a/themes/common-theme/template/macro/TextScreenMacroLibrary.ftl b/themes/common-theme/template/macro/TextScreenMacroLibrary.ftl
index 035f697..f2c8854 100644
--- a/themes/common-theme/template/macro/TextScreenMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/TextScreenMacroLibrary.ftl
@@ -17,11 +17,13 @@ specific language governing permissions and limitations
 under the License.
 -->
 
-<#macro renderScreenBegin>
-</#macro>
+<#macro renderBegin></#macro>
 
-<#macro renderScreenEnd>
-</#macro>
+<#macro renderEnd></#macro>
+
+<#macro renderScreenBegin></#macro>
+
+<#macro renderScreenEnd></#macro>
 
 <#macro renderSectionBegin boundaryComment>
 </#macro>
diff --git a/themes/common-theme/template/macro/XlsScreenMacroLibrary.ftl b/themes/common-theme/template/macro/XlsScreenMacroLibrary.ftl
index a5777fc..bc56159 100644
--- a/themes/common-theme/template/macro/XlsScreenMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/XlsScreenMacroLibrary.ftl
@@ -17,6 +17,10 @@ specific language governing permissions and limitations
 under the License.
 -->
 
+<#macro renderBegin></#macro>
+
+<#macro renderEnd></#macro>
+
 <#macro renderScreenBegin></#macro>
 
 <#macro renderScreenEnd></#macro>
diff --git a/themes/common-theme/template/macro/XmlScreenMacroLibrary.ftl b/themes/common-theme/template/macro/XmlScreenMacroLibrary.ftl
index 4b0a3e7..97f32ea 100644
--- a/themes/common-theme/template/macro/XmlScreenMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/XmlScreenMacroLibrary.ftl
@@ -17,15 +17,21 @@ specific language governing permissions and limitations
 under the License.
 -->
 
-<#macro renderScreenBegin>
+<#macro renderBegin>
 <?xml version="1.0" encoding="UTF-8"?>
 <export>
 </#macro>
 
-<#macro renderScreenEnd>
+<#macro renderEnd>
 </export>
 </#macro>
 
+<#macro renderScreenBegin>
+</#macro>
+
+<#macro renderScreenEnd>
+</#macro>
+
 <#macro renderSectionBegin boundaryComment>
 </#macro>