Dynamically Expanding XPage viewPanel Rows on mouseover

In this article I will demonstrate a technique for dynamically expanding and contracting viewPanel rows. This provides a nice clean interface for the user and allows content to be made visible as necessary. This article also highlights many of the core jQuery capabilities.

Problem

When moving from a notes client application to the web often times view columns can be large and unwieldy

A boring notes view
A boring notes view
Unwieldy data view on an XPage
Unwieldy data view on an XPage

Solution

What we are going to do is not only neaten up the display but provide some simple user interactivity to allow access to data as needed. Here is a quick video of the capability in action.

Demonstration

You can see a working example of the capability on the xomino site. In Firefox and Chrome the fade effect look just fine but due to issues with alpha filters fade does not work consistently in IE.

How does it work?

As you can see from the pictures above, once we put our view into an xPage and display it on the web, the entire contents of the data field are displayed on the page. This can make for an irritating session with customer who don’t want to have to scroll page after page to find their information but want the same view to be available through the notes client.

Once solution would be to determine the medium through which your data is being viewed and truncate it accordingly. But I went another route and made a compromise where the data is available if the users wants it (adding functionality to the page without them having to click and open the document) but does not take up a large amount of space.

I added jQuery and jQueryUI (CSS not code) to my database through the WebContent folder and to my XPage like this

	<xp:this.resources>
		<xp:script src="js/jquery-1.7.2.min.js" clientSide="true"></xp:script>
		<xp:styleSheet
			href="css/custom-theme/jquery-ui-1.8.19.custom.css">
		</xp:styleSheet>
	</xp:this.resources>

I started out by creating a data source of my view and dragging the fields to the XPage, creating a viewPanel on the form. I then “fixed” the width of someData column:

		<xp:viewColumn columnName="somedata" id="viewColumn4">
			<xp:this.facets>
				<xp:viewColumnHeader value="Somedata" xp:key="header" style="width:300px"
					id="viewColumnHeader4">
				</xp:viewColumnHeader>
			</xp:this.facets>
		</xp:viewColumn>

I then created a scriptblock on the XPage after the viewPanel (controls > other > core controls >scriptblock). Within all Properties > data > value I added the following code….

			 $("#[id$='viewPanel1'] tr td:nth-child(4)").each(function (i) {
			 	if ($(this).innerHeight()>=100){
				 	$(this).append('<span class="ui-icon ui-icon-arrowthick-1-s"></span>').children(":first").wrap('<div class="wrapper" />')
			 	}
		    });

and that on its own made quite a difference….

modified viewPanel
modified viewPanel

This is what the jQuery code just did

  • Select all the 4th column Table cells in the viewPanel and cycle through each of them
 //select all elements (one in this case) where the id ends with (id$=) viewPanel1
 //within that select all rows (tr)
 //within that select all the table cells (td)
 //within that select only the cells which are the 4th child element (:nth-child(4))
 //cycle through each of them (.each(function(i)) where i is the nth item in the cycle
 $("#[id$='viewPanel1'] tr td:nth-child(4)").each(function (i)
  • Because we have fixed the column width we are able to accurately ascertain the height of the cell contents on the page
  • As we then cycle through the viewPanel TD cells we determine if the cell contents are larger than 100pixels. If they are then we surround the contents with a wrapper with a fixed height.
 //$(this) means each jQuery object (representing the TD as we cycle through the column
 //.append is as it sounds - append to the end this SPAN (with the arrow in it)
 //.children(:first) selects the first SPAN within the TD we are working on and
 //.wrap means surround the SPAN with the DIV
	$(this).append('<span class="ui-icon ui-icon-arrowthick-1-s"></span>').children(":first").wrap('<div class="wrapper" />')

ok trying to visualize what is going on we start with something which looks like this

<td>
  <span #1>
  </span>
</td>

We .append() another span

<td>
  <span #1>
  </span>
  <span #2>
  </span>
</td>

We then .wrap() the :first Child of the TD with a Div

<td>
  <div>
    <span #1>
    </span>
  </div>
  <span #2>
  </span>
</td>

And this is what we have made (looking at it through FireFox FireBug

The new Table Cell viewed in Firebug
The new Table Cell viewed in Firebug

You have to admire the ability to “chain” jQuery objects and methods together 🙂

Styling the wrapper

We add the following stylesheet to the XPage to ensure that the DIV .wrapper class keeps the display in check

	<style>
		.wrapper {
			height: 50px;
			overflow: hidden;
			padding-bottom: 5px;
		}
	</style>

Animating the Table Rows

So once we now have our reformatted table rows they look better but anything over 50px cannot be “read” as it is hidden.

What we are going to do is add an mouseenter and mouseleave event to the DIV class=wrapper we just added inside our table cell.

Here’s the breakdown in English and the code beneath it

on mouse enter

  • Select all table rows which are NOT the one we are currently over
    • Fade the whole row to 20%
  • Get the SPAN next in the DOM below the wrapper DIV (the one with the down arrow in it)
    • Toggle OFF the down arrow class (ui-icon-arrowthick-1-s)
    • Toggle ON the up arrow class (ui-icon-arrowthick-1-n)
  • Animate the height of the TD to match the height of the .wrapper

on mouse leave

  • Select all table rows which are NOT the one we are currently over
    • Fade the whole row back to 100%
  • Get the SPAN next in the DOM below the wrapper DIV (the one with the up arrow in it)
    • Toggle ON  arrow class (ui-icon-arrowthick-1-s)
    • Toggle OFF the up arrow class (ui-icon-arrowthick-1-n)
  • Animate the height of the TD to match the initial height of the .wrapper
			initHeight = $('.wrapper').height();

		    $('.wrapper').bind('mouseenter', function() {
		        $("#[id$='viewPanel1'] TR:gt(0)").not($(this).closest('tr')).stop(true, true).fadeTo('normal', 0.2);
		        $(this).closest('Div').next('span').toggleClass('ui-icon-arrowthick-1-n').toggleClass('ui-icon-arrowthick-1-s')
		        $(this).stop(true, true).animate({
		            height: this.scrollHeight
		        });
		    });
		    $('.wrapper').bind('mouseleave', function() {
		        $("#[id$='viewPanel1'] TR").not($(this).closest('tr')).stop(true, true).fadeTo('normal', 1);
		        $(this).closest('Div').next('span').toggleClass('ui-icon-arrowthick-1-n').toggleClass('ui-icon-arrowthick-1-s')
		        $(this).stop(true, true).animate({
		            height: initHeight
		        });
		    });

In summary

And there you have it. We have looked at a broad range of jQuery capabilities and brought them all together to create a great looking interface for the end user. All these capabilities are part of the jQuery core and I only used jQueryUI to get the cute up and down arrows. If you wanted to use an icon there is nothing stopping you removing the jQueryUI dependency.

We have looked at jQuery Selectors

  • attribute selector $(“#id$=
  • class selector $(‘.wrapper
  • :nth-child
  • :first

We have looked at DOM traversing and manipulation techniques

  • .children()
  • .next()
  • .closest()
  • .not()
  • .append()
  • .wrap()
We have looked at attaching events to DOM elements
  • .bind()
We have looked at object properties
  • .innerHeight()
  • .height()
We have looked at multiple visual manipulation capabilities
  • .fadeTo()
  • .animate()
  • .toggleClass()
  • .stop()

Demonstration

You can see a working example of the capability on the xomino site.

4 thoughts on “Dynamically Expanding XPage viewPanel Rows on mouseover

  1. I’m seeing some weird behavior in FF at least. If I highlight the first entry in your demo (AACNPP), works as expected, mouse over the second entry(AAGBUF), works as expected, mouse over the third(ADPRRC) and things kind of flicker a bit and I end up having highlighted row id APMOLM – I can reproduce repeatedly

    Otherwise, nice demo. I fall into the category of someone who would want this triggered by a manual action, not simply a mouseover. So making that an option would help greatly I think

    • Sounds like a browser memory issue to me but please let me know if it continues…..

      Adding a clickable link would be easy, instead of binding to mouseover you’d bind to the onclick event of a button probably on he bottom bar where the arrow is currently

    • ahhhh I figured out why

      cos even though you move onto #3 – when #2 collapses your mouse is over #4

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s