Tuesday, April 28, 2015

Peoplesoft Styles Demo

View Peoplecode Styles

SQL


Run this SQL against your database and copy the output.
SELECT '<div class="' || STYLECLASSNAME || '">' || STYLECLASSNAME || '</div>'
FROM PSSTYLECLASS WHERE STYLESHEETNAME = 'PTSTYLEDEF'

Page

Temporary add an HTML area to any page and paste your SQL output into the value constant of your HTML area. View your page and you'll see a demo of every style for that Style sheet name in the query you ran.

Extra

If you want to create a more permanent page in your development environment that dynamically loads the HTML area with any style sheet you choose from the system you can do the following:

 Record

Create a new derived record (MY_RECORD) and add the fields
  • STYLESHEETNAME  (prompt table edit : EOPP_STSHEET_VW)
  • HTMLAREA

Page

Create a new page and add your both your derived record fields to it.  

Component

Create a component add your new page to it and add the following Peoplecode to the STYLESHEETNAME FieldChange event.

Local string &qry, &qoutput, &html;
Local SQL &sql;
Local array &AAny = CreateArrayAny();

&qry = "SELECT '<div class=' || STYLECLASSNAME || '>' || STYLECLASSNAME || '</div>'";
&qry = &qry | " FROM PSSTYLECLASS WHERE STYLESHEETNAME = :1 order by STYLECLASSNAME ";

&html = "";

&sql = CreateSQL(&qry, MY_RECORD.STYLESHEETNAME);
While &sql.Fetch(&AAny)
   &html = &html | &AAny [1];
End-While;

MY_RECORD.HTMLAREA.Value = &html;

Register your component to the menu and load your new page.

Thursday, April 23, 2015

PeopleCode Reference Links

Reference Links

Google is one of a programmers best friends and after a while you build up a small collection of great resources.  Here are a couple of my favorite places to find People code solutions and examples:

PeopleCode Language Reference 8.53

  • Built-in Functions
  • Meta-SQL
  • System Variables
  • Meta-HTML

PeopleCode API Reference 8.53

Every class in a great Tree view that includes a quick link to the class details such as:
  • Understanding
  • Using
  • Declaring
  • Built-in Functions
  • Methods
  • Properties
  • etc....

PeopleCode Developers Guild 8.43


  • Operators
  • Data Types
  • Expressions
  • Variables
  • Editors
  • Events
  • Debugging
  • Short Cuts

PeopleCode Built-in Functions 8.50

Listing of every built in function in one place sorted by category.  
With Frames version.

Toolbox.com 

Message board with many users who post solutions.and is most often referenced when doing a Peoplecode google search.


I have a few other links in my links and references page.

Wednesday, April 22, 2015

Custom PeopleSoft Exceptions

Application Package

I created an application package with a couple classes but I soon realized I needed different exceptions for different behaviours.  This is easy in JAVA you just build a custom exception that extends the exception class.  I figured there must be similar way to do this in poeplesoft but couldn't find an example in peoplebooks they seem to always just use the "CreateException" method.  This example will give you the ability to throw and catch different custom exceptions.


Application Package

MY_PERSON_SYNC
  • MY_PersonBuilder
  • MY_PersonSync
  • MYPersonException

Exception Class

This first step is create your Exception.  I put mine as a different class in the same package that will be throwing it.  This class will extend exception and only have the basic constructor method in it.  This constructor will create the super class Exception using the common peoplesoft method CreateException.  I accepted only the message as a string but you could accept your own custom message numbers as well if you like.


class MYPersonException extends Exception
   method MYPersonException(&MSG As string);
end-class;

method MYPersonException
   /+ &MSG as String +/
   %Super = CreateException(0, 0, &MSG);
end-method;

Person Builder Class
Next in my class MY_PersonBuilder I need I have 2 different errors.  The first is one that I will issue a standard exception that should really stop the process all together.  The second is for when the problem could only be related to a single individual and if multiple transactions are being processed only reject this one transaction.


import MY_PERSON_SYNC:MYPersonException;
...
/* If it is an SQL error you might throw standard Exception */
&recResult = &rec.Update();
If &recResult = false Then
    throw CreateException(0, 0, "update to table **NOT** successful for %1", &rec.ID.Value);
End-If;

/* If my error was specific to a single transaction I would throw my custom Exception */
throw create MY_PERSON_SYNC:MYPersonException("Person missing a Xref value. ");

Person Sync Class
The class MY_PersonSync actually implements the class PS_PT:Integration:INotificationHandler so it has a OnNotify method where the try catch will exist. This is the location that determines if an error should be thown stopping the entire process or just drop a single transaction and continue to process the remaining ones.

import MY_PERSON_SYNC:MYPersonException;
...
   try
      &personBuilder.AddPersonAudit();
      &psXrefId = &personBuilder.AddXrefPS();
      &newid = &personBuilder.GenerateNewID("", False);
      &EMSTXrefId = &personBuilder.AddXrefEMST();
      &alias = &personBuilder.AddIdAlias(&newid);
   catch MY_PERSON_SYNC:MYPersonException &exPerson
      MessageBox(0, "", 0, 0, &exPerson.ToString());
   catch Exception &exError
      Error &exError.ToString();
   end-try

Thursday, April 2, 2015

Using simple HTTP Connectors in Peoplecode

Often times information or remote methods need to be invoked through a very simple HTTP GET/POST request. My exact scenario was to issue an HTTP POST with a single value and the external server would issue a single response code (<response>000</response>) similar to xml but in plain txt. I only needed to issue this from peoplecode in real time and evaluate the response code for success or failure. After days of digging I finally came up on some Integration broker methods that made this task very simple. From this simple demo you should be able to expand your HTTP requests and responses to fit your needs.

External Server

I made a very simple PHP page to respond the same type of message the external system would issue plus dump all the get/post values just for testing.

//PHP Basic HTTP Response Code
$responseValue = "000";
$rawPostContent = file_get_contents("php://input");

// Loop through all URL GET parameters
foreach ($_GET as $name => $value) {
 if($name == "response")
  $responseValue = $value;
}  
  
echo "<response>$responseValue</response>";
echo "<http_method>".$_SERVER['REQUEST_METHOD']."</http_method>";
echo "<proxyMethod>$method</proxyMethod>";

echo "<info> <![CDATA[ ";
foreach ($_POST as $name => $value) {
 echo " POST $name: $value, ";
}  
foreach ($_GET as $name => $value) {
 echo " GET $name: $value,";
}  
echo "]]></info>\n";
echo " <rawhttp><![CDATA[ $rawPostContent ]]></rawhttp> ";

From your browser you simply use the URL http://localhost/webservice.php?response=999
and the response should look something like this:
<response>999</response>
<http_method>GET</http_method>
<info> <![CDATA[  GET response: 999,]]></info>
<rawhttp><![CDATA[  ]]></rawhttp>

If this page is called from a POST the RAWHTTP value will include the posted variables string.

Peoplesoft Node and Routing

When using the connectors you can either load them directly from the node or from a route.  In my example I've setup the node as a GET and the routing as a POST.

Here I  create a simple node and setup the HTTPTARGET connector.  The method here is GET and the The Primary URL is http://[ipaddress]/webservice.php?response=999.



Here I create an active route within a node with the method POST and the same primary URL as above.

The operation tied to the routing that is used to create the message is defined as Synchronous and with a basic message defined.  I was also able to use the same empty message (<?xml version="1.0"?> <MCM_STUPPD_MSG/>) for both request and response.


PeopleCode

HTTP GET

You'll notice I've added a GET query string into the URL and using code to show you that you can use either method or both. Both values show up in the response GET variable list. I also show you how you can loop through the properties. In my final solution I needed to add a unique path to the PRIMARYURL depending on the situation and to do this I would loop through and save the string for primary URL. I then deleting the existing and added the modified PRIMARYURL with the additional path required. Note: Adding a property does not overwrite the existing one if you add a second PRIMARYURL both will exist in the properties list and I'm not sure which would be used.
Local Message &msgRequest, &msgGetResponse;
Local any &ans;
Local string &ibPropName, &ibPropValue;

/* GET */
MessageBox(0, "", 0, 0, " Start %1", %Datetime);
MessageBox(0, "", 0, 0, "HTTP GET Request ");

/* Setup the request message object */
&msgRequest = CreateMessage(Operation.MCM_STUPPD_WS);
/* Setup the request using the Node */
&ans = &msgRequest.IBInfo.LoadConnectorPropFromNode("MCM_STUPPD_TST");
&ans = &msgRequest.IBInfo.IBConnectorInfo.AddQueryStringArg("USERID", "BILLYBOB");

/* Loop through all the property values for logging and debugging only */
For &i = 1 To &msgRequest.IBInfo.IBConnectorInfo.GetNumberOfConnectorProperties()
   &ibPropName = &msgRequest.IBInfo.IBConnectorInfo.GetConnectorPropertiesName(&i);
   &ibPropValue = &msgRequest.IBInfo.IBConnectorInfo.GetConnectorPropertiesValue(&i);
   MessageBox(0, "", 0, 0, "Property : %1 = %2", &ibPropName, &ibPropValue);
End-For;

/* Send HTTP Connector request */
&msgGetResponse = %IntBroker.ConnectorRequest(&msgRequest);
MessageBox(0, "", 0, 0, "HTTP RAW Response: %1 ", &msgGetResponse.GetContentString());

Output
Start 2015-04-02-12.10.58.000000 (0,0)

HTTP GET Request  (0,0)

Property : Accept = */* (0,0)

Property : sendUncompressed = Y (0,0)

Property : Method = GET (0,0)

Property : URL = http://localhost/webservice.php?response=999 (0,0)

HTTP RAW Response: <response>999</response>
<http_method>GET</http_method>
<info> <![CDATA[  GET response: 999,  GET USERID: BILLYBOB, ]]></info>
 <rawhttp><![CDATA[  ]]></rawhttp> 
  (0,0)

HTTP POST

The post is very similar to the GET except I'm loading this message IBinfo connector from the route where I setup the connector as a POST. I will also be adding an XML document to the message and it will be converted to a simple string for the final http connection if your XML document is defined as the following:
<?xml version='1.0'?>
   <data psnonxml='yes'>  
      <![CDATA[ variable1=EDDIE&variable2=BLAH ]]>  
   </data >
I use the built in Peoplesoft XMLDoc and XMLNode methods to do this.
Local Message &msgRequest, &msgPostResponse;
Local any &ans;
MessageBox(0, "", 0, 0, " Start %1", %Datetime);
MessageBox(0, "", 0, 0, "HTTP POST Request ");

/* Setup the request message object */
&msgRequest = CreateMessage(Operation.MCM_STUPPD_WS);
/* Setup the request using the Routing */
&ans = &msgRequest.IBInfo.LoadConnectorPropFromRouting("MCM_STUPPD");

/* Build XML Document With the post string */
Local XmlDoc &inxml = CreateXmlDoc("");
Local XmlNode &rootNode = &inxml.CreateDocumentElement("data");
Local XmlNode &cdataNode = &rootNode.AddCDataSection("postvar1=ABCD&postvar2=BLAH");
&rootNode.AddAttribute("psnonxml", "yes");

&msgRequest.SetXmlDoc(&inxml);

/* Send HTTP Connector request */
&msgPostResponse = %IntBroker.ConnectorRequest(&msgRequest);
MessageBox(0, "", 0, 0, "HTTP RAW Response: %1 ", &msgPostResponse.GetContentString());

Output
HTTP POST Request  (0,0)

HTTP RAW Response: <response>000</response>
<http_method>POST</http_method>
<info> <![CDATA[  POST postvar1: ABCD,  POST postvar2: BLAH, ]]></info>
 <rawhttp><![CDATA[ postvar1=ABCD&postvar2=BLAH ]]></rawhttp> 

  (0,0)

Wednesday, April 1, 2015

Logic Tricks or Shortcuts

Eliminate large IF conditions using Array Find

Instead of creating an if condition with several AND statements when you have a group of codes or values us an array and the FIND method.
Local array of string &AcceptCodes = CreateArray("AA", "AB", "AC", "EE");

If &AcceptCodes .Find(MY_REC.MY_CODE.Value) > 0 Then
   MessageBox(0,"",0,0,"Success. This code is one of the accepted Codes");
   rem Do logic of found condition;
Else
   MessageBox(0,"",0,0,"Denied. This code is not one of the accepted Codes");
   rem Do logic of other codes not in this group;
End-If;
This can also be done with a combination of values. Here I need to avoid producing a log message for a couple of Union code, benefit plan combinations.
Local array of string &UnPlnSuppress = CreateArray("XP3DENCPR", "XP3DENCPP", "XP3DENCCC");
/* if union code & plan combo not found in our suppress list produce message and warning. */
If &UnPlnSuppress.Find(&job.UNION_CD.Value | &bnPlan) = 0 Then
     MessageBox(0, "", 20002, 6, "No mapping found for %1. ", &employee);
End-If;

Loop through Numbered fields

PeopleCode @ operator

I had a derived record with 3 of the same question and response fields that the only difference to the field names is a number suffix. In my example the users are asked to select 3 questions and give 3 answers for password recovery. Each of the questions are either in the Q1 (pre-defined selected question) or C1 (customer defined question) and the R1 would be the response.
Derived Work Record.
  • MACID_Q1
  • MACID_Q2
  • MACID_Q3
  • MACID_C1
  • MACID_C2
  • MACID_C3
  • MACID_R1
  • MACID_R1
  • MACID_R3
I wanted to use a for loop to execute the same checks and save each one to as a single row in my table. To accomplish this I used the @ operator (The @ operator converts a string storing a definition reference into the definition).
Destination Record.
  • EMPLID
  • QUESTION_TEXT
  • ANSWER_TEXT
  • SEQUENCE
  • LAST_CHG_DATE
  • LAST_CHG_ID
For &i = 1 To 3;
   &rec = CreateRecord(Record.CHALLENGE_QA);
   &rec.EMPLID.Value = DERIVED.EMPLID;
   &rec.SEQUENCE.Value = &i;
   &fld = "DERIVED.MACID_C" | &i;
   If None(@&fld) Then
      &fld = "DERIVED.MACID_Q" | &i;
   End-If;
   &rec.QUESTION_TEXT.Value = @(&fld);
   &rec.ANSWER_TEXT.Value = @("DERIVED.MACID_R" | &i);
   &rec.LAST_CHG_DATE.Value = %Datetime;
   &rec.LAST_CHG_ID.Value = %UserId;
   &rec.Insert();
End-For;

Peoplebooks Documenation on the @ Operator

Left Pad number with Zeros

Using the Right Function and String function.
Right("00000" | 22, 4);
This will return the string "0022"

Working with Grids

Navigating with Peoplecode

Example


Local Rowset &rs = GetLevel0()(1).GetRowset(Scroll.NAMES);

For &i = 1 To &rs.ActiveRowCount
   If &rs(&i).NAMES.NAME_TYPE.Value = "PRI" Then
      Messagebox(0,"",0,0,"This is the Primary email name ",&rs(&i).NAMES.NAME.Value);
   End-If;
End-For;

Display the code value of a translate.

I had a case where I wanted to display the actual translate code value on a grid instead of the long or short translate. To accomplish this instead of dragging the record column to the grid to add the column I used the insert menu and added a edit box. Then set the properties of the new dummy column to your record and field.