Wednesday, May 07, 2008

Simplify HTML Pagelet Migration with a Custom Transformer

Generally speaking, IT organizations like to keep development code separate from production code. When development code is ready for production, good change management rules dictate a graduation/migration path, usually something like DEV > TEST > QA > PRO. Likewise, on occasion, a development team may request a fresh copy of a production database. With PeopleTools managed objects, we can easily migrate Application Designer projects between our environments without much concern for environment specific code. Pagelet Wizard created pagelets, however, are not managed objects and need to be migrated using Data Mover Scripts. Some pagelets, like Query pagelets, require managed objects in order to function. Other pagelets, like HTML pagelets may contain hard coded URL references to other DEV/TEST/QA/PRO servers. How can we effectively manage these URL's? Can we, as PeopleSoft developers, automate the process for updating these URL's?

As we know from experience, HTML data sources typically use the Passthru display format. Passthru means the HTML content from step 2 is displayed in the Pagelet without any processing or modification. Therefore, we can't leverage Meta-HTML or any other PeopleTools functionality to simplify the management of our HTML based pagelet. We can, however, create a new transformer and display type and have this new transformer perform additional processing to help us resolve this URL maintenance problem.

Now that we have a method for implementing additional processing, how shall we maintain the actual URL's? I recommend using node definitions. Node definitions contain a portal tab where we can enter a content and a portal URL. As delivered, PeopleTools uses these node URL's when creating real URL's from portal registry content references (CREF's). Using a custom transformer, we can leverage this existing Meta-data to dynamically generate the URL's included in HTML Pagelets.

What we need is a custom Transformer that can convert a URL placeholder into a full URL using database driven Meta-data. Keeping with PeopleTools convention, the solution below will demonstrate the creation of a transformer that resolves custom Meta-HTML tags. This custom transformer will expand the text %NodePortalURL(NODE_NAME) into the Portal URL for that node. This will allow us to write Pagelet Wizard HTML like:

<img src="%NodePortalURL(NODE_NAME)/images/dynamic-chart.png"/>

And our custom transformer will translate this HTML into:

<img src="http://other.server.name:8080/images/dynamic-chart.png"/>

To implement this solution we need an Application Package to store our custom App Class. For demonstration purposes, we will call this Application Package CUSTOM_TRANSFORMERS. I expect in your environment, you will prefix this new Application Package with your site specific prefix. In this new Application Package, we will add a new package called Transform. And, to this new package, we will add the class NodePortalUrlTransformer. This new class, NodePortalUrlTransformer will contain the code required to implement our URL transformation feature. The path for our new Application Class should look something like CUSTOM_TRANSFORMERS:Transform:NodePortalUrlTransformer. Here is the code you will need to paste into this new Application Class:

import PTPPB_PAGELET:UTILITY:*;
import PTPPB_PAGELET:Transformer:*;
import PTPPB_PAGELET:*;

/**
* Transforms Text by replacing %NodePortalURL(NODE_NAME) with the Portal URI
* of the node NODE_NAME.
*/
class NodePortalUrlTransformer extends PTPPB_PAGELET:Transformer:Transformer
method NodePortalUrlTransformer(&ID_param As string);
method execute(&pageletID As string) Returns string;
method Clone() Returns PTPPB_PAGELET:Transformer:Transformer;
end-class;

/**
* Constructor.
*
* @param id_param ID of this object. Should be unique.
*/
method NodePortalUrlTransformer
/+ &ID_param as String +/
%Super = create PTPPB_PAGELET:Transformer:Transformer(&ID_param);
end-method;

/**
* Replaces%NodePortalURL(NODE_NAME) with the Portal URI
* of the node NODE_NAME.
*
* @param pageletID ID of the pagelet being executed.
*/
method execute
/+ &pageletID as String +/
/+ Returns String +/
/+ Extends/implements PTPPB_PAGELET:Transformer:Transformer.execute +/
Local JavaObject &pattern;
Local JavaObject &matcher;
Local string &sourceText = %This.DataToTransform.Value;
Local string &transformedText = &sourceText;
Local string &nodeName;
Local string &nodeUrl;

REM ** Resolve %NodePortalURL tags;
&pattern = GetJavaClass("java.util.regex.Pattern").compile("(?i)%NodePortalURL\((\w+)\)");
&matcher = &pattern.matcher(CreateJavaObject("java.lang.String", &sourceText));
While &matcher.find()
&nodeName = &matcher.group(1);
If (&nodeName = "LOCAL_NODE") Then
&nodeName = %Node;
End-If;
SQLExec(SQL.GET_NODE_URI, &nodeName, "PL", &nodeUrl);
&transformedText = Substitute(&transformedText, &matcher.group(), &nodeUrl);
End-While;
&pattern = Null;
Return &transformedText;
end-method;

/**
* Make an exact copy of this object.
*
* @return Text Exact copy of this object
*/
method Clone
/+ Returns PTPPB_PAGELET:Transformer:Transformer +/
/+ Extends/implements PTPPB_PAGELET:Transformer:Transformer.Clone +/
Return create CUSTOM_TRANSFORMERS:Transform:NodePortalUrlTransformer(%This.ID);
end-method;

Keeping with good PeopleTools coding practices, the code above references a managed SQL object named GET_NODE_URI. Here is the SQL for that SQL definition:

SELECT URI_TEXT 
FROM PSNODEURITEXT
WHERE MSGNODENAME = :1
AND URI_TYPE = :2

Now that we have our code in place, we need to register our new Transformer so we can use it in our HTML pagelets. Rich Manalang wrote up an excellent transformer tutorial following the same steps, but his includes pictures. You can find a link to his tutorial below.

We will start by registering the Transform Type. Navigate to:

Portal Administration > Pagelets > Pagelet Wizard > Define Transform Types > Add

On this page, you will need to give your Transform Type a name, a description, and specify the implementing Application Class. If you have been following this example, then use the following values:

Transformation Type: NODE_PORTAL_URL
Description: Resolve Node URL Meta-HTML
Package Name: CUSTOM_TRANSFORMERS
Path: Transform
Application Class ID: NodePortalUrlTransformer

Next, you will need to define a Display Format that corresponds to your new Transform Type. Navigate to:

Portal Administration > Pagelets > Pagelet Wizard > Define Display Formats > Add

You can use the following values to define your new display format. Again, the Define Display Formats graphic on Rich's post applies. In fact, the only values you need to change are the Display Format ID, Description, and Transform Type. For reference, I've repeated the values below:

Display Format ID: NODE_PORTAL_URL
Description: Resolve Node URL Meta-HTML
Transform Type: NODE_PORTAL_URL
Page Name: PTPPB_WIZ_DISP_PST
Package Name: PTPPB_PAGELET
Path: TransformBuilder
Application Class ID: PassthroughBuilder

The last step is to associate our new Display Format with the HTML Data Type. Navigate to:

Portal Administration > Pagelets > Pagelet Wizard > Define Data Types

Select the HTML Data Type. At the bottom of this page, in the section titled Display Formats to use with this Data Type, we need to add our new Display Format: NODE_PORTAL_URL. After you save, your configuration and development for your new transformer is complete. You may now use this new Meta-HTML character sequence in your Pagelet Wizard HTML Pagelets.

The Enterprise Portal's Pagelet Wizard is one of my favorite productivity and usability tools. One of it's key features is its extensibility. The Pagelet Wizard contains components that allow you to register your own data sources, display types, and transformations. Likewise, the Internet Technology PeopleBook contains a section devoted to Pagelet Wizard configuration, customization, and usage. If you prefer examples over PeopleBooks, you can open and view the delivered Pagelet Wizard Application Packages in Application Designer. If you are interested in creating your own data source, tranformer, or display type, you may find a delivered Application Class you can copy and modify.

Additional resources:

29 comments:

Unknown said...

Hi Jim,

I have a question, not really related to your post, but related to Portal PeopleCode. I have a simple PS page for timesheet entry. I noticed slowness after entering 8 for the daily hours and tabbing to the next day. Upon tracing I've found that just after this entry and tab, the code is calling GetPortalURI and then doing thousands and thousands of lines of portal code. I'm not sure why it would do all of this code just for tabbing out of these daily time fields. I'm not calling anything in the portal and this is slowing down my performance.
Do you know how I can stop this code from happening on the tabs?
Thanks very much!
Susanne

Jim Marion said...

@Susanne, I have not seen that before. I suggest you enter a case in Oracle Support.

James said...

Do you know if it is possible to expose a Peoplesoft pagelet to an external website without executing SignOn PeopleCode, so that it can be accessed by the external website via a URL? In other words, I don't want to have to sign-in via my own user/password, or via a guest account setup in the web profile.

Jim Marion said...

@James, interesting question. Pagelet Wizard pagelets are iScripts that can be accessed from a direct URL, just like any other PepleSoft component. PeopleSoft does require an authenticated session.

I can think of two ways around this. Pagelet Wizard generates pagelets through app classes. This means you could write a web service that executes a pagelet by creating an app class and getting the result. You can use the iScript code in WEBLIB_PTPPB.ISCRIPT1.FieldFormula.IScript_PageletBuilder as an example. Integration Broker handlers can run as the default user on the ANONYMOUS node (or another node if you don't use the "any to local" routing).

The other alternative is to write a "proxy" program that is just some perl, PHP (whatever language you prefer) that hits the signon page to get a cookie, then hits the iscript or component URL to get its contents. You would have to hard code the user's credentials in the proxy program, though.

There are a couple of ways to get an authentication token. I show one way in my post Generating an Authentication Token for SwitchUser. Another way is to create an Integration Broker web service that returns a PS_TOKEN and hard code the limited user's credentials in your request handler.

Abhishek said...

Hi Jim,

I need some help on Peoplesoft portal.. Wanted to know if its possible to call one pagelet from another..

We have a requirement... We need to build pagelet for New hire in our copany, The HTLM in the pagelet has links at the top, on click of the links it open a different HTLM page.. Like we see in soem of the websites..

I developed a paglet using that HTML but we dont know how to show the other pagelets when the Team Member clicks on other links at the top.

Can you please suggest if its doable and how..

Thanks!

Abhishek said...

Hi Jim,

I have a question regarding call pagelet from another pagelet.

We have a require where we got a HTLM from a source, using that we created a pagelet and showing in a seperate TAB, but the HTML has link at the top like different pages.. when you click on the link it should open a different HTML page.. the page is all read only means only TEXT and pics.. How can we achive this in Poeplesoft Portal... Its like webpage, where you open a web page and then click on different link to navigate to differnt pages.

Please suggest.

Thanks
!

Jim Marion said...

@Abhishek, When you say, call another pagelet, are you referring to inter-portlet communication? PeopleSoft does not deliver anything like that. If you use JavaScript and HTML based pagelets, you could certainly build it into your pagelets.

Your second request... are you trying to create a tabbed pagelet? I think the easiest way is to use the jQuery UI tabs and, for each tab, use the Pagelet Wizard iScript URL for each tab's content. You can get the URL for each pagelet by viewing the source of a homepage.

Abhishek said...

Sorry.. My question was not clear. What we need is.. a TAB on portal and that tabl will have a HTML page and the HTML page will have different link.. for example when we open a webpage we see file edit view favorites tools help.. The same way our HTML page will have at the top.. so when user click on the different link it will open a new HTML page.. To achive this we are planning to have a TAB and HTML pagelet with No Boders so that it will look like a Page.. and for every link we are planning to create HTML pagelet (HTML will be provied by other team)

But we are not sure how we open a pagelet when the user clicks on the link at the top. We are not assiging the pagelt to any of the TAB so that it will not be visible. What I tried is in the HTML I hardcoded the URL with the other pagelet(gave the href) its pulling the pagelet but its opening header under header and its a hardcoding which we dont want.. Can you please suggest how can we achive this..

I am new to Portal and scripting....

Abhishek said...

I have created this Iscript..

Function IScript_GeneratePageletURl

&uRL = GenerateScriptContentURL(%Portal, %Node, Record.WEBLIB_PTPPB, Field.ISCRIPT1, "FieldFormula", "IScript_PageletBuilder");
&uRL = &uRL | "?PTPPB_PAGELET_ID=GEX_NEW_HIRE";
%Response.RedirectURL(&uRL);

End-Function;

when I give call this iscript on the URL it works fine... I see the pagelet... The problem is I am not able to call this from HTML

His my HTML.. In the below HTML I need to call this Iscript when user clicks on Your benefit... With the current HTML its appending the Iscript path to the URL but its coming as /EMPLOYEE/EMPL/h/WEBLIB_PTPPB.ISCRIPT1.FieldFormula.IScript_GeneratePageletURl but when I change the "h" to "s" in the URL it shows the pagelet...



I was also trying to use "script" but not sure its not working ... I am not sure how to change the h to s and if its the way of doing it... can you please suggest.

Jim Marion said...

@Abhishek, GenerateScriptContentURL is the function to use. It should generate a /s/ URL, not a /h/ URL. I suggest you install Fiddler to see the redirects, etc, and see if you see something else going on.

I am curious, though, why are you using an iscript to generate a URL to an iscript? Is this iscript just for testing purposes?

Abhishek said...

Ok let me explain the requirement

In our requirement.. We have a home page and with links at the top which will call other pages... Now the home page is all HTML and for that we have created pagelet using data source as HTML....

This new pagelet is assigned to a TAB and it shows the page properly... Now we need to work on other pages which will be displayed when the user click on the link on the HOME page.. we created pagelet for other pages also( This pages will be displayed when user click on the link of HOME page)

Now the issue is how do we call this different pagelets when user click on it??? We dont know how to call pagelet from HTML.... if we hardcode the link(Pagelet link) in the HTLM as "Href" it works but we dont want to hardcode as the sever may change and it not good..

We created Iscript which will generate the link to different pagelets... but we dont know how to call Iscript from HTML(used in the pagelet of HOME page)..... This is kind of critical and I never worked on scripting before so I dont even know if what I am doing is right..

Please suggest.

Jim Marion said...

@Abhishek, OK. So, it sounds like you are using an HTML based pagelet from pagelet wizard, and you don't want to hard code the URL for all the other pagelets within the one HTML pagelet. What I suggest instead is that you move your HTML into an HTML definition in app designer, replace the URL's with %Bind(:n) (where n represents the bind number), and then create an iScript to generate all the URL's. You would then register the iScript as a pagelet by going to PeopleTools > Portal > Structure and content, and then to Portal Objects > Pagelets > [folder where you want pagelet] and creating a new CREF with usage type set to Homepage Pagelet.

Perhaps this is where you already were in your steps and is why you were creating a URL to an iScript from an iScript. That would make sense... although you wouldn't be doing a redirect if that was the case.

Now, about the /h/ instead of /s/. As you probably know /h/ is for homepage URL's. It sounds like the GenerateScriptContentURL function isn't generating a fully qualified URL, like it is missing the server, site, portal, and node portions. Did you check your local portal node definition's portal setting? Not your default local node, but the local portal node. It would be something like HRMS, EMPL, ERP, CRM, ELM, EPM, etc, depending on the application.

Abhishek said...

Yes.. You got the requireent however I am confused with tha what I need to do... I pasted the HTMl into HTML Object... and What would I write in the Iscript and how will I call the Iscript, If I make the content refference as Home Page TAB .. IT will be a tab but no data right?

Please suggest.

Jim Marion said...

@Abhishek, start at the bottom of this list of posts and read to the top: IScript Posts.

Your PeopleCode will have your calls to GenerateScriptContentURL and then a call to GetHTMLText, and finally, %Response.Write to write the contents to the client. GetHTMLText will take the URL's as parameters and return the resultant HTML that you want to send to the client.

I think you said you would make the iScript a tab CREF? No, it doesn't work that way. What you would do is create a tab CREF and add the iscript pagelet as required fixed. If you only want one pagelet, and not the 2 column/3 column layout then create your own template, set the layout to be 2 or 3 column, and then add a CREF attribute named PORTAL_HP_2COL_LAYOUT (or PORTAL_HP_3COL_LAYOUT) and set the value of the attribute to be your new HTML layout (usually a modified clone of PORTAL_HP_2COL_LAYOUT or PORTAL_HP_3COL_LAYOUT)

Abhishek said...

Thanks a lot JIM for you help in this.

I could make it work... This is what I did... I am not sure if this is the best way of doing it...

1)Create one TAB
2) Create a pagelet which calls Iscript for Home page...
3) Created iscript for all other pages..
Function IScript_YourBenefits

GEX_Prepare_URL();
&html = GetHTMLText(HTML.GEX_YOUR_BENEFITS, &Url_Your_Benefits, &Url_Health_Wellness, &Url_TM_CareFund, &Url_TM_Discount, &Url_Store_n_Brand, &Url_Diversity, &Url_Safety);
%Response.WriteLine(&html);

End-Function;

Function IScript_HeathnWellness

GEX_Prepare_URL();
&html = GetHTMLText(HTML.GEXHEALTHANDWELLNESS, &Url_Your_Benefits, &Url_Health_Wellness, &Url_TM_CareFund, &Url_TM_Discount, &Url_Store_n_Brand, &Url_Diversity, &Url_Safety);
%Response.WriteLine(&html);

End-Function;

Function IScript_TM_CareFund

GEX_Prepare_URL();
&html = GetHTMLText(HTML.GEX_TM_CAREFUND, &Url_Your_Benefits, &Url_Health_Wellness, &Url_TM_CareFund, &Url_TM_Discount, &Url_Store_n_Brand, &Url_Diversity, &Url_Safety);
%Response.WriteLine(&html);

End-Function;

/*Iscript Your Benefits - End*/

Function IScript_TM_Dicount

GEX_Prepare_URL();
&html = GetHTMLText(HTML.GEX_TM_DISCOUNT, &Url_Your_Benefits, &Url_Health_Wellness, &Url_TM_CareFund, &Url_TM_Discount, &Url_Store_n_Brand, &Url_Diversity, &Url_Safety);
%Response.WriteLine(&html);

End-Function;

Function IScript_StoreNBrand

GEX_Prepare_URL();
&html = GetHTMLText(HTML.GEX_STORE_BRAND, &Url_Your_Benefits, &Url_Health_Wellness, &Url_TM_CareFund, &Url_TM_Discount, &Url_Store_n_Brand, &Url_Diversity, &Url_Safety);
%Response.WriteLine(&html);

End-Function;

Function GEX_Prepare_URL

&Url_Your_Benefits = &GEX_URL_PSC | &GEX_Portal_Registry_Name | "/" | &GEX_URL_Node | "/" | "s" | "/" | "WEBLIB_NEW_HIRE.ISCRIPT1.FieldFormula.IScript_YourBenefits";
&Url_Health_Wellness = &GEX_URL_PSC | &GEX_Portal_Registry_Name | "/" | &GEX_URL_Node | "/" | "s" | "/" | "WEBLIB_NEW_HIRE.ISCRIPT1.FieldFormula.IScript_HeathnWellness";
&Url_TM_CareFund = &GEX_URL_PSC | &GEX_Portal_Registry_Name | "/" | &GEX_URL_Node | "/" | "s" | "/" | "WEBLIB_NEW_HIRE.ISCRIPT1.FieldFormula.IScript_TM_CareFund";
&Url_TM_Discount = &GEX_URL_PSC | &GEX_Portal_Registry_Name | "/" | &GEX_URL_Node | "/" | "s" | "/" | "WEBLIB_NEW_HIRE.ISCRIPT1.FieldFormula.IScript_TM_Dicount";
&Url_Store_n_Brand = &GEX_URL_PSC | &GEX_Portal_Registry_Name | "/" | &GEX_URL_Node | "/" | "s" | "/" | "WEBLIB_NEW_HIRE.ISCRIPT1.FieldFormula.IScript_StoreNBrand";
&Url_Diversity = &GEX_URL_PSC | &GEX_Portal_Registry_Name | "/" | &GEX_URL_Node | "/" | "s" | "/" | "WEBLIB_NEW_HIRE.ISCRIPT1.FieldFormula.IScript_Diversity";
&Url_Safety = &GEX_URL_PSC | &GEX_Portal_Registry_Name | "/" | &GEX_URL_Node | "/" | "s" | "/" | "WEBLIB_NEW_HIRE.ISCRIPT1.FieldFormula.IScript_Safety";
&Url_HomePage = &GEX_URL_PSC | &GEX_Portal_Registry_Name | "/" | &GEX_URL_Node | "/" | "s" | "/" | "WEBLIB_NEW_HIRE.ISCRIPT1.FieldFormula.IScript_HomePage";

End-Function;

Abhishek said...

Ok.. I found where they have defined the footer but I dont understand why the HTML is cutting from bottom. Can yu please suggest.

Abhishek said...

Hi Jim,

Need some suggest on refreshing the home page.. We have a pagelet on home page with Iscript.. We are doing some validation and based on output of validation we are displaying modal page. basically we need display modal email colelction page when user logon if the email address is not there.. the page works fine but it deosnt update the pagelet.. So if the user has enteretd the email address he should not see th modal again but unless he logs out he see the modal becaise of cache.. I read there are couple of the deluvered fucntion which would refresh the TAb but where it to use those fucntions as the page would be laoded with cache data it wont execute the fucntion and also ther eis another option of unchecking Cache home page in web profile config..
the second option works but it would increase the load on the serevr by tripping everytime...

Would you please suggest how can we fix this cache issue.. We need to have the code executed evertiem the page laods..

Jim Marion said...

@Abhishek, I know exactly what you are talking about because I find myself in the same situation. PeopleTools 8.52 has a checkbox for CREF's that allows you to specify that a pagelet should not be cached. Another option that avoids cache is to set the 8.51 attribute to PTPGLTDEFERLOAD. This attribute is described in the Red Paper Configuring a Contemporary User Experience. If neither of these options satisfy your requirements, then I suggest you log a bug.

Abhishek said...

Thanks Jim, unfortunately we are still in 8.48. Is there any solution for 8.48.. Now I think web profile option is also not working but I am sure it was working...

Jim Marion said...

@Abhishek, I'm pretty sure that if you turn off homepage caching, it will work the way you want it to work. For 8.48 no-cache pagelet settings, you may want to file a bug to get the settings. I'm not familiar with pagelet caching in pre 8.50.

Mohit said...

Hi Jim,

I want to show worklist as homepage portal. Please help me

Thanks,
Mohit.

Jim Marion said...

@Mohit, a pagelet is just a CREF that exists in the portal registry under the folder Portal Objects > Pagelets > [some category]. When creating the pagelet, set the usage type to homepage pagelet.

You can create a pagelet from anything, including an existing component. To avoid the "content reference already exists" error, add a parameter to the parameters section of the CREF (not attributes, but additional query string parameters). I usually add something like one=1.

Tom Mannanchery said...

Hi Jim,

I had a question about the Content Management pagelet in Interaction Hub, but I didn't know where to post it - so asking here.

A user with the 'Author' privileges can see 'Edit Content' link in a CM pagelet. Is there a way to hide this link in the pagelet without customizing the code or the HTML object?

Thanks,
Tom

Jim Marion said...

@Tom, for Authors, no. The alternative is to publish the news item with a custom stylesheet in Pagelet Wizard.

Unknown said...

Hi jim,
I had a question , i want to show some information from a web page to peoplesoft landing page. i created pagelet using html type but didn't get solution.please tell me proper way.

Jim Marion said...

If your html is on a different server or outside PeopleSoft, then try the URL data type.

Jim Marion said...

If your html is on a different server or outside PeopleSoft, then try the URL data type.

Ti said...

Jim,
I hae a similar problem, but on the branding footer HTML. Every refresh I have to go there and udpate the hardcoded path (dev/test/prod), is there a meta data I can use to dynamically get the right path? Isee that some html files within the WEB-INF folder have <%=psCtxPath%><%=psHome%>, will this work inside an HTML definition?
Thanks!

Jim Marion said...

@Ti, there is nothing delivered. I believe those variables only work in the templates in the WEB-INF directory. The point of this blog post was to show you how to create a custom transformer so you can create your own key words for things like the dev/test/prod path.