Archive | July 2013

XPages Data Views – Part 1: Overview

The Data View control in XPages is easy to create like a view panel, but highly flexible like a repeat control or data table, which makes it easy to create good-looking and highly-functional views very quickly. In this series, I’ll show how to make use of its many features and customize the content to create more modern data displays in your applications.

I recorded two NotesIn9 shows (Part 1 – Implementation and Part 2 – Customization) after learning the Data View, but I also want to write about many of the features so I can go more in depth to explain them further, show where all the settings are, share some code, and have it all be easily searchable.

One of the great features of this control is that it works well on a mobile device, but in this series, I’ll be focusing on designing for the full browser. You can find more information on using the data view on a mobile device in these NotesIn9 shows recorded by Peter Presnell and Kathy Brown.

Features

Here are some of the great features of the Data View control that will be covered in the series:

  • Property-driven simple configuration
  • Flexible summary layout
  • Built-in link to open document
  • Collapsible details section
  • Multi-column layout
  • Categorization
  • Response hierarchy
  • Icon columns
  • Selection checkbox
  • Pager More Rows control
  • Pager Save State control

Example

Here’s a screen shot from the Domino App Dev Wiki that shows one example of what a Data View can look like (without a collapsible Details section):

Data View Example Domino App Dev Wiki

Layout

This screen shot highlights the different aspects of another data view control, so you can get familiar with the terminology and the placement of each component.

DataViewDesign

Availability

The Data View control is not a core control, so it will not always be available in Domino Designer. It is available if you have any of the following configurations:

  • Extension Library
  • Notes 8.5.3 with Upgrade Pack 1
  • Notes 9

Up Next

In the next post, I’ll step through how to create a Data View, taking a look at the minimum requirements to get it up and running and adding a few features from there.

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.

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.

XPages Tip: Adding a Favicon

When users view an XPage, they icon they see in the browser tab by default is a Notes icon. In this post, I’ll show how you can change that icon with a theme setting in order to add another nice touch of customization.

Default Favicon

By default, the users will see a Notes icon displayed in the browser tab:

favicon1

Custom Favicon

You can customize the icon that’s displayed in the browser tab globally for the application via the theme using the pageIcon property of ViewRoot.

For example, if I have an image resource in my application named document.png, I can set it as the favicon by adding this to the application theme:

<control>
  <name>ViewRoot</name>
  <property>
    <name>pageIcon</name>
    <value>/document.png</value>
  </property>
</control>

Now I have a custom favicon:

favicon2

If you happen to specify an invalid icon path, this is what it looks like in Firefox:

favicon3

XPages Tip: Fixing the Display of Comboboxes, Radio Button Groups, Checkbox Groups, and Listboxes in Read Mode with Dojo

With a couple of minor adjustments to the code shown in a previous post, I now have a script that will fix the display of comboboxes, radio button groups, checkbox groups (single or multi-valued), and listboxes (single or multi-valued) all in one fell swoop!

The output of a single-value for any of these field types looks like this:

<table id="id">
  <tr>
    <td>value </td>
  </tr>
</table>

However, if you have a field with multiple values, it separates each value out into its own row, so the previous code needed to be adjusted to read the values out of all cells in the nested table. This code will concatenate multiple values to be separated by a comma and a space, but you can easily change that by modifying the code below.

<table id="id">
  <tr>
    <td>value 1</td>
  </tr>
  <tr>
    <td>value 2</td>
  </tr>
  <tr>
    <td>value 3</td>
  </tr>
...and so on...
</table>

This code, when run onClientLoad of the page, will fix all of these fields types, by replacing the table with just the value of the field, concatenating multiple values as needed.

if("#{javascript:document1.isEditable()}" == "false"){

  dojo.query('table td table[id]').forEach(function(node) {
    var value = '';

    // Get the value out of all cells in the nested table and concatenate them
    dojo.query('td', node).forEach(function(innerNode) {
      value += innerNode.innerHTML + ', ';
    });

    // Replace the table with only the value(s) (or a blank), but cut off the trailing comma
    node.outerHTML = value.substring(0, value.length-2);
  });
}

Line 1 tells it to only run in read mode. (Update the name of the document data source variable if you need to.)

Line 3 gets a handle on tables with ids that are nested inside of table cells.

Lines 7-9 search for all table cells within the table and retrieve each cell’s innerHTML, which contains a value. If there is no value, then there won’t be a table cell and it will replace the table with an empty string.

Line 12 replaces the nested with just the value that needs to be displayed.

And now the output from the example shown above looks like this, as it should:

If you run into conflicts with this affecting other tables in your page, you may need to refine the selector in line 3.

If you need to preserve the of the element and/or wrap it in a span tag like other field values, you can tweak the code accordingly.

XSnippet

The latest version of the code is posted as an XSnippet on OpenNTF

XPages Tip: Fixing the Display of Radio Button Groups in Read Mode with Dojo

In my last post, I showed a script that can be used to fix the display of combo boxes in read mode. As it turns out, the same code will fix radio button groups, because the output is the same!

The same code will also fix the display of single-value checkbox groups and listboxes. It will not properly handle multi-valued checkbox groups or listboxes, so I’ll be working on that next.

I have updated the comments on the XSnippet accordingly.

XPages Tip: Fixing the Display of ComboBoxes in Read Mode with Dojo

There’s a quirk with the display of combobox values when XPages are in read mode. Instead of just displaying the value, it generates a table with one row and one cell. This can cause problems with your form alignment if your fields are aleady in a table (especially a OneUI form layout table). This post contains a few lines of dojo code that can be used to fix the problem.

If you have a one-row two-cell table that contains a label and a combobox, it looks like this in the page source in Domino Designer:

<table>
  <tr>
    <td>
      <xp:label value="Label" id="label1" for="comboBox1"></xp:label>
    </td>
    <td>
      <xp:comboBox id="comboBox1" value="#{document1.Field1}">
        <xp:selectItem itemLabel="Value 1"></xp:selectItem>
        <xp:selectItem itemLabel="Value 2"></xp:selectItem>
        <xp:selectItem itemLabel="Value 3"></xp:selectItem>
      </xp:comboBox>
    </td>
  </tr>
</table>

But this is the output in read mode:

<table>
  <tr>
    <td>
      <label id="view:_id1:label1" for="view:_id1:comboBox1" class="xspTextLabel">Label</label>
    </td>
    <td>
      <table id="view:_id1:comboBox1">
        <tr>
          <td>Value 1</td>
        </tr>
      </table>
    </td>
  </tr>
</table>

Instead of just writing out the value of the combobox, it wraps it in a table.

(A regular field value is generally written out within a span tag that has the id of the control.)

When the combobox doesn’t have a value, then an empty table is inserted (no rows or cells):

<table id="view:_id1:comboBox1"></table>

This code, when run onClientLoad of the page, will fix this issue by replacing the table with just the value of the combobox.

if("#{javascript:document1.isEditable()}" == "false"){
  // Search for all nested tables with ids
  dojo.query('table td table[id]').forEach(function(node) {
    var value = '';

    // Locate the first table cell, which contains the value
    dojo.query('td:first-child', node).forEach(function(innerNode) {
      value = innerNode.innerHTML;
    });

    // Replace the table with only the value (or a blank)
    node.outerHTML = value;
  });
}

Line 1 tells it to only run in read mode. (Update the name of the document data source variable if you need to.)

Line 3 gets a handle on tables with ids that are nested inside of table cells.

Lines 7-9 search for the first cell within the table and retrieve it’s innerHTML, which is the value of the combobox. If there is no value, then there won’t be a table cell and it will replace the table with an empty string.

Line 12 replaces the original combobox table with just the value that needs to be displayed.

And now the output from the example shown above looks like this, as it should:

<table>
  <tr>
    <td>
      <label id="view:_id1:label1" for="view:_id1:comboBox1" class="xspTextLabel">Label</label>
    </td>
    <td>
      Value 1
    </td>
  </tr>
</table>

If you run into conflicts with this affecting other tables in your page, you may need to refine the selector in line 3.

If you need to preserve the of the element and/or wrap it in a span tag like other field values, you can tweak the code accordingly.

XPages Tip: Dependent Drop-Down Lists

This post describes how to create dependent drop-down lists in XPages. When the value in the first drop-down list is selected, it refreshes the second drop-down list, whose list of options is based on the value in the first field.

Field #1 has a list of options (generally from a DbColumn() or keywords). On its onchange event, it triggers calls a partial refresh on Field #2.

Field #2 has a list of options (generally from a DbLookup() or keywords) that is based upon the selection in Field #1. When Field #1 is updated, this field is refreshed and its list of choices is updated. (It starts out blank.)

Example

Here’s the source of Field 1, which triggers a partial refresh on Field 2 in the onchange event:

<xp:comboBox id="cbField1"
  value="#{doc.Field1}" defaultValue="">
  <xp:selectItems>
    <xp:this.value>
      <![CDATA[#{javascript:return @DbLookup('', 'vwKeywords', 'TopLevelKeywordName', 'ValueFieldName', '[FAILSILENT]');}]]>
    </xp:this.value>
  </xp:selectItems>
  <xp:eventHandler
    event="onchange" submit="true"
    refreshMode="partial" refreshId='cbField2'>
  </xp:eventHandler>
</xp:comboBox>

Here’s the source of field 2, which reads the value form field 1 when building its list of options:

<xp:comboBox id="cbField2"
  value="#{doc.Field2}" defaultValue="">
  <xp:selectItems>
    <xp:this.value>
      <![CDATA[#{javascript:var field1Value = getComponent("cbField1").getValue();
return @DbLookup('', 'vwKeywords', field1Value, 'ValueFieldName', '[FAILSILENT]');}]]>
    </xp:this.value>
  </xp:selectItems>
</xp:comboBox>