Quantcast

XPages Tip: Dynamically Set Row Colors in a View or Repeat Control Based on Data

In a recent post, Kathy Brown showed how to alternate row colors, in XPages. In this post, I’ll show how to take that a step further and dynamically set the row color based on data in the row.

You can compute the style class of each row in a view panel or repeat control (or data table or data view, etc), so you have the ability to check the data in each view entry and set a style class accordingly. Let’s start with the view panel.

In my example, I have a list of tasks. When a task is about to expire, I want the row to appear with a yellow background. When the task has expired, I want the row to appear with a red background.

Here’s a View Panel using this technique:

Image may be NSFW.
Clik here to view.
Dynamic Row Styling Part 1 - View

Here’s a Repeat Control:
Image may be NSFW.
Clik here to view.
Dynamic Row Styling Part 1

This requires 3 steps:

  1. Create CSS style classes for the row colors (and include the style sheet on the page)
  2. Set the var property of the view panel
  3. Compute the style class to use for the row, based on the data

1. Create CSS Style Classes for the Row Colors

I have defined these styles in a style sheet that is included on my page:

.yellowRow {
background-color: #FFFF00;
}

.redRow {
background-color: #FF0000;
}

2. Set the var property of the view panel

In order to compute the class based on data in each view entry, we need to have the ability to read data from the view entry. The var property of the view panel gives us that handle.

Image may be NSFW.
Clik here to view.
Dynamic Row Styling Part 1 - View Var Property

3. Compute the style class to use for the row, based on the data

Now that I have styles defined and a handle to the view entry, I can compute the style class for each row. To do so, select the view panel properties, select the Style subtab, and select the rowClasses property. Next to the Class property, click on the diamond to open up an SSJS window and add the code to compute the style class.

Image may be NSFW.
Clik here to view.
Dynamic Row Styling Part 1 - View Property

This code will read the value of the ‘status’ column and return a class name to use for the row accordingly.

var status = varTask.getColumnValue('Status');
var cssClass = '';

if (status == 'Expiring') {
  cssClass = 'yellowRow';
} else if (status == 'Late') {
  cssClass = 'redRow';
}

return cssClass;

Working with a Repeat Control

All of the same concepts apply. The difference is that you don’t have a built-in styleClass property to use. Assuming your repeat control contains a table, you can compute the style class on the xp:tr tag within the xp:repeat tag.

It can be difficult to try to select the xp:tr tag directly, but you can click on the first cell in the row and then locate the xp:tr tag either via the Outline view or in the page source. Once you have the xp:tr tag selected, you can compute its styleClass property and use the same code as shown above, provided you have defined the same var name for the repeat control.

Image may be NSFW.
Clik here to view.
Dynamic Row Styling Part 1 - Repeat


Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.

XPages Tip: Alternating Row Colors along with Dynamically Setting Row Colors

In my last past, I showed how you can dynamically set row colors in views and repeats based on data in each entry. In this post, I’ll show how you can take it a step further and use that technique along with providing default alternating row colors.

In my task list, I want to alternate the rows between white and light gray to provide visual separation of the data. But I want to override those defaults and display the row with a yellow background if the task is expiring and a red background if the task is late.

Here’s an example of a repeat control using this combined technique:

Image may be NSFW.
Clik here to view.
Dynamic Row Styling Part 2

Here’s the updated css:

.repeatRow {
background-color: #EEEEEE;
}

.repeatRowAlt {
background-color: #FFFFFF;
}

.yellowRow {
background-color: #FFFF00;
}

.redRow {
background-color: #FF0000;
}

NOTE: It is important to define the red and yellow classes after the default row classes. Whichever ones are defined later will take precedence when multiple classes are assigned to a row.

Here’s the computed style class code to make it work:

var status = varTask.getColumnValue('Status');
var cssClass;

if (rowIndex % 2 == 0) {
cssClass = 'repeatRow';
} else {
cssClass = 'repeatRowAlt';
}

if (status == 'Expiring') {
cssClass += ' yellowRow';
} else if (status == 'Late') {
cssClass += ' redRow';
}

return cssClass;

The code checks the row number and starts with a class of repeatRow or repeatRowAlt. If the status is Expiring or Late, it adds another class to the list. Note that there’s a space before the class name in lines 11 and 13. This is because I’m retaining the first class name and adding a second class to it.

I’m doing it this way because I tend to define font and spacing settings in the repeatRow and repeatRowAlt classes. By assigning two classes to rows as needed, I can retain those settings while overriding the background color when required.


Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.

Accessing the Value of Components within a Repeat Control from Outside

As a general rule, XPages variable resolution makes it very easy to access components within the same instance of a repeat control, but very difficult to access a component that’s within a repeat control from outside of it. In this post, I’ll show you you can configure the repeat control and define component IDs in a way that you can access them from outside of the repeat control.

1. Set the “Create Controls at Page Creation” Property

By default, the JSF component tree only includes one copy of each component in for the repeat control and it iterates over it as many times as needed to generate each instance of the repeat. I’m not aware of a way to access an individual instance of a component this way.

This is where the Create controls at page creation property of a repeat control comes into play. This property adds the repeatControls attribute to your repeat control tag and sets it to true. When enabled, it causes an instance of each component to be created for each instance of the repeat control in the JSF tree.

Image may be NSFW.
Clik here to view.
RepeatControls

Update: Read about the side effect of enabling this property so you understand the effect it could have on your application.

2. Dynamically-Assign Component IDs Based on the Repeat Index

In order to access a specific control within a specific instance of the repeat, I need to know what its name is. I need to assign a unique ID to each instance of each component in order to be able to access it.

With inspiration from this great blog post by Chris Toohey (which was, in turn, assisted by Tim Tripcony), I found a way to make all of this work.

As Chris pointed out, you cannot compute component IDs dynamically, but you can compute them on page load.

By setting the Index name property of the repeat control (see screen shot above), I’ll have an index variable available for each instance of the repeat control. To create a unique name, I took the existing component name and updated it to dynamically add a suffix based on the index variable.

There’s no blue diamond to compute the ID of a component, so you have to go into the source and change it directly.

<xp:comboBox id="MyComboBox_${rptIndex}">

Now the component is uniquely but predictably named and can be accessed as needed!

Example Repeat Control Source

Here’s the source of the repeat control from my simple sample XPage. Each instance of the repeat control displays a combobox with a few hard-coded options. An array with 4 elements is passed to the repeat control in order to create 4 instances of the repeat. (The values in that array are not used at all in this simplistic example.)

<xp:repeat id="repeat1" rows="30" indexVar="rptIndex" repeatControls="true">
  <xp:this.value>
    <![CDATA[#{javascript:return ['aa', 'bb', 'cc', 'dd']}]]>
  </xp:this.value>
		
  <xp:comboBox id="MyComboBox_${rptIndex}">
    <xp:selectItems>
      <xp:this.value><![CDATA[#{javascript:return ['', '1', '2', '3', '4'];}]]></xp:this.value>
    </xp:selectItems>
  </xp:comboBox>
  <xp:br /><xp:br />
</xp:repeat>

Checking the Component Values

The code check the values within the repeat control to can be dynamic. Since I don’t know how many instances there will be when using this in a more dynamic application, I set it up to look for a component within the repeat and loop until it doesn’t exist with a next index suffix.

Here is sample SSJS code on a button on the page (outside of the repeat control) that will check the value of the combobox within each instance of the repeat control and print the value to the server console.

var i=0;
var boolContinue = getComponent('MyComboBox_' + i);
while (boolContinue) {
  print ('value ' + i + ': ' + getComponent('MyComboBox_' + i).getValue());						
		
  // Check whether there's another RCA form an determine whether to continue
  boolContinue = getComponent('MyComboBox_' + ++i);
}

Line 2 sets a boolean value based on whether the component in the first instance of the repeat control can be found. (If there are no entries in the repeat control, then the code will not proceed.)

Line 4 accesses the value of the control with the index suffix and prints the value.

Line 7 checks if there’s another instance of the control with the next suffix number and determines whether to continue based on that.

Proactive Disclaimer

I know there are some (**cough** Tim **cough) who would strongly suggest that I read data from the document directly rather than try to access the components in this way. As a general rule, I wholeheartedly agree. However, the use case that necessitated this workaround is that it’s for form validation for related documents within a repeat control and, with the method of validation I’m using, I need to access the components in order to mark them as invalid as needed.


Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.

The Side Effect of Creating Repeat Controls at Runtime

In a recent post, I showed how to access the value of components within a repeat control from outside. While the technique served a great purpose in a recent application, there is a significant side effect that you need to be aware of before implementing a similar solution.

The technique described in the post requires all controls within the repeat to be created at runtime and dynamically but consistently named (based on the repeat index) so they can be programmatically accessed in a predictable manner.

I had a good discussion about this topic with Tim Tripcony and he pointed out that the side effect of the Create controls at page creation property of the repeat control is that all of the controls within the repeat are locked into place at page creation. This means that you cannot dynamically update the repeat control. A partial refresh won’t do it. A full refresh won’t even do it.

I saw this side effect in my application. In order to see any updates to the repeat control while the page is still loaded required context.reloadPage().

That’s still fine in my case, but it wouldn’t be fine in many cases where I use repeat controls, so please keep it in mind that setting that property will prevent you from being able to update a repeat control with a partial refresh.


Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.