Magnolia 5.4 reached end of life on November 15, 2018. This branch is no longer supported, see End-of-life policy.
This page explains how to create a page template. First, create a static HTML file based on the Bootstrap Landing Page template. Then transform the prototype into a Magnolia page template and make its content editable.
It is good practice to create a static HTML prototype first. It gives you an overview of the page you need to build.
Download the prototype and extract it to my-first-website-tutorial/light-modules/one-pager-module/webresources/prototype
.
light-modules └── one-pager-module └── webresources └── prototype ├── 3rd-party-files │ ├── bootstrap-theme.min.css │ ├── bootstrap.min.css │ ├── bootstrap.min.js │ ├── jquery.easing.min.js │ └── jquery.js ├── imgs │ ├── car-0_medium.jpg │ ├── car-1_medium.jpg │ ├── car-2_medium.jpg │ ├── car-3_medium.jpg │ ├── car-4_medium.jpg │ ├── components.jpg │ ├── eric-the-viking.jpg │ └── intro-bg.jpg ├── one-pager.css ├── one-pager.js └── prototype.html
Study the prototype.html
file. It has the following sections:
Every Magnolia template needs a definition and a script. A template definition gives the template a name and makes it available to the system. It also tells the system which script renders the content. We also want a dialog definition.
Create the file one-pager-module/templates/pages/main.yaml
.
templateScript: /one-pager-module/templates/pages/main.ftl renderType: freemarker visible: true title: One pager template dialog: one-pager-module:pages/main areas: content-sections: availableComponents: content-items-list: id: one-pager-module:components/content-items-list textImage: id: one-pager-module:components/textImage
Define a page dialog. The dialog defines properties which are stored on the page node:
form: tabs: - name: tabText label: Title texts fields: - name: title class: info.magnolia.ui.form.field.definition.TextFieldDefinition label: Title - name: subTitle class: info.magnolia.ui.form.field.definition.TextFieldDefinition label: Subtitle - name: copyrightNote class: info.magnolia.ui.form.field.definition.RichTextFieldDefinition label: Copyright note description: Photographers who own the images used on this site - name: tabBgImage label: Background fields: - name: introBgImage class: info.magnolia.ui.form.field.definition.LinkFieldDefinition targetWorkspace: dam appName: assets label: Select image description: Background image of the intro section identifierToPathConverter: class: info.magnolia.dam.app.assets.field.translator.AssetCompositeIdKeyTranslator contentPreviewDefinition: contentPreviewClass: info.magnolia.dam.app.ui.field.DamFilePreviewComponent - name: tabMeta label: SEO fields: - name: keywords class: info.magnolia.ui.form.field.definition.TextFieldDefinition i18n: true label: Keywords description: Keywords and description for search engine optimization rows: 3 - name: description class: info.magnolia.ui.form.field.definition.TextFieldDefinition i18n: true label: Description rows: 5 actions: !include /one-pager-module/includes/default-dialog-actions.yaml
/one-pager-module/dialogs/common/default-dialog-actions.yaml
.<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><title>Log in - Magnolia Bitbucket</title><script> window.WRM=window.WRM||{};window.WRM._unparsedData=window.WRM._unparsedData||{};window.WRM._unparsedErrors=window.WRM._unparsedErrors||{}; WRM._unparsedData["com.atlassian.bitbucket.server.bitbucket-webpack-INTERNAL:user-keyboard-shortcuts-enabled.data"]="true"; WRM._unparsedData["com.atlassian.analytics.analytics-client:programmatic-analytics-init.programmatic-analytics-data-provider"]="false"; WRM._unparsedData["com.atlassian.plugins.atlassian-plugins-webresource-plugin:context-path.context-path"]="\u0022\u0022"; WRM._unparsedData["com.atlassian.plugins.atlassian-clientside-extensions-runtime:runtime.atlassianDevMode"]="false"; WRM._unparsedData["com.atlassian.bitbucket.server.bitbucket-webpack-INTERNAL:date-format-preference.data"]="\u0022\u0022"; WRM._unparsedData["com.atlassian.bitbucket.server.feature-wrm-data:bitbucket.global.theme.data"]="false"; WRM._unparsedData["com.atlassian.analytics.analytics-client:policy-update-init.policy-update-data-provider"]="false"; WRM._unparsedData["com.atlassian.bitbucket.plugins.bitbucket-slack-server-integration-plugin:slack-link-error-resources.slack-link-error"]="{}"; WRM._unparsedData["com.atlassian.bitbucket.server.feature-wrm-data:user.time.zone.onboarding.data"]="true"; if(window.WRM._dataArrived)window.WRM._dataArrived();</script> <link rel="stylesheet" href="/s/c2e7fb6cb0fa3d99cfd1ed5573a5b732-CDN/1015515914/fdb5904/1h5ouvp/a93e8b54a53a5453c42e8c7cfb35551a/_/download/contextbatch/css/_super/batch.css" data-wrm-key="_super" data-wrm-batch-type="context" media="all"> <link rel="stylesheet" href="/s/c3b4ddaba8d3dce9466950696444d252-CDN/1015515914/fdb5904/1h5ouvp/80af30e6da65e29f7038393c9f70794e/_/download/contextbatch/css/bitbucket.page.login,-_super/batch.css" data-wrm-key="bitbucket.page.login,-_super" data-wrm-batch-type="context" media="all"> <link rel="stylesheet" href="/s/a5886b3e3e45b4adb9b9991e2cf18803-CDN/1015515914/fdb5904/1h5ouvp/f4992ec124d5384cd0091fae42c3ca97/_/download/contextbatch/css/bitbucket.layout.focused,bitbucket.layout.base,atl.general,bitbucket.internal.feature.theme,-_super/batch.css?slack-enabled=true" data-wrm-key="bitbucket.layout.focused,bitbucket.layout.base,atl.general,bitbucket.internal.feature.theme,-_super" data-wrm-batch-type="context" media="all"> <script src="/s/2fb1765c7438a1e383facff8dd19270a-CDN/1015515914/fdb5904/1h5ouvp/a93e8b54a53a5453c42e8c7cfb35551a/_/download/contextbatch/js/_super/batch.js?locale=en-US" data-wrm-key="_super" data-wrm-batch-type="context" data-initially-rendered></script> <script src="/s/8fd0897a356f9fee979922482537ec9e-CDN/1015515914/fdb5904/1h5ouvp/80af30e6da65e29f7038393c9f70794e/_/download/contextbatch/js/bitbucket.page.login,-_super/batch.js?locale=en-US" data-wrm-key="bitbucket.page.login,-_super" data-wrm-batch-type="context" data-initially-rendered></script> <script src="/s/b26a251d5dcc697b8b37326544044035-CDN/1015515914/fdb5904/1h5ouvp/f4992ec124d5384cd0091fae42c3ca97/_/download/contextbatch/js/bitbucket.layout.focused,bitbucket.layout.base,atl.general,bitbucket.internal.feature.theme,-_super/batch.js?locale=en-US&slack-enabled=true" data-wrm-key="bitbucket.layout.focused,bitbucket.layout.base,atl.general,bitbucket.internal.feature.theme,-_super" data-wrm-batch-type="context" data-initially-rendered></script> <meta name="application-name" content="Bitbucket"><link rel="shortcut icon" type="image/x-icon" href="/s/1015515914/fdb5904/1h5ouvp/1.0/_/download/resources/com.atlassian.bitbucket.server.bitbucket-webpack-INTERNAL:favicon/favicon.ico" /><link rel="search" href="https://git.magnolia-cms.com/plugins/servlet/opensearch-descriptor" type="application/opensearchdescription+xml" title="Bitbucket code search"/></head><body class="aui-page-focused aui-page-focused-small aui-page-size-small bitbucket-theme user-login"><script type="text/javascript">require('@bitbucket/internal/feature/theme').init();</script><ul id="assistive-skip-links" class="assistive"><li><a href="#content">Skip to content</a></li></ul><div id="page"><!-- start #header --><header id="header" role="banner"><section class="notifications"></section><nav class="aui-header aui-dropdown2-trigger-group" aria-label="site"><div class="aui-header-inner"><div class="aui-header-before"><button class=" aui-dropdown2-trigger app-switcher-trigger aui-dropdown2-trigger-arrowless" aria-controls="app-switcher" aria-haspopup="true" role="button" data-aui-trigger href="#app-switcher"><span class="aui-icon aui-icon-small aui-iconfont-appswitcher">Linked Applications</span></button><div id="app-switcher" class="aui-dropdown2 aui-style-default" role="menu" hidden data-is-user-admin="false" data-is-switcher="true"><div class="app-switcher-loading">Loading…</div></div></div><div class="aui-header-primary"><span id="logo" class="aui-header-logo bitbucket-header-logo"><a href="https://git.magnolia-cms.com"><img src="/s/1015515914/fdb5904/1h5ouvp/1.0/_/download/resources/com.atlassian.bitbucket.server.bitbucket-webpack-INTERNAL:bitbucket-logo/images/logo/bitbucket.svg" alt="Bitbucket"/></a></span><ul class="aui-nav"></ul></div><div class="aui-header-secondary"><ul class="aui-nav"><li class=" help-link"title="Help"><a class=" aui-dropdown2-trigger aui-dropdown2-trigger-arrowless" aria-controls="com.atlassian.bitbucket.server.bitbucket-server-web-fragments-help-menu" aria-haspopup="true" role="button" tabindex="0" data-aui-trigger><span class="aui-icon aui-icon-small aui-icon-small aui-iconfont-question-filled">Help</span></a><div id="com.atlassian.bitbucket.server.bitbucket-server-web-fragments-help-menu" class="aui-dropdown2 aui-style-default" role="menu" hidden data-aui-dom-container="body"><div class="aui-dropdown2-section help-items-section"><ul class="aui-list-truncate" role="presentation"><li role="presentation"><a href="https://docs.atlassian.com/bitbucketserver/docs-0814/Bitbucket+Data+Center+and+Server+documentation?utm_campaign=in-app-help&amp;utm_medium=in-app-help&amp;utm_source=stash" title="Go to the online documentation for Bitbucket" data-web-item-key="com.atlassian.bitbucket.server.bitbucket-server-web-fragments:general-help">Online help</a></li><li role="presentation"><a href="https://www.atlassian.com/git?utm_campaign=learn-git&utm_medium=in-app-help&utm_source=stash" title="Learn about Git commands & workflows" data-web-item-key="com.atlassian.bitbucket.server.bitbucket-server-web-fragments:learn-git">Learn Git</a></li><li role="presentation"><a href="/getting-started"class="getting-started-page-link" title="Overview of Bitbucket features" data-web-item-key="com.atlassian.bitbucket.server.bitbucket-server-web-fragments:getting-started-page-help-link">Welcome to Bitbucket</a></li><li role="presentation"><a href="/#"class="keyboard-shortcut-link" title="Discover keyboard shortcuts in Bitbucket" data-web-item-key="com.atlassian.bitbucket.server.bitbucket-server-web-fragments:keyboard-shortcuts-help-link">Keyboard shortcuts</a></li><li role="presentation"><a href="https://go.atlassian.com/bitbucket-server-whats-new?utm_campaign=in-app-help&utm_medium=in-app-help&utm_source=stash" title="Learn about what's new in Bitbucket" data-web-item-key="com.atlassian.bitbucket.server.bitbucket-server-web-fragments:whats-new-link">What's new</a></li><li role="presentation"><a href="https://go.atlassian.com/bitbucket-server-community?utm_campaign=in-app-help&utm_medium=in-app-help&utm_source=stash" title="Explore the Atlassian community" data-web-item-key="com.atlassian.bitbucket.server.bitbucket-server-web-fragments:community-link">Community</a></li><li role="presentation"><a href="/about" title="About Bitbucket" data-web-item-key="com.atlassian.bitbucket.server.bitbucket-server-web-fragments:about">About</a></li></ul></div></div></li><li class=" alerts-menu"title="View system alerts"><a href="#alerts" id="alerts-trigger"class="alerts-menu" title="View system alerts" data-web-item-key="com.atlassian.bitbucket.server.bitbucket-server-web-fragments:global-alerts-menu-item">Alerts</a></li></ul></div></div> <!-- End .aui-header-inner --></nav> <!-- End .aui-header --></header><!-- End #header --><!-- Start #content --><section id="content" role="main" tabindex="-1" data-timezone="-120" ><div class="aui-page-panel content-body" ><div class="aui-page-panel-inner"><main role="main" id="main" class="aui-page-panel-content" ><h2>Log in</h2><form class="aui top-label prevent-double-submit " action="/j_atl_security_check" method="post" accept-charset="UTF-8"><div class="field-group"><label for="j_username" >Username</label><input class="text long-field" type="text" id="j_username" name="j_username" autofocus accesskey="u"/></div><div class="field-group"><label for="j_password" >Password</label><input class="text long-field" type="password" id="j_password" name="j_password" accesskey="p"/></div><div class="aui-group"><fieldset class="group checkbox"><div class="checkbox"><input class="checkbox" type="checkbox" id="_atl_remember_me" name="_atl_remember_me" checked="checked" accesskey="r"/><label for="_atl_remember_me" >Keep me logged in</label></div></fieldset></div><div class="aui-group"><input name="next" type="hidden" value="/projects/DOCUMENTATION/repos/my-first-website-tutorial/browse/light-modules/one-pager-module/dialogs/common/default-dialog-actions.yaml?at=master&raw"/><input name="queryString" type="hidden" value="next=%2Fprojects%2FDOCUMENTATION%2Frepos%2Fmy-first-website-tutorial%2Fbrowse%2Flight-modules%2Fone-pager-module%2Fdialogs%2Fcommon%2Fdefault-dialog-actions.yaml%3Fat%3Dmaster%26raw"/><input class="aui-button aui-button-primary" type="submit" id="submit" name="submit" value="Log in" accesskey="s"/><a id="forgot" class="aui-button aui-button-link" name="forgot" href="/passwordreset" autocomplete="off" tabindex="0">Unable to access your account?</a></div></form></main></div></div></section><!-- End #content --><!-- Start #footer --><footer id="footer" role="contentinfo"><section class="notifications"></section><section class="footer-body"><ul><li data-key="footer.license.free.eval">Git repository management powered by a free <a href="https://www.atlassian.com/software/bitbucket/">Atlassian Bitbucket</a> evaluation license</li></ul><ul><li>Atlassian Bitbucket <span title="fdb5904e84ca72e8d62953007cf5cf6cfcdfebd8" id="product-version" data-commitid="fdb5904e84ca72e8d62953007cf5cf6cfcdfebd8" data-system-build-number="fdb5904"> v8.14.0</span></li><li data-key="footer.links.documentation"><a href="https://docs.atlassian.com/bitbucketserver/docs-0814/Bitbucket+Data+Center+and+Server+documentation?utm_campaign=in-app-help&utm_medium=in-app-help&utm_source=stash" target="_blank">Documentation</a></li><li data-key="footer.links.jac"><a href="https://jira.atlassian.com/browse/BSERV?utm_campaign=in-app-help&utm_medium=in-app-help&utm_source=stash" target="_blank">Request a feature</a></li><li data-key="footer.links.about"><a href="/about">About</a></li><li data-key="footer.links.contact.atlassian"><a href="https://www.atlassian.com/company/contact?utm_campaign=in-app-help&utm_medium=in-app-help&utm_source=stash" target="_blank">Contact Atlassian</a></li></ul><div id="footer-logo"><a href="https://www.atlassian.com/" target="_blank">Atlassian</a></div></section></footer><!-- End #footer --></div><script>require('bitbucket/internal/layout/base/base').onReady(null, "Magnolia Bitbucket" ); require('bitbucket/internal/widget/keyboard-shortcuts/keyboard-shortcuts').onReady();</script><script type="text/javascript">require('bitbucket/internal/page/login/login').onReady();</script></body></html>
Create a template script in one-pager-module/templates/pages/main.ftl
.
Note the following things:
title
and assign the page title to it using the content
rendering context object. We use the variable in the <title>
and <h1>
tags.${content.keywords!""}
and ${content.description!""}
for the respective meta tags.${ctx.contextPath}/.resources/one-pager-module/webresources/css/one-pager.css
. This file does not yet exist.Copy resource files to the folders the script references.
/one-pager-module/webresources/bootstrap-3.3.5
and copy the following files from the prototype
into the folder:bootstrap-theme.min.css
bootstrap.min.css
bootstrap.min.js
jquery.easing.min.js
jquery.js
one-pager-module/webresources/prototype/one-pager.css
to one-pager-module/webresources/css/
.one-pager-module/webresources/prototype/one-pager.js
to one-pager-module/webresources/js/
. one-pager.css
. Remove the line background: url(imgs/intro-bg.jpg) no-repeat center center;
from the .intro-section
.One way to make a template available would be adding a new node to the list of available templates in the Site definitions app. This is the same procedure as the one followed in the Hello Magnolia tutorial:
templates/availability/templates
.one-pager
.Add a property called id
with the value one-pager-module:pages/main
to the node.
Please note that there are also two additional ways of making a page template available.
Now try to create a page in the Pages app with the new "One pager template" template.
If it works, proceed with the tutorial.
Structure and style the intro section
Edit the template script main.ftl
. Remove the first two lines of the <body>
element and replace them with an intro section that applies Bootstrap classes for structure and style.
We introduce the built-in Freemarker directive has_content
. We use the built-in to check if the user entered a subtitle. If there is no content we don't need to create the <h3>
element at all.
Replace this:
<h1>${title}</h1> <p>${content.subTitle!"No subtitle defined"}</p>
With this:
<div class="intro-section" id="intro"> <div class="container"> <div class="row"> <div class="col-lg-12"> <div class="intro-message"> <h1 class="dark">${title}</h1> [#if content.subTitle?has_content] <h3>${content.subTitle}</h3> [/#if] <hr class="intro-divider"> </div> </div> </div> </div> </div> [#--eof: intro-section--]
Display a background image in the intro section. Add the following snippet inside the <head>
element in the template script:
<style> [#if content.introBgImage?has_content] [#assign assetRendition = damfn.getRendition(content.introBgImage, "xxlarge")! /] [#if assetRendition?has_content] .intro-section { background: url(${assetRendition.getLink()}) no-repeat center center; background-size: cover; } [/#if] [/#if][#-- eof: introBgImage --] </style>
/cars/007-cars/intro-bg.jpg
in the Magnolia DAM or upload your own.The prototype contains two in-page navigation menus, one at the top and one at the bottom of the page. If you study the html you realize that both menus contain very similar code. Let's remove this duplication and "outsource" the menu code to a Freemarker macro and use it twice.
createSectionNav
macroCreate a macro that renders a list of anchor links. The anchors are page areas that you will create in a moment.
Add the following snippet at the top of the template script:
[#macro createSectionNav page areaName="content-sections" type="top" ] [#assign ulClass = "default-ul-class" ] [#assign isTop = false ] [#if type=="top"] [#assign ulClass = "nav navbar-nav navbar-right" ] [#assign isTop = true ] [#elseif type=="bottom"] [#assign ulClass = "list-inline" ] [/#if] <ul class="${ulClass}"> [#if isTop] <li><a class="page-scroll" href="#intro">Home</a></li> [#else] <li><a class="page-scroll" href="#intro">Intro</a></li> [/#if] [#if cmsfn.contentByPath(page.@path+"/"+areaName)?exists] [#list cmsfn.children(cmsfn.contentByPath(page.@path+"/"+areaName), "mgnl:component") as component] [#assign navTitle = component.sectionName!component.headline!/] [#if navTitle?has_content] [#if !isTop]<li class="footer-menu-divider">⋅</li>[/#if] <li><a class="page-scroll" href="#${component.@uuid}">${component.sectionName!component.headline!component.@uuid}</a></li> [/#if] [/#list] [/#if] </ul> [/#macro]
Call the macro at the top of the page to create a top menu.
Add the following snippet right after the opening <body>
tag.
[#--top navigation--] <nav class="navbar navbar-default navbar-fixed-top topnav" role="navigation"> <div class="container topnav"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand topnav" href="#intro">Home</a> </div> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> [#--dynamic part of top-nav--] [@createSectionNav page=content areaName="content-sections" type="top" /] </div> </div> </nav> [#--eof: top navigation--]
[@createSectionNav page=content areaName="content-sections" type="top" /]
. The macro takes the following parameters:page
: content of the page as a ContentMap.
areaName
: name of the area definition node.
type
: location of the menu, top
or bottom
.
Call the macro again in the page footer to create a bottom menu.
Add the following snippet before the closing </body>
tag but before the <script>
elements:
[#--Footer--] <footer> <div class="container"> <div class="row"> <div class="col-lg-12"> [#--footer-nav--] [@createSectionNav page=content areaName="content-sections" type="bottom" /] [#if content.copyrightNote?has_content]<div class="copyright">${cmsfn.decode(content).copyrightNote}</div>[/#if] </div> </div> </div> </footer> [#--eof: Footer--]
content-sections
areaThe page template defines one area – content-sections
.
areas: content-sections: availableComponents: content-items-list: id: one-pager-module:components/content-items-list textImage: id: one-pager-module:components/textImage
[#--eof: intro-section--] [@cms.area name="content-sections"/] [#--Footer--]
You should now have the following files for the page template:
light-modules └── one-pager-module ├── dialogs │ ├── common │ │ └── default-dialog-actions.yaml │ └── pages │ └── main.yaml ├── templates │ └── pages │ ├── main.ftl │ └── main.yaml └── webresources ├── bootstrap-3.3.5 │ ├── bootstrap-theme.min.css │ ├── bootstrap.min.css │ ├── bootstrap.min.js │ ├── jquery.easing.min.js │ └── jquery.js ├── css │ └── one-pager.css ├── js └── one-pager.js
You can download the complete one-pager-module
directory as a ZIP from our Git server. It also includes the prototype which is not shown on the files tree above.
Components and the files shown previously from the prototype directory are not shown here.
Next: Accessing content on the server side