Designing and programming a theme

Updated at 1694420467000

Overview

Some of the EzyPlatform base source code for themes use Bootstrap 5 library, so when designing a theme, you can also follow these rules to make it easier for developers.

Steps to Create a Theme

  1. Design the interface in the form of images, PSD, or Figma.
  2. Convert the design to HTML.
  3. Convert HTML to Thymeleaf templates.
  4. Integrate dynamic content.

How to Design a Theme

  1. Horizontally, the theme will be designed with 12 columns for large screens, and smaller screens like tablets or mobiles will be responsive.
  2. Vertically, the theme will be divided into three distinct parts: header, main content, and footer. The header and footer should remain consistent across all pages to facilitate development.

Example:

ezyplatform-theme-design.png

How to Code HTML for the Theme

The HTML source code for a page should adhere to the following principles:

  1. The three parts: header, main content, and footer should each be placed in separate tags and wrapped in a div because there may be necessary HTML before and after these three parts.
  2. script tags should be placed at the end of the body to ensure that HTML is initialized and displayed as early as possible for users. Additionally, this ensures accessibility to DOM components from JavaScript.

Example HTML Source Code:

<!DOCTYPE html>
<html>

<head th:remove="tag">
<!-- meta data tags -->
<!-- link tags -->
</head>
<body>
<!-- some top html content -->
<div class="wrapper">
	<!-- header -->
	<header class="main-header">
		<!-- header content -->
	</header>
	<!-- main -->
	<main class="main-content">
        <!-- main content -->
	</main>
	<!-- footer -->
	<footer class="main-footer">
	    <!-- footer content -->
	</footer>
</div>
<!-- some bottom html content -->
<!-- script tags -->
</body>
</html>

CSS source code should follow these principles:

  1. Use B.E.M code style.
  2. Declare variables for commonly used values, for example:
--color-canvas-subtle: #f6f8fa;
--color-neutral-muted: rgba(175,184,193,0.2);
--color-fg-default: #24292f;
--color-prettylights-syntax-keyword: #cf222e;
--color-prettylights-syntax-entity-tag: #116329;
--color-prettylights-syntax-string: #0a3069;
--color-prettylights-syntax-entity: #8250df;
--color-prettylights-syntax-constant: #0550ae;
  1. Create common classes for reuse across multiple pages to minimize the amount of CSS code.

How to Convert HTML to Thymeleaf Template

Step 1: Create the page.html File

This file will contain the common structure for all pages of the theme. For example:

<!DOCTYPE html>
<html
	xmlns="http://www.w3.org/1999/xhtml"
	xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	xmlns:ezy="http://www.ezyplatform.com/thymeleaf/layout">

<head th:remove="tag">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,500;0,700;0,900;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
<link rel="stylesheet" type="text/css" ezy:vhref="/css/entypo.css">
<link rel="stylesheet" type="text/css" ezy:vhref="/css/style.css">
<link rel="stylesheet" type="text/css" ezy:vhref="/ezylogin/css/style.css" />
<style th:inline="css">
	.custom-file-label::after {
		content: "[[#{browse}]]" !important;
	}
</style>
</head>
<body th:remove="tag">
<th:block th:replace="~{ezycommon/toast :: content}" />
<div class="wrapper">
	<!-- header -->
	<header class="main-header">
		<div th:replace="~{fragments/header :: header}"></div>
	    <div th:replace="~{fragments/header :: main-mobile-bar}"></div>
	</header>
	<!-- main -->
	<main class="main-content">
		<div layout:fragment="content"></div>
	</main>
	<!-- footer -->
	<footer th:replace="~{fragments/footer :: footer}"></footer>
</div>
<div id="loadingScreen" class="screen-loading d-none">
	<i class="fas fa-3x fa-sync fa-spin text-secondary"></i>
</div>
<th:block th:if="${loggedIn != true}">
<div th:replace="~{ezylogin/login :: content}"></div>
<div th:replace="~{ezylogin/register :: content}"></div>
<div th:replace="~{ezylogin/forgot-password :: content}"></div>
</th:block>
<th:block th:replace="~{ezycommon/alert :: content}" />
<th:block layout:fragment="modals"></th:block>

<!-- REQUIRED SCRIPTS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.3/js/bootstrap.bundle.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.11.7/umd/popper.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="

https://cdnjs.cloudflare.com/ajax/libs/bs-custom-file-input/1.3.4/bs-custom-file-input.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<script th:inline="javascript">
/*<![CDATA[*/
var showRegister = /*[[${showRegister}]]*/ '';
var showLogin = /*[[${showLogin}]]*/ '';
var loggedIn = /*[[${loggedIn}]]*/ '';
var userStatus = /*[[${user != null ? user.status : ''}]]*/ '';
/*]]>*/
</script>
<script ezy:vsrc="/js/main.js"></script>
<script layout:fragment="import-scripts" th:remove="tag"></script>
<script layout:fragment="scripts" type="text/javascript"></script>
<script layout:fragment="post-scripts" type="text/javascript"></script>
<script>
var maxNotificationContentLength = 20;
$( document ).ready(function() {
	ezyweb.formatDateStringElements();
    ezyweb.formatDateTimeStringElements();
    ezyweb.formatDateTimeMinuteStringElements();
    ezyweb.formatStatusTextElements();
    ezyweb.formatNumberWithCommasElements();
    if (ezyweb.lang) {
		ezyweb.appendLangParameterToLinks(ezyweb.lang);
    }
});
</script>
<th:block th:if="${loggedIn != true}">
<script th:replace="~{ezylogin/login :: scripts}"></script>
<script th:replace="~{ezylogin/register :: scripts}"></script>
<script th:replace="~{ezylogin/forgot-password :: scripts}"></script>
</th:block>
<script th:replace="~{ezycommon/alert :: scripts}"></script>
<script th:replace="~{ezycommon/toast :: scripts}"></script>
<script th:replace="~{fragments/header :: scripts}"></script>
<th:block th:if="${loggedIn == true}">
<script th:replace="~{ezyaccount/notification :: scripts}"></script>
</th:block>
</body>
</html>

In this example, you can see:

  1. The head tag contains link and style tags.
  2. At the beginning of the body, a th:block tag is used to include the toast fragment.
<th:block th:replace="~{ezycommon/toast :: content}" />
  1. After that, a div with the class wrapper wraps around header, main content, and footer.
  2. Then, fragments for modal components are included.
  3. Finally, fragments for JavaScript are included.

Step 2: Create Necessary Fragments

For example, the fragments/header.html file:

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org">

<body>

<!-- header -->
<header th:fragment="header" class="header">
    <div class="container">
        <div class="header-content">
            <a href="/" class="logo header-border d-flex align-items-center">
                <img src="/images/logo.png" alt="stackask">
            </a>
            <button type="button" class="navbar-toggler" aria-label="button-toggle">
                <i class="icon-menu"></i>
            </button>
            <nav class="navbar navbar-expand-lg navbar-light header-border">
                <button type="button" class="navbar-close">
                    <i class="icon-cancel"></i>
                </button>
                <!-- other HTML source codes -->
                 </div>
            </div>
        </div>
    </div>
</header>
<script th:fragment="scripts">
// ========== question search =============
var onKeydownToSearchQuestions = function(from) {
    if(event.key === 'Enter') {
        searchQuestions(from);
    }
}

var searchQuestions = function(from) {
    var searchKeyword = from === 'mobile'
        ? $('#searchKeywordMobile').val()
        : $('#searchKeyword').val();
    window.location = '/questions?keyword=' + searchKeyword
        + (ezyweb.lang ? '?lang=' + ezyweb.lang : '');
}
</script>
</body>
</html>

In this file, there are two fragments: header for HTML and scripts for JavaScript.

Step 3: Create Pages

For example, a home page:

<!DOCTYPE html>
<html
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:th="http://www.thymeleaf.org"
        xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
        layout:decorate="~{ezytheme}">
<body>
<div layout:fragment="content">
    <section th:replace="~{fragments/overlay :: overlay}"></section>
    <section class="main-content-home">
        <div class="container">
            <div class="row">
                <div th:replace="~{fragments/questions :: content}"></div>
                <div th:replace="~{fragments/sidebar :: sidebar}"></div>
            </div>
        </div>
    </section>
</div>
<th:block layout:fragment="post-scripts">
    <script th:replace="~{fragments/questions :: scripts}"></script>
    <script th:replace="~{fragments/question-scripts :: vote}"></script>
</th:block>
</body>
</html>

A page should include layout:decorate="~{ezytheme}", and ezytheme will call the components of the page.html file you created earlier.

Step 4: Replace Text with i18n Messages

You can use regular text in HTML, but with EzyPlatform, to ensure multilingual responsiveness, it's recommended to switch to using i18n keys. For example

, in HTML, you have:

<a class="nav-link" href="/account">
    <i class="nav-icon fas fa-user-circle"></i> My profile
</a>

In the template, you should replace it with:

<a class="nav-link" href="/account">
    <i class="nav-icon fas fa-user-circle"></i> [[#{my_profile}]]
</a>

Then, declare values for the my_profile key in message files for different languages.

How to Integrate Dynamic Content

For dynamic content or large text content that can change, you should fetch it from the database and insert it into the template. For example:

<div class="post-content" id="questionContent">
    <th:block th:utext="${question.content}"></th:block>
</div>