In the first tutorial we used Portofino 4 to create the blog data model and to create the main pages. In the second tutorial we introduced the permissions and changed the layout. For these operations, we used mainly Portofino out of the box via the web interface.

 
In this third tutorial we create custom pages for visualizing and editing the posts. The final goal is shown in the following picture.
 

Step 1. Change the layout of the list for Posts

At the moment, the main page displays a list of posts in tabular form as in all Portofino CRUD pages. But usually blog applications display posts in sequence, showing the title, a summary and then a link "Read" to read the whole article.
 
Therefore we are going to create a custom page (a jsp) to replace the search page. We edit the Groovy script to load objects and redirect to a new custom page.
I created the custom jsp starting from the standard jsp located in layouts/crud/search.jsp.
 
Let's see the script.
    //*******************************************************
    // List objects
    //*******************************************************

    @DefaultHandler
    @Override
    public Resolution execute() {
        if (object == null) {
            return doList();
        } else {
            return read();
        }
    }

    public Resolution doList(){
        firstResult = 0;
        maxResults= 10;
        loadObjects();
        return forwardToPortletPage("/apps/default/web/listPosts.jsp");
    }
In the script we override the default  method "execute" called  by Stripes. Originally the method redirects to doRead or doSearch. We modify it to redirect to our method doList. The method doList calls a method of CrudAction to load objects and forwards to a jsp page that we will create.
 
The JSP page is shown below. Portofino uses Stripes templating to define the jsp pages.
 
<%@ page import="com.google.inject.internal.Objects" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="com.manydesigns.elements.xml.XhtmlBuffer" %>
<%@ page import="org.apache.commons.lang.StringUtils" %>
<%@ page contentType="text/html;charset=UTF-8" language="java"
         pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="stripes"
    uri="http://stripes.sourceforge.net/stripes-dynattr.tld"%>
<%@taglib prefix="mde" uri="/manydesigns-elements"%>
<%@ taglib tagdir="/WEB-INF/tags" prefix="portofino" %>
<stripes:layout-render name="/skins/${skin}/portlet.jsp">
 <jsp:useBean id="actionBean" scope="request"
   type="com.manydesigns.portofino.pageactions.crud.CrudAction"/>
 <stripes:layout-component name="portletTitle">
   <c:out value="${actionBean.crudConfiguration.searchTitle}"/>
 </stripes:layout-component>
 <stripes:layout-component name="portletBody">
  <div id="posts" >
   <portofino:buttons list="crud-search" cssClass="portletButton" />
   <%
     XhtmlBuffer buffer = new XhtmlBuffer();
     int i = 0;
     for (HashMap object : (List<HashMap>)actionBean.objects){
       buffer.writeH2((String)object.get("title"));
       buffer.write(StringUtils.abbreviate(
        (String) object.get("summary"),300));
       buffer.writeBr();
       buffer.writeAnchor("post/"+object.get("id"), "Read");
       buffer.writeHr();
       i++;
       if(i==10)
         break;
     }
     out.println(buffer.toString());
     %>
   </div>
<script type="text/javascript">
   result = 10;
   isLoading=false;
   $(window).scroll(function () {
      if ($(window).scrollTop() + $(window).height() > 
            $('#posts').height() - 5 && !isLoading ) {
         isLoading = true;
         $.ajax({
            type: 'GET',
            url: "post?jsonSearchData=1&firstResult="+
                  result+"&maxResults=10",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function (msg) {
            for (var i = 0; i < msg.Result.length; i++) {
                  $("div#posts").append('<h2>'+
                   msg.Result[i].title.value+
                   '</h2>'+msg.Result[i].summary.value+'<br/>'+
                   '<a href="post/'+msg.Result[i].id.value+
                   '">Read</a><hr/>');
                }
                isLoading = false;
                }
              });
              result = result+10;
            }
         });

</script>
  <portofino:buttons list="crud-search" cssClass="portletButton" />
  </stripes:layout-component>
</stripes:layout-render>
We scan the list of objects in variable "actionBean.objects" populated by the Groovy. For each object we print the title, the summary and a link to the detail. Through a counter, we print only the first ten elements.
 
We don't print all posts, instead we use an Ajax call via JavaScript, which loads 10 posts when a user reaches the bottom of the page (a behaviour similar to posts in Facebook).  
JavaScript makes an Ajax call at the address
post?jsonSearchData=1&firstResult="+result+"&maxResults=10
The method jsonSearchData is a method already present in the CrudAction that returns a search result in JSON, e.g. it is used for pagination.
Returned data in JSON format are printed after previously loaded posts. 
 
 
The list of the buttons in the search is prnted by the following row
<portofino:buttons list="crud-search" cssClass="portletButton" />

Buttons are defined with the annotation @Button in any method that returns a Resolution.

If you want you can remove buttons, such as those which export to Excel and PDF. Let's override button methods in AbstractCrudAction without putting the annotation @Button.  

...

//Buttons disabled
    @Override
    Resolution exportReadExcel() {
        return super.exportReadExcel()
    }

    @Override
    Resolution exportReadPdf() {
        return super.exportReadPdf()
    }

    @Override
    Resolution exportSearchExcel() {
        return super.exportSearchExcel()
    }

    @Override
    Resolution exportSearchPdf() {
        return super.exportSearchPdf()
    }

    @Override
    Resolution bulkDelete() {
        return super.bulkDelete()
    }


    @Override
    Resolution bulkEdit() {
        return super.bulkEdit()
...

The result is the following

 
 

Step 2. Html editor in posts

In this step we modify the CRUD pages of the post for using an html editor to write and display the body field. Portofino 4 already uses the JavaScript editor CKEditor to create "Text" pages. Add the annotation "RichText" to the column "body" to use html editor also in CRUD.
 
Let's go to "Administration" > "Tables"
Select the table "post". Then click on the "body" column.
Here we select "Type of content" value "RichText" to annotate it.
 
 
With this operation we have a html editor for creating and editing posts, as you can see in the following picture.
 
 

Step 3. Change the post display

Now we are going to change the display of the individual post for avoiding the classical format property-value of Portofino 4.  We edit the Groovy script to forward to  our custom jsp.
@Override
    Resolution read() {
        super.read();
        return forwardToPortletPage("/apps/default/web/readPost.jsp");
    }

Then write the jsp page readPost.jsp

 

<%@ page import="java.util.HashMap" %>
<%@ page import="com.manydesigns.elements.xml.XhtmlBuffer" %>
<%@ page contentType="text/html;charset=UTF-8" language="java"
       pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="stripes" 
       uri="http://stripes.sourceforge.net/stripes-dynattr.tld"%>
<%@taglib prefix="mde" uri="/manydesigns-elements"%>
<%@ taglib tagdir="/WEB-INF/tags" prefix="portofino" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<stripes:layout-render name="/skins/${skin}/portlet.jsp">
    <jsp:useBean id="actionBean" scope="request" 
       type="com.manydesigns.portofino.pageactions.crud.CrudAction"/>
    <stripes:layout-component name="portletTitle">
        <% HashMap obj = (HashMap)actionBean.object;%>
        <%= obj.get("title")%>
    </stripes:layout-component>
    <stripes:layout-component name="portletBody">
        <!-- Print the post -->
        <%
            HashMap obj = (HashMap)actionBean.object;
            XhtmlBuffer buffer = new XhtmlBuffer();
            buffer.openElement("em");
            buffer.write((String) obj.get("summary"));
            buffer.closeElement("em");
            buffer.writeBr();
            buffer.writeBr();
            buffer.writeNoHtmlEscape((String) obj.get("body"));
            buffer.writeBr();
            buffer.openElement("strong");
            buffer.write((String) obj.get("author"));
            buffer.closeElement("strong");
            buffer.writeBr();
            out.println(buffer.toString());
        %>

        <c:if test="${not empty actionBean.searchString}">
            <input type="hidden" name="searchString"
             value="<c:out value="${actionBean.searchString}"/>"/>
        </c:if>
    </stripes:layout-component>
    <stripes:layout-component name="portletFooter">
        <div class="crudReadButtons">
            <portofino:buttons list="crud-read" cssClass="portletButton" />
        </div>
        <stripes:submit name="print" value="Print" 
          disabled="true" class="portletButton"/>-->
        <script type="text/javascript">
            $(".crudReadButtons button[name=delete]").click(function() {
                return confirm ('<fmt:message key="commons.confirm" />');
            });
        </script>
    </stripes:layout-component>
</stripes:layout-render>
Jsp page  above print the fields of the post in the desired format. The final result is shown below.
 

In the next tutorial we are going to customize the "comment" page putting the list and the creation of comments in  a single page.

 

comments powered by Disqus