Tuesday, August 7, 2007

Indexed properties and Highlighting a row in a table on click of it

Tutorial Links:

http://faq.javaranch.com/java/IndexedProperties

Problem Description:

We have a table with 4 columns
comment (description – text box)
commenttype (drop-down)
user name (plain text – bean:write)
date (plain text – bean:write)

This is how the table looks

Comment CommentType UserName Date
textbox Drop-down Plain text Plain text

From the search screen, when user clicks NEW Request, we take him to the new screen (MyRequest.jsp) where user will see the above table as specified. But when user comes via VIEW รจ EDIT (same page - MyRequest.jsp) mode then he should see everything (all the 4 columns) as plain text (technically – bean:write tags) and not as textbox and drop-downs.

Also user should be able to add multiple rows to the table and save it to the database. User should be able to select a row (highlight a row) and delete it. Also deleted rows should not be displayed on screen.

Here we had implemented indexed properties, one of the features in struts with which we can add more than one row to a table on JSP Page and send it to java layer.

Implementation Details:

The table has an ADD button and a DELETE button at top right corner. When user clicks the ADD button an action class is invoked where we set the mandatory attributes like

1. Description (comment)
2. CommentType
3. user name
4. date
5. systemstatuskey
6. sequencenumber
7. CommentTypeKey (Associated with comment type selected)

These are the six fields in comment table in database. First 4 fields are directly related to user input from UI. Systemstatuskey field is used to identify whether the record is active (1) or deleted (3). All records are active by default. Sequence number is unique for every row that’s added to the table.

Action class:

public class AddRequestCommentAction extends DispatchAction{

public ActionForward addRequestComments(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
try{

List<CommentsBean> requestCommentList = objRequestForm.getRequestCommentList();

CommentsBean objCommentsBean = new CommentsBean();
if(requestCommentList == null ) {
requestCommentList = new ArrayList<CommentsBean>();
}

objCommentsBean.setSequenceNumber(-1-requestCommentList.size());
objCommentsBean.setDescription(ABCApplicationConstants.COMMON_EMPTYSTRING);
objCommentsBean.setCommentType(ABCApplicationConstants.COMMON_EMPTYSTRING);
objCommentsBean.setCreationUserId(gtemUser.getUserId());
objCommentsBean.setCreationDate(DateUtility.todaysDate());
objCommentsBean.setSystemStatusKey(Integer.parseInt(ABCApplicationConstants.GTEMCONSTANTS_GTEMSTATUS_ACTIVE));
requestCommentList.add(objCommentsBean);
objRequestForm.setRequestCommentList(requestCommentList);

}
catch (Exception ex) {
Logger.Log(this.getClass(), "In execute->>>Exception:"+ex.getMessage(), Logger.ERROR);
}

return mapping.findForward(ApplicationConstants.CONSTANTS_SUCCESS);
}

Description:

objCommentsBean.setSequenceNumber(-1-requestCommentList.size());

The above line might have left you to wonder about negative values getting stored in DB. Actually it’s just for UI purpose. When the main SUBMIT happens in DAO layer we will be getting the count of sequencenumber from request table and adding one to it before we save it.

The way we display the rows differs from NEW mode to VIEW ==> EDIT mode.

All the 4 columns should be displayed as plain text (bean:write) for VIEW ==> EDIT mode and for NEW mode we need to show textbox, drop-down and plain texts (bean:write).

We use ‘sequence number’ property to control this requirement. As we can see in the action class we set sequencenumber with negative value, which means for a new request, when we add request comments the sequence number gets incremented in negative range. In JSP we use logic:greaterThan & logic:lessThan tags to control the way we display the rows.

We have a requirement to delete the rows. So when we delete a particular row (In both the modes) we invoke a JS where we change the SystemStatusKey to 3 to indicate it is deleted and hide the row from table on screen.

Next time when we fetch the rows from database the deleted row’s (record) systemstatuskey will be 3 indicating it’s deleted. Such deleted row shouldn’t be shown to the user.

We control it in JSP using logic:equal and logic:notEqual tags.

RequestForm:

For indexing to work we need to modify getRequestCommentBean in the below way.

public CommentsBean getRequestCommentBean(int index) {
if(this.getRequestCommentList() == null ) {
this.requestCommentList = new ArrayList<CommentsBean>();
}
while(index >= this.requestCommentList.size())
{
this.requestCommentList.add(new CommentsBean());
}

return (CommentsBean) requestCommentList.get(index);
}

MyRequest.jsp

We use the style attribute in <tr> tag to control the displaying of the row on screen.
We need to index those properties that we need to persist. Like we need to retain the value of systemstatuskey. So we include it in hidden variable and used the attribute indexed="true"

<!-- start Request Comments -->
<table id="tblComment" class="tableElem" style="position:relative;margin-top:5px;" cellspacing="0" cellpadding="2" width="100%" minmax="true" default="0">
<caption class="chrome"><span style="position:relative;float:left;"><bean:message key="request.label.commentstoABC" bundle="abc"/></span> <span
style="position:relative;float:right;"><img id="minmax2" src="../images/chrome_minimize.gif" hide="yes"></span></caption>
<thead>
<tr>
<td id="deleteComment" class="rmt-toolbar" align="right" colspan="4">
<a href="#p" id="delete" class="actionButtonUp" onclick="deleteSelectedRow('CommentTable');">
<bean:message key="common.btn.Delete" bundle="abc"/>
</a>
<a href="#P" class="actionButtonUp" id="addComment" onclick="document.forms[0].action='addRequestComment.do?method=addRequestComments';document.forms[0].submit();"/>
<bean:message key="common.btn.add" bundle="abc"/>
</a>
</td>
</tr>
<tr>
<th width="55%"><bean:message key="common.label.comment" bundle="abc"/></th>
<th width="15%"><bean:message key="common.label.commentType" bundle="abc"/></th>
<th width="20%"><bean:message key="common.label.user" bundle="abc"/></th>
<th width="10%"><bean:message key="common.label.date" bundle="abc"/></th>
</tr>
</thead>

<table id="CommentTable" class="tableElem" cellspacing="0" cellpadding="2" width="100%">
<tbody>

<logic:present name="RequestForm" property="requestCommentList">
<logic:iterate id="requestCommentBean" name="RequestForm" property="requestCommentList">
<logic:greaterThan name="requestCommentBean" property="sequenceNumber" value="0">
<logic:notEqual name="requestCommentBean" property="systemStatusKey" value="3">
<tr id="srch_r1" clickable="yes" style="display:visible" >

<!--
In this case all the values are displayed as plain text, since this is an EDIT ==> VIEW mode
-->

<!-- systemstatuskey should be the first element, as we are setting the systemstatuskey to 3 on delete, refer request.js.deleteSelectedRow(); -->

<html:hidden name="requestCommentBean" property="systemStatusKey" indexed="true" />
<html:hidden name="requestCommentBean" property="commentTypeKey" indexed="true" />

<td onClick="clicked('CommentTable',this);" width="55%">
<bean:write name="requestCommentBean" property="description" />
</td>
<td onClick="clicked('CommentTable',this);" width="15%">
<bean:write name="requestCommentBean" property="commentType" />
</td>
<td onClick="clicked('CommentTable',this);" width="20%">
<bean:write name="requestCommentBean" property="creationUserId" />
</td>
<td onClick="clicked('CommentTable',this);" width="10%">
<bean:write name="requestCommentBean" property="creationDate" />
</td>
</tr>
</logic:notEqual>

<logic:equal name="requestCommentBean" property="systemStatusKey" value="3">
<tr id="srch_r1" clickable="yes" style="display:none" >
<!-- systemstatuskey should be the first element, as we are setting the systemstatuskey to 3 on delete, refer request.js.deleteSelectedRow(); -->

<html:hidden name="requestCommentBean" property="systemStatusKey" indexed="true" />
<html:hidden name="requestCommentBean" property="commentTypeKey" indexed="true" />

</tr>
</logic:equal>
</logic:greaterThan>

<logic:lessThan name="requestCommentBean" property="sequenceNumber" value="0">
<logic:notEqual name="requestCommentBean" property="systemStatusKey" value="3">
<tr id="comm_r7" clickable="yes" style="display:visible" >
<!-- systemstatuskey should be the first element, as we are setting the systemstatuskey to 3 on delete, refer request.js.deleteSelectedRow(); -->

<html:hidden name="requestCommentBean" property="systemStatusKey" indexed="true" />

<td width="55%" onClick="clicked('CommentTable',this);" >
<html:textarea rows="3" cols="1" name="requestCommentBean" indexed="true" size="120" property="description" maxlength="2047" style="margin-right:6px;" styleClass="rmt-formTxt" />
</td>
<td width="15%" onClick="clicked('CommentTable',this);" >
<html:select size="1" property="commentTypeKey" name="requestCommentBean" indexed="true" styleClass="rmt-formTxt" style="margin-right:6px;" >
<html:option value="">Comment Type</html:option>
<logic:present name="RequestForm" property="lstRequestCommentTypes">
<bean:define id="row" name="RequestForm" property="lstRequestCommentTypes" />
<html:options collection="row" property="key" labelProperty="display"/>
</logic:present>
</html:select>
</td>
<td onClick="clicked('CommentTable',this);" width="20%">
<bean:write name="requestCommentBean" property="userName"/>
</td>
<td onClick="clicked('CommentTable',this);" width="10%">
<bean:write name="requestCommentBean" property="creationDate" />
</td>
</tr>
</logic:notEqual>

<logic:equal name="requestCommentBean" property="systemStatusKey" value="3">
<tr id="comm_r7" clickable="yes" style="display:none" >
<!-- systemstatuskey should be the first element, as we are setting the systemstatuskey to 3 on delete, refer request.js.deleteSelectedRow(); -->

<html:hidden name="requestCommentBean" property="systemStatusKey" indexed="true" />
<html:hidden name="requestCommentBean" property="commentTypeKey" indexed="true" />
</tr>
</logic:equal>
</logic:lessThan>
</logic:iterate>
</logic:present>
</tbody>
</table>
</table>
<!-- end of RequestComments -->

When one of the rows is clicked, we need to highlight that row. Here is the JS code for it.

function clicked(table_name, elem )
{
var rowElem = elem.parentNode;
var tbl = document.getElementById(table_name);
for (var i=0; i<tbl.tBodies[0].rows.length; i++)
{
if (tbl.tBodies[0].rows[i].className.indexOf("rmt-rowClick") != -1)
{
tbl.tBodies[0].rows[i].className="";
}
}
rowElem.className = "rmt-rowClick";
}

Here is the JS code to delete the row from UI and set systemStatusKey value to 3.

//This function deletes the row and changes systemstatuskey to 3
// Note: keep systemstatuskey as first element of the row.
function deleteSelectedRow(TABLE_NAME)
{
hasLoaded=true;
if (hasLoaded) {
var tbl = document.getElementById(TABLE_NAME);
for (var i=0; i<tbl.tBodies[0].rows.length; i++) {
if (tbl.tBodies[0].rows[i].className.indexOf("rmt-rowClick") != -1) {
var rowElem = tbl.tBodies[0].rows[i];
var elem=rowElem.getElementsByTagName("input")[0];
elem.value=3;

tbl.tBodies[0].rows[i].style.display='none';
break;
}
}
}
}