Quantcast
Channel: XPages – Xcellerant
Viewing all 216 articles
Browse latest View live

Categorized Dojo TreeGrid in XPages – Add Totals and Counts

$
0
0

In a previous post, I showed how to set up a Dojo TreeGrid to create a categorized grid. In this post, I’ll show how to create a TreeGrid that can calculate entry counts and totals for the categories.

TreeGrid_a_SumOneCol

When expanded, the totals show at the bottom of the category section. When collapsed, the totals show at the category level.

Required Modules and XPage Property

See the previous post for information on including the required dojo modules and style sheets and setting the XPage property to parse dojo on load.

JSON Data Store

As in the previous post, I showed how to use an XAgent to generate my own JSON in the format required by the TreeGrid. In this post, we’ll use the same technique, but the format of the JSON must be different for the categorized view with totals.

Fortunately, this format is simpler to generate.

In thise case, we are no longer building the list of references to child items and then generating the child items separately; it now requires inclusion of the child items as an array of items under a property of the category item.

Here’s a chunk of JSON that populates the first category in the view shown above.

The first item in the list is the ‘AK’ category. It has an array of child items for all of the entries within that category. The property that contains the child items (named childItems in this code) can be whatever you want.

{
identifier: 'id',
label: 'name', 
items: [
{id:'AK', type: 'state', state:'AK', numPeople: 3, childItems:[ 
  {id:'B3093953178C98E905257838007ABC48', firstname:'Bella', lastname: 'Martin', valueToAdd: 2}, 
  {id:'7FDB9CCDE7D6923E05257838007ABC1E', firstname:'Brian', lastname: 'Leggett', valueToAdd: 2}, 
  {id:'8CD685A9D29150D005257838007ABDD7', firstname:'Jesse', lastname: 'Pack', valueToAdd: 2}, 
  {id:'B281D25B6AE91D7A05257838007ABD53', firstname:'Jose', lastname: 'Bibeau', valueToAdd: 2}, 
  {id:'A4CA4CA43B93673605257838007ABC7E', firstname:'Karen', lastname: 'Buss', valueToAdd: 2}, 
  {id:'5656D38EC6DB315E05257838007ABA5D', firstname:'Margarita', lastname: 'Levesque', valueToAdd: 2}, 
  {id:'B2CC1FA115074A9305257838007ABEEA', firstname:'Mary', lastname: 'Witzel', valueToAdd: 2}, 
  {id:'EDDFFA8D2C2169B405257838007ABB03', firstname:'Rufus', lastname: 'Davis', valueToAdd: 2}
] }, 

...

]}

Here is the code that generates the data, based on a view from the FakeNames database, categorized by State. It is very similar to the code in the previous post, but it just builds one string of data per category, rather than two separate strings in the previous code.

var externalContext = facesContext.getExternalContext();
var writer = facesContext.getResponseWriter();
var response = externalContext.getResponse();

response.setContentType('application/json');
response.setHeader('Cache-Control', 'no-cache');

writer.write("{\n");
writer.write("identifier: 'id',\n");
writer.write("label: 'name', \n");
writer.write("items: [\n");

var categoryAndChildren = "";

// Walk the view and build the JSON data
var vw:NotesView = database.getView('ByState');
var nav:NotesViewNavigator = vw.createViewNav();
var ve:NotesViewEntry = nav.getFirst();

while (ve != null) {
  var cv = ve.getColumnValues();

  // When a categorized entry is reached:
  // (a) write out the previous category and children
  // (b) set up the new category element	
  if (ve.isCategory()) {
    // Write out the previous category and child entries		
    if (categoryAndChildren != "") {
      // Trim the trailing comma and space off the category item, close of the childItems array (]) and the category item (})
      categoryAndChildren = categoryAndChildren.substring(0, categoryAndChildren.length - 2);
      writer.write("\n" + categoryAndChildren + "] }, ");
    }	
	
    // Start building the new category and child entries
    categoryAndChildren = "  {id:'" + cv[0] + "', type: 'state', state:'" + cv[0] + "', numPeople: 3, childItems:[ \n";
	
  } else {
    // This isn't a category, so add another child item
    categoryAndChildren += "{id:'" + ve.getUniversalID() + "', firstname:'" + cv[1] + "', lastname: '" + cv[2] + "', valueToAdd: 2}, "
  }	
	
  // Get the next entry and recycle the current one
  var tmpentry = nav.getNext();
  ve.recycle();
  ve = tmpentry;
}

// Write out the last category and children, without the trailing commas
categoryAndChildren = categoryAndChildren.substring(0, categoryAndChildren.length - 2);
writer.write("\n" + categoryAndChildren + "] }, ");

// Close the JSON string
writer.write("]}");
writer.endDocument();

Grid Generation Code – Category Totals

The grid generation is also a bit different in this case. In the first categorized grid, all we had to do was define a few columns for the grid layout.

In this case, we need to define a more complex layout.

Lines 1-15 define the layout for the grid. Line 4 defines the State column at the category level. Line 5 defines the property of the category that will contain the array of child documents. I named the field childItems, but that is not a reserved name. However, in line 6, children is a reserved word. That must be the name of the property that contains the array of child documents. Line 11 defines that the grid will add the values in each column and display the sum total for each category.

Line 17 reads the JSON data that is generated by the XAgent page.

Lines 19-26 define the grid itself. The structure property must point to the layout that was defined above and the store property must point to the JSON data store. As mentioned in the previous post, the query property is required or else the grid will display erroneous duplicate entries. Line 26 defines the div tag where the grid will be generated, so that div tag must exist on the page.

var layout = [ 
  { cells: [
    [ 
      {field: "state", name: "State"},
      {field: "childItems",   
        children: [ 
          { field: "firstname", name: "First Name" },
          { field: "lastname", name: "Last Name" },
          { field: "valueToAdd", name: "Totals Col" }  
        ], 
      aggregate: "sum"
      }
    ]] 
  }					
]
			
var jsonStore = new dojo.data.ItemFileWriteStore({ url: "TreeGrid_DataStore_TotalsAndCounts.xsp"});

var grid = new dojox.grid.TreeGrid({
  structure: layout,
  store: jsonStore,
  query: {type: 'state'},
  queryOptions: {deep: true},
  rowSelector: true,
  openAtLevels: [10]
}, dojo.byId("treeGrid"));

grid.startup();
dojo.connect(window, "onresize", grid, "resize");

This code generates the grid shown below:

TreeGrid_b_WithTotals_SumsAllCols

Formatter Functions

By default it will attempt to sum every column in the child items. For numeric values, this is what you want, but for text values, it will just concatenate the values as shown above.

Fortunately, we can prevent that with a formatting function. Each child entry can use a formatter property that takes the name of a function to format the data.

The formatter function automatically accepts two parameters, the value in the current cell and a row index variable that denotes the level of categorization.

The child item rows have a level number that matches the nth category in the view, starting with 0. The category total rows have a negative number that corresponds to the level of categorization. Top level category rows will be -1, second-level category rows will be -2, and so on.

This formatter function will not display anything in the category totals row. To update the grid to only add up the numeric column, we will add this formatter function to the text columns.

function formatText(value, rowIndex) {
  if (rowIndex >= 0) {
    return value;
  } else {
    return '';
  }
}

Then update the text columns (First Name and Last Name, below) to use the formatter function.

var layout = [ 
  { cells: [
    [ 
      {field: "state", name: "State"},
      {field: "childItems",   
        children: [ 
          { field: "firstname", name: "First Name", formatter: formatText},
          { field: "lastname", name: "Last Name", formatter: formatText },
          { field: "valueToAdd", name: "Totals Col" }  
        ], 
      aggregate: "sum"
      }
    ]] 
  }					
]

TreeGrid_a_SumOneCol

Much better.

Row Counts

If you’d like to do a row count instead of a sum, all you need to do is change a couple of lines in the grid layout object.

Line 8 was changed to remove the formatter function so it will display the row count.

Line 10 was changed to set the aggregation type to cnt rather than sum

var layout = [ 
  { cells: [
    [ 
      {field: "state", name: "State"},
      {field: "childItems",   
        children: [ 
          { field: "firstname", name: "First Name", formatter: formatText}, 
          { field: "lastname", name: "Last Name"}
        ], 
      aggregate: "cnt"
      }
    ]] 
  }					
]

TreeGrid_c_count

openAtLevels

You may have noticed the openAtLevels attribute in the grid generation code earlier in this post.

This is a property of the tree grid that can be used to define category expansion at the time of grid generation. The attribute takes an array of values, each of which can be true, false, or a number. If you have multiple levels of categorization, then each element in the array will apply to the corresponding category level. In our case, we only have one level of categorization, so there’s only one element.

If you set it to true, it will expand all categories at that level. If you set it to false, it will collapse all categories at that level. If you give it a number, then it will automatically expand only the categories that have that many child items or less.

This property has no bearing on the grid’s ability to calculate totals or row counts.



Dojo Data Grid – Part 19: Pass-through HTML Grid Columns

$
0
0

By default, the grid will escape any HTML content in your data. In this post, I’ll show how to pass HTML data through a column of any type of Dojo grid.

Dojo Data Grid Series

Here’s an example of a grid with 3 columns displaying HTML content. All three of the columns combine the first and last name fields. The first column displays the value in bold and italics. The second column displays it within an H2 tag. The third column displays an HTML button that, when clicked, will pop up an alert that displays the name.

Grid20-PassThruHTML

Steps

Here are the steps required to pass HTML data through a grid column:

  1. Create a custom column in the REST service with HTML content
  2. Add a Dojo Data Grid Column and set it to display the HTML content column
  3. Set a grid property to prevent escaping the HTML data

If you already have HTML content in the data in the underlying view, then you do not need to create a custom column in the REST service, so you can skip to step 2.

1. Create a custom column in the REST service with HTML content

In order to add a custom column that reads data from the underlying view entry, we need to first set the var property of the REST service (All Properties > basics > service > var). This provides a variable that we can use to access the view entry.

To add a custom column, click the plus (+) icon in the columns property of the REST service (All Properties > basics > service > columns)

Set the name property to define the name of the column and then click the diamond next to the value property and write Server-Side JavaScript to compute the value.

Here is the full source code of the REST service used in the screen shot above. In each of the column values, you can see the SSJS used to compute the value.

<xe:restService id="restJsonService" pathInfo="gridData">
  <xe:this.service>
    <xe:viewJsonService defaultColumns="true" viewName="ByName-First" var="gridEntry">
      <xe:this.columns>
        <xe:restViewColumn name="BoldItalicName">
          <xe:this.value><![CDATA[#{javascript:return '<b><i>' + gridEntry.getColumnValue('firstname') + ' ' + gridEntry.getColumnValue('lastname') + '</i></b>';}]]></xe:this.value>
        </xe:restViewColumn>
        <xe:restViewColumn name="H2Name">
          <xe:this.value><![CDATA[#{javascript:return '<h2>' + gridEntry.getColumnValue('firstname') + ' ' + gridEntry.getColumnValue('lastname') + '</h2>';}]]></xe:this.value>
        </xe:restViewColumn>
        <xe:restViewColumn name="ButtonName">
          <xe:this.value><![CDATA[#{javascript:var fullName = gridEntry.getColumnValue('firstname') + ' ' + gridEntry.getColumnValue('lastname');
return '<input type="button" value="Display Name" onclick="alert(\'' + fullName + '\');">';}]]></xe:this.value>
        </xe:restViewColumn>
      </xe:this.columns>
    </xe:viewJsonService>
  </xe:this.service>
</xe:restService>

2. Add a Dojo Data Grid Column and set it to display the HTML content column

Now that the columns with HTML content have been added to the REST service, we just need to add Dojo Data Grid Column controls to the grid for each column to display. Set the field property of the grid column to the value of the name property defined in the REST service column.

3. Set a grid property to prevent escaping the HTML data

The last step is to set a property on the grid to prevent it from displaying this content as plain text.

The grid’s escapeHTMLInData property will do the trick. Set this property to false in order to see the pass-through HTML content.

Up Next

In the next post, we’ll take a look at how display icons in grid columns, as well as an alternate method of displaying HTML content.


Dojo Data Grid – Part 20: Icon Columns

$
0
0

In the last post, we saw how to pass HTML content through a grid column. In this post, we’ll take a look at how to display an icon in a grid column, including the alternate method of passing through HTML content that is required to make this work.

Dojo Data Grid Series

Here’s an example of a grid that is designed to show an icon that denotes the quality of the NFL football team that residents of a city root for:

Grid20-IconColumn

I had assumed that the technique shown in the previous post could also be used to display an icon in a grid column, but it didn’t work. The image tags were always stripped out and all that made it through to the browser was the alt text of the image.

Fortunately, there’s another way to go about it. The good news is that this alternate method also provides a simpler, more secure way to display HTML content in the grid!

Column Formatter

The answer lies in a grid column feature that we looked at earlier in the series — the column’s formatter property, which can be set to the name of a client-side JavaScript function that will modify the column data.

The function can be placed in an Output Script block on the page or in a JavaScript library. A formatter function automatically receives a parameter that is the value of the column data. It can process the data however it needs to and then return the value to display.

Set the grid column control’s formatter property (All Properties > data > formatter) to the name of the formatter function. In this example, I’ve created a function called displayIcon().

Grid20-IconColumn_Formatter

Below is the source code of the function. It will check the value and return an HTML image tag to display an image that’s in the current database as an image resource.

Note in the screen shot above that the grid column is set to display the city column from the REST service. So, the formatter function is receiving a city name and sending back the image to display in its place.

// Conditionally return an image tag to display an icon in a cell
function displayIcon (value) {
  var image = '';

  switch (value.toLowerCase()) {
    case "dallas":
      image = '<img alt="" src="awful_team.gif" />';
      break;
    default:
      image = '<img alt="" src="good_team.gif" />';
      break;
  }

  return image;
}

Easier, More Secure Pass-Through HTML

Not only is this method simpler than creating a custom REST service column, it’s also more secure. With this method of generating the image tags, we did not have to set the grid’s escapeHTMLInData property to true.

Formatter functions can be used to return much more pass-through HTML content without having to expose the grid to potential code injection problems.


Data Display Controls in Notes9 – List of Property Updates

$
0
0

I’ve spent a lot of time over the last several months reviewing the features data display controls in XPages. I’ve used or at least tested them all with 8.5.2 and 8.5.3 Upgrade Pack 1, but I wanted to take a look to see what new properties became available in Notes9.

After reviewing the display controls property-by-property between 8.5.3 UP1 and Notes 9, I’ve compiled a list of new properties available in R9:

 
 
 
 
 
 
 
 
 
 
 
 
 

Crickets.

There is not a single new or removed property in all of the controls I checked! (View Panel, Repeat, Data Table, Data View, Dojo Data Grid, iNotes List View, Dynamic View Panel)

The only difference I found was just a slight reorganization of a small set of properties on the iNotes List View control.

DisplayControls_PropertyUpdatesInR9

I guess the good news is that there’s no learning curve to use them effectively in Notes9!


XPages Tip: Using a SwitchFacet Control to Change Menus in a OneUI Application Layout Control

$
0
0

The OneUI Application Layout control has a built-in facet for left-side navigation. It is common to create a single navigation menu in that facet. However, rather than create one giant menu with lots of hide formulas, I prefer to create separate menu custom controls for each section of the application and use a Switch Facet control to determine which menu to display, based on the selected tab in the application.

Within the xe:switchFacet tag, there are two key tags. The xp:this.facets tag defines the options to display in the switch facet control. In this example, each option is a separate custom control. The xp:key property just gives the facet a name. The xp:this.selectedFacet tag contains the logic to compute the facet to display, based on the facet names defined in the xp:key properties of each facet.

Notes that sessionScope.AppTab is the scope variable where I store the name of the selected tab in the application layout. Update that line for wherever you store that property. I believe you can also programmatically change the selected facet if you so choose.

<xe:switchFacet id="switchFacet1" defaultFacet="menu1">
  <xp:this.facets>
    <xc:ccMenu1 xp:key="menu1" />
    <xc:ccMenu2 xp:key="menu2" />
    <xc:ccMenu3 xp:key="menu3" />
    <xc:ccMenu4 xp:key="menu4" />
    <xc:ccMenu5 xp:key="menu5" />
  </xp:this.facets>

  <xe:this.selectedFacet><![CDATA[#{javascript:
    var tab = sessionScope.AppTab;
    var menu;
    if (!tab) menu = 'Tab1';
    switch (tab) {
      case 'Tab1':
        menu = 'menu1';
        break;
      case 'Tab2':
        menu = 'menu2';
        break;		
      case 'Tab3':
        menu = 'menu3'
        break;
      case 'Tab4':
        menu = 'menu4'
        break;
      case 'Tab5':
        menu = 'menu5';
        break;
      default:
        menu = 'menu1';
        break;
      }
    return menu;}]]>
  </xe:this.selectedFacet>
</xe:switchFacet>

XPages Tip: Working around a Bug to Allow Collapsing Links within a Container in an xe:navigator control

$
0
0

I noticed a bug when working with an xe:navigator control with container nodes. The properties panel property does allow it to be expandable in 8.5.3 UP1.

By default, the navigator’s expandable property is set to true. When you add container nodes to a navigator control, you would expect that they would be expandable.

navigator_1

Here’s the source for a navigator with two levels of nested nodes.

<xe:navigator id="navigator1">
  <xe:this.treeNodes>
    <xe:basicLeafNode label="Link 1"></xe:basicLeafNode>
    <xe:basicLeafNode label="Link 2"></xe:basicLeafNode>
    <xe:basicContainerNode label="Container 1">
      <xe:this.children>
        <xe:basicLeafNode label="Child Link 1.1"></xe:basicLeafNode>
        <xe:basicLeafNode label="Child Link 1.2"></xe:basicLeafNode>
        <xe:basicContainerNode label="Container 2">
          <xe:this.children>
            <xe:basicLeafNode label="Link 1.2.1"></xe:basicLeafNode>
            <xe:basicLeafNode label="Link 1.2.2"></xe:basicLeafNode>
          </xe:this.children>
        </xe:basicContainerNode>
      </xe:this.children>
    </xe:basicContainerNode>
  </xe:this.treeNodes>
</xe:navigator>

The Problem

Even though the navigator’s expanded property was set to true, the container nodes are not collapsible — they default to being fully expanded.

navigator_2

However, I noticed that when the expandable property is deselected, an expandable attribute is added to the navigator tag and set to false. This changes line 1 from the source above to this:

<xe:navigator id="navigator1" expandable="false">

But when the property is re-selected, the expandable attribute disappears.

The Solution

The good news is that there’s an easy solution. All you need to add the expandable attribute back to the navigator tag and set it to true.

<xe:navigator id="navigator1" expandable="true">

Now there are twisties next to the container nodes that provide the ability to collapse them.

navigator_3

Another Solution

If you set the expandable property of the navigator under All Properties, it also works to set the proper attribute on the navigator.

navigator_4


Dojo Data Grid – Part 21: Locking Columns

$
0
0

In a recent post, Mark Roden showed how to horizontally lock columns in an ExtJS grid, which has the same effect as Freeze Panes in Excel. In this post, I’ll show how to achieve the same functionality with the Dojo DataGrid.

Dojo Data Grid Series

LockedCols_A

When the grid has locked columns, a horizontal scroll bar will appear to scroll unlocked columns when there isn’t enough screen real estate to display all of the columns. In the screen shot above, the first two columns are locked and the rest will scroll.

Based on the Dojo DataGrid documentation this seems like it should be a relatively easy feature to implement. With a declaratively-or programmatically-defined grid, it’s pretty simple. Unfortunately, I don’t see a way to make it work with the Dojo Data Grid control in XPages.

Declarative Definition

In a declaratively-defined grid, you add colgroup tags before the tags defining the grid columns. For example, if you have a 6-column grid and you want to freeze the first column, you’d add this before the thead tag in the grid definition:

<colgroup colspan="1" noscroll="true"></colgroup>
<colgroup colspan="5"></colspan> 

Programmatic Definition

You can implement the same effect in a programmatically-defined grid by modifying the structure property to list two groups of cells (one set to freeze and one set to scroll), rather than just defining the grid rows.

An example of this method will be shown in the solution below.

Problem with the Dojo Data Grid Control

Unfortunately, there doesn’t appear to be a way to modify the output of the Dojo Data Grid control in a similar manner. I tried adding colgroup tags within the grid tag but before the column tags, but the colgroup tags were not rendered. I also tried other methods of running client-side javascript after the page loads to insert the tags and re-draw the grid, but none of it was effective.

The Solution – Programmtically Define the Grid

So, for the time being, the way to implement column locking in the grid is to programmatically declare your grid instead of using the Dojo Data Grid control.

It’s not as complicated as it might sound, but it’s definitely more work than using the grid control.

Here is the source of the entire page that generated the grid shown in the screen shot above.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
  xmlns:xe="http://www.ibm.com/xsp/coreex" dojoParseOnLoad="true"
  dojoTheme="true">

  <xe:restService id="restJsonService" pathInfo="gridData">
    <xe:this.service>
      <xe:viewItemFileService defaultColumns="true"
        var="gridEntry" viewName="ByName-First" count="10000">
      </xe:viewItemFileService>
    </xe:this.service>
  </xe:restService>

  <xp:this.resources>
    <xp:dojoModule name="dojox.grid.DataGrid"></xp:dojoModule>
    <xp:dojoModule name="dojo.data.ItemFileWriteStore"></xp:dojoModule>

    <xp:styleSheet
      href="/.ibmxspres/dojoroot/dojox/grid/resources/tundraGrid.css">
    </xp:styleSheet>
  </xp:this.resources>
	
  <xp:eventHandler event="onClientLoad" submit="false">
    <xp:this.script><![CDATA[		
      var jsonStore = new dojo.data.ItemFileWriteStore({ url: "Grid_21_LockColumns.xsp/gridData"});

      var layout = [
        {cells:[ [ 
          {field:'firstname', name:'First',width:'8em'}, 
          {field:'lastname', name:'Last', width:'10em'}
          ] ], noscroll:true 
        }, 
        {cells:[ [ 
          {field:'address', name:'Address',width:'18em'},
          {field:'city', name:'City',width:'10em'},
          {field:'state', name:'State',width:'5em'},
          {field:'zip', name:'Zip',width:'7em'}
          ] ] 
        }
      ];
	
      grid = new dojox.grid.DataGrid({
        store: jsonStore,
        structure: layout,
        rowsPerPage: 25,
        autoHeight: 15
      }, '#{id:gridNode}');

      grid.startup();
    ]]></xp:this.script>
  </xp:eventHandler>

  <xp:div id="gridNode"></xp:div>

</xp:view>

Lines 3 and 4 show the two dojo properties that need to be set at the page level. Since we’re not using the Dojo Data Grid control, we have to set these properties manually.

Lines 6-12 define the REST service that provides the data for the grid

Lines 14-21 include the resources required. This generally isn’t required when using the grid control, because some resources are automatically included by the control.

Lines 23-51 include the code that defines the grid. The code runs on the page’s onclientload event.

  • Line 25 sets up the data store for the grid. It reads data from the REST service on the same page.
  • Lines 27-40 define the grid structure. The first group of cells has the noscroll attribute set to true. This locks the columns in place.
  • Lines 42-47 create the grid and set the key properties
  • Line 49 generates the grid.

Disclaimer

This is a proof-of-concept to show the column locking feature. I realize that creating the REST service and setting its count to a high number to include all records while also not using a RESTful data store defeats the purpose of lazy loading. However, I had issues getting the REST data store to display data in the grid.


XPages Tip: Check whether your Checkbox is Checked with SSJS

$
0
0

If you’re not used to the fact that all component values are treated as strings by default, it can seem strange when you check the value of a checkbox component and expect a boolean return value.

I was recently working with a form that used a checkbox for one field. As I wrote SSJS code to check whether the checkbox was selected, I was reminded that comparing the value of that field is not done the way I would naturally expect. I would expect that code like this would work to execute logic based on the state of the checkbox:

if (getComponent('checkBox1').getValue()) {
  // checkbox selected - do something
} else {
  // checkbox not selected- do something else
}

This doesn’t work as expected. The condition in line 1 above will always evaluate to true.

In this next block of code, the condition will always evaluate to false and the ‘else’ block will always execute:

if (getComponent('checkBox1').getValue() == true) {
  // checkbox selected - do something
} else {
  // checkbox not selected- do something else
}

This is because component values are always treated as strings (on the front end, they’re not converted yet). Therefore, if you don’t explicitly define selected and unselected values for the checkbox, then it’s value will either be “true” or “false”. (Even if you compute the values for the checkbox and set them to boolean values, they’re still treated as strings when checking the component.)

So, if you want to check whether the checkbox is selected, you need to use code like this:

if (getComponent('checkBox1').getValue() == "true") {
  // checkbox selected - do something
} else {
  // checkbox not selected- do something else
}


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

$
0
0

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:

Dynamic Row Styling Part 1 - View

Here’s a Repeat Control:
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.

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.

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.

Dynamic Row Styling Part 1 - Repeat


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

$
0
0

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:

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.


Upcoming TLCC Webinar: Dojo Grids in XPages

$
0
0

Join me next Thursday (6/27) at 10:30am Eastern for the next TLCC webinar.

I’ll show how you can provide a new look and feel and rich functionality with several variations of Dojo data grids. You’ll learn about the XPages Dojo Data Grid control and its key features, including infinite scrolling, sorting, and editable cells. Then you’ll see how to transform the grid into a Dojo EnhancedGrid and take advantage of enhanced plugins to provide even more functionality, such as multi-rule filtering and context menus. Finally, you’ll get a glimpse of how to bypass the grid control in order to create a categorized Dojo TreeGrid.

Click here to register.


Dojo Data Grid – Part 22: Using a Date Picker in an Editable Column

$
0
0

Plain text, checkbox, and drop-down list are the options built into the dojo data grid control for editable column field types. In this post, I’ll show how you can use a date picker to edit a column in Notes 9.

Grid - Edit Date Widget 1

Dojo Data Grid Series

Dojo Grids in XPages — All Blog Posts

Implementation

There are two steps required to make this work.

1) Add the dojox.grid.cells.dijit module to the XPage. (Resources > Add > Dojo Module…)

Grid - Edit Date Widget 2

2) Set a date column’s editable property to true and enter dojox.grid.cells.DateTextBox in the column’s cellType property. (You’ll have to type it in directly — it’s not an option in the list.)

Grid - Edit Date Widget 3

That’s it!

Only Works in Notes 9

This feature was only mentioned in the Dojo DataGrid documentation as of version 1.7. Notes 8.5.3 uses dojo 1.6, so it doesn’t work properly there. It will display the date picker and even display the updated date value in the cell after it’s edited, but it fails when saving the updates.

Formatting the Date Column

Of course, you’re generally not going to leave your dates in this format, because they’re tough to read. In the documentation there is information about column-formatting functions for date fields.

These functions seem to initially work fine for the columns in R9 (it requires overriding he date text box’s default function to return a value, which can be done onclientload). However, when the column value is edited and a new date is selected, it’s no longer in the original format, so it doesn’t get re-formatted properly and the column appears to be blank (and an error about retrieving a date object is thrown quietly). The good news is that the value is still saved properly, so it’s still working in that case.

I tried to tweak the code to trap for that date error and just return the value as is in that case, but I haven’t had success with it yet.

The formatting functions supplied in the documentation work perfectly fine for a straight Dojo DataGrid implementation (i.e. not using the Dojo Data Grid control). The grid control just can’t implement them in the same way because there’s neither a getValue property to attach to a column nor a property to attach dojo attributes.


Dojo Data Grid – Part 23: Using a ComboBox in an Editable Column

$
0
0

In the last post, I showed how to use a date picker in an editable column. In this post, I’ll show how you can use a ComboBox in an editable grid column. The date picker required Notes 9 to work, but the ComboBox works in 8.5.3.

Dojo Data Grid Series

Dojo Grids in XPages — All Blog Posts

Implementation

There are three steps required to make this work.

1) Add the dojox.grid.cells.dijit module to the XPage. (Resources > Add > Dojo Module…)

Grid - Edit Date Widget 2

2) Set a date column’s editable property to true and enter dojox.grid.cells.ComboBox in the column’s cellType property. (You’ll have to type it in directly — it’s not an option in the list.)

Grid_23_A

3) Provide options for the ComboBox. These can be computed with SSJS in the options property — the same way it’s done for a Select cell.

The Result

Now, you have a ComboBox, where the user can type as well as select from the list.

Grid_23_B

As the user types, the options in the list are filtered down, so this is a nice step up from the standard Select field.

Grid_23_C


Dojo Data Grid – Part 24: Using a Multiline Editor in an Editable Column

$
0
0

In the last post, I showed how to use a ComboBox in an editable column. In this post, I’ll show how you can use a multiline editor in an editable grid column.

Dojo Data Grid Series

Dojo Grids in XPages — All Blog Posts

Multiline Editor Features

A standard editable cell is a single line. When you hit the [Enter] key while typing, it will finish editing the cell and jump to the next editable cell.

Grid_24_A

With the Editor cell type, you get a multiline editable cell. You can hit the [Enter] key to start a new line. If you take up more space than is available, you will get a scroll bar, so this is much more useful for editing larger pieces of text in the grid.

Grid_24_B

Implementation

There are two steps required to make this work.

1) Add the dojox.grid.cells.dijit module to the XPage. (Resources &gt; Add &gt; Dojo Module…)

Grid - Edit Date Widget 2

2) Set a date column’s editable property to true and enter dojox.grid.cells.Editor in the column’s cellType property. (You’ll have to type it in directly — it’s not an option in the list.)

Grid_24_C


XPages Tip: EL Statements Do Not Work in JavaScript Library Functions

$
0
0

You may already be aware that you can use Expression Language (EL) to evaluate information on the server into client-side JavaScript code. However, this does not work in a library function. This post explains how modularize your code and still use EL to read server-side information into client-side JavaScript.

Here’s how it usually plays out:

  1. You realize that you need to access the value of an xp:inputText control in client-side JavaScript and use EL syntax in button code to retrieve that value. It works well.
  2. You decide that you’re going to be a good developer and modularize your code for reuse, so you move it to a script library.
  3. The code fails at the point where it tries to evaluate the EL statement.
  4. <facepalm>
  5. You update the library function to accept values evaluated with EL on the page to make it work properly.

An Example

Here’s a page with an xp:inputText (inputText1) and an xp:button to display the value.

alert('Value: ' + dojo.byId('#{id:inputText1}').value);

Blog_EL_CSJS_1

This works because #{id:inputText1} is evaluated on the server and it returns the actual client-side ID for that element and passes it into the client-side javascript code on the button.

When the page is rendered, it generates this function (and then uses dojo to attach it to the button):

function view__id1__id4_clientSide_onclick(thisEvent) {
  alert('Value: ' + dojo.byId('view:_id1:inputText1').value);
}

Notice that there’s a fully-fleshed out ID where I previously had an EL statement.

If I want to use that code more than once, I’d want to move it to a script library function:

function displayFieldValue() {
  alert('Value: ' + dojo.byId('#{id:inputText1}').value);
}

But when I try it, it doesn’t work. Nothing happens in the UI, but there’s an error that I can see in Firebug:

Blog_EL_CSJS_2

The error says that it couldn’t find the field so I can display the value. This is because EL statements do not evaluate in script library functions. They only evaluate in code that is on the page.

Modularization

There is a middle ground, though. If you have code that you want to re-use, you can still move it to a script library — you just have to evaluate EL statements on the page and pass them into the library function.

In this same example, change the library function as shown:

function displayFieldValue(fieldID) {
  alert('Value: ' + dojo.byId(fieldID).value);
}

Then call it from the button like this:

displayFieldValue('#{id:inputText1}');

And now it works!



Fix Indenting on Multiple Categories in a View Panel

$
0
0

I previously demonstrated a solution for fixing category indenting in a view panel that contains a totals column. In this post, I’ll provide an updated version of the code that handles indenting multiple columns properly.

For more insight into the logic, please take a look at that previous post.

Code

Here is the code that fixes the category indentation:

function fixViewIndentation(categoryClass) {
  // Get a list of all rows in the view panel.
  dojo.query('.xspDataTableViewPanel table tr').forEach(function(nodeRow, indexRow) {
	
  // Locate the category cells within the context of the view rows
  dojo.query('td.' + categoryClass, nodeRow).forEach(function(nodeCat){
    // Execute a search on the parent node (row) and remove all cells until data is found   
    var emptyCells = 0;
    var countCells = false;
    dojo.query('td', nodeRow).forEach(function(nodeTD, indexTD){
      // Start counting when the first category cell is found (this is more for levels after the top-level categorization)
      // Check all non-category cells until a non-empty cell is found
      if (dojo.hasClass(nodeTD, categoryClass)) {
        countCells = true;
      } else if (countCells){          
        if (nodeTD.innerHTML == '') {          
          emptyCells +=1;
          dojo.destroy(nodeTD);
        } else {
          countCells = false;
        }
      }    
    });
    // Add a colspan attribute to the category cell (1 + [# of empty cells])  
    // dojo.attr(nodeCat, 'colspan', 1+emptyCells);
    nodeCat.colSpan = 1+emptyCells;
  });
  });	
}

Custom Control for Easy Reuse

In order to reuse this feature easily, I put the above function in a client JavaScript library called viewPanel.js and I created this custom control (named ccViewIndentationFixer) that will execute the code onClientLoad.

Here’s the full source of the custom control:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
  <xp:this.resources>
    <xp:script src="/viewPanel.js" clientSide="true">
    </xp:script>
  </xp:this.resources>
  <xp:eventHandler event="onClientLoad" submit="false">
    <xp:this.script><![CDATA[fixViewIndentation("CATEGORY_CLASS_NAME");]]></xp:this.script>
  </xp:eventHandler>
</xp:view>

The call to the fixViewIndentation() function must pass the name of the class used on the category columns that need to be indented. (I showed how I set the category column class in the previous post.)

Per a great tip from Sven Hasselbach, I’ve learned that the best way to use it is to include it within a view panel facet by putting this in the page source:

<xp:viewPanel ...>
  <xp:this.facets>
    <xp:pager...></xp:pager>
    <xc:ccViewIndentationFixer xp:key="south" />
  </xp:this.facets>
</xp:viewPanel>

Data View Bug — Can’t Expand/Collapse Details with Custom Data Collection

$
0
0

I’ve run into an apparent bug with the Data View control (in 8.5.3 UP1 and 9) that I’d like to document and see if anyone has a solution. When you pass a custom data collection to the view (whether it be an array or event a collection of view entries) rather than using a view as the data source, the expand/collapse link to toggle the visibility of the details section does not work.

When you click on the expand/collapse link, it generates a partial refresh POST request (as seen in Firebug), but nothing changes on the screen.

I looked at the source and I can see why — the links that trigger the partial refresh are not named properly.

The expand/collapse links end up looking like this (should have a row number at the end, rather than null):

<a id="view:_id1:dataView1:0__show:null"

And the corresponding XSP.attachPartial function is even more messed up because it has the incorrect link name as the first parameter and has the data view ID as the last parameter (but it should be the ID of the row to refresh):

XSP.attachPartial("view:_id1:dataView1:0__show:null", null, "view:_id1:dataView1", "onclick", function(){}, 0, "view:_id1:dataView1");

I spent some time working on dojo code to replace the IDs, but to no avail. I even wrote code to copy the link, fix the ID, remove the original link, then generate the proper XSP.attachPartial function call on the fixed link. That kind of improved it, but when the view refreshed the target row disappeared completely — there was nothing at all in the response of the POST request. My assumption is that, even though I fixed it on the front end, when it got to the back end, it re-generated the bad IDs.

The toggleDetailVisible() and setDetailVisible() methods of the Data View also do not work. (toggleDetailVisible() seems to be broken completely in R9, even in a normal Data View.)

I can work around the issue, but I don’t want to have to set the detailsOnClient property and pre-load all of the details sections. While that would make it easy to manage toggling visibility with my own links (since the details would already be on the page but hidden), I don’t think that’s ideal from a performance perspective.

Confirmation? Suggestions?

Let me know if you’ve seen this issue, so I can confirm it. If you know of a way to fix it — even better!

I’ve posted this issue (albeit with less detail) in the XPages forum a few days ago as well: http://www-10.lotus.com/ldd/xpagesforum.nsf/xpTopicThread.xsp?documentId=2679178BDB872A2B85257BA0006D8A50


Article Published in The View: Flexible Form Validation in XPages

$
0
0

My article on creating a flexible form validation model in XPages has been published in The View (subscription required). It describes a model can handle complex validation rules, but still provides ease of maintenance by being centralized and structured in a highly-readable manner.

Abstract

Form validation plays a large part in enforcing data integrity in your applications. It is important to properly implement validation logic, but managing all the rules can be complicated, especially if the logic is spread over a large number of locations.

This article discusses the shortfalls of existing validation methods and demonstrates tactics to lower costs for development and maintenance by implementing a flexible custom XPages validation framework that has a well-organized structure.

Excerpt

An excerpt from the article has been published on SocialBizUG.org.


NotesIn9 Episode 120 – XPages Data View Control Part 2: Customization

$
0
0

The second part of my two-part video series on the XPages Data View control has been published on NotesIn9. This episode focuses on how to take advantage of the flexibility of the control in order customize the display and add useful features.

Here’s a list of the topics covered in the video:

  • Customized Summary
  • Customized Details
  • Custom Link to Expand/Collapse Details on MouseOver/MouseOut (if details preloaded)
  • Pager Add Rows Control
  • Pager Save State Control
  • Search
  • Multiple Column Layout

XPages Tip: Dependent Drop-Down Lists

$
0
0

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>

Viewing all 216 articles
Browse latest View live