Skip navigation

Note: This blog post reflects my own personal views and do not necessarily reflect the views of my employer, Accenture.

 

One of the nice features of Service Portal is the sheer number of widgets that are included by default after activating the plugins. In today's AISP (yep, I acronymed it) post I'm going to explore one of those widgets a bit. The Form Widget.

 

One of the most common questions I used to see around CMS was something like: "How do I open this record in CMS?" There were a couple of different ways to do it with content types and detail records, a static iframe, or with a little bit of work a more dynamic iframe that would take in url parameters and render some content. Each of these methods had some drawbacks and I was never quite satisfied with both the learning curve and the result. Oh, and they all used iframes.

 

With the form widget it's easy to display any record in ServiceNow within your Service Portal without doing any configuration, all you need to do is link to it. Lets take a look at the self-service view of an incident form in the standard view of ServiceNow:

 

incidentdefault.PNG

 

We know that this form has a url that looks something like:

https://instance.service-now.com/nav_to.do?uri=incident.do?sys_id=b28a474f4fb8220067d9b5e18110c758&sysparm_view=ess

 

Without doing anything other than making sure that the Service Portal plugins are enabled, I can show this same record in my Service Portal using this url:

https://instance.service-now.com/sp?id=form&table=incident&sys_id=b28a474f4fb8220067d9b5e18110c758&view=ess

 

incidentformwidget.PNG

 

The form widget is on a page already by default that can be called in any service portal called form. You're then able to pass the page the following url parameters:

  • table - the name of the ServiceNow table where the record is located
  • sys_id - the unique identifier of the record
  • view - an optional parameter where you can specify which view the fields are pulled from
  • query - allows you to specify a query

 

If you want to include this widget on another page, you also have some widget options you can set:

  • Disable UI Actions on Form
  • Display current form view
  • Omit header options icon

 

You may have noticed that by default the form widget includes ui actions, all fields and related lists on the form view, and also client scripts and ui policies. However, there are some exceptions to those. They are documented here, but I'll list them out:

  • Client side ui actions
  • Anything that uses jelly, including
    • UI Macros
    • UI Pages/Formatters
  • Client Scripts where Run scripts in ui type is Desktop

 

In addition to those, there are a number of different client side scripting techniques and methods that are not supported. All of the Service Portal supported objects and methods are listed in the github doc below, and these are not limited to the form widget.

documentation/client_scripting.md at master · service-portal/documentation · GitHub

 

Previous blogs in this series:

Adventures in Service Portaling: Introduction and Resources

Adventures in Service Portaling: How Do I Get To That?

In this week's podcast, Patrick Wilson explains how to use the new Service Portal, available in Helsinki.


volume_icon.png

Listen

 

 

Subscribe

 

to iTunes

 

This episode covers:

  • No code, low code, and pro code for all levels of users
  • Customizing widgets in the portal
  • Debugging Service Portal
  • Security in the portal

For more information on Service Portal, see:

 

Your feedback helps us better serve you! Did you find this podcast helpful? Leave us a comment to tell us why or why not.

 

LISTEN BELOW

 

 

To catch clips behind the scenes with our podcast guests, and find out what topics we'll be covering next before they are posted, follow @NOWsupport on Twitter. You can also search Twitter using the hashtag#SNtechbytes for all previous podcasts, video clips and pictures.

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE to ADVANCED

Assumes good knowledge and/or familiarity of scripting in ServiceNow.

____________________________________________________________________________

 

In this article I will present a couple of different methods I use to locate a record in a complex object array.

 

This is the third of three articles I wanted to write concerning object arrays.  My previous two were:

 

Community Code Snippets - Three Methods to Sort an Object Array

Community Code Snippets - Removing Duplicates From an Object Array

 

I would do both before playing with the code in this example.

 

You will need to do the following Mini-Labs in order to use underscore.js and the JavaScriptExtensions code in the following example:

 

Mini-Lab: Adding Underscore.js Into ServiceNow

Mini-Lab: Extending the JavaScript Array Object with findIndex

 

So, now that we have the base libraries, let me present the examples!

 

I have attached the Fix Script I used for this article.  It contains the getIncidentList(...) code, but does not include the underscore.js or Array.polyfill.findIndex code.  You will need to do the labs to bring that functionality in.

 

// Set up for all of the following examples.  Get 10 records from the Incident table

var incidentList = getIncidentList(10);

var incidentID = '0c43b55ac6112275019abd2b247baf31';

 

EXAMPLE 1:

 

This is my preferred method.  It does not require a library, and works with existing out-of-the-box ServiceNow JavaScript libraries.

 

var incident = incidentList.filter(function(element) { return element.sys_id === incidentID; })[0];

 

gs.print('---> sys_id1: ' + incident.sys_id + ' - ' + incident.name);

 

This gives the following result:

 

*** Script: ---> sys_id1: 0c43b55ac6112275019abd2b247baf31 - EFOWEB

 

However, you don’t get the actual index location back; if you care about it that is.

 

 

EXAMPLE 2:

 

So, let me demonstrate a way of doing the same thing with the underscore.js library.  Here you can see that there is a way of retrieving the location of the record based on the ID. 

 

gs.include('underscorejs.min');

var index = _.chain(incidentList).pluck("sys_id").indexOf(incidentID).value();

var incident2 = incidentList[index];

 

gs.print('---> sys_id2: ' + incident2.sys_id + ' - ' + incident2.name);

 

Produces the exact same results:

 

*** Script: ---> index2: 5

*** Script: ---> sys_id2: 0c43b55ac6112275019abd2b247baf31 - EFOWEB

 

The only problem I have with this example is in maintenance.  I have to comment it a bit so that it explains what exactly is happening.

 

 

EXAMPLE 3:

 

So finally, if you implement the new ECMA6 polyfill for Array.findIndex you get the same functionality as the underscore.js example, but it is easier to implement (and future-proof; as when ServiceNow finally implements the ECMA6 functionality). 

 

gs.include('JavaScriptExtensions');

var index = incidentList.findIndex(function(element) { return element.sys_id == incidentID; });

var incident3 = incidentList[index];

 

gs.print('---> sys_id3: ' + incident3.sys_id + ' - ' + incident3.name);

 

Produces the exact same results:

 

*** Script: ---> index3: 5

*** Script: ---> sys_id3: 0c43b55ac6112275019abd2b247baf31 - EFOWEB

 

Again, this example would require some commenting just to make it clear to a maintenance coder what is going on.

 

 

OTHER FUTURE EXAMPLES:

 

The following are examples that will work when ECMA6 is implemented in ServiceNow:

 

// ecma 6 --- doesn't work yet ... sniff!

var incident4 = incidentList.map(x => x.says_id).indexOf(incidentID);

 

// ecma 6 --- nor this!  makes me want to weep...

var incident5 = incidentList.findIndex(x => x.sys_id === incidentID);

 

// ecma 6 --- or even this! gad!

var index = incidentList.reduce((i, item, index) => item.sys_id === incidentID ? index : i, -1);

var incident7 = incidentList[index];


So you can see that there are a several ways to retrieve a record from an object array.  Explore this a bit, and you might find other ways (yes, there are more).

 

Some "light" additional reading:

 

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex


Steven Bell

 

accenture logo small.jpg

 

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  ADVANCED

Assumes good knowledge and/or familiarity of scripting in ServiceNow.

____________________________________________________________________________

 

Awhile ago I wrote on how to extend the JavaScript language base inside of ServiceNow with several functions available via ECMA 6 and Mozilla Polyfill definitions.  Well, here is another I found myself needing recently:  The Array.findIndex function.

 

What does this bring to the table?  Well, it allows you to basically do an indexOf on a complex object array.  This allows you to do two things:  (1) bring back the location of the record by giving the findIndex the object property and value, and/or (2) determine if a record exists in an object array given an object property and value.

 

For example:

 

var ID = <<sys_id>>

var array = <<list of objects with a property of ID>>

var index = array.findIndex(function(element) { return element.ID == ID; });

 

This would return the array index of the first match.

 

 

Lab 1.1: Add the Polyfill Script to the JavaScriptExtensions Library

 

1. If you have not already done so then go ahead and work through the following mini-lab article:

 

Mini-Lab: Extending ServiceNow JavaScript Using ECMAScript 6 Polyfills

 

2. Update the JavaScriptExtensions Script Include to contain the new prototype.

 

a. Obtain the Mozilla polyfill code from the following location: link

 

b. Add the code to the JavaScriptExtensions script.

 

Here is the script from the Mozilla article:

 

if (!Array.prototype.findIndex) {
  Array.prototype.findIndex = function(predicate) {
    'use strict';
    if (this == null) {
      throw new TypeError('Array.prototype.findIndex called on null or undefined');
    }
    if (typeof predicate !== 'function') {
      throw new TypeError('predicate must be a function');
    }
    var list = Object(this);
    var length = list.length >>> 0;
    var thisArg = arguments[1];
    var value;

    for (var i = 0; i < length; i++) {
      value = list[i];
      if (predicate.call(thisArg, value, i, list)) {
        return i;
      }
    }
    return -1;
  };
}

 

 

Lab 1.2: Testing the findIndex Functionality

 

1. Create a new Fix Script to test the new array extension

 

a. Name: Array.findIndex Test

b. Description: Test of the Array.polyfill.findIndex functionality.

c. Script:

 

gs.include('JavaScriptExtensions'); 

var incidentList = getIncidentList(10);
var incidentID = '0c43b55ac6112275019abd2b247baf31';

var index = incidentList.findIndex(function(element) { return element.sys_id == incidentID; });
gs.print(index);

// get a list of [limit] number of cmdb ci records
function getIncidentList(limit) {
  var incidentRecords = new GlideRecord('incident');
  incidentRecords.addNotNullQuery('cmdb_ci');
  incidentRecords.addActiveQuery();
  incidentRecords.setLimit(limit);
  incidentRecords.query();

  var cmdbList = [];

  // The following looping code is a GlideRecord inside of a GlideRecord.
  // This is normally a BAD way of doing this, but for the purposes of this test...

  while (incidentRecords.next()) {

    var ciRecords = new GlideRecord('cmdb_ci');
    if (ciRecords.get('sys_id', incidentRecords.getValue('cmdb_ci'))) {
      cmdb_ci = {};
      cmdb_ci.sys_id = ciRecords.getValue('sys_id');
      cmdb_ci.serial_number = ciRecords.getValue('serial_number');
      cmdb_ci.name = ciRecords.getValue('name');
      cmdb_ci.className = ciRecords.getValue('sys_class_name');
      cmdb_ci.install_status = ciRecords.getValue('install_status');
      cmdbList.push(cmdb_ci);
    }

  }

  for (var i=0; i < cmdbList.length; i++) {gs.print(cmdbList[i].sys_id);}  

  return cmdbList;
}

 

When you run the Fix Script you should get something like the following results:

 

 

I ran the first part to get hold of a sys_id to look for, then plugged that into my script to test the findIndex function.

 

Pretty cool, huh?

 

Steven Bell

 

accenture logo small.jpg

 

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE

Assumes some knowledge and/or familiarity of scripting in ServiceNow.

____________________________________________________________________________

 

Normalization of data.  If you are a DBA you know immediately how painful it can be to get rid of duplicates!  I used to cry myself to sleep every night in frustration...well, not really, but I came pretty close.

 

For me, as a ServiceNow Admin and Developer this pain-point centers a lot around Manufacturer and Model.  Let’s say I have a computer made by a fictitious company called: Omni-Widgets.  Omni-Widgets let’s their manufacturing floor personnel put in anything they want into their BIOS for the company name.  For example:

 

Omni-W, Inc.

Omni-Wid

Omni-Wdgts

Omni-Widgets, Inc.

Omni-Widgets Incorporated

 

And so on.  With something like Discovery in place this would end up with all of these generating a record in the core_company table!  Arrrrgh! 

 

With Field Normalization it is possible to reduce these down to a single value of my company’s choosing.

 

 

The same pain-point impacts with Model as well. 

 

ServiceNow provides a plug-in that can help alleviate this pain.  The Field Normalization plug-in.  The application provides a mechanism that can rectify existing data, AND intercept and change data about to be written down.  Once I set up the rules in the application I never have to worry about this sort of name duplication again.  Nice!

 

As a developer I then do not have to worry about handling all of the various names, but can then depend on a single value being there!  Bliss!

 

So code like the following would actually be reliable.  If multiple values were present then dependability would be out the window.  I couldn’t rely on what value would be returned for sys_id!



var manufacturer = 'Omni-Widgets';
var sys_id = getManufacturerIDByName(manufacturer);
gs.print(sys_id);
function getManufacturerIDByName(manufacturer) {
    var manufacturerRecord = new GlideRecord('core_company');
    manufacturerRecord.get('name', manufacturer);
    return manufacturerRecord.sys_id;
}

 

The same would hold true for model.  If normalized it is simplicity itself to pull back a single value.  If un-normalized I would have to figure out a way to deal with the myriad variations that could be present!

 

var modelName = ‘x27549’;
var model_id = this.getModelIDByName(modelName);
gs.print(model_id);
function getModelIDByName(modelName) {
    var modelRecord = new GlideRecord('cmdb_model');
    modelRecord.get('model_number', modelName);
    return modelRecord.sys_id;
}

 

For a developer the consistency of data is all important.  It reduces the need for extra code to deal with exceptions.  Thus reducing the code footprint, and the extra maintenance.  Coding for exceptions with data can also introduce unreliability.  For Asset and CMDB management it reduces the need for complex reports that might miss certain CIs because the make or model had an exception that wasn’t taken into account.  Keeping it simple with normalization just plain makes life easier for all around!

 

Suggested reading:

Field Normalization - ServiceNow Wiki

Normalizing a Field - ServiceNow Wiki

Normalizing and Transforming Field Values - ServiceNow Wiki

Helsinki and the Normalization Plugin

 

Steven Bell

 

accenture logo small.jpg

 

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE

Assumes some knowledge and/or familiarity of scripting in ServiceNow.

____________________________________________________________________________

 

A technique I utilize myself for turning run-time server-side debugging on and off; after a push to production, is to create a System Property for the purpose.  The need for this is two-fold:  First to put control of debug messages into the hands of the ServiceNow Admin (who, after all, will be saddled with looking after what I wrote), and Second to reduce the amount of messaging ending up in the log, unless it is absolutely necessary.

 

The nice part about this technique is that it is usable with all Server-Side coding:  Business Rules, Script Includes, Workflows, Scheduled Jobs, Script Actions, UI Actions (server-side), Transform Scripts, etc.  I find it gives a certain engineering polish to my projects!

 

To implement this is really a simple matter. 

  1. Create a system property
  2. Pull in the property value in my server-side script
  3. Utilize the value


To create the system property:

  1. Navigate to sys_properties.list. The System Properties list view will be displayed.
  2. Click the New button to display the new System Property form.
  3. I fill in my form thusly:
    1. Name: serverside.debug
    2. Description: Server side debug control
    3. Choices: true|false
    4. Type: true | false
    5. Value: true
    6. Submit to save my work.

 

 

Now I can use it anywhere I write server-side code!

 

For example, I could use this property to turn debugging on or off in a Business Rule:

 

(function executeRule(current, previous /*null when async*/) {  
  var debug = gs.getProperty('serverside.debug', false);
  if (debug) {
    gs.info('---> debugging is on!');
  }  
})(current, previous);

 

Since this is a true/false property it is pulled into the code by getProperty as a boolean; making things simple.

 

You can surround all of your debugging code (logging or otherwise) with a simple “if” statement, and turn it off prior to going to production (or even after if you want to watch things there for a bit after implementation).  This provides an excellent way to do after-push production debugging if needed.

 

One suggestion here is to use this with restraint.  You might consider doing this on a project level as, after creating this sort of thing for several releases, turning it on may fill up the log with a lot of unneeded debugging noise.  Just saying.  :-)

 

Steven Bell

 

accenture logo small.jpg

 

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE

Assumes some knowledge and/or familiarity of scripting in ServiceNow and Windows.

____________________________________________________________________________

 

I wanted a way to retrieve the current user’s view.  So, I did my usual digging:

 

  • Looked for an example in: Script Includes, Business Rules, Client Scripts, etc.
  • Looked on the ServiceNow Community
  • Looked on the Web
  • Desperately asked someone at Accenture for help!

 

Well maybe not the last one as this wasn’t critical!  :-)

 

Interestingly I found a couple of ways that ServiceNow’s developers had adopted, and I found them in the Business Rules!

 

I thought I would pass these along; in case you were wondering what they were.

 

I found two.

 

Method 1:  GlideTransaction:

 

What info there is on this function (next to nothing):

GlideTransaction API reference

 

     var view = GlideTransaction.get().getRequestParameter("sysparm_view");



Method 2: getGlideURI

 

What info there is on this function (almost nothing):

Scoped GlideURI API Reference - ServiceNow Wiki

What is "gs.action"?What is "gs.action"?

 

     var view2 = gs.action.getGlideURI().getMap().get('sysparm_view');



Both of these will return a null if it is the Default view, otherwise the string name of the view.

 

So I created a Business Rule to show the actual operation.

 

Name: View Check

Table: Incident

Active: Checked

Advanced: Checked

When: Before

Update: Checked

Script:

 

(function executeRule(current, previous /*null when async*/) {
  
  try {
    var view = GlideTransaction.get().getRequestParameter("sysparm_view");
    view = global.JSUtil.nil(view) ? 'default' : view;
    gs.log('---> view: ' + view);
  }
  catch (err) {
    gs.log('---> ERROR: ' + err);
  }
  
  try {
    var view2 = gs.action.getGlideURI().getMap().get('sysparm_view');
    view2 = global.JSUtil.nil(view2) ? 'default' : view2;
    gs.log('---> view2: ' + view2);
  }
  catch (err) {
    gs.log('---> ERROR: ' + err);
  }
  
})(current, previous);

 

To test I simply went into an open Incident, and updated the status.

 

My initial results in the System Log were:

 

 

So the results were both null.

 

I then changed my Incident form view to Self Service:

 

 

And got the following results in the system log:

 

 

There you go!  Two different methods for retrieving the User’s current view.  I would be interested if you find any others.

 

For extra interesting reading check this out:

 

Navigating by URL - ServiceNow Wiki

 

Steven Bell

 

accenture logo small.jpg

 

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

at-1020063_640.jpg

In my first part, I talked about the background of an application we are building. With this post, I want to get into the specifics of the first big problem I tackled.

 

In my previous GTD implementation, I used Evernote as the main tool. One of the features I got for free with that was the ability to receive email from arbitrary email addresses. Evernote gives you a private email address that you can use to forward email to the system, where it will be converted into a note. This is exceedingly helpful in a GTD implementation because for most information workers, a lot of the actions you’ll be tackling on a daily basis originate as emails.

 

This first thing I ran across in my design is that I want to be able to forward emails to my ServiceNow instance from multiple originating email accounts and have them all associated with the same user. This means that the out-of-the-box behavior cannot be relied on. By default, an Inbound Email Action will create a record owned by the same user whose email matches in the User table (sys_user). I want to do almost the opposite, I want an email address that will create a record for a user no matter what account sent the email.

 

This sounds like a job for the SMTP addressing loophole. When you send an email address, for most modern email systems addressing to <username>+<arbitrary_text>@domain.com will deliver to the account of <username>. I use this frequently on my personal Gmail account where the arbitrary text assigns the email to a folder. This trick absolutely works with the default inbox that ServiceNow uses. Thus you can send email to <instancename>+<text>@service-now.com and process that email on your instance. Now we have something to work with! We can get emails delivered to the instance with additional information about how we want them processed right there in the address. Good start!

 

Given that situation is true, I created a table to hold Email Tokens. This is a very simple table that contains only a string field (the token) and a reference to a user. The basic strategy here is that when an email is received, in order to find out if it is one of these emails sent to the Action Inbox, we will scrutinize the email token. If we find an active email token in the “To” address, then it not only is one of our Action emails to process but it will be linked to the user that owns that email token.

 

In our application we have a Script Include called DoNowUtil in which we put most (or hopefully all) of the hairy logic. I created a pair of functions called getGTDUser(emailAddress) and hasGTDUser(emailAddress). The former will take in a given email address. If the address has a token and that token has an entry in the table, it will return the sys_id of the user associated with it. The hasGTDUser(emailAddress) does similar work but returns true or false. Now that we have this, we can begin to do some work creating the Inbound Email Action.

 

Screen Shot 2016-08-26 at 10.11.33.png

Screen Shot 2016-08-26 at 10.08.48.png

Screen Shot 2016-08-26 at 10.18.36.png

I created a new Inbound Email Action against the Action table in our app (note we have two things named “action” in some way). I set the order to 99 because I wanted it to run before the default built-in email actions. I left the type as “None” because I want this to work on either new or forwarded emails. The condition field on whether to run is now pretty simple:

 

(new DoNowUtil()).hasGTDUser(email.recipients)

 

If that is true, this will run. Inside the script (under the “Actions” tab for extra confusion and naming overload) I create a new DoNow Action record and set the opened_by and assigned_to fields to the user found via the email token. I do some parsing of the subject to look for context and priority indicators (more on that in a future post), I set the short_description to a sanitized version of subject line. Originally we were putting the full email.body_text into the description field (remember that our Action record extends Task). We decided that we wanted to use that field for arbitrary information and that it was a tossup if the body of the email was actually valuable context. Instead, we created a more_information HTML field so that we could both maintain the incoming information from the email but also edit a description if so desired.

 

This strategy has worked pretty well so far. I actually do use this in the production version of our DoNow application. This allows me to have a secret email address to which I can forward emails from my various email accounts, personal and professional. It is pretty close to a clone of the Evernote functionality or a “Send to Kindle” type situation. In practice, I have not run across a problem either sending or receiving emails or having them create my Actions from these emails.

 

Future Work:

 

The original implementation of the logic in getGTDUser(emailAddress) was suboptimal. It looped over the email token table and did a substring look of each token against the email address. If the table was large, that would be grossly expensive. I recently improved it to parse the token out of the email address and then query for that one record. I’m including a screenshot of the code of the improved version.

 

There is also a big flaw in the current handling of the tokens. Originally my plan was that one would create and delete the tokens at will, and that would be the totality of managing them. However the use case for deleting them is that some rogue process somewhere has discovered the email address with the token and is sending bogus emails creating bogus actions. In that case, deleting the token would just make hasGTDUser(emailAddress) return false. That means that all these bogus emails will fall through to whatever the default Inbound Email Actions are, probably creating an Incident for each one. What is really needed is at least an active column on the table. Even more thorough would be the ability to blacklist and whitelist specific emails from sending. This is in the backlog for the future but not a high priority at this moment.

 

Summary:

 

This has turned out to be an interesting exercise in creating a new strategy for accepting emails inside of ServiceNow. It is not a common use case that an application needs to route emails from multiple sending email addresses to a single user on the instance but if you have that use case, this is a way to accomplish that.

 

Series so far:

Building an Application: Part 1, Setup and Background

Building an Application: Part 2, Using Inbound Email Tokens

Building an Application: Part 3, Adding Service Portal Widgets

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

We have been hard at work developing tons of new Widget functionality, and as some of you noticed in the recent “Gothenburg Theme” video, that we have developed the shopping cart functionality that has been missing from Service Portal. The good news is that we have now released this as an update set that can be applied to any portal.

The cart includes the following functionality:

  • Multiple quantity support for both “Buy Now” and “Add to Cart”
  • Cart preview dropdown in the theme header
  • Customizable URL’s for “continue shopping” and “successful order”
  • Ability to save and load a previously saved cart
  • Order on behalf of another user
  • Generate “Deliver to” from requested for users location
  • Many “Instance Options” to customize the widgets
  • Mobile cart icon when viewed on mobile devices

 

Check out some of the screen captures below. If anyone is interested, please contact us through http://serviceportal.io for more information and pricing.

Screen Shot 2016-08-24 at 4.41.19 PM.png

Screen Shot 2016-08-24 at 4.24.52 PM.pngScreen Shot 2016-08-25 at 2.38.07 PM.pngScreen Shot 2016-08-24 at 4.26.35 PM.pngScreen Shot 2016-08-24 at 4.25.14 PM.png

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

I promised a community user that I would help her with how to get information from a call record to the catalog item that they wanted to create.

 

There is a couple of ways to do this and what I personally prefer and by experience worked best is to take advantage of GlideAjax.

You could just put the info in the URL or use clientData, but there are limitations and possible user mishaps with those that can lead to errors.

 

Now, scenario would be this. From a call, I want to create a catalog item and populate the calls description into a variable on the item.

 

The short version would be like this:

  • Put the sys_id of the call in the url that opens up the catalog item
  • Use a GlideAjax in a catalog client script to get call description
  • Use the result from GlideAjax to populate whatever variable you want.

 

Lets fix the URL:

Now this is all done in a OOB Business rule called "CallTypeChanged to Request". On line 8(if you haven't done any modifications earlier, you can find:

var url = 'com.glideapp.servicecatalog_cat_item_view.do?sysparm_id=' + reqItem + '&sysparm_user=' + reqFor + '&sysparm_location=' + location + '&sysparm_comments=' + comments;

 

This gives you to url for the catalog item and it also puts a few sysparm for you to pick up later as well. Since all of them points at the call record and you only need the sys_id of the call, you can just have a sysparm_call_id like this:

 

var url = 'com.glideapp.servicecatalog_cat_item_view.do?sysparm_id=' + reqItem + '&sysparm_call_sysid =' + current.sys_id;

 

This will send with the call id and then we can use that with a GlideAjax to get the whole call record and use what ever data we need.

 

Something like this:

 

 

 

Now we have the made so all parameters that we want comes along with the url, if you try it out. You will get a url like this when you see the item

 

 

when you are at this screen you can press Shift+Ctrl+Alt+J to get the JavaScript Executor. Not sure what to press on a Mac

This little popup will let you run client code, but you wont have access to the current object thou. You can also choose "browse vars" and see what variables you got available to you.

 

 

If you like, you can test here and see if you can get the sysparm_value. Paste in the following code in the executor:

 

var callSysID = getParmVal('sysparm_call_sysid');


alert(callSysID);


function getParmVal(name){
  var url = document.URL.parseQuery();
  if(url[name]){
  return decodeURI(url[name]);
  }
  else{
  return;
  }
}





 

Press "Run my code" and you should get an alert like this:

 

 

So now we know that we got all the info we are at the item. Lets start doing the client script:

 

Making of the onLoad Catalog Client Script:

 

For this example I added a variable here that i named "callDescription" and this is where I want the magic to happen.

 

So, back to the client script. Here is the code that is going in there and remember this is a onLoad script.

 

 

And the code:

 

function onLoad() {

  var callSysID = getParmVal('sysparm_call_sysid');

  var ga = new GlideAjax('getInfoFromCall');
  ga.addParam('sysparm_name','getDescription');
  ga.addParam('sysparm_callSysID', callSysID);
  ga.getXML(handleResponse);

  function handleResponse(response){
  var answer = response.responseXML.documentElement.getAttribute("answer");
  g_form.setValue('callDescription',answer);
  }

  function getParmVal(name){
  var url = document.URL.parseQuery();
  if(url[name]){
  return decodeURI(url[name]);
  }
  else{
  return;
  }
  }
}

 

 

Lets take a look at the Script Include that is used:

 

 

Code:

var getInfoFromCall = Class.create();
getInfoFromCall.prototype = Object.extendsObject(AbstractAjaxProcessor, {

  getDescription: function() {
//Get the sys_ID for the call
  var callSysID = this.getParameter('sysparm_callSysID');

  var gr = new GlideRecord('new_call');
  gr.get(callSysID);

  var description = gr.getDisplayValue('description');
  return description;
  },

  type: 'getInfoFromCall'
});


 

That's it, not need to do more than this to have a functional GlideAjax working. In other cases a display Business rule might save the day, but sadly they don't work in the catalog. Then I would use the display rule to put stuff in the scratchpad and then fetch them with the client script. Then we don't need to do call the server with the GlideAjax to get the info.

 

This is a very simple way, but good enough if you just want a single field on the call record.

 

If you want more fields from the call, I would recommend reading this post I wrote earlier: Lets make GlideAjax a little more dynamic

//Göran

ServiceNow Witch Doctor and MVP
-----------------------------------
For all my blog posts: http://bit.ly/2fCzj1g

In this blog, we will see how we can use $rootScope to share data between widgets via a simple example. Every angular application has a single root scope. All other scopes are descendant scopes of the root scope. Theoretically, if we assign something to a root scope variable, we should be able to access this value inside other widgets as well.

 

In my previous blog How to communicate between widgets in Service Portal , we created a portal, where we used $rootScope.broadcast to hide/show widgets. Today we will be using the same example, but this time, we will use a root scope variable to achieve this.

 

So, I am going to start by changing HTML and Client Controller of Widget 1 we created in  Step 3.

 

 

Step 1: Edit the widget we created in Step 3 (Menu Pills widget)

 

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

 

Please use the below snippets to create your widget

 

Client Controller:

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

  /* widget controller */

  var c = this;

 

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

 

  //Let's create an array of objects in root scope here, This array should be available for use inside any of the widgets in this app.

  //We will try to access this again later inside other widget

  $rootScope.simpSons = [

       {

            "name":"Homer Simpson",

            "title" : "Nuclear Safety Inspector"

       },

       {

            "name":"Bart Simpson",

            "title" : "Troublemaker"

       }

  ];

  

 

  $scope.selectPill = function(selection){

       //When the pills are clicked, we change the root scope variable.  

       $rootScope.selectedPill = selection;

  };

}

 

HTML:

<div>

<!-- your widget template -->

  <div class=" pill-background row">

    <div class="each-pill" ng-class="{'active':$root.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':$root.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':$root.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 above, we can access $rootScope variable in HTML using $root.

 

 

 

 

Now let's edit the other three widgets we created in Step 4, 5 and 6

 

Step 2:  Edit Widget we created in Step 4 (All requests widget)

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

HTML(Only first line edited, everything else is same):

<div ng-if="$root.selectedPill == '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 the root scope variable selectedPill, these widgets are shown/hidden.

 

Client Controller:

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

  /* widget controller */

  var c = this;

}

 

 

Step 3: Edit Widget we created in Step 5 (Create request widget)

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

 

 

HTML: (Only first line edited, everything else is same)

<div ng-if="$root.selectedPill == '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,$timeout) {

  /* widget controller */

  var c = this;

}

 

 

Step 4: Edit Widget created in Step 6 (Contact widget)

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

 

 

HTML(Only first line edited, everything else is same):

<div ng-if="$root.selectedPill == '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;

 

  //Let's try to log the array of objects we created in step 1 here.

  console.log(JSON.stringify($rootScope.simpSons));  

 

}

 

 

Now that we have edited our widgets to make use of root scope variable. Go ahead and test it out. It should work as before.

 

rootscope.gif

 

And if you check your browser console. You can see the  $rootScope.simpSons we logged inside "Contact widget".

 

Screen Shot 2016-08-19 at 4.41.14 PM.png

This is just a simple demonstration of how we can use $rootScope to share data between widgets.

 

Thanks,

Sush

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  ADVANCED

Assumes good knowledge of scripting in ServiceNow (Script Includes, Scripts-Background, Fix Scripts, Debugging Techniques, JavaScript Language, GlideSystem, GlideRecords).

____________________________________________________________________________

 

From time-to-time I find that I need to be able to sort object arrays.  I’m always having to go to the web and look for a solution.  Not always is that solution satisfactory.  Then one day I found this REALLY great article on sorting object arrays using JavaScript.  It had some ideas in it that would be easily implemented in ServiceNow to provide the needed functionality!

 

http://stackoverflow.com/questions/1129216/sort-array-of-objects-by-string-property-value-in-javascript

 

So here are three methods for sorting an object array!

 

NOTE: All of the below methods can be tested using Scripts - Background (where I played with them), or Fix Scripts (which would be overkill in this case).

 

 

METHOD 1:  Create a Script Include to contain a prototype sort function that would add to the JavaScript language when called. 

 

This would be the great beginning to a function library if you don't already have one.

 

NOTE: I am not a big fan of single-letter variables (hate them actually), but this code comes directly from the referenced article so I let it be...for now.

 

Name: DynamicObjectSort

Script:

 

Array.prototype.sortBy = function(p) {
  return this.slice(0).sort(function(a,b) {
    return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0;
  });
};

 

 

Then when you write your code you could include it thus:

 

gs.include('DynamicObjectSort');

var peopleList = [ 
  {first_name: 'Barny', last_name: 'Rubble'},
  {first_name: 'George', last_name: 'Jetson'},
  {first_name: 'Wilma', last_name: 'Flintstone'},
  {first_name: 'Adam', last_name: 'Ant'}
];

peopleList = peopleList.sortBy('last_name');

for (var i=0; i < peopleList.length; i++) {
    gs.print('---> ' + peopleList[i].first_name+ '\t - ' + peopleList[i].last_name);
}

 

Results in the following output:

 

[0:00:00.005] Script completed in scope global: script


*** Script: ---> Adam      - Ant
*** Script: ---> Wilma      - Flintstone
*** Script: ---> George      - Jetson
*** Script: ---> Barny      - Rubble

 

So, that works nicely.  Now onto a couple of other methods.

 

 

METHOD 2: Utilize the existing JavaScript Array.sort method with a an element sort function.


This one was cool, and could be useful in some cases where you wouldn’t want to bring in the prototype:  It rubs my strongly typed (non-global) variable fur the wrong way, but hey!  This IS JavaScript where everything is global, right?!

 

var peopleList = [ 
  {first_name: 'Barny', last_name: 'Rubble'},
  {first_name: 'George', last_name: 'Jetson'},
  {first_name: 'Wilma', last_name: 'Flintstone'},
  {first_name: 'Adam', last_name: 'Ant'}
];

peopleList.sort(function(a, b){
  return a.last_name == b.last_name ? 0 : +(a.last_name > b.last_name) || -1;
});

for (var i=0; i < peopleList.length; i++) {
    gs.print('---> ' + peopleList[i].first_name+ '\t - ' + peopleList[i].last_name);
}


You will end up with the same results as Method 1.

 

 

METHOD 3:  Use the Underscore.js library

 

Some time ago I wrote on how to incorporate the underscore.js library into the ServiceNow JavaScript language base.  Underscore has a built-in function: .sortBy(...) which does the trick nicely!

 

See: Mini-Lab: Adding Underscore.js Into ServiceNow

 

gs.include('underscorejs.min');

var peopleList = [ 
  {first_name: 'Barny', last_name: 'Rubble'},
  {first_name: 'George', last_name: 'Jetson'},
  {first_name: 'Wilma', last_name: 'Flintstone'},
  {first_name: 'Adam', last_name: 'Ant'}
];

var sortedPeeps = _.sortBy(peopleList, 'last_name');

for (var i=0; i < sortedPeeps.length; i++) {
    gs.print('---> ' + sortedPeeps[i].first_name+ '\t - ' + sortedPeeps[i].last_name);
}

 

Again, you will end up with the same results as Method 1 & 2.


As you can see, there are multiple methods for implementing a sort.  I really like the last one best as the Underscore.js library has a LOT of additional functionality for both Server and Client side coding.

Obviously my list is not all-exhaustive, but rather gives you an idea for some of the ways in which useful new functions can be added to the Scripting code base!

 

Steven Bell

 

accenture logo small.jpg

 

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

NOTE: MY POSTINGS REFLECT MY OWN VIEWS AND DO NOT NECESSARILY REPRESENT THE VIEWS OF MY EMPLOYER, ACCENTURE.

 

DIFFICULTY LEVEL:  INTERMEDIATE

Assumes some knowledge and/or familiarity of scripting in ServiceNow and Windows.

____________________________________________________________________________

 

Something, as a developer, that I run into quite a bit is the need to remove duplicates from an Object List by a key or a value such as a sys_id.  In this article I will describe how to do this with ServiceNow Scripting and JavaScript.

 

This a very useful type of functionality when dealing with any size object array.  In the following example I will build a list (array) of cmdb_ci objects, build a key for each object in the list, and then use a function to remove all of the duplicates.  The key is simply an attempt to build a unique identifier.  This identifier can be a sys_id, or some combination of values that will allow the code to identify a unique record.

 

var incidentRecords = new GlideRecord('incident');
incidentRecords.addNotNullQuery('cmdb_ci');
incidentRecords.addActiveQuery();
incidentRecords.query();

var cmdbList = [];

// The following looping code is a GlideRecord inside of a GlideRecord.
// This is normally a BAD way of doing this, but I want
// to make SURE I have duplicates in the Object List!

while (incidentRecords.next()) {

    var ciRecords = new GlideRecord('cmdb_ci');
    if (ciRecords.get('sys_id', incidentRecords.getValue('cmdb_ci'))) {
        cmdb_ci = {};
        cmdb_ci.serial_number = ciRecords.getValue('serial_number');
        cmdb_ci.name = ciRecords.getValue('name');
        cmdb_ci.className = ciRecords.getValue('sys_class_name');
        cmdb_ci.install_status = ciRecords.getValue('install_status');
        cmdb_ci.key = cmdb_ci.serial_number + ':' + cmdb_ci.name + ':' + cmdb_ci.className;
        cmdbList.push(cmdb_ci);
    }

}

gs.print('BEFORE: ' + cmdbList.length);
for (var i=0; i < cmdbList.length; i++) {gs.print(cmdbList[i].key);}

cmdbList = uniqueObjectList(cmdbList);

gs.print('AFTER: ' + cmdbList.length);
for (var i=0; i < cmdbList.length; i++) {gs.print(cmdbList[i].key);}



function uniqueObjectList (dedupList) {
    for (var i = 0; i < dedupList.length; i++) {
        for (var j = i + 1; j < dedupList.length; j++) {
            if (dedupList[j].key == dedupList[i].key) {
                dedupList.splice(j, 1);
                --j;
            }
        }
    }
    return dedupList;
}

 

 

 

Results:

 

[0:00:00.043] Script completed in scope global: script


 

*** Script: BEFORE: 17

*** Script: null:FileServerFloor2:cmdb_ci_server

*** Script: null:Saints and Sinners Bingo:cmdb_ci_spkg

*** Script: K3CD321:IBM-T33-DLG:cmdb_ci_computer

*** Script: null:Windows XP Hotfix (SP2) Q817606:cmdb_ci_spkg

*** Script: null:WeatherBug:cmdb_ci_spkg

*** Script: null:EFOWEB:cmdb_ci_computer

*** Script: null:Access IBM:cmdb_ci_spkg

*** Script: null:WEBSERVER:cmdb_ci_computer

*** Script: null:Sales Force Automation:cmdb_ci_service

*** Script: L3BB911:*FREEZ-IBM:cmdb_ci_computer

*** Script: null:Sales Force Automation:cmdb_ci_service

*** Script: cy320-4AA1-15a:nyc rac nas200:cmdb_ci_msd

*** Script: null:EXCH-SD-05:cmdb_ci_email_server

*** Script: null:SAP Controlling:cmdb_ci_service

*** Script: null:SAP Human Resources:cmdb_ci_service

*** Script: null:SAP Sales and Distribution:cmdb_ci_service

*** Script: null:Kastle:cmdb_ci_dns_name

 

*** Script: AFTER: 16

*** Script: null:FileServerFloor2:cmdb_ci_server

*** Script: null:Saints and Sinners Bingo:cmdb_ci_spkg

*** Script: K3CD321:IBM-T33-DLG:cmdb_ci_computer

*** Script: null:Windows XP Hotfix (SP2) Q817606:cmdb_ci_spkg

*** Script: null:WeatherBug:cmdb_ci_spkg

*** Script: null:EFOWEB:cmdb_ci_computer

*** Script: null:Access IBM:cmdb_ci_spkg

*** Script: null:WEBSERVER:cmdb_ci_computer

*** Script: null:Sales Force Automation:cmdb_ci_service

*** Script: L3BB911:*FREEZ-IBM:cmdb_ci_computer

*** Script: cy320-4AA1-15a:nyc rac nas200:cmdb_ci_msd

*** Script: null:EXCH-SD-05:cmdb_ci_email_server

*** Script: null:SAP Controlling:cmdb_ci_service

*** Script: null:SAP Human Resources:cmdb_ci_service

*** Script: null:SAP Sales and Distribution:cmdb_ci_service

*** Script: null:Kastle:cmdb_ci_dns_name

 

 

If you look careful you will see that there is a duplicate of the following entry:

 

Sales Force Automation:cmdb_ci_service

 

and it has been removed.

 

Okay, so let’s refactor our new uniqueObjectList function a bit.

 

If we rewrite the function to take a field value we can have the flexibility of actually specifying what the key field will be in our dedup function:

 

 

gs.print('BEFORE: ' + cmdbList.length);
for (var i=0; i < cmdbList.length; i++) {gs.print(cmdbList[i].key);}

cmdbList = uniqueObjectList(cmdbList, 'key');

gs.print('AFTER: ' + cmdbList.length);
for (var i=0; i < cmdbList.length; i++) {gs.print(cmdbList[i].key);}

function uniqueObjectList (dedupList, field) {
    for (var i = 0; i < dedupList.length; i++) {
        for (var j = i + 1; j < dedupList.length; j++) {
            if (dedupList[j][field] == dedupList[i][field]) {
                dedupList.splice(j, 1);
                --j;
            }
        }
    }
    return dedupList;
}

 

The results should be exactly the same.

 

You might want to put this into a Script Include function library (utils) of some sort for ease of use, and to minimize any maintenance.  Just a thought.

 

Despite the disdain I run into from other programmers working with “real” languages (i.e. C#, Java), I feel JavaScript can be just as powerful in its own way!  And lest you sniff at that comment, let me point out that I have done C#/.Net development since .Net 0.9 Beta, and I took a class on OOP from Bjarne Stroustrup himself when he had just started touring to talk about C++!  Go Ada!  Each language has it's uses!  Whoops, got off into the weeds there!

 

Steven Bell

 

accenture logo small.jpg

 

For a list of all of my articles:  Community Code Snippets: Articles List to Date

 

Please Share, Like, Bookmark, Mark Helpful, or Comment this blog if you've found it helpful or insightful.

 

Also, if you are not already, I would like to encourage you to become a member of our blog!

One of the things dave.slusher and I do every day is read/respond to the feedback you submit on developer.servicenow.com. We recently noticed that a number of developers have been running into an odd dictionary issue. This issue could occur in any app, but I'll focus on fixing it in the Marketing Events application we build as part of the Building a sample ServiceNow application course.

 

The Problem

 

Applies to: Helsinki

 

If we:

 

  1. Create a table that extends task
  2. Change our minds and delete the table we just created
  3. Change our minds again and create a table with the same name

 

This happens:

 

blog_aug15_img1.png

 

For the benefit of search engines:

 

Error 1: Invalid 'Table' selected on the Module record. The 'Task' table is in application 'Global', but the current application is 'Table Extension Issue'.

Error 2: Invalid 'Table' selected on the Dictionary Entry record. The 'Task' table is in application 'Global', but the current application is 'Table Extension Issue'. The 'Table' field can only select 'Global' tables with read access enabled.

 

In addition to these errors, the Label is forcefully reverted to Task, and it it initially seems like we can't create a table with this name ever again.

 

But There's a Workaround!


The good news is that we discovered a workaround. It's a bit weird, but should let you create the table with the name you want, and get you back onto a path of productivity.

 

  1. If you haven't already, delete the bad table
  2. Create a new table with exactly the same name, but do not extend task this time
  3. Delete the table you just created
  4. Now, try creating the table as you originally intended, extending task

 

Steps 2 and 3 are enough to flush out whatever was interfering before, and we're back on track.

 

We've logged an INT to track this, but hopefully this will help anyone currently stuck on the error.

 

Let me know in the comments if this workaround does or doesn't work for you!

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

Death to email! Just kidding. I understand email is still an important part of our day-to-day office work. Some teams just prefer email while the rest of us might prefer to use Connect. If you are like me, a clean Outlook inbox is the goal. I have filters set up for specific folders where I prioritize what needs to be addressed and when. But there are some notifications I get in my inbox that create more clutter than provide actionable information.

 

For example, I don't need to get email notifications for every response to calendar invitations for change requests. The Change Request application can send pending changes to a user's Microsoft Outlook Calendar automatically by email in the base system. This notifies recipients of the change request schedule and exports the schedule to Microsoft Outlook formatted with iCal. There are certain business needs that require an individual to see and view all of those responses, but others may want to turn them off.

CHG NOTIF TRUE.jpg

 

Depending on business requirements, users may not want the calendar invitations for change requests to have the ability to send a response to ServiceNow meeting invitations.

 

Turn off response notifications from Change calendar invites for requests:

    1. Navigate to the Email Templates under Policy.
    2. Open the change.calendar.integration email template.
    3. Change the value for RSVP to FALSE.
Change:To:
ATTENDEE;ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:${to}ATTENDEE;ROLE=REQ-PARTICIPANT;RSVP=FALSE:MAILTO:${to}

 

CHNG NOTIF FALSE.jpgNotice that the arrow to send a response is removed.

 

When the property is TRUE, the received calendar invitation will show an arrow mark on the Accept, Decline, and Tentative buttons. Once clicked, it will give your attendees several options to reply to the sender. When RSVP is set to FALSE, there is no option to respond. Users just need to click to accept or decline the change. That means users who receive the Change calendar request invitation in Outlook will no longer see the ability to send a response and you can save yourself the grief of having to "Mark as read" or create yet another separate filter.

david_allen.jpg

I have been working on a skunkworks project with ctomasi, josh.nerius  and a few other people for months now as a low level background task. One of the downsides in working in an evangelism role is that sometimes you do lots of things to communicate about developing without actually doing any development yourself. In order to change that, we carved out a problem space that all of us were interested in, had an opportunity to improve toolsets and would be something that we ourselves would want to use every day.

 

We opted to use the ServiceNow platform to build an application for the GTD organization method from the mind of the brilliant David Allen. All of us on the project use some implementation of it and none of us love our current tools. This seemed like a rich space to mine for the opportunity to bring us onto a tool we all liked better than our current. I personally was using an Evernote based solution, but OmniFocus and other tools were in play. In fact, no two of us used the same thing.

 

The first thing we did was work out a list of use cases for the application. The upside to implementing GTD is that much of the design is already done. There is no discussion about the priorities, for example, or that there is a review phase. It just comes down to the implementation details. All of us threw in use cases important to us to make this a tool capable of being a daily driver. These included:

 

  • Ability to filter down lists to specific contexts
  • Quick entry of new tasks (Ubiquitous capture in GTD terminology)
  • Convert emails into tasks
  • Ability to delegate and track tasks
  • Notifications around changes done by others, upcoming due dates
  • A template system for creating recurring tasks

 

Screen Shot 2016-08-12 at 12.35.30.png

There were many others, this is just an example to give a taste of the types of features that were on our minds. Given that set of use cases, Chuck went off and created an initial cut of the data model and a framework for the basic operation of the system. Because of the timing of the project, we were able to begin on the Helsinki release. This made Service Portal available as an option for some of the user interface, and gave us the ability to use a Github account as the backbone for our changes. We set up instances for our dev and production, and then each of us connected our personal Github accounts to our own development instances. One note of caution, we learned the hard way that when you update from source control, it has the potential to scramble data since it uninstalls and reinstalls. Because of this, we pull to our development/test instance from source control and then promote to production via update sets.

 

All this is background to get us to the subsequent posts, where I will talk about some of the programming challenges we ran into and how we addressed them. Our goal is to make this available to the world eventually. Although we are close, we are not quite at the point where we consider what we have as a releasable V1 product. I will post an update when we make that available. At some point in every post like this, people will ask for a pointer to the GitHub repo. For the same reasons it is not quite releasable yet, I’m going to hold off on publicizing that repository at this moment. However, it isn’t private and there are tools on the internet for the really motivated if you know what I mean.

 

Progress on this application moves in fits and starts because it is no one’s top priority (or even top 5) but sometimes we devote some time to it anyway, frequently over evenings and weekends. I hope that people find value in following along with our progress and our thought patterns. The very first time we did the Live Coding Happy Hour was for this project, in fact. It was a reaction to the slick demos we frequently work out. We wanted to do the opposite because we felt there was also value in showing the struggle, the problems you can bump into and how to work your way out of those. This blog series is from a similar mind set, walking along with how we pursue the project. With any luck, you will take something away that you can use in your own application development work.

 

As always, feel free to leave any comments on this post. This series is for y’all so please let me know what you do and don’t find valuable so I can dial it in as I proceed. Next post: how to deal with incoming email (aka, getting to the fun stuff!)

 

Series so far:

Building an Application: Part 1, Setup and Background

Building an Application: Part 2, Using Inbound Email Tokens

Building an Application: Part 3, Adding Service Portal Widgets

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

Note: This blog post reflects my own personal views and do not necessarily reflect the views of my employer, Accenture.

 

One of the things that I learned early on in working with Service Portal is that while there are relatively few types of artifacts to track, namely portals, pages, widgets, and widget instances there are multiple ways to view most of them. It was a bit confusing for me to be able to get to the right view of the page or widget I wanted at different times, sometimes taking me a few clicks, even when using the sp_config portal.

 

At some point I saw something that changed how I worked with Service Portal pages and widgets: CTRL+Right-Click. If you have the admin role and are looking at a rendered service portal page you can CTRL+Right-Click on any widget (think content rather than blank space) and get a menu with some great options in it.

 

EditorContextMenu.png

 

Let's walk through those here and I'll provide links to the documentation on the ServiceNow docs site and/or service portal documentation on github where applicable.

 

  • Widget Performance - The very first message tells you how long it took for the widget to load, which is a nice little feature.
  • Instance Options - You can specify options when someone adds a widget to their page which can determine how the widget is rendered. Choosing this option gives you an overlay where you can edit the values of the options for this widget instance.
  • Instance in Page Editor - This shows you the widget instance in the page editor view, with the hierarchy of all the containers, rows, columns, and widgets on the page. Clicking on a widget allows you to edit the the widget instance fields.

 

  • Page in Designer - Designer lets you add containers, columns, and widgets to a page as well as setting some styling for those artifacts.
  • Edit Container Background - This one allows you to change some of the styling of the container where the widget you clicked is located.
  • Widget Options Schema - I mentioned a bit earlier that you can edit the values for the the options of the specific widget instance you're clicking on, but this let's you edit the options definitions for the widget. If you wanted to collect an additional data point and use it in the widget definition you would do that here.
  • Widget in Form Modal - This opens the widget form in a modal overlay. You can edit field such as demo data, etc. The widget form itself isn't all that useful and there is a better alternative.
  • Widget in Editor - This is where I have spent most of my time since I've been working in Service Portal and it's an incredibly useful tool for writing and editing widgets.
  • Log to Console: $scope.data - This is one of my favorites. In Service Portal, the $scope.data object is what is used to pass data from the server to the client side controller. If you're having issues with some data coming through, instead of adding console.log messages or even alerts into your controller code, you can just log the $scope.data object to your browsers console and take a look at the data there. You could always add this to every widget and turn it on and off, but it's just a whole lot easier to render a SP page, realize that you didn't return some data you thought you would, then look at the logs without ever leaving the page.
    *Could not find any documentation on this
  • Log to Console: $scope.data - This is much like the previous, except it logs every thing in $scope to the console, so you'll get a lot more noise here, but it can be useful if you're looking for something other than data.
    *Could not find any documentation on this

 

I hope this was a useful explanation of some other feature within the CTRL+Right-Click functionality in Service Portal. I plan on going into a bit more detail on some of these topics in future blogs.

After doing some work in the new Helsinki Service Portal over the last couple of weeks I've found some things I think will be helpful for those who will be starting their Service Portal journeys. Some topics will be fairly short tips and tricks and some will be a little longer, but I think everything could be useful to someone looking to implement Service Portal.

 

Also, I know portaling isn't a real word, but apparently portaled (having a portal according to Merriam-Webster) is, so I'm going to take the liberty of using it.

 

I'm going to start this series off by listing some places where you can find official and unofficial documentation.

 

ServiceNow Official Documentation *Updated with more content

serviceportal.io Blog

CodeCreative.io Blog

ServiceNow Elite Blog

Brad Tilton's Community Blog (I'm not above a little self-promotion)

TechNow Ep. 28 - Service Portal

TechNow Ep. 29 - Service Portal Part 2

HelsinkiHint - My First ServicePortal Widget — CAVUCode

Yansa Design System

Portal Guru Blog Archives - Cerna Solutions

 

**NEW**

Developer Site - Service Portal Course

serviceportal.io github Documentation

serviceportal.io Widget Library

Live Coding Happy Hour - Service Portal Videos (scroll down for the Service Portal section)

 

Know of any other good resources? Let me know and I'll list them!

200x200_podcast-album_art-Final.jpg

 

I work with a lot of really cool people and I want to share that enthusiasm with you. In this episode I'm joined by jason.mckee, a principal developer evangelist at ServiceNow. Jason has been a ServiceNow employee since October 2014, but his history with the company goes back even further. I was talking to Jason at our annual ServiceNow Knowledge conference, in Las Vegas in May 2016 when he started telling me some really great stories and I knew he had to be on the show.

 

Listen
itunes24.png

 

Subscribe using iTunes

 

The developer site recently introduced a feature where instances will hibernate, or "sleep" when they go unused after a couple hours. In this podcast, Bryan Barnard and Jack Nguy discuss what this means for you and how to "wake up" your instance.

volume_icon.png

Listen

 

 

Subscribe

 

to iTunes

 

This episode covers:

  • Personal Development Instances (or PDIs) as the on-ramp to ServiceNow
  • Differences between the developer program and the technology partner program
  • Overview of hibernation
  • Hibernation vs instance reclamation
  • Troubleshooting issues
  • Instance versions and PDIs
  • Plugins and integrations for your PDI

For more information on hibernation and the developer site, see:

 

Your feedback helps us better serve you! Did you find this podcast helpful? Leave us a comment to tell us why or why not.

 

LISTEN BELOW

 

 

To catch clips behind the scenes with our podcast guests, and find out what topics we'll be covering next before they are posted, follow @NOWsupport on Twitter. You can also search Twitter using the hashtag #SNtechbytes for all previous podcasts, video clips and pictures.

Reporting in ServiceNow allows you to measure the success of your efforts across the different functions. You can report on the state of your incidents, views on your knowledge base articles, status on your projects and more. Once you have your reports finalized, there's a good chance you will need to send them to other teams, higher-ups and organization leads. Where one option would be to just give them access to the report, that is not always ideal as some level of employees strictly use email or don't have the training or experience to navigate their way into the reports. This is where being able to share reports via an email notifications would be handy.

 

By default, email notifications will attach a report result as a .PDF. PDFs are great if the recipient of the report is a manager, or a higher-up in that they will not be using the data for anything besides review. For others, there's a need to be able to use the data represented in the email notification for means to a great cause for the business. For example, let's say you are managing the incident creation metrics and the Knowledge Management team, wants to see the data to compare to their own article view metrics. You send them your report results as a PDF and they immediately reply asking for it in an Excel form so that they can copy and paste the values into their own sheets.

 

You can change the default attachment form from PDF to an Excel to make sharing reports through email easier.

 

For example: ${report:reportID:430363d7db4caa00939f3c8caf961900};

 

You attach a report to the notification by adding the following text in the Message HTML  section:

${report:reportID:sys_id_of_report}

 

Changing the default format from PDF to Excel is pretty easy. All you have to do is ":xls" to the report attachment in the Message HTML section. For example: ${report:reportID:sys_id_of_report:xls}

 

Now you can change your report PDF to an Excel and share it with your coworkers to make reports more accessible and easier to work with across the organization.

 

 

Thank you ayca

Filter Blog

By date: By tag: