Friday, March 20, 2009

Whats the coolest thing to use with PeopleSoft? For me it is "Prototype"

Getting bored from the usual PeopleCode/AppEngine etc I started to experiment with using JavaScript with PeopleSoft after getting inspiration from Jim's PeopleSoft Journal and Manalang's Blog. It was a truly eye opener when we consider designing application which are more interactive at the same time high performance.
You can download Prototype JavaScript library from Here.

The results of using JavaScript with PeopleSoft are wonderful. To take full advantage of JavaScript make sure you are well versed with the basic HTML structure and approximately major HTML elements. You also will need basic understanding of DOM (locating elements within the HTML document) etc.

Till date we have used Prototype along with other JavaScript libraries to display information as a ToolTip on a element (consider this a page field). This reduces the clutter on the page (otherwise you would have to place all such information as page fields unnecessarily) and also control on formatting the information contained within the tooltip. The other beautiful use we have put prototype to was to display the "Time Report" totals (in the PeopleSoft Travel and Expense > Time Entry application). The Time Entry page contains a grid for Time Entry by Projects by Date. As delivered the Totals per Project are shown on the right and are refreshed when a field is changed. Thus all the time entry fields on this page are "Non-Deferred" that deteriorate the performance of this application. We made all fields as "Deferred" and using Javascript refreshed the totals for a change in field value using the JavaScript DOM event. Thus the performance of the application improved incredibly. Also the coolest item we plugged on this page was to include the Day Totals as a Row in the table grid using JavaScript. We could not have done this using PeopleCode, as then the new row would be propagated to the database. We used the Prototype DOM manipulation (Element insertion) method to insert a new row as a <TR> element and also columns as <TD> elements. This truly looks a improved application.


Wednesday, March 18, 2009

Custom Parameters in XML Publisher (RTF Templates)

Oracle XML Publisher implementation for PeopleSoft has a little known feature which is passing custom parameters to the RTF template. This could be well appreciated if we consider the following:

I have a rowset based RTF template. The rowset is populated using some PeopleCode based upon certain run control parameters. Now since my rowset does not contain the run control record, then how is it possible to print the run control parameter in my RTF template?

The simplest and the most flexible (**my opinion**) is passing run control parameters as custom parameter to the XML Publisher Report and to retrieve and use those in your RTF template.

The ReportDefn class contain a method "SetRuntimeProperties" which accept two parameters: one is the array of parameter names and other is an array of parameter values.
You must call this method before the "ProcessReport" method of the ReportDefn class.
Lets assume I want to pass BUSINESS_UNIT and PROJECT_ID as custom parameters to the report (possibly derived from a run control record). The sample code is:

(the imports and fully classified class names have been omitted. You have to use fully qualified classnames).

Local ReportDefn &ReportDefn;
&ReportDefn = Create ReportDefn("MYREPORT");
&ReportDefn.Get();

Local Array of String &ParamNames, &ParamValues;
Local String &BU,&Project_id;
&BU=RUNREC.BUSINESS_UNIT.Value;
&Project_id=RUNREC.PROJECT_ID.Value;
&ParamNames = createarray("xslt.BUSINESS_UNIT","xslt.PROJECT_ID");
&ParamValues = createarray(&BU,&Project_id);

&ReportDefn.SetRuntimeDataRowset(&SomeRowset);
&ReportDefn.SetRuntimeProperties(&ParamNames, &ParamValues);
&ReportDefn.ProcessReport.......

(code below processreport not shown..its your code dude!).


Now in your RTF template do the following:
1. Declare the custom parameters: Insert a form field and write the following in the "help text".
<?param@begin:BUSINESS_UNIT?><?param@begin:PROJECT_ID?>
2. To use the parameter (probably for printing): Insert a form field and write the following in the help text:
<?$BUSINESS_UNIT?>
or
<?$PROJECT_ID?>

Thus in order to retrieve the values of the "defined" parameter we use a "$" before the parameter name.

This method makes it quite easy to pass anything from PeopleCode to the RTF template without actually getting into the trouble to modifying the rowsets or the records.


Smarter Standalone Rowset - Part 2

We will continue with our exploration of enhancing the StandAlone Rowset class to include more functionality. We have seen how you utilize the population of a standalone rowset using a SQL statment.
In this post we will explore populating the standalone rowset from one source record and with a where clause + bind Variables.

The delivered Fill() method (or Select() for Standard rowset) accepts any number of Bind Variables for substitution in the where clause. PeopleCode API does not permit creation of methods/functions that accepts variable number of arguments. So in our approach we will explore the possibility of passing variable number of bind variables as elements of array of type any.

Class StandAloneRowset extends Rowset
    method
StandAloneRowset(&StandAloneRec as String);
    method FillFromRecord(&FillRecName As String,&WhereStr as String, &Params As Array of Any);
    method
FillUsingSQLObject(&SQLObj as SQL);
private
   instance String &RsRec;

end-class;


method StandAloneRowset
    &RsRec = &StandAloneRec;
    %Super = CreateRowset(@("Record."|&RsRec));
end-method;


method FillFromRecord
    Local Record &Rec;
    Local String &SQLStr;
    Local SQL &SQL;
    Local integer &Count;

   
&SQLStr = "%SelectAll(:1) Where "|&WhereStr;
    &SQL = CreateSQL(&SQLStr,CreateArrayAny(CreateRecord(@("Record."|&FillRecName)), &Params));
   
    &Rec = CreateRecord(@("Record."|
&FillRecName));
    While
&SQL.Fetch(&Rec)
        if &Count > 0 then
             %This.InsertRow(%This.ActiveRowCount);
        end-if;
        &Rec.CopyFieldsTo(%This.GetRow(%This.ActiveRowCount).GetRecord(@("Record."|&RsRec)));

        &Count = &Count + 1;
    end-while;
  
end-method;

method FillUsingSQLObject
    Local Record &Rec;
    Local integer &Count;

    &Rec = CreateRecord(@("Record."|&RsRec));
    While &SqlObj.Fetch(&Rec)
        if &Count > 0 then
           %This.InsertRow(%This.ActiveRowCount);
       end-if;
   &Rec.CopyFieldsTo(%this.GetRow(%this.ActiveRowCount).GetRecord(@("Record."|&RsRec));
    &Count = &Count + 1;
    End-While;
end-method;



Since in last post we have already seen how to use the StandAloneRowset class with SQL object for data population in the rowset. Here we will use our "latest" FillFromRecord method to simulate a Rowset.Select functionality.

Local StandAloneRowset &MyRs;

&MyRs = Create
StandAloneRowset
("REC1");

&MyRs.FillFromRecord("REC2","WHERE FIELD1=:2 AND FIELD2=:3 AND FIELD3=:4",CreateArrayAny("Val1","Val2","val3");


Note that the Bind variables are specified starting from :2. This is because the first bind parameter to be passed is the record object. Remember to number your bind parameters from :2.
Thus we have an identical functionality like the Rowset.Select method where it is possible to populate the rowset from another record.

We will keep on exploring our standalone rowset class....adding new method for saving data etc..but later...







Smarter Standalone Rowset - Part 1

In PeopleCode two types of rowsets exists:
1. One which is attached to the component and it maintained by the component processors. These rowsets are the scrolls, grids, scrollbars which are drawn on the pages included in the component.
2. Standalone rowsets which are not attached to the component and thus are not maintained by the component processor. Any changes to these are not migrated to the underlying tables and thus it is left to the developer to manually migrate the changes.

Lets have a closer look at the creation and population of a standalone rowset. For our example we will consider a simple rowset with one record. Let the record in the rowset be REC1 with fields EMPLID, FIRST_NAME,LAST_NAME.

Local Rowset &MyRowset;
&MyRowset = CreateRowset(Record.REC1);

Now to populate the data in this rowset object, Rowset class has listed Fill() method. The Fill method accepts a where string and bind parameter values and thus selects data in the rowset. The only issue is that the data is selected from the same record which is the rowset record. This means if I am creating my rowset from Record REC1, I can only select the data from record REC1. This is because the Fill() method does not give us the liberty to use another record for selecting data from [this is not the case with the Select() method which is used to select data in the standard component rowset].

So what if I want to have a standalone rowset created from Record REC1 but wanted to select the data into this from another record say REC2 using a custom SQL Select. This gives us the flexibility of using any number of records joined together for data selection into the rowset. A simple example can be:
SELECT A.EMPLID, B.FIRST_NAME,C.LAST_NAME FROM PS_REC2 A, PS_REC3 B, PS_REC4 C WHERE ......

Lets implement a simple app class to accomplish this.
We can directly extend the delivered Rowset class and create our own StandAloneRowset class with enhanced functionality.

Class StandAloneRowset extends Rowset
     method StandAloneRowset(&StandAloneRec As String);
     method FillUsingSQLObject(&SqlObj as SQL);
private
     instance String &RsRec;
end-class;

method StandAloneRowset
    &RsRec=&StandAloneRec;
    %Super = CreateRowset(@("Record."|&RsRec));
end-method;

method FillUsingSQLObject
     Local Record &Rec;
     Local integer &Count;
    
     &Rec = CreateRecord(@("Record."|&RsRec));
     While &SqlObj.Fetch(&Rec)
         if &Count > 0 then
            %This.InsertRow(%This.ActiveRowCount);
         end-if;

 &Rec.CopyFieldsTo(%this.GetRow(%this.ActiveRowCount).GetRecord(@("Record."|&RsRec));
     &Count = &Count + 1;
     End-While;
end-method;



Lets use this simple class now. Since we have extended the rowset class, we do not have to write our own methods for insertrow(), deletrow etc...thus OOP's concepts comes handy in PeopleCode.

Local StandAloneRowset &MyRs;
Local SQL &MySql;
&MySql = CreateSQL("SELECT A.EMPLID, B.FIRST_NAME,C.LAST_NAME FROM A,B,C, WHERE ......",&Bind1,&Bind2....,&bindn);

&MyRs = create StandAloneRowset("REC1");
&MyRs.FillUsingSQLObject(&MySql);



So now your rowset contains the data returned by using SQL object. Remember all other Rowset operations on our StandAloneRowset remains the same [because we actually are having an instance of Rowset class].

We will explore more on the rowset in the days to come.

Are Component Interfaces really difficult to implement in PeopleCode? I don't think so!!

There are times when developers are asked to use component interfaces for what-not-to-mention reasons and they happily drag and drop the CI object to the PeopleCode editor and then spend another hour cleanup the skeleton code...when they really only want a few lines of the code..

What is that which makes CI code difficult to understand for beginners (probably some of the experience may also complain the same)? According to me it is the habit of drag-n-drop and never-really-looking-at-the-code practice that makes the simple CI and FileLayout PeopleCode difficult to understand. Lets begin our journey to understand what CI PeopleCode says to a developer...

The Basics:
1 . The Rowsets are reffered by Collections in the CI Jargon (derived from ApiObject).
2. Record and Fields are mapped to Properties in CI.
3. GetRow() [method of Rowset class] is mapped to Item() of ApiObject [your Collection is an instance of ApiObject].

The Coding Technique:

When we code for CI we tend to forget that CI is just like a Robot which has been automated for data entry into a PeopleSoft Component. So the basic rules of the game must be the same as

a. Signing on to the PeopleSoft application
b. Navigating to Component
c. Entering Search Key values
d. clicking on Search or Add button.
e. Enter data in the page fields. Insert/delete rows in the scrolls etc..
f. Save the data (or cancel if do not want to save).

Keeping in mind the steps a to f makes it very very simple to work with CI.
Thus here it goes:
1. Signon to PeopleSoft
Local Apiobject &Session;
&Sessions = %Session;

The above code clearly tells us that the first step is to sign on to the application. The &Session variables holds the derived session value from the system variable %Session.

2. Next "navigate" to the component interface.
Local ApiObject &CI;
&CI = &Session.GetCompIntfc(CompIntfc.MYCI);


3. Next "select" the mode in which you want to enter the data. Isn't it that we enter search keys when we want to add/update the data!!
&CI.FIELD1 = "MyVal";
&CI.FIELD2 = "MYVal2";

rem Lets get into add mode!!;
&CI.Create();
Just in case you want to get into update mode use &CI.Get();

4. Now we are on the page [i.e. moved on from Search/Add page to our real application page]. So lets enter data in some of the fields [remember record fields are properties here].
&CI.FIRSTNAME = "Funny";
&CI.LASTNAME = "NAME";


5. Do we have a Level 1 Scroll?. Ah yes..lest retrieve it and enter data in the fields...
Local ApiObject &Level1Scroll, &Level1Row;
&Level1Scroll = &CI.MYLEVEL1SCROLLNAME;

Since by default at least one blank row is there in the scroll [this can be controlled via the GetDummyRows property of the CI].
&Level1Row = &Level1Scrol.Item(1);
&Level1Row.SOMELEVEl1FIELD = "SOMEVAL1";


Oh now you want to insert a new row in your level 1 scroll as if you are clicking on the "+" button on the online page. Recall that Rowset class has InsertRow method to insert a new row after some row in the rowset. The corresponding method for our ApiObject [Collection] is InserItem(integer) where integer specifies the insertion of a new row "after which row".
Since we have currently one row in our Collection, we want to insert a new row after this row. Thus:
&Level1Row = &Level1Scroll.InsertItem(1);
&Level1Row.SOMELEVEl1FIELD= "SOMEVAL2";


6. Isn't this that we are all done with our data entry...Why not just save the data now.
&CI.Save();

The above code example shows the necessary steps to be followed when writing the CI Code. Of course you need to take care of including your code within the try...Catch block and appropriately wrapping your code within this block. The code is a simplistic explanation of what seems difficult for some of the developers.