Bloggers recientes

Matthew Draper

Staff
13 Mensajes
28 de junio de 2017

David H Nebinger

58 Mensajes
27 de junio de 2017

Jamie Sammons

Staff
9 Mensajes
19 de junio de 2017

gulnaaz Shaik

3 Mensajes
16 de junio de 2017

Bryan Cheung

Staff
36 Mensajes
15 de junio de 2017

Milen Dyankov

Staff
6 Mensajes
15 de junio de 2017

Zeno Rocha

Staff
16 Mensajes
15 de junio de 2017

Yasuyuki Takeo

Staff
1 Mensajes
12 de junio de 2017

Javeed Chida

18 Mensajes
12 de junio de 2017

Cody Hoag

Staff
9 Mensajes
30 de mayo de 2017

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) {

  super(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
   };

   AUI.$.ajax(requestConfig)
    .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 + ")";
   colors.push(color)
  }

  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. 定義圖片長寬比

參考Lexicon的文檔,可以通過css為圖片播放定義一個固定的長寬比。在我這裡,長寬比可以通過structure設置,針對移動設備回應式的比例為了簡單,我就直接寫在範本中了。

<style>

 #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%;

     }

 }

</style>

 

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

可以直接參考Bootstrap的carousel的實現方式寫HTML,CSS,並且通過freemarker迴圈寫入圖片。在圖片所在的div中定義class為aspect-ratio和aspect-ratio-custom。

 

<#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>

    </#list>

   </ol>


   <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()}">

      </div>

     </div>

    </#list>

   </div>


   <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>


   <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>

   </a>

  </div>

 </section>

</#if>

 

5. 上傳圖片,添加web content

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

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

也可以使用Liferay內置的圖片編輯器添加一些濾鏡效果。

6. 預覽並發佈

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

你可以嘗試改變流覽器尺寸,試試外掛程式是否保持長寬比。

也可以使用Simulation工具來預覽我們設置的響應式尺寸是否正常(67%)。

這樣一個回應式的圖片滾動外掛程式就完成啦。

希望你能喜歡。

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.
Reusable.
 
 
Analysis:
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.
<style>
 #carousel-<@portlet.namespace /> .aspect-ratio-custom {
    padding-bottom: ${aspectRatio.getData()};
 }
@media (max-width: 799px) {
    #carousel-<@portlet.namespace /> .aspect-ratio-custom {
        padding-bottom: 67%;
    }
 }
</style>
 
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>

                </#list>

            </ol>


            <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()}">

                        </div>

                    </div>

                </#list>

            </div>


            <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>


            <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>

            </a>

        </div>

    </section>

</#if>
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

 
原文作者: DAVID H NEBINGER
 
 
当你查看Liferay 7 CE/Liferay DXP源码时,你会看到大量不同的注解。当你除此看到这些注解的时候,可能会感觉到有些无所适从。所以我想写一些引用指导来解释这些注解是什么,并且在你自己的OSGi代码中什么时候使用比较合适。
 
我们开始吧...
 
@Component
 
在OSGi世界中,这种注解用于定义“声明式服务(Declarative Service)”。声明式服务是OSGi中用于动态生成服务。它使容器中的大量的组件(component)间可以互相关联调用。
 
在Component注解中有三个组要属性:
  • immediate - 通常设置为true。用于使组件在部署后直接启动(start),不用等待其他引用或者使用懒加载(lazy startup)。
  • properties - 用设置一系列OSGi属性绑定到组件。这些属性对于当前组件是可见的,但是更重要的是它们对于其他组件也是可见的。这些组件可以帮助配置组件,也可以用于支持组件过滤。
  • service - 定义组件实现的服务。有时这个属性是可选的,但是通常是必要的,因为这样可以使组件要实现服务更明确。service的值通常是一个接口,但是也可以使用一个实体类。
什么时候需要使用@Component?只要需要在OSGi容器中使用要发布一个组件的时候,就可以使用。不是所有的类都需要是组件。只是当你需要将一个插件植入到Liferay环境中的时候才会声明一个组件。(例如,添加一个导航项、定义一个MVC command handler、覆写一个Liferay组件或者为自己的扩展框架写一个插件)。
 
@Reference
 
这个注解是和@Component相对应的。@Reference用于获取OSGi容器中其他的组件并且注入到你自己的组件中。需要注意的是,因为OSGi在做注入的工作,你只能注入OSGi组件类。@Reference注解会被非组件类忽略,并且也会组件的被子类忽略。对于所有的引用注入,都必须在@Component类中声明。
当你定义一个基类时注入了一些其他的服务,并且这个基类没有使用@Component注解(因为定义时还没有完成),@Reference注解会被非组件类忽略,这样的情况下,所有的注入都是无效的。最终,所有的setter方法和@Reference注解都需要复制到实习子类中。这样显得相当冗余。需要注意这一点。
可能@Reference最常见的属性是“unbind”属性了。在setter方法中,经常会看见@Reference(unbine="-")这样的写法。当你为setter方法使用@Reference时,OSGi使用使用这个setter方法作为组件的setter方法。unbind属性指定了当组件释放是,不需要调用任何其他的方法。也就是说,不需要为组件释放做任何的处理。多数情况下,这么做没有问题,服务器启动,OSGi绑定组件,并且使用组件一直到系统关闭。
另外一个属性是“target”,Target用于支持过滤。还记得@Component中的属性吧?通过使用target属性,可以通过特定查询语法指定一类组件。
例如:
@Reference( target = "(javax.portlet.name=" + NotificationsPortletKeys.NOTIFICATIONS + ")", unbind = "-" ) 

protected void setPanelApp(PanelApp panelApp) { 
    _panelApp = panelApp; 
}
这段代码希望获取PanelApp组件的实例,但是它却指定了Notification portlet中的PanelApp组件。所以其他portlet中的PanelApp不符合条件,只有Notification portlet中的PanelApp才符合条件。
我们这里讨论的属性有时候会很重要,所以我会详细讨论这些属性。
Cardinality
第一个是cardinality属性,默认值是ReferenceCardinality.MANDITORY,可选值有:OPTIONAL、MULTIPLE和AT_LEAST_ONE。这些值的意义是:
  • MANDITORY - 引用必须在组件启动前可用并且注入。
  • OPTIONAL - 引用在组件启动阶段时不是必要的,在不指定组件的时候也是可以正常工作的。
  • MULTIPLE - 有多种不同的资源可以引用,组件会使用全部的方法。和OPTIONAL相似,在组件启动阶段,引用不是不要的。
  • AT_LEAST_ONE - 多种资源可以满足引用,组件会全部使用。但是在组件启动时,至少有一个资源是可用的。
Multiple选项可以让你通过引用,使用多种符合条件的方法调用。这种方式,只有在使用setter方法中@Reference注解,并且setter方法是向列表或者数组中添加内容的情况下才合理。或者可以使用ServiceTracker替换这种写法,这样你就不用亲自手动去管理列表了。
 
Opional可以使组件在启动时不必须指定某个引用。在有循环引用问题时,这种方法就会帮助你解决问题。例如:A引用B,B引用C,C引用A。如果这三个组件都设置引用为REQUIRED,所有组件都不会启动,因为它们都在等待其他组件满足条件(只有已经启动的组件才能指派给@Reference)。为引用使用optional选项就打破了这种循环,组件就可以正常启动,并且引用问题也解决了。
 
Policy
下一个重要的@Reference属性是policy。Policy的值可以是ReferencePolicy.STATIC (默认)或者ReferencePolicy.DYNAMIC。他们的意义是:
  • STATIC - 只有在已经指派了引用之后,组件才能启动。在启动之后,当出现其他可用引用时,组件也不会理会。
  • DYNAMIC - 无论有没有可用的引用,组件都会启动。并且在有新的可用引用的时候,组件会使用新的引用。
引用策略控制着在组件启动后,当新的引用资源可用的时候的使用策略。总体来说,使用STATIC,组件会忽略新的引用,使用DYNAMIC,当出现新的可用引用时,组件会改变引用。
 
PolicyOption
和policy一同使用的另外一个属性是policyOption。这个属性的值可以是ReferencePolicyOption.RELUCTANT (默认) 或者 ReferencePolicyOption.GREEDY。他们的意思是:
  • RELUCTANT - 对于单一(single)的引用,新的可用引用会被忽略。对于多项(multiple)引用,新的可用引用出现的时候会被绑定。
  • GREEDY - 只要新的可用引用出现的时候,组件就会绑定这些引用。.
 
这些选项组合起来有很多种方式。
首先是默认的,ReferenceCardinality.MANDITORY + ReferencePolicy.STATIC + ReferencePolicyOption.RELUCTANT。
这样组合是组件一定要一个引用可用才能启动,并且忽略新的可用引用。这样的默认组和确保了组件的稳定性。
另外一种组合是ReferenceCardinality.OPTIONAL/MULTIPLE + ReferencePolicy.DYNAMIC + ReferencePolicyOption.GREEDY。
在这样的配置中,组件在缺少服务引用的时候也会正常使用,但是组件在使用过程中允许新的引用和引用变更,在新的引用可用时,组件会主动的绑定引用服务。
也会有其他的组合,但是在设置的时候需要了解这样的设置对组件的影响。毕竟在你声明引用的时候你要了解如何是组件工作。你需要考虑到组件在没有引用的时候如何响应,如果服务不可用的时候组,件是否需要停止工作。在你考虑问题的时候不信要考虑理想情况,也要考虑到特殊情况,例如重新部署、卸载、服务差距和组件的容错性。如果这些问题都能解决,那么组件就是比较理想的。
最后,回想一下,什么时候需要使用@Reference注解?就是你需要从OSGi环境中向你的组件注入服务的时候。这些服务可以是你自己的服务,也可以是OSGi容器中其他模块的服务。记住,@Reference只能在OSGi组件中使用,但是你可以使你的类通过@Component注解变成组件。
 
@BeanReference
这个是一个Liferay注解,用于向Spring bean中注入一个Liferay core中的引用。
 
@ServiceReference
这是一个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,用同样的方法修改你自己的模块。
 
@Activate
@Activate注解是对应Spring的InitializingBean接口。它声明了组件在启动是要调用的方法。在Liferay源码中,你会看到它会被三个主要的方法使用:
@Activate 
protected void activate() { 
    ... 
} 

@Activate 
protected void activate(Map<String, Object> properties) {
     ... 
} 

@Activate 
protected void activate(BundleContext bundleContext, Map<String, Object> properties) {
    ... 
} 
也有其他的方法使用这个注解,只要在源码中搜索@Activate就可以找到很多不同的用例了。除了无参数的activate方法,其他的都依赖于OSGi所注入的值。注意properties映射实际是从你的OSGi的Configuration Admin服务取得的。
 
什么时候需要使用@Activate呢?只要你需要在组件启动后、被使用前需要做一些初始化操作的时候就可以使用。例如,我曾经设置Quartz计划任务,验证数据库实体的时候使用过。
 
@Deactivate
@Dactivate注解是和@Ativate注解相反的,它定义了组件在停用是需要调用的方法。
 
@Modified
@Modified注解定义了当组件被改变时需要调用的方法。特别是在标记@Reference了的方法被改变的时候。在Liferay源码中,@Modified注解经常是和标记@Activate的方法绑定在一起的,这样同一个方法就能同时处理启动和改变了。
 
@ProviderType
@ProviderType是BND中使用的,通常是在考虑将head包含的复杂情况时使用。长话短说,@ProviderType是被BND使用,用来定义在实现类中可以指派的OSGi manifest的版本。并且它尝试限制组件版本范围的使用。
 
它的重点是用来确定接口的改变,通过为实现类限制的版本,会强制实现类来根据接口来进行更新。
 
什么时候使用@ProviderType呢?其实,并不一定需要使用。你可以看见,在ServiceBuilder生成的代码里面已经被使用了。我在这里提到这个注解的原因并不是因为你一定要使用它,而是满足你的好奇心。
 
@ImplementationClassName
这个是Liferay中为ServiceBuilder实体接口使用的注解。它定义了在service module中用来实现接口的类。
这个你并以需要使用,只是为了让你知道它是做什么的的。
 
@Transactional
这是另一个ServiceBuilder服务接口使用的注解。它定义了服务方法的事务需求。
这个方法在你开发的时候也用不到。
 
@Indexable
@Indexable用来修饰会使搜索索引更新你的方法,特别是ServiceBuilder中的add、update和delete实体的方法。
你可以为你的实现增、删、改的方法使用@Indexable注解。如果实体关联com.liferay.portal.kernel.search.Indexer 相关的实现方法,那么实体就可以被索引。
 
@SystemEvent
@SystemEvent注解是在ServiceBuilder生成的代码中被可能生成系统事件的方法使用的。系统事件和staging、LAR和导入导出处理等相关。例如,当一个web conteng被删除,这个就会生成一个SystemEvent记录。在staging环境中,当“Publish to Live”原型是,删除SystemEvent确保了相关联的web content在live站点中也被删除。
需要什么时候使用@SystemEvent注解呢?说实话,我也不知道。在我10年的经验中,我从来没有需要生成SystemEvent记录的时候,也从来没更改过Liferay发布和LAR处理。如果任何人有相关经验使用@SystemEvent注解的话,我很愿意侧耳恭听。
 
@Meta
OSGi有一个基于XML的系统用于为Configuration Admin定义配置详细信息。BND项目的通过使用@Meta注解,可以使BND根据配置接口中使用这个注解的方法生成配置文件。
 
重要提示:一定要在bnd.bnd文件中添加下行代码才能使用@Meta注解:
-metatype: *
如果没添加的话,在生成XML配置文件的时候,是不会用到@Meta注解的。
 
@Meta.OCD
这个注解用于“Object Class Definition”方面的配置详细信息。这个注解用于为在接口层为类的定义提供id,名字和语言等详细信息。
什么时候使用这个注解呢?当为组件定义一个Configuration Admin接口时,并且这个接口是在Control Panel -> System Setting有一个配置选项的时候使用。
 
注意@Meta.OCD属性包含了本地化的设置。这样就可以使用resource bundle来翻译配置名称、详细配置信息和@ExtendedObjectClassDefinition分类了。
 
@Meta.AD
这个注解用于“Attribute Definition”,用于定义配置中表单项层面的定义。注解用于为表单项提供ID、名称、说明、默认值和其他详细信息。
 
什么时候使用这个注解?就是在需要为System Setting中的配置提供更多关于配置项目细节的时候使用。
 
@ExtendedObjectClassDefinition
这个是Liferay的注解,用于定义配置的类型(就是在System Setting中上面显示的那些分类)和配置的范围。
 
范围可以是下面的值:
  • SYSTEM - 整个系统中的全局配置,在生个系统中只有一个配置。
  • COMPANY - Company级,每个portal实例中只可以有一个配置。
  • GROUP - Group(站点)级,每个站点中只可以有一个配置。
  • PORTLET_INSTANCE - 这个和portlet实例参数类似,每个portlet实例中都会有一分配置。
什么时候要用到这个注解呢?每次用到@Meta.OCD注解的时候,都需要用到一次@ExtendedObjectClassDefinition注解来定义配置的分类。
 
@OSGiBeanProperties
这是一个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.
 
Requirement:
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">
    <namespace>Banking</namespace>
    <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" />
        </order>

        <!-- Finder methods -->

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

   </entity>
</service-builder>
 
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);
    monthlyTrading.setYear(year);
    monthlyTrading.setMonth(month);
    monthlyTrading.setVolume(volume);

    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.
@JSONWebService
 public MonthlyTrading addMonthlyTrading(int year, int month, int volume) {

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

 @JSONWebService
 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.
 
Knowledge:
    Google Closure Soy template.
    Liferay Soy Portlet.
    ECMA script 2015(ES6)
    Chart.js
 
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 life, 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">
    </canvas>
{/template}
 
 
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>
<script>
    var myChart = new Chart({...})
</script>
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(255,99,132,1)',
                        '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) {
        super(opt_config);

        this.createChart_();

    }
 
 
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.
 
Knowledge:
    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:
 
@Component(
    immediate = true,
    property = {
            "com.liferay.portlet.add-default-resource=true",
            "com.liferay.portlet.application-type=full-page-application",
            "com.liferay.portlet.application-type=widget",
            "com.liferay.portlet.css-class-wrapper=table-wrapper",
            "com.liferay.portlet.display-category=category.sample",
            "com.liferay.portlet.header-portlet-css=/SoySample.css",
            "com.liferay.portlet.layout-cacheable=true",
            "com.liferay.portlet.preferences-owned-by-group=true",
            "com.liferay.portlet.private-request-attributes=false",
            "com.liferay.portlet.private-session-attributes=false",
            "com.liferay.portlet.render-weight=50",
            "com.liferay.portlet.scopeable=true",
            "com.liferay.portlet.use-default-template=true",
            "javax.portlet.display-name=Soy Sample Portlet",
            "javax.portlet.expiration-cache=0",
            "javax.portlet.init-param.copy-request-parameters=true",
            "javax.portlet.init-param.template-path=/",
            "javax.portlet.init-param.view-template=SoySample",
            "javax.portlet.name=soy_sample_portlet",
            "javax.portlet.resource-bundle=content.Language",
            "javax.portlet.security-role-ref=guest,power-user,user",
            "javax.portlet.supports.mime-type=text/html"
    },
    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.
    @Override
    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>
        </div>

        <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>
        </div>

        <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>
        </div>

        <div class="form-group">
            <button class="btn btn-default" type="button">Donate</button>
        </div>
     </div>
{/template}
 
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"
 
 
Troubleshoot:
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

原文地址:https://web.liferay.com/web/user.26526/blog/-/blogs/osgi-module-dependencies

如有問題,歡迎在原文下面討論,也歡迎在這裡留言。

 

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

在這片簡短的文章中,我會介紹幾種方法...

假設你有一個module需要iText(和iText的依賴)作為依賴。這其實和module本身的功能關係不是很大,但是你現在有這個依賴,需要一種方法來使用它。

 

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

 

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

但是全域的jar有全域的問題。不僅所需要的jar需要在全域目錄,所有jar的依賴也需要在全域目錄。並且全域類只有一個版本,其他的消費類無法使用不同的版本。

 

方法 2 - 讓OSGi處理

 

這個是第二簡單的方法,但是可能無法使用。如果你在module中聲明一個運行環境的依賴,並且OSGi中有一個bundle可以滿足依賴的話,module會使用使用這個依賴。

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

例如,假設我們要聲明iText依賴,雖然iText應該不會作為bundle已經部署到了OSGi中,所以如果依賴OSGi容器來使用iText很可能會出錯的。現在僅是用作舉例

使用build.gradle檔來聲明運行環境依賴。這段代碼是用來聲明iText運行環境依賴的


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

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

 

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

 

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

使用Gradle和BND很好實現。

在build.gradle中,你應該像方法2中一樣聲明運行環境依賴。

並且通過在bnd.bnd中包含所需的資源使module成為一個巨型module:

Include-Resource: @itext-1.4.8.jar

在這裡引入依賴jar,一般在gradle下來依賴或者流覽maven庫的時候都可以看到具體版本。

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

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

也需要在build.gradle中添加這些依賴,以便gradle引入這些jar。

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

 

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

 

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

和方法2,3相似的是,也需要在build.gradle中聲明運行環境依賴。

引用jar是在bnd.bnd中完成的。

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

Bundle-ClassPath:\
  .,\
  lib/itext.jar

這裡我們不使用Include-Resource聲明,而是使用-includeresource來-將jar引入到bundle中:

-includeresource:\
  lib/itext.jar=itext-1.4.8.jar

在這裡,我們將itext-1.4.8.jar包含到module中,保存為lib/itext.jar。itext-1.4.8是通過Gradle導入的運行環境依賴。

這種語法也支援萬用字元,可以有效利用build.gradle選擇版本的特性。這個例子是用來包含任何版本的commons-lang:

-includeresource:\
  lib/itext.jar=itext-1.4.8.jar,\
  lib/commons-lang.jar=commons-lang=[0-9]*.jar

如果使用壓縮軟體打開module的jar檔的話,可以看到jar檔會在lib資料夾下。

 

總結

 

實際項目中應該使用哪種方法?和Liferay開發一樣,因需而異。

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

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

第三種和第四種方法是最常用的放法。在上面這些情境下,你的依賴是包含在module中的,OSGi類容器中的類是不會被不同版本的ja“污染”的。並且,module也是不依賴於當前容器環境的,因為module自己已經包含了所有的依賴,運行環境也不需要為module事前準備依賴。

我個人很喜歡使用第四種方法,以為巨型jar在解壓縮類的時候,可能會有路徑上的交錯(例如xml和設定檔)。第四種方法就不會有這樣的問題。

 

希望你喜歡

 

譯者補充:

在使用方法3,4的時候,OSGi容器會要求導入要引用jar的所有依賴,這樣的結果是,需要導入無盡的包,因為依賴還有依賴,依賴還有依賴,子子孫孫無窮匱也。

 

所以,需要通過bnd.bnd來聲明那些包不需要導入。

 

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

 

Bundle-ClassPath:\
    .,\
    lib/gwt-servlet.jar,\
    lib/gson.jar

-includeresource:\
    lib/gwt-servlet.jar=gwt-servlet-2.8.0.jar,\
    lib/gson.jar=gson-2.8.0.jar

 

但是卻需要額外聲明,這些包我不需要import

Import-Package: \
    !com.google.gwt.xhr.client.*,\
    !com.google.gwt.core.*,\
    !com.google.gwt.dev.*,\
    !com.google.gwt.i18n.*,\
    !com.google.gwt.json.*,\
    !com.google.gwt.junit.*,\
    !com.google.gwt.thirdparty.*,\
    !com.google.gwt.uibinder.*,\
    !com.google.gwt.user.*,\
    !com.google.gwt.util.*,\
    !jsinterop.*,!javax.annotation.*,!javax.imageio.*,!javax.lang.model.*,\
    !javax.tools.*,!javax.validation.*,!org.apache.*,!org.objectweb.*,\
    !org.w3c.*,!sun.*,!junit.framework,\
    *

其中!是不導入的意思。最後的*是指導入所需要並且在OSGi容器中能導入的。

译文:OSGi Module依赖

Technical Blogs 7 de enero de 2017 Por Neil Jin Staff

原文作者:David H Nebinger

原文地址:https://web.liferay.com/web/user.26526/blog/-/blogs/osgi-module-dependencies

如有问题,欢迎在原文下面讨论,也欢迎在这里留言。

 

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

在这片简短的文章中,我会介绍几种方法...

假设你有一个module需要iText(和iText的依赖)作为依赖。这其实和module本身的功能关系不是很大,但是你现在有这个依赖,需要一种方法来使用它。

 

方法 1 - 放在全局目录下

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

但是全局的jar有全局的问题。不仅所需要的jar需要在全局目录,所有jar的依赖也需要在全局目录。并且全局类只有一个版本,其他的消费类无法使用不同的版本。

 

方法 2 - 让OSGi处理

 

这个是第二简单的方法,但是可能无法使用。如果你在module中声明一个运行环境的依赖,并且OSGi中有一个bundle可以满足依赖的话,module会使用使用这个依赖。

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

例如,假设我们要声明iText依赖,虽然iText应该不会作为bundle已经部署到了OSGi中,所以如果依赖OSGi容器来使用iText很可能会出错的。现在仅是用作举例

使用build.gradle文件来声明运行环境依赖。这段代码是用来声明iText运行环境依赖的

 

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

 

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

 

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

 

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

使用Gradle和BND很好实现。

在build.gradle中,你应该像方法2中一样声明运行环境依赖。

并且通过在bnd.bnd中包含所需的资源使module成为一个巨型module:

Include-Resource: @itext-1.4.8.jar

在这里引入依赖jar,一般在gradle下来依赖或者浏览maven库的时候都可以看到具体版本。

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

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

也需要在build.gradle中添加这些依赖,以便gradle引入这些jar。

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

 

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

 

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

和方法2,3相似的是,也需要在build.gradle中声明运行环境依赖。

引用jar是在bnd.bnd中完成的。

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

 

Bundle-ClassPath:\
  .,\
  lib/itext.jar

这里我们不使用Include-Resource声明,而是使用-includeresource来-将jar引入到bundle中:

-includeresource:\
  lib/itext.jar=itext-1.4.8.jar

在这里,我们将itext-1.4.8.jar包含到module中,保存为lib/itext.jar。itext-1.4.8是通过Gradle导入的运行环境依赖。

这种语法也支持通配符,可以有效利用build.gradle选择版本的特性。这个例子是用来包含任何版本的commons-lang:

-includeresource:\
  lib/itext.jar=itext-1.4.8.jar,\
  lib/commons-lang.jar=commons-lang=[0-9]*.jar

如果使用压缩软件打开module的jar文件的话,可以看到jar文件会在lib文件夹下。

 

总结

实际项目中应该使用哪种方法?和Liferay开发一样,因需而异。

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

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

第三种和第四种方法是最常用的放法。在上面这些情境下,你的依赖是包含在module中的,OSGi类容器中的类是不会被不同版本的ja“污染”的。并且,module也是不依赖于当前容器环境的,因为module自己已经包含了所有的依赖,运行环境也不需要为module事前准备依赖。

我个人很喜欢使用第四种方法,以为巨型jar在解压缩类的时候,可能会有路径上的交错(例如xml和配置文件)。第四种方法就不会有这样的问题。

 

希望你喜欢

 

译者补充:

在使用方法3,4的时候,OSGi容器会要求导入要引用jar的所有依赖,这样的结果是,需要导入无尽的包,因为依赖还有依赖,依赖还有依赖,子子孙孙无穷匮也。

所以,需要通过bnd.bnd来声明那些包在容器中不需要导入。

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

Bundle-ClassPath:\
    .,\
    lib/gwt-servlet.jar,\
    lib/gson.jar

-includeresource:\
    lib/gwt-servlet.jar=gwt-servlet-2.8.0.jar,\
    lib/gson.jar=gson-2.8.0.jar
 
但是却需要额外声明,这些包我不需要import
Import-Package: \
    !com.google.gwt.xhr.client.*,\
    !com.google.gwt.core.*,\
    !com.google.gwt.dev.*,\
    !com.google.gwt.i18n.*,\
    !com.google.gwt.json.*,\
    !com.google.gwt.junit.*,\
    !com.google.gwt.thirdparty.*,\
    !com.google.gwt.uibinder.*,\
    !com.google.gwt.user.*,\
    !com.google.gwt.util.*,\
    !jsinterop.*,!javax.annotation.*,!javax.imageio.*,!javax.lang.model.*,\
    !javax.tools.*,!javax.validation.*,!org.apache.*,!org.objectweb.*,\
    !org.w3c.*,!sun.*,!junit.framework,\
    *

其中!是不导入的意思。最后的*是指导入所需要并且在OSGi容器中能导入的。

在DXP中使用公共参数支持IPC

Company Blogs 16 de diciembre de 2016 Por Neil Jin Staff

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

关键点:

    在6.2以前的,我们需要在portlet.xml中声明公共参数,但是在OSGi时代,我们需要在Component中进行声明:

     "javax.portlet.supported-public-render-parameter=message"

     其中“message”是公共参数的名称。

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

     在Jsp中创建参数时可以这样写:

    <portlet:renderURL var="senderURL">

        <portlet:param name="message" value="Prove you can receive me" />
    </portlet:renderURL>
 
    可以通过这样的方式获取参数的值:
    renderRequest.getParameter("message");
 
    附件中是一个可以使用的样例,安装之后可以在Sample分类下找到portlet。
 
 

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.

     "javax.portlet.supported-public-render-parameter=message"

     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" />
    </portlet:renderURL>
 
    You can receive the value like this:
    renderRequest.getParameter("message");
 
Please check the attachment for a functional project.
After the module is installed you can find the portlets under Sample category.
 

有的放矢,Liferay进军数字体验市场

Company Blogs 3 de junio de 2016 Por Neil Jin Staff

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

 

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

 

Chueng说,当我们在观察分析自己的案例时候,发现我们已经朝着数字体验管理方向转变了。但是从某些角度看,Liferay却有着其他数字体验平台不具备的特性

 

首先,由于portal独特的架构,Liferay可以深度整合多种后台商务系统。正因为Liferay是许多客户使用的骨干门户,我们借此得以深入客户体验。这也使得Liferay,可以在整个客户生命周期中,帮助客户打造更好的客户关系,而不仅仅是停留在客户获取阶段。

什么是Liferay DXP

 

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

 

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

 

这样的架构,可以使客户在他们的客户生命周期中提供丰富用户体验方案,包括页面体验、移动设备体验、离线在线混合体验。Liferay甚至支持新型技术的的接口,如React或者Angular。这些体验还包括市场前景站点、客户站点(客户登录接口)、客户支持(在客户门户系统或者技术支持系统中)。

 

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

 

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

 

下面是一个银行柜员使用的页面中显示的单一用户视图。在一个页面中,柜员可以看见实时的相关信息,比如客户与银行最后一次的对话(也可能是其他任何信息),这些信息是从银行中其他部门的后台系统中整理出来。基于视图中的信息,柜员可以向用户提供更精准的建议,为银行开创更多交叉销售和业绩提升机遇。

 

客户门户新兴方案

 

Chueng论及了Liferay在传统客户门户解决方案,重点不仅是考虑到成本方面,同时也顾及拓展更多机遇、提供自助服务、强化客户忠诚度以及在与客户的交互中增进购买率。

 

Cheung谈论的另外一个领域就是如何让客户更道德的去管理他们的客户数据。Chueng相信,终端用户理应在如何使用他们自己的数据有一定程度的控制,公司也需要工具来控制用户数的的安全性和敏感性。

 

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

 

完备的用户体验

 

Liferya正在以一种独创的方式前进。在整个市场营销领域,整体客户生命周期支持的解决方案,是许多其他数字体验商从未提及的。在这个方案,Liferay在整体用户体验扮演者更重要的角色。

 

大多数的厂商关注在客户获取客户服务阶段。过度关注某一方面会,都会使企业在用户全局参数方面时使失焦。其实这么做也不意味着对与不对,但是对于许多企业,有一种基于各种组件的一体化的解决方案来帮助企业优化业务处理了流程,是一种更智慧的选择。

 

原文地址:http://www.cms-connected.com/News-Articles/May-2016/%E2%80%8BLiferay-Takes-Direct-Aim-at-the-Digital-Experienc

原文作者:BARB MOSHER ZINCK

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

Company Blogs 24 de mayo de 2016 Por Neil Jin Staff

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

此次Liferay公开的这个全新的数字平台,针对企业管理体验和用户端对端关系管理在设计上有了一个质的飞跃。这些全新的设计可以向企业展现出他们数字用户的的全貌,为桌面站点,移动站点,社交平台和店面的触屏设备等众多平台提供出众一致性客户体验。

 

进化中的市场

商务巨头们很早就明白一个道理,今天的市场早就已经变化了。当今客户们至始至终都希望优化客户体验。在最新的产品中,Liferay正在通过新设计来企及这一目标。

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

Liferay DXP:惊鸿一瞥

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

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

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

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

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

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

Liferay的数字体验平台方案还有另外三重概念:

 

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

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

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

更多信息,请访问Liferay DXP官网
 
原文链接:https://www.cmscritic.com/liferay-digital-experience-platform/
原文作者: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任务了,这样就可以生成所需的服务了。

那么,接下来的问题是,怎么调用这个服务呢。

在7.0的项目中,我们使用gradle来管理依赖。

我们可以创建一个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.

 

1920x1080

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',

 TWITTER_ADDR = '',

 /*this version only support share button yet*/

 TWITTER_BUTTON = 

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

 ;


 var Twitter = A.Component.create({


   NAME: TWITTER,


   ARRTS: {

   },

   prototype: {

     initializer: function() {

       var instance = this;

       instance._loadScript();

     },

    _loadScript: function() {

      A.Get.script(

        'https://platform.twitter.com/widgets.js',

        {

          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);


      A.one(options.contentBox).append(button);


      return button;


    },


    //can add more features


    COUNT_ALIGN: {

      HORIZONTAL: 'horizontal',

      VERTICAL: 'vertical'

    },

    LANG: {

      ARABIC: 'ar',

      CHINESE_SIMPLIFIED: 'zh-cn',

      CHINESE_TRADITIONAL: 'zh-tw',

      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
    };

    tObj.createTwitterButton(btnOpt);

  });
</aui:script>

 

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