• Steven Gentens

  • Software Engineer

Across Framework tutorial: part 3

In the third and final part of our Across tutorial, we’ll show you how to make your website manageable by end users in less than thirty minutes, using the Across WebCmsModule. Across is a free, open source Java framework built on top of Spring.

This is the last part of a three part Across tutorial based on our talk at Devoxx Belgium 2017. In the first two parts, we focused on using EntityModule; in this part we’ll take a look at WebCmsModule, which provides basic webcms functionality.

Missed the parts on EntityModule? You can find part 1 here and part 2 here. Remember to follow us on Facebook or Twitter to stay up to date with new blogposts.

All information on Across can be found on https://across.foreach.be/.

Introduction

In this last part we’re going to show you how to make content on a website manageable by users. Content is a broad term, of course. We’ve already seen how we can let users manage the entities defined in our application in the previous parts of this tutorial. This time, we’re also going to give them the ability to tune the website to their needs, maintaining texts, images, and entire pages. In the following sections we’ll once again use the administrative part of our application, in combination with some Thymeleaf magic to create components from existing templates.

Building the application

Just like in the first part, we’ll jump start our development by using the Across Initializr, where we’ll customize one of the preset options, which includes practically everything you’ll need.

Setup

We’ll start off our development with the ‘Website with basic content management’ preset. In the examples we’ve used, you won’t see a sample admin controller, as we’ve unchecked that option under ‘AdminWebModule’. Please note that the Initializr preset we’ve used doesn’t configure anything for images by default. Should you want to play around with images, we advise that you check ‘Connect to Cloudinary for image management’ under WebCmsModule. Afterwards you can simply enter your credentials in the YAML configuration files and your images will be stored on that Cloudinary account.

The Across Initializr will generate a Maven project based on the chosen settings, which we’ll be able to start up immediately. Don’t forget to activate the ‘dev’ profile in your run configuration, as this will enable devtools and speed up the development process. During the following sections, we’ll primarily focus on what WebCmsModule brings to the table.

As usual, our website will be served on http://localhost:8080 by default. This time we’re actually playing around with web content management features, so we’re also going to need content to play around with. Luckily the options we’ve selected on the Across Initializr provided us with a default front-end layout, as well as some sample pages and articles. We’ll still be spending some time in the secured section under /admin as well, so just in case you forgot, the default login provided is admin for both the username and the password.

The main difference you’ll immediately notice from the parts about EntityModule is, on the homepage (http://localhost:8080) we’ve now got a basic blog site! Cool! If we take a look at our code, we’ll notice some familiar entities being Author and Blogpost, which we’ve also been using in our previous posts.

When we travel to the secure part of our application, we see that we have some additional modules with manageable entities available under the ‘Entity Management’ section: UserModule and WebCmsModule.

UserModule provides a domain model for users and corresponding aspects, like groups and roles. As we’ve mentioned in part 1, if you include UserModule, an actual user will correspond with our admin credentials, which you’ll discover when navigating to the User overview under the UserModule section.

WebCmsModule is what we’ll be working with this time. It provides quite a few entities out of the box, among which the basic building blocks of a manageable website like articles, pages, components. In the following sections, we’re going to show you how to create manageable components by simply cutting up already existing templates. Let’s dive right in!

Creating a global component from a template

What we are going to do is create a component that holds the html content of the footer, so that it is editable through our administrative interface. When we take a look at the web components that are already available, we’ll only find a sample component. We can create more components if we’d like to, but that wouldn’t help us much, since they wouldn’t be used anywhere.

The web components we find here are the global components, so they can be accessed on any page or article, as they can be found inside the global scope. Let’s try to get our footer in there.

At the moment, our footer is already the same on every page, since it is defined in the default template that will be used. We can find the html in views/th/demo/layout/default-layout.html in the resources folder. What we’re going to do is tell WebCmsModule that when the page is rendered, it should create an html component containing the html of the footer. If that component exists, it should be rendered instead of the html for our footer in our template. We’ll add the following attributes to the footer html tag:

  • wcm:component=”global-footer”
    • The component with the name global-footer should be rendered here.
  • wcm:auto-create=”global”
    • If the component does not exist yet, create it on the global scope.
  • wcm:type=”html”
    • The component should be created as an html component.

Our footer in the default layout will now look like this:

<footer wcm:component="global-footer" wcm:auto-create="global" wcm:type="html">
    <!-- removed for brevity -->
</footer>

The ‘wcm’ attributes are part of the thymeleaf dialect that WebCmsModule provides for working with components.

Now if we go back to and refresh our homepage, nothing has changed. Well, at least we didn’t break anything! If we go back to take at our web components, however, we’ll notice that we’ve got a new component called ‘Global footer’, which contains the html that was inside our footer tag.

If we would now edit the content of this component, we’ll be changing the footer. Let’s take out the twitter button for example.

When we refresh our homepage again, we’ll notice that our twitter button is no longer there.

If you’d like to reset the footer component, you could just delete it and render the homepage once more, then the default footer will be re-created.It is also possible to delete the component and manually create one with the same name, which will not contain the html of the footer, but it does allow you to use a different component type, for example a rich-text field.

Creating page-specific components from a template

Right now, all of our pages have a hero image with some text on top of it. I’d like to edit the hero-text on my homepage, but leave it unchanged on the other pages. First, we’ll need a page that we can add components to, as right now our homepage is simply rendered by a Controller (a class annotated with @Controller that can define handler methods for incoming requests) named the BlogController.

First things first, let’s create a page called Homepage for the “/” url, which we’ll immediately publish as well. We’ll uncheck the ‘generate canonical path based on parent and path segment’ option, as we’ll enter it manually. If we leave the option checked, it would generate a canonical path based on the title of the page, which would result in ‘/homepage’ in our case. Since we are currently using ‘/’ as our homepage, we opt to manually set the canonical path for our newly created page.

Now we’ve created a page to which we can start adding web components and more. However, if we were to change something at this time, the homepage would remain unchanged. This is because on the BlogController, there’s an @GetMapping specified for the ‘/’ path to render the homepage. If there is an @RequestMapping annotation present for a path that is also defined by a WebCmsUrl, it will always take priority. This means that since there is an @GetMapping (which is actually an @RequestMapping with RequestMethod.GET) for the path ‘/’, it will be resolved as a controller method and not as a WebCmsAsset with url ‘/’. We can, however, map WebCmsAssets to controller methods as well, by using annotations provided by WebCmsModule. Since we’re working on a page, let’s opt to use @WebCmsPageMapping instead of @WebCmsAssetMapping. We’ll replace @GetMapping(/) with @WebCmsPageMapping(canonicalPath = “/”), to quickly fix our controller. Our controller method will end up looking like this:

@WebCmsPageMapping(canonicalPath = "/")
public String homepage( @PageableDefault(size = 3) Pageable pageable, Model model ) {
    model.addAttribute( 
		"blogsPage",
		blogPostRepository.findByPublicationSettings_PublishedIsTrueOrderByPublicationSettings_PublicationDateDesc( pageable )
    );
	return "th/demo/homepage";
}

Let’s reload the application to perform the changes. Remember you can use Ctrl+F9 (or Cmd+F9) in dev-mode to use hot-reloading! If we take a look at our homepage, again nothing has changed. However, upon navigating to the homepage, we’ll be navigating to the page we’ve just created. If you were to delete the page, you’ll notice that the homepage no longer works. (But don’t forget to recreate it if you do so.)

We’ve got our page, now let’s make a component out of the text on top of our hero image. As we’ve just noticed in the controller method, the template of our homepage is homepage.html, so that’s where we’ll go next. In there we’ll find a header tag, in which we’ll find a div with the class site-heading. That’s where the text is that we want to customize. Now, we’ve already seen that we can edit html, but really, that isn’t very user friendly. Let’s use a rich-text field instead. We’ll do this by once more adding a few attributes to our html element in the template:

  • wcm:component=”hero-text”
    • We want to render the component with the name hero-text instead of the current content.
  • wcm:auto-create
    • Our component should be auto-created if it does not exist yet. Since we’ve omitted the scope to create the component in, it will use the scope provided by wcm:scope.
  • wcm:scope=”page”
    • The component will be looked for within the page scope and the parent scopes. This attribute is the one you’d use in a setup where your component is not auto-created and you’d like to access a component of a specific scope
  • wcm:type=”rich-text”
    • If our component is created, it should be created as a rich-text component.

Our header will look like this:

<header class="intro-header" th:style="${'background-image: url(' + #webapp.path('@static:/demo/img/home-bg.jpg') + ')'}">
       <div class="container">
           <div class="row">
               <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
                   <div class="site-heading" wcm:component="hero-text" wcm:auto-create wcm:scope="page"
                        wcm:type="rich-text">
                       <h1>Hello Across!</h1>
                       <hr class="small">
                       <span class="subheading" th:text="#{sample.message}">A message from your message source</span>
                   </div>
               </div>
           </div>
       </div>
   </header>

Within the content of the component, Thymeleaf attributes have been used. These will no longer work, as the component will be created using the processed html once the page is rendered for the first time. Subsequent page visits will then use the created component. Once our homepage has been rendered again, a manageable component will be created for our hero text. This time it won’t be visible among the global web components. We can, however, edit it using our recently created page.

Just like with the footer, we can now manage the hero text by editing our component.

Having fun with components

Thus far we’ve created some basic components from a pre-defined template to serve our needs. WebCmsModule has a lot more to offer, including some fancy tricks that we can perform using components. Let’s see just how dynamic we can make our homepage.

What we want to achieve, is to divide our homepage into several segments, which we’ll be able to move around and add additional components to. Currently our homepage has two very clear parts, the first one being the header, containing a hero and our hero-text, the second being the teasers for the blog posts.

We’ll start off by wrapping everything inside the content part of our page (everything within the th:block tag that has a th:fragment=”content” attribute), inside another th:block with some attributes:

  • wcm:component=”section-positions”
    • We want to render the component with the name ‘section-positions’.
  • wcm:type=”container”
    • The component should be of the type ‘container’ if it is created.
  • wcm:auto-create
    • If the component does not exist, create it.
  • wcm:scope=”page”
    • The component should be found in the page scope.
  • wcm:parse-placeholders
    • If you find any placeholder components within the container, use their content when rendering the page.

Then we’ll also add an attribute to the header and div that contains our teasers.

  • wcm:placeholder=”header” to the header tag
  • wcm:placeholder=”teasers” to the div with the class ‘container’.

wcm:placeholder will create a placeholder component with the value as name, inside the section-positions container.

Our html will now look like this:

<th:block th:fragment="content">
   <th:block wcm:component="section-positions" wcm:type="container" wcm:auto-create wcm:scope="page"
             wcm:parse-placeholders>
       <!-- Page Header -->
       <!-- Set your background image for this header on the line below. -->
       <header wcm:placeholder="header" class="intro-header"
               th:style="${'background-image: url(' + #webapp.path('@static:/demo/img/home-bg.jpg') + ')'}">
          <!-- removed for brevity -->
       </header>

       <!-- Main Content -->
       <div wcm:placeholder="teasers" class="container">
          <!-- removed for brevity -->
       </div>

   </th:block>
</th:block>

Upon rendering the homepage, nothing changes, but we’ll have an additional container component named ‘section-positions’, containing two placeholder components (with content that cannot be altered). If we then move the teasers component above the header component, the hero will be rendered underneath the blog teasers.

Pretty cool right? That’s not everything, the various blog post teasers are rendered using Thymeleaf, so that dynamic code is still processed! When the placeholder is rendered, it is processed as if its content is part of the template. Thus, dynamic Thymeleaf will still apply, and other components within the placeholder will be processed correctly. Aside from that, since section-positions is a container, we can also add other components to it, in case you’d like to, for example, insert an image in between the header and blog teasers.

Conclusion

A short recap: we started off by creating a globally shared component, the footer. Next, we created a page-specific component, the text on top of the hero image on the homepage. Finally, we made the content of our homepage moveable, so we could switch the order of the parts that are rendered and even add additional parts by adding additional components to a container.

What we’ve shown here are just a few basic building blocks for WebCmsModule. Some of the features we haven’t touched for example are multi-domain/multi-site management, creating custom components and assets, importing data.

The complete documentation of WebCmsModule can be found on the Across site.