• Steven Gentens

  • Software Engineer

Across Framework tutorial: part 2

 In this second part of the tutorial, we’ll tailor the CRUD administration interface we made in part 1 to our needs. Across is a free, open source Java framework built on top of Spring.

This is the second part of a three part Across tutorial based on our talk at Devoxx Belgium 2017. In the first part, we looked at setting up a CRUD interface to manage a Spring data repository; in the third part we’ll take a look at WebCmsModule, which provides basic webcms functionality.

Follow us on Facebook or Twitter so you don’t miss any updates.

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

Introduction

In the first part we’ve shown you how EntityModule brings a lot of added value out of the box, simply by plugging it into your application. Here, we’ll further expand that part by customizing the administration interface so that it meets our requirements.

Customization

Last time, we had a generated form that we weren’t completely satisfied with, so let’s add some additional configuration. In the following paragraphs, we’re also going to assume that our base domain model (being BlogPost and Author) and its initial configuration comes from an external library. So we’ll add additional configuration without modifying any generated sample code.

We’re going to divide it into two steps: first we’ll add some additional configuration to our Invoice entity, just to have that slightly better interface. Secondly, we’ll add some configuration to our Author.

Customizing the Invoice entity

If you remember our invoice form, then you’ll realize that some properties could use some quality of life changes:

  • The ‘amount’ property
    • We’re handling invoices, where amount specifies how much we have to pay our author. Instead of having a simple input field where we can put a number, it’d be much nicer to see it rendered as an actual currency.
  • The ‘status’ property
    • Now we’ve got a fancy dropdown for the status property, but there are only three options available. It would be much more efficient to just have radio buttons, because, well, it means one less click.

Aside from those two things, it’d also be nice if the list view of the invoices doesn’t show the note field, as it can get rather large. In addition, let’s have it sorted on the received date by default.

To do this, we’ll start by creating a configuration class for our Invoice entity, called InvoiceConfiguration. InvoiceConfiguration will implement EntityConfigurer, which opens up configuration for any entity in our application.

@Configuration
public class InvoiceConfiguration implements EntityConfigurer {

   @Override
   public void configure(EntitiesConfigurationBuilder entities) {
   }
}

Let’s start off by configuring our amount and status property for every form they are rendered on, and then move to the specific configuration of our list view.

Inside the ‘configure’ method, we’ll add the following:

entities.withType( Invoice.class )
               .properties(
                       props -> props.property( "amount" )
                       .attribute( Currency.class, Currency.getInstance( "EUR" ) )
                       .and()
                       .property( "status" )
                       .viewElementType( ViewElementMode.CONTROL, BootstrapUiElements.RADIO )
               );

To make sure our amount property is rendered as a currency, we simply add an attribute to the property, which says our property should be rendered as a euro currency. EntityModule will pick up and try to resolve the attributes. Just like the amount property, we’ll also start configuring the status property, but this time we’ll define the view element type, because we want to render it as a radio button for every form that defines a control for the property. A CONTROL element allows modification of the specified property.

If we now try to create a new invoice, or update an existing one, we’ll notice that our amount property is formatted as a currency (by way of a simple jQuery plugin) and our status property is rendered as a set of radio buttons.

 radiobuttons in entitymodule


Now let’s also update our listview by expanding the newly created configuration of our Invoice entity. We’ll end up with the following:

entities.withType( Invoice.class )
               .properties(
                       props -> props.property( "amount" )
                               .attribute( Currency.class, Currency.getInstance( "EUR" ) )
                               .and()
                               .property( "status" )
                               .viewElementType( ViewElementMode.CONTROL, BootstrapUiElements.RADIO )
               )
               .listView(
                       lvb -> lvb.showProperties( ".", "~note" )
                               .defaultSort( "receivedDate" )
               );

We’ve now added a specific configuration on the listview of the Invoice entity, which says it should render all the properties that were previously configured to render, denoted by the dot (“.”), and to exclude the note property, denoted by the tilde prefix (“~note”). We could also list all the properties we want to show, but since we’re lazy and we just want to exclude a single property, this is much more practical. Aside from that, we’ve configured the defaultSort property of the listview to sort on the receivedDate property.

 listview entitymodule

Customizing the Author entity

Let’s start customizing our Author entity. All in all, I think the Author entity looks great, except for the fact that I’d like to see how many invoices an author has at a glance. What we’re going to do, is add a fictive property to every author that simply returns how many invoices the author has.

Note: As we’ve said above, we’re going to assume that we cannot edit existing configurations, and we’ve also said that we can modify the configuration for any entity within a single EntityConfigurer configuration class, so we will reuse the InvoiceConfiguration class in the following section.

entities.withType( Author.class )
               .properties(
                       props -> props.property( "totalInvoices" )
                               .propertyType( Integer.class )
                               .valueFetcher( author -> invoiceRepository.countAllByAuthor( (Author) author ) )
               );

The above snippet adds the property ‘totalInvoices’ to the author. At the very least, we need to define the property type, so that EntityModule knows how to render the property, and a value fetcher, so that we can actually display the value. Of course, we didn’t add the countAllByAuthor method yet to our InvoiceRepository, so let’s quickly define it:

public interface InvoiceRepository extends JpaSpecificationExecutor<Invoice>, JpaRepository<Invoice, Long> {
   int countAllByAuthor(Author author);
}

And we’ll also need to wire our invoiceRepository to the InvoiceConfiguration class, for which we’ll once again use some Lombok magic:

@Configuration
@RequiredArgsConstructor
public class InvoiceConfiguration implements EntityConfigurer {

   private final InvoiceRepository invoiceRepository;

   @Override
   public void configure(EntitiesConfigurationBuilder entities) {
       // configuration…

   }
}

Now we can once again reload our application, and we’ll see our totalInvoices property appear on the listview. invoices in listview

If we would navigate to the update form of an author, we will not see the property. When we define a custom property, it will by default have read access, but not write access, and the update form by default renders controls for a form write, so it will not be visible.


I’m not entirely satisfied with the property, because the label is exactly the same as the property name I’ve defined. Usually we would add a displayname to the property, through the configuration. However, we can also edit nearly every label through message codes, as long as we know how to formulate the message code. Let’s give that a try and quickly fix our property name.

The message code is as follows:

DemoApplicationModule.entities.author.properties.totalInvoices=Total Invoices

The way the message code is built up is pretty logical, so let’s reiterate over what we wanted to do. We wanted to rename our custom property ‘totalInvoices’ to ‘Total Invoices’ (totalInvoices=Total Invoices). The property is on the author entity (entities.author.properties), inside our demo application module (DemoApplicationModule).

 

If you’re struggling with message codes, you should definitely take a look at the developer tools, as they offer a lot of help with how the entity is configured, as well as how to build up message codes, even for specific views.

developertools

Because we’re in dev mode, changes to the message source are detected automatically and we don’t even have to reload our application to see a result! If we go back to the author listview after we refreshed the page, we’ll now see ‘Total Invoices’ instead of 'totalInvoices' as the displayed label for our custom property.

titel adjusted

Conclusion

EntityModule provides us with a lot of functionality out of the box. Setting up a decent CRUD administration interface for a domain model is a matter of hours. Of course, we’ve only shown very little of what EntityModule brings to the table, as we can customize nearly everything it provides, going from simple things like customizing labels, to more advanced ones like adding custom controls and form views.


All documentation on EntityModule can be found on the Across site.