2014年9月3日 星期三

SearchContainer另一種寫法。

會這樣寫的用意是可以自由讓  header 放在想要的地方,只要在config裡面做設定就好了。


config.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@include file="/html/init.jsp"%>

<%

String tabs2 = ParamUtil.getString(request, "tabs2", "display-settings");
String redirect = ParamUtil.getString(request, "redirect");

%>

<liferay-portlet:renderURL portletConfiguration="true" var="portletURL">
<portlet:param name="tabs2" value="<%= tabs2 %>" />
<portlet:param name="redirect" value="<%= redirect %>" />
</liferay-portlet:renderURL>

<liferay-portlet:actionURL portletConfiguration="true"
var="configurationURL" />

<aui:form action="<%=configurationURL%>" method="post" name="fm" onSubmit='<%= "event.preventDefault(); " + renderResponse.getNamespace() + "saveConfiguration();" %>'>

<aui:input name="<%=Constants.CMD%>" type="hidden" value="<%=Constants.UPDATE%>" />
<aui:input name="tabs2" type="hidden" value="<%= tabs2 %>" />
<aui:input name="redirect" type="hidden" value="<%= redirect %>" />

<%
String tabs2Names = "display-settings,rss";
%>

<liferay-ui:tabs
names="<%= tabs2Names %>"
param="tabs2"
url="<%= portletURL %>"
/>

<c:choose>
<c:when test='<%= tabs2.equals("display-settings") %>'>
<aui:input name="preferences--newsColumns--" type="hidden"/>

<liferay-ui:panel-container extended="<%= true %>" id="newsSettingsPanelContainer" persistState="<%= true %>">
<liferay-ui:panel collapsible="<%= true %>" extended="<%= true %>" id="newsEntriesListingPanel" persistState="<%= true %>" title="新聞列表欄位顯示">
<aui:fieldset>
<aui:field-wrapper label="顯示的欄位">

<%
Set<String> availableNewsColumns = SetUtil.fromArray(StringUtil.split(allNewsColumns));

// Left list

List leftList = new ArrayList();

for (String newsColumn : newsColumns) {
leftList.add(new KeyValuePair(newsColumn, LanguageUtil.get(pageContext, newsColumn)));
}

// Right list

List rightList = new ArrayList();

Arrays.sort(newsColumns);

for (String entryColumn : availableNewsColumns) {
if (Arrays.binarySearch(newsColumns, entryColumn) < 0) {
rightList.add(new KeyValuePair(entryColumn, LanguageUtil.get(pageContext, entryColumn)));
}
}

rightList = ListUtil.sort(rightList, new KeyValuePairComparator(false, true));
%>

<liferay-ui:input-move-boxes
leftBoxName="currentEntryColumns"
leftList="<%= leftList %>"
leftReorder="true"
leftTitle="目前的"
rightBoxName="availableEntryColumns"
rightList="<%= rightList %>"
rightTitle="可用的"
/>
</aui:field-wrapper>
</aui:fieldset>
</liferay-ui:panel>
</liferay-ui:panel-container>
</c:when>

<c:when test='<%= tabs2.equals("rss") %>'>
<liferay-ui:rss-settings
delta="<%= rssDelta %>"
displayStyle="<%= rssDisplayStyle %>"
enabled="<%= enableRSS %>"
feedType="<%= rssFeedType %>"/>
</c:when>
</c:choose>
<aui:button type="submit" />
</aui:form>





<aui:script>
Liferay.provide(
window,
'<portlet:namespace />saveConfiguration',
function() {
<c:choose>
<c:when test='<%= tabs2.equals("display-settings") %>'>
document.<portlet:namespace />fm.<portlet:namespace />newsColumns.value = Liferay.Util.listSelect(document.<portlet:namespace />fm.<portlet:namespace />currentEntryColumns);
</c:when>
</c:choose>

submitForm(document.<portlet:namespace />fm);
},
['liferay-util-list-fields']
);
</aui:script>



然後在init.jsp裡面加入config需要的表頭設定

//searchContainer;
String defaultNewsColumns = "title,category,status,priority,action";
String allNewsColumns = defaultNewsColumns + ",createDate,userName,viewCount";
String[] newsColumns = StringUtil.split(PrefsParamUtil.getString(portletPreferences, request, "newsColumns", defaultNewsColumns));



再來這是list.jsp , SearchContainer不用tag的方式 , 改用JAVA的寫法。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
 
<%@ include file="/html/init.jsp"%>


<%
PortletURL listURL = renderResponse.createRenderURL();
listURL.setParameter("jspPage", "/html/news/latestnews_list.jsp");
listURL.setWindowState(WindowState.NORMAL);


String searchDateId = "searchDateId";
int listCount = 0 ;

ThemeDisplay display = (ThemeDisplay) request
.getAttribute(WebKeys.THEME_DISPLAY);


RowChecker rowChecker = null;

if (permissionChecker.isGroupAdmin(display.getScopeGroupId())
|| permissionChecker.isOmniadmin()) {
rowChecker = new RowChecker(renderResponse);
}

ResourceURL rssURL = liferayPortletResponse.createResourceURL();
//rssURL.setParameter("p_l_id", String.valueOf(plid));


SearchContainer searchContainer = new SearchContainer(liferayPortletRequest,
null, null, "news",
SearchContainer.DEFAULT_DELTA,
SearchContainer.DEFAULT_DELTA,
listURL,
null, null);



List<String> headerNames = new ArrayList<String>();

for (String headerName : newsColumns) {
headerNames.add(headerName);
}

searchContainer.setRowChecker(rowChecker);
searchContainer.setHeaderNames(headerNames);

List results = null;
int total = 0;

results = NewsLocalServiceUtil.findByTop();
total = results.size();

searchContainer.setResults(results);
searchContainer.setTotal(total);
List resultRows = searchContainer.getResultRows();

for(int i = 0 ; i < results.size(); i++){
News news = (News)results.get(i);
ResultRow row = new ResultRow(news, news.getPrimaryKey(), i);
PortletURL rowURL = liferayPortletResponse.createRenderURL();


rowURL.setParameter("jspPage", "/html/news/latestnews_view.jsp");  
rowURL.setParameter("redirect", currentURL);
rowURL.setParameter("newsId", String.valueOf(news.getNewsId()));

for (Object column : searchContainer.getHeaderNames()) {
String columnName = (String)column;


if (columnName.equals("action")) {
row.addJSP("/html/news/latestnews_action.jsp");
}

if (columnName.equals("createDate")) {
row.addDate(news.getCreateDate());
}

if (columnName.equals("title")) {
//TextSearchEntry fileEntryTitleSearchEntry = new TextSearchEntry();
//fileEntryTitleSearchEntry.setName(news.getTitle());
//row.addSearchEntry(fileEntryTitleSearchEntry);
row.addText(StringUtil.shorten(news.getTitle(), 100), rowURL);
}

if (columnName.equals("category")) {
row.addText(news.getCategory());
}

if (columnName.equals("viewCount")) {
row.addText(String.valueOf(news.getViewCount()));
}

if (columnName.equals("status")) {
row.addStatus(news.getStatus(),news.getStatusByUserId(),news.getStatusDate());
}
if (columnName.equals("userName")) {
row.addText(news.getUserName());
}
if (columnName.equals("priority")) {
row.addText(String.valueOf(news.getPriority()));
}
}
resultRows.add(row);

}

%>

<portlet:renderURL var="createNewURL">
<portlet:param name="backURL" value="<%=currentURL%>" />
<portlet:param name="<%=NewsConstants.CMD%>" value="<%=NewsConstants.ADD%>" />
<portlet:param name="jspPage" value="/html/news/latestnews_edit.jsp" />
</portlet:renderURL>

<portlet:renderURL var="categoryMgtURL" windowState="<%=LiferayWindowState.MAXIMIZED.toString() %>">
<portlet:param name="backURL" value="<%=currentURL %>" />
<portlet:param name="jspPage" value="/html/news/category/category_mgt.jsp" />
</portlet:renderURL>

<portlet:renderURL var="viewPermissionMgtURL" windowState="<%=LiferayWindowState.MAXIMIZED.toString() %>">
<portlet:param name="backURL" value="<%=currentURL %>" />
<portlet:param name="jspPage" value="/html/news/viewpermission/vp_list.jsp" />
</portlet:renderURL>

<portlet:actionURL name="deleteList" var="dellistURL"/>

<portlet:actionURL name="searchNews" var="searchURL"/>


<aui:form action="<%=searchURL%>" method="post">
<%--
<aui:input type="hidden" name="redirect" value="<%=currentURL %>"/>
 --%>

<aui:nav-bar>
<aui:nav>
<aui:nav-item href="${categoryMgtURL}" label="新聞類別管理" title="新聞類別管理"/>
<aui:nav-item href="${viewPermissionMgtURL}" label="檢視權限管理" title="檢視權限管理" name="addAnnouncementButton" />
</aui:nav>
<aui:nav-bar-search cssClass="pull-right">
<div class="form-search">
類別
<aui:select name="" label="" inlineField="true" inlineLabel="left" style="width:100px;">
</aui:select>
時間區間
<aui:input id="<%=searchDateId %>" name="startDate" value="" label="" inlineField="true" style="width:70px;"/>
~
<aui:input id="<%=searchDateId %>" name="endDate" value="" label="" inlineField="true" style="width:70px;"/>
<liferay-ui:input-search autoFocus="<%= windowState.equals(WindowState.MAXIMIZED) %>" id="keywords1" name="keywords" placeholder='<%=LanguageUtil.get(locale, "keywords")%>' title="關鍵字"/>

</div>
</aui:nav-bar-search>
</aui:nav-bar>
</aui:form>

<a class="btn btn-info btn-large" href="<%=createNewURL %>">
  <i class="icon-plus-sign icon-large"></i>新增新聞
  </a>

<br>

<div style="float: right;" >
  <liferay-ui:rss
delta="<%= rssDelta %>"
displayStyle="<%= rssDisplayStyle %>"
feedType="<%= rssFeedType %>"
resourceURL="<%= rssURL %>" />
</div>


<liferay-ui:search-iterator paginate="<%= false %>" searchContainer="<%= searchContainer %>" />





<aui:script>
AUI().use('aui-datepicker', function(A) {
new A.DatePicker({
trigger: '#<portlet:namespace />searchDateId',
popover: {zIndex: 1 },
calendar: {
dates: ['10/01/2014'],
dateFormat: '%Y/%m/%d'
},
on: {
          selectionChange: function(event) {
            console.log(event.newSelection)
          }
        }
});
});
</aui:script>











2014年9月2日 星期二

客製Portlet 產生RSS Feed的做法

環境Liferay6.2.1 + tomcat-7.0.42 + JAVA 7
使用postgreSQL 9版


本例以新聞發布為例。
先在 liferay-plugin-package.properties
加入 jdom.jar , dom4j.jar , rome.jar  這三個jar檔主要是用來產生RSS Feed。
連XML template都不用自己建。
如果沒有載入,<liferay-ui:rss> 與 <liferay-ui:rss-settings> 這兩個Tag會產生null的Exception。

portal-dependency-jars=\
    jstl-api.jar,\
    jstl-impl.jar,\
    jdom.jar,\
    dom4j.jar,\
    rome.jar



這是這次所使用的model 
<service-builder package-path="com.xxx.portlet">
<author>Danny</author>
<namespace>news</namespace>

<entity name="News" human-name="news" local-service="true"
remote-service="false" uuid="true">

<column name="newsId" type="long" primary="true" />
<column name="groupId" type="long" />
<column name="companyId" type="long" />
<column name="userId" type="long" />
<column name="userName" type="String" />
<column name="modifiedDate" type="Date" />
<column name="createDate" type="Date" />
<column name="title" type="String" />
<column name="content" type="String" />
<column name="viewCount" type="long" />
<column name="priority" type="boolean" />
<column name="category" type="String" />
<column name="copyToMainSite" type="boolean" />
<column name="urlTitle" type="String" />
    <column name="resourcePrimKey" type="long"/>
   <column name="status" type="int"/>
   <column name="statusByUserId" type="long"/>
   <column name="statusByUserName" type="String"/>
   <column name="statusDate" type="Date"/>

<order by="desc">
<order-column name="createDate" />
</order>

<finder name="CD_T" return-type="Collection">
<finder-column name="createDate" comparator="&gt;"/>
<finder-column name="title" comparator="LIKE"/>
</finder>
<finder name="CD_C" return-type="Collection">
<finder-column name="createDate" comparator="&gt;"/>
<finder-column name="content" comparator="LIKE"/>
</finder>
<finder name="R_S" return-type="Collection">
  <finder-column name="resourcePrimKey"/>
  <finder-column name="status"/>
</finder>
<finder name="GroupId" return-type="Collection">
<finder-column name="groupId" />
</finder>
<finder name="CompanyId" return-type="Collection">
<finder-column name="companyId" />
</finder>
<finder name="G_UT" return-type="News" unique="true">
<finder-column name="groupId" />
<finder-column name="urlTitle" />
</finder>
<finder name="G_NotS" return-type="Collection">
<finder-column name="groupId" />
<finder-column name="status" comparator="!=" />
</finder>
<finder name="G_S" return-type="Collection">
<finder-column name="groupId" />
<finder-column name="status" />
</finder>
<reference package-path="com.liferay.portal" entity="Group" />
<reference package-path="com.liferay.portal" entity="WorkflowInstanceLink"/>
<reference package-path="com.liferay.portlet.asset" entity="AssetEntry" />
<reference package-path="com.liferay.portlet.asset" entity="AssetLink" />
<reference package-path="com.liferay.portlet.asset" entity="AssetTag" />
</entity>
</service-builder>


在init.jsp 上加入以下這幾四個參數,這是使用<liferay-ui:rss>所需要使用到的參數。

boolean enableRSS = !PortalUtil.isRSSFeedsEnabled() ? false : GetterUtil.getBoolean(portletPreferences.getValue("enableRss", null), true);
int rssDelta = GetterUtil.getInteger(portletPreferences.getValue("rssDelta", StringPool.BLANK), SearchContainer.DEFAULT_DELTA);
String rssDisplayStyle = portletPreferences.getValue("rssDisplayStyle", RSSUtil.DISPLAY_STYLE_DEFAULT);
String rssFeedType = portletPreferences.getValue("rssFeedType", RSSUtil.FEED_TYPE_DEFAULT);




latestnews_view.jsp 就可以使用 rss的Tag了。

<liferay-portlet:resourceURL varImpl="rssURL" >
</liferay-portlet:resourceURL>
        <liferay-ui:rss
delta="<%= rssDelta %>"
displayStyle="<%= rssDisplayStyle %>"
feedType="<%= rssFeedType %>"
resourceURL="<%= rssURL %>" />
如以下,就會產生RSS的超連結出來。



當點下之後,會去呼叫 resourceURL。
就會導到Control的 public void serveResource(ResourceRequest request, ResourceResponse response)
方法。
在這裡取得RSS Feed的做法也就是在這裡開始產生。

如果是Struts的作法,就要去繼承 Struts.RSSAction.java 然後在裡面實作。

在這裡我們是採用MVCPortlet.java

public class NewsPortlet extends MVCPortlet{
@Override
public void serveResource(ResourceRequest request, ResourceResponse response)
            throws PortletException, IOException {

response.setContentType(ContentTypes.TEXT_XML_UTF8);
ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
WebKeys.THEME_DISPLAY);

Layout layout = themeDisplay.getLayout();
long plid = themeDisplay.getPlid();
long groupId = themeDisplay.getScopeGroupId();
int max = ParamUtil.getInteger(request, "max", SearchContainer.DEFAULT_DELTA);
String type = ParamUtil.getString(request, "type", RSSUtil.FORMAT_DEFAULT);
double version = ParamUtil.getDouble(request, "version", RSSUtil.VERSION_DEFAULT);
String displayStyle = ParamUtil.getString(request, "displayStyle", RSSUtil.DISPLAY_STYLE_DEFAULT);

String feedURL = themeDisplay.getPortalURL() + themeDisplay.getPathMain() +
"/news/find_entry?";
_log.info("themeDisplay.getPortalURL():" + themeDisplay.getPortalURL() + "==");
_log.info("themeDisplay.getPathMain():" + themeDisplay.getPathMain() + "==");
String entryURL = feedURL;
String rss = StringPool.BLANK;
try {

if (layout != null) {
groupId = themeDisplay.getScopeGroupId();

feedURL = PortalUtil.getLayoutFullURL(themeDisplay) +
Portal.FRIENDLY_URL_SEPARATOR + "news/rss";
_log.info("feedURL:" + feedURL + "==");
entryURL = feedURL;

rss = NewsLocalServiceUtil.getGroupNewsesRSS(
groupId, max, type, version, displayStyle,
feedURL, entryURL, themeDisplay);
}
} catch (SystemException e) {
e.printStackTrace();
} catch (PortalException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

// return rss.getBytes(StringPool.UTF8);
PrintWriter writer = response.getWriter();
writer.print(rss);
}
}
這當中有去呼叫到 NewsLocalServiceUtil.getGroupNewsesRSS();
這是自己定義的method。
所以在NewsLocalServiceImpl.java內要實作這個method

 public String getGroupNewsesRSS(
long groupId, int max, String type,
double version, String displayStyle, String feedURL,
String entryURL, ThemeDisplay themeDisplay)
throws PortalException, SystemException {

Group group = groupPersistence.findByPrimaryKey(groupId);

String name = group.getDescriptiveName();
List<News> newses = getGroupNewses(groupId, 0, max);

return exportToRSS(
name, name, type, version, displayStyle, feedURL, entryURL,
newses, themeDisplay);
}
 
protected String exportToRSS(
String name, String description, String type, double version,
String displayStyle, String feedURL, String entryURL,
List<News> newses, ThemeDisplay themeDisplay)
throws SystemException {

SyndFeed syndFeed = new SyndFeedImpl();

syndFeed.setDescription(description);

List<SyndEntry> syndEntries = new ArrayList<SyndEntry>();

syndFeed.setEntries(syndEntries);

for (News news : newses) {
SyndEntry syndEntry = new SyndEntryImpl();

String author = PortalUtil.getUserName(news);

syndEntry.setAuthor(author);

SyndContent syndContent = new SyndContentImpl();

syndContent.setType(RSSUtil.ENTRY_TYPE_DEFAULT);

String value = null;

if (displayStyle.equals(RSSUtil.DISPLAY_STYLE_ABSTRACT)) {
String summary = news.getTitle();

if (Validator.isNull(summary)) {
summary = StringUtil.shorten(
HtmlUtil.extractText(news.getContent()),
200, StringPool.BLANK);
}

value = StringUtil.shorten(
HtmlUtil.extractText(summary),
200, StringPool.BLANK);
}
else if (displayStyle.equals(RSSUtil.DISPLAY_STYLE_TITLE)) {
value = StringPool.BLANK;
}
else {
value = StringUtil.replace(
news.getContent(),
new String[] {
"href=\"/", "src=\"/"
},
new String[] {
"href=\"" + themeDisplay.getURLPortal() + "/",
"src=\"" + themeDisplay.getURLPortal() + "/"
});
}

syndContent.setValue(value);

syndEntry.setDescription(syndContent);

StringBundler sb = new StringBundler(4);

if (entryURL.endsWith("/news/rss")) {
sb.append(entryURL.substring(0, entryURL.length() -11 ));
sb.append(StringPool.QUESTION);
sb.append("p_p_id=");
sb.append(PortletKeys.LATESTNEWS_PORTLET);
sb.append(StringPool.AMPERSAND); //&
sb.append("p_p_lifecycle=0&p_p_state=normal&p_p_mode=view&p_p_col_id=column-1&p_p_col_count=1&");
sb.append(PortletKeys.NEWS_NAMESPACE + "jspPage=");
sb.append("%2Fhtml%2Fnews%2Flatestnews_view.jsp");
sb.append(StringPool.AMPERSAND); //&
sb.append(PortletKeys.NEWS_NAMESPACE + "newsId=");
sb.append(news.getNewsId());
_log.info("if=>sb:" + sb + "==");
}
else {
sb.append(entryURL);
_log.info("else=>sb:" + sb + "==");

if (!entryURL.endsWith(StringPool.QUESTION)) {
sb.append(StringPool.AMPERSAND);
}

}

String link = sb.toString();

syndEntry.setLink(link);

syndEntry.setPublishedDate(news.getCreateDate());
syndEntry.setTitle(news.getTitle());
syndEntry.setUpdatedDate(news.getModifiedDate());
syndEntry.setUri(link);

syndEntries.add(syndEntry);
}

syndFeed.setFeedType(RSSUtil.getFeedType(type, version));

List<SyndLink> syndLinks = new ArrayList<SyndLink>();

syndFeed.setLinks(syndLinks);

SyndLink selfSyndLink = new SyndLinkImpl();

syndLinks.add(selfSyndLink);

selfSyndLink.setHref(feedURL);
selfSyndLink.setRel("self");

if (feedURL.endsWith("/-/news/rss")) {
SyndLink alternateSyndLink = new SyndLinkImpl();

syndLinks.add(alternateSyndLink);

alternateSyndLink.setHref(
feedURL.substring(0, feedURL.length() - 11));
alternateSyndLink.setRel("alternate");
}

syndFeed.setPublishedDate(new Date());
syndFeed.setTitle(name);
syndFeed.setUri(feedURL);

try {
return RSSUtil.export(syndFeed);
}
catch (FeedException fe) {
throw new SystemException(fe);
}
}

當中的entryURL 和 FeedURL 是比較難搞的,
FeedURL = 消息來源網址。
entryURL = 消息來源之內部每單一筆的導向網址,點擊RSS其中一筆後,會開啟該篇新聞的來源網址。

上面每一筆的entryURL 都是仿照PortletURL在網址上的參數,
有用到portletKeys,我是這樣寫,
public static final String LATESTNEWS_PORTLET = "News_WAR_Newsportlet";
public static final String NEWS_NAMESPACE = "_News_WAR_Newsportlet_";
portletId 與 namespace的差別 在於 左右兩側的底線。



另外這是設定RSS的型態,可以寫在Portlet-Configuration上。

<%@include file="/html/init.jsp"%>
<liferay-portlet:actionURL portletConfiguration="true"
var="configurationURL" />

<aui:form action="<%=configurationURL%>" method="post">
<aui:input name="<%=Constants.CMD%>" type="hidden"
value="<%=Constants.UPDATE%>" />

<aui:fieldset>
<liferay-ui:rss-settings
delta="<%= rssDelta %>"
displayStyle="<%= rssDisplayStyle %>"
enabled="<%= enableRSS %>"
feedType="<%= rssFeedType %>"/>

<aui:button type="submit" />
</aui:fieldset>
</aui:form>