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

Set Focus on a Field when Showing a Bootstrap 3 Modal

$
0
0

While testing an application that I’m working on, I noticed that the focus is not set on a field within a Bootstrap modal when it’s shown. This is annoying to the user because it requires to tab a number of times (since the focus stays on the button clicked to show the modal) or click into a field in order to start typing. In this post, I’ll show how to force the focus to be set on a specific field when a modal is shown.

Event Delegation

One simple approach is to use jQuery event delegation (here’s a great explanation from Mark Roden). An event handler can be added to the body tag to fire whenever a modal is shown. Within that handler, we can set the focus on a specific field.

$('body').on('shown.bs.modal', '.modal', function () {
  $('[id$=myField]').focus();
})

The first line sets up the event delegation by adding a listener to the body of the page for the event of showing a modal (shown.bs.modal). The second line locates the field named myField and sets focus on it.

The selector in the second line may look a bit strange, but it’s effect is to find the element where the id attribute ends with myField. This is because XPages adds a number of colon-separated prefixes to each element, depending on it’s depth in the page structure. In order to not worry about all of that, the code looks for the element that ends with the name that I assigned to the field.

Here are two other options that you have for achieving the same effect:

  1. Add a unique class name to the field and use $(‘.yourClassName’) as the selector
  2. Use Mark Roden’s x$() function to select an element

Modals and Partial Refreshes

As if there haven’t already been enough (too many?) references to Marky, take a look at this post if you have a modal that needs to perform partial refreshes.

Ultimately, the code I’m actually using in my application looks like this:

$('body').on('shown.bs.modal', '.modal', function () {
  $('FORM').append($('.modal'));
  $('[id$=myField]').focus();
})

Multiple Modals

This method works fine on a page that only has one modal. If you have multiple modals, you’d need to adjust the logic in order to set the field focus as desired. One more generic option would be to change the jQuery selector to just find the first input field on the modal.



XPages Tip: Control Declaration Snippets

$
0
0

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


Computing Custom Date Format Patterns Throughout an Application

$
0
0

If you want to ensure consistency in date formatting throughout your application, you can define the pattern for each Edit Box or Computed Value control that displays a date. What you may not have considered is that you can compute the date format, so you can define it in one place and use the same format everywhere. In this post, I’ll show how you can set it up and even change it conditionally.

Locale Conversions

The browser will automatically attempt to format dates based on your specified locale. Here’s an example of a computed field set to display a date. The Display type must be set to Date/Time.

Date Format 1a Default Date Format - Settings

Here’s how it displays in my browser when the language is set to English:

Date Format 1b Default Date - English

Here’s how it displays when I change my browser language to French:
Date Format 1c Default Date - French

Custom Date Patterns

If you want to change the default formatting, you can define a custom date format pattern.

To define a specific pattern, change Display format to Custom and then you can edit the Pattern field. (You can select a pre-defined pattern or enter your own.)

Date Format 2 - Custom Date Format

In this case, the pattern will stay the same, regardless of the browser language.

(Note: When defining a date pattern, the month value uses one or more capital M’s, because the lower case m is for minutes when defining a time pattern.)

Computing Date Patterns

Just like most other properties in XPages, there’s a blue diamond next to the Pattern field, allowing you to use SSJS to compute it. This gives you the opportunity to create a library function to return some pattern to use. Then, you can set all of your date controls to call the same function and modify the pattern in one place.

Your library function could be as simple as this:

function getDateFormat() {
  return 'dd-MMM-yyyy';
}

Taking it one step further, you could add logic in the function to conditionally determine the pattern, whether it be based on a user preference or a locale or just changing date formatting patterns on a daily basis because you like to mess with your users.

If you use more complex logic, you may also want to consider storing the pattern in an applicationScope variable, so it’s available throughout the application.

function getDateFormat() {
  if (!applicationScope.dateFormat) {
    // Logic to determine date format
    applicationScope.put('dateFormat', some_date_format);
  }

  return applicationScope.dateFormat;
}

Computation Timing

The default setting for the logic Compute Dynamically (#). If you leave it that way, it will throw this error when you load the page: The value of the property pattern cannot be a run time
binding.

To fix this, select Compute on Page Load when entering/editing the snippet or change the octothorpe/pound sign/hash (#) to a dollar sign ($) in the page source, because date pattern computations only work on page load.

Compute Date Formats - 3

Quick Replace

If you’ve already set up hard-coded patterns on date fields, they would look something like this:

You can do a quick search and replace to modify them all easily.

Search for:

pattern="dd-MMM-yyyy"

Compute Date Formats - 4a

Click the Replace... button rather than the Search button.

Then replace with:

pattern="${javascript:return getDateFormat();}"

Compute Date Formats - 4b


Monitor Source Control Synchronization with the Console View in DDE

$
0
0

If you’d like to monitor the synchronization of an application with the local source control repository, you can use the Console view in Domino Designer. In this post, I’ll show different kinds of helpful information that the view displays.

Console View

If you’re using source control, the Console view displays information about the synchronization of design elements between the NSF and the (source control) on-disk repository.

This can be helpful to see what’s going on if your DDE client is responding slowly (and may help you decide whether you want to enable or disable automatic synchronization). It may also be helpful in tracking down the creation of the dreaded .orig files.

Opening the Console View

Select Window > Show eclipse views > Other...

Blog - Console Sync - Add 1

Under General, select Console

Blog - Console Sync - Add 2

Console Output

When you open an application in DDE, you’ll see the console start the synchronization process. (There’s even a misspelling to keep the grammar cops on alert.)

Console - Show initial synch

When you update an existing design element, you see that it exports the change to the on-disk repository (for the .xsp and related .xsp-config file).

Console - Show Element Modified

When you add a new design element, it creates the .xsp, .xsp.metadata, and .xsp-config files in the on-disk repository.

Console - Show Element Added

Automatic Synchronization

If you have automatic synchronization enabled, it will happen when the application is built. (If you have Build Automatically enabled, it will happen automatically. Otherwise, it’ll happen when you build the application.)

These two properties in the Domino Designer preferences determine whether synchronization is automatic:
Console - DDE Prefs - Auto Sync

 

Simple Sync Conflicts

If you’ve seen a popup like this, then there is a synchronization conflict:

Blog - Console Sync Conflict

Here’s the corresponding message in the Console view:

Nsf file AppProperties/database.properties and disk file AppProperties/database.properties both have been updated since last sync or are never synced:Wed Aug 20 20:17:36 EDT 2014

It indicates a conflict and it’s making it easy for you to choose how to resolve it. You can also see evidence of this in the Console view. I tend to see this often with the database.properties (because the time stamps are always being modified). This type of popup is displayed when you synchronize with source control before opening the application in DDE. (If you modify the same element as someone else, conflicts will show up differently when you try to commit changes within your source control plugin or application.)

One View – Multiple Uses

The Console view is displayed as Console (DDE Sync Console) when displaying source control sync information. You can also switch it to be used as a Java stack trace console by via the Display Selected Console icon.


XPages Tip: Fixing a partial refresh issue with the Pager Add Rows control in a Data View

$
0
0

I came across a strange issue with a Data View using a Pager Add Rows control resetting the number of documents displayed after a partial refresh. In this post, I’ll explain the scenario where I saw the issue and show the simple solution that fixed the problem.

The Problem

I have a Data View that starts with 20 rows and uses a Pager Add Rows control to add 10 more rows at a time.

Within the summary facet, there’s an action that can take place, which runs a minimal script and then performs a partial refresh with partial execution on the summary section.

This *seems* to be fine when performed within the first set of rows, but I noticed that if additional rows are added to the display and then the action is run in any one of the rows, the action runs successfully, but the data view is reset to only display the initial set of 20 rows.

If the action was taken on a row after the initial 20, I can add rows again and see that the action has taken place and the partial refresh has occurred as designed.

The Fix – Changing the ‘State’ Property

I tried making a number of changes to partial refresh and partial execution settings, but none of them fixed this issue.

Ultimately, the issue was fixed by a simple property change. The Pager Add Rows control has a state property (All Properties > basics > state)

When set to true, it stores the view state on the server.

Pager Add Rows - State Property

This did the trick. It allows the row count to be preserved between partial refreshes by storing it on the server.


XPages Tip: Select a Working Set in Package Explorer (and Navigator)

$
0
0

If you’ve been using Domino Designer for any length of time, you’re likely aware that you can group your applications into Working Sets in the Applications view. But did you know that you can do the same thing in the Package Explorer and Navigator views?

Working Sets in the Applications View

Here’s what it looks like to select a working set in the Applications view in DDE:

Blog - Package Explorer - 1

You can choose one or select the Multiple... option to select multiple working sets.

Working Sets in the Package Explorer View

Until recently, I didn’t realize that I could make a similar selection in the Package Explorer view. I always just saw it as annoying that I had to dig through every NSF I’ve worked on until I looked around for this option.

If you click on the icon with 3 horizontal gray lines, you get this drop-down:

Blog - Package Explorer - 2 - None Selected

You can select a single working set or choose the magic Window Working Set option to have the Package Explorer databases automatically updated based on your working set selection in the Applications view.

Blog - Package Explorer - 3 - Window Working Set Selected

If you want to disable this option and show them all, you can select Deselect Working Set from the menu.

Working Sets in the Navigator View

Interestingly, you can also choose a working set in the Navigator view, but the options are more limited; there’s no option to link it up with the current working set.

Blog - Package Explorer - 4 - Navigator


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

$
0
0

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();


URL @Functions in the Extension Library

$
0
0

There are a few @Functions for working with URLs available in the Extension library. They can come in handy if you need to work with URLs related to any element in your database.

Availability

These functions are part of the extension library. They’re available if you’ve installed the Extension Library, or if you have Upgrade Pack 1 for 8.5.3. They’re also part of the extension library features built directly into Notes9.

@FunctionsEx

The functions are available on the Reference tab in the SSJS Script Editor. Under Libraries, select @FunctionsEx to see the additional functions.

ExtLib URL Functions - 1

The following URL functions are available:

  • @EncodeUrl() – Encodes spaces and symbols in the URL
  • @FullUrl() – Displays the full path to a resource in the database (relative to the server)
  • @AbsoluteUrl() – Displays the absolute path to a resource in the database*
  • @IsAbsoluteUrl() – Boolean value for whether a given URL is absolute

Examples

Here are some examples of how they can be used in an application:

Code Result
@FullUrl(view.getPageName()) /BlogTesting.nsf/ExtLibURLFormulas.xsp
@AbsoluteUrl(view.getPageName()) http://127.0.0.2/ExtLibURLFormulas.xsp
@EncodeUrl(context.getUrl().toString());

Where the url is: http://127.0.0.2/BlogTesting.nsf/ExtLibURLFormulas.xsp?my_Parameter=spaces and $ymbol$
http://127.0.0.2/BlogTesting.nsf/ExtLibURLFormulas.xsp?my_Parameter=spaces+and+%24ymbol%24
@IsAbsoluteUrl(view.getPageName()) false
@IsAbsoluteUrl(context.getUrl().toString()) true

*@AbsoluteUrl

I generally use context.getUrl() and work with that to parse and build URLs, so I haven’t run into this before, but the output of @AbsoluteUrl() wasn’t what I expected. At first glance, it appeared to give the full url to the specified design element, but it actually just gives the protocol, server, and design element name that was supplied.

It looks like I can get the real absolute url to the design element by combining @FullUrl() with @AbsoluteUrl()

Code Result
@AbsoluteURL(@FullUrl(view.getPageName())) http://127.0.0.2/BlogTesting.nsf/ExtLibURLFormulas.xsp


Getting the Base URL of the Current Database with SSJS

$
0
0

It’s easy to get the current page URL in SSJS with context.getUrl().toString(). However, it’s a little less straightforward if you just want to determine the base URL of the current application. This can be useful when you need to build a link to some other page within the current application and send it out in an e-mail notification. In this post, I’ll look at the results of some methods in the XSPUrl object and show how to achieve the desired effect.

In the last post, I looked at URL @functions in the Extension Library. There are some useful functions there, but none that retrieve what I’m looking for.

XSPUrl

The global context object has a getUrl() method that makes it easy to get a handle to an XSPUrl object representing the current URL. You can retrieve the full URL (including the querystring) or use any of the methods available to get pieces of the URL ( getAddress(), getHost(),getParameter(), getPath(), getPort(), getQueryString()).

Here are some examples of the output, given this url: http://127.0.0.2/BlogTesting.nsf/URL.xsp?parameter1=a

Method Result
context.getUrl().toString() http://127.0.0.2/BlogTesting.nsf/URL.xsp?parameter1=a
context.getUrl().getPath() /BlogTesting.nsf/URL.xsp
context.getUrl().getAddress() http://127.0.0.2/BlogTesting.nsf/URL.xsp
context.getUrl().getHost() 127.0.0.2
context.getUrl().getSiteRelativeAddress(context) /URL.xsp

There are several handy methods, but none providing exactly what I’m looking for.

Options for Getting the Base URL

The getAddress() method gets the URL up to, but not including, the querystring. However, it still includes the current page name. The global view object has a getPageName() method that returns the name of the currently-displayed XPage.

The snippet that I’ve been using recently uses the getAddress() method and replaces the current page name with an empty string, leaving us with the base URL:

context.getUrl().getAddress().replace(view.getPageName(), '')

You could just as easily use Javascript’s substr or substring methods to look for the ‘.nsf’ and only retrieve characters up to the end of it in most cases, but that may be problematic if you have routing that masks the NSF name.

What’s Your Solution?

This seems like something that probably has a number of workarounds. How have you solved it? Is there something simpler that I’ve overlooked?

Update

Check out David Leedy’s XPages URL Cheatsheet for more URL-related functions http://xpagescheatsheet.com/cheatsheet.nsf/url.xsp

There are two listed on there that sounded like they might provide what I was looking for, but did not.

  • database.getHttpURL() – includes the database replica ID rather than name and appends ?OpenDatabase to the end
  • view.getBaseURL() – sounds perfect, but doesn’t return anything

Allowing 0 Rows in a Repeat

$
0
0

The Allow zero rows in repeat controls setting in XSP Properties can be used to cause a Repeat control not to display any rows, giving you the ability to compute the row count as desired, even when there is data that would otherwise have been displayed. In this post, I’ll show how to update the setting and describe its effect.

XSP Properties

In Notes 9, you can find the XSP properties under Application Configuration in the Applications view of Domino Designer.

ZeroRowsInRepeat-A

This UI gives you a nicer front end to modify a number of values in the xsp.properties file, which contains a lot of configuration information for the application.

There are 4 tabs, 3 of which contain several properties that you can modify and the fourth tab which displays the full source of the properties file.

There are many, many properties that can be set in the file, but there are only a few specified by default. The rest are treated as default values when not otherwise specified.

ZeroRowsInRepeat-B

As you change properties in the UI, they will be added to the source.

Allow zero rows in repeat controls

The Allow zero rows in repeat controls setting on the Page Generation tab is not selected (i.e. false) by default.

ZeroRowsInRepeat-C

The effect is that if you have a Repeat control where the rows property is computed to 0, it will revert to the default row count of 30.

If you select that option, it adds this line to xsp.properties:

xsp.repeat.allowZeroRowsPerPage=true

With this setting, you can compute a Repeat control’s row count to 0 and it will not show any rows. This effectively hides it from the UI, but still loads the repeat control into the JSF component tree so it can be modified programmatically, if necessary.

Applicability

The XPages Portable Command Guide says that this property applies to View Panel, Data Table, and Repeat controls.

It works as expected with a Repeat control, but did not work properly with a View Panel in my testing (with Notes 9).

With the property set to false, a View Panel with the rows property set to 0 would show 30 rows. With the property set to true, the View Panel throws a divide by zero error.

ZeroRowsInRepeat-D


Modifying an XSP Property fo a Single XPage

$
0
0

I’m familiar with setting XSP properties in xsp.properties at the application or server level and I’ve used tags to do the same within a theme, but I did not realize until recently that you can also set these properties at the XPage level to override the broader settings on a page-by-page basis.

Select the root tag for the page and go to All Properties in the Properties view. Under the data subtab, click on properties and click the plus (+) button to add a property.

This adds a new parameter and defaults the name attribute to param and the value attribute to val. In the source, it adds an xp:this.properties tag containing an xp:parameter tag.

XSP Property Page Level - 1

Let’s see this in action with a few examples.

Here’s the first fiew lines of a brand new, unmodified XPage on my test server. The application leaves the server defaults for the HTML doctype and character set (although the latter is not visible in this output).

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
<head>
<title></title>

If you want to add a meta tag to the page that displays the doctype and character set, you can add the xsp.html.meta.contenttype property to the page and set its value to true.

You can also override the HTML doctype and character set for the page with additional properties. (Click on the picture to enlarge.)

XSP Property Page Level - 2

Here’s the updated first few lines of output, including the meta tag and reflecting the updated doctype and character set:

<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title></title>

There are many more properties that can be modified at the page level (though not all of them). If you have the XPages Portable Command Guide, you can find them all described in depth there (The book covers through 8.5.3. Sven Hasselbach has a good post on some new properties that were added in Notes 9.)

It appears that you can similarly modify properties at the custom control level, but I have not tested the effectiveness or how conflicts are handled.


Domino Usage Survey

Client-Side JavaScript Debugging Tip — Using XSP.dumpObject() and XSP.log()

$
0
0

The XSP object is a client-side JavaScript object that is built and delivered by the XPages runtime and it contains a number of useful functions, some of which are always available and some of which are context-specific. In this post, I’ll show two functions that are useful for client-side JavaScript debugging.

XSP.dumpObject()

The dumpObject() method returns a string representation of all properties of any object that you pass to it. If you want to track down why something isn’t working as expected, it may be helpful to inspect the properties of the object.

The object can be a variable that you programmatically create and modify or it can be an element on the page.

I used to make frequent use of a small script to display all properties of a JavaScript object, but this is a much simpler way to get that information.

Here’s an example of client-side JavaScript to build an object and use XSP.dumpObject() to write out all properties to the browser console:

var myObject = {}
myObject.property1 = 'One';
myObject.property2 = 'Two';
myObject.property3 = 'Three';
myObject.arrayProperty = ['a', 'b', 'c'];

console.log(XSP.dumpObject(myObject));

This information can be very helpful in debugging, so I would recommend using it when handling errors in a try-catch block.

XSP.log()

In addition to logging to the console, you have another similar option available by another XSP function. XSP.log() is analogous to the standard console.log() function that you use when debugging client-side

JavaScript, but the log() method opens up a new browser window and logs messages there, in descending order.

All we have to do is change the last line in the previous snippet:

XSP.log(XSP.dumpObject(myObject));

And this is what we see in a separate browser window:

XSPLog - 1

The log() method will continue appending any messages that you send until you close the window. The next call to XSP.log() will open a new, blank window and start logging again.

Using dumpObject on an HTML field and XPages control

You can also use dumpObject on an HTML element.

For example, if I have a pure HTML select with 3 options and an onchange event, this will be the output of if I get a handle to the element (the id is HTMLComboBox) and dump the object properties:

XSP.log(XSP.dumpObject(dojo.byId('htmlComboBox')));

object
  0: [object HTMLOptionElement] (Maximum Depth Reached)
  1: [object HTMLOptionElement] (Maximum Depth Reached)
  2: [object HTMLOptionElement] (Maximum Depth Reached)
  autofocus: false
  disabled: false
  form: [object HTMLFormElement] (Maximum Depth Reached)
  multiple: false
  name: 
  required: false
  size: 0
  type: select-one
  options: [object HTMLOptionsCollection] (Maximum Depth Reached)
  length: 3
  selectedOptions: [object HTMLCollection] (Maximum Depth Reached)
  selectedIndex: 0
  value: v1
  willValidate: true
  validity: [object ValidityState] (Maximum Depth Reached)
  validationMessage: 
  title: 
  lang: 
  dir: 
  dataset: [object DOMStringMap] (Maximum Depth Reached)
  itemScope: false
  itemType:  (Maximum Depth Reached)
  itemId: 
  itemRef:  (Maximum Depth Reached)
  itemProp:  (Maximum Depth Reached)
  properties: [object HTMLPropertiesCollection]... (Maximum Depth Reached)
  itemValue:  (Maximum Depth Reached)
  hidden: false
  tabIndex: 0
  accessKey: 
  accessKeyLabel: 
  draggable: false
  contentEditable: inherit
  isContentEditable: false
  contextMenu:  (Maximum Depth Reached)
  spellcheck: false
  style: [object CSS2Properties] (Maximum Depth Reached)
  className: 
  oncopy:  (Maximum Depth Reached)
  oncut:  (Maximum Depth Reached)
  onpaste:  (Maximum Depth Reached)
  offsetParent: [object HTMLBodyElement] (Maximum Depth Reached)
  offsetTop: 39
  offsetLeft: 0
  offsetWidth: 67
  offsetHeight: 19
  onabort:  (Maximum Depth Reached)
  onblur:  (Maximum Depth Reached)
  onfocus:  (Maximum Depth Reached)
  oncanplay:  (Maximum Depth Reached)
  oncanplaythrough:  (Maximum Depth Reached)
  onchange:  (Maximum Depth Reached)
  onclick:  (Maximum Depth Reached)
  oncontextmenu:  (Maximum Depth Reached)
  ondblclick:  (Maximum Depth Reached)
  ondrag:  (Maximum Depth Reached)
  ondragend:  (Maximum Depth Reached)
  ondragenter:  (Maximum Depth Reached)
  ondragleave:  (Maximum Depth Reached)
  ondragover:  (Maximum Depth Reached)
  ondragstart:  (Maximum Depth Reached)
  ondrop:  (Maximum Depth Reached)
  ondurationchange:  (Maximum Depth Reached)
  onemptied:  (Maximum Depth Reached)
  onended:  (Maximum Depth Reached)
  oninput:  (Maximum Depth Reached)
  oninvalid:  (Maximum Depth Reached)
  onkeydown:  (Maximum Depth Reached)
  onkeypress:  (Maximum Depth Reached)
  onkeyup:  (Maximum Depth Reached)
  onload:  (Maximum Depth Reached)
  onloadeddata:  (Maximum Depth Reached)
  onloadedmetadata:  (Maximum Depth Reached)
  onloadstart:  (Maximum Depth Reached)
  onmousedown:  (Maximum Depth Reached)
  onmouseenter:  (Maximum Depth Reached)
  onmouseleave:  (Maximum Depth Reached)
  onmousemove:  (Maximum Depth Reached)
  onmouseout:  (Maximum Depth Reached)
  onmouseover:  (Maximum Depth Reached)
  onmouseup:  (Maximum Depth Reached)
  onpause:  (Maximum Depth Reached)
  onplay:  (Maximum Depth Reached)
  onplaying:  (Maximum Depth Reached)
  onprogress:  (Maximum Depth Reached)
  onratechange:  (Maximum Depth Reached)
  onreset:  (Maximum Depth Reached)
  onscroll:  (Maximum Depth Reached)
  onseeked:  (Maximum Depth Reached)
  onseeking:  (Maximum Depth Reached)
  onselect:  (Maximum Depth Reached)
  onshow:  (Maximum Depth Reached)
  onstalled:  (Maximum Depth Reached)
  onsubmit:  (Maximum Depth Reached)
  onsuspend:  (Maximum Depth Reached)
  ontimeupdate:  (Maximum Depth Reached)
  onvolumechange:  (Maximum Depth Reached)
  onwaiting:  (Maximum Depth Reached)
  onmozfullscreenchange:  (Maximum Depth Reached)
  onmozfullscreenerror:  (Maximum Depth Reached)
  onmozpointerlockchange:  (Maximum Depth Reached)
  onmozpointerlockerror:  (Maximum Depth Reached)
  onerror:  (Maximum Depth Reached)
  tagName: SELECT
  id: htmlComboBox
  classList:  (Maximum Depth Reached)
  attributes: [object MozNamedAttrMap] (Maximum Depth Reached)
  onwheel:  (Maximum Depth Reached)
  scrollTop: 0
  scrollLeft: 0
  scrollWidth: 67
  scrollHeight: 19
  clientTop: 0
  clientLeft: 0
  clientWidth: 67
  clientHeight: 19
  scrollTopMax: 0
  scrollLeftMax: 0
  innerHTML: <option value="v1">Value 1</option><option value="v2">Value 2</option><option value="v3">Value 3</option>
  outerHTML: <select id="htmlComboBox"><option value="v1">Value 1</option><option value="v2">Value 2</option><option value="v3">Value 3</option></select>
  previousElementSibling: [object HTMLBRElement] (Maximum Depth Reached)
  nextElementSibling: [object HTMLBRElement] (Maximum Depth Reached)
  children: [object HTMLCollection] (Maximum Depth Reached)
  firstElementChild: [object HTMLOptionElement] (Maximum Depth Reached)
  lastElementChild: [object HTMLOptionElement] (Maximum Depth Reached)
  childElementCount: 3
  nodeType: 1
  nodeName: SELECT
  baseURI: http://127.0.0.2/BlogTesting.nsf/dumpObject.xsp
  ownerDocument: [object HTMLDocument] (Maximum Depth Reached)
  parentNode: [object HTMLFormElement] (Maximum Depth Reached)
  parentElement: [object HTMLFormElement] (Maximum Depth Reached)
  childNodes: [object NodeList] (Maximum Depth Reached)
  firstChild: [object HTMLOptionElement] (Maximum Depth Reached)
  lastChild: [object HTMLOptionElement] (Maximum Depth Reached)
  previousSibling: [object Text] (Maximum Depth Reached)
  nextSibling: [object HTMLBRElement] (Maximum Depth Reached)
  nodeValue:  (Maximum Depth Reached)
  textContent: Value 1Value 2Value 3
  namespaceURI: http://www.w3.org/1999/xhtml
  prefix:  (Maximum Depth Reached)
  localName: select
  ELEMENT_NODE: 1
  ATTRIBUTE_NODE: 2
  TEXT_NODE: 3
  CDATA_SECTION_NODE: 4
  ENTITY_REFERENCE_NODE: 5
  ENTITY_NODE: 6
  PROCESSING_INSTRUCTION_NODE: 7
  COMMENT_NODE: 8
  DOCUMENT_NODE: 9
  DOCUMENT_TYPE_NODE: 10
  DOCUMENT_FRAGMENT_NODE: 11
  NOTATION_NODE: 12
  DOCUMENT_POSITION_DISCONNECTED: 1
  DOCUMENT_POSITION_PRECEDING: 2
  DOCUMENT_POSITION_FOLLOWING: 4
  DOCUMENT_POSITION_CONTAINS: 8
  DOCUMENT_POSITION_CONTAINED_BY: 16
  DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 32

If I have a similar Combo Box control in XPages, the output is the same because the HTML element that is generated is virtually identical.

Note: You must pass a fully-qualified client-side ID to dumpObject(), so if you’re passing it an XPages element, use the pass-thru EL syntax "#{id:myControl}" or a client-side query that looks for the element id ending with the name of the control, such as dojo.query('[id~=myControl]')[0].id

console.dir(object)

There’s a method built into browser development tools that performs a similar function. This can be similarly handy when troubleshooting something directly on the page.

For example, to see all of the properties of the first input field on the form, you can use it like this:

console.dir(dojo.query('input')[0]);


Article Published in The VIEW: Improve Application Performance with JSON-RPC

$
0
0

An article that I wrote on improving XPages application performance using JSON RPC has been published in The VIEW journal (subscription required).

One of the best-kept secrets of XPages is JSON-RPC control. It is the ideal solution for application developers who want:

  • Their XPages applications to be more efficient and run faster
  • To easily implement client-side and server-side JavaScript interaction

Abstract

If you want your XPages applications to perform more efficiently, or if you’ve ever struggled with executing server-side JavaScript from client-side JavaScript (or taken it a step further and plotted hacktastic workarounds for passing information back and forth), then you need to get to know JSON-RPC. Compared to workarounds that involve partial refreshes, JSON-RPC is a much cleaner method of client and server code interaction and is much more efficient because there is no form submission. JSON-RPC is also asynchronous, so it’s faster and appears more responsive because it doesn’t block user interaction. In this article, get an introduction to JSON-RPC and learn how to reap the benefits in your XPages applications.


XPages Tip: Using _dump to write information to the server console

$
0
0

I recently showed how to use a built-in client-side JavaScript function to dump the contents of an object for review. In this post, I’ll show a somewhat similar method available in Server-Side JavaScript.

_dump() is a global method available in SSJS. You can use it to write the contents of an object out to the server console (and log).

The XPages Portable Command Guide mentions using it to dump the contents of scope variable maps.

Since the requestScope automatically has a bunch of properties, I can see the results if I run this beforePageLoad:

_dump(requestScope);

Here is the output on the server console — basic URL, user, server, and page info:


HTTP JVM: com.sun.faces.context.RequestMap
HTTP JVM: =
HTTP JVM: {database=BlogTesting.nsf, __xspconvid=null, opensession=CN=XDev9/O=Xcellerant, userScope={}, session=CN=XDev9/O=Xcellerant, cookie={SessionID=javax.servlet.http.Cookie@7190719}, component
HTTP JVM: arameters=com.ibm.xsp.application.ComponentParameters@3f953f95, context=com.ibm.xsp.designer.context.ServletXSPContext@56fc56fc,
identityScope={}, serverScope={Anonymous={}}, opendatabase=BlogTesting.nsf, com.ibm.xsp.SESSION_ID=B7117B555E750057C2668FD5A46
HTTP JVM: 51CB288B7621}

The sessionScope is empty by default, but I can add a few properties and then look at the output:

sessionScope.property1 = 'abc';
sessionScope.property2 = 'def';
_dump(sessionScope);

HTTP JVM: com.sun.faces.context.SessionMap
HTTP JVM: =
HTTP JVM: {__XSP_STATE_FILE=com.ibm.xsp.application.FileStateManager$SessionState@70647064,
__notescontext_publicaccess=com.ibm.domino.xsp.module.nsf.NotesContext$AccessPrivileges@2a0b2a0b, property2=def, xsp.sessionData=com.ibm.xsp.designer.context.PersistentSessi
HTTP JVM: nData@34fb34fb, property1=abc}

Not exactly easy to read, but the information is there.

But you’re not limited to scope variables. Here’s an example of creating my own object and writing it out:

var myObject = {};
myObject.foo = 'xyz';
myObject.bar = 'uvw';
_dump(myObject);

HTTP JVM: Object
HTTP JVM: =
HTTP JVM: [object Object]
HTTP JVM:
HTTP JVM: +-
HTTP JVM: foo
HTTP JVM: :
HTTP JVM: string
HTTP JVM: =
HTTP JVM: xyz
HTTP JVM:
HTTP JVM: +-
HTTP JVM: bar
HTTP JVM: :
HTTP JVM: string
HTTP JVM: =
HTTP JVM: uvw

This seems ridiculously spread out, but it’s pretty easy to see the contents of the object.

All in all, MarkLeusink’s Debug Toolbar is a much nicer tool to use when working with scope variables, but if you’re doing some quick debugging — or setting up a consistent method of handling errors, the _dump() method can come in handy.



Including a Java Class in SSJS

$
0
0

You’re most likely aware that SSJS really is really turned into Java under the covers, but did you realize that you can use Java classes in SSJS? In this post, I’ll show how with a simple example.

Using a Java class in SSJS

There are examples similar to this in the DDE help when looking up how to get a document from a view:

var vec = new java.util.Vector();
vec.addElement('firstLookupKey');
vec.addElement('secondLookupKey');
var doc - myView.getDocumentByKey(vec);

And so on. Several view lookup methods take a vector as a way to look up documents or view entries with multiple keys for multiple sorted columns.

The first line specifies that it’s creating a new java.util.Vector() object. After that, you can use methods of that Java class right from SSJS.

Using Type Ahead Autocompletion for Java Class Methods

If you specify the type of the variable that you create, Domino Designer provide type ahead. Take this example:

var vec:java.util.Vector = new java.util.Vector();

Just type the variable name and a period, and (maybe after a slight pause), you’ll see a list of properties and methods to choose from:

Java Class in SSJS

Importing a Java Class into SSJS

If you want to refer to an object more easily in SSJS, you can import the class you want to use and then reference it just by the class name, rather than by the fully-qualified name. To do this, use importPackage().

Here’s how this would look different when working with a Vector:

importPackage (java.util.Vector);	
var vec = new Vector();

And now you have code that’s a little easier to follow and also has the ability to display the properties and methods of the class:

Java Class in SSJS 2

Clearly, there is far more opportunity to use this than the simple examples I’ve shown here, but I hope this gets the gears turning and becomes a useful feature for you.


Initializing Global Variables and Synchronous AJAX Calls in an Angular XPages Application

$
0
0

In a recent project (an XPages application with an Angular front end), I had a need to (a) run a script to determine user access and (b) store that value globally. In this post, I’ll show one way to achieve this with a synchronous AJAX call and a module run block.

Use Case

In this application, the front end pages need to know whether the user has Admin rights in the application in order to determine whether to display advanced options such as configuration links.

The security check needs to be run before the page is loaded and it needs to be stored globally so that the security check does not have to run constantly as many page elements are loaded.

Initial Implementation

I created a custom REST service that checks the user’s roles and returns a boolean value for whether the user is an Admin in the application. I called it via AJAX and stored the value in the $rootScope.

I then set up the logic from each admin-controlled feature call a function to check whether the value existed in $rootScope. If not, the function would make the AJAX call to look up the value and store it.

The code to call the REST service looks like this:

$http.get('rest.xsp/isUserAdmin').success(function (data) {
  $rootScope.isAdmin = (data == 'true') ? true : false;
})

The logic seemed to be set up properly. However, it didn’t initially work as I expected.

The Problem

Watching the Net panel in the browser tools made it clear that the AJAX call was being made many times as each page loaded.

The AJAX calls were fired off so rapidly that numerous calls were made before any of them had a return value available in the $rootScope.

It turns out that Angular’s AJAX calls are asynchronous by design. For the most part, this is great because it keeps the application more responsive. However, in this case, I needed the security check to complete before loading the rest of the page.

The Solution

Ultimately, I solved the problem by making two changes.

  1. Moved the role-checking code to a module run block so it would run before page load
  2. Replaced the Angular AJAX call with a standard jQuery AJAX call so I could add a parameter to force the call to be synchronous.

As the application loads, it will call the REST service and wait for the response. When done, it will store the value in the $rootScope.

Here is a link to the Angular.js module documentation.

Run blocks are the closest thing in Angular to the main method. A run block is the code which needs to run to kickstart the application. It is executed after all of the service have been configured and the injector has been created.

A run block generally goes in the app.js file rather than in a controller. Just add a run() method to the application and include the required directive and code.

This code sets up a function in the run block that includes a synchronous jQuery AJAX call to an XPages REST service, and stores the return value in the $rootScope for global access in the application. This runs immediately and the global variable is initialized and available when the rest of the page loads.

// Make the user access check available globally
PSApps.run(['$rootScope',                    
  function($rootScope) {
	 
      // Use jQuery for a synchronous http request -- angular hard-codes all requests to be asynchronous
      $.ajax('rest.xsp/isUserAdmin', {async: false})
        .success(function (data) {
          $rootScope.isAdmin = (data == 'true') ? true : false;
          console.log('is user admin? ' + data);
      });

  }
]);

Then, all places that need to check the value in order to determine whether to display an element can just reference the global property.

ng-show="$rootScope.isAdmin"

Gridx in XPages – Series Introduction

$
0
0

Gridx is the next generation of the Dojo data grid. I am very excited to say that I’ve got a basic Gridx grid loading properly inn XPages.

About Gridx

Gridx is currently at version 1.3.3 (as of June).

The Gridx home page describes it as follows:

A fast rendering, well modularized and plugin architecture based Grid.

Besides supporting a rich set of popular features, GridX is optimized and extremely capable of supporting huge dataset.

GridX is consisted of:

  • A compact and lightweight core
  • A flexible plugin machinery with comprehensive module life-cycle and conflict management
  • A rich and extend-able set of modules which can be loaded on demand

The site lists these as the primary features:

  • Fast rendering speed
  • Flexible module machinery
  • New smart scroller
  • Huge data store support
  • Easy for upgrading
  • Enterprise level i18n & a11y compliance

Why Gridx?

There are many grids out there. I chose to focus on Gridx because it seems like the logical progression from the Dojo data grid. Given that XPages is heavily intertwined with Dojo and 6 of the 7 listed committers to the project are IBMers, it seems reasonable to move in this direction within the platform.

Gridx basically combines the best features of the Dojo EnhancedGrid and TreeGrid and adds much more. It has a nicer look and feel, a number of plugins that seem to offer useful functionality, and it claims to perform well with large data sets.

It’s conceptually similar to dGrid (in that it’s a next-generation Dojo grid), but claims to be more feature-rich.

A few more advantages that it has over some other grids are that it’s free and it seems to have a very small file size 1.82 MB (and it only loads the modules that it needs to build the grid).

For More Information…

Here are some links for more information:

I’m just starting to dig into it, but I will write about features as I have the chance to figure them out.

In the next post, I’ll describe the major hurdle to getting it working and how that problem was solved.


Gridx in XPages – Using djConfig to Make the Code Available

$
0
0

The last post covered some of the benefits of using Gridx, but before we can create a Gridx grid in XPages, we have to make the code available to the application. In this post, I’ll show how to use djConfig to make the code available to the NSF. This is not only the key to allowing Gridx to work, but the concept will allow other Dojo libraries to be included as well.

Dojo Version Required

Gridx uses AMD-style module loading, so it seems to need Dojo 1.7 or higher. The documentation says that it works best with 1.8.0 or higher. Notes 9 ships with Dojo 1.8.1, so that is what I will be using for my testing and development.

Installation

Including Gridx in an application is different than including a module (as with the first step in using Dojo EnhancedGrid) because the modules for EnhancedGrid are already part of the standard Dojo installation, so the files are already on the server and just need to be included on the page to be available.

As with third-party JavaScript libraries, you can easily add the code to an NSF.

First, download gridx and extract it.

Rename the folder above core, modules, etc to just gridx (without the version number).
Gridx 2 - Directory Structure

Then, open your NSF if the Package Explorer view and drag the gridx folder into WebContent
Gridx 2 - WebContent Folder

Making the Code Available

The next step is making the code available to use within the application.

Dojo would generally look for the code in a sibling directory to dojo, so it would expect to find it in this directory structure:

  • dojo
  • dijit
  • dojox
  • gridx

This is the point where I was stuck the last time I tried this.

You can set up your own dojo installation on yoru server and reference it. While it works, it creates a bigger administrative burden to maintain it and update it and it leaves you operating with a different version than was intended with your current version of XPages. You could also go the route of OSGi plugin, but, at this point, I want to add the library to the application and use it like I do with any other JavaScript library.

djConfig modulePaths

The key to making it work like any other library lies in djConfig — the Dojo configuration object. There’s an attribute named modulePaths that can include a list of additional places (outside of the standard directory structure) to look for code.

Once this is configured, we can reference the Gridx code as thought it exists in the default Dojo structure.

Here’s an example from the Dojo documentation:

modulePaths: {'foo': '../../bar'}

Setting modulePaths in XPages

In XPages, you can set a djConfig property as an XPage or Custom Control property. Select the xp:view tag and go to the Properties view. Select All Properties > Data > Properties and click the + button to add a property.

The property name is xsp.client.script.dojo.djConfig

The property value should be something like this: modulePaths: {'gridx': '/myDB.nsf/gridx'}

But it doesn’t work. You get a "SyntaxError: invalid property id" error in the browser console.

Gridx 2 - ModulePaths Error

The issue is that the value is escaped incorrectly by XPages, so it doesn’t allow it to work.

Fortunately, Sven Hasselbach posted a workaroud for this type of problem. You can use the beforePageLoad event to write out a client-side JavaScript variable with the modulePath setting (like an XAgent) so that it can be referenced by the djConfig property.

// Get the relative URL up to the .nsf
var relativeUrl = context.getUrl().getPath();
var index = relativeUrl.indexOf('.nsf');
var gridxPath = relativeUrl.substring(0, index+4) + '/gridx';

// Write out a JS variable with the module path for djConfig
// This works around a problem of the property being improperly escaped.
// Solution by Sven Hasselbach: http://stackoverflow.com/questions/13720849/djconfig-in-xpages-escapes-values-making-the-setting-wrong
var exCon = facesContext.getExternalContext();
var response = exCon.getResponse();
var writer = response.getWriter();
writer.write("<script>\n");
writer.write("var modPaths = {'gridx':'" + gridxPath + "'}");
writer.write("\n</script>\n");

The lines 2-4 build a relative URL up to the .nsf. It then appends /gridx, because that’s where the directory is accessible starting with the WebContent folder.

Lines 9-14 write out a script tag with a client-side JavaScript variable named modPaths.

Now we can reference that modPaths variable in our djConfig property, because the code above runs before the page is loaded:

<xp:this.properties>
  <xp:parameter name="xsp.client.script.dojo.djConfig"
    value="modulePaths:modPaths">
  </xp:parameter>
</xp:this.properties>

Here’s what the property (and related source code) looks like:

Gridx 2 - djConfig Property

Up Next

Now, the gridx code is available to our application. In the next post, I’ll show how to get a simple gridx grid loading.


Gridx in XPages – Creating a Simple Grid

$
0
0

In the last post, I showed how to make Gridx available in an XPages application. In this post, I’ll show how to create your first Gridx grid in XPages.

Assuming you have already followed the steps in the last post, you are ready to create your first grid.

A Gridx grid requires the following:

  • A data store
  • A cache implementation
  • Defining grid columns
  • Defining the grid
  • Placing and instantiating the grid

The example is a very simple one with hard-coded data. The goal here is to get a grid up and running so we can build upon it in future posts.

It starts with a script tag and code that uses Dojo AMD loading to include the required modules. In the code below, the Gridx/Grid module is loaded and given the name Grid; a dojo Memory store module is loaded and given the name Memory, and a synchronous cache module is loaded and given the name Sync. The dojo.domReady! module is also loaded — this causes the script to wait until the page is ready before executing.

<script> require([
  "gridx/Grid", "dojo/store/Memory",
  "gridx/core/model/cache/Sync", "dojo/domReady!"
  ], function(Grid, Memory, Cache) {

First, we need to set up the data store. This example uses a Memory store to hold hard-coded data.

var store = new Memory({
  data: [
    {id: 1, name: 'John', country: 'United States', things: 100},
    {id: 2, name: 'Bill', country: 'Lithuania', things: 57},
    {id: 3, name: 'Bob', country: 'China', things: 123},
    {id: 4, name: 'Jim', country: 'Germany', things: 154},
    {id: 5, name: 'Tom', country: 'Brazil', things: 78}
  ]
});

Next, we need to define the column structure for the grid. The field attribute maps to the name attribute of the data store. The name attribute of the column defines the column title to display. When a width is not defined, it appears that it will take up the remaining available width.

		
var columns = [
  {id: 'name', field: 'name', name: 'Name', width: '70px'},
  {id: 'country', field: 'country', name: 'Country'},
  {id: 'things', field: 'things', name: 'Things', width: '40px'}
];

Then, we need to define the grid object. The object takes the cache, data store, and column structure.

grid = new Grid({ 
  id: &amp;quot;my_gridX&amp;quot;, 
  cacheClass: Cache, 
  store: store, 
  structure: columns 
}) 

Finally, we need to tell the grid where to be placed and then initialize it with the startup method.

		
//Put it into the DOM tree.
grid.placeAt('gridX_Here');
grid.startup();

The placeAt call needs the ID of an element where the grid should be generated.

This div tag on the page provides a place. IMPORTANT: The div must have a height defined, or it will end up at 0 and you won’t see anything. (This example defines it inline, but it’s better to use CSS.)

<div id="gridX_Here" style="height:300px; width:300px;">
</div>

CSS

The grid also requires a few stylesheets. This example uses the Claro theme. Those stylesheets can be loaded from the existing Dojo implementation on the server. The URLs use /.ibmxspres/dojoroot/, which tells the server to look in the root Dojo directory.

The third stylesheet is specific to Gridx and is loaded with a URL relative to the application’s WebContent directory.

<xp:this.resources>
  <!-- Claro Theme -->
  <xp:styleSheet href="/.ibmxspres/dojoroot/dijit/themes/claro/claro.css"></xp:styleSheet>
  <xp:styleSheet href="/.ibmxspres/dojoroot/dijit/themes/claro/document.css"></xp:styleSheet>
		
  <!-- -Main GridX Stylesheet  -->
  <xp:styleSheet href="gridx/resources/claro/Gridx.css"></xp:styleSheet>
</xp:this.resources>

Full Page Source

Putting this code together with the setup code from the last post, here is the full source of my sample page:

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

  <xp:this.resources>
    <!-- Claro Theme -->
    <xp:styleSheet href="/.ibmxspres/dojoroot/dijit/themes/claro/claro.css"></xp:styleSheet>
    <xp:styleSheet href="/.ibmxspres/dojoroot/dijit/themes/claro/document.css"></xp:styleSheet>
		
    <!-- -Main GridX Stylesheet  -->
    <xp:styleSheet href="gridx/resources/claro/Gridx.css"></xp:styleSheet>
  </xp:this.resources>


  <xp:this.properties>
    <xp:parameter name="xsp.client.script.dojo.djConfig" value="modulePaths:modPaths"></xp:parameter>
  </xp:this.properties>

	<xp:this.beforePageLoad><![CDATA[#{javascript:// Get the relative URL up to the .nsf
var relativeUrl = context.getUrl().getPath();
var index = relativeUrl.indexOf('.nsf');
var gridxPath = relativeUrl.substring(0, index+4) + '/gridx';
	
// Write out a JS variable with the module path for djConfig
// This works around a problem of the property being improperly escaped.
// Solution by Sven Hasselbach: http://stackoverflow.com/questions/13720849/djconfig-in-xpages-escapes-values-making-the-setting-wrong
var exCon = facesContext.getExternalContext();
var response = exCon.getResponse();
var writer = response.getWriter();
writer.write("<script>\n");
writer.write("var modPaths = {'gridx':'" + gridxPath + "'}");
writer.write("\n</script>\n");}]]>
</xp:this.beforePageLoad>

  <script> require([
    "gridx/Grid", "dojo/store/Memory",
    "gridx/core/model/cache/Sync", "dojo/domReady!"
    ], function(Grid, Memory, Cache) {

    var store = new Memory({
      data: [
        {id: 1, name: 'John', country: 'United States', things: 100},
        {id: 2, name: 'Bill', country: 'Lithuania', things: 57},
        {id: 3, name: 'Bob', country: 'China', things: 123},
        {id: 4, name: 'Jim', country: 'Germany', things: 154},
        {id: 5, name: 'Tom', country: 'Brazil', things: 78}
      ]
    });
		
    var columns = [
      {id: 'name', field: 'name', name: 'Name', width: '70px'},
      {id: 'country', field: 'country', name: 'Country'},
      {id: 'things', field: 'things', name: 'Things', width: '40px'}
    ];

    grid = new Grid({ 
      id: "my_gridX", 
      cacheClass: Cache, 
      store: store, 
      structure: columns 
    }) 
		
    //Put it into the DOM tree.
    grid.placeAt('gridX_Here');
    grid.startup(); 
  });
  </script>

  <div id="gridX_Here" style="height:300px; width:300px;">
  </div>
</xp:view>

If you’ve include gridx in your WebContent folder as shown in the last post, you should be able to copy this source into an XPage and load a grid.

Gridx 3 - Grid

It doesn’t do anything yet, but we’ll add features soon.

Up Next

In the next post, we’ll look at loading live data from a Notes application via a REST service.


Viewing all 216 articles
Browse latest View live