Archive | SSJS RSS for this section

XPages Tip: Getting the Value of the Current Field with SSJS (@ThisValue equivalent)

Sometimes, I miss the convenience of several @functions. If you’re writing SSJS code that needs to access the value of the current field from an event handler on that field, but you don’t want to hard-code the component name (so you can easily reuse it), it would be handy if there was an @ThisValue equivalent. Alas, there isn’t, but, fortunately, you can still easily access the value.

this

The special this keyword is where we need to start. It provides a handle to the current object.

When running code from an event handler, the object type is: com.ibm.xsp.component.xp.XspEventHandler

I find this by using the typeof operator in JavaScript

print (typeof this);

getParent()

The event handler doesn’t have a value — we need to get to the parent component that contains it. That’s where the getParent() method comes in handy. As you would expect, it goes up the chain and gives us a handle to the parent object, which for my sample input field is: com.ibm.xsp.component.xp.XspInputText

This object has a getValue() method to return the value of the component.

Getting the Current Field Value

Putting this together, I can easily get the value with this line:

var thisValue = this.getParent().getValue();

FYI — if you want the ID of the parent control from an event handler, you can use this line:

var thisID = this.getParent().getId();

Advertisements

XPages Tip: Control Declaration Snippets

Control Declaration Snippets give you an easy way to retrieve a properly-typed handle to an XPages control on your page. In this post, I’ll show how to use them and why they are beneficial.

Control Declaration Snippets

From the SSJS script editor, Control Declaration Snippets is one of the Libraries: options on the Reference tab.

It displays an alphabetized list of controls on the page.

ControlDeclarationSnippets1

Double-click any one to insert the control declaration snippet into your script.

This screen shot shows the result of adding snippets for all 7 controls on my sample page:

ControlDeclarationSnippets2

Benefits

This is a convenient way to save a few keystrokes in setting up a handle to a control on your page, but that’s not the only benefit.

Normally, when you set up a variable for a handle to a control, you don’t enter the full data type of that control. Control declaration snippets do that for you. The big advantage is that typed variables provide better typeahead.

Take, for example, these two lines, which accomplish the same thing:

var myInputText = getComponent("inputText1");
var inputText1:com.ibm.xsp.component.xp.XspInputText = getComponent("inputText1");

If you type myInputText. you’ll get some typeahead options because the editor knows that you’re dealing with a some component.

ControlDeclarationSnippets4

However, if you type inputText1. you’ll get many more typeahead options (including event handlers) because the editor now knows exactly what type of component that you’re working with.

ControlDeclarationSnippets3

Displaying a dGrowl message from SSJS

In a recent NotesIn9 video, I showed how to use the dGrowl Dojo plugin to create growl-style messages in XPages. Jesse Gallagher and Frank Van der Linden showed how to trigger growl-style message from server-side code with Java. In this post, I’ll round out the discussion with an SSJS snippet to do the same.

view.postScript()

As in Jesse’s and Frank’s examples, the key method here is view.postScript(). This method adds a client-side snippet to run after a server-side refresh occurs. I believe it has been available since 8.5.3.

As Jesse mentioned, the biggest challenge here is usually escaping characters properly, remembering that you’re using server-side code to write out client-side code.

The concept is simple, though — take the client JS from an example message and, if it only uses single quotes, wrap it in double quotes and pass it through.

var notificationJS = "dg.addNotification('Here is an info message...',{'channel':'info', 'duration': 3000});";
view.postScript(notificationJS);

Full Refresh vs Partial Refresh

This works fine with both full and partial refreshes. However, if you use a partial refresh, you would have the ability to run the script multiple times and display separate messages (as needed), whereas a full refresh would clear all messages because it refreshes the entire page.

XPages Tip: Check whether your Checkbox is Checked with SSJS

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
}

Dojo Data Grid – Part 2: Providing the Data with a REST Service

Data grids need a data store to obtain the information to display in the grid.

An easy way to set one up with XPages by using a REST Service control, which, like the Dojo Data Grid control, is available in the Extension Library and with 8.5.3 Upgrade Pack 1. You can find it in the Data Access section of the Controls view in Domino Designer:

Grid2_1

When you add one to the page, you see this little icon on the Design tab:

Grid2_2

Configuring the REST service

Follow these steps to set up the REST service to provide the data from a view:

1. Select the REST service control and All Properties subtab of the Properties panel, go to basics > service and click the plus (+) icon and select xe:viewJsonService
Grid2_3

2. Set the contentType (basics > service > contentType) to application/json

3. Set the defaultColumns property (basics > service > defaultColumns) to true. This provides the data from all columns in the underlying view, without you having to define them individually. (You can select individual columns or define your own via the columns property, if you choose, but this is the easiest way to get all columns from the view.)

4. Select the target view in the viewName property (basics > service > viewName)

Verifying the REST service

If you set the pathInfo property of the rest service (All Properties > basics > pathInfo), you will have a way to verify the data being returned by the service. This is a very helpful tool not only for troubleshooting, but for gaining an understanding of the data structure that you’re working with.

Grid2_5

To verify the data, load the XPage that contains the REST service and include the pathInfo name in the URL, like this:

server.com/dbname.nsf/xPageName.xsp/pathInfoName

Here is an example of what you will see:

Grid2_6

The pathInfo is not required when working with the Dojo Data Grid, but it is required when consuming the REST service from another library, like ExtJS.

Creating a Custom Column

In addition to (or in lieu of) the default columns, you can define your own custom columns for the REST service. You can use this to compute values that combine information in the underlying view or even look up information from another database altogether, because you use server-side JavaScript.

In order to read data from the view entry, you will need to set the ‘var’ property of the REST service.

For example, if I wanted to add a column that combines the firstname and lastname columns from underlying view, I would need to take these steps:

  1. Set the var property of the REST service (All Properties > basics > service > var)
  2. Add a new column, but clicking the ‘+’ icon in the columns property (All Properties > basics > service > columns)
  3. Click the diamond to compute the value of the column (All Properties > basics > service > columns > restViewColumn[0] > value)
  4. Set the name of the column (this will be the way to reference the column in the grid) under All Properties > basics > service > columns > restViewColumn[0] > name
  5. Enter a script like this:
return restEntry.getColumnValue("firstname") + ' ' + restEntry.getColumnValue("lastname");

Grid2_7

Now, if you verify the REST service data, you’ll see the additional column.
Grid2_8

There does not appear to be a built-in way to get a handle to the underlying document, but you should be able to reference the unid from the view entry and use that to look up the document, should you need more information.

System Columns

The REST service also has a systemColumns property, which allows you to select system values to include with each entry. Clicking the button in the property row will bring up a dialog box with checkboxes for the options to select.

Grid2_4

Here are the options:

  • NoteID
  • UNID
  • Position
  • Read
  • Siblings
  • Descendents
  • Children
  • Indent
  • Form
  • Category
  • Response

As you can see in the screen shots earlier in this post (with the REST service data), several of these system columns are included by default: unid, noteid, position, siblings, form.

If you select one or more system columns specifically, then only the selected system columns will be included. It appears that @entryid will always be included.

Ready for the grid!

How that you have a REST service providing data, you are ready to surface the grid! Tune in next time to see how to surface your first grid.

Return to the Last View Opened in XPages

There are many ways of handling navigation between pages in an XPages application. This post describes a technique that I’ve used to provide a way for users to return to the last (page containing a) view after closing a document. This isn’t specific to any type of data display; the concept is the same regardless of whether you use a data view, repeat control, view panel, data grid, etc.

Considerations

There are a few things to consider when deciding how to handle the page flow:

  • You often don’t want to hard-code the return destination (e.g. always force users to return to the home page of an application). Especially if coming from the Notes client, the user is accustomed to returning to the view in which they clicked a link to open a document.
  • If you’re not using a ‘back’ button, you need to know where to return.
  • ‘Back’ buttons can be problematic because, when you go back, the page isn’t refreshed the same is if it were reloaded.
  • If the user adds or modifies a document, they may wonder if the update was successful if they don’t see the update when they return to the view.
  • This isn’t a concern if your application happens to open documents in separate browser tabs, because they’ve never left the view page. However, the view will not be updated with a new or modified document if it isn’t refreshed.

The Concept

Here are the steps required for a simple solution, at a high level:

  1. When opening a view, log the page name in a scope variable.
  2. From your form button, call a function to return to the last view opened.
  3. If it cannot determine the last view opened, return to a default view. (For example, when opening a document directly from a link outside of the application.)

The Solution

To track the name of the last view page opened, add this line of code to the beforePageLoad event of all XPages that contain view controls:

sessionScope.put('lastView', view.getPageName());

To return to the last view page when closing a document, use this code:

function returnToLastView() {
	var defaultView = 'myViewPage.xsp';
	
	if (sessionScope.containsKey('lastView')) {
		context.redirectToPage(sessionScope.get('lastView'))
	} else {
		context.redirectToPage(defaultView);
	}
}

I generally put this function in a script library and call it within the logic of the back/close and save buttons.

Other Ideas?

How have you handled this in your applications?

Dynamically Modify the Data Source on an Embedded Form in XPages

With inspiration from awesome XSnippet posted by Sven Hasselbach, I’ve found a much better way to work with embedded forms of related documents on an XPage.

Managing the Data Source with Scope Variables

I recently worked on an application that had a view of related documents embedded within a parent document page, along with an embedded form to work with the related documents. This concept applies to child/response documents as well as separate documents, related based on common piece of information. (By “embedded form”, I’m referring to a custom control with it’s own data source based on a separate Notes client form, displayed within the context of the parent page.)

As with any type of document data source, creating, viewing, and editing are the three types of data source actions.

By default, the embedded form is blank and can be used to submit a new related document. However, if the user clicks on a related document in the embedded view, they should have the ability to view or edit the selected document, based on their rights.

Along with the action, the document ID needs to be managed. It either needs to be blank (in the case of a new document) or set to the UNID of the document selected in the view.

Initial Implementation

The following steps were taken to implement this functionality:

  • Compute the data source’s action from a scope variable (defaulting to createDocument if no other variable was specified)
  • Compute the data source’s documentId from a scope variable
  • Set the data source’s ignoreRequestParams attribute to true
  • Set the links in the view to set the selected document’s UNID in a scope variable and also determine whether the form should be opened or edited and set the appropriate action in a scope variable (openDocument or editDocument, respectively)
  • Set the links in the view to trigger a partial refresh on the form panel in order to update the document in the embedded form
  • Set the Save button on the embedded form to clear the embedded form fields after saving a new or edited document
  • Hope that no one would try to view and create documents within the same page view

The Biggest Challenge

The biggest challenge I ran into was creating a new document with the embedded form after viewing or editing an existing document. No matter what I did, it would always update the previously-edited document with any changes that were made.

The Solution

Fortunately, I was able to solve that problem by scoping the data source to the request, so it stopped hanging onto the previous document and allowed for the creation of new documents. This was done via the scope property of the data source, found under All Properties > data > data > dominoDocument[0] > scope.

Managing the Data Source Dynamically

This method works, manipulating the data source directly makes it much simpler to implement and manage! With a few lines of code, you can replace the need for computed data source values, leaving all of the logic in the link that opens the form.

In my case, I’m attaching the data source to a panel on the page. Sven’s snippet shows how to attach the data source to the page itself.

This response from Sven to a question on Stack Overflow shows how you can clear all data sources and add a new one (to work around the same problem I was facing). Note: If you do that, be sure to set the variable name of the new data source the same as when you first bound fields to it, so all field bindings remain in tact.

I did some testing and found that I didn’t even need to remove and re-create the data source. I’ve been testing just updating the parameters of the data source and it seems to work well. One caveat I’ve seen so far is that if I try to open a document in edit mode, close it, then re-open in read mode before changing the documentId of the source to another document, it sticks with the first mode. If you have a case where you need to do that in succession, then maybe removing the data source and re-creating it altogether would do the trick.

The Code

Now, I have a ‘New’ button at the top and a repeat control with links to open and edit each document.

New Document

var ds = getComponent('panelName').getData()[0]; 
ds.setDocumentId(''); 
ds.setAction('createDocument'); 

Open Document (varRepeat is the repeat control variable)

var ds = getComponent('panelName').getData()[0]; 
ds.setDocumentId(varRepeat.getDocument().getUniversalID().toString()); 
ds.setAction('openDocument');

Edit Document (varRepeat is the repeat control variable)

 
var ds = getComponent('panelName').getData()[0]; 
ds.setDocumentId(varRepeat.getDocument().getUniversalID().toString()); 
ds.setAction('editDocument'); 

Two other things are required for this to work:

  1. The ignoreRequestParams property of the data source must be set to true, or the embedded form will pick up the id of the parent document from the URL. If you want to be sure it’s set properly, include this line: ds.setIgnoreRequestParams(true);
  2. Set the button or link to partially-refresh the panel that includes the embedded form and data source

This is much easier to maintain that all of the steps that the other method required.