Tutorial: Customizing FootPrints Service Core using JavaScript, Part 2

3 Flares 3 Flares ×

Goal

Learn how to inject custom JavaScript onto any page in FootPrints.

Applicability

FootPrints Service Core version 11 (any sub-release) running on Microsoft Windows Server 2003/2008/2012.

Prerequisites

  • Access to file system on FootPrints application server
  • Access to IIS on FootPrints application server
  • URL Rewrite extension for IIS
  • Ability to write JavaScript
  • (Suggested) Familiarity with the Sencha Ext JS framework (version 3.4, specifically)
  • (Suggested) Familiarity with the HTML Document Object Model (DOM)

It’s well known that the incident, problem, change request, and other forms can be customized in FootPrints Service Core with injected JavaScript. Just look on the BMC Communities forums, or see my previous post in this series, which fully documents the process*. What’s new and exciting for me right now is a technique I recently discovered that allows you to customize ANY page in FootPrints:

  • Want to add instructions to the login form so that users will know they should be logging in with their Active Directory credentials?
  • Want to add a button to the chat feature that creates a new issue with a transcript of the conversation?
  • Want to make saved searches available to customers?
  • Want to have a single saved search that checks for issues assigned to or submitted by the currently logged-in agent, determined at runtime?

All of these things are possible, and they’re really just the tip of the iceberg. This technique opens the door for virtually any custom behavior; your willingness and ability to write the JavaScript for your desired behavior is really the only possible limiting factor.

Back Story

Earlier this year, a client asked if it would be possible to create a saved search that any agent could use to find their assignments in a particular category. In other words, they wanted a single, shared search that would apply a condition at runtime based on whichever agent was running the search. FootPrints comes with a small number of searches that behave this way, like “My Assignments” and “My Active Approvals”, but  it doesn’t let administrators create their own. The workaround is to tell each and every agent to create their own version of the search that has their own user id hardcoded into one of the conditions. That’s hardly practical. (Thankfully, version 12 introduces this capability.)

Changing the source code was not an option for this project, so I started thinking about using JavaScript. The challenge was finding a reliable way to get this JavaScript onto the agents’ homepage. Each user could run a browser extension such as TamperMonkey that injects JavaScript on pages matching certain URL patterns. I knew there was no way they would go for that. Or, I could deploy a dashboard widget to every agent’s homepage that contained the JavaScript. Hmmm…but what happens if the agent deletes that widget? I happened to know that there was one JavaScript file that gets included on every page. I could modify it, I thought, but then there would be the chance that the client wouldn’t know to back it up before running an upgrade. The solution I came up with was to hijack all requests for that JavaScript file, sending back a file of my own instead. The key to making this happen was IIS’s URL Rewrite module.

The Solution

Just about every webpage in FootPrints includes a file called ResizeDocument.js. I created a URL Rewrite rule that looks for requests for this file and redirects them to my own script file. My own script file would carry out whatever customizations I wanted to implement, and, acting responsibly, tell the page to make another request for the ResizeDocument.js it originally requested. In order to prevent the second request for ResizeDocument.js from also being redirected, I wrote the second request with a slight change in capitizalization such that it does not match my URL Rewrite rule.

Let’s set up that new JavaScript file first. Call this file GlobalJavaScript.js and place it in the C:\FootPrintsServiceCore\html directory (adjust drive letter and/or FootPrints root directory as needed.) Below is how this file should be defined initially:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
window.addEventListener("load", function() {
    // DEFINE YOUR CUSTOM BEHAVIOR HERE
 
    // END CUSTOM BEHAVIOR
});
 
{
// DO NOT CHANGE THE REMAINDER OF THIS FILE
    var head = document.getElementsByTagName('head')[0];
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = '/tmp/javascript/ResizeDocumenT.js';
    head.appendChild(script);
    function AddWindowResizeEvent() {
        setTimeout(function() { 
            AddWindowResizeEvent(arguments)
        }, 100);
    }
}

Notice the last couple of lines are telling the page to request a JavaScript file that has a random capital ‘T’ in its name. That capital ‘T’ is what allows this second request for that file to bypass the URL Rewrite rule, which we’re going to setup as case-sensitive.

Next, open IIS and open the FootPrints web site, which is listed as “Default Web Site” in most cases. Click on the virtual directory named “tmp” and you should see the URL Rewrite module. As a side note, if you don’t have this module installed, it means you can’t possibly be running FootPrints in FastCGI mode. I strongly recommend you put reading this tutorial on hold, open up the FootPrints documentation and search for “FastCGI”.

nav to correct virtual directory

IIS setup: Opening the URL Rewrite feature for the correct virtual directory

Double-click the URL Rewrite module. Click “Add Rule(s)…” from the menu of Actions on the right. On the next window, double-click the first option to create a blank, inbound rule:

blank rule

IIS setup: Creating a blank inbound rule.

The next window has a lot of options.

  • The first two dropdowns should read “Matches the Pattern” and “Regular Expressions”.
  • For Pattern, type in ^ResizeDocument\.js
  • Make sure Ignore case is UNchecked.
  • Jump down to Action type and select “Rewrite”
  • For Rewrite URL, type in /footprints/GlobalJavaScript.js
  • Check the option to Append query string
  • Log rewritten URL is probably better left unchecked, but it won’t have any effect on how the rule works either way. It only affects the IIS  request logs.

The complete rule configuration should look like this:

new rule

IIS setup: settings for the new inbound rule

After saving the rule, if you go to the C:\FootPrintsServiceCore\html\tmp\javascript directory, the web.config file should look like this:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="Global JavaScript Injection">
                    <match url="^ResizeDocument\.js" ignoreCase="false" />
                    <action type="Rewrite" url="/footprints/GlobalJavaScript.js" logRewrittenUrl="true" />
                </rule>
            </rules>
        </rewrite>
    </system.webServer>
</configuration>

Of course, you could also just create a web.config file containing the above text and drop it into the C:\FootPrintsServiceCore\html\tmp\javascript\ directory rather than setting up the rule through IIS.

Getting back to the GlobalJavaScript.js file we created, you’ll see there is a space at the top where you are supposed to put your custom code. Do not feel like you are rushed to edit the file with your code. As it stands, the file will cause no custom behavior or disruption. I recommend that you take your time developing the code, pasting it from a text editor into your browser’s developer tools, or using a browser extension that runs custom JavaScript such as GreaseMonkey or TamperMonkey.

The window.addEventListener("load"...) construct is intended to delay your code from running until the web page has fully loaded. If your code requires more accurate timing than this, there are ways to do that, but I won’t get into them here.

Here are some tips to help you get started writing your code:

  • Have your code check the current URL to determine what page is being shown. For example,
    if (window.location.pathname.search("/MRcgi/MRhomepage.pl") > -1)

    tells you that the homepage is being shown.

  • Certain Perl scripts have more than one function. For example, MRsearch_page.pl is used for creating reports, advanced searches, and escalations. You will need to look at more than just window.location.pathname to determine which specific case is running. You could look at window.location.search, which shows all of the parameters in the URL after the “?”. For example, the create/edit escalation page can be detected with this condition:
    if (window.location.search.search("ESCALATIONPAGE=1") > -1)
  • Not all FootPrints URLs have a set of parameters after a “?” that JavaScript can inspect. In these cases, window.location.search will be an empty string. If you need to determine what page is running, you’ll have to look for some HTML element or combination of elements that identifies the page. For instance,
    if (window.location.search == "" 
        && document.faqs !=== undefined && document.faqs.nodeName == "FORM" 
        && document.faqs.SOLSEARCH !== undefined)

    will tell you that the customer’s simple knowledge base search page is being shown. It’s checking that there’s a <form> element named “faqs” on the page that has an input named “SOLSEARCH“. I arrived at this condition by going to that page and using my browser’s developer tools to inspect certain HTML elements that I thought would be indicative of the scenario I wanted my code to identify.

  • If you want to use JavaScript to make an HTTP request to another FootPrints page, let’s say in a popup window, at a minimum, the request must pass the logged-in user’s id, session key, and current workspace id. These parameters are USER, MRP, and PROJECTID, respectively. Requests made for customer-level users will also need to have a CUSTM parameter. If these parameters are not present in the current page’s URL, you can collect them from one of the <form> elements (the navigation toolbar contains several) on the page:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    
    // Gather USER, MRP, PROJECTID, and possibly CUSTM, parameters from page.
    // Check all forms on document until one can supply values for all needed parameters.
    var capturedParams = { };
     
    for (var i = 0; i < document.forms.length; i++) {
        var form = document.forms[i];
     
        var neededParams = ['USER', 'MRP', 'PROJECTID'];
        var paramsFoundThisForm = 0;
     
        // look for each needed parameter:
        for (var j = 0; j < neededParams.length; j++) {
            var p = neededParams[j];
            if (form.elements[p] !== undefined && form.elements[p].value != "") {
                capturedParams[p] = form.elements[p].value;
                paramsFoundThisForm++;
     
                // MRP beginning with a 0 signals this is a customer user.
                // The CUSTM parameter will also need to be passed in the request.
                if (p == 'MRP' && form.elements[p].value.match(/^0/)) {
                    neededParams.push('CUSTM');
                }
            }                
        }
     
        // Check if this form supplied all needed parameters.
        if (paramsFoundThisForm == neededParams.length) {
             // Launch a new ticket window with parameters in URL
             var baseUrl = "/MRcgi/MRTicketPage.pl";
     
             var keyValuePairs = [];
             capturedParams.MAJOR_MODE = "CREATE";
             for (var p in capturedParams) {
                 if (capturedParams.hasOwnProperty(p)) {
                     keyValuePairs.push(p + "=" + encodeURIComponent(capturedParams[p]));
                 }
             }
     
             window.open(baseUrl + "?" + keyValuePairs.join("&"));
     
             break;
         }
    }

 

Caveats

  1. IIS cannot be running in a case-sensitive mode where it considers xyz.html to be a different resource than XYZ.html. IIS is case-insensitive by default, and I couldn’t even tell you how to change that setting, but I figured I should stipulate that anyway.
  2. You might run across a page that does not include ResizeDocument.js. Our technique of rewriting requests for that particular file won’t work on those pages. See if there’s another JavaScript file being loaded that you can construct another rewrite rule for to handle such cases.
  3. I haven’t personally run into this problem, but in theory, browser cache might interfere with the rewrite mechanism after you first create the rewrite rule. You (and other FootPrints users) might need to clear your browser’s cache or force a no-cache refresh of FootPrints by holding the  CTRL key and clicking the refresh button.

Coming Soon

My next post will describe a complete use case for this JavaScript injection technique. Most likely, I will explain how to create shared searches that change according to which user runs them, as I mentioned in the back story for this post.

Need help?

For help writing a FootPrints Service Core customization, or to discuss a project that might require a custom solution, contact RightStar Technical Services.

 *The previous post in this tutorial series is not required reading to follow this tutorial, but it does contain some best practice suggestions for writing JavaScript to extend FootPrints in general.

Disclaimer

In no event shall RightStar Systems, Inc. or its employees be held responsible for loss or damages arising out of the use, inability to use, or the results of use of, the information and source code contained in this article. The information and materials contained or referred to in this article are for reference only. RightStar Systems, Inc. and its employees make no warranty of any kind regarding the information and source code in this article. In no event shall RightStar Systems, Inc. or its employees be obligated to provide additional support on the topic discussed in this article.

3 Flares Twitter 2 Facebook 1 LinkedIn 0 3 Flares ×

Leave a Reply

Show Buttons
Hide Buttons