Tuesday, January 06, 2009

Serve those JavaScript Libraries Quickly... and Safely

If at all possible, don't serve JavaScript libraries from PeopleSoft applications (dynamic PeopleCode). The best place to serve a JavaScript library is from a static file residing on the PeopleSoft web server. Many PeopleSoft developers, however, don't have access to their web server file systems. The next logical place to store a JavaScript library is in an HTML definition in app designer. This is where the PeopleTools developers store JavaScript libraries and, therefore, is the preferred location for small JavaScript libraries. Unfortunately, the maximum size of a PeopleTools 8.4x HTML definition is too small to fit a good JavaScript library like jQuery. Enter John and Derek. John and Derek both wrote about a workaround to this limitation. Rather than using HTML Definitions, these innovative developers used the message catalog to store their JavaScript libraries (jQuery, of course). Here are links to those blog posts:

Both of these posts provide an alternative for PeopleSoft developers that want to use a JavaScript library but don't have access to their PeopleSoft web servers' file structure. Building upon John's and Derek's approach, here are some ideas to help improve performance.

Packing/Minification/Raw

Based on Julien Lecomte's post Gzip Your Minified JavaScript Files, we can see that Dean Edwards's Packer algorithm produces the smallest compressed file. As Julien notes, however, the benefit of that smaller download is offset by a corresponding increase in execution/evaluation time. Julien points out that other compression techniques like Douglas Crockford's JSMin and Yahoo's YUI Compressor produce larger files that evaluate much faster. Therefore, when using JavaScript compression alone, you, as an analyst or developer, need to determine which method is appropriate for your implementation. Do you have low bandwidth connecting powerful computers to your PeopleSoft instance? If so, then maybe the Packer algorithm is more appropriate for you. If you have good bandwidth (or low bandwidth and low power client computers), then maybe you should consider JSMin or the YUI Compressor. Both of these compression techniques produce compressed text files that can be stored in a message catalog entry as described by John and Derek.

Caching

Browsers are designed to cache static, externally referenced resources. The methods demonstrated in the previously mentioned blogs insert the contents of a JavaScript library into the body of an HTML page. By inserting the contents directly into a page, the browser is not able to cache it with other JavaScripts, stylesheets, and images. An alternative approach is to serve JavaScript libraries stored as message catalog entries from IScripts. Using this approach, you could reference your JavaScript library using HTML like:

<script type="text/javascript" language="JavaScript" src="/psc/portal/EMPLOYEE/EMPL/s/WEBLIB_JS_LIBS.ISCRIPT1.FieldFormula.IScript_jQuery">
</script>

The PeopleCode for this IScript would look something like:

Function IScript_jQuery()
Local string &jq = MsgGetExplainText(28000, 1, "alert('Unable to load the jQuery JavaScript library!\n\nThe Message Catalog entry (28000, 1) does not exist.');");
%Response.SetHeader("Cache-Control", "max-age=28800, proxy-revalidate");
%Response.WriteLine(&jq);
End-Function;

Update November 23, 2009: PeopleSoft actually overwrites the Cache-Control header. I have not found a way to set this through an IScript. The only solution I have found to cache IScript content is to use a ServletFilter to set the Cache-Control header after PeopleSoft returns a response.

The benefit of this approach is that users only download the JavaScript library once, rather than once per page visit. Considering cache, file size is not as important as execution time. Because the Packer algorithm produces a smaller file size, it excels in low bandwidth scenarios. With caching, however, the one time bandwidth hit might not compensate for the on going evaluation cost of the Packer algorithm. If you cache your JavaScript library, then a minification algorithm like JSMin or YUI Compressor may provide better performance.

GZip Compression

The first column of stats on Julien Lecomte's matrix compares the size of jQuery when packed, minified, or YUI Compressed. The second column compares that same content GZip compressed. GZip compressed, all three JavaScript compression methods result in a fairly comparable download size. When GZipped, the main difference between JSMin/YUI and Packer is the browser evaluation time required to "unpack" the packed JavaScript library.

The next performance improvement to explore with our IScript scenario is GZip compression. Fortunately, PeopleSoft takes care of GZip compression for us. If you look at your Web Profile settings, you will notice a check box labeled Compress Responses. If you enable this setting, PeopleSoft will automatically GZip compress the contents of your IScripts (and the contents of any other PeopleSoft page).

Whatever approach you take, be sure to consider security. OWASP listed Injection flaws as the Number 2 security concern in the OWASP 2007 top 10. Injection isn't limited to SQL. If you take user entered values and insert them into the HTML of a page, unescaped, then you have given that user the ability to inject malicious content into a page. If you use a message catalog approach and insert the contents of a message catalog entry into a page, then be sure to secure your message catalog. Failure to do so could have the same impact as the number one security threat in the OWASP 2007 Top 10: Cross Site Scripting (XSS)

29 comments:

Jaffar said...

Hi Jim,
I have a page which is redirected
by an Iscript. The iscript uses
GenerateComponentRelativeURL function to get the URL of the new component/page.Now, if the grid in the redirected page contains a lot of rows then the page takes time to load but the broweser status bar shows as 'Done' and there is no "Processing" message shown in the page till the
page contents are rendered. This is confusing..Any ways to show the processing msg till the page is rendered?

Jim Marion said...

@Jaffar, I know exactly what you are talking about. The answer? I haven't given it much thought. Here are a couple of ideas, none of them tested.

It has been a while since I've dealt with a page like this. If I remember right, the head section of the content frame HTML document shows up quickly, it is just portions of the body that take a while to appear. If this is the case, then you can use the technique demonstrated in my post Injecting JavaScript Libraries into PeopleSoft Pages to inject some JavaScript into the head section that displays a "loading..." message. You can use the body.onload event to turn off the message (I would use jQuery's $(document).ready().

Another approach is to inject JavaScript into both the header frame and the content frame. The header should load relatively quickly. You can display a message in the header and then, when the content page loads, use JavaScript in the content page to call a function in the header page to turn the message off. This method is a bit tricky because you have to account for display templates and different page types. For example, you could have a page that uses the No Frames template to display the header and content in the same page. In this case, there is no content frame to call the "hide status message" method in the header frame. Likewise, certain page types (IScripts, external pages, etc), won't contain the JavaScript necessary to reset the status message. Furthermore, if you display a page outside the frameset (using psc instead of psp in the URL), then the page will try to call the header frame function, but that frame won't exist.

Another approach is similar to the previous except that the header frame doesn't wait for the content frame to call a function. Instead, the header frame uses JavaScript to bind to the onreadystatechange event of the content frame. With this approach, you will have to account for the No Frames template scenario, but none of the page scenarios. A quick object test should satisfy this requirement.

Jaffar said...

Hi Jim, Thanks indeed for you reply. The HTML Object PT_PAGESCRIPTS[ java script] contains the code for displaying the "processing" image but then it is not getting displayed till the page is building the grid.By the time the grid building is complete the page displays it immediately [ by then 26 seconda are eaten up!!]. Any suggestions on how to do it through the Header frame? Which HTML object can be used? I tried to find out but then couldnt!! :-(

Jim Marion said...

If you are using standard tools portal and not Enterprise Portal, then the HTML definition for the header is PORTAL_UNI_HEADER_NNS.

Raja said...

Hi,
I have a related question, I am loadign grid from a rowset and returning a lot of rows. it is taking too long. after looking at the trace, I noticed that Sql comes back quick but it does a fetch (rs.select) of all the rows (e.g. 5000) and then display only 30 of them because my occurs count is set to 30. Question I have is how can I control it so it only fetches the 30 and if i need more, I can fetch more.
e.g.
PSAPPSRV.19533 (52) 1-4090559 15.21.24 0.000405 Cur#1.19533.FNDMO RC=0 Dur=0.000013 Fetch

Jim Marion said...

@Raja, what you want is lazy loading. PeopleTools 8.49 and earlier do not deliver lazy loading. To implement this, you will need to use Ajax. Take a look at the Ext JS Grid samples. You would use an IScript to serve data into the lazy loaded grid. If this is a read only grid, then this solution should be fairly easy to implement. If you want an editable grid, then you will have to either figure out how to update the component buffer with each change (hidden fields) or post data changes to IScripts.

ASCET2006 said...

Hi jim. this is a general question and not related to this topic.i tried setting the "Cache-Control" value to "max-age=0" using IScript.
%Response.SetHeader("Cache-Control", "max-age=0");
but the server is returning its value to the browser as "no-cache". can u please help me in setting the Cache-Control header value.i was able to set all other header values except "Cache-Control"

Jim Marion said...

@ASCET2006, you are exactly right. I tried the same thing a while back. No, I don't have a solution for this yet. I am not sure where the HTTP header gets set, but I'm guessing that it would be possible to override the value by implementing a servlet filter that is listed first in the servlet filter chain. That way it would be the last to return a response.

Stanny said...

Hi Jim,

I need some help with IScript/Javascript. I have a IScript function that executes a query against a SQL object and stores the return data in a local variable. Below it the iScript Function
Function ISCRIPT_XYZQRY();

Local SQL &SQLGetIDXURL;
Local string &URLIDX, &TEXTName;

&SQLGetIDXURL = GetSQL(SQL.XYZ_IDXURLQRY);
While &SQLGetIDXURL.Fetch(&TEXTName)
&URLIDX = "" | &URLIDX | """,""" | &TEXTName | "";
End-While;
%Response.WriteLine(%Request.GetParameter(&URLIDX));
&SQLGetIDXURL.Close();

End-Function;

On my page I have a HTML Area that opens this iScript as follows

script type="text/javascript" language="JavaScript" src="/psc/portdev/EMPLOYEE/EMPL/s/WEBLIB_TST.ISCRIPT2.FieldFormula.ISCRIPT_XYZIDXURLQRY"script

I wanted to know how can I pass the output of the iScript to a javascript variable

e.g. var test = I need to have this variable populated with the sql object query output from the iscript.

Your help is highly appreciated.

Thanks,
Stanny

Jim Marion said...

@Stanny,

You need to use Ajax for this. I recommend using jQuery for your Ajax library. Using jQuery, you won't create a link to a new page, rather, you will use Ajax to send a transparent request and then get the response as a variable. For your data transfer format, I recommend JSON and the json.org JavaScript JSON parser. You can have jQuery eval the server's response, but I prefer the safety of a JSON parser.

Giridhar said...

Jim,

I am Novice user to Ajax-Peoplesoft.I am trying my first test script to integrate ajax with PS.

I tried the below script mentioned by you in one of the blog:

script type="text/javascript" language="javascript"

$(document).ready(function(){

$("#ajaxcontent").load("/ajaxtest.html", null, function() {

$("#ajaxcontent").fadeIn("fast");

});

});

script



div id="ajaxcontent" style="display: none;"

!-- comment to keep PS from removing this empty div --

/div



I am not able to see the output.. i have placed the ajaxtest.html in

webserver path <..\webserv\PSTESTEDU\applications\peoplesoft\PORTAL\TESTEDU\script>

is this the correct place??...

Also where i have to place my jquery library file.

Please advice

Jim Marion said...

@Giridhar, I think you are on the right track. Looking at your file location and the HTML/JavaScript source, I think I see the problem. The directory ...\webserv\PSTESTEDU\applications\peoplesoft\PORTAL is considered "/". Since you put the file in the TESTEDU\script, the path is actually /TESTEDU/script/ajaxtest.html.

Depending on your web server and the web server's deployment mode, you may have to restart your web server after adding a file to the web server's file system.

mymithraa said...

Jim,
In my current project, client is looking for Live search in vendor search page. While they are adding new vendor they want the live search to be enabled inorder to avoid duplication. For eg: they have created a vendor with name 'Jim-peoplesoft'. Currently they are able to create vendor name 'JimPeoplesoft' which is a duplication. inorder to avoid this they are looking for livesearch. is there anyother way we can achieve override this issue..

Thanks,

Jim Marion said...

@mymithraa, I'm not sure if there is a delivered method, but you could use FieldChange PeopleCode to find similar matches. If similarities are found, then you could use one of the modal PeopleCode functions to show a list of matches and ask the user to pick one.

The type ahead prompts demonstrated for PeopleTools 8.50 is probably your best bet. To get this feature, I suggest you upgrade to PeopleTools 8.50 when it is available.

anand said...

Hello Jim,

I'm using jquery through an iscript to build some custom UI. But, when I load the page I get error "permission denied - http://xxx.com/psc/dev/EMPLOYEE/HRMS/s/WEBLIB_NAVNEWS.ISCRIPT1.FieldFormula.IScript_JQuery" in internet explorer.

Do you have any idea why I'm facing this error?

Jim Marion said...

@Anand, make sure you add the iScript/WEBLIB to a permission list and that your user has access to that permission list.

anand said...

Thanks Jim for the reply. I get the same error even though I added the web libraries to the permission list. BTW, I bought your book and I feel it is incredible.Its a great book.

Jim Marion said...

@Anand, I really hope you find the book useful. Thank you for the compliments.

I am very familiar with that error. I have only seen it under two conditions:

1. Permission list/security. Make sure you select the Full Access drop-down item instead of the No Access one for you function.

2. The URL is incorrect. By incorrect, I mean the function is spelled wrong, the field or record are spelled wrong, or you used the wrong case, upper when it should be lower, etc.

You mentioned that you already set the permissions correctly, so the next place to look is your URL.

sweet said...

Hi Jim,

Thanks for your reponse. I was able to download the json_parse.js. Could you please give me an example of how to use this json_parse.js to pass the output of SQL object from an iScript to Javascript variable.

Thanks,
Stanny

Jim Marion said...

@Stanny, From SQL Object to JavaScript... At this time, there is no "good" way to convert the output of SQL into a JSON object. Basically, what you do is string together the results as a JSON string, and return that as the result from your iScript. When you string together your JSON, make sure you encode unsafe characters. Take a look at my post JSON Encoding in PeopleCode for more information.

sweet said...

Hi Jim,

Thanks for your reponse. I just purchased your book to understand how I can use Ajax to get the reponse from my iScript. I started reading chp 6 and 7 and I am on pg250 of chp 7 where I copied the bootstrap code at the end of the PT_COPYURL html defn. But I am getting this error "'null' is null or not an object." error line number points to buffer[0] = "/psc/" statement. I just wanted to know if this chp7 is defendent on others chapters.

Could you please me with the error.

Thanks,
Stanny

Jim Marion said...

@Stanny, chapters 6, 7, and 8 build very heavily on each other. Each one rewrites significant portions of the shared code. You might use fiddler or firebug to see if there is a JavaScript library iScript that isn't downloading (either because it doesn't exist or because of a permission issue).

For each chapter, the working code for that chapter is in the source code available from McGraw Hill

D said...

Jim, I got this technique from Chapter 6 of your book and have been using it with much success. After our upgrade to peopletools 8.52 it no longer works. Seems that any script tag in a HTML Area is move to the page header. The "src=" of the script tag is not moved. So, you end up with an url between two script tags. Have you seen this behavior? I am trying to work on new solution.

Pinky&theBrain said...

I am having a similar issue, where we are using an html area to call an external page (redirect user to page outside of ps). We recently upgraded to 8.52 and this functionality is no longer working. We suspect it is due to html objects being deprecated in 8.52. would greatly appreciate any direction on what we need to change to get page working

Jim Marion said...

@D, @Pinky, I have not seen this behavior. I think PeopleSoft generated HTML is run through HTMLTidy before it is sent to the browser. Ensure that your script element's HTML is correct. Between the begin and end tag, place a comment or a non breaking space so that the tag is not empty. Also make sure you properly quote attributes.

HTML Areas are standard, delivered functionality, so open a case with MyOracle Support if you are not able to identify the issue.

@Pinky, are you seeing your scripts moved to the head as well? Since you mentioned deprecated HTML objects, I'm wondering if you are using JavaScript injection through PT_COPYURL or something. In 8.52 there was an effort to consolidate common HTML definitions. The point was to improve performance.

Pinky&theBrain said...

Per a communication back in April of 2011, the following was posted by Oracle

"we no longer support HTML based templates for use with Peopletools generated pages. HTML can be used with static pages in 8.50 & 8.51, but customers will be required to move to new templates completely in 8.52"

Does this verbiage relate to HTML areas? Would the deprecation of the templates prevent html area from routing user to external page outside of PS. What does the above mean and what does it apply to??

Jim Marion said...

@Pinky, that quote applies specifically to HTML CREF Templates stored as CREF's in the Portal Registry. CREF templates are in Portal Objects > Templates. Instead of HTML stored in the portal registry, PT 8.52 requires you to use iScript based CREF templates. This is for related content, workcenters, etc.

Pinky&theBrain said...

thank you very much Jim. My inclination in regard this issue was that the 8.52 deprecation posting, as it related to the behavior we we're seeing, was a red herring. I have been able to get to the external site by changing a peoplecode command.

While we are able get to external site, and are returned from external site back to PS without issue; however, the behavior remains unpredictable in IE. In Firefox, Google Chrome, and Safari, we encountered no issues with the functionality of this page.

Thus, I think the behavior we've seen is more attributable to IE8 issues versus HTML Area functionality being deprecated. Thank you for confirming my suspicions.

Shantanu Kumar said...

Thanks Jim. I'm doing a reCaptha integration with my PeopleSoft page. I create a Iscript function similar to yours that has below script in Message catalog.

script src="https://www.google.com/recaptcha/api.js"

I need to load this iscript when user enters that page, so that library gets loaded. I tried multiple ways but it is not loading. Can you please advise?

Thanks, Shantanu