Custom Themes Tutorial

Creating custom themes in Scipio ERP is a rather straightforward endeavor and is in line with modern front-end methodologies. Because of the systems high level of standardization, you can switch out virtually every aspect of how Views are generated with your own theme. Due to the templating toolkit, themes contain not only Stylesheets and Javascript files, but also Freemarker Macros, which determine how the underlying HTML is generated. In short: Themes are your friend, if you want to customize your applications and add the little wow-factor for your user base.

Before we begin, it is a good idea to read up on the following principles:

 

This tutorial will cover the creation of a new shop theme. The steps for creating a shop theme or an admin theme do not differ and so you will be able to apply the techniques learned here to any theme you create. We will base our custom theme on a bootstrap 4 template and the “ignite-shop” theme.

The latter is not a requirement, in fact you can built your own theme from scratch, but will merely serve as a time-saver for us here. The ignite-component comes with clean bootstrap 4 build tools and css & html definitions, which we will become useful for the actual modification of the theme. This tutorial will guide you through all necessary steps and will enable you to build a new theme regardless. Likewise, you do not need to base your theme on Bootstrap at all – the concepts do not change.

As for the design, there are plenty of templates available on the net and even some free ones. I laid my eyes on MDB Bootstrap for this excercise. MDB Bootstrap is a free Bootstrap 4 theme that is pretty close to the Bootstrap 4 HTML & class patterns and therefore easy to adapt. It also happens to be a free for personal and commercial use and sources are available on GitHub, too.

If you simply want to customize an existing Scipio ERP theme, then you can also largely ignore the html & css definitions and jump right into the theme modification. The webapp folder, including its frontend build tools and sources will be your friend in this case. All Scipio ERP themes follow modern frontend development standards and come with a base instruction on how to modify the sources and rebuild your own compressed source files.

About theme components

Before jumping into the actual customization, it is best to familiarize yourself with the structure of theme components. In principle, a theme is a custom plugin component, which gets loaded alongside the other components and contains:

 

  1. “ofbiz-component.xml” & “build.xml” file
    These contain the web-app definition and link to the seed data we want to load on installation.
  2. “data” folder, containing Seed Data
    These are database entries that are loaded during system installation and register the Visual Theme in the system. They also contain:

    1. Templates we want to use to render specific overarching sections of the Decorator.
    2. Stylesheet includes
    3. Javascript includes
    4. Variable definitions (Have a look out for: “VT_STL_VAR_LOC”)
    5. Templating Toolkit Overrides (“VT_STL_TMPLT_LOC”)
  3. “includes” folder, containing all custom templates and variable/toolkit definitions
    The most important files are “themeStyles.groovy” and “themeTemplates.ftl” – these contain your html, as well as CSS class definitions
  4. “Webapp” directory
    This is where your web-application content is stored. Usually this will be where your resources are loaded from.

Step 1: A custom component

Every new theme requires a new theme component. Since components can inherit aspects from one another, it is usually a good idea to start by copying an existing theme component and modify it afterwards.For our effort, we will start by copying the “ignite shop” component into our themes directory and rename the folder while we are at it (If you do not want to use the ignite-component, you can also use foundation-shop as a basis. Please be aware, however, that in that case, you will be required to add additional html & css class definitions at a later stage).

Next, we will replace the following references in our files (keep your case-sensitivity on, when you do a mass replace):

  • “ignite-shop-theme” to “my-shop-theme”
  • “Ignite Shop” to “My Shop”
  • “IGNITE” to “MY_CUSTOM”
  • “Ignite” to “MyCustom”
  • “ignite” to “mytheme”

Next rename the ignite folder located under /webapp to “mytheme” and change the name of the IgniteShopThemeData.xml (in data folder) to “MyCustomShopThemeData.xml”.

Your component structure should resemble this now:

tutorial-1

Step 2: Adding the theme

We now add our own design template to our theme, by copying the theme files directly into the webapp/mytheme directory. Since I am using MDB here, I will now initialize the build tools for it. If you don’t plan on using SASS for the Stylesheets or if your own theme comes with its own set of frontend tools, you can of course skip this step.

 


cd themes\my-theme\webapp\mytheme
bower install MDBootstrap
npm install -g grunt-cli
npm install grunt-sass --save-dev
npm install grunt-contrib-watch --save-dev
npm install grunt-contrib-copy --save-dev

 

This will essentially setup my frontend build tools, so that I can recompile my stylesheets using SASS and update my sources whenever I please. It will automatically download the depencency for me and add them to the newly created bower_components sub directory.

Now to recompile the sources, create a new file called ‘Gruntfile.js’ and add the following content:

 

module.exports = function (grunt) {
 grunt.initConfig({
 pkg: grunt.file.readJSON('package.json'),

 // Copy web assets from bower_components to more convenient directories.
 copy: {
 main: {
 files: [
 // Vendor scripts.
 {
 expand: true,
 cwd: 'bower_components/bootstrap-sass/assets/javascripts/',
 src: ['**/*.js'],
 dest: 'scripts/bootstrap-sass/'
 },
 {
 expand: true,
 cwd: 'bower_components/jquery/dist/',
 src: ['**/*.js', '**/*.map'],
 dest: 'scripts/jquery/'
 },

 // Fonts.
 {
 expand: true,
 filter: 'isFile',
 flatten: true,
 cwd: 'bower_components/',
 src: ['bootstrap-sass/assets/fonts/**'],
 dest: 'fonts/'
 },

 // Stylesheets
 {
 expand: true,
 cwd: 'bower_components/bootstrap-sass/assets/stylesheets/',
 src: ['**/*.scss'],
 dest: 'sass/'
 }
 ]
 },
 },

 // Compile SASS files into minified CSS.
 sass: {
 options: {
 includePaths: ['bower_components/bootstrap-sass/assets/stylesheets','sass']
 },
 dist: {
 options: {
 outputStyle: 'compressed'
 },
 files: {
 'css/mdb.min.css': 'sass/mdb.scss'
 }
 }
 },

 // Watch these files and notify of changes.
 watch: {
 grunt: { files: ['Gruntfile.js'] },

 sass: {
 files: [
 'sass/**/*.scss'
 ],
 tasks: ['sass']
 }
 }
 });

 // Load externally defined tasks. 
 grunt.loadNpmTasks('grunt-sass');
 grunt.loadNpmTasks('grunt-contrib-watch');
 grunt.loadNpmTasks('grunt-contrib-copy');

 // Establish tasks we can run from the terminal.
 grunt.registerTask('build', ['sass', 'copy']);
 grunt.registerTask('default', ['build', 'watch']);
}

 

You can now run the ‘grunt’ command from command line to recompile all your sources. We have succeeded in setting up the theme, now we must register it with the system.

In order for the visual theme to find its resources, we must edit the seed data in data/MyCustomShopThemeData.xml and add all the JS and CSS includes. Once you open the file, you will notice that it contains a long list of xml entries, such as:


<VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_TOP_JAVASCRIPT" resourceValue="" sequenceId="01"/>

 

That is where the assigns are happening. Since we already mass replaced all entries in our previous step, we can leave the visualThemeId untouched and simply update the resourceValues to our liking. The resourceTypeEnumId represents the location in which something is loaded. All of which will be interpreted by our Decorator and transferred into the html structure. The following resourceTypeEnumIds are most common:

  • VT_STYLESHEET (Stylesheet)
  • VT_RTL_STYLESHEET (Right-to-left specific stylesheet – replaces VT_STYLESHEET)
  • VT_TOP_JAVASCRIPT (Javascript loaded in the header a page)
  • VT_FTPR_JAVASCRIPT (Javascript loaded in the footer of a page)

You can determine the load order of the resources by assigning a sequenceId, the resourceValue will contain our paths to the themes resources.

We can now add our own Styles to VT_STYLES and Javascript files (except for jquery) to the footer of the page, so that the resources get loaded correctly with the theme:

 



<entity-engine-xml>
 <VisualTheme visualThemeId="MY_CUSTOM_SHOP" visualThemeSetId="ECOMMERCE" description="Custom Shop - My first Boostrap CSS Shop Theme"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_THEME_DATA_RES" resourceValue="component://my-shop-theme/data/MyCustomShopThemeData.xml" sequenceId="01"/>
 
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_NAME" resourceValue="MY_CUSTOM_SHOP" sequenceId="01"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_SCREENSHOT" resourceValue="/my-shop-theme/screenshot.jpg" sequenceId="01"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_HDR_TMPLT_LOC" resourceValue="component://my-shop-theme/includes/header.ftl" sequenceId="01"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_MSG_TMPLT_LOC" resourceValue="component://my-shop-theme/includes/messages.ftl" sequenceId="01"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_FTR_TMPLT_LOC" resourceValue="component://my-shop-theme/includes/footer.ftl" sequenceId="01"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_NAV_TMPLT_LOC" resourceValue="component://my-shop-theme/includes/appbar.ftl" sequenceId="01"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_NAV_OPEN_TMPLT" resourceValue="component://my-shop-theme/includes/appbarOpen.ftl" sequenceId="01"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_NAV_CLOSE_TMPLT" resourceValue="component://my-shop-theme/includes/appbarClose.ftl" sequenceId="01"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_STL_VAR_LOC" resourceValue="component://my-shop-theme/includes/themeStyles.groovy" sequenceId="01"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_STL_TMPLT_LOC" resourceValue="component://my-shop-theme/includes/themeTemplate.ftl" sequenceId="01"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_MENU_MACRO_LIB" resourceValue="component://my-shop-theme/includes/htmlMenuMacroLibrary.ftl" sequenceId="01"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_SHORTCUT_ICON" resourceValue="/base-theme/images/favicon.ico" sequenceId="01"/>

 <!-- SCIPIO: Set custom logo -->
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_HDR_IMAGE_URL" resourceValue="/base-theme/images/scipio-logo.png" sequenceId="01"/>

 <!-- SCIPIO: The following includes can be used to override specific default pages
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_ERROR_TMPLT_LOC" resourceValue="component://my-shop-theme/includes/error.ftl" sequenceId="01"/> // Custom Error Page
 --> 
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_LOGIN_TMPLT_LOC" resourceValue="component://my-shop-theme/includes/login.ftl" sequenceId="01"/> 

 
 <!-- GENERAL -->
 <!-- Styles -->
 <!-- Scripts -->
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_TOP_JAVASCRIPT" resourceValue="/my-shop-theme/js/jquery-3.2.1.min.js" sequenceId="01"/>
 
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_TOP_JAVASCRIPT" resourceValue="/my-shop-theme/js/popper.min.js" sequenceId="02"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_TOP_JAVASCRIPT" resourceValue="/my-shop-theme/js/bootstrap.min.js" sequenceId="03"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_TOP_JAVASCRIPT" resourceValue="/my-shop-theme/js/mdb.min.js" sequenceId="04"/>
 
 <!-- THEME SPECIFIC -->
 <!-- Styles -->
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_STYLESHEET" resourceValue="/my-shop-theme/css/bootstrap.min.css" sequenceId="02"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_STYLESHEET" resourceValue="/my-shop-theme/css/mdb.min.css" sequenceId="03"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_STYLESHEET" resourceValue="/my-shop-theme/css/style.css" sequenceId="04"/>

 
 
 <!-- Not required for shop
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_STYLESHEET" resourceValue="/base-theme/bower_components/trumbowyg/dist/ui/trumbowyg.min.css" sequenceId="13"/>-->
 
 <!-- Right-to-Left support
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_RTL_STYLESHEET" resourceValue="" sequenceId="01"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_RTL_STYLESHEET" resourceValue="" sequenceId="02"/>-->
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_STYLESHEET" resourceValue="/base-theme/bower_components/font-awesome/css/font-awesome.min.css" sequenceId="10"/>
 
 
 <!-- Scipio Scripts -->
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_FTPR_JAVASCRIPT" resourceValue="/base-theme/bower_components/Chart.js/dist/Chart.min.js" sequenceId="03"/>
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_FTPR_JAVASCRIPT" resourceValue="/base-theme/bower_components/sass-to-js/js/dist/sass-to-js.min.js" sequenceId="04"/>
 
 
 <!-- Theme
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_FTPR_JAVASCRIPT" resourceValue="" sequenceId="05"/>-->
 
 
 <!--// OWL Slider http://www.owlcarousel.owlgraphic.com/ (To activate, uncomment the following and switch the slider macro library to "owl", e.g.: <@slider library="owl")
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_STYLESHEET" resourceValue="/base-theme/bower_components/owlcarousel/owl-carousel/owl.carousel.css" sequenceId="60"/> 
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_STYLESHEET" resourceValue="/base-theme/bower_components/owlcarousel/owl-carousel/owl.theme.css" sequenceId="61"/> 
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_FTPR_JAVASCRIPT" resourceValue="/base-theme/bower_components/owlcarousel/owl-carousel/owl.carousel.min.js" sequenceId="62"/> -->

 <!--// Slick Slider http://kenwheeler.github.io/slick/ (To activate, uncomment the following and switch the chart slider library to "slick", e.g.: <@slider library="owl") -->
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_STYLESHEET" resourceValue="/base-theme/bower_components/slick-carousel/slick/slick.css" sequenceId="63"/> 
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_STYLESHEET" resourceValue="/base-theme/bower_components/slick-carousel/slick/slick-theme.css" sequenceId="64"/> 
 <VisualThemeResource visualThemeId="MY_CUSTOM_SHOP" resourceTypeEnumId="VT_FTPR_JAVASCRIPT" resourceValue="/base-theme/bower_components/slick-carousel/slick/slick.js" sequenceId="65"/>
 
</entity-engine-xml>

Note that we did not touch the other includes. This is so that we can still use the templating toolkits “slider” macros and remain compatible to the theme component structure in general.

We now load the theme into our system – either by reseeding entirely (there is a handy ant command for this: ./ant load-extseed) or by using our webinterface once we have started our system: https://localhost:8443/admin/control/EntityImport – we can copy paste the entire document into the window there.

Once installed, you can reload the visual themes and all their seed information with a handy tool in your Admin backoffice, the Entity Utitilties. We will use this subsequently to just reload our theme includes with the push of a button – if we ever wanted to add a new resource.

The theme should now be available as a dropdown option in the Catalog app under Store/ScipioShop (or your storename)/ Visual Theme. We head there and select the theme from the dropdown.

Step 3: Theme Template Changes

Now that we have covered the basic aspects of loading a theme, we can move on to the more difficult aspects of creating a theme: making the Stylesheets compatible with our HTML. Luckily, everything has been standardized in Scipio ERP and the templating toolkit renders everything according to some html definitions and default class assignments. So, in order for the theme to properly work, we want to assign the proper html to our templating toolkit and theme templates.

There are three places in a theme that can influence the html:

  1. Basic templates that are loaded directly by the Decorator (These contain the overarching screen features, like a navigation, a footer and so on)
  2. Html macro overrides (located in includes/themeTemplate.ftl)
  3. Theme variables and class assignments (located in includes/themeStyles.groovy)

 

It is usually best to work through the basic templates first, before moving on to the html macros or variable definitions, simply because it will allow us to check back with the shop for all the changes that we do. So let’s check the header.ftl and modify it according to the theme definitions (includes/header.ftl). I will base the modifications on the sample outlined by MDB:


<#-- Applications -->
<#include "common.ftl">

<#-- Applications -->
<#if (requestAttributes.externalLoginKey)??><#assign externalKeyParam = "?externalLoginKey=" + requestAttributes.externalLoginKey!></#if>
<#if externalLoginKey??><#assign externalKeyParam = "?externalLoginKey=" + requestAttributes.externalLoginKey!></#if>
<#assign ofbizServerName = application.getAttribute("_serverId")!"default-server">
<#assign contextPath = request.getContextPath()>
<#if person?has_content>
 <#assign userName = (person.firstName!"") + " " + (person.middleName!"") + " " + (person.lastName!"")>
<#elseif partyGroup?has_content>
 <#assign userName = partyGroup.groupName!>
<#elseif userHasAccount><#-- NOTE: see common.ftl for userHasAccount setup -->
 <#assign userName = userLogin.userLoginId>
<#else>
 <#assign userName = "">
</#if>
<#if defaultOrganizationPartyGroupName?has_content>
 <#assign orgName = " - " + defaultOrganizationPartyGroupName!>
<#else>
 <#assign orgName = "">
</#if>
<#macro generalMenu>
 <#if userHasAccount>
 <#--
 <#if layoutSettings.topLines?has_content>
 <#list layoutSettings.topLines as topLine>
 <#if topLine.text??>
 <li>${topLine.text}<a href="${rawString(topLine.url!)}${rawString(externalKeyParam)}">${topLine.urlText!}</a></li>
 <#elseif topLine.dropDownList??>
 <li><#include "component://common/webcommon/includes/insertDropDown.ftl"/></li>
 <#else>
 <li>${topLine!}</li>
 </#if>
 </#list>
 <#else>
 <li>${userLogin.userLoginId}</li>
 </#if>
 -->
 <li><a href="<@ofbizUrl>orderhistory</@ofbizUrl>" class="nav-link dropdown-item">${uiLabelMap.CommonOrders}</a></li><#--uiLabelMap.EcommerceOrderHistory-->
 <#-- TODO: Ofbiz/ecommerce supports more (above are bare essentials only):
 <li><a href="<@ofbizUrl>messagelist</@ofbizUrl>">${uiLabelMap.CommonMessages}</a></li>
 <li><a href="<@ofbizUrl>ListQuotes</@ofbizUrl>">${uiLabelMap.OrderOrderQuotes}</a></li>
 <li><a href="<@ofbizUrl>ListRequests</@ofbizUrl>">${uiLabelMap.OrderRequests}</a></li>
 <li><a href="<@ofbizUrl>editShoppingList</@ofbizUrl>">${uiLabelMap.EcommerceShoppingLists}</a></li>
 -->
 <li><a href="<@ofbizUrl>viewprofile</@ofbizUrl>" class="nav-link dropdown-item">${uiLabelMap.CommonProfile}</a></li>

 <#-- not implemented for shop, belongs to profile settings: <li><a href="<@ofbizUrl>ListLocales</@ofbizUrl>">${uiLabelMap.CommonLanguageTitle}</a></li>-->
 <#-- not implemented for shop: <li><a href="<@ofbizUrl>ListVisualThemes</@ofbizUrl>">${uiLabelMap.CommonVisualThemes}</a></li>-->
 <#else>
 <#-- language select for anon users 
 MOVED to icon
 <li><a href="<@ofbizUrl>ListLocales</@ofbizUrl>">${uiLabelMap.CommonLanguageTitle}</a></li> -->
 </#if>
 <#--
 <#if parameters.componentName?? && requestAttributes._CURRENT_VIEW_?? && helpTopic??>
 <#include "component://common/webcommon/includes/helplink.ftl" />
 </#if>-->
 <#if userHasAccount>
 <li class="divider"></li>
 </#if>
 <#-- Now show this even for anon, unless it's anon without a party -->
 <#if userIsKnown>
 <li class="active"><a href="<@ofbizUrl>logout</@ofbizUrl>" class="nav-link dropdown-item active">${uiLabelMap.CommonLogout}</a></li>
 </#if>
</#macro>

<#-- in theory there is a transform that converts the selected menu to a proper list on these screens. It is never used by any of the other ofbiz screens, however and poorly documented
so for now we have to split the screens in half and rely on the menu widget renderer to get the same effect
<#macro currentAppMenu>
 <#if appModelMenu?has_content>
 <li class="has-dropdown not-click active"><a href="#">${title!"TEST"}</a>
 <ul class="dropdown">
 
 </ul>
 </li>
 </#if>
</#macro>-->

<#macro logoMenu hasLink=true isSmall=false>
 <#if layoutSettings.headerImageUrl??>
 <#assign headerImageUrl = layoutSettings.headerImageUrl>
 <#elseif layoutSettings.commonHeaderImageUrl??>
 <#assign headerImageUrl = layoutSettings.commonHeaderImageUrl>
 <#elseif layoutSettings.VT_HDR_IMAGE_URL??>
 <#assign headerImageUrl = layoutSettings.VT_HDR_IMAGE_URL.get(0)>
 </#if>
 <#if headerImageUrl??>
 <#if organizationLogoLinkURL?has_content>
 <#if hasLink><a href="<@ofbizUrl>${logoLinkURL}</@ofbizUrl>" class="navbar-brand"></#if><img src="${headerImageUrl!}" style="height:30px;margin-right:10px;" class="d-inline-block align-top" /> My Shop<#if hasLink></a></#if>
 <#else><#if hasLink><a href="<@ofbizUrl>${logoLinkURL}</@ofbizUrl>" class="navbar-brand"></#if><img src="${headerImageUrl!}" style="height:30px;margin-right:10px;" class="d-inline-block align-top" /> My Shop<#if hasLink></a></#if>
 </#if>
 <#else>
 <a href="<@ofbizUrl>${logoLinkURL!""}</@ofbizUrl>" class="navbar-brand"><img src="${headerImageUrl!}" style="height:30px;margin-right:10px;" class="d-inline-block align-top" /> My Shop</a>
 </#if>
</#macro>

<#macro rightMenu>
 <#-- SCIPIO: NOTE: We must display something for the anonymous user that has a partyId
 attached (created during anon checkout), because otherwise he has no way to clear his session.
 His temporary partyId is now (and must be) kept after checkout is done, for technical reasons,
 but also it's very convenient. 
 Presence of userLogin.partyId is what marks the difference. -->
 <#---->
 <#if userIsKnown>
 <li class="nav-item ml-2">
 <#if userIsAnon>
 <#assign person = delegator.findOne("Person", {"partyId":userLogin.partyId}, true)!>
 <#if person?has_content>
 <#assign welcomeName = person.firstName!userLogin.userLoginId>
 <#else>
 <#assign partyGroup = delegator.findOne("PartyGroup", {"partyId":userLogin.partyId}, true)!>
 <#if partyGroup?has_content>
 <#assign welcomeName = partyGroup.groupName!userLogin.userLoginId>
 <#else>
 <#-- Use userLoginId ("anonymous") as the fallback for now; the partyId will be a random number, kind of insulting -->
 <#assign welcomeName = userLogin.userLoginId>
 </#if>
 </#if>
 <#else>
 <#-- NOTE: This is a bit inconsistent with the anon user -->
 <#assign welcomeName = userLogin.userLoginId>
 </#if>
 <li class="nav-item ml-2 dropdown">
 <a href="#" class="nav-link waves-effect waves-light dropdown-toggle" data-toggle="dropdown"><i class="fa fa-user"></i> ${userLogin.userLoginId} </a>
 <ul class="dropdown-menu dropdown-menu-right">
 <@generalMenu />
 </ul>
 </li>
 <#else>
 <li class="nav-item ml-2 dropdown hidden-md-down">
 <a href="<@ofbizUrl>${checkLoginUrl}</@ofbizUrl>" class="nav-link waves-effect waves-light"><i class="fa fa-user"></i> ${uiLabelMap.CommonLogin}</a>
 </li>
 </#if>
</#macro>

 <@scripts output=true> <#-- ensure @script elems here will always output -->

 <title>${layoutSettings.companyName}<#if title?has_content>: ${title}<#elseif titleProperty?has_content>: ${uiLabelMap[titleProperty]}</#if></title>
 
 <#if layoutSettings.shortcutIcon?has_content>
 <#assign shortcutIcon = layoutSettings.shortcutIcon/>
 <#elseif layoutSettings.VT_SHORTCUT_ICON?has_content>
 <#assign shortcutIcon = layoutSettings.VT_SHORTCUT_ICON.get(0)/>
 </#if>
 <#if shortcutIcon?has_content>
 <link rel="shortcut icon" href="<@ofbizContentUrl>${rawString(shortcutIcon)}</@ofbizContentUrl>" />
 </#if>
 
 <#if layoutSettings.styleSheets?has_content>
 <#--layoutSettings.styleSheets is a list of style sheets. So, you can have a user-specified "main" style sheet, AND a component style sheet.-->
 <#list layoutSettings.styleSheets as styleSheet>
 <link rel="stylesheet" href="<@ofbizContentUrl>${rawString(styleSheet)}</@ofbizContentUrl>" type="text/css"/>
 </#list>
 </#if>
 <#if layoutSettings.VT_STYLESHEET?has_content>
 <#list layoutSettings.VT_STYLESHEET as styleSheet>
 <link rel="stylesheet" href="<@ofbizContentUrl>${rawString(styleSheet)}</@ofbizContentUrl>" type="text/css"/>
 </#list>
 </#if>
 <#if layoutSettings.rtlStyleSheets?has_content && langDir == "rtl">
 <#--layoutSettings.rtlStyleSheets is a list of rtl style sheets.-->
 <#list layoutSettings.rtlStyleSheets as styleSheet>
 <link rel="stylesheet" href="<@ofbizContentUrl>${rawString(styleSheet)}</@ofbizContentUrl>" type="text/css"/>
 </#list>
 </#if>
 <#if layoutSettings.VT_RTL_STYLESHEET?has_content && langDir == "rtl">
 <#list layoutSettings.VT_RTL_STYLESHEET as styleSheet>
 <link rel="stylesheet" href="<@ofbizContentUrl>${rawString(styleSheet)}</@ofbizContentUrl>" type="text/css"/>
 </#list>
 </#if>
 
 <#-- VT_TOP_JAVASCRIPT must always come before all others and at top of document -->
 <#if layoutSettings.VT_TOP_JAVASCRIPT?has_content>
 <#assign javaScriptsSet = toSet(layoutSettings.VT_TOP_JAVASCRIPT)/>
 <#list layoutSettings.VT_TOP_JAVASCRIPT as javaScript>
 <#if javaScriptsSet.contains(javaScript)>
 <#assign nothing = javaScriptsSet.remove(javaScript)/>
 <@script src=makeOfbizContentUrl(javaScript) />
 </#if>
 </#list>
 </#if>

 <#-- VT_PRIO_JAVASCRIPT should come right before javaScripts (always move together with javaScripts) -->
 <#if layoutSettings.VT_PRIO_JAVASCRIPT?has_content>
 <#assign javaScriptsSet = toSet(layoutSettings.VT_PRIO_JAVASCRIPT)/>
 <#list layoutSettings.VT_PRIO_JAVASCRIPT as javaScript>
 <#if javaScriptsSet.contains(javaScript)>
 <#assign nothing = javaScriptsSet.remove(javaScript)/>
 <@script src=makeOfbizContentUrl(javaScript) />
 </#if>
 </#list>
 </#if>
 <#if layoutSettings.javaScripts?has_content>
 <#--layoutSettings.javaScripts is a list of java scripts. -->
 <#-- use a Set to make sure each javascript is declared only once, but iterate the list to maintain the correct order -->
 <#assign javaScriptsSet = toSet(layoutSettings.javaScripts)/>
 <#list layoutSettings.javaScripts as javaScript>
 <#if javaScriptsSet.contains(javaScript)>
 <#assign nothing = javaScriptsSet.remove(javaScript)/>
 <@script src=makeOfbizContentUrl(javaScript) />
 </#if>
 </#list>
 </#if>
 <#if layoutSettings.VT_HDR_JAVASCRIPT?has_content>
 <#assign javaScriptsSet = toSet(layoutSettings.VT_HDR_JAVASCRIPT)/>
 <#list layoutSettings.VT_HDR_JAVASCRIPT as javaScript>
 <#if javaScriptsSet.contains(javaScript)>
 <#assign nothing = javaScriptsSet.remove(javaScript)/>
 <@script src=makeOfbizContentUrl(javaScript) />
 </#if>
 </#list>
 </#if>
 <#if layoutSettings.VT_EXTRA_HEAD?has_content>
 <#list layoutSettings.VT_EXTRA_HEAD as extraHead>
 ${extraHead}
 </#list>
 </#if>
 <#if lastParameters??><#assign parametersURL = "&amp;" + lastParameters></#if>
 <#if layoutSettings.WEB_ANALYTICS?has_content>
 <@script>
 <#list layoutSettings.WEB_ANALYTICS as webAnalyticsConfig>
 ${rawString(webAnalyticsConfig.webAnalyticsCode!)}
 </#list>
 </@script>
 </#if>

 </@scripts>
</head>
<#if layoutSettings.headerImageLinkUrl??>
 <#assign logoLinkURL = "${layoutSettings.headerImageLinkUrl}">
<#else>
 <#assign logoLinkURL = "${layoutSettings.commonHeaderImageLinkUrl}">
</#if>
<#assign organizationLogoLinkURL = "${layoutSettings.organizationLogoLinkUrl!}">
<body class="app<#if activeApp?has_content>app-${activeApp}</#if><#if parameters._CURRENT_VIEW_?has_content> page-${parameters._CURRENT_VIEW_!}</#if> <#if userLogin??>page-auth<#else>page-noauth</#if>">
 <#-- ================================
 SOCIAL LOGIN 
 ================================
 -->
 <#-- Facebook Authentication Addon (required)-->
 <#if getPropertyMsg("shop.properties","facebook.enabled")== "Y">
 <#include "component://auth-facebook/webapp/facebook/fb-common.ftl"/>
 <@fbInit scope="public_profile,email"/>
 </#if>
 <#-- Google Authentication Addon (required)-->
 <#if getPropertyMsg("shop.properties","google.enabled")== "Y">
 <#include "component://auth-google/webapp/google/google-common.ftl"/>
 <@googleInit/>
 </#if>
 <#-- Twitter Authentication Addon (required)-->
 <#if getPropertyMsg("shop.properties","twitter.enabled")== "Y">
 <#include "component://auth-twitter/webapp/facebook/fb-twitter.ftl"/>
 <@twitterInit/>
 </#if>
 <#-- ================================ -->
 <!-- Navigation -->
 <header>
 <nav class="navbar navbar-expand-lg navbar-dark bg-elegant">
 
 <!-- Brand and toggle get grouped for better mobile display -->
 <#--<button class="navbar-toggler mobile-sidebar-toggler hidden-lg-up" type="button">&#9776;</button>-->
 <@logoMenu isSmall=true/>
 
 <ul class="nav navbar-nav navbar-left" id="left-nav">
 <li class="nav-item ml-2 hidden-md-down">
 <@render resource="component://shop/widget/CatalogScreens.xml#keywordsearchbox" />
 </li>
 </ul>
 
 <ul class="nav navbar-nav navbar-right ml-auto" id="right-nav">
 <#-- Messages
 <li class="dropdown">
 <a href="#" class="nav-link waves-effect waves-light dropdown-toggle" data-toggle="dropdown"><i class="fa fa-envelope"></i> <b class="caret"></b></a>
 <ul class="dropdown-menu message-dropdown">
 <li class="message-preview">
 <a href="#">
 <div class="media">
 <span class="pull-left">
 <img class="media-object" src="http://placehold.it/50x50" alt=""/>
 </span>
 <div class="media-body">
 <h5 class="media-heading"><strong>John Smith</strong>
 </h5>
 <p class="small text-muted"><i class="fa fa-clock-o"></i> Yesterday at 4:32 PM</p>
 <p>Lorem ipsum dolor sit amet, consectetur...</p>
 </div>
 </div>
 </a>
 </li>
 <li class="message-footer">
 <a href="#">Read All New Messages</a>
 </li>
 </ul>
 </li>
 -->
 <#-- Alerts
 <li class="dropdown">
 <a href="#" class="nav-link waves-effect waves-light dropdown-toggle" data-toggle="dropdown"><i class="fa fa-bell"></i> <b class="caret"></b></a>
 <ul class="dropdown-menu alert-dropdown">
 <li class="nav-item ml-2">
 <a href="#">Alert Name <span class="label label-default">Alert Badge</span></a>
 </li>
 <li class="nav-item ml-2">
 <a href="#">Alert Name <span class="label label-primary">Alert Badge</span></a>
 </li>
 <li class="nav-item ml-2">
 <a href="#">Alert Name <span class="label label-success">Alert Badge</span></a>
 </li>
 <li class="nav-item ml-2">
 <a href="#">Alert Name <span class="label label-info">Alert Badge</span></a>
 </li>
 <li class="nav-item ml-2">
 <a href="#">Alert Name <span class="label label-warning">Alert Badge</span></a>
 </li>
 <li class="nav-item ml-2">
 <a href="#">Alert Name <span class="label label-danger">Alert Badge</span></a>
 </li>
 <li class="divider"></li>
 <li class="nav-item ml-2">
 <a href="#">View All</a>
 </li>
 </ul>
 </li>
 -->
 <@rightMenu/>
 <#assign showHeadActn = (showHeaderActions!true) == true && (useMinimalTheme!false) == false>
 <#if showHeadActn>
 <li class="nav-item ml-2"><@render resource="component://shop/widget/CartScreens.xml#microcart" /></li>
 </#if>
 </ul>
 </nav>
 </header>

This looks like a lot more work than it actually is, as most of the html was already matching the example. Now to the footer (includes/footer.ftl):


<#include "common.ftl">
 

 <!-- /.container-fluid -->

 <!-- /#page-wrapper -->

 <!-- /#wrapper -->
 </main>
 </div>
<#-- FOOTER SECTION -->
<footer class="page-footer center-on-small-only stylish-color-dark">

<#if (showFooterOtherContent!true) == true && (useMinimalTheme!false) == false>
 <div class="other-content">
 <div class="container">
 <@row>
 <@cell columns=3>
 <#-- <i class="${styles.icon} ${styles.icon_prefix}laptop"></i>-->
 <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cum maiores alias ea sunt facilis impedit fuga dignissimos illo quaerat iure in nobis id quos, eaque nostrum! Unde, voluptates suscipit repudiandae!</p>
 </@cell>
 <@cell columns=3>
 <#-- <i class="${styles.icon} ${styles.icon_prefix}html5"></i>-->
 <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Fugit impedit consequuntur at! Amet sed itaque nostrum, distinctio eveniet odio, id ipsam fuga quam minima cumque nobis veniam voluptates deserunt!</p>
 </@cell>
 <@cell columns=3>
 <@heading>SCIPIO Webstore</@heading>
 <ul class="other-links">
 <#-- language select --> 
 <li>
 <a href="<@ofbizUrl><#if userHasAccount>viewprofile<#else>ListLocales</#if></@ofbizUrl>">
 ${uiLabelMap.CommonChooseLanguage}
 </a>
 </li>
 <li><a href="<@ofbizUrl>showAllPromotions</@ofbizUrl>">${uiLabelMap.ProductPromotions}</a></li>
 <li><a href="<@ofbizUrl>license</@ofbizUrl>">License</a></li> 
 <li><a href="https://www.scipio-erp.com/products/faq">FAQ's</a></li>
 </ul>
 </@cell>
 <@cell columns=3> 
 <@heading>Follow Us!</@heading>
 <ul class="other-links">
 <li><a href="#">GitHub</a></li>
 <li><a href="#">Facebook</a></li>
 <li><a href="#">Twitter</a></li>
 <li><a href="#">Instagram</a></li>
 </ul>
 </@cell>
 </@row>
 </div>
 </div>
</#if> 
 <hr class="clearfix w-100 d-md-none">
 <div class="footer-copyright">
 <div class="container-fluid">
 ${uiLabelMap.CommonCopyright} (c) 2014-${nowTimestamp?string("yyyy")} <a href="https://www.ilscipio.com" target="_blank">ilscipio GmbH</a>.<span class="float-right">${uiLabelMap.CommonPoweredBy} <a href="http://www.scipioerp.com" target="_blank">SCIPIO ERP</a>. <#include "ofbizhome://runtime/svninfo.ftl" /> <#include "ofbizhome://runtime/gitinfo.ftl" /></span>
 </div>
 </div>
</footer> <#-- END FOOTER -->

<@scripts output=true> <#-- ensure @script elems here will always output -->
 <#-- New in scipio; priority footer javascripts (before screen footer javascripts) -->
 <#if layoutSettings.VT_FTPR_JAVASCRIPT?has_content>
 <#--layoutSettings.javaScripts is a list of java scripts. -->
 <#-- use a Set to make sure each javascript is declared only once, but iterate the list to maintain the correct order -->
 <#assign javaScriptsSet = toSet(layoutSettings.VT_FTPR_JAVASCRIPT)/>
 <#list layoutSettings.VT_FTPR_JAVASCRIPT as javaScript>
 <#if javaScriptsSet.contains(javaScript)>
 <#assign nothing = javaScriptsSet.remove(javaScript)/>
 <@script src=makeOfbizContentUrl(javaScript) />
 </#if>
 </#list>
 </#if>

 <#-- New in scipio; for app scripts that aren't (exclusively) styling but must go at end of page -->
 <#if layoutSettings.javaScriptsFooter?has_content>
 <#assign javaScriptsSet = toSet(layoutSettings.javaScriptsFooter)/>
 <#list layoutSettings.javaScriptsFooter as javaScript>
 <#if javaScriptsSet.contains(javaScript)>
 <#assign nothing = javaScriptsSet.remove(javaScript)/>
 <@script src=makeOfbizContentUrl(javaScript) />
 </#if>
 </#list>
 </#if>

 <#-- For theme styling-related scripts -->
 <#if layoutSettings.VT_FTR_JAVASCRIPT?has_content>
 <#--layoutSettings.javaScripts is a list of java scripts. -->
 <#-- use a Set to make sure each javascript is declared only once, but iterate the list to maintain the correct order -->
 <#assign javaScriptsSet = toSet(layoutSettings.VT_FTR_JAVASCRIPT)/>
 <#list layoutSettings.VT_FTR_JAVASCRIPT as javaScript>
 <#if javaScriptsSet.contains(javaScript)>
 <#assign nothing = javaScriptsSet.remove(javaScript)/>
 <@script src=makeOfbizContentUrl(javaScript) />
 </#if>
 </#list>
 </#if>

 <#-- New in scipio; always-bottom guaranteed-last javascripts -->
 <#if layoutSettings.VT_BTM_JAVASCRIPT?has_content>
 <#--layoutSettings.javaScripts is a list of java scripts. -->
 <#-- use a Set to make sure each javascript is declared only once, but iterate the list to maintain the correct order -->
 <#assign javaScriptsSet = toSet(layoutSettings.VT_BTM_JAVASCRIPT)/>
 <#list layoutSettings.VT_BTM_JAVASCRIPT as javaScript>
 <#if javaScriptsSet.contains(javaScript)>
 <#assign nothing = javaScriptsSet.remove(javaScript)/>
 <@script src=makeOfbizContentUrl(javaScript) />
 </#if>
 </#list>
 </#if>
 </@scripts>
</body>
</html>
<#include "common.ftl">
 

 <!-- /.container-fluid -->

 <!-- /#page-wrapper -->

 <!-- /#wrapper -->
 </main>
 </div>
<#-- FOOTER SECTION -->
<footer class="page-footer center-on-small-only stylish-color-dark">

<#if (showFooterOtherContent!true) == true && (useMinimalTheme!false) == false>
 <div class="other-content">
 <div class="container">
 <@row>
 <@cell columns=3>
 <#-- <i class="${styles.icon} ${styles.icon_prefix}laptop"></i>-->
 <p class="column-title white-text"><strong>Bla?</strong></p>
 <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cum maiores alias ea sunt facilis impedit fuga dignissimos illo quaerat iure in nobis id quos, eaque nostrum! Unde, voluptates suscipit repudiandae!</p>
 </@cell>
 <@cell columns=3>
 <#-- <i class="${styles.icon} ${styles.icon_prefix}html5"></i>-->
 <p class="column-title white-text"><strong>Bla bla!</strong></p>
 <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Fugit impedit consequuntur at! Amet sed itaque nostrum, distinctio eveniet odio, id ipsam fuga quam minima cumque nobis veniam voluptates deserunt!</p>
 </@cell>
 <@cell columns=3>
 <p class="column-title white-text"><strong>SCIPIO Webstore</strong></p>
 <ul class="other-links">
 <#-- language select --> 
 <li>
 <a href="<@ofbizUrl><#if userHasAccount>viewprofile<#else>ListLocales</#if></@ofbizUrl>">
 ${uiLabelMap.CommonChooseLanguage}
 </a>
 </li>
 <li><a href="<@ofbizUrl>showAllPromotions</@ofbizUrl>">${uiLabelMap.ProductPromotions}</a></li>
 <li><a href="<@ofbizUrl>license</@ofbizUrl>">License</a></li> 
 <li><a href="https://www.scipio-erp.com/products/faq">FAQ's</a></li>
 </ul>
 </@cell>
 <@cell columns=3> 
 <p class="column-title white-text"><strong>Follow Us!</strong></p>
 <ul class="other-links">
 <li><a href="#">GitHub</a></li>
 <li><a href="#">Facebook</a></li>
 <li><a href="#">Twitter</a></li>
 <li><a href="#">Instagram</a></li>
 </ul>
 </@cell>
 </@row>
 </div>
 </div>
</#if> 
 <hr class="clearfix w-100 d-md-none">
 <div class="footer-copyright">
 <div class="container-fluid">
 ${uiLabelMap.CommonCopyright} (c) 2014-${nowTimestamp?string("yyyy")} <a href="https://www.ilscipio.com" target="_blank">ilscipio GmbH</a>.<span class="float-right">${uiLabelMap.CommonPoweredBy} <a href="http://www.scipioerp.com" target="_blank">SCIPIO ERP</a>. <#include "ofbizhome://runtime/svninfo.ftl" /> <#include "ofbizhome://runtime/gitinfo.ftl" /></span>
 </div>
 </div>
</footer> <#-- END FOOTER -->

<@scripts output=true> <#-- ensure @script elems here will always output -->
 <#-- New in scipio; priority footer javascripts (before screen footer javascripts) -->
 <#if layoutSettings.VT_FTPR_JAVASCRIPT?has_content>
 <#--layoutSettings.javaScripts is a list of java scripts. -->
 <#-- use a Set to make sure each javascript is declared only once, but iterate the list to maintain the correct order -->
 <#assign javaScriptsSet = toSet(layoutSettings.VT_FTPR_JAVASCRIPT)/>
 <#list layoutSettings.VT_FTPR_JAVASCRIPT as javaScript>
 <#if javaScriptsSet.contains(javaScript)>
 <#assign nothing = javaScriptsSet.remove(javaScript)/>
 <@script src=makeOfbizContentUrl(javaScript) />
 </#if>
 </#list>
 </#if>

 <#-- New in scipio; for app scripts that aren't (exclusively) styling but must go at end of page -->
 <#if layoutSettings.javaScriptsFooter?has_content>
 <#assign javaScriptsSet = toSet(layoutSettings.javaScriptsFooter)/>
 <#list layoutSettings.javaScriptsFooter as javaScript>
 <#if javaScriptsSet.contains(javaScript)>
 <#assign nothing = javaScriptsSet.remove(javaScript)/>
 <@script src=makeOfbizContentUrl(javaScript) />
 </#if>
 </#list>
 </#if>

 <#-- For theme styling-related scripts -->
 <#if layoutSettings.VT_FTR_JAVASCRIPT?has_content>
 <#--layoutSettings.javaScripts is a list of java scripts. -->
 <#-- use a Set to make sure each javascript is declared only once, but iterate the list to maintain the correct order -->
 <#assign javaScriptsSet = toSet(layoutSettings.VT_FTR_JAVASCRIPT)/>
 <#list layoutSettings.VT_FTR_JAVASCRIPT as javaScript>
 <#if javaScriptsSet.contains(javaScript)>
 <#assign nothing = javaScriptsSet.remove(javaScript)/>
 <@script src=makeOfbizContentUrl(javaScript) />
 </#if>
 </#list>
 </#if>

 <#-- New in scipio; always-bottom guaranteed-last javascripts -->
 <#if layoutSettings.VT_BTM_JAVASCRIPT?has_content>
 <#--layoutSettings.javaScripts is a list of java scripts. -->
 <#-- use a Set to make sure each javascript is declared only once, but iterate the list to maintain the correct order -->
 <#assign javaScriptsSet = toSet(layoutSettings.VT_BTM_JAVASCRIPT)/>
 <#list layoutSettings.VT_BTM_JAVASCRIPT as javaScript>
 <#if javaScriptsSet.contains(javaScript)>
 <#assign nothing = javaScriptsSet.remove(javaScript)/>
 <@script src=makeOfbizContentUrl(javaScript) />
 </#if>
 </#list>
 </#if>
 </@scripts>
</body>
</html>

Step 4: Custom html & theme variables

The MDB theme uses a few custom classes that differentiate it from the default bootstrap 4 definition. Scipio ERP allows us to modify all default classes in a long list of variables, which are stored in the includes/themeStyles.groovy file. Since most of the classes remain unchanged from the Ignite Shop theme, I will list only the differences here:

169 – “menu_sidebar” : “nav menu-type-sidebar”,
173 + “menu_sidebar” : “nav nav-pills smooth-scroll flex-column”,
613 – “grid_theme_pre” : “”, /*pre-content-section*/
614 – “grid_theme” : “row-offcanvas row-offcanvas-left container”, /*content-section*/
617 + “grid_theme_pre” : “view intro-2 hm-black-light”, /*pre-content-section*/
618 + “grid_theme” : “row container-fluid”, /*content-section*/

 

All of these variables are used by the decorator, templating toolkit macros with and screens to add various style classes to the rendered html. All variables are grouped by type, which makes it very easy to maintain. If you are not using the Ignite Theme as a foundation, more changes will be needed for the same result. The same is true for html changes.

If you want to override the html the templating toolkit generates, then the includes/themeTemplate.ftl file can be used to do so. The templating toolkit stores all of its macros located under /framework/common/webcommon/scipio/lib/standard/. To override, simply copy the appropriate macro to the themeTemplate.ftl file and add your changes. The theme will use the macro accordingly. The following example displays the changes made to the modal component:


<#-- @modal main markup - theme override -->
<#macro modal_markup id="" label="" href="" class="" icon="" origArgs={} passArgs={} catchArgs...>
 <a href="${escapeFullUrl(href, 'html')}" data-toggle="modal" data-target="#${escapeVal(id, 'html')}_modal" <@compiledClassAttribStr class=class />><#if icon?has_content><i class="${escapeVal(icon, 'html')}"></i> </#if>${escapeVal(label, 'htmlmarkup')}</a>
 <div id="${escapeVal(id, 'html')}_modal" class="${styles.modal_wrap!}" role="dialog" aria-hidden="true">
 <div class="modal-dialog">
 <#-- Modal content-->
 <div class="modal-content">
 <div class="modal-header">
 <button type="button" class="close" data-dismiss="modal" aria-label="Close">&times;</button>
 </div>
 <div class="modal-body">
 <#nested>
 </div>
 <div class="modal-footer">
 </div>
 </div>
 </div>
 </div>
</#macro> 

Cases where alot of the html must be changed are rare, however, and in our case, the Ignite Shop theme already covers them splendidly. So no changed are required for our purpose here.

This covers the essentials of how to create a new theme.

(Optional) Step 5: Styling

With a proper theme in place and our build tools in order, we can now modify the styles to our own liking. The theme itself looks okayish, out of the box, but it is clear that there are still a few things require attention. Changes to the


// Your custom style

/*
* GENERAL CONTENT
*/

// Fix padding on content-main-section 
#content-main-section {
 padding-top:20px;
}

// Fix margin on rows
.row {
 .row {
 margin-bottom:10px; 
 }
}

/*
* Slider
*/


.slick-list {
 margin: 0 -10px;
}

.slick-slide {
 margin: 0 10px; 
 margin-bottom: 20px;
}

.slick-slider .slick-slide{
 display: none;
}
 .slick-slider .slick-slide:first-child{
 display: block;
}
.slick-initialized.slick-slider .slick-slide,
.slick-initialized.slick-slider .slick-slide:first-child{
 display: block;
}

/*******
* SHOP
********/
// Navigation
#microcart{
 line-height:36px;
 display: flex;
 flex-flow: row wrap;
 justify-content: space-around;
 color: #fff;
}

#microCartIcon{
 border-radius: 0;
 text-align: center;
 font-size: 16px;
 color: #fff;
 order:0;
}

#microCartTotal{
 order:1;
 margin: 0 0px 0 10px;
}

#content-main-section {
 display: flex;
 flex-direction: row;
 flex-wrap: nowrap;
 flex-shrink: 0;
 margin-top:20px;
}

// Ribbon

.ribbon {
 background: $stylish-color;
 color: $rgba-white-strong;
 margin-bottom:8px;
}
 

This should fix the most apparent glitches on the screen. Extend on this to your own liking.