Creating a Custom Watchlist using AngularJS - ben-vargas/servicenow-wiki GitHub Wiki

This article explains how to create a custom watchlist on a ServiceNow form using AngularJS. This allows you to implement a user-friendly interface for adding and removing watchers, synchronized with a hidden field for server-side processing. This approach provides a more flexible and interactive alternative to the standard watchlist functionality.

The Challenge

The standard ServiceNow watchlist functionality can be somewhat limited in terms of user interface customization and direct manipulation. Creating a custom watchlist allows for more control over the presentation and user experience, potentially improving user efficiency.

The Solution

This solution leverages AngularJS, a powerful JavaScript framework, to build an interactive watchlist component directly within a ServiceNow form. The watchlist displays added users, allows removal of watchers, and automatically synchronizes the watchlist state with a hidden ServiceNow form field.

Implementation Steps

This solution involves the following components:

  1. UI Macro: A UI Macro contains the HTML, CSS, and AngularJS code for the custom watchlist component.
  2. Client Script: A client script on the form handles the initial setup if necessary. (Usually not needed with this solution)
  3. Hidden Form Field: A hidden field on the form to store the watchlist data in a comma-separated list.

Code Snippets

Here's the UI Macro code:

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
    <script language="javascript" src="angular_min_js.jsdbx" />
    <script>

        var watchlist = angular.module('watchlist', []);
        watchlist.controller('watchlistController', ['$scope', '$log',
            function($scope, $log) {
                $scope.watchlist = [];

                // Add an entry to the watchlist array, used by the 'Add Watcher' button
                $scope.addWatcher = function() {

                    var watchListSnField = g_form.getValue('watchlistUser');

                    if (!watchListSnField) {
                        return;
                    }

                    if ($scope.watchlist.indexOf(watchListSnField) == -1) {
                        $scope.watchlist.push(watchListSnField);
                    }
                };

                // Remove an entry in the array; used by "x" button click
                $scope.removeWatcher = function(removeIdx) {
                    $scope.watchlist.splice(removeIdx, 1);
                };

                // Angular watch list to synchronize the watchlist var with SN watchlist_save variable/field
                $scope.$watchCollection('watchlist', function(newValue, oldValue) {
                    $log.debug('Changed!');
                    $log.debug('Old:' + oldValue);
                    $log.debug('New:' + newValue);
                    g_form.setValue('watchlist_save', newValue.toString());
                });
            }
        ]);
    </script>
    <div ng-app="watchlist">
        <div ng-controller="watchlistController">
            <div>
                <input type="hidden" class="form-control" ng-model="newWatchMember" />
                <button ng-click="addWatcher()" type="button" class="btn btn-primary" style="margin-bottom:10px;">Add Watcher</button>
            </div>
            <ul class="list-group" ng-model="watchlist">
                <li class="list-group-item" ng-repeat="watcher in watchlist" style="padding-left:6px;padding-bottom:2px;padding-top:2px;font-size:12px;padding-right:6px;">{{watcher}}<span ng-click="removeWatcher($index)" style="cursor:pointer;float:right;margin-right:16px;">x</span></li>
            </ul>
        </div>
    </div>
</j:jelly>

Explanation:

  1. Include AngularJS Library:

    • <script language="javascript" src="angular_min_js.jsdbx" /> includes the AngularJS library. Ensure that the angular_min_js.jsdbx is uploaded as a db_image record into the instance.
  2. AngularJS Module and Controller:

    • var watchlist = angular.module('watchlist', []); creates a new AngularJS module named 'watchlist.'
    • watchlist.controller('watchlistController', ['$scope', '$log', function($scope, $log) { ... }]); defines a controller named watchlistController.
      • $scope is used to access data and functions within the view.
      • $log is used for debugging messages.
  3. $scope.watchlist:

    • This array holds the current list of watchers.
  4. $scope.addWatcher() Function:

    • This function is called when the "Add Watcher" button is clicked.
    • var watchListSnField = g_form.getValue('watchlistUser'); gets the value from the variable name 'watchlistUser'.
    • if (!watchListSnField) { return; } Checks to ensure a user has been selected.
    • if ($scope.watchlist.indexOf(watchListSnField) == -1) { $scope.watchlist.push(watchListSnField); } adds the user to the array if it is not already present.
  5. $scope.removeWatcher() Function:

    • This function is called when the "x" button is clicked next to a user's name.
    • $scope.watchlist.splice(removeIdx, 1); removes the watcher from the array using its index.
  6. $scope.$watchCollection('watchlist', function(newValue, oldValue) { ... });

    • This watches for any changes to the watchlist array.
    • It logs changes to the console for debugging.
    • g_form.setValue('watchlist_save', newValue.toString()); updates the hidden field named 'watchlist_save' with a comma-separated list of the current watchers.
  7. HTML Structure:

    • <div ng-app="watchlist"> specifies the AngularJS app.
    • <div ng-controller="watchlistController"> links to the AngularJS controller.
    • <input type="hidden" class="form-control" ng-model="newWatchMember"/> creates a hidden form field for internal use by the angular controller.
    • <button ng-click="addWatcher()" ...>Add Watcher</button> creates the button that calls addWatcher() when clicked.
    • <ul class="list-group" ng-model="watchlist"> creates an unordered list to display the watchers and to manage the watchlist values.
    • <li class="list-group-item" ng-repeat="watcher in watchlist" ...>{{watcher}}<span ng-click="removeWatcher($index)" ...>x</span></li> displays the list of watchers and the "x" button that calls removeWatcher() when clicked.

How to Implement

  1. Create a UI Macro:
    • Navigate to System UI > UI Macros.
    • Create a new UI Macro with a name (e.g., custom_watchlist).
    • Copy the code from the "Code Snippets" section above into the UI Macro's XML field.
  2. Upload the Angular JS file:
    • Download angular.min.js from an online source.
    • Navigate to System UI > Images
    • Create a new image record named angular_min_js.jsdbx
    • Add the angular_min.js file as the image.
  3. Create Hidden Field:
    • Add a new hidden field (e.g., watchlist_save) to your form where you will be using this UI Macro. This field is used for saving the state of the watch list.
  4. Add UI Macro to the Form:
    • Navigate to your form, and go to Form Layout.
    • Drag and drop the UI Macro custom_watchlist to your form where you want the watchlist to appear. Ensure you are adding this as a new element, and not inside an existing field.
  5. Add a User Reference Field:
    • Add a reference field on the form where you want to select the user to add as a watcher and name this watchlistUser.
    • If you would like to display the watchlist on a different table, ensure this field is in that table.

Best Practices

  • AngularJS Library: Ensure that the AngularJS library is loaded correctly in your ServiceNow instance.
  • Hidden Field: The hidden field is used to persist watchlist data when the form is saved.
  • Form Placement: Add UI Macros as new form elements and not inside existing fields to avoid layout issues.
  • Error Handling: Consider adding error handling, especially in the $scope.addWatcher() function.
  • Performance: Keep the number of watchers in a reasonable amount to avoid performance issues.
  • UI Styling: Customize the CSS to match your branding.
  • Security: Ensure users have access to the tables that are being manipulated.

Security Considerations

  • Be aware of any security issues when passing user ids on the client side, make sure to validate this on the server side to ensure no unauthorized user access.
  • Ensure the hidden field is not accessible to users who should not have access to the data.

Conclusion

This custom watchlist provides a more user-friendly and interactive experience compared to the standard watchlist field. By using AngularJS, you can easily build a more dynamic and customizable UI component on a ServiceNow form. Remember to test your UI Macro thoroughly in a non-production environment before deploying to production. This solution promotes flexibility and provides a basis for implementing more complex interactive elements on your ServiceNow forms.

⚠️ **GitHub.com Fallback** ⚠️