[ofbiz-framework] branch trunk updated: Improved: Headerize external script in multi-block html template (OFBIZ-11741)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[ofbiz-framework] branch trunk updated: Improved: Headerize external script in multi-block html template (OFBIZ-11741)

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

jamesyong 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 c0e527e  Improved: Headerize external script in multi-block html template (OFBIZ-11741)
c0e527e is described below

commit c0e527efb2bc63803298138a415699dee507ee02
Author: James Yong <[hidden email]>
AuthorDate: Sat May 30 12:00:21 2020 +0800

    Improved: Headerize external script in multi-block html template (OFBIZ-11741)
   
    Allow external scripts within the multi-block html template, to be rendered
    within the html head tag, when a new attribute data-import is set to “head”
   
    Thanks: Jacques for review
---
 .../humanres/template/category/CategoryTree.ftl    |   2 +
 applications/humanres/widget/CommonScreens.xml     |   1 -
 .../product/template/category/CategoryTree.ftl     |   2 +
 .../template/store/ProductStoreGroupTree.ftl       |   2 +
 .../product/widget/catalog/CommonScreens.xml       |   1 -
 .../java/org/apache/ofbiz/common/CommonEvents.java |   4 +-
 ...ansform.java => ScriptTagsFooterTransform.java} |  10 +-
 .../ofbiz/webapp/freemarkerTransforms.properties   |   2 +-
 framework/widget/dtd/widget-screen.xsd             |  10 +-
 .../widget/model/HtmlImportWidgetVisitor.java      | 236 +++++++++++++++++++++
 .../org/apache/ofbiz/widget/model/HtmlWidget.java  | 116 +++++-----
 .../ofbiz/widget/model/ModelScreenWidget.java      | 139 ++++++++----
 .../widget/model/MultiBlockHtmlTemplateUtil.java   | 228 ++++++++++++++++++++
 .../ofbiz/widget/model/ScriptTemplateUtil.java     | 105 ---------
 .../ofbiz/widget/renderer/ScreenRenderer.java      |   5 +-
 .../template/includes/CloseHtmlBody.ftl            |   2 +-
 .../template/includes/LookupFooter.ftl             |   2 +-
 themes/common-theme/widget/Theme.xml               |   1 -
 themes/flatgrey/template/Footer.ftl                |   2 +-
 themes/rainbowstone/template/includes/Footer.ftl   |   2 +-
 themes/tomahawk/template/Footer.ftl                |   2 +-
 21 files changed, 666 insertions(+), 208 deletions(-)

diff --git a/applications/humanres/template/category/CategoryTree.ftl b/applications/humanres/template/category/CategoryTree.ftl
index f14bbfc..b1b7465 100644
--- a/applications/humanres/template/category/CategoryTree.ftl
+++ b/applications/humanres/template/category/CategoryTree.ftl
@@ -17,6 +17,8 @@ specific language governing permissions and limitations
 under the License.
 -->
 
+<script src="/common/js/jquery/plugins/jsTree/jquery.jstree.js" type="application/javascript" data-import="head"></script>
+
 <script type="application/javascript">
 <#-- some labels are not unescaped in the JSON object so we have to do this manualy -->
 function unescapeHtmlText(text) {
diff --git a/applications/humanres/widget/CommonScreens.xml b/applications/humanres/widget/CommonScreens.xml
index 965b590..cf5db20 100644
--- a/applications/humanres/widget/CommonScreens.xml
+++ b/applications/humanres/widget/CommonScreens.xml
@@ -39,7 +39,6 @@ under the License.
                 <set field="layoutSettings.styleSheets[]" value="/partymgr/static/partymgr.css" global="true"/>
                 <set field="layoutSettings.styleSheets[]" value="/images/humanres/humanres.css" global="true"/>
                 <set field="layoutSettings.javaScripts[+0]" value="/common/js/jquery/ui/js/jquery.cookie-1.4.0.js" global="true"/>
-                <set field="layoutSettings.javaScripts[+0]" value="/common/js/jquery/plugins/jsTree/jquery.jstree.js" global="true"/>
                 <set field="applicationMenuName" value="HumanResAppBar" global="true"/>
                 <set field="applicationMenuLocation" value="component://humanres/widget/HumanresMenus.xml" global="true"/>
                 <set field="applicationTitle" from-field="uiLabelMap.HumanResManagerApplication" global="true"/>
diff --git a/applications/product/template/category/CategoryTree.ftl b/applications/product/template/category/CategoryTree.ftl
index dd4ca21..dcb6a47 100644
--- a/applications/product/template/category/CategoryTree.ftl
+++ b/applications/product/template/category/CategoryTree.ftl
@@ -17,6 +17,8 @@ specific language governing permissions and limitations
 under the License.
 -->
 
+<script src="/common/js/jquery/plugins/jsTree/jquery.jstree.js" type="application/javascript" data-import="head"></script>
+
 <script type="application/javascript">
 <#-- some labels are not unescaped in the JSON object so we have to do this manualy -->
 function unescapeHtmlText(text) {
diff --git a/applications/product/template/store/ProductStoreGroupTree.ftl b/applications/product/template/store/ProductStoreGroupTree.ftl
index 039f981..91c0027 100644
--- a/applications/product/template/store/ProductStoreGroupTree.ftl
+++ b/applications/product/template/store/ProductStoreGroupTree.ftl
@@ -17,6 +17,8 @@ specific language governing permissions and limitations
 under the License.
 -->
 
+<script src="/common/js/jquery/plugins/jsTree/jquery.jstree.js" type="application/javascript" data-import="head"></script>
+
 <script type="application/javascript">
 <#-- some labels are not unescaped in the JSON object so we have to do this manualy -->
 function unescapeHtmlText(text) {
diff --git a/applications/product/widget/catalog/CommonScreens.xml b/applications/product/widget/catalog/CommonScreens.xml
index c3842fd..cbaf3e6 100644
--- a/applications/product/widget/catalog/CommonScreens.xml
+++ b/applications/product/widget/catalog/CommonScreens.xml
@@ -42,7 +42,6 @@ under the License.
                 <set field="applicationMenuLocation" value="component://product/widget/catalog/CatalogMenus.xml" global="true"/>
                 <set field="applicationTitle" from-field="uiLabelMap.ProductCatalogManagerApplication" global="true"/>
                 <set field="layoutSettings.javaScripts[+0]" value="/common/js/jquery/ui/js/jquery.cookie-1.4.0.js" global="true"/>
-                <set field="layoutSettings.javaScripts[+0]" value="/common/js/jquery/plugins/jsTree/jquery.jstree.js" global="true"/>
             </actions>
             <widgets>
                 <include-screen name="ApplicationDecorator" location="component://commonext/widget/CommonScreens.xml"/>
diff --git a/framework/common/src/main/java/org/apache/ofbiz/common/CommonEvents.java b/framework/common/src/main/java/org/apache/ofbiz/common/CommonEvents.java
index aeda1ec..9450567 100644
--- a/framework/common/src/main/java/org/apache/ofbiz/common/CommonEvents.java
+++ b/framework/common/src/main/java/org/apache/ofbiz/common/CommonEvents.java
@@ -53,7 +53,7 @@ import org.apache.ofbiz.entity.GenericValue;
 import org.apache.ofbiz.entity.util.EntityUtilProperties;
 import org.apache.ofbiz.webapp.control.JWTManager;
 import org.apache.ofbiz.webapp.control.LoginWorker;
-import org.apache.ofbiz.widget.model.ScriptTemplateUtil;
+import org.apache.ofbiz.widget.model.MultiBlockHtmlTemplateUtil;
 import org.apache.ofbiz.widget.model.ThemeFactory;
 import org.apache.ofbiz.widget.renderer.VisualTheme;
 
@@ -180,7 +180,7 @@ public class CommonEvents {
     public static String jsResponseFromRequest(HttpServletRequest request, HttpServletResponse response) {
 
         String fileName = request.getParameter("name");
-        String script = ScriptTemplateUtil.getScriptFromSession(request.getSession(), fileName);
+        String script = MultiBlockHtmlTemplateUtil.getScriptFromCache(request.getSession(), fileName);
 
         // return the JS String
         Writer out;
diff --git a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/ScriptTemplateListTransform.java b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/ScriptTagsFooterTransform.java
similarity index 86%
rename from framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/ScriptTemplateListTransform.java
rename to framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/ScriptTagsFooterTransform.java
index f8c67fa..8288ab5 100644
--- a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/ScriptTemplateListTransform.java
+++ b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/ScriptTagsFooterTransform.java
@@ -24,7 +24,7 @@ import java.util.Map;
 import java.util.Set;
 import javax.servlet.http.HttpServletRequest;
 
-import org.apache.ofbiz.widget.model.ScriptTemplateUtil;
+import org.apache.ofbiz.widget.model.MultiBlockHtmlTemplateUtil;
 
 import freemarker.core.Environment;
 import freemarker.ext.beans.BeanModel;
@@ -32,14 +32,14 @@ import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateTransformModel;
 
 /**
- * Render the script tags collected from the "script-template" tag
+ * Render the externalized script tags collected from the "html-template" tag where multi-block = true
  */
-public class ScriptTemplateListTransform implements TemplateTransformModel {
+public class ScriptTagsFooterTransform implements TemplateTransformModel {
 
     private static final String MODULE = CsrfTokenAjaxTransform.class.getName();
 
     @Override
-    public Writer getWriter(Writer out, @SuppressWarnings("rawtypes") Map args)
+    public final Writer getWriter(Writer out, @SuppressWarnings("rawtypes") Map args)
             throws TemplateModelException, IOException {
 
         return new Writer(out) {
@@ -51,7 +51,7 @@ public class ScriptTemplateListTransform implements TemplateTransformModel {
                     BeanModel req = (BeanModel) env.getVariable("request");
                     if (req != null) {
                         HttpServletRequest request = (HttpServletRequest) req.getWrappedObject();
-                        Set<String> scriptSrcSet = ScriptTemplateUtil.getScriptSrcLinksFromRequest(request);
+                        Set<String> scriptSrcSet = MultiBlockHtmlTemplateUtil.getScriptLinksForFoot(request);
                         if (scriptSrcSet != null) {
                             String srcList = "";
                             for (String scriptSrc : scriptSrcSet) {
diff --git a/framework/webapp/src/main/resources/org/apache/ofbiz/webapp/freemarkerTransforms.properties b/framework/webapp/src/main/resources/org/apache/ofbiz/webapp/freemarkerTransforms.properties
index b4f4d8b..caf421d 100644
--- a/framework/webapp/src/main/resources/org/apache/ofbiz/webapp/freemarkerTransforms.properties
+++ b/framework/webapp/src/main/resources/org/apache/ofbiz/webapp/freemarkerTransforms.properties
@@ -31,4 +31,4 @@ renderWrappedText=org.apache.ofbiz.webapp.ftl.RenderWrappedTextTransform
 setContextField=org.apache.ofbiz.webapp.ftl.SetContextFieldTransform
 csrfTokenAjax=org.apache.ofbiz.webapp.ftl.CsrfTokenAjaxTransform
 csrfTokenPair=org.apache.ofbiz.webapp.ftl.CsrfTokenPairNonAjaxTransform
-scriptTemplateList=org.apache.ofbiz.webapp.ftl.ScriptTemplateListTransform
+scriptTagsFooter=org.apache.ofbiz.webapp.ftl.ScriptTagsFooterTransform
diff --git a/framework/widget/dtd/widget-screen.xsd b/framework/widget/dtd/widget-screen.xsd
index 087809b..6e3fe97 100644
--- a/framework/widget/dtd/widget-screen.xsd
+++ b/framework/widget/dtd/widget-screen.xsd
@@ -519,7 +519,15 @@ under the License.
     </xs:element>
     <xs:attributeGroup name="attlist.html-template">
         <xs:attribute type="xs:string" name="location" use="required" />
-        <xs:attribute type="xs:boolean" name="multi-block" use="optional" default="false" />
+        <xs:attribute type="xs:boolean" name="multi-block" use="optional" default="false">
+            <xs:annotation>
+                <xs:documentation>
+                    Multi-block processing of template targeted for the html body.
+                    Inline script will be rendered as external script after html body tag.
+                    External script tag with attribute data-import='head' will be rendered within html head tag if the template location is static.
+                </xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
     </xs:attributeGroup>
     <xs:element name="html-template-decorator" substitutionGroup="HtmlWidgets">
         <xs:annotation>
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlImportWidgetVisitor.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlImportWidgetVisitor.java
new file mode 100644
index 0000000..9e6add0
--- /dev/null
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlImportWidgetVisitor.java
@@ -0,0 +1,236 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.ofbiz.widget.model;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ofbiz.base.util.FileUtil;
+import org.apache.ofbiz.base.util.UtilValidate;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.select.Elements;
+
+public final class HtmlImportWidgetVisitor implements ModelWidgetVisitor {
+
+    private Set<String> jsImports = new LinkedHashSet<String>();
+
+    /**
+     * Get the script source locations collected by HtmlImportWidgetVisitor
+     * @return
+     */
+    public Set<String> getJsImports() {
+        return jsImports;
+    }
+
+    @Override
+    public void visit(HtmlWidget htmlWidget) throws Exception {
+        List<ModelScreenWidget> widgetList = htmlWidget.getSubWidgets();
+        for (ModelScreenWidget widget: widgetList) {
+            // HtmlTemplate
+            widget.accept(this);
+        }
+    }
+
+    @Override
+    public void visit(HtmlWidget.HtmlTemplate htmlTemplate) throws Exception {
+        String fileLocation = htmlTemplate.locationExdr.getOriginal();
+        boolean isStaticLocation = !fileLocation.contains("${");
+        if (isStaticLocation && htmlTemplate.isMultiBlock()) {
+            String template = FileUtil.readString("UTF-8", FileUtil.getFile(fileLocation));
+            Document doc = Jsoup.parseBodyFragment(template);
+            Elements scriptElements = doc.select("script");
+            if (scriptElements != null && scriptElements.size() > 0) {
+                for (org.jsoup.nodes.Element script : scriptElements) {
+                    String src = script.attr("src");
+                    if (UtilValidate.isNotEmpty(src)) {
+                        String dataImport = script.attr("data-import");
+                        if ("head".equals(dataImport)) {
+                            jsImports.add(src);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void visit(HtmlWidget.HtmlTemplateDecorator htmlTemplateDecorator) throws Exception {
+
+    }
+
+    @Override
+    public void visit(HtmlWidget.HtmlTemplateDecoratorSection htmlTemplateDecoratorSection) throws Exception {
+
+    }
+
+    @Override
+    public void visit(IterateSectionWidget iterateSectionWidget) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelSingleForm modelForm) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelGrid modelGrid) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelMenu modelMenu) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelMenuItem modelMenuItem) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreen modelScreen) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.ColumnContainer columnContainer) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.Container container) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.Content content) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.DecoratorScreen decoratorScreen) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.DecoratorSection decoratorSection) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.DecoratorSectionInclude decoratorSectionInclude) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.Form form) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.Grid grid) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.HorizontalSeparator horizontalSeparator) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.ScreenImage image) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.IncludeScreen includeScreen) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.Label label) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.ScreenLink link) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.Menu menu) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.PlatformSpecific platformSpecific) throws Exception {
+        Map<String, ModelScreenWidget> widgetMap = platformSpecific.getSubWidgets();
+        for (Map.Entry<String, ModelScreenWidget> entry : widgetMap.entrySet()) {
+            if (entry.getKey().equals("html")) {
+                // HtmlWidget
+                entry.getValue().accept(this);
+            }
+        }
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.PortalPage portalPage) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.Screenlet screenlet) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.Section section) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.Tree tree) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelTree modelTree) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelTree.ModelNode modelNode) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelTree.ModelNode.ModelSubNode modelSubNode) throws Exception {
+
+    }
+
+    @Override
+    public void visit(ModelScreenWidget.Column column) throws Exception {
+
+    }
+}
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java
index 13449db..4817a37 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/HtmlWidget.java
@@ -179,6 +179,72 @@ public class HtmlWidget extends ModelScreenWidget {
         }
     }
 
+    public static void renderHtmlTemplateMultiBlock(Appendable writer, FlexibleStringExpander locationExdr, Map<String, Object> context) throws IOException {
+        String location = locationExdr.expandString(context);
+
+        StringWriter stringWriter = new StringWriter();
+        context.put(MultiBlockHtmlTemplateUtil.MULTI_BLOCK_WRITER, stringWriter);
+        renderHtmlTemplate(stringWriter, locationExdr, context);
+        context.remove(MultiBlockHtmlTemplateUtil.MULTI_BLOCK_WRITER);
+        String data = stringWriter.toString();
+        stringWriter.close();
+
+        Document doc = Jsoup.parseBodyFragment(data);
+
+        // extract scripts
+        Elements scriptElements = doc.select("script");
+        if (scriptElements != null && scriptElements.size() > 0) {
+            StringBuilder scripts = new StringBuilder();
+
+            // check if location contains variable
+            String originalLocation = locationExdr.getOriginal();
+            boolean isStaticLocation = !originalLocation.contains("${");
+
+            for (org.jsoup.nodes.Element script : scriptElements) {
+                String type = script.attr("type");
+                String src = script.attr("src");
+                if (UtilValidate.isEmpty(src)) {
+                    if (UtilValidate.isEmpty(type) || type.equals("application/javascript")) {
+                        scripts.append(script.data());
+                        script.remove();
+                    }
+                } else {
+                    String dataImport = script.attr("data-import");
+                    if ("head".equals(dataImport)) {
+                        if (isStaticLocation) {
+                            // remove external script in the template that is meant to be imported in the html header
+                            script.remove();
+                        } else {
+                            // throw error to the browser
+                            writer.append("<script>alert('Unable to headerize "
+                                    + UtilCodec.getEncoder("html").encode(script.toString())
+                                    + " when template location not is static');</script>");
+                        }
+                    }
+                }
+            }
+
+            if (scripts.length() > 0) {
+                // store script for retrieval by the browser
+                String fileName = location;
+                fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
+                if (fileName.endsWith(".ftl")) {
+                    fileName = fileName.substring(0, fileName.length() - 4);
+                }
+                MultiBlockHtmlTemplateUtil.putScriptInCache(context, fileName, scripts.toString());
+
+                // store value to be used by scriptTagsFooter freemarker macro
+                String webappName = (String) context.get("webappName");
+                MultiBlockHtmlTemplateUtil.addScriptLinkForFoot(context, "/" + webappName + "/control/getJs?name="
+                        + fileName);
+            }
+        }
+
+        // the 'template' block
+        String body = doc.body().html();
+        writer.append(body);
+    }
+
     // TODO: We can make this more fancy, but for now this is very functional
     public static void writeError(Appendable writer, String message) {
         try {
@@ -208,54 +274,8 @@ public class HtmlWidget extends ModelScreenWidget {
         @Override
         public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws IOException {
 
-            if (false && isMultiBlock()) {
-
-                StringWriter stringWriter = new StringWriter();
-                context.put("MultiBlockWriter", stringWriter);
-                renderHtmlTemplate(stringWriter, this.locationExdr, context);
-                context.remove("MultiBlockWriter");
-                String data = stringWriter.toString();
-                stringWriter.close();
-
-                Document doc = Jsoup.parseBodyFragment(data);
-
-                // extract scripts
-                Elements scriptElements = doc.select("script");
-                if (scriptElements != null && scriptElements.size()>0) {
-                    StringBuilder scripts = new StringBuilder();
-
-                    for (org.jsoup.nodes.Element script : scriptElements) {
-                        String type = script.attr("type");
-                        String src = script.attr("src");
-                        if (UtilValidate.isEmpty(src)) {
-                            if (UtilValidate.isEmpty(type) || type.equals("application/javascript")) {
-                                scripts.append(script.data());
-                                script.remove();
-                            }
-                        }
-                    }
-
-                    // store script for retrieval by the browser
-                    String fileName = this.getLocation(context);
-                    fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
-                    if (fileName.endsWith(".ftl")) {
-                        fileName = fileName.substring(0, fileName.length() - 4);
-                    }
-                    ScriptTemplateUtil.putScriptInSession(context, fileName, scripts.toString());
-
-                    // store value to be used by ScriptTemplateList freemarker macro
-                    String webappName = (String) context.get("webappName");
-                    ScriptTemplateUtil.addScriptSrcToRequest(context, "/" + webappName + "/control/getJs?name="
-                            + fileName);
-                }
-
-                // check for external script
-                String externalScripts = doc.body().select("script").toString();
-                writer.append(externalScripts);
-
-                // the 'template' block
-                String body = doc.body().html();
-                writer.append(body);
+            if (isMultiBlock()) {
+                renderHtmlTemplateMultiBlock(writer, this.locationExdr, context);
             } else {
                 renderHtmlTemplate(writer, this.locationExdr, context);
             }
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java
index 75d84cc..a460679 100644
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelScreenWidget.java
@@ -77,7 +77,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
         }
     }
 
-    public abstract void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException;
+    public abstract void renderWidgetString(Appendable writer, Map<String, Object> context,
+                                            ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException;
 
     protected static List<ModelScreenWidget> readSubWidgets(ModelScreen modelScreen, List<? extends Element> subElementList) {
         if (subElementList.isEmpty()) {
@@ -90,13 +91,16 @@ public abstract class ModelScreenWidget extends ModelWidget {
         return Collections.unmodifiableList(subWidgets);
     }
 
-    protected static void renderSubWidgetsString(List<ModelScreenWidget> subWidgets, Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
+    protected static void renderSubWidgetsString(List<ModelScreenWidget> subWidgets, Appendable writer,
+                                                 Map<String, Object> context, ScreenStringRenderer screenStringRenderer)
+                                                    throws GeneralException, IOException {
         if (subWidgets == null) {
             return;
         }
         for (ModelScreenWidget subWidget: subWidgets) {
             if (Debug.verboseOn()) {
-                Debug.logVerbose("Rendering screen " + subWidget.getModelScreen().getName() + "; widget class is " + subWidget.getClass().getName(), MODULE);
+                Debug.logVerbose("Rendering screen " + subWidget.getModelScreen().getName()
+                        + "; widget class is " + subWidget.getClass().getName(), MODULE);
             }
 
             // render the sub-widget itself
@@ -255,6 +259,25 @@ public abstract class ModelScreenWidget extends ModelWidget {
                 this.failWidgets = Collections.emptyList();
             }
             this.isMainSection = isMainSection;
+
+            String screenLocation = modelScreen.getSourceLocation();
+            String screenName = modelScreen.getName();
+
+            // check required html import from the widgets
+            HtmlImportWidgetVisitor visitor = new HtmlImportWidgetVisitor();
+            try {
+                for (ModelScreenWidget widget : this.subWidgets) {
+                    widget.accept(visitor);
+                }
+                for (ModelScreenWidget widget : this.failWidgets) {
+                    widget.accept(visitor);
+                }
+                MultiBlockHtmlTemplateUtil.addLinksToHtmlImportCache(screenLocation, screenName, visitor.getJsImports());
+            } catch (Exception e) {
+                String errMsg = "Error adding links to Html Import Cache";
+                Debug.logError(e, errMsg, MODULE);
+                throw new RuntimeException(errMsg);
+            }
         }
 
         @Override
@@ -263,7 +286,17 @@ public abstract class ModelScreenWidget extends ModelWidget {
         }
 
         @Override
-        public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
+        public void renderWidgetString(Appendable writer, Map<String, Object> context,
+                                       ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
+
+            try {
+                String location = getModelScreen().getSourceLocation();
+                String name = getModelScreen().getName();
+                MultiBlockHtmlTemplateUtil.addLinksToLayoutSettings(context, location, name);
+            } catch (Exception e) {
+                throw new GeneralException(e);
+            }
+
             // check the condition, if there is one
             boolean condTrue = true;
             if (this.condition != null) {
@@ -286,7 +319,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
 
                     screenStringRenderer.renderSectionEnd(writer, context, this);
                 } catch (IOException e) {
-                    String errMsg = "Error rendering widgets section [" + getName() + "] in screen named [" + getModelScreen().getName() + "]: " + e.toString();
+                    String errMsg = "Error rendering widgets section [" + getName() + "] in screen named ["
+                            + getModelScreen().getName() + "]: " + e.toString();
                     Debug.logError(e, errMsg, MODULE);
                     throw new RuntimeException(errMsg);
                 }
@@ -300,7 +334,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
 
                     screenStringRenderer.renderSectionEnd(writer, context, this);
                 } catch (IOException e) {
-                    String errMsg = "Error rendering fail-widgets section [" + this.getName() + "] in screen named [" + getModelScreen().getName() + "]: " + e.toString();
+                    String errMsg = "Error rendering fail-widgets section [" + this.getName() + "] in screen named ["
+                            + getModelScreen().getName() + "]: " + e.toString();
                     Debug.logError(e, errMsg, MODULE);
                     throw new RuntimeException(errMsg);
                 }
@@ -361,7 +396,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
         }
 
         @Override
-        public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
+        public void renderWidgetString(Appendable writer, Map<String, Object> context,
+                                       ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
             try {
                 screenStringRenderer.renderColumnContainer(writer, context, this);
             } catch (IOException e) {
@@ -457,7 +493,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
         }
 
         @Override
-        public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
+        public void renderWidgetString(Appendable writer, Map<String, Object> context,
+                                       ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
             try {
                 screenStringRenderer.renderContainerBegin(writer, context, this);
 
@@ -596,7 +633,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
         }
 
         @Override
-        public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
+        public void renderWidgetString(Appendable writer, Map<String, Object> context,
+                                       ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
             boolean collapsed = getInitiallyCollapsed(context);
             if (this.collapsible) {
                 String preferenceKey = getPreferenceKey(context) + "_collapsed";
@@ -722,7 +760,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
         }
 
         @Override
-        public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
+        public void renderWidgetString(Appendable writer, Map<String, Object> context,
+                                       ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
             screenStringRenderer.renderHorizontalSeparator(writer, context, this);
         }
 
@@ -759,10 +798,12 @@ public abstract class ModelScreenWidget extends ModelWidget {
             this.nameExdr = FlexibleStringExpander.getInstance(includeScreenElement.getAttribute("name"));
             this.locationExdr = FlexibleStringExpander.getInstance(includeScreenElement.getAttribute("location"));
             this.shareScopeExdr = FlexibleStringExpander.getInstance(includeScreenElement.getAttribute("share-scope"));
+            MultiBlockHtmlTemplateUtil.collectChildScreenInfo(modelScreen, this.locationExdr.getOriginal(), this.nameExdr.getOriginal());
         }
 
         @Override
-        public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
+        public void renderWidgetString(Appendable writer, Map<String, Object> context,
+                                       ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
             // if we are not sharing the scope, protect it using the MapStack
             boolean protectScope = !shareScope(context);
             if (protectScope) {
@@ -784,13 +825,15 @@ public abstract class ModelScreenWidget extends ModelWidget {
                 context.put("_WIDGETTRAIL_", widgetTrail);
             }
 
-            // don't need the renderer here, will just pass this on down to another screen call; screenStringRenderer.renderContainerBegin(writer, context, this);
+            // don't need the renderer here, will just pass this on down to another screen call;
+            // screenStringRenderer.renderContainerBegin(writer, context, this);
             String name = this.getName(context);
             String location = this.getLocation(context);
 
             if (name.isEmpty()) {
                 if (Debug.verboseOn()) {
-                    Debug.logVerbose("In the include-screen tag the screen name was empty, ignoring include; in screen [" + getModelScreen().getName() + "]", MODULE);
+                    Debug.logVerbose("In the include-screen tag the screen name was empty, ignoring include; in screen ["
+                            + getModelScreen().getName() + "]", MODULE);
                 }
                 return;
             }
@@ -851,10 +894,12 @@ public abstract class ModelScreenWidget extends ModelWidget {
                 sectionMap.put(decoratorSection.getName(), decoratorSection);
             }
             this.sectionMap = Collections.unmodifiableMap(sectionMap);
+            MultiBlockHtmlTemplateUtil.collectChildScreenInfo(modelScreen, this.locationExdr.getOriginal(), this.nameExdr.getOriginal());
         }
 
         @Override
-        public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
+        public void renderWidgetString(Appendable writer, Map<String, Object> context,
+                                       ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
             // isolate the scope
             if (!(context instanceof MapStack)) {
                 context = MapStack.create(context);
@@ -862,7 +907,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
 
             MapStack<String> contextMs = UtilGenerics.cast(context);
 
-            // create a standAloneStack, basically a "save point" for this SectionsRenderer, and make a new "screens" object just for it so it is isolated and doesn't follow the stack down
+            // create a standAloneStack, basically a "save point" for this SectionsRenderer,
+            // and make a new "screens" object just for it so it is isolated and doesn't follow the stack down
             MapStack<String> standAloneStack = contextMs.standAloneChildStack();
             standAloneStack.put("screens", new ScreenRenderer(writer, standAloneStack, screenStringRenderer));
             SectionsRenderer sections = new SectionsRenderer(this.sectionMap, standAloneStack, writer, screenStringRenderer);
@@ -918,7 +964,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
         }
 
         @Override
-        public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
+        public void renderWidgetString(Appendable writer, Map<String, Object> context,
+                                       ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
             // render sub-widgets
             renderSubWidgetsString(this.subWidgets, writer, context, screenStringRenderer);
         }
@@ -941,7 +988,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
         }
 
         @Override
-        public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
+        public void renderWidgetString(Appendable writer, Map<String, Object> context,
+                                       ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
             Map<String, ? extends Object> preRenderedContent = UtilGenerics.cast(context.get("preRenderedContent"));
             if (preRenderedContent != null && preRenderedContent.containsKey(getName())) {
                 try {
@@ -955,7 +1003,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
                 SectionsRenderer sections = (SectionsRenderer) context.get("sections");
                 // for now if sections is null, just log a warning; may be permissible to make the screen for flexible
                 if (sections == null) {
-                    Debug.logWarning("In decorator-section-include could not find sections object in the context, not rendering section with name [" + getName() + "]", MODULE);
+                    Debug.logWarning("In decorator-section-include could not find sections object in the context, "
+                            + "not rendering section with name [" + getName() + "]", MODULE);
                 } else {
                     sections.render(getName());
                 }
@@ -1054,7 +1103,9 @@ public abstract class ModelScreenWidget extends ModelWidget {
             // Output format might not support forms, so make form rendering optional.
             FormStringRenderer formStringRenderer = (FormStringRenderer) context.get("formStringRenderer");
             if (formStringRenderer == null) {
-                if (Debug.verboseOn()) Debug.logVerbose("FormStringRenderer instance not found in rendering context, form not rendered.", MODULE);
+                if (Debug.verboseOn()) {
+                    Debug.logVerbose("FormStringRenderer instance not found in rendering context, form not rendered.", MODULE);
+                }
                 return;
             }
             boolean protectScope = !shareScope(context);
@@ -1069,7 +1120,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
                 FormRenderer renderer = new FormRenderer(modelForm, formStringRenderer);
                 renderer.render(writer, context);
             } catch (Exception e) {
-                String errMsg = "Error rendering included form named [" + getName() + "] at location [" + this.getLocation(context) + "]: " + e.toString();
+                String errMsg = "Error rendering included form named [" + getName() + "] at location ["
+                        + this.getLocation(context) + "]: " + e.toString();
                 Debug.logError(e, errMsg, MODULE);
                 throw new RuntimeException(errMsg + e);
             }
@@ -1141,7 +1193,9 @@ public abstract class ModelScreenWidget extends ModelWidget {
             // Output format might not support forms, so make form rendering optional.
             FormStringRenderer formStringRenderer = (FormStringRenderer) context.get("formStringRenderer");
             if (formStringRenderer == null) {
-                if (Debug.verboseOn()) Debug.logVerbose("FormStringRenderer instance not found in rendering context, form not rendered.", MODULE);
+                if (Debug.verboseOn()) {
+                    Debug.logVerbose("FormStringRenderer instance not found in rendering context, form not rendered.", MODULE);
+                }
                 return;
             }
             boolean protectScope = !shareScope(context);
@@ -1156,7 +1210,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
             try {
                 renderer.render(writer, context);
             } catch (Exception e) {
-                String errMsg = "Error rendering included grid named [" + getName() + "] at location [" + this.getLocation(context) + "]: " + e.toString();
+                String errMsg = "Error rendering included grid named [" + getName() + "] at location ["
+                        + this.getLocation(context) + "]: " + e.toString();
                 Debug.logError(e, errMsg, MODULE);
                 throw new RuntimeException(errMsg + e);
             }
@@ -1233,11 +1288,14 @@ public abstract class ModelScreenWidget extends ModelWidget {
         }
 
         @Override
-        public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
+        public void renderWidgetString(Appendable writer, Map<String, Object> context,
+                                       ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
             // Output format might not support trees, so make tree rendering optional.
             TreeStringRenderer treeStringRenderer = (TreeStringRenderer) context.get("treeStringRenderer");
             if (treeStringRenderer == null) {
-                if (Debug.verboseOn()) Debug.logVerbose("TreeStringRenderer instance not found in rendering context, tree not rendered.", MODULE);
+                if (Debug.verboseOn()) {
+                    Debug.logVerbose("TreeStringRenderer instance not found in rendering context, tree not rendered.", MODULE);
+                }
                 return;
             }
             boolean protectScope = !shareScope(context);
@@ -1252,7 +1310,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
             String location = this.getLocation(context);
             ModelTree modelTree = null;
             try {
-                modelTree = TreeFactory.getTreeFromLocation(this.getLocation(context), this.getName(context), getModelScreen().getDelegator(context), getModelScreen().getDispatcher(context));
+                modelTree = TreeFactory.getTreeFromLocation(this.getLocation(context), this.getName(context),
+                        getModelScreen().getDelegator(context), getModelScreen().getDispatcher(context));
             } catch (IOException | SAXException | ParserConfigurationException e) {
                 String errMsg = "Error rendering included tree named [" + name + "] at location [" + location + "]: " + e.toString();
                 Debug.logError(e, errMsg, MODULE);
@@ -1315,7 +1374,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
                     } else if ("xls".equals(childElement.getNodeName())) {
                         subWidgets.put("xls", new HtmlWidget(modelScreen, childElement));
                     } else {
-                        throw new IllegalArgumentException("Tag not supported under the platform-specific tag with name: " + childElement.getNodeName());
+                        throw new IllegalArgumentException("Tag not supported under the platform-specific tag with name: "
+                                + childElement.getNodeName());
                     }
                 }
             }
@@ -1323,12 +1383,14 @@ public abstract class ModelScreenWidget extends ModelWidget {
         }
 
         @Override
-        public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
+        public void renderWidgetString(Appendable writer, Map<String, Object> context,
+                                       ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
             ModelScreenWidget subWidget = null;
             subWidget = subWidgets.get(screenStringRenderer.getRendererName());
             if (subWidget == null) {
                 // This is here for backward compatibility
-                Debug.logWarning("In platform-dependent could not find template for " + screenStringRenderer.getRendererName() + ", using the one for html.", MODULE);
+                Debug.logWarning("In platform-dependent could not find template for "
+                        + screenStringRenderer.getRendererName() + ", using the one for html.", MODULE);
                 subWidget = subWidgets.get("html");
             }
             if (subWidget != null) {
@@ -1591,7 +1653,9 @@ public abstract class ModelScreenWidget extends ModelWidget {
             // Output format might not support menus, so make menu rendering optional.
             MenuStringRenderer menuStringRenderer = (MenuStringRenderer) context.get("menuStringRenderer");
             if (menuStringRenderer == null) {
-                if (Debug.verboseOn()) Debug.logVerbose("MenuStringRenderer instance not found in rendering context, menu not rendered.", MODULE);
+                if (Debug.verboseOn()) {
+                    Debug.logVerbose("MenuStringRenderer instance not found in rendering context, menu not rendered.", MODULE);
+                }
                 return;
             }
             ModelMenu modelMenu = getModelMenu(context);
@@ -1911,7 +1975,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
         }
 
         @Override
-        public void renderWidgetString(Appendable writer, Map<String, Object> context, ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
+        public void renderWidgetString(Appendable writer, Map<String, Object> context,
+                                       ScreenStringRenderer screenStringRenderer) throws GeneralException, IOException {
             try {
                 Delegator delegator = (Delegator) context.get("delegator");
                 List<GenericValue> portalPageColumns = null;
@@ -1933,8 +1998,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
                 String prevColumnSeqId = "";
 
                 // Iterates through the PortalPage columns
-                ListIterator <GenericValue>columnsIterator = portalPageColumns.listIterator();
-                while(columnsIterator.hasNext()) {
+                ListIterator<GenericValue> columnsIterator = portalPageColumns.listIterator();
+                while (columnsIterator.hasNext()) {
                     GenericValue columnValue = columnsIterator.next();
                     String columnSeqId = columnValue.getString("columnSeqId");
 
@@ -1958,8 +2023,8 @@ public abstract class ModelScreenWidget extends ModelWidget {
                     }
 
                     // Iterates through the Portlets in the Column
-                    ListIterator <GenericValue>portletsIterator = portalPagePortlets.listIterator();
-                    while(portletsIterator.hasNext()) {
+                    ListIterator<GenericValue> portletsIterator = portalPagePortlets.listIterator();
+                    while (portletsIterator.hasNext()) {
                         GenericValue portletValue = portletsIterator.next();
 
                         // If not the last portlet in the column, get the next nextPortletId and nextPortletSeqId
@@ -1981,10 +2046,12 @@ public abstract class ModelScreenWidget extends ModelWidget {
                         // Get portlet's attributes
                         portletAttributes = EntityQuery.use(delegator)
                                                        .from("PortletAttribute")
-                                                       .where("portalPageId", portletValue.get("portalPageId"), "portalPortletId", portletValue.get("portalPortletId"), "portletSeqId", portletValue.get("portletSeqId"))
+                                                       .where("portalPageId", portletValue.get("portalPageId"),
+                                                               "portalPortletId", portletValue.get("portalPortletId"),
+                                                               "portletSeqId", portletValue.get("portletSeqId"))
                                                        .queryList();
 
-                        ListIterator <GenericValue>attributesIterator = portletAttributes.listIterator();
+                        ListIterator<GenericValue> attributesIterator = portletAttributes.listIterator();
                         while (attributesIterator.hasNext()) {
                             GenericValue attribute = attributesIterator.next();
                             context.put(attribute.getString("attrName"), attribute.getString("attrValue"));
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/MultiBlockHtmlTemplateUtil.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/MultiBlockHtmlTemplateUtil.java
new file mode 100644
index 0000000..ee29af5
--- /dev/null
+++ b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/MultiBlockHtmlTemplateUtil.java
@@ -0,0 +1,228 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.ofbiz.widget.model;
+
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.apache.ofbiz.base.util.UtilGenerics;
+import org.apache.ofbiz.base.util.UtilValidate;
+
+public final class MultiBlockHtmlTemplateUtil {
+
+    public static final String MULTI_BLOCK_WRITER = "multiBlockWriter";
+    private static final String HTML_LINKS_FOR_HEAD = "htmlLinksForHead";
+    private static final String SCRIPT_LINKS_FOR_FOOT = "ScriptLinksForFoot";
+    private static int maxNumOfScriptInCache = 10;
+    private static int cacheSize = 100;
+    // store inline script from freemarker template by user session
+    private static LinkedHashMap<String, Map<String, String>> scriptCache =
+            new LinkedHashMap<String, Map<String, String>>() {
+                private static final long serialVersionUID = 1L;
+                protected boolean removeEldestEntry(Map.Entry<String, Map<String, String>> eldest) {
+                    return size() > 100; // TODO probably set to max number of concurrent user
+                }
+            };
+    // store the additional html import by screen location
+    private static LinkedHashMap<String, Set<String>> htmlImportCache =
+            new LinkedHashMap<String, Set<String>>() {
+                private static final long serialVersionUID = 1L;
+                protected boolean removeEldestEntry(Map.Entry<String, Set<String>> eldest) {
+                    return size() > 300; // TODO probably set to max number of screens
+                }
+            };
+    // store the child screen
+    private static LinkedHashMap<String, Set<String>> dependentScreenCache =
+            new LinkedHashMap<String, Set<String>>() {
+                private static final long serialVersionUID = 1L;
+                protected boolean removeEldestEntry(Map.Entry<String, Set<String>> eldest) {
+                    return size() > 100;
+                }
+            };
+
+    private MultiBlockHtmlTemplateUtil() { }
+
+    public static void collectChildScreenInfo(ModelScreen parentModelScreen, String location, String name) {
+        String key = parentModelScreen.getSourceLocation() + "#" + parentModelScreen.getName();
+        Set<String> childList = dependentScreenCache.get(key);
+        if (childList == null) {
+            childList = new LinkedHashSet<>();
+            dependentScreenCache.put(key, childList);
+        }
+        if (UtilValidate.isNotEmpty(location)) {
+            childList.add(location + "#" + name);
+        } else {
+            childList.add(parentModelScreen.getSourceLocation() + "#" + name);
+        }
+    }
+
+    public static void addLinksToHtmlImportCache(String location, String name, Set<String> urls) throws Exception {
+        if (UtilValidate.isEmpty(urls)) {
+            return;
+        }
+        String locHashName = location + "#" + name;
+        Set<String> existingUrls = htmlImportCache.get(locHashName);
+        if (existingUrls == null) {
+            existingUrls = new LinkedHashSet<>();
+            htmlImportCache.put(locHashName, existingUrls);
+        }
+        existingUrls.addAll(urls);
+    }
+
+    public static void addLinksToLayoutSettings(final Map<String, Object> context, String location, String name) throws Exception {
+        HttpServletRequest request = (HttpServletRequest) context.get("request");
+        if (request.getAttribute(HTML_LINKS_FOR_HEAD) == null) {
+            String currentLocationHashName = location + "#" + name;
+            request.setAttribute(HTML_LINKS_FOR_HEAD, currentLocationHashName);
+            return;
+        }
+        // check "layoutSettings.javaScripts" is not empty
+        Map<String, Object> layoutSettings = UtilGenerics.cast(context.get("layoutSettings"));
+        if (UtilValidate.isEmpty(layoutSettings)) {
+            return;
+        }
+        List<String> layoutSettingsJsList = UtilGenerics.cast(layoutSettings.get("javaScripts"));
+        if (UtilValidate.isEmpty(layoutSettingsJsList)) {
+            return;
+        }
+        Object objValue = request.getAttribute(HTML_LINKS_FOR_HEAD);
+        if (objValue instanceof String) {
+            String currentLocationHashName = (String) request.getAttribute(HTML_LINKS_FOR_HEAD);
+            Set<String> htmlLinks = new LinkedHashSet<>();
+            Set<String> locHashNameList = getRelatedScreenLocationHashName(currentLocationHashName);
+            for (String locHashName:locHashNameList) {
+                Set<String> urls = htmlImportCache.get(locHashName);
+                if (UtilValidate.isNotEmpty(urls)) {
+                    // check url is not already in layoutSettings.javaScripts
+                    for (String url : urls) {
+                        if (!htmlLinks.contains(url)) {
+                            htmlLinks.add(url);
+                        }
+                    }
+                }
+            }
+            if (UtilValidate.isNotEmpty(htmlLinks)) {
+                // check url is not already in layoutSettings.javaScripts
+                for (String url : htmlLinks) {
+                    if (!layoutSettingsJsList.contains(url)) {
+                        layoutSettingsJsList.add(url);
+                    }
+                }
+            }
+            request.setAttribute(HTML_LINKS_FOR_HEAD, true);
+        }
+
+    }
+
+    /**
+     * Get all the child screens including itself
+     * @param locationHashName
+     * @return
+     */
+    private static Set<String> getRelatedScreenLocationHashName(String locationHashName) {
+        Set<String> resultList = new HashSet<>();
+        resultList.add(locationHashName);
+        Set<String> locHashNameList = dependentScreenCache.get(locationHashName);
+        if (locHashNameList != null) {
+            for (String locHashName : locHashNameList) {
+                resultList.addAll(getRelatedScreenLocationHashName(locHashName));
+            }
+        }
+        return resultList;
+    }
+
+    /**
+     * add the script links that should be in the head tag
+     * @param context
+     * @param urls
+     */
+    private static void addJsLinkToLayoutSettings(final Map<String, Object> context, final Set<String> urls) {
+
+    }
+
+    /**
+     * add script link for page footer.
+     * @param context
+     * @param filePath
+     */
+    public static void addScriptLinkForFoot(final Map<String, Object> context, final String filePath) {
+        HttpServletRequest request = (HttpServletRequest) context.get("request");
+        Set<String> scriptLinks = UtilGenerics.cast(request.getAttribute(SCRIPT_LINKS_FOR_FOOT));
+        if (scriptLinks == null) {
+            // use of LinkedHashSet to maintain insertion order
+            scriptLinks = new LinkedHashSet<String>();
+            request.setAttribute(SCRIPT_LINKS_FOR_FOOT, scriptLinks);
+        }
+        scriptLinks.add(filePath);
+    }
+
+    /**
+     * get the script links for page footer. Also @see {@link ScriptTagsFooterTransform}
+     * @param request
+     * @return
+     */
+    public static Set<String> getScriptLinksForFoot(HttpServletRequest request) {
+        Set<String> scriptLinks = UtilGenerics.cast(request.getAttribute(SCRIPT_LINKS_FOR_FOOT));
+        return scriptLinks;
+    }
+
+    /**
+     * put script in cache for retrieval by the browser
+     * @param context
+     * @param fileName
+     * @param fileContent
+     */
+    public static void putScriptInCache(Map<String, Object> context, String fileName, String fileContent) {
+        HttpSession session = (HttpSession) context.get("session");
+        String sessionId = session.getId();
+        Map<String, String> scriptMap = UtilGenerics.cast(scriptCache.get(sessionId));
+        if (scriptMap == null) {
+            // use of LinkedHashMap to limit size of the map
+            scriptMap = new LinkedHashMap<String, String>() {
+                private static final long serialVersionUID = 1L;
+                protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
+                    return size() > maxNumOfScriptInCache;
+                }
+            };
+            scriptCache.put(sessionId, scriptMap);
+        }
+        scriptMap.put(fileName, fileContent);
+    }
+
+    /**
+     * Get the script stored in cache.
+     * @param session
+     * @param fileName
+     * @return script to be sent back to browser
+     */
+    public static String getScriptFromCache(HttpSession session, final String fileName) {
+        Map<String, String> scriptMap = UtilGenerics.cast(scriptCache.get(session.getId()));
+        if (scriptMap != null) {
+            return scriptMap.get(fileName);
+        }
+        return "";
+    }
+}
diff --git a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ScriptTemplateUtil.java b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ScriptTemplateUtil.java
deleted file mode 100644
index 7bce317..0000000
--- a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ScriptTemplateUtil.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.widget.model;
-
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSession;
-
-import org.apache.ofbiz.base.util.UtilGenerics;
-
-public class ScriptTemplateUtil {
-
-    private static String sessionKey = "ScriptTemplateMap";
-    private static String requestKey = "ScriptTemplateList";
-    private static int maxNumOfScriptInCache = 10;
-
-    private ScriptTemplateUtil() { }
-
-    /**
-     * add script src link for use by @see {@link org.apache.ofbiz.webapp.ftl.ScriptTemplateListTransform}
-     * @param context
-     * @param filePath
-     */
-    public static void addScriptSrcToRequest(final Map<String, Object> context, final String filePath) {
-        HttpServletRequest request = (HttpServletRequest) context.get("request");
-        Set<String> scriptTemplates = UtilGenerics.cast(request.getAttribute(requestKey));
-        if (scriptTemplates == null) {
-            // use of LinkedHashSet to maintain insertion order
-            scriptTemplates = new LinkedHashSet<String>();
-            request.setAttribute(requestKey, scriptTemplates);
-        }
-        scriptTemplates.add(filePath);
-    }
-
-    /**
-     * get the script src links collected from the html-template tags where multi-block=true.
-     * @param request
-     * @return
-     */
-    public static Set<String> getScriptSrcLinksFromRequest(HttpServletRequest request) {
-        Set<String> scriptTemplates = UtilGenerics.cast(request.getAttribute(requestKey));
-        return scriptTemplates;
-    }
-
-    /**
-     * put script in user session for retrieval by the browser
-     * @param context
-     * @param fileName
-     * @param fileContent
-     */
-    public static void putScriptInSession(Map<String, Object> context, String fileName, String fileContent) {
-        HttpSession session = (HttpSession) context.get("session");
-        Map<String, String> scriptTemplateMap = UtilGenerics.cast(session.getAttribute(sessionKey));
-        if (scriptTemplateMap == null) {
-            synchronized (session) {
-                scriptTemplateMap = UtilGenerics.cast(session.getAttribute(sessionKey));
-                if (scriptTemplateMap == null) {
-                    // use of LinkedHashMap to limit size of the map
-                    scriptTemplateMap = new LinkedHashMap<String, String>() {
-                        private static final long serialVersionUID = 1L;
-                        protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
-                            return size() > maxNumOfScriptInCache;
-                        }
-                    };
-                    session.setAttribute(sessionKey, scriptTemplateMap);
-                }
-            }
-        }
-        scriptTemplateMap.put(fileName, fileContent);
-    }
-
-    /**
-     * Get the script stored in user session.
-     * @param session
-     * @param fileName
-     * @return script to be sent back to browser
-     */
-    public static String getScriptFromSession(HttpSession session, final String fileName) {
-        Map<String, String> scriptTemplateMap = UtilGenerics.cast(session.getAttribute(sessionKey));
-        if (scriptTemplateMap != null) {
-            return scriptTemplateMap.get(fileName);
-        }
-        return null;
-    }
-}
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 9f4bd7e..6724ad7 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
@@ -58,6 +58,7 @@ import org.apache.ofbiz.widget.cache.GenericWidgetOutput;
 import org.apache.ofbiz.widget.cache.ScreenCache;
 import org.apache.ofbiz.widget.cache.WidgetContextCacheKey;
 import org.apache.ofbiz.widget.model.ModelScreen;
+import org.apache.ofbiz.widget.model.MultiBlockHtmlTemplateUtil;
 import org.apache.ofbiz.widget.model.ScreenFactory;
 import org.apache.ofbiz.widget.model.ThemeFactory;
 import org.xml.sax.SAXException;
@@ -137,8 +138,8 @@ public class ScreenRenderer {
             }
         } else {
             context.put("renderFormSeqNumber", String.valueOf(renderFormSeqNumber));
-            if (context.get("MultiBlockWriter") != null) {
-                StringWriter stringWriter = (StringWriter) context.get("MultiBlockWriter");
+            if (context.get(MultiBlockHtmlTemplateUtil.MULTI_BLOCK_WRITER) != null) {
+                StringWriter stringWriter = (StringWriter) context.get(MultiBlockHtmlTemplateUtil.MULTI_BLOCK_WRITER);
                 modelScreen.renderScreenString(stringWriter, context, screenStringRenderer);
             } else {
                 modelScreen.renderScreenString(writer, context, screenStringRenderer);
diff --git a/themes/common-theme/template/includes/CloseHtmlBody.ftl b/themes/common-theme/template/includes/CloseHtmlBody.ftl
index 6390ae7..9ad25cb 100644
--- a/themes/common-theme/template/includes/CloseHtmlBody.ftl
+++ b/themes/common-theme/template/includes/CloseHtmlBody.ftl
@@ -17,5 +17,5 @@ specific language governing permissions and limitations
 under the License.
 -->
 </body>
-<@scriptTemplateList/>
+<@scriptTagsFooter/>
 </html>
diff --git a/themes/common-theme/template/includes/LookupFooter.ftl b/themes/common-theme/template/includes/LookupFooter.ftl
index 1c9f388..e00660c 100644
--- a/themes/common-theme/template/includes/LookupFooter.ftl
+++ b/themes/common-theme/template/includes/LookupFooter.ftl
@@ -17,5 +17,5 @@ specific language governing permissions and limitations
 under the License.
 -->
   </body>
-  <@scriptTemplateList/>
+  <@scriptTagsFooter/>
 </html>
diff --git a/themes/common-theme/widget/Theme.xml b/themes/common-theme/widget/Theme.xml
index 9b708c5..3fee75f 100644
--- a/themes/common-theme/widget/Theme.xml
+++ b/themes/common-theme/widget/Theme.xml
@@ -76,7 +76,6 @@ under the License.
         <property name="VT_HDR_JAVASCRIPT['add']" value="/common/js/util/miscAjaxFunctions.js"/>
         <property name="VT_HDR_JAVASCRIPT['add']" value="/common/js/util/selectMultipleRelatedValues.js"/>
         <property name="VT_HDR_JAVASCRIPT['add']" value="/common/js/util/util.js"/>
-        <property name="VT_HDR_JAVASCRIPT['add']" value="/common/js/jquery/plugins/jsTree/jquery.jstree.js"/>
         <property name="VT_HDR_JAVASCRIPT['add']" value="/common/js/jquery/ui/js/jquery.cookie-1.4.0.js"/>
         <property name="VT_HDR_JAVASCRIPT['add']" value="/common/js/plugins/date/FromThruDateCheck.js"/>
         <property name="VT_HDR_JAVASCRIPT['add']" value="/common/js/util/application.js"/>
diff --git a/themes/flatgrey/template/Footer.ftl b/themes/flatgrey/template/Footer.ftl
index 3a65a83..67d70f9 100644
--- a/themes/flatgrey/template/Footer.ftl
+++ b/themes/flatgrey/template/Footer.ftl
@@ -42,5 +42,5 @@ under the License.
   </#list>
 </#if>
 </body>
-<@scriptTemplateList/>
+<@scriptTagsFooter/>
 </html>
diff --git a/themes/rainbowstone/template/includes/Footer.ftl b/themes/rainbowstone/template/includes/Footer.ftl
index 057ad23..2741617 100644
--- a/themes/rainbowstone/template/includes/Footer.ftl
+++ b/themes/rainbowstone/template/includes/Footer.ftl
@@ -34,5 +34,5 @@ under the License.
   </#list>
 </#if>
 </body>
-<@scriptTemplateList/>
+<@scriptTagsFooter/>
 </html>
diff --git a/themes/tomahawk/template/Footer.ftl b/themes/tomahawk/template/Footer.ftl
index baf94a8..1f27daa 100644
--- a/themes/tomahawk/template/Footer.ftl
+++ b/themes/tomahawk/template/Footer.ftl
@@ -42,5 +42,5 @@ under the License.
 
 </div>
 </body>
-<@scriptTemplateList/>
+<@scriptTagsFooter/>
 </html>