Join me at CollabSphere 2021!

CollabSphere, perennially a great conference, will be held virtually for the 2nd year in a row from Oct. 19th – Oct. 21st and I am fortunate to be presenting a session this year.

DEV115: Using XPages and Bootstrap tabs to build a (Monolithic) Single-Page Application (of Micro Front-ends)
Time: Tuesday 10/19 @ 2PM EST

While the title is a bit wordy, it is meant to be a little cheeky … This session will demonstrate how at Mutual Boiler Re we use Bootstrap tabs and jQuery to provide our XPages applications with a structure that allows multiple pages, views, and documents to be opened within one browser tab, essentially creating a Single Page Application that mimics the Notes Client in a browser.

Thank you to Richard and the Collabsphere team for this opportunity. I look forward to seeing you there!


Kudos to HCL

Last week I received a nice little gift in my inbox … an email from HCL with an update to an SPR I submitted late last year indicating the defect identified in that SPR was RESOLVED.

This bug was incredibly annoying for me and the workaround initially suggested didn’t make sense… and even though I was able to reproduce the issue in each version and had to push a bit to get this issue upgraded to an SPR, ultimately HCL listened to the customer and resolved the issue.

While not listed above, according to the release notes for Domino 10.0.1 FP7, this defect has also been resolved in that fix pack.


Reader question – How does the Flexible View Control handle response documents?

Recently, I received a comment on a blog post asking how the FVC handles response documents:

One question I have is how best to deal with response documents in views, I am not sure if I have missed something in the tutorials but I am unsure how to render responses or if the control handles views with responses?

In building the control, response documents were never even a consideration for me since we don’t use them in our environment so the Flexible View Control does not contain specific functionality for handling response documents. However, because the framework was built to be as flexible and configurable as possible, the tools provided make creating that functionality incredibly easy. Based on the comment above I created a demo to prove that:

http://demos.xpage.me/demos/datatables-xpages-bootstrap.nsf/viewGeneral.xsp?viewdef=response-docs

Building the Demo

For this demo I created a new used car database and added some records from the original used car database. In addition to the listing form that already existed for the main docs, I added a response form and response to response form so I could add comments to listings and comments to comments:

response form

On both the response and response to response forms the ID field from the original document is being inherited to have a common value to link all of the documents besides docids, but it’s not really necessary to make the demo function properly.

response to response form

Notes View Configuration

To get the View set up I need to make sure that Show response documents in a hierarchy is turned on and add the comments and parent_docid columns.

Note: No column is set to “show responses only”.

After adding a couple of responses in the Notes Client the back-end view looks like this:

The View Definition

In the View Definition I’m setting the columns for the responses to hidden because I don’t need that data to display. We *do* need the comments column to display but you will see momentarily how that will be done.

The REST Service

Another important step is making sure the REST service being utilized to serve the data contains the appropriate document hierarchy information. Since we are using the standard <xe:viewJsonService>, the system columns can be configured to include the position data in addition to the unid. By default, all of the columns are enabled but I prefer to remove the ones I don’t need to cut down on payload size.

Now we have the data we need:

Configuring the Flexible View Control

So after all this setup we still haven’t actually displayed anything with the control yet. How do we go about doing that? Before adding functionality to create the response hierarchy in the control the response docs look like standard docs, but with little information:

The Callbacks

The first step I take to build the response hierarchy is adding a createdRow callback function to the control. Normally, I would probably use a rowCallback out of habit but in the process of tinkering around, I moved my rowCallback code into the createdRow callback. Here is what the DataTables documentation says about each:

rowCallback
This callback allows you to ‘post process’ each row after it have been generated for each table draw, but before it is rendered into the document. This means that the contents of the row might not have dimensions ($().width() for example) if it is not already in the document.

This function might be used for setting the row class name or otherwise manipulating the row’s tr element (although note that createdRow can often be more efficient).

createdRow
This callback is executed when a TR element is created (and all TD child elements have been inserted), or registered if using a DOM source, allowing manipulation of the TR element.

This is particularly useful when using deferred rendering (deferRender) or server-side processing (serverSide) so you can add events, class name information or otherwise format the row when it is created.

There are a few things that need to be accomplished in the createdRow:

  • For responses, reconstruct the response row to be more like the “classic” Notes response document and add classes and attributes that connect it to its parent row.
  • For non-responses, add that document’s docid as a row class and add a click event that will expand and collapse any responses.
  • For responses with responses, add a click event to expand/collapse its child documents.
  • Use the position data to provide classes and attributes to the response rows to make it easier to build our hierarchy.
rowCallbackResponse : function(row,data,index,params,o) {

	var attr = $(row).attr("data-docid");
	
	// For some browsers, `attr` is undefined; for others, `attr` is false. Check for both.
	if (typeof attr !== typeof undefined && attr !== false) {
	  // Element has this attribute
		return;
	}
	
	// action item view on pcspDocument
	$(row).attr("data-docid",data['@unid']);
	
	// get the colspan
	var colspan = $('td',row).length;
	
	// if this is a response then add some info to this row
	var position = data['@position'];
	
	if (position.split('.').length > 1) {
		// remove cells and replace with 1
		$(row).html('<td colspan="' + colspan + '"><div><i class="fa fa-comment-o right5"></i>'+ data['comments'] + '</div></td>')
	
		// add some classes and attributes
		$(row).addClass('response response-'+data['parent_docid']);
		$(row).attr('data-position',position);
		$(row).addClass('response-level-'+position.split('.').length)
		
		// add a reference to the parent doc
		$(row).attr('data-parent-docid',data['parent_docid']);
		
	} 
	
	$(row).click(function(ev) {
		$('[data-position^="' + position + '."]').toggleClass('hidden');
		$('td:first div .fa',this).toggleClass('fa-caret-down');
		$('td:first div .fa',this).toggleClass('fa-caret-right');
	});	
}

I also add a little CSS to create the indentation of the responses:

table.dataTable tr.response-level-2 td {padding-left:100px}
table.dataTable tr.response-level-3 td {padding-left:125px}
table.dataTable tr.response-level-4 td {padding-left:150px}

Now looking at the View, we are starting to see a result more like what we would expect. We can expand and collapse the responses by clicking the parent row and any response row that has responses, but we have a little bit more work to do. I need to add a visual indicator to those rows so the user can see they have responses.

To do that I’m going to add a drawCallback function. With that I want to accomplish the following:

  • Add a visual indicator to any row that has response documents. Using FontAwesome, a twistie icon is added to the first column of any row with responses to simulate the classic Notes look. The great thing is this is completely customizable. I chose the first column for simplicity.
  • Move the node of each response so they appear after the parent. This is not an issue when the View first loads, but is necessary when the View is sorted or filtered.
drawCallbackResponse : function() {
		
	// retrieve data from browser sessionStorage
	var responses = sessionStorage.getItem('responses') != '' ? JSON.parse(sessionStorage.getItem('responses')) : {};

	// loop through each item 
	// responses are stored in an object with the parent docid as key
	for (var key in responses) {
		if (responses.hasOwnProperty(key)) {
			// prevent dupes 
			$('[data-parent-docid="' + key + '"]').remove();
			var pos = '.'+key;
			// insert each response after
			for (var x=0;x<responses[key].length;x++) {
				pos = $(responses[key][x]).insertAfter(pos);
			}
		}
	}
	
	$('.response').each(function() {
		var post = $('.'+$(this).attr('data-parent-docid'));
	
		if ($('.'+$(this).attr('data-parent-docid') + ' td:first div .fa-caret-down').length==0 &&
			$('.'+$(this).attr('data-parent-docid') + ' td:first div .fa-caret-right').length==0
		) {
			$('.'+$(this).attr('data-parent-docid') + ' td:first div').prepend('<i class="fa fa-caret-down right5"></i>');
		}
		pos = $(this).insertAfter(pos);
		
		if ($('.fa').hasClass('fa-caret-right')) {
			$(this).addClass('hidden');
		}
	});
}

Now we have a fully functional View with response documents in a hierarchy. We can click the column headers to change the sort order of the View and the responses will still travel with their parent.

But we still have an issue .. filtering the View will make the responses disappear because the response rows don’t contain the same data as their parent.

Again, we have all of the tools we need to overcome this. Using the initComplete callback functionality of the control, we can store the html of all response rows in the sessionStorage of the browser when the View initialization is complete:

initCompleteResponse : function(_v,dataTableClass) {
	var responses = {};
	var oa = [];
	var o = {};
	
	$('.response').each(function() {
		//store the html of response rows 
		if (typeof (responses[$(this).attr('data-parent-docid')]) == 'undefined') {

			responses[$(this).attr('data-parent-docid')] = [];
		} 
		var html = $(this).clone().wrap('<p/>').parent().html();
		responses[$(this).attr('data-parent-docid')].push(html);
	});
	sessionStorage.setItem('responses',JSON.stringify(responses));
}

You may have noticed in the drawCallback function there was a reference to the sessionStorage:

// retrieve data from browser sessionStorage
var responses = sessionStorage.getItem('responses') != '' ? JSON.parse(sessionStorage.getItem('responses')) : {};

By doing this little bit of caching we can ensure that we can get the responses back into the table even if the DataTables filtering removes these records from the display.

Conclusion

The combination of the createdRow (or rowCallback), initComplete, and the drawCallback functions give us the power to rebuild the response hierarchy of a Notes View in the Flexible View Control for XPages.


Humbled to be an HCL Ambassador for 2021

HCL has announced the HCL Ambassador Class of 2021 and I’m very honored to be on this list for the first time.

The HCL Ambassadors Program recognizes customers and partners who go above and beyond to promote HCL Software products, engage with the community, and help others.

Thank you to those who nominated me, and to HCL for selecting me based on the criteria above. We’ve all had enough of 2020 at this point, so here’s to 2021!


Flexible View Control for XPages – Mashing Up Total Columns and Category Renderers

In a recent blog post we covered the concept of ‘Category Renderers’, which allows the user to easily alter the visual representation of category rows in a categorized View. Combining this functionality with totals columns, highlighted in the last blog post in this series, we can further enhance Views in XPages and make them more user-friendly than their Notes counterparts.

The Mission

Continuing with our dataset from previous posts of used cars available in Wilmington, DE I want to display the average PRICE of each MAKE of vehicle. In my View Definition I categorize the MAKE column:

Note: As you can see in the screenshot below, the View Definition has an option to display average values. This option currently doesn’t function properly and will be updated in the next release.

Then I add a Category Renderer to the MAKE column:

And I still have totals enabled for PRICE and MILEAGE:

The Category Renderer

The demoFn.category.convert_total_to_avg function is a function in a client-side js library. Remember, you do not provide any parameters to the function. This function is applied to every category row and the dom object for that category is automatically passed into the function as a parameter.

convert_total_to_avg : function(_this) {
	// _this is the category row dom element
	// take a total value for a category and instead display the avg value
			
	// get the total # of rows for this category
	var total_rows = $('.total_rows',$(_this)).html();

	// get the total PRICE for this category
	var total_value = $(_this).attr('total_price_values');

	// format the column that contains the total #
	$('.MILEAGE_total',$(_this)).html(total_rows+' (total)');

	// calculate and format the average price
	$('.PRICE_total',$(_this)).html('$'+$U.formatMoney((total_value/total_rows),0)+' (avg)');
}

Notice in the code above we are extracting the total_value from the attributes of the category tr. We are able to do so because when totals are enabled for a categorized column the Flexible View Control stores the accumulated data in the tr element for easy access:

The Result

See the demo: http://demos.xpage.me/demos/datatables-xpages-bootstrap.nsf/viewGeneral.xsp?viewdef=wilmington-de-category-renderer-totals&key=city:Wilmington:state:DE

Here is our result:

What About The Footer?

While we are achieving the desired outcome for each category, the footer still displays the total PRICE for all used cars in Wilmington, DE.

How do we get the average price for all used cars instead? In the next post we’ll show you how!


Flexible View Control for XPages – Adding Column Totals

When building Views in the Notes Client adding totals to one or more columns is a pretty common occurrence.

Does the Flexible View Control for XPages support column totals? Absolutely!

As usual, the power is in the View Definition

The Flexible View Control provides two options for displaying totals in View columns:

Total Rows

This option simply does what you would expect – totals the number of rows. This is equivalent to creating a column in a View, hard coding a ‘1’ for the value, and telling the column to total the values. Another advantage of the Flexible View Control … there is no need to create a special column to display the total rows and no need to worry about selecting the ‘hide detail rows’ option to hide all those 1s in your View. Instead, simply pick any column, turn on Total Rows, and the total will display at the bottom of that column.

Total Values

Again, this option does exactly what you would expect it to do and adds up the value contained in each row and displays the total in the footer at the bottom of the View.

Building A Demo

To get started with a demo for this post I created brand spanking new View Definition called wilmington-de-totals, pointed it to the same View in the Used Cars database I’ve used for most of the other demos, and selected all of the data columns. To show Total Rows, I select that checkbox for the MILEAGE column and to show Total Values, the PRICE column.

View Definition demonstrating Total Rows and Total Values

See the demo: http://demos.xpage.me/demos/datatables-xpages-bootstrap.nsf/viewGeneral.xsp?viewdef=wilmington-de-totals&key=city:Wilmington:state:DE

Did you notice … the total value displayed in the PRICE column is formatted as currency to match the data type of the values in the individual rows.

An argument could be made that the Total Rows option is not needed since we have the DataTables info box in the bottom left corner. However, the Flexible View Control has a custom property that controls whether the info section is displayed, or not. So some may opt to turn it off and display the total rows under a column.

custom properties of the Flexible View Control

Here’s what it looks like:

View with showInfo=false

What About Filtering?

What happens if you enter a value in the search box and filter the view based on that value? The totals displayed reflect the filtered value. In the example below, I searched for all of the Land Rovers. The total PRICE contains values only for the Land Rovers, as does the Total Rows displayed under the MILEAGE column.

The totals columns reflect totals only for the filtered value

Category Totals

One of the great, unique features of Notes Views is the ability to have total columns in a categorized View and display totals for each category in addition to the full column total. With the Flexible View Control, when either of the totals options is enabled, categorized Views will also display the totals for each category by default:

click to see this demo live

See the demo: http://demos.xpage.me/demos/datatables-xpages-bootstrap.nsf/viewGeneral.xsp?viewdef=wilmington-de-category-totals&key=city:Wilmington:state:DE

Summary

Organizations that have been Notes/Domino shops and start to move their apps off of the Notes Client and into the browser still expect some of the standard functionality provided by Notes Views. The Flexible View Control for XPages strives to meet those expectations and also to exceed them by providing additional functionality that we, as developers, were not able to provide for our users within the Notes Client.

In the next blog post we will take the Totals functionality a step further and mash it up with the Category Renderer functionality from a previous post to show how to create richer Views for our users.


Flexible View Control for XPages – Category Renderers

What is a Renderer?

In the DataTables world, “Renderers” play an important role in creating the visual representation of a table. In fact, behind the scenes the Flexible View Control uses Renderers to display all of the data in a View. In the future, I will be posting about creating custom renderers to further enhance the Flexible View Control, but this post focuses on “Category Renderers” in keeping with the category/subcategory theme of recent posts.

Technically speaking, Category Renderers are not the same as standard DataTable Renderers. But the concept is the same – taking a data input and altering the output that is presented to the user.

Building a Category Renderer Demo

Let’s pick up where we left off in the previous post with our subcategory demo. I made a copy of the View Definition used in this demo and called it ferraris-category-renderer. Take look at the MODEL column:

Adding a Category Renderer to a View Definition

Underneath the category checkbox is another checkbox labeled “Renderer”. Clicking this reveals a pop-up window:

In this box I can enter a function string without parameters in the same way that callback functions are configured in the FVC custom properties. If a value exists in this box, then after the categories are drawn in the DataTable, the dom element for each MODEL category row is passed into the function defined in the category renderer box:

 // does this group need a renderer?
if (o.columnCategory[x].categoryRenderer != null && o.columnCategory[x].categoryRenderer != "" ) {

	$('.group-'+o.columnCategory[x].itemName).each(function() {

		var fnstring = o.columnCategory[x].categoryRenderer;
		if ((typeof fnstring) == "undefined" || fnstring == null) {
			return;
		}
		if (fnstring.indexOf('.') > -1) {
			var fn = window.getFunctionFromString(fnstring);
		} else {
			var fn = window[fnstring];
		}
		
		// is object a function?
		if (typeof fn === "function")  {
			fn(this);
		} else {
			console.log("renderer error")
			return;
		}
	});
}

In the screen below I’ve entered a function name into the category renderer box. This is just a client-side JavaScript function that lives in a demos script library.

Category Renderers are JavaScript functions

Starting simple, I’m just going to change the font for the MODEL category row. *Technically* this should be done with CSS instead of a renderer, but it’s fine for a simple example:

model_renderer : function(_this) {
	// _this is the row dom element
	console.log('category renderer')
	$(_this).css({
		'font-family':'Oswald',
		'font-size':'1.25em'
	})
}
Changing the font of the top-level category with a renderer

A more practical use for the category renderer is to manipulate the dom node for the category. For this next example I’m going to wrap the category value in a Bootstrap label:

model_renderer : function(_this) {
	// _this is the row dom node
	
	$(_this).css({
		'font-family':'Oswald',
		'font-size':'1.25em'
	});
	
	
	// add a Bootstrap label
	var v = $('.group_value',$(_this)).text();
	$(_this).find('.group_value').addClass("label label-success")
	
}

Checkout the demo: http://demos.xpage.me/demos/datatables-xpages-bootstrap.nsf/viewCategories.xsp?viewdef=ferraris-category-renderer&key=make:ferrari

Wrapping the category with a Bootstrap label

Summary

In summary, Category Renderers can be a valuable, low-code tool that adds functionality to Views created with the Flexible View Control for XPages and provide a more rich experience for your users.


Flexible View Control for XPages – Adding Subcategories To Categorized Views

With the last post in the Flexible View Control Series introducing categorized Views, this post is picking right up where that left off and demonstrating how easy it can be to add multiple levels of categorization to a View.

The View Definition

Jumping right in, I’ve created a copy of the ferraris-categorized View Definition used in the previous demo and named the copy ferraris-subcategorized. Since this View Definition is already categorized by MODEL, I want to subcategorize the YEAR column. All I need to do is make a couple of small changes:

  1. Sort the subcategory column
  2. Tell the YEAR column to categorize in addition to the MODEL category
  3. Hide the YEAR column for the detail rows

Displaying the View

For displaying the View I can reuse my viewCategories XPage created for the category demo in the last blog post since it’s configured to dynamically read the View Definition and query from the url QueryString.

Keep in mind … I haven’t altered the back-end View at all. The power is in the View Definition, allowing me to use the same data source to create multiple Views.

See the demo for yourself

http://demos.xpage.me/demos/datatables-xpages-bootstrap.nsf/viewCategories.xsp?viewdef=ferraris-subcategorized&key=make:ferrari

Adding a subcategory to a categorized View

Expanding, collapsing, and filtering all still work as you would expect.

How About More than Two Levels of Categorization?

The Flexible View Control will support as many levels of categorization as you need. The caveat, of course, is all of the categorized columns need to be sorted in the order of categorization.

Demo: Three levels of categorization

View Definitions currently support four levels of client-side sorting. Alternatively, if the data being sent from the server is already sorted, then theoretically the FVC will support as many levels of categorization as needed.

Note: It’s important to understand the performance implications of using multiple levels of categorization. Every additional level adds an nth degree of speed reduction in rendering the View since categorization is accomplished by traversing the dataset. The more data in the View, the more impact on performance.

Note: For any categorized View, whether the data is sorted sever-side or client side, the first category column needs to be listed in the first client-side sort field of the View Definition. This is an unintended byproduct of the categorization functionality and I will look to remove this requirement in the future

To demonstrate the server-sort, I created a View in the used car database of all Ferraris and sorted the first four columns, By MODEL, By STATE, By YEAR, and By CITY.

Click to open the View Definition
Click to open this View

Note: The category CSS for the Flexible View Control that handles category indentation only accommodates three levels of categories but its easy enough to add custom CSS for four levels and beyond:

/* level-3 refers to the 4th level since it's
 zero based indexing */
tr.group-level-3 td:nth-child(1) {
    padding-left: 75px;
}

tr[data-category-level="3"] td:nth-child(1) {
	padding-left:100px;
}

Summary

Again, not to beat a dead horse … but flexibility, reusability, and low-code-ability (I think I just made that word up) are qualities that make the Flexible View Control a game-changer for XPage development. The next post will talk about “Category Renderers”, a killer feature to enhance the UI and UX of categorized views.


Flexible View Control for XPages – Categorized Views

HCL Digital Week made very clear the emphasis in the application development space is on “low-code”. That got me thinking about the Flexible View Control and how, at the end of the day, it really is a low-code tool to add View data to XPage applications. Just like with Domino Volt, many aspects of configuring a View with the Flexible View Control are as simple as drag, drop, and configure.

Creating a categorized View is no different. Let’s take a look at just how easy it is by creating a View of all the Ferraris listed in the used car database. Naturally, we start by creating the View Definition:

The View Definition

The View Definition being used for the demos shown in this post can be found here.

I want to categorize this View by the MODEL column. Just like we do for a Notes View, I make that the first column. Next, check off the “category” checkbox in the last column of the MODEL column row.

A couple of important notes in the above sceenshot:

  1. The column to be categorized MUST be sorted.
  2. In the image above the MODEL column is being hidden. That might seem counter-intuitive since this is the column we are categorizing, but it will make sense in a moment.

The View XPage

Link to the demo used in this post.

BEFORE adding categorization

The image above shows what the Ferraris View looks like before adding the categorization.

The image below shows what the View looks like after adding the categorization and hiding the MODEL column.

AFTER adding categorization and hiding the MODEL column

How does it work?

To create the categorization the Flexible View Control traverses the DataTable during the drawCallback phase and when a row contains a different MODEL value, a new tr node is inserted before that row to represent the category. The reason why we can hide the MODEL column is the traversal is done on the DataTable dataset, not the actual HTML of the table. After inserting the tr node a click event is applied to provide expand/collapse functionality for the category.

Let’s break down the tr node that gets added for each category:

category tr element insert for each category
  • The data-category attribute is the unique identifier for this category. This value also gets applied to all of the child rows for this category.
  • “group” classes are added to aid in referencing the category rows.
  • The data-hide attribute is used to handle expansion/collapse of the category.
  • The table cell created inside the tr node is automatically given a colspan equal to the number of cells in a non-category row.

Doing the Twist(ie)

You probably noticed in the image above that there is a “twistie” node in the tr HTML. By default this is simply a “-” to denote expansion and a “+” to denote a collapsed category. This is handled with some simple CSS:

.twistie {margin-right:5px}
.twistie-expand::before{
	content:'-';
	font-size:1.25em;
}
.twistie-collapse::before {
	content:'+';
	font-size:1.25em;
}

Adding a little more “flair” to the twistie is easy by simply overriding the CSS. For example, I’m a big fan of Font Awesome and use that for my twistie icons:

Using Font Awesome icons for twisities
.twistie-expand::before{
	font-family: FontAwesome;
	content: "\f056";
	font-size: inherit;
}
.twistie-collapse::before {
	font-family: FontAwesome;
	content: "\f055";
	font-size: inherit;
}

Expand/Collapse All

Just as in a Notes View that contains categorization, the Flexible View Control provides the ability to expand or collapse the entire View. In our demo there are two buttons at the top that are wired with this functionality:

When the control is rendered functions are created dynamically that call functions contained in the csjsCCRestView.js script library:

collapseAll : function() {
	ccRestView.groups.collapseAll(this.thisView);
},
expandAll : function() {
	ccRestView.groups.expandAll(this.thisView);
}

The code behind the buttons can then refer directly to a specific View, which we’ve named ViewBasic in the thisView property of the control for this demo:

ViewBasic.collapseAll();
ViewBasic.expandAll();

Filtering

One of the great features of DataTables is being able to enter a query in the search box and having the contents of the DataTable reduce to the records that match that query. This functionality still works when the View is categorized!

The next post will expand on the categorization functionality by introducing subcategories and a feature called “category renderers”.


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!