Build Your First Liferay Custom Application With Angular UI

Introduction

Liferay DXP is a flexible platform which has been developed initially to support developers to easily implement business automation needs. We always face the question “what if i have a custom business requirements, how easy can we implement that if we adapt Liferay?”, in this blog we will discover the ease of customizing Liferay to implement business needs smoothly.

Prerequisites

In order to get the most understanding of this blog, this blog has been authored as a user guide which walks you through the steps to build the custom application, for that you will need the following to be installed / configured on your computer:

  1. Liferay Developer Studio / IntelliJ Studio with Liferay Plugin.

  2. Liferay bundle to be installed and configured.

In this blog i will be using IntelliJ Integrated Development Environment 

i

Quick Jump Into Liferay Workspace Project and Modules

In order to start working with liferay customization you will need to create your Liferay Workspace Project, this workspace will be the main host for your custom bundles.

The following are the steps to create your workspace project:

  1. Open IntelliJ

  2. Click on File -> New -> Project.

  3. Select Liferay then Liferay Gradle Workspace.

  4. Name your project, for this Exercise, we will name the workspace as Exercise-Project.

  5. From the project file structure, select modules and create a new Liferay Module.

  6. Provide the package name “com.liferay.training.bookmarks”.

  7. Select service-builder from the module types and click Next.

  8. Provide the module name “services”.

Congratulations! You have just created your project successfully, now let's look at the project structure that you have just created and try to understand it.

Liferay Service Module 

File Structure

In this section we will try to understand the main files and folders that you will be interested in while reading this blog.

Figure (01)

  • Services-api: This folder will contain the auto generated interfaces required to identify your services signatures and data envelopes, most probably you will not use this folder to do any customizations.

  • Services-service:  This is where you will be building your Local Services and Remote Services, and defining your data entities.

  • Services-service -> service.xml: This is where you will be defining your data tables which are required for to be developed bookmarks.

Design Your Data Structure

In order to build your data structure for your bookmarks application, all what you need to do is to define your data schema in the service.xml, once you decided about the schema and you specify that in the xml file, Liferay will generate the services code and database tables using a Gradle process.

The following is a simple example of the data structure schema:

<service-builder dependency-injector="ds" package-path="com.liferay.training.bookmarks">

<namespace>BOOKMARK</namespace>

<entity local-service="true" name="Bookmark" remote-service="true" uuid="true">

<!-- PK fields -->

<column name="bookmarkId" primary="true" type="long" />

<!-- Group instance -->

<column name="groupId" type="long" />

<!-- Audit fields -->

<column name="companyId" type="long" />

<column name="userId" type="long" />

<column name="userName" type="String" />

<column name="createDate" type="Date" />

<column name="modifiedDate" type="Date" />

<!-- Other fields -->

<column name="title" type="String" />

<column name="description" type="String" />

<column name="url" type="String" />

<!-- Order -->

<order by="asc">

<order-column name="createDate" />

</order>

<!-- Finder methods -->

<finder name="UserId" return-type="Collection">

<finder-column name="userId" />

</finder>

                                <finder name="BookmarkId" return-type="Collection">

<finder-column name="bookmarkId" />

</finder>

<!-- References -->

<reference entity="Group" package-path="com.liferay.portal"></reference>

<reference entity="AssetEntry" package-path="com.liferay.portlet.asset"></reference>

<reference entity="AssetLink" package-path="com.liferay.portlet.asset"></reference>

<reference entity="AssetTag" package-path="com.liferay.portlet.asset"></reference>

</entity>

</service-builder>

You have noticed that we have added Audit fields which we will be using it to track the asset creation, and we have created a primary key for the asset it self “bookmarkId” and then we have added something called finder, finder will be translated in the generated code as a query over bookmarks by the finder column which is in our case is the userId.

For the references, we are adding those references to integrate the bookmark entity with the asset framework.

Build Your Service 

Now we have defined our data structure we will need to build the service which will generate the local service and remote service.

The question is what is the difference between local service and remote service?

Local Service will be used without permission check, so the one who will be calling this service will be able to crawl all of the bookmarks with regards to the permissions, Remote Service will be calling the permission checker on each call.

Please follow the below steps in order to build your service:

  1. Open Gradle Panel

  2. Navigate to: Exercise-Project -> Modules -> services -> services-service -> tasks -> build

  3. Run the “BuildService” task.

Once the build service successfully completed you will find the following under your module:

Figure ( 02 )

Gradle has successfully generated the source code for the bookmarks local and remote services along with the data model.

Add Your Service Code

Now we need to create our own code to Create / Remove / Get bookmark(s), this will require for your side a knowledge of Java programming, Liferay does not impose a special programming language or something specialized and tailored only for Liferay.

Let's start by implementing the local service for the required methods:

Local Service Code

  • Create new bookmark method

public Bookmark addBookmark(long groupId,String title, String description, String url

     , ServiceContext serviceContext) throws PortalException {

  Group group = groupLocalService.getGroup(groupId);

  long userId = serviceContext.getUserId();

  User user = userLocalService.getUser(userId);

  // Generate primary key for the bookmark.

  long bookmarkId = counterLocalService.increment(Bookmark.class.getName());

  Bookmark toBeCreated = createBookmark(bookmarkId);

  toBeCreated.setGroupId(groupId);

  toBeCreated.setUserId(userId);

  toBeCreated.setUserName(user.getScreenName());

  toBeCreated.setCreateDate(serviceContext.getCreateDate(new Date()));

  toBeCreated.setModifiedDate(serviceContext.getModifiedDate(new Date()));

  toBeCreated.setTitle(title);

  toBeCreated.setDescription(description);

  toBeCreated.setUrl(url);

  return super.addBookmark(toBeCreated);

}

  • Delete bookmark method

Public void removeBookmarkById(long bookmarkId,ServiceContext serviceContext)

{

  bookmarkPersistence.removeByBookmarkId(bookmarkId);

}

  • Get bookmarks method

public List<Bookmark> getBookmarksForCurrentUser(ServiceContext serviceContext)

{

  List<Bookmark> result =

        bookmarkPersistence.findByUserId(serviceContext.getUserId());

  return result;

}

Remote Service Code

  • Create new bookmark method

public Bookmark addBookmark(long groupId, String title, String description, String url

     , ServiceContext serviceContext) throws PortalException {

  return bookmarkLocalService.addBookmark

        (groupId,title,description,url,serviceContext);}

  • Delete bookmark method

public void removeBookmarkById(long bookmarkId,ServiceContext serviceContext)

{bookmarkLocalService.removeBookmarkById(bookmarkId,serviceContext);}

  • Get bookmarks method

public List<Bookmark> getBookmarksForCurrentUser(ServiceContext serviceContext)

{return bookmarkLocalService.getBookmarksForCurrentUser(serviceContext);}

With the above implemented, you have successfully implemented the OSGI service and the JSON Web Services, and that's the beauty of Liferay Developer tools.

Lets deploy it and discover the newly created JSON APIs, in order to build it, please follow the below steps:

  1. Open Gradle Panel

  2. Navigate to: Exercise-Project -> Tasks -> Docker 

  3. Run the “BuildDockerImage” task.

  4. Navigate to your project folder and open the build -> Docker -> Deploy

  5. Copy both *.Jar files to your liferay bundle -> Deploy folder

  6. Navigate to your Liferay JSON APIs explorer.

http://<Liferay:Port>/api/jsonws

  1. From the services list select bookmark

Figure ( 03 )

User Interface Module (Progressive Web Application)

Inorder to build your user interface with Liferay, you have many options:

  • JSP Widget

  • Progressive Web Application

    • Angular 

    • React

    • VueJS

  • JavaScript

For this blog, we will be working with Angular to build the user interface.

Bookmark New Entry Widget

We will be creating a widget which will be adding the current url as a bookmark, for that please follow the below steps:

  1. Navigate to the Workspace folder structure and select Modules.

  2. Right click on Modules and click on Liferay Module.

  3. Provide the package “com.liferay.training.bookmarks.web.create”.

  4. Select “NPM-Angular-Portlet” for the module type and click next.

  5. Provide the module name “addBookmark”

  6. Click Finish.

  7. Navigate to the app.component.ts inside Modules -> addBookmark -> src -> main -> resources -> META-INFO -> lib -> app

  8. Place the following code 

import { Component } from '@angular/core';

declare const Liferay: any;

 

@Component({

  template: `<button (click)="addToBookmarks()">Add To Bookmarks</button>`,

})

export class AppComponent {

  currentURL()

  {

     return Liferay.ThemeDisplay.getLayoutURL();

  }

  getRelativeURL()

  {

     return Liferay.ThemeDisplay.getLayoutRelativeURL();

  }

  generateServicePromise(serviceURL:string,serviceObject:any):Promise<any>

  {

     const prom = new Promise((resolve,reject)=>{

        Liferay.Service(serviceURL,serviceObject,(result:any)=>{

           resolve(result);

        },(error:any)=>{

           reject(error);

        });

     });

     return  prom;

  }

  getGroupId()

  {

     return Liferay.ThemeDisplay.getScopeGroupId();

  }

  async addToBookmarks()

  {

     var serviceUrl = "/bookmark.bookmark/add-bookmark";

     var object = {

        groupId:this.getGroupId() ,

        title: this.getRelativeURL(),

        description: '',

        url: this.currentURL()

     }

     var prom = this.generateServicePromise(serviceUrl,object);

     var toBeAdded = await prom;

  }

}

  1. Build your module and deploy the generated Jar file which will add a new widget to your page editor toolbox.

Figure ( 04 )

Bookmark List Widget

We will be creating a widget which will be adding the current url as a bookmark, for that please follow the below steps:

  1. Navigate to the Workspace folder structure and select Modules.

  2. Right click on Modules and click on Liferay Module.

  3. Provide the package “com.liferay.training.bookmarks.web.list”.

  4. Select “NPM-Angular-Portlet” for the module type and click next.

  5. Provide the module name “listBookmark”

  6. Click Finish.

  7. Navigate to the app.component.ts inside Modules -> addBookmark -> src -> main -> resources -> META-INFO -> lib -> app

  8. Place the following code 

import {Component, OnInit} from '@angular/core';

declare const Liferay: any;

@Component({

  template: `<div *ngFor="let bookmark of bookmarks" class="row">

     <div class="col-10">

        {{bookmark.title}}

     </div>

     <div class="col-2">

        <button class="btn btn-danger" (click)="deleteBookmark(bookmark.bookmarkId)">

           Delete

        </button>

     </div>

  </div>`,

})

export class AppComponent  implements OnInit{

  generateServicePromise(serviceURL:string,serviceObject:any):Promise<any>

  {

     const prom = new Promise((resolve,reject)=>{

        Liferay.Service(serviceURL,serviceObject,(result:any)=>{

           resolve(result);

        },(error:any)=>{

           reject(error);

        });

     });

     return  prom;

  }

  public bookmarks:Array<any> = [];

 

  async deleteBookmark(bookmarkId:number)

  {

     var serviceUrl = "/bookmark.bookmark/remove-bookmark-by-id";

     var object =

        {

           bookmarkId:bookmarkId

        };

     var prom = this.generateServicePromise(serviceUrl, object);

     await prom;

     this.getBookmark();

  }

  async getBookmark()

  {

     var serviceUrl = "/bookmark.bookmark/get-bookmarks-for-current-user";

     var prom = this.generateServicePromise(serviceUrl, {});

     this.bookmarks = await prom;

  }

 

  ngOnInit(): void {

     this.getBookmark();

  }

}

  1. Build your module and deploy the generated Jar file which will add a new widget to your page editor toolbox.

 

Source Code

Please visit the following git repository to have a copy of the code we have been working on in this blog.

https://github.com/mahmoudhtayem87/LR-Bookmarks