Starter: My first web app

Contents

Starter: My first web app

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..

Ready, set, code!

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 project deploy

Accept starting the sandbox

To verify that your app started successfully, you should find an entry similar to this in the sandbox log:

2019-04-09 13:40:40,765 INFO ... Application [<name.of.your.app>] installed successfully

Webapp

To see your current application:

  1. log in to the XP admin console (http://localhost:8080)

  2. open the "Applications" app, and select the listed applications

  3. 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:

src/main/resources/webapp/webapp.js
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 asset/ as if they were located in the root of your web app.

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:

  1. To make sure Thymeleaf is available for our project, we must update the gradle.build file. Uncomment the following line from the "dependencies" section:

    include "com.enonic.lib:lib-thymeleaf:2.0.0"
  2. Create a new file hello.html and add it to the webapp/ folder

    src/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>
  3. Then update your controller file to use the new template:

    src/main/resources/webapp/webapp.js
    var thymeleaf = require('/lib/thymeleaf'); // Load template engine
    
    exports.get = function (req) {
      var view = resolve('hello.html') // Lookup template file
      var model = { // Build model object
        title: "Hello Web app",
        message: "Views are working too!"
      };
    
      return  {
        body: thymeleaf.render(view, model) // Render page
      }
    
    };
  4. When finished, redeploy the app. NB! Just start a new shell if your current shell is running the sandbox:

    enonic project deploy

    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:

Screenshot of template showing dummy heading and html5 logo

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:

  1. Update hello.html template with the following content:

    src/main/resources/webapp/hello.html
    <html>
      <head>
        <title data-th-text="${title}">Dummy title</title>
        <link rel="stylesheet" type="text/css" data-th-href="${portal.assetUrl({'_path=styles.css'})}" href="../assets/styles.css"/>
      </head>
      <body>
          <h1 data-th-text="'Faster assets... '+ ${message}">Dummy heading</h1>
          <img data-th-src="${portal.assetUrl({'_path=html5logo.svg'})}" src="../assets/html5logo.svg"/>
      </body>
    </html>
  2. Redeploy once more:

    enonic project deploy

    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.

  1. 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:2.0.0"
  2. Then update your webapp.js and hello.html as follows:

    src/main/resources/webapp/webapp.js
    var 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 data-th-text="${title}">Dummy title</title>
        <link rel="stylesheet" type="text/css" data-th-href="${portal.assetUrl({'_path=styles.css'})}" href="../assets/styles.css"/>
      </head>
      <body>
        <nav>
          <a data-th-href="${portal.pageUrl({'_path=/'})}" href="./">Main</a>
          <a data-th-href="${portal.pageUrl({'_path=page'})}" href="page">Page</a>
          <a data-th-href="${portal.pageUrl({'_path=page/subpage'})}" href="page/subpage">Subpage</a>
        </nav>
        <h1 data-th-text="${message}">Dummy heading</h1>
        <img data-th-src="${portal.assetUrl({'_path=html5logo.svg'})}" src="../assets/html5logo.svg"/>
      </body>
    </html>
  3. Build and deploy the app once more to see the result:

    enonic project deploy

    Once the app is deployed, 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/.

  1. Start by locating your sandbox' config folder. It is placed within your users home folder at .enonic/sandboxes/<name-of-sandbox>/home/config/

  2. 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.cfg
    enabled = true
    
    # Vhost to test our new web app
    mapping.mywebapp.host = mywebapp.com
    mapping.mywebapp.source = /
    mapping.mywebapp.target = /webapp/ping.webapp/
    
    # 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/"
  3. 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.
  4. 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
  5. 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

Contents