Tutorial: Customizing FootPrints Service Core using JavaScript, Part 1

0 Flares 0 Flares ×

Goal

Learn best practices for customizing the FootPrints ticket page with JavaScript.

Applicability

FootPrints Service Core version 11 (any sub-release)

Prerequisites

  • Access to Form Designer in one or more workspaces
  • Access to file system on FootPrints application server
  • 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)

One of the things I love most about FootPrints is how readily customizable it is. I’m not talking about the fact that administrators are able to tweak so many options through the web interface, or that the source code is uncompiled plain text that can be edited by anyone who knows how to program Perl, but the fact that the makers of FootPrints have always kept open the means to inject JavaScript onto the incident form. It’s been very rare that a client will ask if something is possible in FootPrints and my answer is not “Yes, with some JavaScript.”

In the first part of this tutorial, we will see how to customize the incident form using JavaScript added through the Form Designer. The business case we will use as an example is one where the organization wants a particular field to appear above the Assignees picker in the Assignees and Notifications section of the incident form. The Form Designer does not allow fields to be placed in this section, establishing the case for a JavaScript customization. While this particular use case may not be of any interest to your organization, by following this tutorial, you will receive advice on best practices and find code that can be reused in your own customizations.

Let’s say your organization wants to assign incidents automatically to different groups based on categorization fields. Your categorization is based on two dropdown fields, Category and Sub Category, and so the standard Auto Assignment feature, which can only be based on a single field, cannot be used to determine assignees. Instead, the more flexible AutoField feature must be used. The potential problem with AutoField is that it runs each and every time an incident is updated and we don’t want the assignees to change if they’ve been set by other means throughout the incident’s lifecycle. We’re going to let the editing agent decide on a case-by-case basis whether the assignees should be subjected to the AutoField rules by introducing a field called Let Categories Determine Assignees. Each AutoField rule will be set up to run only if this field is set to “Yes”, like so:

AutoField rules for setting assignees

AutoField rules for setting assignees. Each rule mandates that “Let Categories Determine Assignees” be set to “Yes”.

Our JavaScript is going to accomplish the following things:

Rename the first option in the Let Categories Determine Assignees field from the built-in value of “No Selection” to “No”.
This makes the field a little more user-friendly. If the user wants to override the auto-assignment behavior, they won’t be scratching their heads wondering what the effect of “No Selection” might be.

Default Let Categories Determine Assignees to “No”.
Prevents auto-assignment from occurring each and every time the incident is edited.

Set Let Categories Determine Assignees to “Yes” when categorization fields change.
Necessary to have the auto-assignment rules run after the incident is created or updated.

Move the Let Categories Determine Assignees field to the assignees area.
Intended to improve usability. If the user wants to manually set assignees, they’ll open the assignees section of the form where they’ll be more likely to notice this field and understand that they have to change it from “Yes” to “No”.

The first step in this tutorial is to create a new text file called incident_management.js inside the C:\FootPrintsServiceCore\html\ directory (throughout this tutorial, we’ll be assuming FootPrints is installed to C:\FootPrintsServiceCore.) Paste the following code into the file and save the file.

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// Moves the "Let Categories Determine Assignees" field 
// to the assignees and notifications section.
// Defaults this field to "No.
// Sets this field to "Yes" when the category fields are selected
function assigneeCustomizations() {
 
    var categoryFields = ["Category", "Sub Category"];
 
    // Only run this customization under certain conditions
    if (isDetailsMode || isCustomerMode) {
        return;
    }
 
    // field to be moved
    var fName = "Let Categories Determine Assignees";    
    var f = document.getElementById(fName.fix());
    // verify that the field to be moved exist on the form as a dropdown
    if (f === null || f.type != 'select-one') {
        return;
    }
 
    // move the field and label above the assignee picker.
    // verify that the assignees picker is displayed.
    if (window.assigneePicker !== undefined) {
        var originalDivHoldingFieldAndLabel = Ext.get(f).up('div.cell'); 
 
        var tbody = Ext.get(assigneePicker.box1).up('tbody');
 
        var td = new Ext.Element(document.createElement('td'));
        td.set({
            colSpan: tbody.child('tr').query('td').length
        });
 
        td.appendChild(originalDivHoldingFieldAndLabel);
        var tr = new Ext.Element(document.createElement('tr'));
        tr.appendChild(td);
 
        tbody.insertFirst(tr);
    }
 
    // Set listeners on the category fields so that when they change,
    // the field to set assignees based on categorization 
    // will switch to 'Yes'
    for (var i = 0; i < categoryFields.length; i++) {  
        var c = document.getElementById(categoryFields[i].fix());
        if (c !== null) {
            Ext.EventManager.on(
                c,
                'change',
                function() {
                    f.selectedIndex = 1;
                }
            );
        }
    }
 
    // Rename "No Selection" to "No"
    f.options[0].text = 'No';
 
    // Always default or reset the field to not override assignees
    // based on categorization
    f.selectedIndex = 0;
}
 
Ext.onReady(function() {
    afterFormInitialized([setGlobals, assigneeCustomizations]);
});
 
// Supporting code follows. This can be included in
// other custom javascript files with no change.
var isDetailsMode;
var isEditMode;
var isCreateMode;
var isAgentMode;
var isCustomerMode;
 
function setGlobals() { 
    if (document.regform === undefined) {
        isDetailsMode = true;
    }
    else {
        if (document.regform.MAJOR_MODE !== undefined) {
            var mode = document.regform.MAJOR_MODE.value;
            if (mode == "CREATE" || mode == "PREVIEW") {
                isCreateMode = true;
            }
            else if (mode == "EDIT") {
                isEditMode = true;
            }
        }
    }
 
    // determine if customer or agent user.
    // MRP parameter begins with 0 if customer.
    for (var i = 0; i < document.forms.length; i++) {
        var f = document.forms[i];
        if (f.elements.MRP !== undefined) {
            var mrp = f.elements.MRP.value;
            if (mrp.charAt(0) == '0') {
                isCustomerMode = true;
            }
            else {
                isAgentMode = true;
            }
            break;
        }
    }
}
 
function afterFormInitialized(callbacks) {
    Ext.TaskMgr.start({
        run:  function() {
            var el = Ext.get('wait_c') || Ext.get('waitP_c');
            if (el === null) {
                // the "Loading..." mask hasn't been created yet
            }
            else {
                if (!el.isVisible(true)) {
                    Ext.each(callbacks, function(i) { i.call()});
                    return false;
                }
            }
        },
        interval: 50
    });   
}

 

Next, we’ll use the Form Designer to add a Custom HTML element to the form that references the file we just created.

Adding a Custom HTML element to the incident form

The “Custom HTML” form element is listed under the “Special Features” section of the Form Designer palette.

Form Designer palette with Custom HTML highlighted

Form Designer palette with Custom HTML highlighted

Because of how the code was written [I’ll come back to this], we are free to place the Custom HTML element anywhere on the form. I prefer to drop it into the area above the tabbed sections so that it will always be prominent when viewing the Form Designer. You should be aware that while the Custom HTML element we are going to add is not going to contain any visible content, it will still occupy a space on the form’s layout grid. If you put it on a row by itself, users will see a blank row on the form. For that reason, I recommend you put it in an unoccupied space on a partially filled row.

Placement of Custom HTML element above tabbed sections

Placement of Custom HTML element above tabbed sections

Once the element has been placed on the form, click the pencil icon to edit its properties.

Populating the Custom HTML element's body

Populating the Custom HTML element’s body

In this example, set the element’s name to “Incident Management Customizations”.

Next, disable Rich Text Mode.

Important: you must disable Rich Text Mode on a Custom HTML element that holds raw HTML or JavaScript

and enter the following content:

<script src="/footprints/incident_management.js"></script>

Set the element’s Permissions and Access properties so that it is visible to both customers and agents. You should always do this, even if you have a customization that should not run, let’s say, on the customer version of the incident form.

Next, add a dropdown field to the form called Allow Categories to Determine Assignees. This field should have only 1 choice, “Yes”, and should be optional to agents/hidden to customers.

That’s it for the step-by-step portion of this tutorial. What remains is to create and populate the Category and Sub Category fields, and to create some AutoField rules that set the assignees field based on combinations of these two fields plus the Allow Categories to Determine Assignees (having a value of “Yes” in each rule). I assume the reader knows how to perform these basic administrative tasks already. I will now move on to highlighting a number of techniques used in this example that I consider to be best practices.

Best Practices

B.P. #1: Delay your code from running until the form has finished initializing

If you have ever experimented with adding JavaScript using the Form Designer before, you have probably had to ask yourself the question “Where is the best place [on the form] to add my code?” Placed too near the top and it might try to address a field before it has been rendered. Adding a fixed amount of delay before the code runs might work most of the time but inexplicably fail other times. The reason it is a hard question to answer is that browsers use multiple, parallel threads to render the page, fetch resources over the network, and execute JavaScript, and these threads do not execute in any predictable order.

The technique I advocate does not make a loose prediction and leave things to chance. It delays the code from running for as long as needed until it receives a signal that indicates with 100% certainty that the form has finished loading. The code below demonstrates how to call the myCustomCode1 and myCustomCode2 functions after the form has finished initializing.

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
// Your customizations go inside functions whose names are
// passed into afterFormInitialized.
function myCustomCode1() {
     alert("The form is ready.");
}
function myCustomCode2() {
     alert("I also think the form is ready.");
}
Ext.onReady(function() {
    afterFormInitialized([myCustomCode1, myCustomCode2]);
});
 
function afterFormInitialized(callbacks) {
    Ext.TaskMgr.start({
        run:  function() {
            var el = Ext.get('wait_c') || Ext.get('waitP_c');
            if (el === null) {
                // the "Loading..." mask hasn't been created yet
            }
            else {
                if (!el.isVisible(true)) {
                    Ext.each(callbacks, function(i) { i.call()});
                    return false;
                }
            }
        },
        interval: 50
    });   
}

You can see this technique in practice in our example code: the two functions, setGlobals and assigneeCustomizations, are passed into afterFormInitialized, delaying their execution until the appropriate time.

B.P. #2: Place your JavaScript in a Custom HTML form element

Back in the days before FootPrints Service Core had its Form Designer feature, custom JavaScript had to be injected onto the ticket form using what were called “field headers”. Field headers were always tied to a specific field on the form and they were always printed (even if they contained only invisible JavaScript) above the field they were tied to. The equivalent of a field header in version 11 is the “Help Text and Instructions” property of a field. There are a couple of reasons I do not recommend using a field’s “Help Text and Instructions” property to hold your JavaScript. The first is that it makes the JavaScript hard to locate: you cannot see at a glance whether a field’s Help Text and Instructions holds JavaScript, actual instructions for the user, or both. You might be thinking that keeping track of where JavaScript is located can be made into a straightforward task by always putting the JavaScript in the instructions property of the particular field it affects, but what you have to remember is that not all customizations target a specific field. The other reason I do not use any field’s instructions property is that it forces you to use extra care when changing a field’s visibility to particular user roles or when deleting a field, as the injection of the JavaScript will be affected by such changes.

With the introduction of the Form Designer in version 11, you can now add Custom HTML elements to the form. By using one of these to hold your JavaScript, you will avoid the two problems mentioned earlier: you can give the HTML element a name that makes its purpose (holding JavaScript) self-evident, allowing you to quickly locate where you JavaScript resides without drilling down into multiple fields; custom HTML elements are not tied to any fields, allowing you to manage fields and custom JavaScript independently.

B.P. #3: Load an external script file

When we created the Custom HTML element, we did not fill it with any actual JavaScript; instead it only contains a <script> tag that loads an external file containing the JavaScript.   The main reason I advocate this practice is that it avoids an issue where + characters can disappear from JavaScript defined directly in the Form Designer. This can be a serious problem given the role + plays in JavaScript syntax:

Imagine your code includes the for loop:

1
2
3
for (var i = 0; i &lt; 5; i++) {
	x = x * x;
}

One day, you make a change to some unrelated part of the form and publish. Suddenly, no one can create any incidents–the browser just hangs. It turns out that the + characters in the JavaScript disappeared, leaving your code looking like this:

1
2
3
for (var i = 0; i &lt; 5; i) {
	x = x * x;
}

Your incident form now has an infinite loop because the loop’s counter variable, i, never changes!

Keeping the JavaScript in an external file also facilitates easier maintenance of the code. You can write your code in your favorite text editor instead of having to use the Form Designer interface. You can also keep as many different versions and backups of the code as you need just by copying the file to different names.

B.P. #4: Keep all of your JavaScript for a given workspace together

You might be wondering why I chose to name the Custom HTML element “Incident Management Customizations”, as that name doesn’t give any indication as to what the code actually does. The file name referenced by this Custom HTML is given a similarly generic name, “incident_management.js”. This is intentional. I recommend defining all customizations for a given workspace in a single file for the following reasons:

  • Fewer files that have to be transferred to the browser
  • More control over the order in which the customizations run
  • Less chance of naming collisions resulting from a global function or variable having been defined more than once across multiple files

There are a couple of caveats to be aware of, however. First, if you are editing the JavaScript file on a live system, any syntax error you might introduce can break all of the other JavaScript customizations defined in that file. Therefore, you really must test your changes on a development server, or by making your changes in a copy of the JavaScript file, editing your Custom HTML element to load that new file, and then validating the code using the Form Designer’s Preview mode. Once the code has been tested, you can discard the draft of the form and replace the .js file currently referenced by the Custom HTML element. Second, because you will not be defining your customizations in the Help Text and Instructions property of the specific fields they interact with, you will not be able to control the inclusion of the JavaScript based on whether the field it is associated with appears on the incident form using the options shown here

Options for conditionally displaying a field's Help Text and Instructions

Options for conditionally displaying a field’s Help Text and Instructions

That’s okay, though, because you can instead take a JavaScript-only approach to conditionally having portions of your code run or not, and this will actually give you more control than you could get by using the Form Designer’s options for conditionally displaying the Help Text and Instructions content.

B.P. #5: Take a JavaScript-only approach to running your code under certain conditions

So far, I’ve advocated using a single Custom HTML element to inject all of your JavaScript onto the form, and allowing the entirety of this JavaScript to appear on the form unconditionally, without any regard for whether certain fields referenced by the JavaScript are actually present for the given user on the given incident at the given time. You might then be wondering how we are going to have the code run only under certain targeted scenarios such as whether the form being displayed is for creating or editing an incident, whether that form is being accessed by a customer- or agent-level user, whether that user has rights to see a certain field, or even whether the time is currently after midnight on the 4th Friday of the month. The answer is to have the code itself check that all the proper conditions for running a certain portion of custom code are met.

You can see this technique in our example code where the function setGlobals defines a number of flags such as isCustomerMode and isDetailsMode that are later checked throughout the code. For instance, our example customization only runs on the create and edit forms opened by an agent:

9
10
11
12
    // Only run this customization under certain conditions
    if (isDetailsMode || isCustomerMode) {
        return;
    }

If another customization were later added to the JavaScript file, that customization could also make use of these flag variables. The setGlobals function is suitable for reuse in your own customizations.

The Form Designer lets you show or hide the Custom HTML element that loads your JavaScript based on user role. If the element is to be shown, then all of the JavaScript will be loaded; it’s an all-or-nothing deal. This JavaScript-only technique, by contrast, produces JavaScript that assumes it will always be present on the incident form and takes responsibility for controlling which parts execute. This allows for much greater flexibility than can be achieved through the Form Designer options, as you can have single lines of code execute or not based on the current user type, or any other condition you care to check.

B.P. #6: Check form elements (fields) before attempting to access them

Given that the JavaScript is going to be present on all forms, as just mentioned, it follows that your code should always check for the existence of form elements before attempting to reference them. In our example, no assumption was made that the assignees field is displayed. Perhaps the user accessing the form is an agent whose role does not allow specifying assignees. Therefore, the entire block of code that tries to reference the assignees picker is surrounded by the following conditional statement:

if (window.assigneePicker !== undefined) { 
    [do stuff to assigneesPicker]
}

Similarly, the code makes no assumption that any of the categorization fields are present:

var c = document.getElementById(categoryFields[i].fix());
if (c !== null) { 
    [do stuff to c]
}

Whenever I’m not sure whether my code should be comparing against null or undefined,  I open up my browser’s developer tools and test some on-the-fly JavaScript:

6-undefined-vs-null

B.P. #7: Reference fields by their internal names (without needing to know how they’re encoded)

In FootPrints, a field’s internal name is formed by encoding all non-alphanumeric characters in the visible name into a 3-character sequence. Sub Category has the internal name of Sub__bCategory, Phone # is Phone__3, and so on.  If you need to access the form input for a field (to get or set its value), you will need to know this internal name because that’s how the form inputs are named. However, you don’t have to know the actual internal name and all of its special character encodings. Your code can make use of the fix and unfix functions that are available on all String objects. This is what we’re doing in our example:

    var fName = "Let Categories Determine Assignees";    
    var f = document.getElementById(fName.fix());

It reads a lot better than

    var fName = "Let__bCategories__bDetermine__bAssignees";

B.P. #8: Don’t change field names

Changing a field’s name through the Form Designer will change the name of the corresponding column in the database and the name of the HTML input on the incident form. This could adversely affect reporting tools that read the database, clients of FootPrints’ web services, and JavaScript customizations that interact with the form inputs. If you do need to change a field’s name, consider using the “Language of Workspace and Address Book Fields” feature to do so, rather than changing the name through the Form Designer. This feature merely changes the displayed name of the field in the graphical interface.

Changing a field's label but not its internal name

Changing a field’s label but not its internal name

Keep in mind that the field’s internal name and therefore the name of the form input for the field will always be the encoded version of its original name; i.e., the name of the field before it has been re-labeled for a particular language.

Until next time

If you’ve taken away anything from this tutorial, I hope it’s what I put forward as a re-usable basis for any incident form customization you might ever need to write:

  1. Create a single JavaScript file for the workspace
  2. Add the afterFormInitialized function to the file
  3. Add the setGlobals function to the file
  4. Call afterFormInitialized as follows
    Ext.onReady(function() {
        afterFormInitialized([setGlobals]);
    });
  5. Add a Custom HTML element to your form to load the JavaScript file

Once you have that in place, just add a function to the file that performs the real work of your customization, and pass this new function into the afterFormInitialized call

Ext.onReady(function() {
    afterFormInitialized([setGlobals, myCustomization]);
});

The next tutorial will cover a method for injecting JavaScript on any page in FootPrints, not just the incident form. I discovered this method during the last year and I am really excited to reveal it. Until then, I hope this tutorial has prepared you to start writing your own JavaScript customizations for FootPrints.

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.

 

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.

0 Flares Twitter 0 Facebook 0 LinkedIn 0 0 Flares ×

Leave a Reply

Show Buttons
Hide Buttons