Archive | January 2014

Changing the Output Tag on a Computed Field in XPages

By default, a computed field writes out a <span> tag to the page around the contents of the field, but you can use the tagName property to change that.

For example, this computed field…

<xp:text escape="true" id="computedField1" 
  value="#{javascript:return 'Hello, World';}">
</xp:text>

… will result in this html:

<span id="view:_id1:computedField1" class="xspTextComputedField">
  Hello, World
</span>

But, if you change the tagName property to something else — for example a list item (li)…

<xp:text escape="true" id="computedField1"
  value="#{javascript:return 'Hello, World';}" 
  tagName="li">
</xp:text>

… it will produce this html:

<li id="view:_id1:computedField1" class="xspTextComputedField">
  Hello, World
</li>

The class name stays the same, but the surrounding tag changes. This can come in very handy.

Dojo Data Grid – Part 35: Suppressing Blank Rows Due to Readers Fields

Over the last few months, I’ve received several messages and read several posts trying to figure out how to suppress blank rows in a grid due to Readers field security on the documents in the underlying view. In this post, I’ll share an anticlimatcally-simple solution to this white whale of a problem.

Dojo Data Grid Series

Dojo Grids in XPages — All Blog Posts

The Problem

When you have a Dojo data grid that displays entries from a view via a REST service, you will see one row for each entry in the underlying view. If there are documents that are hidden from the user due to readers field security, you will still see a row for each of those documents, but they will be virtually blank (displaying … in each cell).

So, if you have 10,000 documents in a view, but the user can only see 100 of them, there will be 9,900 blank lines to scroll through in the view.

It’s a maddening issue. I’ve heard multiple people say that they stopped trying to use a dojo grid because of this.

Attempted Solutions

I spent hours trying to handle this issue in numerous ways.

I tried code that tried to check the row contents (similarly to this post) to apply a css class to hide it when blank, but I found that *every* row starts out with the default cell content (…) and then, on a second pass, fills in the real data. When I looked for an empty row and applied the class, it would hide every row before the valid ones were filled in.

I made several attempts at writing code that scanned the current set of records in memory (a block of rows the same size as the rowsPerPage property), but I found that the block of rows in memory was constantly shifting, so I couldn’t check the current block and suppress rows as needed dynamically.

I also noticed that the grid’s row.index property and the @Position attribute of a grid entry got out of synch as soon as there was a document hidden due to reader security. However, it didn’t display the blank rows inline — it moved them all to the end. This was also very problematic in checking data and determining what to suppress.

Ultimately, I realized that I can’t help the fact that the built-in REST service appears to look at view meta data and tell the REST service that it’s returning the number of elements corresponding to the total number of documents in the underlying view, regardless of the security.

A Solution

Then it occurred to me that I wouldn’t have to try to scan through the rows in memory if the entire set was in memory. Then it would just be a matter of counting how many actual rows were generated and hiding the rest.

I noticed that the REST service doesn’t actually include blank rows, but it does include a row count at the beginning that tells the grid how many rows to render. The grid will include all valid rows and then fill in the rest with blank rows.

To solve the problem, you can load all rows into memory and the use the onStyleRow event handler (see this post for more info) to hide the blank rows (which come after the actual row count has been reached).

Follow these two steps to implement the solution:

1) Set the rowsPerPage property of the grid to a number that is equal to (or greater than) the number of rows that could be included in the grid

2) Put this code in the onStyleRow property of the grid:

var row = arguments[0];

if (row.index >= restViewItemFileService['_items'].length) {
 row.customStyles += 'display:none;';
}

This assumes that you’re using a restViewItemFileService type of REST service. The second line would be different for a viewJsonService.

In the code above, ‘restViewItemFileService’ is the name of the REST service. Change it to match the ID of your rest service.

All in all, I spent hours and probably wrote a few hundred lines of code in various attempts in order to come up with what was ultimately a property change and a 4-line solution!

Caveat

Performance is certainly a big factor in whether this solution will work in your grid, because all documents must be loaded into memory rather than pre-loading a small chunk and then loading the rest on demand.

Another Potential Approach

Another approach that may work (but one that I have not yet tried) would be creating a custom REST service that only returns rows based on the current user’s security. That would seem to be a valid approach, but this post was focused on solving the problem with the provided view REST service types.

Creating an Application Layout with CSS/HTML Provided by a Designer

I recently started working on an XPages application for which the UI had already been designed by a visual designer. In this post, I’ll cover the steps I took to implement the provided design within the application.

I must admit, it was a pleasant surprise to discover that sample pages had already been created to define the UI of the application; the visual designer sent me a list of HTML files, images, and style sheets. In my experience, this has not been the norm, so I took a few minutes to think through how to make the best use of those files as efficiently as possible.

I was involved in a project awhile back where a UI was mocked up and CSS was provided, but it was left to the developers to get the XPages application looking just right. It can be a challenge to try to make sense of the massive style sheets that are generated by some UI design tools (which seem to add many levels of div tags to get everything just so). Fortunately, in this case, I was given not only CSS but also sample pages.

In short, my goal was (a) to get the static page and styles working in an XPages application, (b) to modularize the layout, and (c) make it dynamic one piece at a time.

Here is the approach that I took:

A. Implement Static HTML in XPages

    1. Import CSS and images into the application
    2. Account for everything included in the <head> section; create a Theme that includes the CSS in the application (and set the application to use the theme)
    3. Copy the HTML from a static page into a custom control called ccLayout
    4. Create an XPage that contains ccLayout
    5. Clean up the HTML in ccLayout as needed (adding closing tags where missing, etc)
    6. Test the page and fix CSS references as needed (many image references are looking for subfolders, but those directory paths can be modified once image resources are in the app)

At this point the goal is to have the XPage looking exactly like the sample page. Once you’ve achieved that, you’re ready to modularize it.

B. Modularize the Layout

    1. Break up the static html into logical chunks and move the chunks to separate custom controls for each component of the layout. Some examples:
      1. Title bar
      2. Top navigation
      3. Search bar
      4. Side navigation
      5. Footer
    2. Replace those chunks of HTML with references to the layout component controls in the correct areas within the main layout control
    3. Move the main content HTML out to a separate control
    4. Put an Editable Area control in place of the main content area.
      1. The main content will change from page to page, so the editable area lets you keep the same general layout, but put different content in the layout on each page, all while reusing a single layout custom control
      2. If multiple areas of the layout change from page to page, then use more editable areas

Now, you have an application layout control and separate controls for components reused throughout the layout. You can now reuse the layout easily throughout the application.

Ultimately, this UI is not based on OneUI or Bootstrap or any other framework. It’s a fully-customized design. Some of the concepts that Paul Della-Nebbia and I covered at our MWLUG session and TLCC webinar on creating custom layouts are foundational to this approach of creating a custom application layout.

Here are the slide show and webinar recording if you’d like to take a look.

C. Make the Application Dynamic

  1. Go back through the layout controls and build in live links, etc as needed
  2. Build the content custom controls (forms, views, etc)

Now, for each form or view that you need to display, all you have to do is create an XPage, add the ccLayout control, and then drop in the form or view custom control into the editable area and you’ll have a consistent UI throughout the application.

It doesn’t seem very complicated looking back at the list of steps, but it was a good exercise to go through.

Feedback

I’m curious — have you ever been provided a working static UI for a web application? If so, how did you go about using it?

Presenting at Connect: Uno! Deux! Three! Making Localization of XPages Apps as Easy as 1-2-3

Connect is less than a week away! I’m looking forward to having my first opportunity to present at the conference formerly known as Lotusphere. It will be a Best Practices session on making XPages applications work well in multiple languages, which is an important topic in an increasingly global world of business. The built-in localization features are great, but there are more steps you can take to improve upon it in order to translate more aspects of each application.

I’ll be co-presenting with Kathy Brown (I don’t want to give away too much, but one of us will have blue hair).

Stop by and say hello. I look forward to meeting you there!

Date/Time

Wednesday 04:15 PM – 05:15 PM @ Dolphin S. Hem II

Abstract

Social Business doesn’t only happen in your native language. Developing an app that translates well into multiple languages requires more than just the application-level localization features built into XPages. Labels on forms and views columns are a big part of the story, but there’s much more to consider! Keywords (on forms and views), menu options, and programmatic error/information messages are some of the additional important aspects to consider when developing an application that is highly readable in each supported language. In this session, you’ll learn a proven strategy for managing all of these values for multiple languages and handling the locale-specific display of each value using resource bundles and a few simple library functions.

Dojo Data Grid – Part 34: Customizing Cell Styles Based on Data

In a previous post, I showed how to set the row color based on data in the row. In this post, I’ll show how style a cell individually based on the data that it contains.

Dojo Data Grid Series

Dojo Grids in XPages — All Blog Posts

Formatter Function

The key to this solution is a formatter function, which is a client-side JavaScript function that can be used to process a grid cell. (See this post for another example of how to use it.)

All you have to do is specify the name of the function on the grid column’s formatter property and and it will run on every cell as it is rendered. You can use a formatter function to modify the cell data or change the styling. The cell will display the value that is returned, to return the original value if you do not modify it.

Grid34_B

Below are examples formatter functions that use inline styles and CSS classes. Both use different approaches to change the font color to red if the name starts with ‘Ad’. The formatter function automatically receives a handle to the cell itself when its called, so you can use either the customStyles property or the customClasses property of the cell to modify the styles.

<script>
  function colorCell_InlineStyle(value, rowIndex, cell) {
    if (value.substr(0,2) == 'Ad') {
      cell.customStyles.push('color:red');
    }	
    return value; 
  }
		
  function colorCell_Class(value, rowIndex, cell) {
    if (value.substr(0,2) == 'Ad') {
      cell.customClasses.push('redCell');
    }
    return value; 
  }
</script>

Grid34_A

Conditionally Render a Passthru tag within the body of an XPage.

I recently had a need to conditionally include a new client-side javascript library on an XPage after another library was loaded. The other library was loaded within the body of the page and not within the header, so, in this case, it did not work to include the new library as a page resource.

However, when I put an <xp:script> tag within the body of the page, I received this error:

The complex type tag (xp:script) must be within a property tag, like <this.something>

Normally, those tags appear within <this.resources>.

So, I needed a way to conditionally include a passthru <script> tag to load the library.

My solution was to wrap it within an <xp:span> tag. Since it’s an xp tag, I can use a server-side rendered formula. If it returns true, then the passthru script tag will be added to the page. Admittedly, it feels like a bit of a hack, but it does the job:

<xp:span rendered="#{javascript:context.getUserAgent().isIE()}" >
	<script src="MyLibrary.js" />
</xp:span>

For the record…

I’m not advocating this as ideal design. But if there is some case where you need to conditionally include a script tag within the bod of an XPage, this will do the trick. In my case, I was updating an application and needed to keep the changes to a minimum.

Library Relative Reference

There’s a secondary tip in here, which Marky Roden pointed out. With a passthru <script> tag, do not start the src url with a backslash (/), even though that’s what the <xp:script> tags do. On a passthru <script> tag, that will be relative to the server root.

Dojo Data Grid – Part 33: Reading Data from a Custom REST Service

Setting the data source of a grid is generally very straightforward when using one of the provided view or Json rest services, but it doesn’t work the same way for a custom REST service. In this post, I’ll show how to get it to work.

Dojo Data Grid Series

Dojo Grids in XPages — All Blog Posts

Error Binding to a Custom REST Service

Normally, you add a REST service to the page to provide data and select it in the storeComponentId of the grid (as shown in ), but it throws an error when you do this with a custom REST service. (I tried binding the REST service to the grid based on both the id and jsId properties of the custom REST service, but the result was the same.)

The grid would not display on the page and Firebug would show this error:

ReferenceError: restService1 is not defined

Solution

In order to find a solution, I did some digging into what a pure dojo grid needs (outside of XPages) for a JSON data store.

Key points in the solution:

  • Include two dojo modules in the page resources to set up the data store
  • A pass-thru script tag with code to set up a JSON data store for the grid (uses the dojo modules that the resources specify)
  • The grid’s store property is set to the variable set up for the data source in the tag. (storeComponentId needs an XPages component name.)

Below is the entire source of an XPage that demonstrates this technique.

Lines 05-08 include two dojo modules that you need in order to set up the data source.

Lines 10-46 define a custom REST service. The pathInfo value in line 10 will be used to read the data. The majority of this is hard-coded data, which you would replace with your code to provide data for the grid.

Lines 48-53 are a standard client-side script tag that set up the data store and make it available for the grid. The jsonStore object reads the REST service data via URL path, so it needs the XPage name and the pathInfo value for the REST service. Modify this line as needed to point to the page and REST service.

Lines 55-58 define the grid, but the key is that line 55 sets the store property to the name of the JavaScript object defined in line 52.

<?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">

  <xp:this.resources>
    <xp:dojoModule name="dojo.store.JsonRest"></xp:dojoModule>
    <xp:dojoModule name="dojo.data.ObjectStore"></xp:dojoModule>
  </xp:this.resources>
	
  <xe:restService id="restService1" pathInfo="gridData">
    <xe:this.service>
      <xe:customRestService contentType="application/json"
        requestVar="customData2" requestContentType="application/json">
      <xe:this.doGet><![CDATA[#{javascript:// Create hard-coded test data
var jsonData = [];

var thisEntry = {};
thisEntry.Status = 'Open';
thisEntry.Name = 'John';
jsonData.push(thisEntry);

thisEntry = {};
thisEntry.Status = 'Closed';
thisEntry.Name = 'Bill';
jsonData.push(thisEntry);

thisEntry = {};
thisEntry.Status = 'Closed';
thisEntry.Name = 'Mike';
jsonData.push(thisEntry);

thisEntry = {};
thisEntry.Status = 'Open';
thisEntry.Name = 'Jim';
jsonData.push(thisEntry);

thisEntry = {};
thisEntry.Status = 'Open';
thisEntry.Name = 'Steve';
jsonData.push(thisEntry);

return toJson(jsonData);}]]>
        </xe:this.doGet>
      </xe:customRestService>
    </xe:this.service>
  </xe:restService>
	
  <script>
    var jsonStore = new dojo.store.JsonRest(
      {target:"CURRENT_PAGE_NAME_HERE.xsp/gridData"}
    );	
    var dataStore = dojo.data.ObjectStore({objectStore: jsonStore});
  </script>
	
  <xe:djxDataGrid id="djxDataGrid1" store="dataStore">
    <xe:djxDataGridColumn id="djxDataGridColumn2" field="Name"></xe:djxDataGridColumn>
    <xe:djxDataGridColumn id="djxDataGridColumn1" field="Status"></xe:djxDataGridColumn>
  </xe:djxDataGrid>
</xp:view>