Spring Roo
  1. Spring Roo
  2. ROO-1896

Allow custom columns to the Roo table tag

    Details

    • Type: Improvement Improvement
    • Status: Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: 1.1.0.M1, 1.1.0.M2, 1.1.0.M3, 1.1.0.RC1, 1.1.0.RELEASE, 1.1.1.RELEASE, 1.1.2.RELEASE
    • Fix Version/s: None
    • Component/s: WEB MVC
    • Labels:
      None

      Description

      I wish I would have bugged this up a lot earlier, but I haven't been working with ROO for a while and didn't have a chance when I was. Anyhow, I am calling this a bug because of how limiting it is.

      I created a production site using ROO 1.1.0 M1-RC1 and I really enjoyed using ROO for the most part (http://covercontest2010.pictage.com – This site originally allowed a Photographer to upload an image, then allow users and judges to vote on the images in order to select the magazine cover winner). However, the part I found the most limiting was the inability to modify the generated html. Specifically, the table tag. The table tag is extremely limiting. The reason being is that it tries to do too much with as little as possible. Beyond that, it is not customizable. You can't do anything with the generated html except throw it out.

      Example:

      I needed to add a thumbnail for each row of displayed data. The thumbnail is generated based on the primary key. I could not simply add:
      <table:column><img src="$

      {object.id}

      .jpg" /></table:column>

      I needed to generate information in a column that was a composite of multiple pieces of other information.

      I needed to modify what was displayed in the table header (title) without modifying what was displayed in the table body.

      Solution:
      At the bare minimum, allow addition of columns that do not require specifying an id to a corresponding property.

      Rework the tag so that the column header is separate from the column tag OR (better) if a column header tag is specified, the column tag does not generate a header.

      Allow for a css class property to be specified on any of the generated html tags (I actually needed to apply a css class to a table header element and was unable to do this). For that matter, PLEASE consider adding an optional tr tag. I can think of numerous reasons why you would want to do this (allowing alternating css based upon the row #).

      To Summarize
      There would almost never be a case where you would use the ROO generated html in a production environment (other than admin pages). However, ROOs hash mechanism is extremely powerful and it would be nice to be able to utilize this for maintaining production pages. This becomes possible by breaking up the ROO table tag to either use traditional html or more closely resemble it.

        Activity

        Hide
        Stefan Schmidt added a comment -

        Scott,

        Same as with your other ticket. This is not a bug but rather a (admittedly important) improvement request. Ideally I would like to see an example application in action which employs the proposed changes. Maybe you can also attach a patch for me to review?

        I think fundamental changes in the tag library should be tacked in a milestone phase which will be coming up soon.

        Cheers,
        Stefan

        Show
        Stefan Schmidt added a comment - Scott, Same as with your other ticket. This is not a bug but rather a (admittedly important) improvement request. Ideally I would like to see an example application in action which employs the proposed changes. Maybe you can also attach a patch for me to review? I think fundamental changes in the tag library should be tacked in a milestone phase which will be coming up soon. Cheers, Stefan
        Hide
        Eric Taix added a comment -

        Stefan,

        It's quite easy to find a use case which needs this improvement ! For example i'm trying to create an application with roo which will manage volleyball championships. In my domain I have Teams, Players, and much more. Each player have properties like firstname, name, licence number AND a picture (BTW it was quite difficult to add a picture as a blob in my database, but it is another subject).

        Now I want to show player's picture while I'm browsing a team.
        Sorry I tried to add a screenshot but it failed : I'll try later

        I found a way to do it but not sure it is the best way !
        I had to add a new property in Column: cellRenderer which defines a class name which implements CellRenderer

        renderableColumn
        ...
            <c:choose>
              <c:when test="${empty columnProperties and empty columnLabels}">
                <c:set var="columnProperties" value="${property}" scope="request" />
                <c:set var="columnLabels" value="${label}" scope="request" />
                <c:set var="columnMaxLengths" value="${empty maxLength ? 10 : maxLength}" scope="request" />
              </c:when>
              <c:otherwise>
                <c:set var="columnProperties" value="${columnProperties},${property}" scope="request" />
                <c:set var="columnLabels" value="${columnLabels},${label}" scope="request" />
                <c:set var="columnMaxLengths" value="${columnMaxLengths},${empty maxLength ? 10 : maxLength}" scope="request" />
              </c:otherwise>
            </c:choose>
            
            <c:choose>
              <c:when test="${empty cellRenderers}">
                <c:set var="cellRenderers" value="${cellRenderer} ," scope="request" />
              </c:when>
              <c:otherwise>
                <c:set var="cellRenderers" value="${cellRenderers}${cellRenderer} ," scope="request" />
              </c:otherwise>
            </c:choose>    
          </c:if>
        </jsp:root>
        

        I had to modify the way how cells are rendered.

        renderableTable.tagx snippet
                  ...
                  <c:forTokens items="${columnProperties}" delims="," var="column" varStatus="num">
                    <c:set var="columnMaxLength" value="${lengths[num.count-1]}" scope="request" />
                    <td>
                      <c:set var="renderer" value="${fn:trim(renderers[num.index])}"/>
                      <c:choose>
                        <c:when test='${empty renderer}'>
                          <c:choose>
                            <c:when test="${columnMaxLength lt 0}">
                              <spring:eval expression="item[column]" var="colTxt" />
                            </c:when>
                            <c:otherwise>
                              <spring:eval expression="item[column]" var="colTxt" />
                            </c:otherwise>
                          </c:choose>
                          <c:out value="${fn:substring(colTxt, 0, columnMaxLength)}" />
                        </c:when>
                        <c:otherwise>
                          <c:set var="value" value="${item[column]}"/>
                          <jared:cellRenderer rendererClass="${renderer}" propertyName="${column}" propertyValue="${value}" item="${item}" />
                        </c:otherwise>
                      </c:choose>
                    </td>
                  </c:forTokens>
                  ...
        

        Here is the CellRenderer interface:

        CellRenderer.java
        /**
         * A simple interface which declare a method to render a cell content in a table (not the cell itself)
         * @author eric.taix at gmail.com
         */
        public interface CellRenderer {
        	/**
        	 * Render a specific cell 
        	 * @param context The current page context (use it to retrieve JspWriter or others things like current context path)
        	 * @param propName The property name
        	 * @param propValue The current property value
        	 * @param data The object itself which contains the property
        	 */
        	public void render(PageContext context, String propName, Object propValue, Object data) throws IOException;
        }
        

        How to use it ? In your list.jspx just add the cellRenderer property when needed like:

        list.jspx example
                <table:tableRenderable data="${players}" id="l_org_jared_voscobo_domain_Player" path="/players" z="PqLFQtt8v7NQKbggUS1BN2Cy504=">
                    <table:columnRenderable id="c_org_jared_voscobo_domain_Player_firstName" property="firstName" z="tw3y9glYSpKR17g5VrELNcWx/2Q="/>
                    <table:columnRenderable id="c_org_jared_voscobo_domain_Player_name" property="name" z="VsyxyqAhGZi3G56a6FypehaFMxo="/>
                    <table:columnRenderable id="c_org_jared_voscobo_domain_Player_licenceNumber" property="licenceNumber" z="G9BNSMiHXvcGkFlyvlponcu5gSc="/>
                    <table:columnRenderable render="false" id="c_org_jared_voscobo_domain_Player_picture" property="picture" z="ZsX4ZqTmgPQtBtH5FhKxlB9uUZw="/>
                    <table:columnRenderable cellRenderer="org.jared.voscobo.web.PlayerPictureRenderer" id="c_org_jared_voscobo_domain_Player_pictureUrl" property="pictureUrl" z="user-managed"/>
                </table:tableRenderable>
        

        And finally I created a picture renderer.

        PlayerPictureRenderer
        /**
         * A cell renderer which display the player's picture
         * @author eric
         */
        public class PlayerPictureRenderer implements CellRenderer {
        
        	/* (non-Javadoc)
        	 * @see org.jared.voscobo.web.CellRenderer#render(javax.servlet.jsp.PageContext, java.lang.String, java.lang.Object, java.lang.Object)
        	 */
        	@Override
        	public void render(PageContext context, String propName, Object propValue, Object item) throws IOException {
        		JspWriter writer = context.getOut();
        		Player player = (Player)item;
        		String url = context.getServletContext().getContextPath()+"/players/picture/" + player.getId();
        		writer.print("<img src='"+url+"'/>");
        	}
        }
        

        You may have notice that I used a custom tag in renderableTable : <jared:cellRenderer rendererClass="$

        {renderer}

        " propertyName="$

        {column}

        " propertyValue="$

        {value}

        " item="$

        {item}

        " />
        This tag aims to simplify the CellRenderer instanciation.

        Hope all of this will help Roo users and Spring Roo Team !
        BTW let me tell you that Roo is fantastic (I love it). Keep the good work.

        Show
        Eric Taix added a comment - Stefan, It's quite easy to find a use case which needs this improvement ! For example i'm trying to create an application with roo which will manage volleyball championships. In my domain I have Teams, Players, and much more. Each player have properties like firstname, name, licence number AND a picture (BTW it was quite difficult to add a picture as a blob in my database, but it is another subject). Now I want to show player's picture while I'm browsing a team. Sorry I tried to add a screenshot but it failed : I'll try later I found a way to do it but not sure it is the best way ! I had to add a new property in Column: cellRenderer which defines a class name which implements CellRenderer renderableColumn ... <c:choose> <c:when test= "${empty columnProperties and empty columnLabels}" > <c:set var = "columnProperties" value= "${property}" scope= "request" /> <c:set var = "columnLabels" value= "${label}" scope= "request" /> <c:set var = "columnMaxLengths" value= "${empty maxLength ? 10 : maxLength}" scope= "request" /> </c:when> <c:otherwise> <c:set var = "columnProperties" value= "${columnProperties},${property}" scope= "request" /> <c:set var = "columnLabels" value= "${columnLabels},${label}" scope= "request" /> <c:set var = "columnMaxLengths" value= "${columnMaxLengths},${empty maxLength ? 10 : maxLength}" scope= "request" /> </c:otherwise> </c:choose> <c:choose> <c:when test= "${empty cellRenderers}" > <c:set var = "cellRenderers" value= "${cellRenderer} ," scope= "request" /> </c:when> <c:otherwise> <c:set var = "cellRenderers" value= "${cellRenderers}${cellRenderer} ," scope= "request" /> </c:otherwise> </c:choose> </c: if > </jsp:root> I had to modify the way how cells are rendered. renderableTable.tagx snippet ... <c:forTokens items= "${columnProperties}" delims= "," var = "column" varStatus= "num" > <c:set var = "columnMaxLength" value= "${lengths[num.count-1]}" scope= "request" /> <td> <c:set var = "renderer" value= "${fn:trim(renderers[num.index])}" /> <c:choose> <c:when test='${empty renderer}'> <c:choose> <c:when test= "${columnMaxLength lt 0}" > <spring:eval expression= "item[column]" var = "colTxt" /> </c:when> <c:otherwise> <spring:eval expression= "item[column]" var = "colTxt" /> </c:otherwise> </c:choose> <c:out value= "${fn:substring(colTxt, 0, columnMaxLength)}" /> </c:when> <c:otherwise> <c:set var = "value" value= "${item[column]}" /> <jared:cellRenderer rendererClass= "${renderer}" propertyName= "${column}" propertyValue= "${value}" item= "${item}" /> </c:otherwise> </c:choose> </td> </c:forTokens> ... Here is the CellRenderer interface: CellRenderer.java /** * A simple interface which declare a method to render a cell content in a table (not the cell itself) * @author eric.taix at gmail.com */ public interface CellRenderer { /** * Render a specific cell * @param context The current page context (use it to retrieve JspWriter or others things like current context path) * @param propName The property name * @param propValue The current property value * @param data The object itself which contains the property */ public void render(PageContext context, String propName, Object propValue, Object data) throws IOException; } How to use it ? In your list.jspx just add the cellRenderer property when needed like: list.jspx example <table:tableRenderable data= "${players}" id= "l_org_jared_voscobo_domain_Player" path= "/players" z= "PqLFQtt8v7NQKbggUS1BN2Cy504=" > <table:columnRenderable id= "c_org_jared_voscobo_domain_Player_firstName" property= "firstName" z= "tw3y9glYSpKR17g5VrELNcWx/2Q=" /> <table:columnRenderable id= "c_org_jared_voscobo_domain_Player_name" property= "name" z= "VsyxyqAhGZi3G56a6FypehaFMxo=" /> <table:columnRenderable id= "c_org_jared_voscobo_domain_Player_licenceNumber" property= "licenceNumber" z= "G9BNSMiHXvcGkFlyvlponcu5gSc=" /> <table:columnRenderable render= " false " id= "c_org_jared_voscobo_domain_Player_picture" property= "picture" z= "ZsX4ZqTmgPQtBtH5FhKxlB9uUZw=" /> <table:columnRenderable cellRenderer= "org.jared.voscobo.web.PlayerPictureRenderer" id= "c_org_jared_voscobo_domain_Player_pictureUrl" property= "pictureUrl" z= "user-managed" /> </table:tableRenderable> And finally I created a picture renderer. PlayerPictureRenderer /** * A cell renderer which display the player's picture * @author eric */ public class PlayerPictureRenderer implements CellRenderer { /* (non-Javadoc) * @see org.jared.voscobo.web.CellRenderer#render(javax.servlet.jsp.PageContext, java.lang. String , java.lang. Object , java.lang. Object ) */ @Override public void render(PageContext context, String propName, Object propValue, Object item) throws IOException { JspWriter writer = context.getOut(); Player player = (Player)item; String url = context.getServletContext().getContextPath()+ "/players/picture/" + player.getId(); writer.print( "<img src='" +url+ "'/>" ); } } You may have notice that I used a custom tag in renderableTable : <jared:cellRenderer rendererClass="$ {renderer} " propertyName="$ {column} " propertyValue="$ {value} " item="$ {item} " /> This tag aims to simplify the CellRenderer instanciation. Hope all of this will help Roo users and Spring Roo Team ! BTW let me tell you that Roo is fantastic (I love it). Keep the good work.
        Hide
        Ihor Kaharlichenko added a comment -

        I was just about to post my own solution, it doesn't involve any Java code creation, just modification of table.tagx & column.tagx, though I don't know the performance impact.

        The whole idea is to move rendering from table tag to column tag and repeatedly invoke column's <jsp:doBody /> for each row.

        I'll post my solution in my fork tomorrow.

        Show
        Ihor Kaharlichenko added a comment - I was just about to post my own solution, it doesn't involve any Java code creation, just modification of table.tagx & column.tagx, though I don't know the performance impact. The whole idea is to move rendering from table tag to column tag and repeatedly invoke column's <jsp:doBody /> for each row. I'll post my solution in my fork tomorrow.
        Hide
        Ihor Kaharlichenko added a comment -

        Just as I promised, here my merge request. Feedback is welcome.

        Show
        Ihor Kaharlichenko added a comment - Just as I promised, here my merge request. Feedback is welcome.
        Hide
        Ivan Alvarez added a comment -

        I'm trying to do a similar thing. I have a boolean value in the bean and want to display a green icon on one column when its true, and a red one when its not.

        Show
        Ivan Alvarez added a comment - I'm trying to do a similar thing. I have a boolean value in the bean and want to display a green icon on one column when its true, and a red one when its not.
        Hide
        Harald Walker added a comment -

        Ivan, look at table.tagx and how it handles date and calendar values. You could add boolean as type in column.tagx and then render it as you like in table.tagx.

        Show
        Harald Walker added a comment - Ivan, look at table.tagx and how it handles date and calendar values. You could add boolean as type in column.tagx and then render it as you like in table.tagx.

          People

          • Assignee:
            Unassigned
            Reporter:
            Scott Murphy
          • Votes:
            7 Vote for this issue
            Watchers:
            5 Start watching this issue

            Dates

            • Created:
              Updated: