Skip navigation
Capture+_2016-06-29-17-14-40.png

I personally am an Android user so it has been like a pebble in my shoe that the iPhone users have had their app for almost a year with no love for the poor Android users. Well, friends, our day has finally arrived. You can now join the beta program and get access to the ServiceNow app for Android.

 

I have been using it for a day now and am very excited to have it. I am slowly building up the library of instances that I use on a regular basis. It's kind of a lot, so there has been a non-zero administrative hit just in the sheer logging in to all of them.

 

One of the reasons that I am excited is that a group of us have been working on a GTD app to manage our own (shaky) personal productivity. Although using the website from the Android phone isn't impossible, it just isn't in the same class as using a native app. Being able to open the list on my phone, and much more importantly quickly capture incoming items with a click of the button makes a world of difference.

 

At this point I don't have a lot of flight time on the app but almost everything has been great. I've seen one filter issue but I have successfully sent Connect messages, managed my to-do list and checked in my location. I am excited to be able to work with this app and really have been waiting impatiently for 9 months to get it.

 

Be aware, this requires Geneva Patch 6 or newer in order to connect to the instance. When I first tried to connect it to my developer instance, it wouldn't do it because I was on Geneva Patch 4 so I had to upgrade. With luck, a good bit of the field out there should qualify. If not, you'll have to wait for your instances to catch up I'm afraid.

 

Also be aware, this is a beta. Although the general release is not far off, you do have to accept that it is a beta and join the beta program before you can install it. It should be pretty good to go but you may find bugs or situations that don't work. If you do, feel free to send anything you find to me at dave.slusher@servicenow.com and I will route them on to the appropriate team. (I don't work with the mobile team, I'm just a fan.)

 

If you meet the above instance criteria and you have Android 4.4 or greater (and at this point if you don't, what is going on?) join the beta program, install the app and take it for a ride!

Dave Slusher | Developer Advocate | @DaveSlusherNow | Get started at https://developer.servicenow.com

Widgets are a very powerful component in Service Portal. A Service Portal page can have multiple widgets or just one widget. If your page is complex, It is a good practice to divide its functionality into multiple widgets to keep the implementations separate. When dealing with multiple widgets on a page its good to know how to communicate between them.

 

Folks familiar with angularjs might have already come across $emit, $broadcast and $on. This is exactly what we will be using today talk to other widgets.

 

We will start by creating a simple page with four widgets.

  • Widget 1: Has three buttons, clicking on these buttons shows its corresponding widget.
  • Widget 2/3/4: Shown/ Hidden based on button click in widget 1

 

Step 1: Create a portal or use an existing one.

         If you have a portal you are already working with just use that or to create a sample one to play with.  Attaching a screenshot of my example portal.

 

Screen Shot 2016-06-26 at 12.24.25 AM.png

 

Step 2: Create a Service Portal page and add that to your example portal.

         For demo purpose, I am going to create a new page give it a name/id and add that to my portal we created in the previous step.

Screen Shot 2016-06-26 at 12.32.10 AM.png

 

 

Now let's start creating our widgets.

 

Step 3: Create Widget 1

Click on widgets under "Service Portal" application menu (or you can also create widgets using widget editor). Create a new widget by clicking on the new button.

          Below is the example widget I have created.

Screen Shot 2016-06-26 at 12.38.32 AM.png

 

Please use the below snippets to create your widget

 

HTML:

<div>

<!-- your widget template -->

  <div class=" pill-background row">

    <div class="each-pill" ng-class="{'active':c.selectedPill == 'requests'}" ng-click="selectPill('requests')">

       <i class="fa fa-2x fa-list "  aria-hidden="true"></i>

        <p class="remove-margin">All Requests</p>

    </div>

    <div class="each-pill" ng-class="{'active':c.selectedPill == 'create'}" ng-click="selectPill('create')">

     

      <i class="fa fa-2x fa-wrench " aria-hidden="true"></i>

      <p class="remove-margin">Create Request</p>

    </div>

    <div class="each-pill" ng-class="{'active':c.selectedPill == 'contact'}" ng-click="selectPill('contact')">

     

      <i class="fa fa-2x fa-phone "  aria-hidden="true"></i>

      <p class="remove-margin">Contact</p>

    </div>

  </div>

</div>

 

 

As you can see div "each-pill" has ng-click to function "selectPill". There is ng-class to show the difference between selected/unselected pill

 

 

Client Controller:

function($rootScope,$scope,$timeout) {

  /* widget controller */

  var c = this;

 

  c.selectedPill = 'requests'; // When page loads, default pill selected

 

  /*broadcast to other widgets after waiting for 500 milliseconds,

    just to make sure other widgets are loaded and ready to listen when we broadcast.     

  */

  $timeout(function(){

  $rootScope.$broadcast('showHideWidget', 'requests');

  },500);

 

  //broadcast the selection when ever cliked on each pill

  $scope.selectPill = function(selection){

  c.selectedPill = selection;

  $rootScope.$broadcast('showHideWidget', selection);

 

  };

}

 

 

In script above I have used $rootScope.$broadcast to notify other widgets, we can either use $rootScope.$broadcast or $rootScope.$emit.

Keep in mind $rootScope.$broadcast will notify all $rootScope.$on and $scope.$on

$rootScope.$emit will only notify $rootScope.$on.This is the syntax $broadcast/$emit

 

//firing an event

$rootScope.$broadcast('myCustomEvent', 'data');

data can be a JSON object, in our case its just a string.

 

//listening for an event

$scope.$on('myCustomEvent', function (event, data) {

  console.log(data); // 'Data to send'

});

OR

$rootScope.$on('myCustomEvent', function (event, data) {

  console.log(data); // 'Data to send'

});

 

 

 

CSS/SASS:

 

//Make sure sp-landing-back.jpg is in images table

.pill-background {

    background: url(sp-landing-back.jpg) no-repeat center center fixed;

    -webkit-background-size: cover;

    -moz-background-size: cover;

    -o-background-size: cover;

    background-size: cover;

    height: 300px;

    display: flex;

    align-items: center;

    justify-content: space-around;

    .each-pill {

        &.active {

            color: #c70000;

        }

        height:150px;

        width:200px;

        background:#ddd;

        padding:50px;

        text-align:center;

        border-radius:4px;

      cursor:pointer;

     

      p{

       margin-top:5px;

      }

    }

}

.color-red {

    color: #c70000;

}

.remove-margin {

    margin-bottom: 0px;

}

 

After populating your widget with above code, go ahead and save it.

 

Widget 1 looks like this and you can see three pill boxes, clicking on them will emit/broadcast information to all other widgets. By default "All requests" pill is selected when the page loads.

emit.png

 

 

Step 4: Create Widget 2 (All requests)

Screen Shot 2016-06-26 at 1.23.40 AM.png

HTML:

<div ng-if="showWidget == 'requests'">

<div class="container">

<!-- your widget template -->

  <div class="col-md-10 center-div">

  <h3 style="display:inline-block;float:left;">Requests</h3>

  <div ng-repeat="incident in data.requestList | limitTo:5" class="each-incident">

    <h4>{{incident.number}}</h4>

    <p style="margin-bottom:0px;">{{incident.short_desc}}</p>

  </div> 

</div>

</div>

 

The first line of this html has ng-if. Based on "data" broadcasted with the event, these widgets are shown/hidden.

 

Client Controller:

function($scope,$rootScope,$timeout) {

  /* widget controller */

  var c = this;

  $scope.showWidget = "";

 

     //Listening for "showHideWidget" event

  $rootScope.$on('showHideWidget', function(event,data) {

  $timeout(function(){

       $scope.showWidget = data;

  });

  });

}

 

CSS:

.each-incident{

display:flex;

  justify-content:space-between;

  align-items:center;

  padding:10px;

  border-top:1px solid #ddd;

  clear:both;

}

.center-div{

  float: none;

  margin-left: auto;

  margin-right: auto;

}

 

Server script:

(function() {

  /* populate the 'data' object */

  /* e.g., data.table = $sp.getValue('table'); */

data.requestList = [];

 

var gr = new GlideRecord('change_request');

gr.addQuery('category','Hardware');

gr.query();

while(gr.next()){

var temp = {};

  temp.number = gr.number.toString();

  temp.short_desc = gr.short_description.toString();

  data.requestList.push(temp);

}

})();

 

Step 5: Create Widget 3 (Create request)

Screen Shot 2016-06-26 at 1.43.33 AM.png

 

 

HTML:

<div ng-if="showWidget == 'create'" class="col-md-10 center-div">

  <h3 style=" border-bottom: 1px solid #ddd;padding-bottom:5px; ">Create Request</h3>

<form class="form-horizontal">

    <div class="form-group">

        <label for="" class="col-sm-2 control-label">Request Number</label>

        <div class="col-sm-10">

            <input type="text" class="form-control"  placeholder="Number" autocomplete="off"> </div>

    </div>

    <div class="form-group">

        <label for="" class="col-sm-2 control-label">Short Description</label>

        <div class="col-sm-10">

            <input type="text" class="form-control"  placeholder="Short Description" autocomplete="off"> </div>

    </div>

   <div class="form-group">

        <label for="" class="col-sm-2 control-label">Description</label>

        <div class="col-sm-10">

            <textarea class="form-control" rows="3"></textarea>

    </div>

    <div class="form-group">

        <div class="col-sm-offset-2 col-sm-10">

            <div class="checkbox">

                <label>

                    <input type="checkbox"> High priority </label>

            </div>

        </div>

    </div>

    <div class="form-group">

        <div class="col-sm-offset-2 col-sm-10">

            <button class="btn btn-default">Submit</button>

        </div>

    </div>

</form>

</div>

 

Client Controller:

function($scope,$rootScope,$timeout) {

  /* widget controller */

  var c = this;

  $scope.showWidget = "";

  $rootScope.$on('showHideWidget', function(event,data) {

 

  $timeout(function(){

  $scope.showWidget = data;

  });

  });

}

 

CSS:

.center-div{

    margin-left: auto;

    margin-right: auto;

  float:none;

}

 

Step 6: Create Widget 4 (Contact)

Screen Shot 2016-06-26 at 1.48.47 AM.png

 

 

HTML:

<div ng-if="showWidget == 'contact'">

    <div class="container">

        <!-- your widget template -->

        <div class="col-md-10 center-div">

            <h3 style=" border-bottom: 1px solid #ddd;padding-bottom:5px; ">Contact</h3>

            <div class="flex-display col-md-9">

                <div class="">

                    <h4>Address:</h4>

                  <p class="remove-margin">XYZ Company</p>

                    <p class="remove-margin">123 Washington st</p>

                  <p class="remove-margin">Washington</p>

                </div>

                <div>

                    <div class="">

                        <h4>Email:</h4>

                        <a href="javascript:void(0)">abc@abc.com</a>

                    </div>

                    <div class="">

                        <h4>phone</h4>

                        <a href="javascript:void(0)">(123)4567890</a>

                    </div>

                </div>

            </div>

        </div>

    </div>

 

 

Client Controller:

function($scope,$rootScope,$timeout) {

  /* widget controller */

  var c = this;

  $scope.showWidget = "";

  $scope.$on('showHideWidget', function(event,data) {

 

  $timeout(function(){

  $scope.showWidget = data;

  });

  });

}

 

CSS:

.flex-display{

  display:flex;

  justify-content:space-between;

  padding:10px;

  clear:both;

  margin-left: auto;

  margin-right: auto;

  float: none;

}

.center-div{

  float: none;

  margin-left: auto;

  margin-right: auto;

}

.remove-margin{

margin-bottom:0px;

}

 

Step 7: Adding our widgets to Service Portal page.

        The last and final step is to add the widgets we created to a Service portal page using Service portal designer.

Screen Shot 2016-06-22 at 4.22.33 PM.png

 

click on "Service portal configuration" which takes you to a new page with a list of options.

 

Screen Shot 2016-06-22 at 4.22.47 PM.png

 

Select designer, A new page opens up and u can see all the "Service portal pages", search for your page, in my case its "sp_examples_homepage"

Screen Shot 2016-06-26 at 1.53.47 AM.png

Now in the page designer view drag and drop "Container" on the page followed by 12 column layout. On the left side, you can see all the widgets available for us to use, search for our widget we created in step 3 (Menu pills) and drag and drop that into the 12 column layout.

Drag and drop another 12 column layout below the "Menu Pills" widget. Search for our widgets we created in step 4,5,6 (All requests, Create request, Contact widget) and drag and drop them onto the 12 column layout. It should look something like this.

Screen Shot 2016-06-26 at 2.01.33 AM.png

 

We have successfully added our widgets to the page. Now let's check out this widget in action. Click on "portals" under "Service Portal" Application menu. Select the portal we used for this tutorial and hit "try it" button at the bottom.

 

You should see your Menu pills Widget communicating with other widgets to hide/show them.

 

widget_comm.gif

 

This is just a simple demonstration of how widgets can talk to each other. $rootScope.$broadcast('myCustomEvent', 'data'); can be very useful, when you want to send data to other widgets.

 

Sorry for the long post, hope this helps.

 

Thanks,

Sush

A Race between UI Policy and Client script

 

I happened to work on one issue in past for debugging UI Policies and client scripts.

 

Issue was something like:

 

For one particular state, UI Policy was suppose to set some fields as Mandatory and it was not happening. This was happening when form was loading.

I checked other UI policies as well which were of  higher order than the one which was not working as expected.  But I  could not find anything.

 

I was under the perception that client scripts always run first and then the UI Policies. But, I figured out one exception to this case which is being documented in this blog.

 

So let us start off with some test cases which I had built for this experiment. I have used multiple client scripts and UI Policies for this testing and used one global scratchpad variable to keep track of when particular script gets fired.

 

Steps :

 

1) Configure onLoad client script , you can use any table, In my experiment, I have used "Database Instance"

 

Expected result : set "Description (short_description) " field as "Mandatory" when script is executed.

I got my expected result when this script got executed, so far so good.

 

 

 

2) Now let us try to prove the fact that UI Policies are executed after the client script.

So now let's configure the UI Policy as show below.

I am now setting "Description" field to "Non Mandatory" to test if result of client script are applied in case 1 or result of this UI Policy is applied when form is loaded

 

 

 

Once done, open any of the record form on "Database Instance".

 

You will notice two alert messages, first alert message comes from onLoad client script which we built in step 1 and second message comes from UI Policy which we built in  this step.

Now let's try to figure out what is happening

a) Our onLoad client script when gets fired ahead of UI Policy, it tries to set "Description" field as mandatory.

 

 

b) UI Policy when gets fired after onLoad client script, it then tries to set "Description" field as non mandatory.

 

 

Now let's see what we get on the form.

We see "Description" field is non mandatory. And this is the expected behavior as well since UI Policies are executed later than client script.

3) Now configure onChange client script which gets fired when field "Operational Status" changes

 

 

 

Now intension is to set "Description" field as Mandatory.

Note that, we expect this script should gets fired only when field value on "Operational Status" changes.

Let's see what we get

.

 

 

4) Now, let's configure new UI Policy, lets make this UI Policy as conditional.

Configure the condition when operation status is  "Non - Operational", this script should fire and set "Description" field is "Non Mandatory"

 

 

 

 

 

 

Now change the "Operational " to "Non Operational" and see what we get. You will notice first our "onChange" client script gets fired, and then our "Conditional UI Policy" which we have configured in this step

 

 

 

 

 

 

 

 

This is also an expected result since UI Policy is fired at the very last, so far so good.

 

5) Configure onChange client script as per below screenshot. You will see there is an "isLoading" property which we have used in this script.

 

 

Reload the form and see observations.

 

 

 

 

 

 

 

 

You will notice

1) onLoad client script is executed first when form is loading

2) UI Policy which has no condition is executed, it has a lower order compared to next UI Policy

3) UI Policy which is conditional is executed

4) onChange client script gets executed, but if you closely notice, it the "isLoading" part within a client script is getting executed. This is really important and stood out to be the culprit which was causing field to alter its behavior since it is the part which got fired at very last. Someone has set the field action of mandatory is this portion of the script which was conflicting with the UI Policy.

 

Some of the observations:

 

1) onLoad client scripts are fired first

2) UI Policy with increasing order are fired.

3)onChange client scripts gets executed (onChange) if you put something into the isLoading portion when form is loaded irrespective of field is changed or not. Also it gets executed after the UI Policy.

So make sure not to set fields as mandatory or read only or visible using isLoading property. If you encounter similar issue, that would be hard to debug. Make sure to use "UI Policies" for this case always.

4) onChange client script gets executed if field value is changed for which it is configured, also make sure to put return is true in isLoading portion.

 

 

 

Kindly do not forget to like or bookmark this post if it assists you.

Service Portal is the quick and flexible way to build an application portal that your employees will love. I have been playing with it a lot and thought I would share with you all few things I learned. This topic is targeted towards advanced users/web developers.

 

People who have worked with angular might have used the built in filters for formatting expression values like dates, currencies etc or limiting the number of items displayed and more. Most of the time these built-in filters will do the job for us, but sometimes in your application, you might have a need to create a custom angular filter.

 

With angular providers in Service Portal, we can create angular directives, services and factory that can be automatically injected into your client script controller.

Screen Shot 2016-06-22 at 10.16.06 AM.png

If you see the type dropdown above, it doesn't have the option to create a "filter". So you cannot use angular providers for creating filters.

 

I will walk you through the steps to create a custom filter that we can use on a list of Incidents.

 

  • Step 1: Create a portal or use an existing one.

         If you have a portal you are already working with just use that or to create a sample one to play with.  Attaching a screenshot of my example portal.

 

Screen Shot 2016-06-22 at 1.31.55 PM.png

 

 

  • Step 2: Create a Service Portal page and add that to your example portal.

         For demo purpose, I am going to create a new page and call it homepagetest and add that to my portal I created in the previous step.

 

Screen Shot 2016-06-22 at 1.34.44 PM.png

 

  • Step 3: Create a simple widget to get a list of incidents and displaying it.

         Click on widgets under "Service Portal" application menu. Create a new widget by clicking on the new button.

          Below is the example widget i have created.

Screen Shot 2016-06-22 at 1.41.16 PM.png

 

Please use the below snippets to create your widget

HTML:

 

<div class="container">

<!-- your widget template -->

  <div class="col-md-12">

  <h3 style="display:inline-block;float:left;">Incident List</h3>

  <div class="form-group" style="display:inline-block;float:right;margin-top: 10px;margin-bottom: 0px">

   

    <div class="col-sm-12"> <input type="text" ng-model="filterValue" class="form-control"  placeholder="Search" style=""> </div>

  </div>

  </div>

  <div ng-repeat="incident in data.incidentList | startsFilter:filterValue" class="each-incident">

    <h4>{{incident.number}}</h4>

    <p style="margin-bottom:0px;">{{incident.short_desc}}</p>

  </div>

</div>

 

 

CSS:

 

.each-incident{

  display:flex;

  justify-content:space-between;

  align-items:center;

  padding:10px;

  border-top:1px solid #ddd;

  clear:both;

}

 

Server Script:

 

(function() {

  /* populate the 'data' object */

  /* e.g., data.table = $sp.getValue('table'); */

data.incidentList = [];

var gr = new GlideRecord('incident');

  gr.addQuery('active', true);

  gr.query();

  while(gr.next()){

  var temp = {};

  temp.number = gr.number.toString();

  temp.short_desc = gr.short_description.toString();

  data.incidentList.push(temp);

  }

})();

 

After populating your widget with above code, go ahead and save it.

 

  • Step 4: Creating our filter and adding a dependency to our widget.

        We will be coding our filter inside good old UI Scripts. So go ahead and create a new UI Script record.

 

Screen Shot 2016-06-22 at 3.50.50 PM.png

 

Here is the filter code. copy and paste this into UI script.

 

(function() {

    angular.module('startsWithFilter', []).filter('startsFilter', function() {

        return function(input, filtervalue) {

            if (filtervalue) {

                var output = [];

                for (var i = 0; i < input.length; i++) {

                    if (input[i].short_desc.toLowerCase().substring(0, filtervalue.length) === filtervalue.toLowerCase()) {

                        output.push(input[i]);

                    }

                }

                return output;

            } else {

                return input;

            }

        };

    });

})();

 

Now that we have the filter script ready let's add this as a dependency. Under "Service Portal" application menu there is a module called "Dependencies", go ahead and click on that. Once you are in, click new and fill in the form as below.

 

Screen Shot 2016-06-22 at 4.00.21 PM.png

 

Now we have to create a JS includes record and link the UI Script.

 

Screen Shot 2016-06-22 at 4.04.48 PM.png

 

On JS Includes table click on new and create a record for our "startsWithFilter" and select the source as UI Scripts, the search box appears for you to select the UI Script, here please select "startsWithFilter" and hit save.

Screen Shot 2016-06-22 at 4.07.29 PM.png

 

Now your widget dependency record is complete and it should look something like this.

 

Screen Shot 2016-06-22 at 4.10.39 PM.png

 

We now our filter script added to Dependencies list, next let's link this to our simple widget created in step 3. Open the widget record and scroll down to the section that says "Dependencies".

insert a new row and select the dependency we added above, save the widget record.

Screen Shot 2016-06-22 at 4.15.54 PM.png

 

  • Step 5: Adding our widget to a Service Portal page.

        The last and final step is to add the widget we created to a Service portal page using Service portal designer.

Screen Shot 2016-06-22 at 4.22.33 PM.png

 

click on "Service portal configuration" which takes you to a new page with a list of options.

 

Screen Shot 2016-06-22 at 4.22.47 PM.png

 

Select designer, A new page opens up and u can see all the "Service portal pages", search for your page, in my case its "homepagetest"

Screen Shot 2016-06-22 at 4.23.18 PM.png

Now in the page designer view drag and drop "Container" on the page followed by 12 column layout. On the left side you can see all the widgets available for us to use, we will just search for our widget we created in step 3 and drag and drop that into the 12 column layout. It should look something like this.

Screen Shot 2016-06-22 at 4.32.18 PM.png

 

We have successfully added our new widget on the page. Now let's check out this widget in action. Click on "portals" under "Service Portal" Application menu. Select the portal we used for this tutorial and hit "try it" button at the bottom.

 

you should see your Incident List Widget with a search field on the top right. Just start typing and you can see our "startsWithFilter" doing its magic.

 

startsWithFilter.gif

 

Sorry for the long blog, I wanted to make it as straightforward as possible for anyone with any level of expertise to follow this. Hope this helps.

 

Thanks,

Sush

UI16 is the latest UI, included started in Geneva. UI16 provides usability improvements and design changes, including an enhanced application navigator, color themes, and a refreshed knowledge base that delivers a more modern style to help drive user efficiency and productivity.That means if you just received your instance from ServiceNow and it is running on Geneva, you do not need to activate the plugin to get UI16, because it is active OOB. For instances upgrading from a lower version (pre-Geneva), you will need to activate the UI16 plugin (com.glide.ui.ui16) manually. As long as you have the admin role, activating UI16 after upgrading to Geneva or newer version, is a breeze.

 

Before you begin hunting around for the plugin to activate, confirm that you have successfully completed the upgrade of your instance by visiting the upgrade monitor (System Diagnostics > Upgrade Monitor).  When your upgrade is complete, it will display statistics on the most recent upgrade, including start and end date and a green message will appear saying it was a success.

upgrade success.jpg

 

To activate UI16 in your upgraded instance

  1. Navigate to System Definition>Plugins.

    search plugin.jpg

  2. Search for UI16 plugin (com.glide.ui16)

    UI16 plugin.jpg

  3. Open the plugin record.
  4. Click on related link "Activate/Upgrade"
  5. Click Activate to activate the plugin.
  6. Click on "Close and Reload Form" once you see the success message popup.
  7. Click the gear icon in the banner frame to open the System Settings window.
  8. Click "Switch to UI16"

    switch to ui16.jpg

 

Just to emphasize, after you have enabled the plugin, you must also complete step 8! Which means after enabling, you must tell your instance to "Switch to UI16" or you will continue to see UI15. If you do not see the ability to switch between UIs and the  "Switch to UI16" button is not available see ServiceNow KB: The "Switch to UI16" button is not visible (KB0564468)

 

Switching between UI15 and UI16

After UI16 is activated, you may need to view the changes in both versions of user interface. You can switch to UI15 while in UI16 by clicking on the gear icon in the banner frame. Please note, that by default, "Switch to UI15" button is only visible to the admin. However, if you would like to make this button available for other users, you can add a system property to configure other roles to see the "Switch to UI15" button.

 

Switching from UI16 to legacy UIs

If you wish to switch back to a legacy UI, I'm talking pre-UI15, you will first need to switch from UI16 to UI15, then you can switch to UI11. Unfortunately, there is no direct way to switch from UI16 to a legacy UI without, first, switching to UI15.

 

Congratulations, you have successfully activated UI16 plugin on your instance!

 

 

----

 

 

Here are some of the highlights from the UI16 overhaul:

 

Flexibility in the Application Navigator:

  • The Application Navigator is now completely collapsible into the “edge.” Use this feature when you need more screen real estate to focus on the tasks at hand.
  • Three-column structure to help you find what you need, when you need it. Columns include the standard application and module display, your favorites with new icons, and a new history log displaying recent interactions.

 

Improved Personalized Settings and Connect Access:

  • User options are updated to include user profile, Connect sidebar (Chat functionality), search, help, and baseline system settings.
  • The user profiles now feature global user presence. Whenever a user is online, a green dot will appear on their avatar. This applies for your profile and any other user profile in your instance to help communicate your status to team members. You’ll see this functionality appear in activity streams, live feeds, and Connect conversations.
  • Navigate to the settings to choose an instance color theme, language, time zone, list and form appearance. In addition you can set Connect notifications preferences based on personal preference.
  • Automatically jump to your latest conversation or activities in the Connect sidebar.

 

For more details on why UI16 is cool see Navigating the updates in UI16.

 

 

For additional help on activating UI16, see what other customers have asked:

UI16 plugin status can't be changed to "Active".

Can I enable UI16 but make UI15 default for all users in Geneva?

I upgraded to the Geneva instance and received and email saying the upgrade is complete, but it still appears to be Fuji?

Can we enable UI16 after upgrading to Geneva?

- Pradeep Sharma (@sharma_pradeep)
ServiceNow

PS: Hit like, Helpful or Correct depending on the impact of the response

Applies to: Geneva, Helsinki and beyond.

 

Have you ever needed to:

 

  1. Retrieve something using RESTMessageV2
  2. Save that thing as an attachment on a record
  3. Do something with the newly generated attachment?

 

This recently came up as a question over in the sndevs slack channel, and I realized that I knew how to accomplish #1 and #2, but didn't know how to actually identify the attachment to achieve #3 without some ugly hacks. But as it turns out, there's a right way to do this.

 

First, some code

 

Here's a quick refresher on steps 1 and 2: retrieving and saving an attachment.

 

// This is where we'll save the attachment
var tablename   = 'incident';
var recordSysId = '8d6353eac0a8016400d8a125ca14fc1f';
var filename    = 'snlogo.png';

// Let's download the ServiceNow Logo
var logoUrl = 'https://instance.service-now.com/images/logos/logo_service-now.png';
var request = new sn_ws.RESTMessageV2();
request.setHttpMethod('get');
request.setEndpoint(logoUrl);

// Configure the request to save the response as an attachment
request.saveResponseBodyAsAttachment(tablename, recordSysId, filename);

// When we execute the request, the attachment will automatically be
// saved to the record we specified
var response = request.execute();
var httpResponseStatus = response.getStatusCode();
var httpResponseContentType = response.getHeader('Content-Type');
gs.debug("http response status_code: " + httpResponseStatus);
gs.debug("http response content-type: " + httpResponseContentType);

 

Now what?

 

If all you're concerned about is saving the attachment data to a record, you're done. No further actions are necessary. But what if you want to do something else with the attachment you just generated? Copy it somewhere, process its contents, etc?

 

The call to saveResponseBodyAsAttachment() returns void, so we can't get a reference to the attachment from there. We could do something super ugly like go query the sys_attachment table and find the latest attachment associated with our target record, but this is far from ideal and prone to bugs.

 

I was happy to learn that there's a correct way to do this.

 

Using getResponseAttachmentSysid()

 

The response object returned by request.execute() provides a method called getResponseAttachmentSysid(). As the method name suggests, this returns the sys_id of the attachment generated by the REST call.

 

To use this, we simply call the method after executing the request. We can append the following lines of code to the earlier example:

 

// Get the sys_id of the newly created attachment
var newAttachmentSysId = response.getResponseAttachmentSysid();

// Do something useful with it
var grAttach = new GlideRecord('sys_attachment');
if (grAttach.get(newAttachmentSysId)) {
  // Note: we're using the scoped version of GlideSysAttachment
  // for this example.
  var gsa = new GlideSysAttachment()
  var content = gsa.getContent(grAttach);
  // Now we have the data...the rest is up to you
}

 

You can view/download the complete script here. This can be executed from Scripts - Background, just remember to update the instance URL and target table/record.

 

Happy attaching!

Josh Nerius | Developer Evangelist | @NeriusNow | Get started at developer.servicenow.com

Discovery is a flexible and powerful tool for building a robust and trustworthy CMDB. If you've spent any time troubleshooting or enhancing Discovery, you may share my impression that Discovery Logs take a while to get used to reading. You may have Run network Discovery to add your IP Ranges, or you may have chosen to Import IP ranges into Discovery schedules with import sets . In either case, you probably have IP Addresses in your Discovery Schedules for devices that you cannot authenticate to.

 

There are many reasons you would see these in your Discovery Logs:

    

CreatedLevelShort MessageSourceDevice
2016-06-17 23:59:29WarningSSH authentication or connection failureUNIX Classify172.21.120.12
2016-06-17 23:58:35WarningAuthentication failure with the local MID server service credential.Windows Classify172.21.120.132

 

In my case, these devices - and thousands more - are things like workstations or network devices that I already have in my CMDB through an integration source like SCCM or CiscoWorks. Sometimes they are Security appliances that Discovery isn't allowed to have Credentials for. On rare occasions they are devices where authentication is failing even though Discovery should have access. With 30k+ of these authentication failures happening daily, a few issues can be observed:

 

  • Security and Systems Administrators take issue with something failing to authenticate to these devices every day. Especially if more than one credential is attempted. (rightfully so!)
  • Configuration Management and Service Now Administrators have messy logs to comb through when working with Discovery.
  • Discovery Schedules take longer than needed and they can already take all night to run if you have a large CMDB.

 

Out of the box, excludes can be done manually but it's a tedious process to do it and then later maintain them. The question was asked Can discovery ranges exclude IP addresses dynamically? I decided to work on it and the attached Update Set is what I came up with.

 

 

Discovery Status

Here is an example Discovery Status where upon completion, a Business Rule has processed the Discovery Logs looking for Authentication Failures.

If authentication failures exist, QuickExcludes will query the CMDB looking for Hardware Configuration Items matching those IP Addresses which have Discovery Sources other than Service-Now.

When a match is found, an Exclude Parent for the Discovery Source is created within the Discovery IP Range. Any matching IP Addresses with the same Discovery Source will be added to that Exclude Parent.

discovery status with excludes.png

Discovery Schedule

Here is the resulting Discovery Schedule with Excludes added as a Related List to the form view.

Note that the Discovery Source "MS SMS" (SCCM) appears multiple times, as does CiscoWorks. This is because the Discovery Schedule is comprised of multiple IP Ranges. Each of these Ranges must have an Exclude Parent to hold any IP Address Excludes within that IP Range. To logically separate the Excludes, an Exclude Parent for each integration source holds all the Exclude Range Item IP's for that Discovery Source.

discovery schedule with excludes.png

Manual Excludes

 

 

From time to time it may be required that IP Addresses should be excluded for reasons other than their presence in the CMDB through another Data Source. In this case, a person with the cmdb_admin role can list select the IP in the Discovery Logs to Manually Exclude those IP addresses. These Excludes will be contained as the others, with the Exclude Parent being the UID of the user who excluded them.

manual quick exclude UI action.png

Maintenance

Once we exclude an IP Address from Discovery, it becomes necessary to know when to expire that exclusion so that if the IP Address is re-assigned, we will again Discover any device using that IP Address. A scheduled job can be ran nightly to verify that each Exclude still has an Operational Hardware Configuration Item that matches.

 

var qe = new QuickExcludes();
qe.verifyExcludes();

 

Note: Manual QuickExcludes are not checked for hardware records before creation and not removed by the scheduled job.

manual quick excludes created.png

Johnny Walker
Acorio LLC

I am working on a plan to help those that have apps in the global namespace convert those apps into a scoped app.  I'd be willing to hear any thoughts that the community wants to share (either post on this thread and / or email me chris.maloy@servicenow.com).  There are many difficult steps in this process (like migrating to a similar, but different data model as well as identifying which API calls you may be using that need to change because they are not accessible to other scopes).

 

For this reason I am going to start writing a bunch of scripts that can help in the conversion process.

This is a rough first draft for a helper script that can be called to help identify some global dependencies that will change during your conversion process.  Don't be too critical I wrote this while on a phone call and I don't multi-task well.  I am sure there are better ways to use regular expressions (instead of splitting strings like I am doing - welcome to any feedback).

 

You will need to call scanScript passing in the sysId of the script you want to scan (including the type if it is not a script include). If you are in a domain separated environment you will need to query with NoDomain. 

 

var siNameCalls = scanScript('ca4033c1d7110100fceaa6859e610326');

//gs.print("" + identifySIDependencies());

//default script type will be a script include (no need to pass in type unless it is not a script include)
function scanScript(scriptSysId, type) {
         var lgr = new GlideRecord('sys_script_include');

         switch (type) {
                 case 'businessrule':
                         var lgr = new GlideRecord('sys_script');
                         break;
                 default:
                         break;
         }

         lgr.get(scriptSysId);
         if (lgr) {
                 var txt = lgr.script,
                         ans = txt.split("new"),
                         anslength = ans.length;


                 while (anslength--) {
                         var parsedData = ans[anslength];
                         var dependency = parsedData.substr(0, parsedData.indexOf("("));
                         //gs.print(dependency);
                         var result = identifySIDependencies(dependency); //ItomJSONParser is public //JSONParser set to not public unit test values

                         if (result === 0) {
                                 gs.print(dependency + " is invalid script include usage. Not accessible in app scope.");
                         }
                 }
         }
}

function identifySIDependencies(dependency) {
         var sigr = new GlideRecord('sys_script_include'),
                 enc = 'active=true^name=' + dependency.trim();

         //build the lookup query
         sigr.addEncodedQuery(enc);
         sigr.query();

         if (sigr.next()) {
                 //check if public access
                 if (sigr.access == 'public') {
                         return 1; //public
                 }

                 return 0; //private
         }

         return -1; //not found
}

This video can help with several common questions that come up fairly frequently on the community. It will help you with:

  • Debugging business rules
  • Developing and debugging server side scripts
  • Managing lists

 

 

Referenced material:

Business Rules - ServiceNow Wiki

Business Rules Best Practices - ServiceNow Wiki

TechNow Episode List

Managing Glide Lists

Chuck Tomasi

TechNow Episode List

Posted by Chuck Tomasi Employee Jun 17, 2016

This list contains all known episodes of TechNow with links to the corresponding discussion threads and blog entries.

 

BOOKMARK THIS PAGE FOR EASY REFERENCE!

 

Episode #TitleRelease dateDescriptionPPT
1Jelly part 1February 1, 2013Introduction to what Jelly is, how it works, and where you find it with some simple examples.ppt-20.png
2Jelly Part 2February 22, 2013In part 2 we cover some of the language basics and common mistakes. Lots of demos again!ppt-20.png
3Jelly Part 3March 15, 2013In the final part of our series on Jelly, Andrew and Chuck walk you through testing and debugging your Jelly code along with some examples of what people have built with Jelly. Unlock even more power on your favorite platform with Jelly!ppt-20.png
4Server side scripting fundamentalsApril 5, 2013Chuck and Andrew take you through some of the fundamentals and best practices of server side scripting. There's a lot to know and we'll share as much experience as we can in one full hour!ppt-20.png
5Client side scripting fundamentalsMay 10, 2013This time Chuck and Andrew cover fundamentals and best practices of client side scripting. It's going to be another full hour of examples and best practices to help make you more effective with ServiceNow.ppt-20.png
6Script includesMay 31, 2013Andrew and Chuck LOVE script includes. Watch the next TechNow episode to find out the benefits script includes have. You will soon improve the effectiveness and efficiency of creating and maintaining your ServiceNow code.ppt-20.png
7User Interface DesignJune 21, 2013

ServiceNow makes it easy to create forms, lists, and other user interface objects. However, just because you don't need to know HTML, CSS, and JavaScript to quickly build the user interface in ServiceNow doesn't mean an effective result is guaranteed.

 

In this episode of TechNow, Chuck and Andrew share some key concepts to make sure you not only build a functional interface, but one your users/clients find easy to navigate as well.

ppt-20.png
8Introducing the Technical Best Practice WikiJuly 22, 2013Did you know we have over 100 technical best practices documented on our wiki? Watch the short video and find out where you can find these gems to avoid potential issues with performance, manageability, scalability, and more.N/A
9Extending ApprovalsDecember 13, 2013Wouldn't it be nice to have a way to ask a requester for "More Information" or "Justification" without really approving or rejecting it? It would be so easy to just add an option to the workflow activity and tie it to a new state... Sorry, that feature doesn't exist (yet). Until it does, the guys will show you a simple way to get this functionality you want without a lot of work. Using a simple UI action, some basic workflow skills, an email template, and an inbound action, you can make your approval process more robust!ppt-20.png
10Faster Access Control List RulesJanuary 16, 2014Performance can suffer when your ACLs use a script to query the database over and over to get the same result. Andrew and Chuck show you a way to make the system "remember" repeat queries and cache the answer for better performance.ppt-20.png
11Mail HeadersFebruary 14, 2014Make your administrative tasks easier and quicker! Chuck and Andrew share a solution to reduce maintenance for one of the most common notification requirements - standardized outbound headers and/or footers. Sure, copy/paste works well to make them all the same the first time, but maintenance can be time consuming. Join us and start to become a Jedi of managing data instead of going back to dev to make changes.ppt-20.png
12Condition FieldsMarch 14, 2014Save time and effort by using condition fields on your forms effectively. Chuck and Andrew will show you options available to manage data instead of hard coding conditions in to your requirements and the use cases for each. One of these choices is the condition field. This is an easy to implement solution that offers incredible flexibility and avoids costly development/test time as conditions change.ppt-20.png
13Defining Related ListsApril 30, 2014See your ServiceNow related information in a whole new way. You are not limited to records with a reference field to the parent table or a many-to-many table if you want a related list.ppt-20.png
14Avoid hard codingMay 16, 2014Learn how to recognize and avoid hard coded values in your scripts and workflows to help save time and money later by managing data instead of managing code.ppt-20.png
15GlideRecordSecureJune 20, 2014Chuck and Andrew cover the GlideRecordSecure class, compare it to GlideRecord, including the when, why, and how to use it. Let your scripts take advantage of ServiceNow's security. ppt-20.png
16Eureka NotificationsJuly 18, 2014Join Chuck Tomasi and Andrew Kincaid as they guide you through the improvements made to notifications in Eureka. Find out about full HTML support, how scripting is handled, how to debug outbound email issues, and convert your legacy notifications on demand.ppt-20.png
17Eureka ACL DebuggingAugust 18, 2014Join Chuck Tomasi and Andrew Kincaid as they guide you through the improvements made to access control list (ACL) debugging in Eureka. Find out how to quickly and easily resolve access control issues, what the different icons mean, and which ones get evaluated first.ppt-20.png
18REST APIOctober 24, 2014Join Chuck Tomasi and Andrew Kincaid as they guide you Eureka's REST API.ppt-20.png
19Orchestration - Behind the scenes at Knowledge 15March 26, 2015

Join our ServiceNow team of Chuck Tomasi, Kreg Steppe and Eric Williams for a look behind the scenes of what it takes to provision over 35,000 virtual machines for the Knowledge15 conference.  Kreg and Eric are the primary owners of the system that manages and provisions all those instances on time and with the proper configuration.

 

If you don't think ServiceNow uses it's own product, think again.  This is where we really push our Orchestration product.

ppt-20.png
20Fuji Features - Scoped ApplicationsApril 22, 2015Live from Knowledge15!  Join Chuck Tomasi and Kreg Steppe for TechNow Episode 20. This special show highlights new features in the Fuji release for building custom applications, what scoping is and what it means to you as you create awesome new solutions.ppt-20.png
21The Power of ProcessorsAugust 20, 2015Chuck and Kreg dig deep in to one of the most powerful and flexible features of ServiceNow – processors. Processors provide an end-point (URL) allowing you to format ServiceNow data in almost any way imaginable. You are likely already using processors and not even aware of them. We’ll show you some common usage and then demonstrate how to unleash this amazing feature.ppt-20.png
22Native iOS App FeaturesJanuary 27, 2016In this Ask the Expert, you will be guided through the latest features of the ServiceNow iOS application. The app is more than just an embedded browser, it features quick access to your favorites, the camera, geolocation, fast data entry and response, and more. Watch and learn.ppt-20.png
23Geneva - Scripted REST APIsFebruary 24, 2016

Watch the demo with the TechNow Team, Chuck Tomasi, Kreg Steppe, and Dave Slusher

And post your questions below.

 

Dave Slusher along with Chuck Tomasi and Kreg Steppe present an overview of a new feature with Geneva - Scripted REST APIs. This allows you to create flexible sets of functionality with a full scripting environment to integrate with external services and user interfaces. In this episode We show the range of options available and demonstrate some functionality on the fly.

ppt-20.png
24Custom Build in Activity Designer for OrchestrationMarch 30, 2016

Watch the demo with the TechNow Team, Chuck Tomasi, Kreg Steppe, and Dave Slusher

And post your questions below.

 

Kreg Steppe, Chuck Tomasi, and Dave Slusher build a custom activity using the Activity Designer for Orchestration. Much like Orchestration allows you to create a process that is reusable, the Activity Designer allows you do something similar by easily building custom activities that you can reuse in your Workflows. The team demonstrates Activity Designer templates, sharing data between activities, and parsing data from input.

ppt-20.png
25Setting Properties Pages for your Custom AppApril 27, 2016

Join Chuck Tomasi, Kreg Steppe, and Dave Slusher as they show you how easy it is to create a properties page for your custom app.

 

In a past TechNow Episode (14), we showed you how to create system properties to avoid hard-coding values to easily configure your application.

 

This episode follows up to help you manage your properties in a standard, familiar user interface. We cover properties categories, ordering, property usage in scripting, and more.

ppt-20.png
26Source Control New Platform Feature in HelsinkiMay 18, 2016Join Chuck Tomasi, Kreg Steppe, and Dave Slusher, live from Knowledge 16, we'll take an in depth look at source control, one of our favorite new platform features in Helsinki. Source control offers improved management for your application development, allows easy distribution to other dev instances, and leverages industry standard applications.ppt-20.png
27Community EtiquetteJune 29, 2016

Pradeep Sharma joins Dave Slusher and Chuck Tomasi as the subject matter expert to talk about using the community to its fullest. We share the tips on how you can improve chances of getting your question answered and how to participate on the community and earn the cool badges.

ppt-20.png
28Service Portal Part 1
July 20, 2016

Join Chuck Tomasi, Kreg Steppe and Dave Slusher as they walk you through creating and using a Service Portal widget on the Helsinki release. We will demonstrate how to code the widget itself with the real time editor, how to set up a Service Portal page to include that widget, and how to make that page available to end users. Service Portal is the Helsinki developer feature with the most excitement behind it and for good reason. Come see why!

ppt-20.png
29Service Portal Part 2August 17, 2016

Join Chuck Tomasi and Dave Slusher as they continue their discussion on Service Portal based on your input! We get in to some more advanced topics around creating widgets and solving problems you asked about. Service Portal is the Helsinki developer feature with the most excitement behind it and for good reason. Come see why!

ppt-20.png
30Cloud ManagementSeptember 22, 2016Kreg Steppe leads you though the process of managing your existing cloud images and instances. He will take his current assets and create a catalog item with a workflow using the Cloud Management plugin. Be sure to catch the community quick tip.ppt-20.png
31Regular Expressions Part 1October 13, 2016

Chuck, Dave, and Kreg are back to help you understand the world of regular expressions. These powerful patterns can be extremely useful in scripting, Edge Encryption, and more. Find out how to understand, build, and maintain your regular expressions to take advantage of some very powerful features. Community quick tip: IIFE function format.

ppt-20.png
32Regular Expressions Part 2November 10, 2016

Chuck, Dave, and Kreg are back with more regular expression help in part 2. We'll help you understand the world of regular expressions. We'll talk about modifiers, capture groups, and more. Community quick tip: Copy query/addEncodedQuery()

ppt-20.png
33GlideAjaxDecember 15, 2016Chuck and Dave cover GlideAjax - a common scenario for making server information available to your client scripts. While this can seem complex at first, they take it step by step and explain everything to help you feel confident in creating effective and efficient scripts. Community quick tip: Accessing files in Studio and other places.ppt-20.png
34Istanbul Platform FeaturesJanuary 18, 2017This time the trio gives you a developer’s overview at what’s new in Istanbul. They get in to enhancements to Studio, new testing capabilities to reduce your upgrade cycle, new scripting APIs, enhancements to notifications and much more! Community quick tip: GlideRecord positioning.ppt-20.png
35Automated Test FrameworkFebruary 16, 2017Reduce the time it takes to upgrade! The Istanbul release introduces an Automated Test Framework included in the ServiceNow platform. In this session we explore how test cases, test definitions and test suites are organized; how to execute them and what tests are included pre-built with the framework. We also explore how to code your own tests as well and show the abilities and limitations of using the ATF to improve quality of delivered applications. Plus the community quick tip: Cascade Delete.ppt-20.png
36Dates and TimesMarch 16, 2017Join Kreg Steppe, Dave Slusher, and Chuck Tomasi as Kreg clears up some of the mystery around one of the toughest assignments in ServiceNow - scripting dates and times. Learn what controls you have, some of the APIs, and best practices to gain an understanding of how to properly solve your requirements. Plus the community quick tip: Reference Qualifiers.ppt-20.png
37Understanding JSONApril 13, 2017Constructing easy integrations to other systems is a big part of what makes ServiceNow such a wonderful application development platform. Sometimes the challenge lies in figuring out exactly what you have been given and how to apply it in ServiceNow. In this episode, the TechNow crew will guide you through one of the most common data exchange formats - JSON. Even though the results from your remote system are text, it may be difficult to understand it and get access to the specific property or value in JavaScript. As always, the team starts with some easy examples to help you understand the fundamentals and build more complex scenarios to help you interpret and access complex data objects. Community quick tip: Avoiding pitfalls with platform objects.ppt-20.png
CC17-1Create apps at lightspeed - no/low codeMay 9, 2017Chuck, Dave, and Kreg build an app from scratch to manage meetups in your organization with little to no script to show how easily you can build functional apps.ppt-20.png
CC17-2Create apps at lightspeed - pro codeMay 10, 2017The crew returns the next day to build additional functionality on the meetup app with JavaScript, a REST integration, and Service Portal widget to give an amazing user experience.ppt-20.png
38Advanced Workflow Tips and TricksJune 22, 2017

Join Kreg Steppe, Dave Slusher and Chuck Tomasi as Kreg shows some tips and tricks with the ServiceNow Workflow GUI. We will take away a little of the mystery behind it and make it a more useful for you. Learn a bit about Timers, Waits, Scripting in Workflows and building your own activities to make Workflow work exactly as you want it to.

ppt-20.png
39Appointments and RemindersJuly 20, 2017

Chuck takes a look at some hidden gems in the platform that have been in the system almost since the beginning. The Appointments and Reminders table are available to any table extended from task that can add some very useful features to your application without very little development effort. Quick tip: Display values on reference fields

ppt-20.png
40Jakarta Platform FeaturesAugust 14, 2017

Dave, Chuck, and Kreg show off their favorite Platform features, updates, and enhancements in Jakarta. While there are no Earth-shattering new features as in the past several releases, the sum of the features we’ll cover may surprise you. A wide range of features from the UI to integrations have been updated.

ppt-20.png
41Jakarta Service PortalSeptember 14, 2017

Service Portal remains one of the hottest topics for ServiceNow developers. On this episode of TechNow we will overview the state of the art of Service Portal as of the Jakarta release with an overview of capabilities for those new to the functionality. We will also cover a detailed examination of the new and updated features from the Istanbul and Jakarta releases. The ServiceNow platform + AngularJS is greater than the sum of its parts so join us to learn why!

ppt-20.png
42Basic ReportingOctober 12, 2017

Join Kreg Steppe, Dave Slusher, and Chuck Tomasi as Kreg demonstrates the reporting features of ServiceNow. Take a look at the ServiceNow Report Writer and it's features. Leverage data we all collect in our systems and turn them into reports as well as mix it up into various styles and see how many ways we can look at our data. Plus the community quick tip: application navigator shortcuts

ppt-20.png
43Advanced ReportingNovember 16, 2017

Following up on TechNow episode 42, we dive a little deeper into ServiceNow reporting and taking a look at some advanced features. Now that we are visualizing our data in reports, let’s make our reports a little more robust by exploring drilling down, interactive dashboards, related lists, and database views.

Quick tip: Best practices with reference fields

44Kingston Part 1December 4, 2017

ServiceNow's Kingston release is packed with new platform features - so many that we need to make this a two part series. In this part, Dave Slusher and Pradeep Sharma guide us through two of the hottest new features: Flow Designer, our next generation workflow, and Integration Hub, our new orchestration engine. Watch and learn as we demonstrate how to use and build the components around these cornerstones that will change how you build applications. Quick tip: Chuck shows a short hand notation for retrieving one record using Javascript.

Quick tip: Chuck shows a short hand notation for retrieving one record using Javascript.

45Kingston Part 2December 14, 2017

In part 2 of our series on Kingston Platform features, Chuck and Pradeep dive in to many of the other platform features that were released in Kingston. Find out what's new and how it can be applied to your applications and integrations to make development, testing, and deployment faster than ever.

Quick tip: Avoiding scripting issues when your field is named "query" or "next".

Building on the "Xplore GlideRecord Script" Tool post, the "Preview in Background Script" tool will open a new Background Script window and populate the script field with the template of code containing the list view's query:

 

 

The script will be populated 1/4 second after the Background Script window is opened in order to ensure the window has been opened properly.

 

All you need to do is create a new Context Menu record with the following details:

 

Table: Global

Menu: List Header

Type: Action

Name: Preview in Background Script

Order: 116,000

Condition: gs.hasRole("admin")

Action script:

 

(function u_previewGlideRecordScript(){
  var fixedQuery = ("" + g_list.getFixedQuery()).replace("null", "");
  if (fixedQuery == "") {
  fixedQuery = ("" + g_list.getRelatedQuery()).replace("null", "");
  }
  var query = fixedQuery;
  var listQuery = g_list.getQuery();
  if (listQuery != "") {
  if (query == "") {
  query = listQuery;
  } else {
  query += "^" + listQuery;
  }
  }
  var newLine = "\n";
  var script = "(function() {" + newLine;
  script += "    var gr = new GlideRecord('" + g_list.tableName + "');" + newLine;
  if (query != "") {
  script += "    gr.addEncodedQuery('" + query + "');" + newLine;
  }
  script += "    //gr.setLimit(100);" + newLine;
  script += "    //gr.setWorkflow(false);" + newLine;
  script += "    //gr.autoSysFields(false);" + newLine;
  script += "    gr.query();" + newLine;
  script += "    while (gr.next()) {" + newLine;
  script += "        " + newLine;
  script += "    }" + newLine;
  script += "})();";

  // open the Background Scripts page in a new window
  var win = window.open('/sys.scripts.do');

  //and then populate the script box a quarter second later
  setTimeout(function(){
  try{
  var textArea = win.document.getElementById("runscript");
  textArea.value = script;
  } catch(err){}
  }, 250);
})();

 

 

Related posts:

"Preview GlideRecord Script" Tool

"Xplore GlideRecord Script" Tool

*** Please Like and/or tag responses as being Correct.
And don't be shy about tagging reponses as Helpful if they were, even if it was not a response to one of your own questions ***

I previously wrote a helpful little tool called "Preview GlideRecord Script" which would popup a window with the current list view's filter within a block of code:

This would allow you to copy and paste the code wherever you needed it and use it as a starting point.  I thought I'd do the same thing for James.Neale's excellent Xplore tool.  All you need to do is create a new Context Menu record with the following details:

 

Table: Global

Menu: List Header

Type: Action

Name: Xplore GlideRecord Script

Order: 116,000

Condition: gs.hasRole("admin")

Action script:

(function u_previewGlideRecordScript(){
  var fixedQuery = ("" + g_list.getFixedQuery()).replace("null", "");
  if (fixedQuery == "") {
  fixedQuery = ("" + g_list.getRelatedQuery()).replace("null", "");
  }
  var query = fixedQuery;
  var listQuery = g_list.getQuery();
  if (listQuery != "") {
  if (query == "") {
  query = listQuery;
  } else {
  query += "^" + listQuery;
  }
  }
  var newLine = "\n";
  var script = "(function() {" + newLine;
  script += "    var gr = new GlideRecord('" + g_list.tableName + "');" + newLine;
  if (query != "") {
  script += "    gr.addEncodedQuery('" + query + "');" + newLine;
  }
  script += "    //gr.setLimit(100);" + newLine;
  script += "    //gr.setWorkflow(false);" + newLine;
  script += "    //gr.autoSysFields(false);" + newLine;
  script += "    gr.query();" + newLine;
  script += "    while (gr.next()) {" + newLine;
  script += "        " + newLine;
  script += "    }" + newLine;
  script += "})();";

  //open the Xplore tool in a new window
  var win = window.open('/snd_xplore.do');

  //when Xplore has loaded, set the script
  jQuery(win).bind('load', function(){
    win.snd_xplore_editor.setValue(script);
  });
})();

 

This now adds a new context menu when you right-click a list view header:

 

When you select it, a new Xplore window is opened and populated with the block of code containing the list view's query:

 

You obviously need to install the Xplore tool itself in order for this to work and it can be found on the Share site - https://share.servicenow.com/app.do#/detailV2/bbe5dd7213e7de004e8cd4a76144b0ef/overview

 

Have a good weekend!

 

Related posts:

"Preview GlideRecord Script" Tool

"Preview in Background Script" Tool

*** Please Like and/or tag responses as being Correct.
And don't be shy about tagging reponses as Helpful if they were, even if it was not a response to one of your own questions ***

One of the powerful features of the new Helsinki Service Portal is the integrated "record watcher" capability. This allows widgets to update in near realtime as the data changes in the database. In this tutorial we will build a sample To Do (task ) app that uses two widgets, one for creating records, and the second for showing the list of tasks.

 

--------------------------------
Nathan Firth
Principal ServiceNow Architect
nathan.firth@newrocket.com
http://newrocket.com
http://serviceportal.io

What do Transform Map Scripts and algebra have in common? Not much, except the order of execution (precedence) is very important to the final result.  Transform maps provide many opportunities for scripting, but do you know which order they will be executed when data is processed through the mapping?

 

I was recently involved with an import project that needed some fine-tuning.  During this exercise, we found that we were making multiple GlideRecord calls to find the same pieces of data, and this was being done through the various transform map scripts.  Of course, we decided the best option was to make the call once, then store the returned data in variables.  Even though this was the best approach, the results were not exactly what we expected.

 

After some trial-and-error, we found that the order in which we expected the scripts to execute was not the order in which they actually ran.  To make sure we all know exactly what to expect, I created some data to process, and wrote a few different types of scripts (with log.info() statements) to help capture some details.  The log statements will show us which scripts executed, and which order they were processed.

 

Here is an exercise to validate the exact order of execution for the various types of transform map scripts.

First, let's define the types of transform map scripts:

  • onStart - The onStart event script is processed at the start of an import run, before any data rows are read.
  • onBefore - The onBefore event script is processed at the start of a row transformation, before the source row is transformed into the target row.
  • onAfter - The onAfter event script is processed at the end of a row transformation, after the source row has been transformed into the target row and saved.
  • onComplete - The onComplete event script is processed at the end of an import run, after all data rows are read and transformed.
  • FieldMap - Available in the Source script field.
  • "Run script" - the script which is displayed when the "Run script" checkbox is selected.

 

To make sure we exercised the majority of options, with an eye on keeping the output log manageable, here are the tests I've decided to run.

  1. Successful insert.
  2. Update the record.

 

The import set will contain four rows. The third row will ignore, and the fourth row will error (ignore and error will be set in the onBefore script).  Each row will contain three columns.  The first will be the coalesce field, and the next two will each have field map scripts.

 

I will have log.info statements in each of the scripts, printing some details about the script, and some row details being processed.  I also added the date/time (in milliseconds) to the start of each log statement so we can sort A-Z on the Message column, so it will be in the correct order of execution.

 

Here's what I used:

var gdt = new GlideDateTime();

var info = gdt.getNumericValue()+

 

DATA:

RecNumberField1
Field2
1Rec1Field1Rec1Field2
1Rec2Field1Rec2Field2
3Rec3Field1Rec3Field2
4Rec4Field1Rec4Field2

 

I know it is not very exciting, but hopefully it will make reading the log statements clear.

 

I created a .csv file with this set of data.  Using a "File" data source, I attached the file, and performed a test load of 20 records.

DataSource.jpg

 

This created an import set table called u_transformscriptlogger.  I was then able to create the transform map so I could call the scripts.  The scripts I used (provided below) used log.info() statements so I could capture some output in the transform log (found in the navigator).

ImportLog_Filter.jpg

Which then provides the import log messages that were printed through the log statements.

 

For example:

LogSamples.jpg

 

After running the transform map to process the data, this was the message output:

1462763686601 onStart

1462763686610 onBefore 1 Ignore: false Error: false

1462763686612 Field Map Rec1Field1

1462763686614 Field Map Rec1Field2

1462763686616 Run script

1462763686637 onAfter 1

1462763686641 onBefore 1 Ignore: false Error: false

1462763686644 Field Map Rec2Field1

1462763686646 Field Map Rec2Field2

1462763686648 Run script

1462763686653 onAfter 1

1462763686656 onBefore 3 Ignore: true Error: false

1462763686659 onAfter 3

1462763686663 onBefore 4 Ignore: false Error: true

com.glide.db.impex.transformer.TransformerScriptException: onBefore script error.

at com.glide.db.impex.transformer.TransformerScript.checkErrorCondition(TransformerScript.java:79)

at com.glide.db.impex.transformer.TransformerScript.runScript(TransformerScript.java:63)

at com.glide.db.impex.transformer.TransformerScript.runWhenScript(TransformerScript.java:122)

at com.glide.db.impex.transformer.Transformer.runOnBeforeScript(Transformer.java:269)

at com.glide.db.impex.transformer.Transformer.transformBatch(Transformer.java:142)

at com.glide.db.impex.transformer.Transformer.transform(Transformer.java:76)

at com.glide.system_import_set.ImportSetTransformerImpl.transformEach(ImportSetTransformerImpl.java:237)

at com.glide.system_import_set.ImportSetTransformerImpl.transformAllMaps(ImportSetTransformerImpl.java:91)

at com.glide.system_import_set.ImportSetTransformerWorker.startWork(ImportSetTransformerWorker.java:40)

at com.glide.worker.AbstractProgressWorker.startAndWait(AbstractProgressWorker.java:86)

at com.glide.worker.ProgressWorker.startAndWait(ProgressWorker.java:52)

at com.glide.worker.ProgressWorkerThread.run(ProgressWorkerThread.java:50)

onBefore script error.

 

The expectation was that the "run script" would execute either just before, or just after the onBefore script for each row, but this testing shows that this is not the case. The onStart script executed first, as expected. Then, the onBefore script for the first inserted record executed. Followed by the field map scripts, and then the "run script" executed after the field mapping scripts. Finally, the onAfter script for the first record executed.

 

The next row was executed in the same order, so it didn't matter if the record was inserted or updated (based on the coalesce value).

 

The third row may not have executed as expected.

 

The log shows:

1462763686656 onBefore 3 Ignore: true Error: false

1462763686659 onAfter 3

 

So, even though the 3rd record  set the "ignore" variable to "true", the  onAfter script executed.  The field map scripts and the "run script" obeyed the "ignore" but the onAfter script executed.  As I was working on this blog post, I see this tweet by fellow Support engineer jonnyseymour which helped explain exactly why this happened.  Here's the tweet and KB number:

transform map tweet.jpg

Linking to ServiceNow KB: Import Set onAfter script runs for ignored rows during transformation (KB0563524)

 

Finally, for row 4, I set error = true, and processing was terminated. Not just processing for that row, but for the entire import set. I ran it again, without setting any errors, and after the onAfter of the last row was executed, the onComplete script ran.

 

This gives us three take-aways:

  1. The correct order of execution for transform map scripts are:
    1. onStart
    2. onBefore executes on each row before any other processing is performed
    3. then we process the field mapping scripts
    4. the "run script" script follows the field map scripts
    5. we then process the onAfter script
    6. and finally, after all of the data has been processed, we finish with the onComplete script.
  2. Setting "ignore" to true will not prevent the onAfter script from executing.  It only prevents the "run script" and field mappings from being processed for the record being ignored.
  3. When "error" is set to true, all processing for that import set will stop.  No additional scripts will be processed, including onComplete.

 

Happy Mapping!

 

PS: For completeness here are the scripts:

"run script"

var gdt = new GlideDateTime();

var info = gdt.getNumericValue()+" Run script";

log.info(info);

onStart

var gdt = new GlideDateTime();

var info = gdt.getNumericValue()+" onStart";

log.info(info);

onBefore

if (source.u_recnumber == 3)

  ignore = true;

if (source.u_recnumber == 4)

  error = true;

 

var gdt = new GlideDateTime();

var info = gdt.getNumericValue() + " onBefore " + source.u_recnumber + " Ignore: " + ignore + " Error: " + error;

 

log.info(info);

onAfter

var gdt = new GlideDateTime();

var info = gdt.getNumericValue()+" onAfter "+source.u_recnumber;

log.info(info);

onComplete

var gdt = new GlideDateTime();

var info = gdt.getNumericValue()+" onComplete ";

log.info(info);

 

Field Map:

var gdt = new GlideDateTime();

var info = gdt.getNumericValue()+" Field Map "+source.u_field1

log.info(info);

 

and

 

var gdt = new GlideDateTime();

var info = gdt.getNumericValue()+" Field Map " +source.u_field2;

log.info(info);

Field Map

var gdt = new GlideDateTime();

var info = gdt.getNumericValue()+" Field Map "+source.u_field1

log.info(info);

 

and

var gdt = new GlideDateTime();

var info = gdt.getNumericValue()+" Field Map " +source.u_field2;

log.info(info);

 

 

My testing was performed with Fuji and Geneva.

Welcome to the TechNow blog. This is dedicated to our ServiceNow webinar series on technical topics from subject matter experts. Each episode will appear in its own article (to provide better comment organization.) You can watch new webinars live or come back later and watch them any time. New shows are available approximately every 2-3 weeks. We hope you find these useful and informative! We appreciate any feedback you may have.

 

The first episode (below) was recorded 4-February-2013. It was previously located on Chuck Tomasi's blog.



Find more ServiceNow Demo videos on the ServiceNow Demo YouTube channel.

I ran into an issue recently where I discovered that if I set a dot-walked field to mandatory, the form would still submit regardless of the red mandatory indicator.

 

I had worked (what I thought was) a simple Incident to modify a client script for a newly dot-walked field name instead of the old field name. We had decided that instead of copying the comments journal entries from sc_task up to the request, we would instead just uniformly use the request comments field. The onChange client script which required customer communication notes when placing a Catalog Task in a Pending state were simply overlooked.

 

Easy ticket - "done before my second cup of coffee" - so I thought!

 

I modified the client script and visually confirmed the mandatory indicator on the dot-walked journal field when I placed the sc_task.state field in Pending. The idea being that we should notify the customer why their request is now pending.

 

I returned from a mid-morning meeting to find the incident reporter asking me which environment I wanted her to test in because it still wasn't working. I pulled up a test sc_task record and proudly displayed the red splat of mandatoryness on the form.

 

"Go ahead..." she said, "Click Submit."

 

I submit the form. No errors.

 

"Oh man... That's not good. I'll get on it." I tell her. I start guessing there's somehow a javascript error in one of our onSubmit scripts causing it to bypass the mandatory field checks.

 

"Easy fix" I think to myself. (I always think that)

 

After I work on this a little while, I decide to ask for help and open a ticket in HI. I'm told this is working as designed. They point me to Creating New Fields - ServiceNow Wiki  where it says this:

 

A form can be saved with an empty mandatory field, if that field is a reference field (derived from another table) and if the parent field is also blank. However, if the mandatory reference field shows a value from the parent field, then the form cannot be saved if this value is deleted. It is important to note that if the value in the referenced field is changed, the value for that field is changed everywhere it appears.

 

I was a bit confused by this, and while trying to wrap my mind around it, I notice that if I click into the field, type something and remove the value, then the tab section suddenly indicates a mandatory field is present. Sure enough when I try to submit the form now, I can't do it without entering something in this mandatory field. I can even toggle the mandatory off and on again using the state field's onChange script. The 'fix' seems to hold once I've entered text into that field.

 

Section not Mandatory
Section has Mandatory field
Section not Mandatory.PNGSection Mandatory.PNG

 

I took a look at the DOM and noted the onkeyup function for this field:

Inspect Element.PNG

I was then able to add the line 9 here to my client script so that my mandatory field enforcement was in place:

 

function onChange(control, oldValue, newValue, isLoading, isTemplate) {
  if (isLoading) {
  return;
  }
  //If we are hitting the pending state or closed incomplete, require a comment
  if(newValue == -5 || newValue == 4){
  g_form.setMandatory('request_item.comments', true);
  //workaround for PRB569134 where SN Dev states this is working as designed
  multiModified(g_form.getControl('request_item.comments'));
  } else {
  g_form.setMandatory('request_item.comments', false);
  }
}

 

I plan on writing a blog post soon about debugging the client side code, which is where we actually find the multiModified function declaration.

Johnny Walker
Acorio LLC

Filter Blog

By date: By tag: