Thursday, October 15, 2009

Change the Advanced Search Page Default Search Operator

In my OOW presentation yesterday I showed that it is possible to change the default search operator for a specific advanced search page. The JavaScript required to implement this behavior follows:

<script type="text/javascript">
$(document).ready(function() {
var newValue = 9;
var coll = $("select[name='APT_UI_SCRIPTS_MENUNAME$op']");
if(coll.val() != newValue) {
coll.val(newValue).change();
}
});
</script>

Note: The code above uses jQuery for event handling and therefore requires that you inject jQuery along with the JavaScript above. I demonstrate how to accomplish this in the injection blog post referenced below.

In my PeopleTools book I provide full details for implementing this behavior, but if you want to get a head start, I'll give you some hints. First, you need a mechanism for injecting this JavaScript into a search page. To learn more about injection, read my post Injecting JavaScript Libraries into PeopleSoft Pages. Next, you need a way to target a specific component. For this, see my post JavaScript complement of PeopleCode Global Vars. Your final step is to write some JavaScript to determine if the open page is a search page. I determine this by testing for the existence of an object named #ICSearch.

To determine the numerical code for a search page operator (9 = between), open the HTML source of any advanced search page and search for the option child nodes of PSDROPDOWNLIST.

Update (April 15, 2010)

The code above works great with PeopleTools 8.49 and lower. PeopleTools 8.50 uses Ajax to switch from the basic to the advanced search page. The 8.50 Ajax does not trigger the $(document).ready code. I have another solution for PeopleTools 8.50 (documented in my book). I'll post it soon.

35 comments:

Unknown said...

Hi Jim, I need to capture the search params entered on a page for all the components that the user traverses, then put all the links visited on a seperate pagelet, so that he can go directly to that page clicking the urls captured - basically hot links. your Idea how to go aboubt this?

Jim Marion said...

@Kim, have you seen the PeopleTools 8.50 Most Recently Used items in the Favorites menu (MRU)? I believe it does exactly what you are referring to. To implement this yourself, I would create a record definition for storing URL's, an IScript for inserting URL's, and then add some JavaScript to PT_COPYURL (see Injecting JavaScript Libraries into PeopleSoft) to send the strCurrUrl variable up to your IScript (I reference strCurrUrl in comments to several of my posts. You can find them from this Google search). strCurrUrl contains the level 0 search keys so you can open a transaction directly. Unfortunately, this IScript/JavaScript combo would run every time the page loads (FieldChange, etc), I don't have a good way to avoid this. One way to handle this is to key the table by OPRID and URL. If the row exists, you ignore it, or even better, increment a ranking number. The additional posts will add overhead, but you might be able to ignore it. Since you are using an IScript to handle the URL post, it is much lighter on the app server than serializing the component buffer.

Unknown said...

Many thanks for that Jim, let me try it!!! And I dint have a chance to look at the featurei in 8.5. But, yes that is exactly what I am trying to do.

Cory Uhrich said...

Has anyone gotten this to work other than Jim? I need to do this but haven't figured out how to call the javascript on a search page. Any help would be greatly appreciated. Looking forward to the book Jim, I have already pre-ordered it.

Cory Uhrich said...

Ok, I figured out how to get it on a search page from your other post. Now I get "object expected" on I.E.

Here is my code...
$(document).ready(function() {
var newValue = 2;
var coll = $("select[name='C_PERS_SRCH_REH_NATIONAL_ID$op']");
if(coll.val() != newValue) {
coll.val(newValue).change();
}
});

Jim Marion said...

Did you inject jQuery per my instructions at the bottom of the post? It sounds like IE doesn't like "$"

Jim Marion said...

@Cory, one more thing... If you inject jQuery using the technique I show, then you will also have to inject the script on this post. You won't be able to place this script inline. The problem is that when you inject jQuery, your browser will asynchronously download jQuery, but will continue to process the existing script. The end result is that the script (which requires jQuery), will process before jQuery is loaded and will give you the "Object expected" error. Firefox with the Firebug console might give you more help, but I think it will say something like, "$ is not defined."

Cory Uhrich said...

I did not, is there a way to do it without using jQuery?
Thanks

Jim Marion said...

@Cory, yes there is. I am a huge jQuery fan because it dramatically simplifies my code. If you prefer to do it without jQuery, then you want the stuff inside $(document).ready to run on document.onload. Search the internet for attachEvent. I believe that is what you want to use so that you don't overwrite any PeopleSoft delivered onload code. Next, replace $("select[name='APT_UI_SCRIPTS_MENUNAME$op']") with document.getElementById("THE_ID_OF_YOUR_FIELD$op") and adjust the selected item accordingly. It has been a really long time since I've done this without jQuery, so I'm no help there. Next you want to trigger the onchange event. Be sure to keep the if test. You don't want to trigger the onchange event if the value is already set to your new value.

If you aren't using jQuery, you can ignore the whole injection thing and just put your code directly in an HTML definition like PT_COPYURL. Also, be sure to test the result of document.getElementById. If it didn't find that element, then don't run the code. jQuery does this automatically, but without jQuery, you are on your own. If the element doesn't exist, then you might be on the basic search page and you want your browser to ignore the code.

Cory Uhrich said...

Is it possible to use Google's hosted jQuery?

Example...
http://dl.dropbox.com/u/224797/JavaScript.txt

It says I have syntax errors on "var newValue = 2;"

I also tried without calling jQuery but its returning null for "coll" which I think it means that its executing the JavaScript before the page loads. I couldn't seem to figure out the attachEvent piece. I am just placing it on PT_COPYURL. Any ideas? Thanks again for the replies.

Jim Marion said...

@Cory, A couple of issues:

1. PT_COPYURL is JavaScript, not HTML. This means you can't put <script> tags in it. To include additional JavaScript, you have to inject as described in my post on JavaScript injection.

2. If you inject a JavaScript library and want to execute JavaScript that depends on that injected JavaScript, you have to inject it as well. Otherwise, the remaining JavaScript will execute before the library is loaded.

If you do not have access to your web server, you can place both of these (your JavaScript and jQuery) in message catalog definitions and serve them from an IScript.

Yes, you can use the hosted jQuery instead of hosting your own copy. I don't like this solution because it gives someone else direct access to your page content.

Cory Uhrich said...
This comment has been removed by the author.
Cory Uhrich said...
This comment has been removed by the author.
Cory Uhrich said...

Ok, I now have jQuery on the server and I injected the library, but now am trying to inject the above script without success. Do you have an example on how to inject the above code? Thanks again!

Jim Marion said...

@Cory, it sounds like you are getting this figured out. I am sorry you have to struggle through it. My book devotes a few hundred pages to these topics :)

Yes, document.write is how you inject code while the page is loading. Yes, the example is half way down the post Injecting JavaScript Libraries into PeopleSoft Pages. To inject the code on this page, place the code in a static *.js file on your web server, just like your jQuery file and then add this line immediately following your jQuery injection line:

document.write("<scr" + "ipt id='xxx_ui_search_op' " + "src='/scripts/name_of_searchop.js'><\/script>");

When you save the JavaScript, just remove the <script> HTML tag wrapper. Since it is a .js file, you can't include HTML tags.

If you are unsure as to whether your injection is working, Try using Firefox and Firebug, Chrome, or Safari. These three browsers show resource downloads and have a JavaScript console that allows you to type things like $ and see results. Fiddler is also a very good tool for inspecting downloads. So, to see if injection is working, the first thing you need to do is see if your browser is downloading jQuery and your additional search page script. Fiddler, Firebug, Chrome, and Safari will show this. If injection is working, you will see an http request for the file.

Next is to test if your browser is interpreting the injected JavaScript. To test this, change your URL from .../psp/... to .../psc/... and then type $ or window.jQuery into the JavaScript console. This will tell you if jQuery is working.

Once that is working, navigate to your advanced search page and copy/paste the part inside the $(document).ready into the JavaScript console.

Oh, one more thing. Are you using PeopleTools 8.50? This code doesn't work on 8.50. My book has code for 8.50, but I haven't posted it on my blog yet. I'm working on a post for that. Hopefully that is not the case for you.

Cory Uhrich said...

That did it! Awesome. (We are on 8.49) Fiddler showed 404 errors on my custom *.js files, so I found that if I put in the environment name in the path it worked.
/HRDV/scripts/jquery-1.4.2.min.js

Problem is I don't want to have to hard code environment names in the path for each webserver in my js files. Any ideas on how to get around that?

Next step now is to make it specific to the component and page.

Thanks again for your patience and help with this.

Jim Marion said...

@Cory, from your post, it looks like you are still using a relative URL (no server name), but you had to put in the env name (HRDV). Is this because you are using a reverse proxy or because you are placing these files in a sub directory of ../applications/peoplesoft/PORTAL/?

To work around the URL issue, I use regular expressions to parse strCurrUrl or window.location.pathname... actually, that is what I'm doing here, but for different pieces of the URL.

Cory Uhrich said...

Yes, we are placing them in a sub directory of applications/peoplesoft/PORTAL.

Can I just use the existing .js and put in the regular expressions in there?
Thanks

Jim Marion said...

Yes, place it in the existing js.

Cory Uhrich said...

That did it. I parsed it out, used the envt name, and then only had it execute on the specific page for that field. I really appreciate your help. Cant wait for the book!

Jim Marion said...

@Cory, cool. Thank you for the update.

Dan said...

Jim

Do you have any experience using the jQuery validate plugin to do client-side validation in PeopleSoft? I've played with it a bit with no success. I can get the validation to run but it does not seem to catch my errors.

Cheers
Dan

Jim Marion said...

@Dan, unfortunately, I have not worked with it.

Mysha(Hazel) said...

This question is not regarding this but I have a small question. Is it possible to change lookup prompt image on a particular field(from glass image to my own custom image) . And also in grids/scrolls for next/previous and add and delete we can change the image but generally leave it to *use default image* , just want to know where do I change this default image so that its effective everywhere in the system. thanks in advance.

Jim Marion said...

@Pressure, using the inspector tool in Firebug, you can hover over the magnifying glass to see the src attribute. From there you will see that the image name is PT_PROMPT_LOOKUP_dddd.gif. The _dddd suffix is a number to "trick" browser caching. Based on the name (and the fact that tools stores most images in app designer image definitions), you can assume that the PT_PROMPT_LOOKUP image is an image definition in app designer. You can prove it by looking for the image in app designer. I did this and found that image in app designer. Now that you know this, you can change the image on a global basis by updating the image definition in app designer. After making this change, I suggest you clear your web server cache and restart your web server.

Unknown said...

Jim,
Very new to javascript and jquery and trying to implement changing the advanced search page operator to 'contains'. Tried jQuery without too much success so did the following using javascript
changed expandSearchCriteria in PT_COMMON
objnk = document.getElementById("CG_JPM_SCH_VW_SCHOOL_CODE$op");
if (objnk)
{
for(i=0;i<objnk.length;i++)
{
if(objnk.options[i].value == "8")
{
objnk.selectedIndex = i;
}
}
}
This function not only gets called when we click on prompt button but it gets called when we change a search criteria or when we click on any button on the lookup page (Lookup, clear etc). So it is all good if we don't change this default criteria. But if you change the default criteria from ‘contains’ to something else (say begins with) once you are in the lookup page and click on Lookup button, it will again reset the search criteria back to ‘contains’. I need to find out a way to fire this code only once when I click on prompt button or I need to find out some other function which gets executed only when a prompt button is clicked.
Thanks in advance
Dennis

Jim Marion said...

@Dennis,

What is your trigger for that code? Is this just bare JavaScript or is it wrapped in a JavaScript event handler function? If it is bare, that is probably fine.

It sounds like your JavaScript reloads when any value is changed (drop-down). A reload can happen from the Ajax requests triggered by changes on the search page.

Since the search page only loads once, and then uses Ajax to update values, etc, you can wrap your JavaScript in a conditional so it only fires once:

if (objnk) {
if (!window.xxxRunOnceDone) {
// code goes here
} else {
window.xxxRunOnceDone = true;
}
}

Unknown said...

Jim,

Thanks for your reply , much appreciated and thanks for a great book. The trigger for the code is just the load of the search page (not wrapped in a JavaScript event handler). Again very new to all of this but really enjoy learning new technologies. Quick question: when you say (!window.xxxRunOnceDone) does this variable need to be defined as global ?

Thanks is advanced,
Dennis

Unknown said...

Jim,

I believe I have it working using you solution but I would rather have done this using jQuery. I have been able to load jQuery into the system using the methods described in your book (message catelog) but was not able to get this working. Is this a quick change to the existing code below
objnk = document.getElementById("CG_JPM_SCH_VW_TR_DESCR5$op");
if (objnk)
if (!globalChangeSeearch) {
{
for(i=0;i<objnk.length;i++)
{
if(objnk.options[i].value == "8")
{
objnk.selectedIndex = i;
}
}
}
} else { globalChangeSeearch = true;
}
I defined the globalChangeSeearch as a global variable and put code into PT_COMMON.

Again, thanks for you time,
Dennis

Jim Marion said...

@Dennis, I'm glad you like the book. I'm also glad to hear you are trying something new. I'm always happy to hear about customers working outside the PeopleSoft box. It has nothing to do with PeopleSoft and its capabilities, but rather about learning and about satisfying business requirements in the most effective manner.

You asked, "Does this variable need to be defined as global?" The answer is yes, and that is what I did (although it is not obvious). There is only one global object/scope/namespace, and that is window. Everything is a property of "window." If you don't control when and where your code will run, the best way to declare a global variable is just to attach it to window. In our case, the test window.xxx will return undefined because it hasn't been defined yet. This is good. That is exactly what you wanted to know. The other use is where we set a value: window.xxx = true. Now the "property" xxx is defined and has a value.

At the global scope, a variable needs a var statement. But you don't want two var statements. If you don't control the runtime context or how often the code is run, you really can't control the var statement. Since all global variables are really just properties of "window" you can just reference it as a property of window and the JavaScript runtime is happy. Want to test this? Open a JavaScript console (Chrome or Firefox, hit the F12 key. IE has IE Developer Toolbar). Type this in the console:

window.xxx

You should see "undefined"

Type:

var xxx = true;

Now type window.xxx

You should see the value: true.

In regards to using jQuery for what you are doing... I would do it the way you are doing it, except I would also check for the existence of a #ICSearch: document.getElementById("#ICSearch"). This will tell you whether or not you are on a search page. With ID selectors, jQuery is just calling document.getElementById anyway. Your way is faster.

One other thing: global namespace pollution. Yes, add a global variable or two, but be careful not to create too many. I usually add an object to the global namespace (property of window), and then attach all my variables as properties of that object. If you are interested, and everyone writing JavaScript should be interested, then perform a Google search for JavaScript Global Namespace Pollution.

By the way, I'm glad you have a working solution. Great stuff!

Unknown said...

Jim,

Thanks for all your time and I really don't know how you find the time but I am glad you do !!!!!!!!!!

Dennis

Unknown said...

Jim,

OK I give up. We have a function that paints a rectangle around grids (any object) based on an HTML object on the page. Works well most of the time but anytime I try to use the function and a sub-page is on the page where the HTML object is, the entire page gets distorted !! I have tried to use Firebug to determine where / when the HTML gets injected into the page and what is causing the distorted page. From you experience is there any easy way to determine what is causing the page distortion or how to go about determining what is causing it. This has been a problem for some time.

Thanks in advance,
Dennis
I did not know if I should have started a new post for this question.

Jim Marion said...

@Dennis, I don't have an easy answer for you. Like you, I would use Firebug to try to figure it out. You can try posting your question on the PeopleSoft OTN General Discussion forum.

Unknown said...

Jim,

If I wanted to do this for a lookup prompt, and since no peoplecode executes for that, I could create a push button and subpage that resembles the prompt and search page then execute your java code from there?

Thanks,
Alan

Jim Marion said...

@Alan, the JavaScript above executes on the client, so you would want to add an HTML Area with the JavaScript. You will need to modify it a little bit to identify the prompt. Clicking the prompt button executes JavaScript. You just need to execute the same JavaScript. Use your browser's inspector tool to identify the JavaScript.