Starter: My first web app
Contents
A step-by-step tutorial for building your first web application with Enonic XP
Introduction
This guide will take you through the basic steps of creating a web app, using the basic building blocks of Enonic XP.
During this exercise you will:
-
learn about http controllers and view templates
-
optimize serving of static assets
-
make use of the router library
-
set up a vhost, and more..
Create project
To setup a project locally, run the following command:
enonic project create -r starter-myfirstwebapp
Remember to create a new XP sandbox when completing the project wizard.
Don’t have the Enonic CLI? Visit the Getting started guide to install it. |
Project structure
The project folder created in the previous step should now contain the following project structure:
build.gradle (1)
settings.gradle
build/ (2)
src/
main/
java/ (3)
resources/ (4)
assets/ (5)
webapp/ (6)
1 | The gradle files are used by the build system |
2 | Contains output files produced by the build |
3 | Optional folder for Java code (used to import sample site in this starter) |
4 | Main location for XP specific project code |
5 | Static assets such as css and icons are placed here |
6 | Folder containing the root webapp http controller |
Building and Deploying
From the project folder created (i.e. myproject/
), run this command:
enonic dev
|
Webapp
To see your current application:
-
log in to the XP admin console (http://localhost:8080)
-
open the "Applications" app, and select the listed applications
-
visit the app by clicking the web app link.
JS Controller
The essense of a web application is the controller. In your project, you will find the following file:
exports.get = function (req) {
var title = 'Hello Web app';
return {
body: `
<html>
<head>
<title>${title}</title>
<link rel="stylesheet" type="text/css" href="styles.css"/>
</head>
<body>
<h1>Sweet, "${title}" is working!</h1>
<img src="html5logo.svg"/>
</body>
</html>
`
}
};
This controller file is automatically executed when your web app is being accessed.
The example above is also referencing two asset files. These files can be found in the src/main/resources/assets/
folder of your project.
By default, the web app engine automatically serves files placed in the |
Using Views
According to the MVC (Model View Controller) pattern, we should separate the View (template) from the controller. Enonic XP supports a variety of templating engines. In this step, we’ll use Thymeleaf:
-
To make sure Thymeleaf is available for our project, we must update the
build.gradle
file. Uncomment the following line from the "dependencies" section:include "com.enonic.lib:lib-thymeleaf:2.0.0"
-
Create a new file
hello.html
and add it to thewebapp/
foldersrc/main/resources/webapp/hello.html<html> <head> <title data-th-text="${title}">Dummy title</title> <link rel="stylesheet" type="text/css" data-th-href="styles.css" href="../assets/styles.css"/> </head> <body> <h1 data-th-text="'Sweet... ' + ${message}">Dummy heading</h1> <img data-th-src="html5logo.svg" src="../assets/html5logo.svg"/> </body> </html>
-
Then update your controller file to use the new template:
src/main/resources/webapp/webapp.jsvar thymeleaf = require('/lib/thymeleaf'); // Load template engine var VIEW = resolve('hello.html') // Lookup template file exports.get = function (req) { var model = { // Build model object title: 'Hello Web app', message: 'Views are working too!' }; return { body: thymeleaf.render(VIEW, model) // Render page }; };
Notice the VIEW "constant". Things that don’t change can be set outside the get function. |
Refresh your app, and you should see the heading "Sweet… Views are working too!".
A cool feature related to Thymeleaf templates is that they are actually pure HTML. Meaning they may be opened directly in your browser. Try opening the hello.html file to see for yourself: |
Asset serving
Our approach asset serving is simple, but not optimal. XP also provides a more optimized solution for serving assets where:
-
Asset url are created automatically (no need to deal with relative paths)
-
Assets get "infinite" cache headers (used by proxies and browsers)
-
Every time you deploy a new app, new url’s are generated (no more stale assets)
To use this functionality, we simply need to update our view template:
-
Update
hello.html
template with the following content:src/main/resources/webapp/hello.html<html> <head> <title data-th-text="${title}"></title> <link rel="stylesheet" type="text/css" data-th-href="${portal.assetUrl({'_path=styles.css'})}"/> </head> <body> <h1 data-th-text="'Faster assets... '+ ${message}"></h1> <img data-th-src="${portal.assetUrl({'_path=html5logo.svg'})}"/> </body> </html>
Notice the dummy values and attributes are removed. They are only needed if you want to open the Thymeleaf template directly in a browser. After refreshing your app and inspecting the html5 logo, you should now see something like this:
<img src="/webapp/webapp.demo/_/asset/webapp.demo:1557487230/html5logo.svg">
Check out the "Network" tab in your browsers dev tools to see the cache headers |
Routing
Routing, or handling different URLs within your app is a common requirement for web applications.
In this step we will create a basic server-side router. In addition to the start page, we will create two more pages and handle navigation between them.
-
This time we will use the router library. Add the following line to the dependencies section of the
build.gradle
file to make it available.include "com.enonic.lib:lib-router:3.1.0"
-
Then update your
webapp.js
andhello.html
as follows:src/main/resources/webapp/webapp.jsvar thymeleaf = require('/lib/thymeleaf'); // Load template engine var router = require('/lib/router')(); // Load router library router.get(['', '/'], function() { return renderPage('Routing FTW'); } ); router.get('/page', function() { return renderPage('Gone to page, you have'); } ); router.get('/page/subpage', function() { return renderPage('Gone to sub page indeed, you have'); } ); function renderPage(message) { var model = { title: 'Hello router', message: message }; return { body: thymeleaf.render(resolve('hello.html'), model) } }; exports.get = function (req) { return router.dispatch(req); };
src/main/resources/webapp/hello.html<html> <head> <title>[[${title}]]</title> <link rel="stylesheet" th:href="${portal.assetUrl({'_path=styles.css'})}" type="text/css" /> </head> <body> <nav> <a th:href="${portal.pageUrl({'_path=/'})}">Main</a> <a th:href="${portal.pageUrl({'_path=page'})}">Page</a> <a th:href="${portal.pageUrl({'_path=page/subpage'})}">Subpage</a> </nav> <h1>[[${message}]]</h1> <img th:src="${portal.assetUrl({'_path=html5logo.svg'})}"/> </body> </html>
If you don’t need to validate your Thymeleaf template as pure html you can:
-
use inlined expressions [[${variable}]] rather than data-th-text="${variable}"
-
use shorthand th:href rather than data-th-href
Reload and enjoy navigating between the pages.
-
Notice the use of pageUrl() to dynamically generate server relative URLs for the pages. This eliminates the need for dealing with relative links etc. |
Setting up a vhost
Our webapp URL does not look like something we would want in production. XP provides a concept called Vhosts to map the internal XP URI to a public facing URL i.e. mywebapp.com
→ /webapp/my.app.name/
.
-
Start by locating your sandbox' config folder. It is placed within your users home folder at
.enonic/sandboxes/<name-of-sandbox>/home/config/
-
Update the
com.enonic.xp.web.vhost.cfg
file in the config/ folder as follows:enonic/sandboxes/<name-of-sanbox>/home/config/com.enonic.xp.web.vhost.cfgenabled = true # Vhost to test our new web app mapping.mywebapp.host = mywebapp.com mapping.mywebapp.source = / mapping.mywebapp.target = /webapp/com.example.myproject/ # Still access everything on "localhost" mapping.lhost.host = localhost mapping.lhost.source = / mapping.lhost.target = / mapping.lhost.idProvider.system = default
Remember to update the value of mapping.mywebapp.target
to match the URI to your webapp i.e. "/webapp/my.cool.app/" -
After saving the vhost config file, you should see the following line the XP Sandbox log:
2019-05-10 11:34:17,234 INFO c.e.x.w.v.i.c.VirtualHostConfigImpl - Virtual host is enabled and mappings updated.
-
Finally, a small trick to fool our browser into thinking
mywebapp.com
is pointing to your local machine. Add the following line to your hosts file.127.0.0.1 mywebapp.com
On Mac/Linux_: /etc/hosts
, on Windows:c:\Windows\System32\Drivers\etc\hosts
-
Point your browser to http://mywebapp.com:8080 to see the glorious result.
Read more about vhost configuration in the XP docs.
Logging
While developing an app, it can be helpful to do some logging. Try adding the following line into the exports.get section of your webapp controller and see what happens:
log.info('Testing logging: %s', JSON.stringify(req, null, 4));
A simplified logging function and many more are included in the Util Library |