A Flexible View Control for XPages Part 10 – Multiple Views Using Tabs

If you’ve been a Domino Developer long enough, then you remember when embedded views were added to Lotus Notes (version 5?) and how it turned your world upside down with the information you could easily make available to users on one form.

Combining The Flexible View Control with Bootstrap tabs makes adding this type of functionality to your XPages application incredibly easy. The demo below uses the used car database to display a few different types of large SUVs on one XPage in different tabs. Each tab is sourced from the same view that contains 2.2 million documents but is categorized creatively to easily drill down to the desired data.

Now, you don’t have to use Bootstrap tabs (or any tabs for that matter) – I do since I’m using Bootstrap as my layout framework and this page has the standard out-of-the-box tab setup with a tab strip navigator and tab. The entire page layout looks like this:

<div class="level0-flex-container">
		<div class="level0-flex-item">
			<xc:ccNav></xc:ccNav>
		</div>
		<div class="level0-flex-view">
			<div class="level0-flex-item" style="padding-top:10px;background:#ddd">
				<ul class="nav nav-tabs" role="tablist">
					<li role="presentation" class="active" style="margin-left:25px;">
						<a href="#tahoe" aria-controls="tahoe" role="tab" data-toggle="tab"
							class="tahoe">
							Chevrolet Tahoe
						</a>
					</li>
					<li role="presentation">
						<a href="#yukon" aria-controls="profile" role="tab" data-toggle="tab"
							class="yukon">
							GMC Yukon
						</a>
					</li>
					<li role="presentation">
						<a href="#escalade" aria-controls="messages" role="tab"
							data-toggle="tab" class="escalade">Escalade</a>
					</li>
					<li role="presentation">
						<a href="#expedition" aria-controls="settings" role="tab"
							data-toggle="tab" class="expedition">Expedition</a>
					</li>
				</ul>
			</div>
			<div class="tab-content"
				style="">
				<div role="tabpanel" class="tab-pane active flex-tab" id="tahoe"
					style="">
					<Flexible View Control>
				</div>
				<div role="tabpanel" class="tab-pane flex-tab" id="yukon"
					style="">
					<Flexible View Control>
				</div>
				<div role="tabpanel" class="tab-pane flex-tab" id="escalade"
					style="">
					<Flexible View Control>
				</div>
				<div role="tabpanel" class="tab-pane flex-tab" id="expedition"
					style="">
					<Flexible View Control>
				</div>
			</div>
		</div>
</div>

Flexbox, again

If you read Part 8 of this series, then you probably remember how important of a role Flexbox plays in the layout of the Flexible View Control. Getting the FVC to size properly in a Bootstrap tab panel is no different. We add a little extra CSS to make those tab panels flex so the view control fills up the available space in each tab:

.tab-content {	
	flex:1;
	position:relative;
	flex-basis:auto;
	display:flex;
	flex-direction:column;
}
.tab-pane.flex-tab {
	display:flex;
	flex:1;
	position:relative;
	flex-basis:auto;
	flex-direction:column
}
.tab-pane.flex-tab {
	display:none !important;
}
.tab-pane.flex-tab.active {
	display:flex !important;
}

Now that the page is laid out we can add our view controls to each tab panel. The image below shows the FVC for the first tab, although the other three tabs are essentially configured the same.

Custom Control properties for the ‘Tahoe’ tab FVC

Remember: thisView must be unique! This is what allows multiple views to be added to one XPage.

An important takeaway from the highlighted areas in the screenshot above: loadOnInit: false. Setting loadOnInit directly in the control will override the value defined in the View Definition. We set it to false here because we want to build the table manually, and even though the page has four views, we only want to fetch the data for the ‘active’ tab when the page loads:

$( document ).ready(function() {
	TahoeView.build({
				params: 'category=make:Chevrolet:model:Tahoe&view=xspAll'
	});
}
Remember: The thisView property of the FVC is used to construct a client-side JavaScript object at run-time.
  • When loadOnInit: true the control will automatically execute the .build() function.
  • When loadOnInit: false the DataTable can be built manually by calling [thisView].build().
  • Refresh a view at anytime by calling [thisView].build()

So how do we load the data for the other tabs? In Part 7 – Modals & Picklists, we talked about needing to wait until a modal was ‘shown’ before building the view in a Picklist to make sure that the DataTable sized correctly. Similarly here, to load a view in a tab we need to wait until that tab is ‘shown’. To do that, again, we tap into a Bootstrap event:

$('.tahoe').on('shown.bs.tab', function (e) {
	// Even though this view is built on page load
	// the shown property can be set so the view refreshes
	// when this tab is made active again
		TahoeView.build();
});

$('.yukon').on('shown.bs.tab', function (e) {
		YukonView.build({
			category:'make:GMC:model:Yukon',
			params:'view=xspAll'}
		);
});
	
$('.escalade').on('shown.bs.tab', function (e) {
		EscaladeView.build({
			'category':'make:Cadillac:model:Escalade',
 			params:'view=xspAll'});
});
	
$('.expedition').on('shown.bs.tab', function (e) {
		ExpeditionView.build({
			category:'make:Ford:model:Expedition',
			params:'view=xspAll'});
});

What about those .build() parameters?

As you probably noticed in the snippets above, we didn’t define the keys/category for the views within the control. We could have, but the FVC makes it possible to pass options to the .build() command. This is a huge part of what makes the Flexible View Control so ‘flexible’. More in this in the next blog post!


A Flexible View Control for XPages Part 9 – Using Scroller for Large Views

Up to this point, the examples I’ve created to demonstrate the capabilities of the FVC have used fairly small sets of data. In a real-world production scenario there is going to be a need to display datasets of various sizes, including thousands of records. So how does the FVC scale for larger sets of data?

Understanding DataTables

Comprehending what is going on behind the scenes of DataTables is vital to developing a strategy to handle large sets of data efficiently. Similar to XPages, DataTables has a “lifecycle” where a series of events occur in the initialization of the table. Previously, I created a bunch of “pure” DataTables demos, one of which highlights the various phases (callbacks) of the lifecycle (turn on your developer console):

http://demos.xpage.me/xpages/mwlug/datatables.nsf/demoRestLifecycle.xsp

DataTables Lifecycle

The image above shows browser consoles statements to demonstrate what the lifecycle looks like for a DataTable with 25 records. Take a close look and you’ll see that drawCallback executes multiple times. This means DataTables has drawn the table twice as part of the initialization. A good comparison to this in the XPages lifecycle is the renderResponse phase.

Now, let’s focus on the rowCallback and createdRow callbacks. In this table with 25 rows each of these is called every time DataTables processes a row of data. Normally, you are likely to only use one or the other – both are included here for demonstration purposes. Regardless of which callback you use, the code that resides there will be executed for each row. If your DataTable is replicating the functionality of a Notes View, then you’re likely to have click and double-click events at a minimum.

Now imagine you have a view with a large amount of data – thousands of records, for example. Your rowCallback is now executing THOUSANDS of times, resulting in slower rendering of your DataTable and negatively impacting the user experience. YIKES!

Large Datasets – Notes Views vs. Web Views

Anyone that has used Lotus Notes and Domino for years has been spoiled by having views with thousands (or millions) of records, nicely indexed, and extremely performant as you click on columns to re-sort or search the view based on a key. This is one thing that Notes does really, really well, but sending thousands to millions of rows to a view in a web application can seem like a fool’s errand.

While I certainly continue maintaining large Notes views on the back-end, I use categories, keys, and server-side searching to make it easy for users to target the information they need so only a small subset of information is sent to the client-side web view.

I feel like my magic number is about 5000 records or less. Any more than that and users are waiting too long. Obviously, there are going to be use cases where that is not possible and there are other factors to consider, such as server processing power and bandwidth between the client and server.

Pagers

Paging is one way to deal with large amounts of data and can be turned on with a simple paging: true as part of the initialization options within DataTables. But let’s say you have 4000 records and 50 records show up on each page. This results in 80 pages. Do you want to page through 80 pages of data? Do you want your users to page through 80 pages of data? NO!

Enter Scroller

I’ve said this before in previous posts and presentations but it bears repeating again – the more I use DataTables and the more I have challenging business requirements that at first glance seems like I might have to hack a solution together, ultimately DataTables has a fairly simple way to achieve the desired result. Displaying large amounts of data in a performant way is no different.

DataTables has a plugin called Scroller that does a great job of managing large amounts of data. It does so by only “drawing” the rows that are visible within the scroll body of the table plus extra rows so when the user starts scrolling the table is able to respond quickly. As the user scrolls, the scroller plugin draws more rows. All of the features of DataTables still work as you expect them to – filtering, sorting, etc. Technically, DataTables is using paging functionality behind the scenes to manage the data,

Enabling Scroller in the FVC

The Flexible View Control has the ability to utilize Scroller. Do the following to turn it on:

  1. Make sure you are loading the Scroller plugin resources. I’m loading the plugin via a CDN in my theme:
<resources>
	<styleSheet
			href="https://cdn.datatables.net/scroller/2.0.0/css/scroller.dataTables.min.css">
	</styleSheet>
</resources>
<resource>
	<content-type>application/x-javascript</content-type>
		<href>https://cdn.datatables.net/scroller/2.0.0/js/dataTables.scroller.min.js</href>
</resource>

2. You must have showFixedHeader: true on your FVC. Scroller will not work properly without fixed headers being turned on.

3. Set scroller: true in the FVC custom properties.

showFixedHeader must be ‘true’ for scroller to function properly

Demos

This page contains multiple demos that illustrate the dramatic effect scroller has on loading large amounts of data.

Focusing on the first line of the page above, these two sets of console statements highlight the significant difference in loading time between two tables that have the same ~5000 records, one without scroller, one with scroller.

table builds in ~11 seconds WITHOUT scroller enabled
table builds in ~1 second WITH scroller enabled
  • The first thing we notice is the data is retrieved from the server in the same amount of time in both scenarios.
  • In the first console image where scroller is not enabled, every row is being processed by DataTables at initialization, which drastically slows down the table build vs. the second console image, where scroller is enabled.
  • Finally, we see a dramatic difference in the build times between the two tables. 11 seconds is a lifetime compared to 1 second and as tables get bigger the 10-second difference will grow in magnitude.

Some Minor Side-Effects

Using scroller does come with a couple of side-effects, but they are fairly minor and shouldn’t have much of an impact.

Since the scroller plugin relies on precise calculations to build the table rows, it forces each row to be the same height. This is really only a problem if you have one column that has one or more records that have extraordinarily long data. Scroller will stretch that column out so all of the data can fit within that row height.

Luckily, the FVC View Definition makes it easy to overcome this. All you need to do is explicitly define a max width for your column and check the “Hide Overflow” box:

The result is your column tries to honor the width you’ve assigned and any text that exceeds that width is truncated with an ellipsis:

The other noticeable side-effect is simply cosmetic and can easily be overridden with CSS. When a table does not have enough data to fill the table body, scroller fills the white space with a background image:

If your users don’t like it, you can use the following CSS to override the background to be whatever you want:

div.dts div.dataTables_scrollBody {
     background:#fff;
}

Conclusion

The numbers above are pretty clear – using scroller will greatly improve the performance of the Flexible View Control when your table has a large amount of data and a difference can be seen with as few as 2-300 records. Every view in my application now has scroller enabled.