Tutorial of Using Promise Object in SOY Portlet to Access Liferay's web service.

Company Blogs 12 de mayo de 2017 Por Neil Jin Staff

In this tutorial, I am going to talk about how to utilize promise object to access web service in Liferay DXP.
Before we get into more detail, please take a note that the version Liferay I am using is Liferay DXP DE-15, since there's big soy development experience improvement in this patch.
In this tutorial, we are going to put all these knowledge together to make a portlet that can visualize data from a web service. We have talked about making a Service Builder remote services. Liferay DXP serves these services as a service provider. And now we are using Liferay as a service consumer to call these services. These services is not limited in Liferay platform, they can be served by any system. This is a way of integrating with 3rd party system. The 3rd party system is good at their own job in their business domain. Liferay is good at providing a connected and consistent user experience regardless where the data comes from and how users choose to engage with the business.
Matching to real world requirement, the approach in this tutorial can satisfy requirement like:
  • User Dashboard that the data comes from 3rd part systems.
  • Responsive(Liferay's bootstrap)
  • Plug and play(Microservice)
  • Abundant technologies option(NPM)
Let's Go!

Step 1 - User the knowledge you already have to create a chart JS portlet

As we have done in the previous articles, let's create a new chartjs soy portlet project called monthly-trading-web. But this time, we can modify package.json a little bit.
Previously the metal-cli npm plugin has some CRLF compile issue. This issue has been resolved in the latest release of metal-cli. So the good news is you don't need to replace the CRLF with LF anymore. You just need to make sure your metal-cli's version is 4.0.1.
 "dependencies": {
  "metal-component": "^2.10.0",
  "metal-soy": "^2.10.0",
  "chart.js": "^2.4.0"
 "devDependencies": {
  "liferay-module-config-generator": "^1.2.1",
  "metal-cli": "^4.0.1"
 "name": "monthly-trading",
 "version": "1.0.0"
Thanks to my friend Chema Balsas and Liferay UI team for the effort again!

Step 2 - Find your service and mockup data

As we have done in the previous tutorial, we can add some mockup data through /api/jsonws.
The service context is Banking. The method name is add-monthly-trading.
After adding mockup data. You can click on the get-monthly-trading-by-year and then input the year. Then click Invoke button, and then click on the URL Example tab to get the service URL.
Take a note of the p_auth. This is an auth token for authentication.

Step 3 - Pass the Service URL to JS

In the render method of portlet class, we can put the url into the template so that the soy and es.js can receive the variable.
public void render(
   RenderRequest renderRequest, RenderResponse renderResponse)
  throws IOException, PortletException {

   String tradingYear = "2017";
   String pauth = AuthTokenUtil.getToken(PortalUtil.getHttpServletRequest(renderRequest));

   String portletNamespace = renderResponse.getNamespace();

   template.put("remoteURL", "/api/jsonws/banking.monthlytrading/get-monthly-trading-by-year/year/" + tradingYear + "?p_auth=" + pauth);
   template.put("tradingYear", tradingYear);
   template.put("portletNamespace", portletNamespace);
   super.render(renderRequest, renderResponse);
In the es.js, we can receive the variable in constructor method.
constructor(opt_config) {


  let remoteURL = opt_config.remoteURL;
  let tradingYear = opt_config.tradingYear;
  this.portletNamespace = opt_config.portletNamespace;
  this.createRemoteChart_(remoteURL, tradingYear); // Hasn't been defined yet.

Step 4 - Make a promise

What is a Promise?
"A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action's eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future." --MDN
In SOY portlet we can utilize ES6 to use Promise project.
In our es.js file this is how we define and return a promise object.
First you need to import the Promise object:
import { CancellablePromise } from 'metal-promise/src/promise/Promise';
This is using metaljs' cancellable promise.
And then you can define a method to define and return the promise object:
  * Get remote trading data
  * @protected
  * @param {String} remoetURL
  * @return {CancellablePromise} A promise that will resolve save permise
 getChartData_(remoteURL) {
  let promise = new CancellablePromise((resolve, reject) => {
   let requestConfig = {
    contentType: false,
    dataType: "json",
    processData: false,
    type: "GET",
    url: remoteURL

    .done((data) => resolve(data))
    .fail((jqXHF, status, error) => reject(error));

  return promise;
Take note that we are using the out of the box jQuery in Liferay. In Liferay we have sandboxed the jQuery(v2.1.4) in to AUI object. When you need to call jQuery method just use AUI.$.... And you are free to use your prefered jQuery as well without naming conflict.

Step 5 - Fulfill your promise

Next we will write the UI logic based on the data from the web service.
  * Create Chart with data url
  * @param {String} remoteURL
  * @protected
 createRemoteChart_(remoteURL, tradingYear) {
  this.getChartData_(remoteURL).then(data => {

   let chartcanvas = document.getElementById(this.portletNamespace + "monthly-trading-chart");

   let labels = Array.from(data, d => d.month);
   let bgColor = this.getPreferedColors_(data.length, 0.3);
   let borderColor = this.getPreferedColors_(data.length, 0.8);
   let dataValue = Array.from(data, d => d.volume);

   let chartData = {
    labels: labels,
    datasets: [
      label: "Monthly Trade of " + tradingYear,
      backgroundColor: bgColor,
      borderColor: borderColor,
      borderWidth: 1,
      data: dataValue,

   let options = {
    scales: {
     xAxes: [{
      stacked: true
     yAxes: [{
      stacked: true

   let myBarChart = new Chart(chartcanvas, {
    type: 'bar',
    data: chartData,
    options: options

I have another method that lets the bar chart only uses my preferred color.
  * Get Bar background colors from prefered colors
  * @protected
  * @param {int} length
  * @param {string} opacity
  * @return {Array} a color array
 getPreferedColors_(length, opacity=1) {

  let colorsRepo = [
   "255, 99, 132",
   "54, 162, 235",
   "255, 206, 86",
   "75, 192, 192",
   "153, 102, 255",
   "255, 159, 64"

  let colors = new Array();

  for (let i = 0; i < length; i++) {
   let index = i % colorsRepo.length ;
   let color = "rgba(" + colorsRepo[index] + "," + opacity + ")";

  return colors;
Hope you enjoy.

使用Liferay DXP創建圖片播放

Technical Blogs 11 de abril de 2017 Por Neil Jin Staff

Liferay DXP本身帶有一個Carousel的範本,但是這個範本使用的是舊的AUI的範本。現在因為Liferay DXP是基於Lexicon和Bootstrap 3的,創建一個響應式的圖片播放非常簡單。



  • 圖片播放基本功能
  • 螢幕尺寸變化後,圖片不變型
  • 修改圖片不需要修改代碼
  • 不需要部署外掛程式
  • 可複用



  • 可以利用Bootstrap 3的carousel外掛程式實現圖片播放基本功能(https://www.w3schools.com/bootstrap/bootstrap_carousel.asp)
  • 可以利用Lexicon的Image Aspect Ratios保持圖片的長寬比(http://liferay.github.io/lexicon/content/images-and-thumbnails/#image-aspect-ratios)
  • 可以利用Web Content來實現內容存儲和範本化,同時可複用。
  • 不需要額外的OSGi外掛程式開發。JSON格式的structure和FTL格式的範本移植起來很容易。


1. 創建Web Content Structure

可以簡單的創建一個web content structure。其中包含一個文本欄位,用於設置長寬比,一個可重複的圖片欄位。



2. 創建對應的範本

我們可以直接為剛剛創建好的carousel structure創建一個範本。


3. 定義圖片長寬比



 #carousel-${request.get("portlet-namespace")} .aspect-ratio-custom {

  padding-bottom: ${aspectRatio.getData()};


@media (max-width: 799px) {

     #carousel-${request.get("portlet-namespace")} .aspect-ratio-custom {

      padding-bottom: 67%;





4. 實現Bootstrap的carousel和Lexicon的固定比例



<#if image.getSiblings()?has_content>

 <section class="carousel-container">

  <div class="carousel slide" data-ride="carousel" id='carousel-${request.get("portlet-namespace")}'>

   <ol class="carousel-indicators hidden-sm hidden-xs">

    <#list image.getSiblings() as cur_img>

     <li class="${(cur_img?counter == 1)?then('active', '')}" data-slide-to="${(cur_img?counter == 1)?then(0, (cur_img?counter - 1))}" data-target='carousel-<@portlet.namespace />'></li>



   <div class="carousel-inner" role="listbox">

    <#list image.getSiblings() as cur_innerImage>

     <div class="${(cur_innerImage?counter == 1)?then('active', '')} item">

      <div class="aspect-ratio aspect-ratio-custom aspect-ratio-middle">

        <img alt="${cur_innerImage.getAttribute("alt")}" src="${cur_innerImage.getData()}">





   <a class="left carousel-control" href='#carousel-${request.get("portlet-namespace")}' role="button" data-slide="prev">

    <span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>

    <span class="sr-only">Previous</span>


   <a class="right carousel-control" href='#carousel-${request.get("portlet-namespace")}' role="button" data-slide="next">

    <span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>

    <span class="sr-only">Next</span>






5. 上傳圖片,添加web content

可以在Document and Media中新建個資料夾,然後上傳一些圖片

利用我們剛剛創建好的structure和template創建web content。在我這裡我的Aspect Ratio是26%,即長度大概是寬度的4倍。


6. 預覽並發佈

創建完成之後,就可以發佈了,然後可以使用Web Content Display portlet在頁面上顯示了。





Liferay DXP Tutorial: Carousel Template

Technical Blogs 9 de abril de 2017 Por Neil Jin Staff

Liferay DXP ships with a default carousel Application Display Template in global site. But this one is an old fashion AUI one that has been used often before DXP. Since Liferay DXP support Lexicon, I am going to show you how to utilize Lexicon and Bootstrap to create a true mobile responsive carousel in web content. It's remarkably easy with Lexicon and Bootstrap to create responsive UI component in Liferay DXP.
Basic requirement:
A carousel with images.
When the screen resolution changes the images are not out of shape.
Change images without work of modifying the code.
No plugin development.
We can utilize Bootstrap 3(the default bootstrap in Liferay DXP) carousel component to support the basic carousel feature.(https://www.w3schools.com/bootstrap/bootstrap_carousel.asp)
We can utilize Lexicon Image Aspect Ratios to keep the image in shape when screen resolution changes.(http://liferay.github.io/lexicon/content/images-and-thumbnails/#image-aspect-ratios)
We can utilize Web Content Structure to save image information with a repeatable field and a Web Content Template to put HTML and CSS as a framework.
No OSGi module plugin development at all. The JSON format structure and FTL format template are easy to migrate.
Step 1: Building our web content structure.
We can simply create a web content structure with a, aspect ratio field and repeatable image field.
Step 2: Create template for the carousel structure
We can simply create a template for the newly created carousel structure.
Step 3: define your image aspect ratio.
As lexicon documented, you can define a custom aspect ratio for your carousel through CSS. For mobile, I just simply hard-code the ratio.
 #carousel-<@portlet.namespace /> .aspect-ratio-custom {
    padding-bottom: ${aspectRatio.getData()};
@media (max-width: 799px) {
    #carousel-<@portlet.namespace /> .aspect-ratio-custom {
        padding-bottom: 67%;
Step 4: implement by Bootstrap carousel and Lexicon aspect ratio
You can simply apply boot strap HTML and CSS and loop out the image indexed and images. And apply the aspect-ratio and aspect-ratio-custom to the div contains the image.
<#if image.getSiblings()?has_content>

    <section class="carousel-container">

        <div class="carousel slide" data-ride="carousel" id='carousel-<@portlet.namespace />'>

            <ol class="carousel-indicators hidden-sm hidden-xs">

                <#list image.getSiblings() as cur_img>

                   <li class="${(cur_img?counter == 1)?then('active', '')}" data-slide-to="${(cur_img?counter == 1)?then(0, (cur_img?counter - 1))}" data-target='carousel-<@portlet.namespace />'></li>



            <div class="carousel-inner" role="listbox">

                <#list image.getSiblings() as cur_innerImage>

                    <div class="${(cur_innerImage?counter == 1)?then('active', '')} item">

                        <div class="aspect-ratio aspect-ratio-custom aspect-ratio-middle">

                            <img alt="${cur_innerImage.getAttribute("alt")}" src="${cur_innerImage.getData()}">





            <a class="left carousel-control" href='#carousel-<@portlet.namespace />' role="button" data-slide="prev">

                <span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>

                <span class="sr-only">Previous</span>


            <a class="right carousel-control" href='#carousel-<@portlet.namespace />' role="button" data-slide="next">

                <span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>

                <span class="sr-only">Next</span>




Step 5: add you content
You can upload some images to your Document and Media Folder.
Then add a web content article based on Carousel structure and template.
For the Aspect Ratio, I put 26%(width/height)
You can also utilize Liferay DXP's embedded image editor to edit your image and apply filters.
Step 6 Publish and Preview
Publish the web content and display it on the page using Web Content Display portlet.
You can change your screen size check if the aspect ratio works.
You can use the Simulation tool to preview if the responsive selector works. Right now the ratio is 67% as I set it in the template.

For the structure and template, you can visit the following link to download: http://samples.liferayee.com/tree/master/sample-templates/web-content/carousel

Liferay OSGi注解(Annotation) - 使用手册(译文)

Technical Blogs 20 de febrero de 2017 Por Neil Jin Staff

当你查看Liferay 7 CE/Liferay DXP源码时,你会看到大量不同的注解。当你除此看到这些注解的时候,可能会感觉到有些无所适从。所以我想写一些引用指导来解释这些注解是什么,并且在你自己的OSGi代码中什么时候使用比较合适。
在OSGi世界中,这种注解用于定义“声明式服务(Declarative Service)”。声明式服务是OSGi中用于动态生成服务。它使容器中的大量的组件(component)间可以互相关联调用。
  • immediate - 通常设置为true。用于使组件在部署后直接启动(start),不用等待其他引用或者使用懒加载(lazy startup)。
  • properties - 用设置一系列OSGi属性绑定到组件。这些属性对于当前组件是可见的,但是更重要的是它们对于其他组件也是可见的。这些组件可以帮助配置组件,也可以用于支持组件过滤。
  • service - 定义组件实现的服务。有时这个属性是可选的,但是通常是必要的,因为这样可以使组件要实现服务更明确。service的值通常是一个接口,但是也可以使用一个实体类。
什么时候需要使用@Component?只要需要在OSGi容器中使用要发布一个组件的时候,就可以使用。不是所有的类都需要是组件。只是当你需要将一个插件植入到Liferay环境中的时候才会声明一个组件。(例如,添加一个导航项、定义一个MVC command handler、覆写一个Liferay组件或者为自己的扩展框架写一个插件)。
@Reference( target = "(javax.portlet.name=" + NotificationsPortletKeys.NOTIFICATIONS + ")", unbind = "-" ) 

protected void setPanelApp(PanelApp panelApp) { 
    _panelApp = panelApp; 
这段代码希望获取PanelApp组件的实例,但是它却指定了Notification portlet中的PanelApp组件。所以其他portlet中的PanelApp不符合条件,只有Notification portlet中的PanelApp才符合条件。
  • MANDITORY - 引用必须在组件启动前可用并且注入。
  • OPTIONAL - 引用在组件启动阶段时不是必要的,在不指定组件的时候也是可以正常工作的。
  • MULTIPLE - 有多种不同的资源可以引用,组件会使用全部的方法。和OPTIONAL相似,在组件启动阶段,引用不是不要的。
  • AT_LEAST_ONE - 多种资源可以满足引用,组件会全部使用。但是在组件启动时,至少有一个资源是可用的。
下一个重要的@Reference属性是policy。Policy的值可以是ReferencePolicy.STATIC (默认)或者ReferencePolicy.DYNAMIC。他们的意义是:
  • STATIC - 只有在已经指派了引用之后,组件才能启动。在启动之后,当出现其他可用引用时,组件也不会理会。
  • DYNAMIC - 无论有没有可用的引用,组件都会启动。并且在有新的可用引用的时候,组件会使用新的引用。
和policy一同使用的另外一个属性是policyOption。这个属性的值可以是ReferencePolicyOption.RELUCTANT (默认) 或者 ReferencePolicyOption.GREEDY。他们的意思是:
  • RELUCTANT - 对于单一(single)的引用,新的可用引用会被忽略。对于多项(multiple)引用,新的可用引用出现的时候会被绑定。
  • GREEDY - 只要新的可用引用出现的时候,组件就会绑定这些引用。.
首先是默认的,ReferenceCardinality.MANDITORY + ReferencePolicy.STATIC + ReferencePolicyOption.RELUCTANT。
另外一种组合是ReferenceCardinality.OPTIONAL/MULTIPLE + ReferencePolicy.DYNAMIC + ReferencePolicyOption.GREEDY。
这个是一个Liferay注解,用于向Spring bean中注入一个Liferay core中的引用。
这是一个Liferay注解,用于向组件中注入一个引用,这个引用是来自于Spring Extender module bean。
我们来解释一下这三种引用注解。根据我的经验来判断,多数情况下,你需要使用@Reference注解。Liferay core Spring beans和Spring Extender module beans同样也是暴露在OSGi容器中的,所以@Reference在多数情况下是没问题的。
如果在使用@Reference的时候,服务没有被注入,并且返回null,这就意味着你可能需要使用其他的引用注解了。选择哪种注解原则不难:如果bean是在Liferay core中,就是用@BeanReference;反之,但是如果是在Spring Extender module中,就是用@ServiceReference注解。注意,无论是bean或者service注解都会要求你的组件使用Spring Extender。如何引用依赖,参考任何使用ServiceBuilder的服务模块,查看build.gradle和bnd.bnd,用同样的方法修改你自己的模块。
protected void activate() { 

protected void activate(Map<String, Object> properties) {

protected void activate(BundleContext bundleContext, Map<String, Object> properties) {
也有其他的方法使用这个注解,只要在源码中搜索@Activate就可以找到很多不同的用例了。除了无参数的activate方法,其他的都依赖于OSGi所注入的值。注意properties映射实际是从你的OSGi的Configuration Admin服务取得的。
@ProviderType是BND中使用的,通常是在考虑将head包含的复杂情况时使用。长话短说,@ProviderType是被BND使用,用来定义在实现类中可以指派的OSGi manifest的版本。并且它尝试限制组件版本范围的使用。
这个是Liferay中为ServiceBuilder实体接口使用的注解。它定义了在service module中用来实现接口的类。
你可以为你的实现增、删、改的方法使用@Indexable注解。如果实体关联com.liferay.portal.kernel.search.Indexer 相关的实现方法,那么实体就可以被索引。
@SystemEvent注解是在ServiceBuilder生成的代码中被可能生成系统事件的方法使用的。系统事件和staging、LAR和导入导出处理等相关。例如,当一个web conteng被删除,这个就会生成一个SystemEvent记录。在staging环境中,当“Publish to Live”原型是,删除SystemEvent确保了相关联的web content在live站点中也被删除。
OSGi有一个基于XML的系统用于为Configuration Admin定义配置详细信息。BND项目的通过使用@Meta注解,可以使BND根据配置接口中使用这个注解的方法生成配置文件。
-metatype: *
这个注解用于“Object Class Definition”方面的配置详细信息。这个注解用于为在接口层为类的定义提供id,名字和语言等详细信息。
什么时候使用这个注解呢?当为组件定义一个Configuration Admin接口时,并且这个接口是在Control Panel -> System Setting有一个配置选项的时候使用。
注意@Meta.OCD属性包含了本地化的设置。这样就可以使用resource bundle来翻译配置名称、详细配置信息和@ExtendedObjectClassDefinition分类了。
这个注解用于“Attribute Definition”,用于定义配置中表单项层面的定义。注解用于为表单项提供ID、名称、说明、默认值和其他详细信息。
什么时候使用这个注解?就是在需要为System Setting中的配置提供更多关于配置项目细节的时候使用。
这个是Liferay的注解,用于定义配置的类型(就是在System Setting中上面显示的那些分类)和配置的范围。
  • SYSTEM - 整个系统中的全局配置,在生个系统中只有一个配置。
  • COMPANY - Company级,每个portal实例中只可以有一个配置。
  • GROUP - Group(站点)级,每个站点中只可以有一个配置。
  • PORTLET_INSTANCE - 这个和portlet实例参数类似,每个portlet实例中都会有一分配置。
这是一个Liferay的注解,用来定义OSGi组件属性,这些属性用来注册Spring bean为一个OSGi组件。你会在ServiceBuilder模块中见到这个注解被使用,将Spring bean暴露给OSGi容器。记着,ServiceBuilder依然是基于Spring的(SpringExtender),这个注解就是用来将Spring bean注册为OSGi组件用的。
什么时候会用到这个注解呢?如果你在你自己的模块中使用了Spring Extender来使用Spring,并且你想将Spring bean在OSGi容器中暴露为组件(这么做可以使其他模块使用这个Spring bean),这个时候你就需要使用这个注解了。
我没有在这里讨论更多的细节,因为这个注解可以在javadoc中被大量的找到。查看这个例子: https://github.com/liferay/liferay-portal/blob/master/portal-kernel/src/com/liferay/portal/kernel/spring/osgi/OSGiBeanProperties.java
这就是我在Liferay 7 CE / Liferay DXP中见到的所有注解了。希望这些信息可以帮到你的Liferay开发。如果你发现本文没有涉及的注解或者希望了解更多细节,请随时向原文博客或者在这里提问。

Toturial of Creating JSON Web Service using Service Builder

Technical Blogs 6 de febrero de 2017 Por Neil Jin Staff

This article talks about how to create json web service based on Service Builder Service.
Knowledge: Service Builder
JSON Web Service
When we intent to make a service to be a web provider to serve json web service, we can utilize Service Builder to build our json web service.
We have an article to talk about how to use Service Builder to build.
I need to build a trading track system to record monthly trading.
I need to build a web service to pass data as JSON of my bank's monthly trading.
(Eventhough in real world the monthly data normally from a query rather than being save as a record, but let's make it like this)
Step 1, Define Your Entity and Service.
So first I need to build an entity and services from Service Builder. Please check Creating Service Builder MVC Portlet in Liferay 7 with Liferay IDE 3 blog as a guidance.
I call my project "monthly-trading".
Define our entity as following service.xml. Note that I set my remote-service to be "true".
<?xml version="1.0"?>
<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 7.0.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_7_0_0.dtd">

<service-builder package-path="monthly.trading">
    <entity local-service="true" name="MonthlyTrading" remote-service="true" uuid="true">
        <!-- PK fields -->
        <column name="monthlyTradingId" 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="year" type="int" />
        <column name="month" type="int" />
        <column name="volume" type="int" />

        <!-- Order -->
        <order by="asc">
            <order-column name="month" />

        <!-- Finder methods -->

        <finder name="Year" return-type="Collection">
           <finder-column name="year" />

Step 2 Build Your Service
Once you have finished, you can run buildService to build your service.
After all interfaces and impl has been generated, you can modify your LocalServiceImpl to add your own local service implementation.
In my example I simply added an add method in MonthlyTradingLocalServiceImpl ignoring all validation.
public MonthlyTrading addMonthlyTrading(int year, int month, int volume) {

    long pk = counterLocalService.increment();
    MonthlyTrading monthlyTrading = monthlyTradingPersistence.create(pk);

    return monthlyTradingPersistence.update(monthlyTrading);


 public List<MonthlyTrading> getMonthlyTradingByYear(int year) {
    return monthlyTradingPersistence.findByYear(year);
Run buildService again to regenerate interfaces.
Now I can modify my ServiceImpl to call my LocalService.
 public MonthlyTrading addMonthlyTrading(int year, int month, int volume) {

    return monthlyTradingLocalService.addMonthlyTrading(year, month, volume);

 public List<MonthlyTrading> getMonthlyTradingByYear(int year) {
    return monthlyTradingLocalService.getMonthlyTradingByYear(year);
Run buildService again and deploy.
By utilizing @JSONWebService Annotation, you can make you class to be whitelist/blacklist based, you can enable/igore a method in JSON web service.
For more detail please check Liferay Dev KB.
Best Practice tips:
It's a good practice to check user permission in Service Impl to make sure all remote service call is secure.
Step 3, Use Your Remote Service
Now you can navigate to http://localhost:8080/api/jsonws in your browser.
Choose "banking" in Context name.
Now the custom json web service is in the list.
You can find JavaScript Example, curl Example, URL Example after you invoke the service.
This is how we add a JSON web service through service builder.
Hope you enjoy it.
Workable jars for test: API, Service

Liferay DXP(EE) Only, Visualize your workflow.

Technical Blogs 24 de enero de 2017 Por Neil Jin Staff

As a supervisor of my department, some times I want to check how much progress of a work is done, who is working on a certain trading? who is reviewing a loan application? If a new business plan application is getting stuck for 2 weeks who is in charge of that? I want more information than a simple word pending...


With workflow out of the box, you can't review any certain workflow's execution log without being assigned. However, the workflow has all the data you need, just need a little effort to achieve it.


In Liferay DXP there's a plugin called Kaleo Designer. You can design your workflow on a graphic panel by dragging and dropping nodes like tasks, states, conditions and transitions conveniently.

I see this plugin more than a designer, it can actually display any workflow definition. With integrating with log it can highlight the nodes with different color for different information. So you can display your workflow for each workflow instance like this:

Right now this kind of customization can only be done on Liferay DXP(EE).

If you are a DXP subscription client, please feel free to contact me, I would donate my solution for free.

Tutorial of using Chart.js in Soy Portlet

Technical Blogs 12 de enero de 2017 Por Neil Jin Staff

The purpose of this tutorial is to let you know how to involve a 3rd part js lib in metal soy portlet and how to use them in ES6 script. 
In Liferay DXP we provide abundant OOTB js lib to help the development. Jquery, AlloyUI, metal, lexicon component and etc.
They are all good, however, in real world project development nothing can satisfy the requirement once for all. In this changing world, the requirement is unpredictable. 
For this we can extend our ootb library by utilizing nodeJS to get 3rd part js lib works in Liferay.
In this journey, I will lead you to take a glimpse of the power of Liferay's integrability.
    Google Closure Soy template.
    Liferay Soy Portlet.
    ECMA script 2015(ES6)
In Tutorial of Creating Soy Portlet article, we have learned how to create a metal soy portlet by Liferay IDE. 
Based on that, we are going to do a further development -- to create a Chart. (In my professional, many business requirement needs some sense of chart, this kind of requirement is very common.)
To build a chart js lib from scratch is not realistic. By utilizing Chart.js or other product on the market would help us to reduce develop effort and save project cost.
So let's start working on a portlet that display a chart.
Step 1, Create a soy portlet.
Create a Soy portlet as it shows in Tutorial of Creating Soy Portlet article with this file structure:
Step 2, ChartJS dependencies
To use ChartJS, we need to add NodeJS dependencies in package.json. Optionally you could manually copy a Chart.js bundle to your project path.
"chart.js": "^2.4.0"
Step 3, include it to your runtime environment.
Because Chart.js is a 3rd part js lib, it doesn't exist in the system(suppose it's not been used before). We have to involve it in our runtime -- in the jar.
So let's config our bnd.bnd to include the js to the right place. Add/Modify the following code to your bnd.bnd
Include-Resource: package.json,\
    META-INF/resources/js/Chart.js = node_modules/chart.js/dist/Chart.js
This means, I want a Chart.js in META-INF/resources/js of the jar, and the source is from node_modules/chart.js/dist/Chart.js
And add web context path in bnd.bnd
Web-ContextPath: /chartjs-soy
Step 4, Work with soy template
We are going to add a canvas for chart to use. So let's add the following code to ChartjsSoy.soy
{namespace ChartjsSoy}
 * Display Chart canvas
{template .render}
    <canvas id="chartjs-soy">
Step 5, import your Chart.js in your Component
I believe all audience of this blog know how to use ChartJS in a traditional way, in html and javascript like this:
<script src="Chart.js"></script>
    var myChart = new Chart({...})
But this is not going to be our approach. We are going to take advantage ES6 to use Chart, to extend our knowledge :)
So add the import in ChartjsSor.es.js in the head
import Chart from 'chartjs-soy/js/Chart';
You can see chartjs-soy is the web context path we define in bnd.bnd, and js/Chart is our web resource folder.
Step 6, create a chart with Chart.js
Add the following method to your Component
     * Create Chart
     * @protected
    createChart_() {
        let chartcanvas = document.getElementById("chartjs-soy");

        let data = {
            labels: ["January", "February", "March", "April", "May", "June", "July"],
            datasets: [
                    label: "My First dataset",
                    backgroundColor: [
                        'rgba(255, 99, 132, 0.2)',
                        'rgba(54, 162, 235, 0.2)',
                        'rgba(255, 206, 86, 0.2)',
                        'rgba(75, 192, 192, 0.2)',
                        'rgba(153, 102, 255, 0.2)',
                        'rgba(255, 159, 64, 0.2)'
                    borderColor: [
                        'rgba(54, 162, 235, 1)',
                        'rgba(255, 206, 86, 1)',
                        'rgba(75, 192, 192, 1)',
                        'rgba(153, 102, 255, 1)',
                        'rgba(255, 159, 64, 1)'
                    borderWidth: 1,
                    data: [65, 59, 80, 81, 56, 55, 40],

        let options = {
            scales: {
                xAxes: [{
                    stacked: true
                yAxes: [{
                    stacked: true

        let myBarChart = new Chart(chartcanvas, {
            type: 'bar',
            data: data,
            options: options

Step 7, constructor method to call
Let's add a constructor method in our component to call this createChart method
    constructor(opt_config) {


Step 8, compile, deploy, test, debug and play around
If you have done it correctly, you would have a chart sheet like mine.
You can see how easy we can involve a 3rd part js lib in our project to improve our UX. Don't be limited with only 1 js lib, play around and test! The only limitation is your imagination. 
Hope you enjoy it.
Attachment is a compiled jar for you to deploy and test.
For full workable source code please visit sample.liferayee.com
Special Thanks to our Principal UI Engineer Chema Balsas, with his help I can get this tutorial done. Thanks Chema for many precious recommendations and help.

Tutorial of creating soy portlet

Technical Blogs 10 de enero de 2017 Por Neil Jin Staff

In Liferay DXP it supports soy template with portlet. Through soy template we can take advantage of Google Closure to build rich web application.
In next sections I am going to lead you in to a journey of Soy portlet.
Tools we use: 
    Liferay IDE 3.1. -- Super awsome official tool to help us to create project and run gradle task.
    Atom Editor. -- Professional UI development tool.(Or you can use whatever you are familiar tool)
    Git Bash -- Reversion control and windows - Linus EOL/EOF convert.
    Familiar with Liferay OSGi portlet module development. If you are a stranger of this, please check this blog to learn how to create a mvc portlet.
    Familiar with NodeJS development.
    Familiar with Git
Step 1 Portlet Creation with soy frame work
Let's create a mvc portlet module. In my case I call my project sample-soy.
The difference between soy portlet and mvc portlet class is soy portlet class extends SoyPortlet com.liferay.portal.portlet.bridge.soy.SoyPortlet which is a child class of MVCPortlet.
Modify the super class to be SoyPortlet
So you need to add the dependency to build.gradle correspondingly.
compileOnly group: "com.liferay", name: "com.liferay.portal.portlet.bridge.soy", version: "3.0.0"
Make your Soy portlet component annotation like this:
    immediate = true,
    property = {
            "javax.portlet.display-name=Soy Sample Portlet",
    service = Portlet.class
According to this annotation, we will have SoySample.soy as our defaule view. We will have SoySample.scss for our css style
Step 2 node config file
In the build tool, we utilize NodeJS to build and compile UI elements soy, metal, other js, css, and etc.
Create a package.json in project root folder with content:
    "dependencies": {
        "metal-component": "^2.4.5",
        "metal-soy": "^2.4.5"
    "devDependencies": {
        "liferay-module-config-generator": "^1.1.10",
        "metal-cli": "^1.3.1"
    "name": "soy-sample",
    "version": "1.0.0"
It's a good practice that make the version be consistent with version in bnd.bnd
Step 3 OSGi config
Add the following code to the bnd.bnd so that bnd tool knows package.json need to be in the jar file, and the OSGi bundle need soy capability.
Include-Resource: package.json
Require-Capability: soy;filter:="(type=metal)"
Step 4 Use Render method to define variables in soy template.
Add/Modify your render method in portlet class.
    public void render(
            RenderRequest renderRequest, RenderResponse renderResponse)
        throws IOException, PortletException {

        template.put("msg", "Good Job!");

        super.render(renderRequest, renderResponse);

We will have a parameter in our soy template called msg with the a string value "Good Job!"
Step 5 SoySample template
Create a file called SoySample.soy in this path src/main/resources/META-INF/resources/resources
With the following code:
{namespace SoySample}

 * Show portlet message in the view.
 * @param id
 * @param msg
{template .render}
     <div id="{$id}">
        <div class="form-group">
            <div class="input-group">
                <span class="input-group-addon" id="inputGroupAddon01">Message:</span>
                <input aria-describedby="inputGroupAddon01" class="form-control" value="{$msg}" type="text">

        <div class="form-group">
            <div class="input-group">
                <input aria-describedby="inputGroupAddon02" class="form-control" placeholder="Recipient's username, ex. neil.jin ;)" type="text">
                <span class="input-group-addon" id="inputGroupAddon02">@liferay.com</span>

        <div class="form-group">
            <div class="input-group">
                <span class="input-group-addon">$</span>
                <input aria-label="Amount" class="form-control" type="text">
                <span class="input-group-addon">.00</span>

        <div class="form-group">
            <button class="btn btn-default" type="button">Donate</button>
Closure require the template's declaration in java-doc like comment. So the comment about the {templlate .render} is required. All the param has to be used in the template.
And {template .render} has to be the beginning or the line without and space characters.
Everytime you change the soy file, it's better to run build soy gradle task to generate soy.js
In my template I utilized Lexicon to build my UX.
In the same folder, create a file called SoySample.scss, just leave it blank.
Run buildSoy gradle command.
Step 6 test and check.
Now you can build your module and deploy the jar to your Liferay server and test.
If everything is correct you are suppose to see this view from your portlet.
When you build, the task tries to download necessary node_modules, it might take a while(about 100m). 
Sometimes it get stuck on npmInstall, you can stop the process and build again, it will work.
Step 7 javascript actions
I am going to add javascript to my soy template.
Create a file called SoySample.es.js with the same folder of SoySample.soy with the following code:
import Component from 'metal-component/src/Component';
import core from 'metal/src/core';
import dom from 'metal-dom/src/dom';
import Soy from 'metal-soy/src/Soy';
import templates from './SoySample.soy';

class SoySample extends Component {


//Register component
Soy.register(SoySample, templates);

export default SoySample;
Our js framework is done.
Step 8 add javascript method
We are going to create a donate method to send out a message to our js concole.
Add the following method to the class SoySample
     * donate to neil
     * @param  {MouseEvent} event
    donate(event) {
        console.log("donate successful");
Step 9, use method
We will make this method be executed when user click donate button, so add data-onclick="donate" attribute to donate button.
It will look like this:
<button class="btn btn-default" data-onclick="donate" type="button">Donate</button>
After you have done everything, you can start build and deploy your project. When you click on donate button, your browser console should show  a message "donate successful"
Because of an issue of soy compiler, the compiler can't identify the end of line and end of file character.
We have to manually convert CRLF to LF. On windows you may meet js error "TypeError: fn is not a function" in the browser.
To solve this we are going to utilize git.
You can create a file in your project's git repo named  .gitattributes, with the following content:
*.pom text eol=lf
*.soy text eol=lf
ivy.xml text eol=lf
After you have added your work to the index and commit, the CRLF should be automatically converted to LF.
You can also take advantage of IDE 3.1 with AnyEdit eclipse plugin. (Thanks @Greg for the info)
You can also utilize Atom Editor with line-ending-selector
Hope you enjoy.
The attachment is a compiled soy portlet jar, you can downlad and deploy to your portal for a test.

繁体译文 OSGi Module依赖

Company Blogs 7 de enero de 2017 Por Neil Jin Staff

原文作者:David H Nebinger




假設在Liferay DXP平臺上開發module的時候,遇見了需要運行環境(runtime)的依賴的時候,該怎麼辦?




方法 1 - 放在全域(global)目錄下


這種方法最簡單也最粗暴。所有在全域類載入器(比如tomcat的lib和lib/ext)中的類可以被所有類訪問,包括Liferay OSGi容器。



方法 2 - 讓OSGi處理



當你確認OSGi中依賴可以被使用的時候,這個方法就可以使用。因為這個方法是利用portal中已有的依賴,或者你之前已經部署到OSGi容器中的依賴(有些jar可能已經包含了OSGi bundle的資訊,可以直接部署到容器中)。



runtime group: 'com.iowagie', name: 'itext', version: '1.4.8'

如果iText(和其依賴)已經成功的部署到OSGi容器中,通過運行環境依賴聲明就可以在你自己的module中使用了。如果iText不可用,你的module就不會啟動,並且會報錯 -- 依賴無法滿足。


方法 3 - 製作成一個超級胖子Module


像那些巨型jar一樣,巨型module會擁有所有依賴的類直接暴露在module jar中。




Include-Resource: @itext-1.4.8.jar


要注意的是,也需要引入依賴所以依賴的jar。例如,iText2.08依賴於BouncyCasle mail和prov,所以這些依賴也需要添加:

Include-Resource: @itext-2.0.8.jar,@bcmail-138.jar,@bcprov-138.jar


如果使用zip工具打開module jar包的話,會看見所有依賴的jar會被解壓,class檔會被直接放在module jar包中。


方法 4 - 在Module中引入整個jar


最後的方法是在module中引入jar,和巨型module不一樣,這個方法將整個jar包含到module jar中



首先需要定義Bundle-ClassPath屬性來引入module jar和其餘的依賴jar。在下面的例子中,我指定iText jar會包含在module jar中:












全域方法適用於只需要一種版本的jar包並且需要用到大量的依賴的情況。例如,專案中有20個不同的module全部依賴於iText 1.4.8,這樣,全域方法就是最好的選擇。

第二種方法只用於依賴jar是OSGi bundle的情況。在這種情況下,你可以使用不同的版本,並且不用擔心去編輯bnd檔。











例如在我使用google-gwt servlet jar的時候。我需要導入這個包:






Import-Package: \


译文:OSGi Module依赖

Technical Blogs 7 de enero de 2017 Por Neil Jin Staff

原文作者:David H Nebinger




假设在Liferay DXP平台上开发module的时候,遇见了需要运行环境(runtime)的依赖的时候,该怎么办?




方法 1 - 放在全局目录下

这种方法最简单也最粗暴。所有在全局类加载器(比如tomcat的lib和lib/ext)中的类可以被所有类访问,包括Liferay OSGi容器。



方法 2 - 让OSGi处理



当你确认OSGi中依赖可以被使用的时候,这个方法就可以使用。因为这个方法是利用portal中已有的依赖,或者你之前已经部署到OSGi容器中的依赖(有些jar可能已经包含了OSGi bundle的信息,可以直接部署到容器中)。




runtime group: 'com.iowagie', name: 'itext', version: '1.4.8'


如果iText(和其依赖)已经成功的部署到OSGi容器中,通过运行环境依赖声明就可以在你自己的module中使用了。如果iText不可用,你的module就不会启动,并且会报错 -- 依赖无法满足。


方法 3 - 制作成一个超级胖子Module


像那些巨型jar一样,巨型module会拥有所有依赖的类直接暴露在module jar中。




Include-Resource: @itext-1.4.8.jar


要注意的是,也需要引入依赖所以依赖的jar。例如,iText2.08依赖于BouncyCasle mail和prov,所以这些依赖也需要添加:

Include-Resource: @itext-2.0.8.jar,@bcmail-138.jar,@bcprov-138.jar


如果使用zip工具打开module jar包的话,会看见所有依赖的jar会被解压,class文件会被直接放在module jar包中。


方法 4 - 在Module中引入整个jar


最后的方法是在module中引入jar,和巨型module不一样,这个方法将整个jar包含到module jar中



首先需要定义Bundle-ClassPath属性来引入module jar和其余的依赖jar。在下面的例子中,我指定iText jar会包含在module jar中:












全局方法适用于只需要一种版本的jar包并且需要用到大量的依赖的情况。例如,项目中有20个不同的module全部依赖于iText 1.4.8,这样,全局方法就是最好的选择。

第二种方法只用于依赖jar是OSGi bundle的情况。在这种情况下,你可以使用不同的版本,并且不用担心去编辑bnd文件。









例如在我使用google-gwt servlet jar的时候。我需要导入这个包:


Import-Package: \



Company Blogs 16 de diciembre de 2016 Por Neil Jin Staff

本文在于讨论如何在Liferay DXP(Liferay 7)中使用公共参数(public render parameter)支持IPC。





     声明之后,就可以像使用普通render parameter一样使用这个参数了。


    <portlet:renderURL var="senderURL">

        <portlet:param name="message" value="Prove you can receive me" />

Making portlet support public render parameter in DXP

Company Blogs 13 de diciembre de 2016 Por Neil Jin Staff

This is the way how to make IPC through public render parameter in Liferay DXP(Lfieray 7).

Key point:

     You need to add a supported render parameter in all portlet's Component Annotation.


     The "message" is the public render parameter name.

    After you have done this you can use this parameter as a regular render parameter in the portlet. 

    In your link you can add the parameter like this:

    <portlet:renderURL var="senderURL">

        <portlet:param name="message" value="Prove you can receive me" />
    You can receive the value like this:
Please check the attachment for a functional project.
After the module is installed you can find the portlets under Sample category.


Company Blogs 3 de junio de 2016 Por Neil Jin Staff

业内知名门户(Portal)软件商Liferay已经将新产品重命名为DXP - 数字体验平台,剑指数字体验市场。


这种转变其实并不是Liferay特有的,它更像是一种自然演化。“传统portal中的功能,已无法满足当今客户的功能需求,在Liferay平台上我们需要持续开发和整合这些需求功能。所以基于这些转变,我们需要重定义我们在市场中的位置。”Liferay首席执行官Bryan Cheung如是说。





什么是Liferay DXP


Liferay DXP是由一些不同的组件构成。核心部分是portal平台本身。其次是整合的内置后台系统包括CRM、ERP、技术支持和其他系统。整合平台使用的是微服务架构,使其定制化与整合客户需求功能更加方便。


Chueng说,对于企业来说,深度数字化意味着经营方式的转变。Liferay DXP可以深入到业务流程处理,以助力客户系统设计与模块整合,使其可以重新配置和部署必要的商业服务。




基于Liferay DXP平台,可以轻松的创建网页站点、登录页面、移动站点、移动应用和表单等。亦可创建定向受众体验和为市场客户支持和服务定制个性化体验。


Liferay DPX 平台还有两个组件年内问世,营销活动管理(Campaign Management)和单一客户视图(Single View of Customer, SVOC)。SVOC组件可以用来定制化体验,它能够将零散的信息整理重构,然后集中显示。营销活动管理组件使市场营销人员通过一些活动,如促销活动、客户支持或服务,以提高目标客户的参与度。










Liferay DXP采用订阅服务以及混合式结局方案(例如,云服务中的营销活动管理和SVOC)的销售方式。在未来Liferay也会寻机提供更多的管理服务。










Liferay DXP数字体验平台,荣耀绽放:端对端的客户体验

Company Blogs 24 de mayo de 2016 Por Neil Jin Staff

知名软件厂商Liferay,已经宣布Lifeay Digital Experience Platform(DXP)的面世。





Liferay的产品副总裁Edwin Chueng,阐释了这一观点:
“当今的商务市场,早就超越了仅将目光放在用户的体验这一层面。Liferay DXP通过革命性的创新体验,帮助企业博得客户信心,赢得竞标,开创更多销售机遇。在深度整合的商务流程系统中,企业可以全面掌握客户偏好和历史事件,及时为客户提供优质资源及建议。”

Liferay DXP:惊鸿一瞥

Liferay DXP平台上,销售、市场,客户支持与服务团队可以真正的为创造优质的客户体验而协作。Liferay可以汇集不同的系统中的信息,以更全面的方式在平台上展现出来借以优化客户互动体验,整合企业运维流程。

Liferay DXP也可以促进合作伙伴和员工与客户间的交流,借以缔造更佳的企业印象。
  • 弹性架构 - 以变应变:通过高复用性的微服务与高整合性的应用,Liferay可以快速实现数字策略以适应不断更新的尖端技术。

  • 体验管理:基于精准的大数据信息,从促销活动一直到客户服务处理,每一个直接面对客户的商务环节都可以量身打造。

  • 单一客户视图(SVC):基于企业中不同的数据采集点,例如:客户情绪、兴趣点或重要对话,Liferay DXP将所有信息糅合一体,为企业提供单一的客户资料视图。

  • 定向数据:通过使客户访问视频、推广链接、社区活动与社交资料来丰富客户体验。

  • 与新兴技术对接:DXP设计初衷就是为了支持新兴的移动前段框架。这些框架被众多移动前段开发者钟爱和广泛应用。



  1. 真正涵盖(促成)客户旅程:秉承Liferay portal的一贯作风,Liferay DXP更加的适应整个的客户生命周期。生命周期通常是指客户从观望(网站匿名访客)到转变成客户(注册)再到拥护(在客户门户与客户服务系统中)的过程。其他关注CMS产品通常只关注在客户的观望周期。基于Liferay以往的丰富经验,Lifeay在银行业的地位独一无二。

  2. 岂止于客户,广泛的目标受众:除了客户之外,Liferay DXP也是为企业合作伙伴和员工设计的。设计的初衷是为了确保Liferay DXP能够胜任于企业内部协作和效率优化。

  3. 完善的商务平台:Liferay DXP缔造了丰富的协作功能,基于整合后台系统,将更多独立业务部门的信息整理有效利用。

更多信息,请访问Liferay DXP官网
原文作者:Kaya Ismail

使用Liferay IDE 3.0创建支持Service Builder的portlet module

Technical Blogs 4 de mayo de 2016 Por Neil Jin Staff

Liferay IDE 下载页面: https://web.liferay.com/downloads/liferay-projects/liferay-ide

Liferay IDE 3针对Liferay 7.0(Liferay DXP)做了很多的改变。其中一项就是在Liferay Blade,Eclipse Gradle和BND tool的基础上完成了创建OSGi module portlle的向导,通过向导就可以简单的创建出portlet module了。在我的客户和培训学院中,所有的技术人员在开始最关心的问题都是在Liferay 7中,怎么创建一个使用Service Builder的MVC portlet。在Liferay IDE 3中,实现起来其实非常简单。


首先需要创建一个Liferay Workspace项目.

Liferay Workspace更像是一个Liferay项目的容器,而不是一个类似于eclipse workspace的工作空间。在Liferay Workspace中可以开发和管理Liferay插件。

接下来点击File -> New -> Liferay Module Project,然后选择servicebuilder为项目模板。

在Liferay Workspace视图模式下

我们可以看到在新建service builder的项目下有两个gradle项目。API项目包括了各种接口和服务定义。在6.2的项目中这些文件通常是在service文件夹下。Service项目中包含了服务的具体实现源码和服务配置。在6.2中是在src文件夹下面。

在service项目中,包含了service.xml。可以根据需求改变。接下来就可以在Gradle Task窗口中,运行service项目中的buildservice任务了,这样就可以生成所需的服务了。



我们可以创建一个mvc portlet类型的Liferay Module Project。然后修改build.gradle文件。按照我们文章中的示例,我们可以添加下列代码来声明依赖。

compile project(":modules:service-builder-project:service-builder-project-api")

这样在mvc portlet项目中就可以引用服务了。如果IDE的编辑器依然报告有依赖的错误,可以右键点击项目,然后gradle -> refreash gradle project,这样编辑器就会重新计算依赖了。

在部署项目的时候,需要部署三个jar,分别是api,service和mvc portlet。

Creating service builder mvc portlet in Liferay 7 with Liferay IDE 3.

Company Blogs 26 de abril de 2016 Por Neil Jin Staff

Liferay IDE Download Page: https://web.liferay.com/downloads/liferay-projects/liferay-ide

Liferay IDE 3 has massive change regarding to Liferay 7.0(Liferay DXP). One of the change is it streamlines the creation of OSGi module portlet by utilizing Liferay Blade tool, gradle elipse plugin and bnd tool.

One of the most concerned question by the Liferay developers is how we develop Service Builder with MVC portlet in new Liferay 7 development pattern.

With Liferay IDE 3 it's quite easy!

First we need to create a Liferay Space project.

A Liferay Workspace is a project container for Liferay porjects, within the Liferay workspace, we can develop and manage Liferay plugins.

And next we need to create a new Module Project by clicking File -> New -> Liferay Module Project, and choose the Project Template Name servicebuilder.

In the Liferay Workspace perspective 

We can see there are 2 gradle project under the service builder project. API project containse the service definition and interfaces, as the service path folder in 6.2. Service project contains the actual implementation of the service, as the service code in src folder in 6.2.

It contains a default service.xml. You can change the xml according to your need. After that, you can run buildservice task of service-builder-project-service in Gradle Task Window. 

After running buildService then run build task, the service will be ready to use.


How can we reference this service module in a mvc portlet module?

We use gradle to manage dependency.

We can create a mvc portlet Liferay Module Project. And modify build.gradle file. In our case we can add the following code to the dependency declaration.

compile project(":modules:service-builder-project:service-builder-project-api")

The service will be available to the mvc portlet. If the editor still complain about the dependency you can right click to the project -> gradle -> refreash gradle project to refreash the project.

When you deploy the project, remember to deploy 3 jars for api, service and mvc portlet.


You can download an example project here.

Modify existing datepicker that has been existed in the page.

Technical Blogs 5 de noviembre de 2015 Por Neil Jin Staff

It's very convient to use <liferay-ui:input-date> tag to create a date field. But this just provide very basic use of aui date picker. And attributes doesn't cover all options of date picker. This makes me have to create my own aui date picker through javascript. But using <liferay-ui:input-date> can save me a lot of time. So I am going to introduce a practical way to apply advanced options to the date picker that has been generated by input-date tag. Actually you can use the same method to modify any date picker.

The component name of the date picker that is generated by tag is NAMESPACE+FIELD_NAME+DatePicker

For example :

Suppose we are in a portlet under name space "_PersonalInfo_WAR_PersonalInfoportlet_"

<liferay-ui:input-date name="birthday" yearParam="birthYear" yearValue="1984" monthParam="birthMonth" monthValue="01 dayParam="birthDate" dayValue="17" />

This tag generate a date picker called _PersonalInfo_WAR_PersonalInfoportlet_birthdayDatePicker

You can easiliy get the date picker's javascript object by this method:

var datePickerName = '_PersonalInfo_WAR_PersonalInfoportlet_birthdayDatePicker';

var datePicker = Liferay.component(datePickerName);

for example we want to limit the user's input range to be in last 100 days until today, you can modify the date picker's configuration

var today = new A.Date();
var calendar = datePicker.getCalendar();

if (calendar) {
    calendar.setAttrs({minimumDate:A.Date.addDays(today, -100), maximumDate:today);


Same idea if you want to change Date picker's default date format, you can change the mask. Basically if you get the date picker you can do whatever you want to do with it.



A perspective 3D Liferay desktop picture.

Company Blogs 5 de marzo de 2013 Por Neil Jin Staff

This one was designed by using minecraft screenshot! When I see this, I am always feel this grand brandy! Simple, strong, stable and enough space for imagination. 

Hope you will like it. The full picture size is 1920x1080.

If anyone wants some other size, please leave a comment, I would like to make it.



Making a remote javascript library loaded dynamically by AUI and be an AUI plugin

Company Blogs 27 de febrero de 2013 Por Neil Jin Staff

    In the real world project, there are times when a team develops some feature like google map, twitter, facebook that need remote javascript library between several portlets but for the same portal. There are many similiar features that needs to resuse many codes. We want out code to be consistence and easily to be managed and more importantly we don't want to waste time on mutiple people working on the same feature that ultimate increase the cost of a project and once there's a bug or requirement modification(people love to do that), that the whole team need to change their own code, which is really frustrating for the whole team.

    TWO ways we think of to solve this (actually there are more solutions):

    1. Making a shared js and code standard that everybody can read it and copy it to each own code.  This method still can not guarantee a developer that use the right code without updating their js file to the up-to-date.

    2. Making a global portlet as a dependency of the portlets. This portlet will mainly be set layout.static.portlets.all=<portletID>  in portal.properties and add a <liferay-util:html-top> tag to view.jsp page that loads the remote javascript libiary , and that all pages in the portal loaded the remote javascript library.

BUT, when using code like <script src="https://maps.googleapis.com/maps/api/js?sensor=false&amp;libraries=places" type="text/javascript"></script>, in some network environment like some companies or countries, facebook, google does not work very well, so the whole portal pages became extreamly slow to wait the until loading timeout. In my test sample, The longest time to wait a loading of facebook script on a page that does not use any feature was 11.98s and 1m 24s for loading google map script under a total refreshing page (shift + refresh).

    Why do we have to do this, load a unused script in a page in such a poor network environment? This really ruins the work of our core engineer. Core Engineers took really huge effort on decreasing some ms of loading time, but we just can simply increase seconds of loading time by adding 2 scripts. Fanstastic devil

    We don't have to do this. We can easily get rid of the features at the requirement analysis period.

    Once the features were made sure to use, we would choose a better way to handel this. At least, not let every page to load the script that is not useful.

    So We can still make a global portlet as a dependency, and load the script that we need. BUT, this script is not from the remote site directilly. This will be an aui plugin that handels the connection when we need. So that solves the problem.

For example to load twitter's script, the summary script is like this

AUI().add('aui-twitter', function(A) {

 var Lang = A.Lang,

 TWITTER = 'twitter',


 /*this version only support share button yet*/


 '<a href="https://twitter.com/share" class="twitter-share-button">Tweet</a>'


 var Twitter = A.Component.create({


   ARRTS: {


   prototype: {

     initializer: function() {

       var instance = this;



    _loadScript: function() {




          onSuccess: function() {

           //alert("load twitter script success");





    createTwitterButton: function(options) {

      var instance = this;

      var button = A.Node.create(TWITTER_BUTTON);

      var dataLang = (options.dataLang != null ? options.dataLang : instance.LANG.ENGLISH);

      var dataCount = (options.dataCount != null ? options.dataCount : instance.COUNT_ALIGN.HORIZONTAL);

     var dataURL = (options.dataURL != null ? options.dataURL : '');

     if(options.largeButton) {

       button.setAttribute('data-size', 'large');


      button.setAttribute('data-lang', dataLang);

      button.setAttribute('data-count', dataCount);

      button.setAttribute('data-url', dataURL);


      return button;


    //can add more features


      HORIZONTAL: 'horizontal',

      VERTICAL: 'vertical'


    LANG: {

      ARABIC: 'ar',

      CHINESE_SIMPLIFIED: 'zh-cn',


      DUTCH: 'nl',

      ENGLISH: 'en', //can add other languages




A.Twitter = Twitter;

}, '@VERSION@', {skinnable:false, requires:['aui-base', 'get']});
Once the script is loaded, if an aui object is not generated, the script won't be loaded.
In the jsp page can simply add code like:
<div id="<portlet:namespace />twitter-share"></div>

<aui:script use="aui-base, aui-twitter">

  AUI().use(''aui-twitter', function(A) {

    var tObj = new A.Twitter();

    var btnOpt = {

      contentBox: "#<portlet:namespace />twitter-share",
      dataLang: tObj.LANG.ENGLISH,
      dataCount: tObj.COUNT_ALIGN.VERTICAL,
      largeButton: true




If AUI can have an official plugin to support those famous libraries like google and facebook and etc it would be nicer. :)

A vertial 3D Liferay desktop

Company Blogs 23 de diciembre de 2011 Por Neil Jin Staff

Recently I tried to draw a 3D Liferay Logo with AI at home. Haven't been drawing since graduate. Maybe it just an okey job to draw this, but it can keep me thinking new ideas, create new things.

'Cause through a picture we can imagine a lot of stories. Like in this picture I can think of a boy encounters a girl in Liferay. And draw them out. And use that as my Valentine's Day's desktop. I mean it could be many ways to keep imagination.

Mostrando 20 resultados.
Elementos por página 20
de 1