Keep your dataTable clean with a custom popup

The basic idea is to output some data to a user in a table and allow them to take an action on each row individually. A fairly straightforward solution is to create a separate page to link to, passing the necessary row information along. If the action is simple enough, like a single checkbox, you could just embed the necessary component(s) in each row of the table. Too many components, however, can bloat the table and make the UI cumbersome to the user. Instead we can create a popup window to overlay our page, containing whatever components are needed, and activate it by a link embedded in our table. Passing the row information is a little trickier, but the result is a cleaner interface and a better user experience.

The example implementation below is done using Java with JSF, a little JavaScript, a little jQuery, and some CSS. The tag libraries being referenced are:

xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:t="http://myfaces.apache.org/tomahawk"

The page is constructed using two separate forms, one for the main components of the page, including the datatable, and the other for the popup. A little JavaScript and jQuery are used to show/hide the popup form and pass around the pertinent row information. Some CSS is used to shadow the main page components while the popup is active.

Our simple main form is:

<h:form id="someForm">
 
	<t:div id="actionDetailsDiv" styleClass="info" 
			rendered="#{not empty myAction.actionDetails}">
		<t:outputText escape="false" value="#{myAction.actionDetails}" />
	</t:div>
 
	<t:dataTable var="currentRow" 
			rowIndexVar="rowNum" 
			value="#{myAction.myData}" >
		<t:column>
			<f:facet name="header">
				<t:outputText value="row" />
			</f:facet>
			#{rowNum}
		</t:column>
 
		<t:column>
			<f:facet name="header">
				<t:outputText value="some colummn" />
			</f:facet>
			#{currentRow.someProperty}
		</t:column>
 
		<t:column>
			<f:facet name="header">
				<t:outputText value="action links" />
			</f:facet>
			<a href="javascript:showHiddenForm('#{rowNum}')">action link</a>
		</t:column>
	</t:dataTable>	
</h:form>

Here the row number (rowNum) has been passed to a piece of JS, but you could just as easily pass a property from the current row object. Also, actionDetails is a string on our backing action bean used to inform the user that the action has been taken on the appropriate data. This should be declared in the backing bean as an empty string and thus the containing div is not initially rendered.

Our hidden form is:

<h:form id="hiddenForm">
	<div id="backgroundDiv" class="fullscreen outer" />
 
	<div id="foregroundDiv" class="centerPopup inner">
		<p>Action Heading</p>
 
		<t:inputHidden id="hiddenInfo" forceId="true" />
 
 
		<t:commandButton value="Backing Action" actionListener="#{myAction.backingAction}"/>		
		<t:commandButton onclick="hideHiddenForm()" value="Cancel"/>
	</div>
</h:form>

To prove that the data in the hidden field is being populated correctly, change the inputHidden component to inputText.

The CSS referenced above is:

div.fullscreen {
	position:absolute;
	top:0;
	left:0;
	width:100%;
	height:100%;
}
 
div.centerPopup {
	position: fixed;
	top: 50%;
	left: 50%;
	height: 200px;
	width: 500px;
	overflow: auto;
	margin-top: -100px;
	margin-left: -250px;
}
 
div.outer {
	filter: alpha(opacity=50);
	-moz-opacity: 0.5;
	opacity: 0.5;
	background-color: #D8D8D8;
	z-index: 2000;
}
 
div.inner {
	background-color: #000000;
	z-index: 2001;
}
 
p.inner {
	color: white;
}
 
div.info {
    text-align: center;
    font-weight: bold;
    border-top: 1px solid;
    border-bottom: 1px solid;
}

Most of the CSS is pretty straightforward. The “outer” class is setup to make a div gray and transparent in most browsers and “centerPopup” creates a statically sized div in the middle of the screen. The crucial part of the CSS is the z-index used by the “inner” and “outer” classes. The higher the z-index, the higher on the stack the element is, thus to show the “inner” popup over the somewhat transparent “outer” fullscreen overlay, the popup’s z-index must be higher. Both z-indexes are set arbitrarily high to ensure other elements don’t interfere.

The JavaScript/jQuery to show and hide the hidden form is:

function hideHiddenForm() {
	$("#hiddenForm").hide();
}
 
function showHiddenForm(selection) {
	//set the value of the hidden field
	var thingy = document.getElementById("hiddenInfo");
	thingy.value=selection;
 
	$("#hiddenForm").show();
}
 
$(document).ready(function() {
	//initially hide the popup form
	hideHiddenForm();
});

Note showHiddenForm takes a single input and sets this as the value of the hiddenInfo element. Also, we need to hide the popup form when the page is first loaded.

In our backing bean (MyAction) the backingAction method needs to find the hidden field and do something with the value. Here the value is passed to the actionDetails property forcing actionDetailsDiv above the table to render when the page is rerendered. Typically we would actually act on the value instead of merely redisplaying it.

public void backingAction(ActionEvent event) {
	UIComponent link = (UIComponent) event.getSource();
	for (UIComponent divChild : link.getParent().getChildren()) {
		if (divChild.getAttributes().get("id").equals("hiddenInfo")) {
			String rowNum = (String) divChild.getAttributes().get("value");
			setActionDetails("Action taken on row " + rowNum);
			break;
		}
	}
}

The hidden form (popup) can become as complicated as you need and allows your main form to remain clean. If anyone knows an easier way to pull the value out of the hidden element, let me know.

One comment

Comments are closed.