Search
Categories

Entries in custom controls (9)

Thursday
04Feb2010

UPDATE: Self Awareness of Custom Controls in XPages: "Am I alone out there"

UPDATE:  Jeremy Hodge points out the built-in method XPages has for allowing you to do this in the comment below.  Based on that, I've updated the demo and re-recorded it.  So, if you've watched the first one, watch the new one.  The premise, or needs, are the same as what's described below.  The solution is better.

The better approach:

---

 

What the heck does that mean???  Well, here's a scenario. I've taken the time to build a PieChart custom control.  I'd like to be able to drop that PieChart on an XPage multiple times for multiple different charts.  But how can I make that happen without hard-coding the configuration into the control itself?  I would need to be able to PASS that data into the PieChart control from the containing page. 

So if I use something like viewScope.put('PieChartConfig', config) into the XPage, that solves getting data TO the control, but only for 1 instance of the control.  For example, the control would receive that data with viewScope.get('PieChartConfig', config).  But now I can only have 1 control on the page because each instance of the control would be getting the same config.

Somehow each INSTANCE of the control has to be able to get its own data configuration.  Here's how.

When we put each instance of the PieChart custom control on the XPage, we're allowed to name that instance, just like we name any other control.  So, PieChart1, PieChart2, etc...

Now from the XPage, we can modify our earlier code and do something like this:

viewScope.put('PieChartConfig', {'PieChart1':config1, 'PieChart2':config2});

Inside the PieChart control, we can get the config that's intended for that instance by having a control on the page like a computed field with the name of 'sId' which has the value of:

var idArray = getClientId('sId').split(':');
idArray[idArray.length-2]

This will be the name assigned to the custom control instance inside the containing XPage.

To get the config that's specific to this instance of the control, we'd use:

var ConfigAll = viewScope.get('PieChartConfig');
var ConfigMe = d[getComponent('sId').value]

I've recorded a quick (2 min) video example of this, and an .nsf with that code.  Hope this helps.

 

 

Monday
18Jan2010

Custom-Control 2 Custom-Control Communication

I just got out of the very enlightening AD109 session on XPages performance and pigeonholed one of the speakers, Thomas McGuckin from the XPage development team.  There are a couple of things that I'd been scratching my head about that he helped clear up.

Let's say you have 2 custom controls on an XPage, A and B.  In custom control A, you have a field.  In custom control B, you have a computed field that has the server-side code:

getComponent('IDofFieldOnCustomControlA').getValue();

The computed Field will never get set because it can't "SEE" the field in custom control A.  Why is this, and how do you get around it?

Think of the custom control as a container.  For custom control B, when getComponent is called, it operates within the context of that custom control.  Other custom controls are basically black boxes that it can't see into.  While that may sound unusual at first, it actually makes sense.  What if that custom control is inside a repeat control?  There's no way to guarantee the uniqueness of the id of that field.

Given that, it's not really appropriate to even try to phrase the approach in that way.  Instead, it was suggested to do 1 of three things.  For the field where the value is being set:

a.  put it in a scoped variable

b.  in Control B, access getDocument and grab the value from their (relevant where the control you're trying to get is for some data point).

c.  Create a custom property in Custom Control A, and set the value of the property from the control.  Custom Control B can see Custom Control A and access it's properties...it just can't see inside it.

Some worthwhile solutions to what was becoming a common problem for me.  However, while these are good solutions for accessing 'data', what if you really want to get a handle to a panel in the other custom control because you need to hide it for example.  None of these solutions really addresses that kind of requirement.

I'm sure someone brilliant here has a solution for this too.

-Lance

Friday
06Nov2009

XPage Cheat Sheet #2 - DataSource and NotesXspDocument

In my last blog I did an update on the XPage Lifecycle.  The next blog in this new series will cover some of the basics about DataSources and the new NotesXspDocument object/class.  This is targeted at those people like me making a transition from LotusScript to the new world of XPages and SSJS.

With Notes Classic the data model was quite simple.  Notes databases contained a collection of Notes Documents.  These were represented in LotusScript via the NotesDocument class.  The Form was the design element used to display and maintain these documents.  While it was possible to bring in data from other documents there was usually a very strong one to one relationship between the Form and a Document.  So strong that it was often impossible to separate the two.

DataSources

With XPages we are now starting to see a stronger separation between the data layer and the presentation layer.  Data is now (typically) bound to an XPage using one or more DataSources.  The most common DataSources are the Notes Document and the Notes View.  In the future there are plans to support a much wider range of DataSources including XML, JSON (I assume), and Relational Databases.  (You can do this now using a few tricks).

NotesXspDocument

When using Notes Classic to develop applications for the Notes client most of your code is executed on the client with LotusScript able to maintain a continuous exchange with the server.  With the Web model used by XPages it is either not possible or not practical for the Domino server to keep the NotesDocument objects open for the life of a transaction.  This has given rise to the creation of a new NotesXspDocument object which acts as a buffer between Web Page and the NotesDocument objects holding the data. The NotesDocument object only has a life as long as a single trip to the server.  The NotesXspDocument however stays with the XPage as it travels back and forward between the server and the client.

For each DataSource that is linked to a Notes Document on either an XPage or a Custom Control a global SSJS variable is assigned that matches the name assigned to the DataSource.  This variable is an SSJS object of this new type - NotesXspDocument.  The NotesXspDocument is still tied to a NotesDocument but there are some important differences. 

When creating a Notes Document Data Source you are usually asked to connect to an existing form in this (or another database).  As best as I can tell the form selected is significant for just two purposes:-

  1. It determines the list of fields that will be displayed in the data palette.  This can be useful, but if you know the names of the fields you are working with it is not needed;
  2. When a new document is saved the Form name assigned to the document will be that of the selected form (unless you manually assign a value to the field "Form").

Custom Controls & NotesXspDocuments

If your XPages are designed with CustomControls, care needs to be taken to avoid the possible scenario of a separate NotesXspDocument being created for the XPage and each Custom Control.  I have had all manner of troubles trying to share data between my XPage and Custom Controls until I started to understand what was going on.  e.g. A save action on my Xpage would create five new documents!  If your Custom Controls are designed to maintain data in the same Notes Document then it is usually a good idea to share the one NotesXspDocument between the XPage and these controls. 

  1. John Mackey has previously blogged about Passing the NotesXspDocument as a parameter to a Custom Control.
  2. Another technique (also covered by John) is to use the global variable currrentDocument.  This is a NotesXspDocument object that references the first Notes Document data source assigned to the parent XPage.  On the custom control use Advanced Data Binding and Expression Language to enter currentDocument.fieldname to bind the control to a specific field in the first Notes Document DataSource on the parent XPage.  This may not work if you have multiple DataSources bound to the XPage because it seems to always take the first one listed.  Note: The list of DataSources is maintained in alphabetical order so its not even the first DataSource you create that gets assigned!

Using the above it becomes possible to assign the same field to multiple edit controls either on different Custom Controls or even the same XPage/Custom Control.  If this happens the data entered in the last edit control is the one that is always returned to the NotesXspDocument.  After a refresh the other controls will loose their values and display the value entered in this last edit control.

NotesXspDocument & NotesDocument

It appears that during the initial PageLoad events the existing data is transferred from the NotesDocument to the NotesXspDocument.  Only those fields bound to the NotesXspDocument (on XPage or CC) are transferred.  This does include bound controls that are not rendered (visible).  This same data is then transferred back to the NotesDocument when NotesXspDocument.save() method is invoked.  Alternatively you can manually manipulate the NotesDocument object using SSJS.

Note: If you were to perform a save on a newly created document using the NotesDocument via NotesXspDocument.getDocument().save() you will probably find a document saved with a single field (Form) and none of the data entered on the XPage.

Also note that if you pass the NoteID or UNID to a Custom Control you will bind to the same NotesDocument but most likely create a separate NotesXspDocument.

On-Line Demonstration

To illustrate some of the above I have created an on-line demo in the new XPages Kindergarten section of the .Domino Framework: http://www.dominoframework.com/DominoFramework.nsf/XPK_Events.xsp

Friday
06Nov2009

XPage Cheat Sheet #1 - The Page Lifecycle (Update)

A few weeks ago I wrote a blog outlining the XPage lifecycle.  It appears that with 8.5.1 there have been a few changes made.  The following is an updated account of the lifecycle.

This example is based upon a single XPage (X) with 2 custom controls (A & B). Controls seem to be processed in the order they are listed.

The following are executed ONCE only:-
X.beforePageLoad
A.beforePageLoad
A.afterPageLoad
B.beforePageLoad
B.afterpageLoad
X.afterPageLoad

The next set of events are ONLY executed when a Full or Partial refresh occurs:-
X.afterRestoreView
A.afterRestoreView
B.afterRestoreView
If any control events have been fired they are executed following the afterRestoreView events.

The following now occur every time following the PageLoad or restoreView events
X.beforeRenderResponse
A.beforeRenderResponse (only occured with PageLoad in 8.5.0)
B.beforeRenderResponse (only occured with PageLoad in 8.5.0)
X.afterRenderResponse
A.afterRenderResponse (only occured with PageLoad in 8.5.0)
B.afterRenderResponse (only occured with PageLoad in 8.5.0)

The key difference for 8.5.1 being that the beforeRenderResponse and afterRenderResponse are now executed for the XPage and ALL custom controls regardless of whether a partial refresh or full refresh occurs.
Note: This is only a partial listing as the Document events are not (yet) included.

The same observations are still relevant:-
  1. PageLoad events are a good place to place code you only wish to execute the very first time the page is created.
  2. afterRestoreView events seem to be the best place for code that you wish to execute each time a refresh occurs.
  3. Events on a page/control will execute after the afterRestoreView events have fired so they will have access to any variables/object you create in these events.
  4. To have code execute every time the page is processed you need to place it in a PageLoad and an afterRestoreView event
  5. Wen placing code in a control's afterPageLoad event don't assume the beforePageLoad events of the other controls have fired or that the page's afterPageLoad has fired.
You can test this out on-line using a demo XPage I have created as part of a new XPages Kindergarten section of the .Domino Framework: http://www.dominoframework.com/DominoFramework.nsf/XPK_Events.xsp

Wednesday
04Nov2009

New 8.5.1 Design Feature Cleans Up XPage Design When Using Custom Controls

If you have done any XPage development at all you know the power of using custom controls in your XPage design.  One issue though is if you are using many custom controls on an XPage it is difficult to tell where the XPage design elements start/end and the Custom Control elements start/end.

Version 8.5.1 introduced a new design element that slipped under the radar, but I have found very useful in upgrading my XPage application designs.

This is the new Design Definition property when creating Custom Controls.

As an example look at the design of the following XPage in the XPage design editor.

 

This is an XPage that is made up of 5 Custom Controls.

  • A Header
  • Name Fields
  • Address Fields
  • Buttons
  • Footer

Just looking at the design does not tell developers that the elements are Custom Controls.  Plus if this was a large input design, with a big graphics header, designer screen real estate could become a premium.

So what if you could just indicate in the XPage design that there was a Custom Control included without displaying the Custom Control contents?

This is what the new Design Definition Custom Contorl property brings to the table.

To use this new property edit the Custom Control in an 8.5.1 Designer client.  In the Properties view of the Custom Control click on the new Design Definition tab.

This opens an editor that prompts you to "Enter custom XSP markup:"

The XSP markup that is entered in this dialog will be displayed in the XPage design interface instead of the defined elements of the Custom Control.

So here in the Header custom control instead of displaying the company name with the large font I simply want to indicate that this is now a place holder for my Header custom control.

I input the following XSP markup in the Design Definition property

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


<xp:panel>Header Custom Control</xp:panel>

</xp:view>

The code has to be valid XSP markup so it starts with the xml declaration tab and the XSP view tag.

The only other markup added is the XSP panel control with text in between the tags that will describe the functionality of the Custom Control.  Now when this custom control is added to an XPage design instead of seeing all of the Custom Control elements designers will only see the panel with the added text.

Tip:  An easy way to make sure you get the correct markup is to add the control to be used to the design view, then switch to the Source view and cut/paste the correct syntax.

The completed Custom Control looks like the following.

When looking at an XPage design that includes the Custom Control it now looks like the following.

This allows developers to know that there is a custom control there but without taking up a lot of design real estate.

If the same technique was added to the other Custom Controls that make up this XPage design, it would look like the following in the XPage designer

   

Each of the Custom Controls can be double clicked upon to launch the Custom Control editor to change the contents of the Custom Control.

Nothing changes about the features and functionality of the added Custom Control, all of it properties are still available in the XPage design, it simply un-clutters the design interface.  There is no change in functionality when the XPage is rendered in the Notes or Browser client

You can use any valid XSP design element in the Design Definition property of the Custom Control but as you can see in the following graphic, the Panel control provides a nice placeholder to be displayed in the XPage designer.

You do not have to implement this feature in every custom control added to the page, they can be mixed and matched as the developer requires.

An excellent resource to see how this new feature is implemented is the 8.5.1 Discussion Template.

A special thanks to Steve Castledine for helping me understand this new feature. 

The example applciation that I use here can be downloaded from my website here, but the Discussion Template is a better example once you understand how this new feature is being used.

Friday
24Jul2009

Sample NSF of XPage Login Control

As requested by a couple of people, here is an NSF with a working sample from the Advanced In-Page Login Custom Control for XPages blog entry I posted a while back.

Friday
17Jul2009

What custom controls do you want to see?

Stephan Wissel has put together a really nice list of custom controls that he has either heard mention of or thinks himself would be useful utility, re-usable controls for general consumption.

I suspect that over the next weeks and months, enterprising Business Partners (if not IBM themselves) will be developing these "nifty fifty" style tools either as free utilities or for sale. So make sure you get your requirements in there as well.

Wednesday
15Jul2009

Advanced In-Page Login Custom Control for XPages

Declan has a post on his blog on how to create a login window using a Dojo dialog box. Its a very nice example on how to do a quick login control using Dojo and AJAX to log in a user versus using the standard Domino style login.

I use a slightly different approach using an XPage custom control that allows me to have more control over the look of the login, so that I can integrate it with the site's overall look and feel, as well as use some dojo fadeIn/fadeOut animations to give it a little bit of extra flair. It also allows me to authenticate against a specific domino resource of my choice so that I can not only verify they are an authenticated user for the application, but also that they might have a specific role that allows them access to a particular resource.

To use the custom control, you just place it on the XPage where you want the "Log In" prompt to appear, set the control's properties, and customize the prompt's text through the use of some editable areas (facets).

Here is a description of the different properties:

loginText: This is the text you want to appear on the page as the link for the end user to click. I usually use 'Log In'

loginURL: This is the URL that the control uses to actually process the login. 99% of the time, its /names.nsf?login.

verifyAccessURL: After the control issues a successful login attempt to loginURL, the control validates access against this URL, verifying they user has access to what I want to redirect them to. If they don't have access, I tell them so.

logoutText: If a user is currently logged in, the control displays this text, instead of the loginText. I usually use "Log Out"

logoutURL: This is the URL to use for the logout. usually its /names.nsf?logout which takes them back to the default site for the domain/domino server. If I want to redirect them to a different page, then i might use /names.nsf?logout&RedirectTo=/mynew/url/page

successURL: This is the URL to send them to after a successful login and verification.

There are also 2 other properties which I haven't implemented yet, numberAttempts and failureURL, the idea being after numberAttempts failed logins, I'd redirect to failureURL.

In order for the control to work, there are two additional resources you'll need to add to your database. The first is a stylesheet that controls the look of the login window, and the other is a short script that sets up the controls on page load. There isn't alot to them, the control just needs them. If you want to change the look and feel of the control, then you modify the login.css. Both files are included in the download for the control at the end of the article.

Let's get down into the meat of the control so you can see how it works, and see how to develop a custom control.

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

<xp:this.resources>
    <xp:script src="/startsHidden.js" clientSide="true"></xp:script>
    <xp:styleSheet href="/login.css"></xp:styleSheet>
</xp:this.resources>

Above is the first 8 lines of the custom control, pretty basic for any XPage. Two things to note off the bat, is first the xp:view has a custom style set to "display:inline". This allows the "Log In" link to appear right along with and next to any code or elements surrounding it, without causeing the margins to clear on either side. By default, a custom control renders as an XHTML div tag, which is a block element (clearing both margins). The other item to note is the inclusion of the startsHidden javascript and login.css stylesheet.

This particular version of the login custom control displays as a floating panel above the page. To help distinguish it from the rest of the page when it is displayed, I "lower the lights" on the rest of the page by creating a div that is 100% width and height, and black, then set the opacity to 70%. This keeps the user from clicking on other controls, and focuses their attention on the login. I call this the 'dither panel.' That is done with the next three lines of code:

<xp:panel
     styleClass="pnlDither startsHidden" id="pnlDither">
</xp:panel>

The properties of this panel are all controlled in the login.css, and later, i'll show you the code that makes it fade in and out with the login panel.

After that, the next bit of code creates the link on the page where the custom control is embedded.

There is a hefty amount of code in there, so I'll just explain and break down a little bit, download the code below and follow along if you like. The first block <xp:text>...</xp:text> creates the first part of the link, the <a> XHTML tag. If the user is logged in, the link's href is set to the logoutURL property. If the user is not logged in, the onclick of the anchor is used to execute some javascript that makes the dither panel, and the login panel itself visible, then animates them with a fade in. We can use a straight-forward dojo.fadeIn for the panel because we are going to display it at 100% opacity, however we want to stop the opacity of the dither panel at 70% opacity so we can see through it, so we use the dojo.animateProperty to animate the opacity of the panel from it's current setting (currently 0) to 70% (css: opacity: 0.7).

The next block of code, again an <xp:text></xp:text> block decides whether the user is logged in or not, and displays the appropriate text, either loginText or logoutText.

And the final block simply closes the XHTML anchor tag.

The next bit of code just lays out the actual login panel, called loginWindow. It uses several nested <xp:panel> elements (the XHTML equiv to div tags) to define the window, its title bar, etc. There are facets (editable regions) that allow you to set the text for the title bar, the welcome message, etc. I won't post all the code for it as it's lengthly, but its all in the download at the end of the article.

I will however go through the actual login script so you can see how the exchange actually happens.

The first part of the code simply posts a the username and password to the loginURL using dojo.xhrPost:

dojo.xhrPost({ url : '#{javascript:compositeData.get("loginURL");}',
handleAs : "text",
preventCache : true,
content: {
     "UserName": dojo.byId('#{javascript:getClientId("inpUser")}').value,
     "Password": dojo.byId('#{javascript:getClientId("inpPassword")}').value },

You can see in the second line how I retreive the value of the loginURL parameter using the inline XSP javascript command #{javascript: ... } that block is executed on the server side and the result is put back into the code in replacement. In this case the server executes compositeData.get("loginURL") to get the value entered in the parameter loginURL.

Next we have the load function for the xhrPost. This gets executed if the xhrPost is is successful, and data is returned. Here's the first part of that code:

load : function(response, ioArgs) {
    if (response.indexOf('reasonType')==-1) {
           dojo.xhrGet({

Here all we do is take the response back from the server, held in the variable 'response', and look for a string 'reasonType.' That is the key to telling if the login was successful. If it was not a successful login, then the returned HTML includes a variable reasonType indicating the type of error. So far, I haven't cared enough about what the actual error is with the login, so I display a generic message if it fails, shown below (but actually appears later in the script):

} else {
     dojo.byId('#{javascript:getClientId("loginErrorPanel")}').innerHTML = "You have entered an invalid username or password, or do not have sufficient credentials."; }

If the login was successful, I then proceed to actually check against the specific element that I want to veryfiy they have access to. This is done by issueing a dojo.xhrGet to the verifyAccessURL property, then checking again if the access attempt was successful. This time, i do not check for reasonType, as it has caused me a few problems here and there in the past that are really irrelevent to this code. Instead I check for the string 'action="/names.nsf?Login"' that appears in the <form> tag of a login prompt. If the check there is successful, then I redirect the page to the value of the successURL parameter, if not, I force a logout, display my standard message, and have them try to log in again. Here's most of that code:

dojo.xhrGet({
  url : '#{javascript:compositeData.get("verifyAccessURL");}',
  handleAs : 'text',
  preventCache : true,
  load : function(response, ioArgs) {
    if (response.indexOf('action="/names.nsf?Login"')==-1){
      document.cookie = "BackupSesID=; expires=Thu, 01-Jan-70 00:00:01 GMT";
      window.location.href = '#{javascript:compositeData.get("successURL");}';
    }else{
        dojo.xhrGet({
            url : '#{javascript:compositeData.get("logoutURL");}',
            handleAs : "text",
            preventCache : true,
            load : function(response, ioArgs) {
                   if (document.cookie.indexOf('BackupSesId=')!=-1){
                       document.cookie = "DomAuthSessId=" + document.cookie.split('BackupSesID=')[1].substr(0, 32);
                   }
                   document.cookie = "BackupSesId=; expires=Thu, 01-Jan-70 00:00:01 GMT";
                   dojo.byId('#{javascript:getClientId("loginErrorPanel")}').innerHTML = "You have entered an invalid username or password, or do not have sufficient credentials.";
            },
            error : function(response, ioArgs) {
                 dojo.byId('#{javascript:getClientId("loginErrorPanel")}').innerHTML = "You have entered an invalid username or password, or do not have sufficient credentials.";
            }
         })
      }
   },
   error : function(response, ioArgs) {
              dojo.byId('#{javascript:getClientId("loginErrorPanel")}').innerHTML = "You have entered an invalid username or password, or do not have sufficient credentials.";
   }
})

That's pretty much it in a nutshell. Download the Zip with all three elements, the custom control, the css and the javascript (which needs to be created a a javascript script library in your database) from my blog, and let me know if you have any questions!

Happy Coding!

Update: Here is a link to a flash movie of the control in action (sorry about the poor quality)

Monday
13Jul2009

Using Dojo ToolTips in an XPage

I posted an entry on my blog about how to using the dijit.ToolTip plugin in an XPage. Slightly tangential to pure XPages dev, but Dojo is pretty integral to any serious XPages work so it's worth a look.