Subscribe via Feed

Getting Deeper Into The Managed Bean - Part II

Jeremy Hodge, Aug 10, 2010 10:22:39 AM

First of all, Happy Yellow Day all you yellow-peeps ... now on with the show...

Yesterday I began a deep dive into creating a managed bean that would create a cached master detail record set. We created two Java classes, one for the master record, Hello World, and one for the detail record, HelloCountry, set up HelloWorld as a managed bean, and wrote an XPage to work with Record set to display the detail and allow the user to interact with that detail.

Today, we're going to work more with those same classes to add more functionality to the page.

First, lets work on on added a button to remove a line from the detail. I'll add an xp:link with an icon to the beginning of the line thats a red/white minus button to act as the delete button. I'll also style the link to have some right margin to seperate the image from the detail data to help prevent accidental clicking.

Now to remove the line, we are going to need to know which item in the list the user clicked on, so go back to the xp:repeat tag, and set the Index name to countryIndex, like this:

To create the action that will delete the selected HelloCountry instance, we are going to use SSJS. We can directly reference our Java Managed Bean objects as JavaScript objects too! To perform the action, we are going to add code in the link's onclick event handler. So select the delete image link, open the Events view, select onclick, change the action from Simple Action to Script Editor, and enter the following code:

HelloWorld.getCountries().remove(countryIndex);

What we have done is called the getCountries() method directly, which returns our Vector of countries, and we've told the Vector to remove the item whose index matches the current iteration of our repeat, eliminating the item that the user has selected!

Now, let's work on getting the data commited to the database. We can do this by writing a function in our managed bean that will perform all the heavy lifting. So let's open the HelloWorld class again, and add a new method block called commitToDb().

We'll also need to add some other import statements so we can work directly with Notes objects, iterate through our vector, etc. So, add the following import statements at the beginning of your HelloWorld class if they are not already there:

import java.util.Iterator;
import lotus.domino.Database;
import lotus.domino.Document;
import lotus.domino.NotesException;
import javax.faces.context.FacesContext;

Now, let's get down to the nitty gritty of commiting the records to the database. The first thing we want to do is get some of the global objects used in our session from the XPages runtime, most notably, the current database. So let's go ahead and do that. There is a call you can make that will return any global object available, and the call is made like this:

FacesContext.getCurrentInstance().getApplication()
     .getVariableResolver().resolveVariable(
        FacesContext.getCurrentInstance(), variableName);

You replace variableName with the name of the object you want returned, like "database" or "sessionScope" or "viewScope" or any of the other declared objects you have available to you at runtime.

A "best practice" would be to create a single class, named something like JSFUtils and create a set of common functions to perform routinely used functions like this. For example, you could create a static function called getObject that recieved a String variable name to retrieve that made the call above to retrieve it. You could then also create helper functions like getSession, getDatabase, getSessionScope that would return type-cast objects of the type expected.

So let's start off creating our method block, and get the current database.  We're going to enclose the entire a try..catch block so we can send any errors we see to the domino console for debugging. Here we go:

public void commitToDb() {

     try {

          Database database = (Database) FacesContext.getCurrentInstance()
           .getApplication().getVariableResolver()
            .resolveVariable(FacesContext.getCurrentInstance(), "database");

Now, let's go ahead and create the parent document (or the master record) in the database. We don't have any real content for it in our application at this point, so we'll just save an empty document that the detail records can be responses to.

          Document masterRecord = database.createDocument();
          masterRecord.appendItemValue("Form","masterRecord");
          masterRecord.save();

Now we are ready to commit the detail records to the database as well. To do that we need to iterate through our Vector, and save each document in turn. We use the java.util.Iterator to perform the iteration. Let's create the iterator, and start the iteration process:

          Iterator itr = myCountries.iterator();

          Document newCountry;

          while (itr.hasNext()) {

Now we're ready to commit the detail records, the code is fairly self-explanatory if you are familiar with LotusScript or SSJS already:

               HelloCountry currentCountry = (HelloCountry) itr.next();

               newCountry = database.createDocument();
               newCountry.appendItemValue("Form","detailRecord");
               newCountry.appendItemValue("CountryName", currentCountry.getCountryName());
               newCountry.appendItemValue("CapitalCity", currentCountry.getCapitalCity());
               newCountry.appendItemValue("GoodFood", currentCountry.getGoodFood());
               newCountry.makeResponse(masterRecord);
               newCountry.save();

               newCountry.recycle();
         }
   
         masterRecord.recycle();

The one line you may notice that is different with Java than with LotusScript or SSJS is the call to the recycle() method.  You have to call this method on every Notes/Domino object you create to make sure that memory gets released and cleaned up appropriately in the C API side of the call. Make it a habit.

Now we've gotten through the meat of the save, we have some clean up to do, we need to our error catching so we can take care of any needed debugging.

     } catch (NotesException e) {
          e.printStackTrace();
     } catch (Exception e) {
          e.printStackTrace();
     }

}

The multiple catch blocks above are really just for show, but it illustrates how to capture different types of errors and handle them separately.

Ok, the last bit we need to take care of is to add a button to our XPage that will call the commitToDb() method. So let's add that button to our HelloWorldDetail.xsp XPage after the add Country button's. To call the save, you can add either an SSJS or Custom action, calling HelloWorld.commitToDb() ... like this:

   
        
    

And that's all she wrote folks! The next installment will look at loading detail records into our cached object from the database, detecting if the records have already been committed, and saving changes instead of creating new documents on save, and maybe more!

And as before, the demo at http://www.xpagecontrols.com/xpagesblog.nsf/HelloWorldDetail.xsp has been updated with this new functionality.

Until then, happy yellow-Coding!



11 responses to Getting Deeper Into The Managed Bean - Part II

David Leedy, June 15, 2011 11:23 AM

Joacim,

Jeremy did some videos of creating beans for the NotesIn9 Screencast. While they're available at notesin9.com you'll find them much easier on http://xpages.tv


Karsten Lehmann, June 15, 2011 7:28 AM

Joacim, have you seen the series about Java development for XPages that I wrote two years ago?

http://blog.mindoo.com/web/blog.nsf/archive?openview&title=XPages&type=cat&cat=XPages


Joacim Boive, June 14, 2011 10:52 AM

Great series!


But what happened with the next article? Writers block? ;)

There's a define shortage on articles regarding this topic (Java + XPages development). Hope you get around to publishing some more on this topic! (God knows I need it....)


Until then - Thanks!

/J


Mirek Navratil, January 29, 2011 1:07 PM

@Jeremy

I really appreciate your effort. The series really puts a new light on XPages development.
Let me have a question: although it seems that you planed another article on the subject I am not able to find it. Does it mean that it has not been published yet or am I just blind? I hope that it is hidden somewhere as it is quite unlikely that I will find such a good source of information


Jeremy Hodge, January 24, 2011 2:39 PM

@Laura, glad it worked!

As for the value of a "computed field" ... the computed field in XPages client-side really isn't a field, its a span tag with the text value inside of it. If you want to get the value of that "field" you can use innerHTML, but its something to be wary of, since what's displayed may not actually be the value of the field. It can for example, contain additional markup, etc. if you really need to store the value of a computed value to retrieve client side, you should either use a hidden input (although for security purposes be wary of the value getting changed even if you don't program it to) or use something like what I outlined in this post: http://xpagesblog.com/xpages-blog/2010/2/21/embedding-contextual-data-for-client-side-logic-in-xpages.html ...
+
Also, if you want to use something like innerHTML, I would recommend using something like:

var x = dojo.byId('IDOFELEMENT');
var val = x.innerText || x.textContent;

to get the cross-browser text value of the span (minus any markup), etc. Note that innerHTML, innerText and and textContent all will strip newlines, tabs, multiple spaces etc from the value as well since HTML doesn't recognize the existence of those elements (only single a "white space")


Laura Sponaas, January 24, 2011 2:23 PM

@Jeremy
Thanks for your help! I did end up putting two xp:inputHidden fields within one of my table cells of my repeat control and it works flawlessly!! (And it doesn't mess with the design/size of my column/row).

I also stumbled upon something else that people may be able to use (only found it in one spot on the XPage wiki in my searches). To grab a value with client side Javascript from a Computed Field, you have to use ".innerHTML" in place of ".value". Did you know that?! That saved me a bunch more pain, as I have 4 fields thatI also needed to grab that way and did not know why I was getting javascript errors. :-D


Laura Sponaas, January 21, 2011 2:40 PM

@Jeremy
Will there be a problem if I put the hidden field in a repeat control? I tried what I mentioned in my last response and I am getting errors when the panel that the button(s) with the onComplete code are in try to display.

I am returning a set of line items and that bean is set for each one ("T" = already selected, "F" = not selected yet).

Does that make sense? :)

Thanks for the help you have provided so far!!


Laura Sponaas, January 21, 2011 1:32 AM

@Jeremy
Ah Thanks! I looked all over for some type of an event model to understand where I needed to put this code. I am open to moving it, but behind that button seemed like the most logical spot because it is within a repeat control and then has direct knowledge of the beans I want to grab.

I will your xp:hidden solution. The unfortunate thing is the partial refresh is refreshing the button and that is it!! (I found if I only update the button, then it will not lose focus of where my end-user is within my scrolling repeat control. All other updates are happening behind the scenes.

Hmmmmm, I could probably make one of my table cells include a hidden field yes? I will try it and let you know the results.


Jeremy Hodge, January 20, 2011 9:07 PM

@Laura - The problem lies with placing the the line

var addFlag = '#{FOLineData.dspLineAddedFlag}';

in the onComplete. The onComplete code is rendered when your page is initially provided to the user (or when partially updated thereafter) but BEFORE you've actually sent the new value for FOLineData.dspLineAddedFlag to the server. Think about the progression of events that occur:

1. User Requests page
2. Your bean is initialized, with dspLineAddedFlag set to F
3. The Render Response Phase writes out the code for the page, including the code for the onComplete event. Because the onCompete is rendered at this point, the initialized value of dspLineAddedFlag is used, which means the browser get sent a script that says:

var addFlag = 'F';

4. The user updates the control, the partial refresh starts its execution (and at this time the onComplete is sent to the XSP object, w/ addFlag already set)
5. The partial refresh completes, they new content is returned to the browser, the XSP object updates the HTML DOM, and then the pre-defined onComplete from step 3 is executed, addFlag = 'F'...

The next time the onComplete runs, the new onComplete will be updated to reflect the change in value the user sent, but the value will always be a "partial refresh behind".

Assuming the onComplete is where you actually want to utilize the value (and you weren't just trying to test your theories there), you'll have to provide an alternate way for the client-side script to access the current value. For example, you can include an xp:inputHidden with value="#{FOLineData.dspLineAddedFlag}" id="dspLineAddedFlag" inside the bounds of your partial refresh... then use the line

var addFlag = dojo.byId('#{id:dspLineAddedFlag}').value;

in the onComplete to retreive the value from the field, because onComplete runs after the return of the partial refresh has been integrated into the DOM, guaranteeing the input will have the updated value.

Hope this helps!


Laura Sponaas, January 20, 2011 8:26 PM

I have been using managed beans for a project I am working on and they work terrific. They are stored at the session scope.

My new challenge: After a user clicks a button that adds/updates a bean, I need to get one of the pieces of bean data RIGHT after the button click event completes.

As a test, I have tried putting the following simple code into the onComplete of the partial refresh button event:
var addFlag = '#{FOLineData.dspLineAddedFlag}';
if (addFlag=="T"){
alert("addFlag = T");
}else if(addFlag=="Error"){
alert("addFlag = Error");
}else{
alert("addFlag = " + addFlag);
}

This should return a "T". However, it returns an "F". ("F" is my initialized bean value... "T" is after the user clicks the selected line item button).

I know the bean is set right, because my label on the button changes as per my request (this is in my computed Label javascript code):
if (FOLineData.dspLineAddedFlag=="T"){
return "Added";
}else if(FOLineData.dspLineAddedFlag=="Error"){
return "Error";
}else{
return "Add";
}

Any ideas? Is there somewhere else I can or should be calling this code from?
Thanks for any help you can provide!!!
Laura


Olli k, September 19, 2010 10:30 AM

Hi!
Thanks for great articles! I've managed to make xpage that uses managed beans, but there's one huge problem. I seem not be able to store managed bean as view scope variable. Only options are session, request and database. This is very annoying limitation, because i can't build fully working cached master/detail recordset. Ie. if i open two master documents, later will display details from the first one.
This is really driving me crazy, because after so many years of lacking this kind of functionality I got so close but still not could do it.