In Service Portal, "sn-record-picker" directive is not displaying reference field.

Chandan Kumar7
Mega Contributor

Hi Community,

I am trying to use a reference type field as display-field property for sn-record-picker. However, i don't get any value displayed when i am using reference type variable. When i try with other type of the field, i am able to display the field value.

Please help me here in displaying the reference field for sn-record-picker.

7 REPLIES 7

You use in the demo custom filed of your custom table. I could try to help you if I would be able to reproduce the problem. I need to recreate the table on my developer instance or you can better try to do the same with a standard table like sys_user. It has a lot of different fields of different types. For example, I tried

<sn-record-picker id="resolutionCheckBox" field="c.resoutionItem" placeholder="All"
                  table="'sys_user'" display-field="'email'"
                  value-field="'sys_id'" search-fields="'name'" page-size="100"
                  on-change="c.getResolutionValue()"
                  default-query="'active=true'">

and everything worked without problems. Which fields I should use in sys_user to have the same effects (the same problem), which you have? If the problem exist only in your table and not on sys_user, please send enough information so I could reproduce the problem in my test environment.

 

Oleg
Mega Sage

I think, that I found the reason of your problem. The code of "sn-record-picker" directive contains bug, which is fixed only in New York version. You can use the same code of London too. You need just create new directive with the name snRecordPickerNY for example "sn-record-picker-n-y" instead of "sn-record-picker". You should don't forget to include snRecordPickerNY in the list of "Angular Providers" of your widget. The bug fix exist in the code of cleanLabel function (see below at the beginning of the code of snRecordPickerNY). It tests whether the value is object. In case of object one uses the value of display_value property. One have the case in all reference fields.

The code of directive can looks like

function snRecordPickerNY ($timeout, $http, urlTools, filterExpressionParser, escapeHtml, i18n) {
    "use strict";
    var cache = {};
    function cleanLabel(val) {
        if (typeof val == "object")
            return typeof val.display_value == "string" ? val.display_value.trim() : "";
        return typeof val == "string" ? val.trim() : val;
    }
    return {
        restrict: 'E',
        replace: true,
        scope: {
            field: '=',
            table: '=',
            defaultQuery: '=?',
            startswith: '=?',
            searchFields: '=?',
            valueField: '=?',
            displayField: '=?',
            displayFields: '=?',
            pageSize: '=?',
            onChange: '&',
            snDisabled: '=',
            multiple: '=?',
            options: '=?',
            placeholder: '@'
        },
        template: '<input type="text" ng-disabled="snDisabled" style="min-width: 150px;" name="{{field.name}}" ng-model="field.value"' + ' sn-atf-data-type="reference" sn-atf-data-type-params=\'{"reference" : "{{table}}", "reference_qual" : "{{defaultQuery}}",' + ' "valueField" : "{{valueField}}", "displayField" : "{{displayField}}"}\' sn-atf-class="builtin:ATF.BaseSNRecordPicker" sn-atf-component-value="{{field}}"/>',
        controller: function($scope) {
            if (!angular.isNumber($scope.pageSize))
                $scope.pageSize = 20;
            if (!angular.isDefined($scope.valueField))
                $scope.valueField = 'sys_id';
            this.filterResults = function(data, page) {
                return {
                    results: data.data.result,
                    more: (page * $scope.pageSize < parseInt(data.headers('x-total-count'), 10))
                };
            }
            ;
        },
        link: function(scope, element, attrs, ctrl) {
            var isExecuting = false;
            var select2Helpers = {
                formatSelection: function(item) {
                    return escapeHtml(getDisplayValue(item));
                },
                formatResult: function(item) {
                    var displayFields = getdisplayFields(item);
                    if (displayFields.length == 1)
                        return escapeHtml(cleanLabel(displayFields[0]));
                    if (displayFields.length > 1) {
                        var markup = escapeHtml(cleanLabel(displayFields[0]));
                        var width = 100 / (displayFields.length - 1);
                        markup += "<div>";
                        for (var i = 1; i < displayFields.length; i++)
                            markup += "<div style='width: " + width + "%;' class='select2-additional-display-field'>" + escapeHtml(cleanLabel(displayFields[i])) + "</div>";
                        markup += "</div>";
                        return markup;
                    }
                    return "";
                },
                search: function(queryParams) {
                    var url = '/api/now/table/' + scope.table + '?' + urlTools.encodeURIParameters(queryParams.data);
                    if (scope.options && scope.options.cache && cache[url])
                        return queryParams.success(cache[url]);
                    return $http.get(url).then(function(response) {
                        if (scope.options && scope.options.cache) {
                            cache[url] = response;
                        }
                        return queryParams.success(response)
                    });
                },
                initSelection: function(elem, callback) {
                    if (scope.field.displayValue) {
                        if (scope.multiple) {
                            var items = [], sel;
                            var values = scope.field.value.split(',');
                            var displayValues = scope.field.displayValue.split(',');
                            for (var i = 0; i < values.length; i++) {
                                sel = {};
                                sel[scope.valueField] = values[i];
                                sel[scope.displayField] = displayValues[i];
                                items.push(sel);
                            }
                            callback(items);
                        } else {
                            var sel = {};
                            sel[scope.valueField] = scope.field.value;
                            sel[scope.displayField] = scope.field.displayValue;
                            callback(sel);
                        }
                    } else
                        callback([]);
                }
            };
            var config = {
                width: '100%',
                containerCssClass: 'select2-reference ng-form-element',
                placeholder: scope.placeholder || '    ',
                formatSearching: '',
                allowClear: (scope.options && typeof scope.options.allowClear !== "undefined") ? scope.options.allowClear : true,
                id: function(item) {
                    return item[scope.valueField];
                },
                ajax: {
                    quietMillis: NOW.ac_wait_time,
                    data: function(filterText, page) {
                        var params = {
                            sysparm_offset: (scope.pageSize * (page - 1)),
                            sysparm_limit: scope.pageSize,
                            sysparm_query: buildQuery(filterText, scope.searchFields, scope.defaultQuery),
                            sysparm_display_value: true
                        };
                        return params;
                    },
                    results: function(data, page) {
                        return ctrl.filterResults(data, page, scope.pageSize);
                    },
                    transport: select2Helpers.search
                },
                formatSelection: select2Helpers.formatSelection,
                formatResult: select2Helpers.formatResult,
                formatResultCssClass: function() {
                    return '';
                },
                initSelection: select2Helpers.initSelection,
                multiple: scope.multiple
            };
            function buildQuery(filterText, searchFields, defaultQuery) {
                var queryParts = [];
                var operator = "CONTAINS";
                if (scope.startswith)
                    operator = "STARTSWITH";
                if (filterText.startsWith("*")) {
                    filterText = filterText.substring(1);
                    operator = "CONTAINS";
                }
                if (defaultQuery)
                    queryParts.push(defaultQuery);
                var filterExpression = filterExpressionParser.parse(filterText, operator);
                if (searchFields != null) {
                    var fields = searchFields.split(',');
                    if (filterExpression.filterText != '') {
                        var OR = "";
                        for (var i = 0; i < fields.length; i++) {
                            queryParts.push(OR + fields[i] + filterExpression.operator + filterExpression.filterText);
                            OR = "OR";
                        }
                    }
                    for (var i = 0; i < fields.length; i++)
                        queryParts.push('ORDERBY' + fields[i]);
                    queryParts.push('EQ');
                }
                return queryParts.join('^');
            }
            scope.field = scope.field || {};
            var initTimeout = null;
            var value = scope.field.value;
            var oldValue = scope.field.value;
            var $select;
            function init() {
                element.css("opacity", 0);
                $timeout.cancel(initTimeout);
                initTimeout = $timeout(function() {
                    i18n.getMessage('Searching...', function(searchingMsg) {
                        config.formatSearching = function() {
                            return searchingMsg;
                        }
                        ;
                    });
                    element.css("opacity", 1);
                    element.select2("destroy");
                    $select = element.select2(config);
                    $select.bind("change", onChanged);
                    $select.bind("select2-selecting", onSelecting);
                    $select.bind("select2-removing", onRemoving);
                    $select.bind("sn-atf-setvalue", onAtfSetValue);
                    scope.$emit('select2.ready', element);
                });
            }
            function onAtfSetValue(e) {
                var valueToSet = e.detail ? e.detail.newValue : null;
                if (valueToSet) {
                    oldValue = scope.field.value;
                    scope.field.value = valueToSet.value;
                    scope.field.displayValue = valueToSet.displayValue;
                    $select.select2('val', valueToSet.value).select2('close');
                    scope.$apply(function() {
                        callChange(oldValue, e);
                    });
                }
            }
            function onSelecting(e) {
                isExecuting = true;
                oldValue = scope.field.value;
                var selectedItem = e.choice;
                if (scope.multiple && selectedItem[scope.valueField] != '') {
                    var values = !scope.field.value ? [] : scope.field.value.split(',');
                    var displayValues = !scope.field.displayValue ? [] : scope.field.displayValue.split(',');
                    values.push(selectedItem[scope.valueField]);
                    displayValues.push(getDisplayValue(selectedItem));
                    scope.field.value = values.join(',');
                    scope.field.displayValue = displayValues.join(',');
                    e.preventDefault();
                    $select.select2('val', values).select2('close');
                    scope.$apply(function() {
                        callChange(oldValue, e);
                    });
                }
            }
            function onRemoving(e) {
                isExecuting = true;
                oldValue = scope.field.value;
                var removed = e.choice;
                if (scope.multiple) {
                    var values = scope.field.value.split(',');
                    var displayValues = scope.field.displayValue.split(',');
                    for (var i = values.length - 1; i >= 0; i--) {
                        if (removed[scope.valueField] == values[i]) {
                            values.splice(i, 1);
                            displayValues.splice(i, 1);
                            break;
                        }
                    }
                    scope.field.value = values.join(',');
                    scope.field.displayValue = displayValues.join(',');
                    e.preventDefault();
                    $select.select2('val', scope.field.value.split(','));
                    scope.$apply(function() {
                        callChange(oldValue, e);
                    });
                }
            }
            function callChange(oldValue, e) {
                var f = scope.field;
                var p = {
                    field: f,
                    newValue: f.value,
                    oldValue: oldValue,
                    displayValue: f.displayValue
                }
                scope.$emit("field.change", p);
                scope.$emit("field.change." + f.name, p);
                if (scope.onChange)
                    try {
                        scope.onChange(e);
                    } catch (ex) {
                        console.log("directive.snRecordPicker error in onChange")
                        console.log(ex)
                    }
                isExecuting = false;
            }
            function onChanged(e) {
                e.stopImmediatePropagation();
                if (scope.$$phase || scope.$root.$$phase) {
                    console.warn('in digest, returning early');
                    return;
                }
                if (e.added) {
                    var selectedItem = e.added;
                    if (!scope.multiple) {
                        scope.field.value = selectedItem[scope.valueField];
                        if (scope.field.value) {
                            scope.field.displayValue = getDisplayValue(selectedItem);
                        } else
                            scope.field.displayValue = '';
                    }
                } else if (e.removed) {
                    if (!scope.multiple) {
                        scope.field.displayValue = '';
                        scope.field.value = '';
                    }
                }
                scope.$apply(function() {
                    callChange(oldValue, e);
                });
            }
            function getDisplayValue(selectedItem) {
                var displayValue = selectedItem[scope.valueField];
                if (selectedItem) {
                    if (scope.displayField && angular.isDefined(selectedItem[scope.displayField]))
                        displayValue = selectedItem[scope.displayField];
                    else if (selectedItem.name)
                        displayValue = selectedItem.name;
                    else if (selectedItem.title)
                        displayValue = selectedItem.title;
                }
                return cleanLabel(displayValue);
            }
            function getdisplayFields(selectedItem) {
                var displayFields = [];
                if (selectedItem && selectedItem[scope.valueField]) {
                    var current = "";
                    if (scope.displayField && angular.isDefined(selectedItem[scope.displayField]))
                        current = selectedItem[scope.displayField];
                    else if (selectedItem.name)
                        current = selectedItem.name;
                    else if (selectedItem.title)
                        current = selectedItem.title;
                    displayFields.push(current);
                }
                if (scope.displayFields) {
                    var columns = scope.displayFields.split(",");
                    for (var i = 0; i < columns.length; i++) {
                        var column = columns[i];
                        if (selectedItem[column])
                            displayFields.push(selectedItem[column]);
                    }
                }
                return displayFields;
            }
            scope.$watch("field.value", function(newValue) {
                if (isExecuting)
                    return;
                if (angular.isDefined(newValue) && $select) {
                    if (scope.multiple)
                        $select.select2('val', newValue.split(',')).select2('close');
                    else
                        $select.select2('val', newValue).select2('close');
                }
            });
            if (attrs.displayValue) {
                attrs.$observe('displayValue', function(value) {
                    scope.field.value = value;
                });
            }
            init();
        }
    };
}

Swapnil Soni1
Giga Guru

Hi Chandan,

One of the very powerful directives available in Service Portal that we will be covering today is the snRecordPicker. This directive generates a field very similar to a reference field in the platform. This is very useful when creating custom widgets that will be interacting with tables and records in ServiceNow.

The Directive:

It supports the following properties:

PropertyDescription
fielda JavaScript object consisting of “displayValue”, “value” and “name”
tablethe table to pull records from
default-querythe query to apply to the table
display-field (or display-fields)the display field
value-fieldthe value field (usually sys_id)
search-fieldsthe fields to search
page-sizenumber of records to display in dropdown

 

To use the snRecordPicker you will also need to create the “field” object in your controller as well as listen for the “field.change” event.

The Controller:

The Widget:

I’ve created a sample address picker widget that allows the user to select a location, and then retrieves the record from the server and populates several other fields with the information. The widget is available for download here: https://serviceportal.io/downloads/snrecordpicker-example/

 

Or you can go through for more info-

https://hi.service-now.com/kb_view.do?sysparm_article=KB0717097

 

Please mark correct or helpful if this helps.

Thanks

Swapnil