Introduction to Liferay Objects

A comprehensive, step-by-step introduction using Liferay Objects to build an application.

Introduction

Wow, I've blogged about Liferay Objects a lot lately. Talked about them too. Also am working on developing materials for learn.liferay.com about Objects.

But one thing I haven't seen is an introduction to objects suitable for long-time Liferay users or new Liferay users, so I thought I'd take time to write one (since my blog ideas well seems to be running dry again).

So that's what this blog post is going to be, a step-by-step guide towards building an application using Liferay Objects.

It's also going to highlight Liferay's low code/no code proposition, so you'll be able to put those things to the test also.

So let's go ahead and dive in!

Preparation

I didn't want any baggage, so I started by going here and downloading Liferay Portal 7.4 GA112:

I'm going to do this post using Liferay CE 7.4, but you could just as easily do this in Liferay DXP, on Liferay PaaS and also Liferay SaaS.

And you don't have to be on the latest version, although I do recommend it. If you were to go through this step-by-step guide using GA55, for example, it might work or you might fail or you might find something missing. I know that this guide will work for GA112, anything else will be up to you.

Also, as you can see I downloaded the bundle with Tomcat. You can also do this using the Docker image, although you have to have the persistent store set up so your data is retained across container restarts (if you do it all in one sitting, you don't have to worry about restarts).

Before starting the bundle for the first time, I did set up my portal-ext.properties file to contain the following:

setup.wizard.enabled=false

layout.show.portlet.access.denied=false

layout.user.private.layouts.enabled=false
layout.user.public.layouts.enabled=false

session.timeout=15
session.timeout.auto.extend=true
session.timeout.warning=0
session.timeout.auto.extend.offset=300

terms.of.use.required=false
users.reminder.queries.enabled=false

field.enable.com.liferay.portal.kernel.model.Contact.male=false
field.enable.com.liferay.portal.kernel.model.Contact.birthday=false

include-and-override=portal-developer.properties

dl.file.indexing.max.size=419430400

Nothing really special here, although I am including portal-developer.properties as well as setting the session auto-extend settings (so I can log in and walk away but still remained logged in; I wouldn't do this in proper environments, but it works great on my local workstation).

Then I started up Tomcat, logged in using the admin credentials, changed my password, and I was good to go!

To make sure we're on the same page, here's the screenshot of the home page, and I've highlighted the Waffle menu (I'll be referring to that often) on the right side as well as the Site menu (also referring to that a lot) on the left side.

Additionally, sometimes I refer to the Peapod menu: Normally you'll find this in the upper right corner, sometimes by itself where the Waffle menu normally is, sometimes it will be amongst other menu icons.

Step 1: Create Masterclass Site

Now we could just as easily do this in the default Liferay site, but it will be a bit more impressive if we start from a site that is a bit prettier to look at.

Go to the Waffle menu, then to the Control Panel, and then to Sites.

Click the Add button and pick the Masterclass site template. Give your new site whatever name you want, I was original and called mine Masterclass.

After the site is created, you'll end up on the Site Settings control panel. Click on the Site menu, then on the Home button to bring up the main site page.

If you're unfamiliar with the Masterclass site template, take a few moments to look around. Basically you'll find it is basically a brochure site for an online learning platform. Check out the home page, the blog page, and also go to the user menu (upper-right corner of the content area, below the bar w/ the Waffle menu) and then to the My Learning page.

The My Learning page is part of the authenticated user space. It's less flashy and more business like.

Next, go to the Site menu and specifically check out the Design area (Style Books, Fragments, and Page Templates) as well as the Site Builder area (Pages, Navigation Menus, and Collections) to see how the site is constructed.

Note the use of Liferay modern site building tools to build a good looking site that doesn't have a custom theme (yet looks nothing like Classic that it uses), custom Fragments for the presentation, etc.

When you're ready, move on to the next step...

Step 2: Review Application Requirements

Okay, so we're not really going to do much in this step besides introducing what our application is going to be.

Our goal is to allow an authenticated user to register for an online course as well as view the courses they have signed up for. Registrations must go through a custom business process where a Course Administrator (custom role) must accept the registration. Users that are Course Administrators can see the full list of course registrations and can approve or deny registrations.

So that's a pretty basic explanation of what we're going to build, let's think for a minute about how we might have built this using legacy Liferay techniques...

We know that we're going to be working with an entity that we'll call a CourseRegistration. Three ways we might consider handling this entity would have been:

Use ServiceBuilder to create the entity. Would require a Java developer, a Liferay workspace for the modules, a developer to build the UI either as a regular JSP portlet or maybe we go all in with a RESTBuilder implementation and then a React portlet application. Either way it is yet more development resources required to bring this thing to life. Oh and we'll need a second UI for the Course Administrator to see the list and approve/reject the registrations.

Structured Web Content. Yes we could build a web content structure and then permission it so users can create new articles, then leverage FreeMarker templates to display single articles and another FreeMarker template for the Asset Publisher to handle the listing. Would get kind of hacky though handling the business process approval, but maybe we attempt to overlay it into Liferay Workflows?

Liferay Forms. Forms seems kind of obvious since we need to collect registration details, that's what forms are for, right? We'd still need to figure out how to handle the list for Course Administrators and the approval process (Liferay Workflows again?).

Ultimately there are reasons why we wouldn't want to choose any of these approaches. They'll cost us some treasure (money, time, resources), we'd be using Liferay in ways it's not meant to be used (Liferay Workflow as our business process solution), we would probably need to customize a part of Liferay that would be difficult to maintain (i.e. overriding something in Forms or Web Content), ...

So let's discard these old ways and build our application using Liferay Objects. But first, some more pre-work now that we know parts of what our application is going to need...

Step 3: Complete Additional Setup

We now have some more setup work to complete for our application.

First, we know that we need a Course Administrator role that will be assigned to some users and will give them additional privileges.

Additionally we should probably create some user accounts so we'll be able to test the different personas.

There are two student accounts that are regular users and are members of the Masterclass site. There is one course admin account which is also a member of the Masterclass site, but they have been assigned the Course Administrator role.

We're also going to create a couple of Picklists. To add a Picklist, go to the Waffle menu, then to Control Panel, and under the Object category select Picklists.

The first Picklist is going to be the Upcoming Course picklist.

After creating the Picklist, click on it to open the right-side flyout and then add some upcoming courses by clicking on the Add button. I added six to mine, but you can add any number you want.

The next Picklist is the Registration Status.

Here you should add three items, Pending, Registered, and Denied.

With these things ready, we can now move on to the next step, defining the custom Object.

Step 4: Defining the Course Registration Object

To create the new Object, go to the Waffle menu, then to Control Panel, then under the Object category choose Objects. This is the Object Admin control panel.

In GA112 or DXP 2024.Q1, the Object Folders and Model Builder feature flags are enabled, so you'll have an advanced view that prior versions won't have without enabling feature flags.

Start by creating a new Object Folder called Masterclass. This will organize our custom objects created for the Masterclass site in its own folder.

Click on the Add button to create a Course Registration object.

Click on the Course Registration object to open the Object Definition.

On the Details tab, set the Scope to Site, set the Panel Link to Content & Data, and check the Enable Entry History in Audit Framework under Configuration.

This will store all of the course registration object entries at the site level, it will create a control panel under Content & Data for easy reviewing, and it will also enable an audit trail (often handy to see who changed what). You can click on Save to save the draft, but you can't yet Publish the object since we haven't added any fields.

Click on the Fields tab to add our custom fields. We need to add three custom fields by clicking on the Add button:

Course is a Picklist using the Upcoming Course Picklist we created earlier.

Notes is just a LongText field, nothing special there.

Registration Status is a Picklist using the Registration Status Picklist we also created earlier. Check the Mark as State option, then for the Default Value select the Pending item. This is the field that we will be using the for the business process handling for the course registration process.

We need to define a One to Many relationship from User to the Course Registration, as a user can register for many courses.

Click on the Relationships tab, then click on the Add button to add a new relationship. Set the fields as shown below, then click on the button to swap the Course Registration and User records so it becomes One Record of User, Many Records Of Course Registration:

After clicking on Save you'll return to the Relationships tab, but the new relationship will not be shown. Click on the Fields tab to find the new Course Attendee relationship listed:

The last part in defining the Course Registration object is to define some actions. Click on the Actions tab, then click on the Add button to add a new action. Define the action as below:

On the Action Builder tab, set as follows:

This is setting up an action that will execute after the Course Registration is added. The action itself is going to set the Course Attendee to be the current user. This way the current user will not have to pick themselves when entering a new course registration, it will automatically be assigned to themselves.

We then add another action as below:

And set the Action Builder tab as follows:

This defines a Standalone action (an action that can be invoked any time, anywhere) and will update the Course Registration's Registration Status, assigning it the value of registered.

Repeat this to create a Deny Registration action which sets the Registration Status to denied.

Now our Course Registration object definition is ready. Click on the Details tab, then click on the Publish button to complete the object and enable it for use. And since the object is done, we can move on to creating the course registration page...

Step 5: Creating the Course Registration Page

The Course Registration object is ready, now the users need a way to submit registrations for the courses.

Use the Waffle menu to get back to your Masterclass site, then open the Site Menu.

Take a moment to go to the Content & Data section and find your Course Registrations control panel. Initially it will be empty. Go ahead and click on the Add button to add a course registration.

When filling in the form, I only have to do the course dropdown and the notes. The Course Attendee will be set to my login because of the Action that we added, and the Registration Status defaults to Pending.

After adding two course registrations and then returning to the list, I find:

Note the action dropdown on each line item includes the Approve Registration and Deny Registration standalone actions that we added to the object. We could use these to approve or deny registrations.

Before moving on, note that the control panel works, but it's not very practical. We don't want average users having access to the control panel, and heck we probably don't want the Course Administrators coming in here either. It is convenient for us as Liferay Administrators to get to the full list, but that's about as useful as it gets.

So let's create a course registration page. Go to the Site Builder -> Pages panel, then create a new page. Use Main-1 as the page template, and call the page Course Registration. Click the peapod menu (found where the Waffle normally is) and choose Permissions, then uncheck the Guest View permission since only authenticated users can register for courses.

Next we'll start adding fragments to build out the presentation...

First I added a Container fragment so basically I could control the padding around the contained elements.

Next I added a Masterclass Heading fragment (one of the custom fragments from Masterclass) and set it to Course Registration.

Next I added a Form Container fragment and set it to use the Course Registration object. This auto-created four child fragments for each of the fields in the object plus a Submit button.

Since the Course Attendee would be assigned when I created the instance, I deleted the form field for that Object field.

And since the default Registration Status would be Pending and the user cannot change their own status, I removed that form field also.

After playing around some more with margins and padding, I came up with the page you see above.

Since these are just fragments, you could take this a lot farther, affecting the styling or replacing the regular form field fragments with custom form field fragments, add other fragments, basically do whatever you need to in order to get the presentation that you're looking for.

I was happy with this layout, so I published the page.

If you see a message like this one, it is safe to ignore.
The Registration Status field, since it is a State Manager field, is required, but we deleted it from the form. You can ignore this warning because the field does have an assigned default value, so the warning itself is incorrect, just go ahead and click the Publish button.

Finally we have to go to Site Builder -> Navigation Menus to the Main Navigation menu and add the new Course Registration page. Place it where you feel it should go in the navigation, there's no wrong answer here.

After the page is added, go ahead and go to the site, find your page in the navigation, go there and submit a Course Registration.

When you click the Submit button, the new Course Registration object will be added, and you can verify if you go to the Course Registrations control panel.

Note, however, that this works because we are still logged in as the Administrator. We have some work to do to enable this for regular users.

Step 6: Update User Role Permissions

If you did log out as an administrator and back in as one of your test student accounts, the page would display and you could populate the form, but when you clicked Submit you'd encounter an error.

Let's fix that now.

Log in as the administrator, then go to the Waffle menu, then Control Panel, then to Roles. Find the User role, then click on it.

Once in the role, click on the Define Permissions tab.

In the search bar, enter "course". We're interested in the Course Registrations item under the Applications. We don't need to worry about the one under Content & Data because a User is never going to be in the control panel.

Check the permissions as follows, then click on Save. Basically the only thing you're changing here is adding the Add Object Entry permission.

Now, if you log in as one of your students, they can add course registrations without failures.

Step 7: Add My Course Registrations Page

So now students can register for courses, but they probably also want to see which courses they have registered for and what their registration status is.

So now we will create a new My Course Registrations page. Go to the Site menu, to Site Builder -> Pages, then add a new page using the Private Area template, give it the name My Course Registrations.

This page we will configure to show the list of course registrations.

First I added a container (I always like to start with a container), and inside of it I added a Collection Display fragment, and I configured it to use the Course Registrations Collection Provider (you get one of those for each Object that you create).

For the Collection Item, I added a 2-column Grid, and then I added a Masterclass Text Block fragment to each column. For the left column, I mapped it to use the Course, and the right column was mapped to the Registration Status.

The page was ready, so I published it. I then went to the Site menu, to Site Builder -> Navigation Menus and clicked on the Private Area Navigation. I added the My Course Registrations page.

Then when I navigated to the site, then to the My Learning private area, My Course Registrations is there and when I'm on it, I see the list of courses. Here's the view logged in as one of my students:

I only see the courses that this student has signed up for.

So far, so good, but we haven't dealt with the Course Admin view yet...

Step 8: Add Course Administrator View

The course administrator gets to see all of the course registrations and can approve or reject registrations. Let's take care of that now.

First we need to update the Course Administrator role. Like we did for the User role, go to the Define Permissions tab for the Course Administrator role.

For this role, add the Add Object Entry, action.approveRegistration, action.denyRegistration, Update, and View permissions, then save the role.

Go back to the Masterclass site, then to the Site menu, then Design -> Page Templates, then click on the Private Area template to edit the template. When it opens, click on the Configure Allowed Fragments button in the drop zone, then check the Button fragment under the Basic Components category, then click Save and then Publish Master. This change will allow us to use a regular button on a Private Area page, and we'll be needing that button shortly.

Next, go to the Site menu, to People -> Segments. You may not have any defined, which is fine. Click the New button to create a new Segment, call it Course Administrators, and give it a condition that the User has a Regular Role and select the Course Administrators role. This will define a segment for all users that have this particular role. If you created the sample users, you should see the conditions map at least 1 member.

Go to the Site menu, to Site Builder -> Pages, then edit the My Course Registrations page.

Near the top left there is the Experience dropdown. Click on it, then click on the New Experience button.

Create a new experience called Course Administrators and choose the Course Administrators segment in the dropdown.

In the experiences list, select the Course Administrators experience, then click the Up arrow to move the Course Administrators above the Default experience.

The Course Administrators experience is now active (being edited).

Click outside of the Experience dialog to return to editing the page.

Change the grid from 2 column to 4 column. Drop a Masterclass Text Block fragment in the new column 3, and a 2-column Grid fragment into column 4.

Next I dropped Button fragments into each column in the grid in column 4.

I mapped the text block in column 1 to the Course Attendee, I mapped the one in column 2 to the Course, and I mapped the one in column 3 to the Registration Status so I would see what the current status was before clicking the buttons.

For each button, on the Button Options I changed the Type to be an Action, then for each of the child action elements, I set the Mapping to the action to perform, and the Action tab was also set to the action to perform and I also checked the "Reload Page After Success" item. This way, whether you clicked on Approve or Deny, the page should refresh and show the new status right away.

At this point the page was ready, so I published it.

And when I go to the site and navigate to the page when I have the Course Administrator role, I see the list and the buttons and, after clicking some, I can see that the Registration Status has changed.

So, what exactly is going on here?

First, if you have the Course Administrator role, the permissions that we added to the role allow them to see all course registrations, not just their own.

Second, when you land on this page, you have a specific experience that you get, the one with the buttons, but if you're not part of this segment you are in the Default segment and you see the view we created before.

Step 9: Party Party, We're Done

Wait, what?

Yes, that's right, we're done with our application, so now it is time to party!

Well, instead of partying, let's review some of the choices that we made and see if we couldn't have made some different choices...

In Step 1, we created a new site using the Masterclass template. Other than some of the custom fragments that we used, there was nothing in this blog post that couldn't be done with any other site. Everything we did, from Objects to the different Fragments, Content Pages, heck even the roles and Segments, all of those are OOTB features that can be used on new or existing sites.

In Step 3, we created a custom Course Administrator role, but we probably should have also created a Student role and added permissions to it, rather than to the User role. Why? Well every authenticated user has the User role, but not every User should be able to register for courses. If we used a special Student role for that, we would have been better able to control who could add new Course Registrations and who couldn't. Plus, using the technique from Step 8, we could have isolated the Student experience so only they could get to the form, and a non-Student experience could suggest signing up or whatever is necessary to become a Student.

Also in Step 3, we used a Picklist for upcoming courses, but this isn't really practical, yeah? I mean, we actually would have been better served creating a Course object which would allow the Course Admin to easily create new courses, then have the one to many relationship from Course to Course Registration. It would have changed our implementation and obviously the fragments would have needed to change for the form (maybe we'd even want to use a custom fragment for an enhanced course selection). 

In Step 4, we did not define any Layouts or Views for the object. Defining these will control how the control panel looks, both for the list as well as for the form, and it can also affect the generated Course Registration widget if you enabled that item on the Details tab. I tend to create a Layout and a View, both marked as default, to ensure the control panel has what I need and nothing more, but it is optional, especially if you have no plans of using the control panel at all.

Also in Step 4 we didn't really look at the State Manager tab. It's this tab that controls the transition allowed between the states for the Registration Status item. When we added the field, a new State transition was defined for us allowing transition from any state to any other state. In a true BPM, we probably wouldn't want to allow for example a Registered course to transition back to a Pending, so the transition list would need to be edited to define the approved transitions. If we were to do this, the actions we defined for approving or denying registrations would need to be updated so they would only transition if the current state was in an approved state for the transition.

In Step 5, we created a form using a Form Container fragment and mapped fields for the Course Registration object. Another alternative would have been to create a Form from the Site menu, Content & Data -> Forms, but these forms tend to have problems with the State fields. Remember I got a warning trying to save the content page with the Form Container that didn't have a field mapped to the Registration Status guy, and it was okay for me to ignore the warning because the default Pending would be used. When I tried creating a Form using this object, I could not save the Form if the field wasn't in there. No warning this time, an error preventing the save. I could put the field on the form, but it is marked as required and I found no way to hide it or disable it. If you don't have states in your Object, you can probably use a Form. Personally, I'd recommend using a Form Container fragment unless, for some reason, you can't. Maybe you want a multi-page form, maybe you want something else that Forms provide that Form Containers do not yet support. In any case, try going with a Form Container unless you can't.

And finally, note that this was not the only way to have implemented this. I could just as well created a custom React element using a Client Extension and let it use the headless APIs to list course registrations, handle the form/data entry, approve/reject the registrations, ... It gets away from the no code approach that we achieved here, but if the requirements call for it? And since I based it on Objects, the headless APIs mean my React custom element is still going to be fairly simple as it just needs to handle the presentation, the APIs handle the rest.

Conclusion

So, just what did we do here?

We accomplished the following:

  • We defined an object to hold the course registration data.
  • We created a page with a Form fragment on it so users could submit new course registrations.
  • We created a page with a Collection Display fragment using a Collection Provider created automatically for the Course Registration object, allowing us to show a list of Course Registrations and use fragments with field mappings to the Course Registration for the display.
  • We set up different permissions on the Course Registration object based upon roles.
  • We created a view for regular users to see the courses they registered for and the status of those registrations.
  • We are using the Object State Manager feature to define a business process associated with the Course Registration object, a business process which is not part of Liferay Workflow and can conform to whatever business process we want to define.
  • We created a Course Administrator role, a corresponding segment, and a custom experience for this segment which provided the full list of course registrations and buttons to change the registration status.
And how much code did we write to do all of this?

Not a single line of Java, not a single line of FreeMarker, not a single line of Javascript...

This was all done using low code/no code features of Liferay, specifically Liferay Objects and Fragments.

It just couldn't get any easier!

Hopefully you've followed along as we went through these steps. I know it's a long blog post, but I hope you found it worthwhile to do all of these things and have learned something that you can take back and use in your day to day job with Liferay.

Blogs

Awesome blog which helps to piece together different parts of liferay which can sometimes be talked in different contexts and seeing them used together is much appreciated.