Search code examples
javajspliferayportlet

The basic Liferay implementation policy of view.jsp and render method


Please tell me the basic Liferay implementation policy. I am making the porlet of the review list, then should I separate the one function like search and the another function like list function displayed initially? If they are divided, though it will be displayed same entirely on the screen, I think that the implementation is totally different in the case of dividing it and the case of not dividing. I implement both functions at 1 jsp as view.jsp without separating the functions now. Should I make this jsp separately, even if both are displayed entirely same? The most troublesome thing now is the render method of the portlet class. I'm getting data to display in the list with the render method, but this data is not used for other functions in the portlet. The render method passes through whatever screen is displayed in the portlet, is not it? So, I think I might separate jsp by search and list and display data on initial view by calling service in list jsp, though both displayed entirely same. Please tell me the basic way of thinking like this?

Best regards.

Hi. Olaf.

This is my sample source. view.jsp

<%@ include file="/init.jsp" %>

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

<aui:form action="<%=searchURL %>" name="<portlet:namespace />fm">

    <div class="search-form">
        <span class="aui-search-bar">
            <aui:input inlineField="<%= true %>" label="name"
            name="authorName" size="30" title="search" type="text"
            />

            <aui:input inlineField="<%= true %>" label="content"
            name="content" size="1" title="search" type="text"
            />

            <aui:button type="submit" value="search" />
        </span>
    </div>
</aui:form>

<jsp:useBean id="lresult" class="java.util.ArrayList"
    type="java.util.List" scope="request" />

<liferay-ui:search-container>
    <liferay-ui:search-container-results results="<%= lresult %>" />

<liferay-ui:search-container-row
    className="com.liferay.service.model.Results" modelVar="results" indexVar="i">

    <liferay-ui:search-container-column-text property="authorName" name="name" />

    <liferay-ui:search-container-column-text property="rating" name="rating" />

    <liferay-ui:search-container-column-text property="content" name="content" />

    <portlet:renderURL var='detailUrl'>
        <portlet:param name='action' value='detail' />
        <portlet:param name='id' value='<%= results.getId() %>' />
        <portlet:param name="jspPage" value="/detail.jsp" />
    </portlet:renderURL>
    <a href='<%=detailUrl %>}'>detail</a>

</liferay-ui:search-container-row>

<liferay-ui:search-iterator />

</liferay-ui:search-container>

And this is porltet java source. ResultsListPortlet.java

public class ResultsListPortlet extends MVCPortlet {

    private String authorName = "%";
    private String content = "%";

    public void detail(ActionRequest request, ActionResponse response) {
        try {
            String id = ParamUtil.getString(request, "id");
            Results result = _resultsLocalService.findBysearchResult(id);

            renderRequest.setAttribute("result", result);
        }
        catch (Exception e) {
        }
    }

    public void search(ActionRequest request, ActionResponse response) {
        try {
            authorName = ParamUtil.getString(request, "authorName");
            content = ParamUtil.getString(request, "content");
        }
        catch (Exception e) {
        }
    }

    @Override
    public void render(RenderRequest renderRequest, RenderResponse renderResponse)
            throws IOException, PortletException {

        try {
            List<Results> results = _resultsLocalService.findBysearchResults(authorName,
                    content, 0, 20, null, false);
            authorName = "%";
            content = "%";

            renderRequest.setAttribute("lresult", results);
        }
        catch (Exception e) {
            throw new PortletException(e);
        }

        super.render(renderRequest, renderResponse);
    }

    @Reference(unbind = "-")
    protected void setResultsService(ResultsLocalService resultsLocalService) {
        _resultsLocalService = resultsLocalService;
    }
    private ResultsLocalService _resultsLocalService;
}

The data lresults set in renderRequest in the render method is used for search and list functions. However, it is not used for other functions, such as the detailed function to display detailed data and the registration function to register data. This lresults is not necessary for detailed function or registration function, and I think such an implementation is bad. So please tell me how to change the implementation correctly or properly.


Solution

  • You never want to use member variables to hold values between the action and render phases.

    A portlet class will be invoked to service requests from all users, so these member variables will be constantly changed and your results will not be what you want.

    Sure it works locally for you because you do not have users testing against your development system, it is just you so the member fields are safe.

    The normal way you do this is to use the request attributes. You can set them in the action phase and retrieve them in the render phase.

    So you would instead have:

    public void search(ActionRequest request, ActionResponse response) {
       try {
          authorName = ParamUtil.getString(request, "authorName");
          content = ParamUtil.getString(request, "content");
    
          request.setAttribute("authorName", authorName);
          request.setAttribute("content", content);
       } catch (Exception e) {
          // you always want to at least log an exception, even if you plan on ignoring.
       }
    }
    

    In the render() method, you can extract these values, String authorName = GetterUtil.getString(renderRequest.getAttribute("authorName")); to then complete the search.

    This keeps the values out of storing in the member fields and will work regardless of how many users are hitting the portlet.

    I think it is worth raising as an issue whether to do the actual search in the render phase or whether the search should be completed in the action phase.

    The expectation is that an action handler does the actual work, but render should just render the results. The idea is that if a page is refreshed 100 times, you are not completing 100 searches, only the one initial search with 100 renders of the same result set.

    A portlet that is not the target of an action request should be re-rendering the same results as previous renders; a render is not supposed to have side effects. Since your search results could change in between render calls by other users (maybe admins) in other portlets, your results can change and it breaks the spirit of the specification.

    That said, I think one can also argue that the state that should be the same in your case is the search criteria, that if the search results change by other users, the updated results is the expected outcome.