Skip navigation

dev_team4_hackathon.png

ServiceNow Developer Marketing reporting from the middle of the Arizona desert heat (fortunately,inside an air conditioned building). Dateline: Day #1 of a 2-day hackathon at a large ServiceNow customer in the financial services sector.

 

It's been my pleasure to attend this event and learn and observe the ingenuity and creativity of the ITSM development team at this ServiceNow customer. There are 65 developers in 13 teams competing here in Arizona and in India. It's a very cool setup with the teams situated in multiple locations and rooms around the building, with a quasi-central developer area and canteen where all the food and bev is continuously available. And there's plenty of it to fuel the innovation.

 

This event is a combined effort of the customer and ServiceNow - there are a lot of ingredients, and it takes a team effort to make it a success. Here are a few of those ingredients to give you a flavor of what I'm talking about:

 

  • Venue
  • Food and Beverage
  • Team formation and registration
  • Pre-hack training
  • Technical support before and during - this one utilizes a private ServiceNow Community subspace and is monitored by ServiceNow technical experts, as well as onsite ServiceNow solutions consultants
  • Instance provisioning incl. any special technical requirements
  • Event theme, branding, and promotion internally to the developer community
  • Executive sponsor to help make things happen
  • Judging Criteria, Judges, and Judging (yes, it's important!)
  • Prizes and giveaways

 

dev_team7_hackathon.png

 

 

To see the customer and the ServiceNow account team collaborate on this initiative is awesome. As my colleague Nick noted, Hackathons are a win-win so it only makes sense to collaborate. Hackathons are fundamentally an opportunity for developers at ServiceNow customers to leverage the power of the ServiceNow Platform by creating new enterprise SaaS apps, integrations, and extensions that deliver value to their company in different ways. For example, via new use cases, improving workflows that are broken or inefficient, or porting cumbersome apps from stale or dying platforms to ServiceNow, where they can be reborn as modern, efficient, and effective apps.  It's also a great opportunity for relatively informal, ad-hoc,in-depth,face-to-face discussions between the customer and the account team to discuss and address any business or technical issues and requests they might have on their minds. And it's about community building - a highly social event - the vibe here is really upbeat  - lots of smiling faces, active discussions, and interactions. Always great to see!

 

Here are just a few highlights of the app concepts I had the privilege of seeing in development today during a walk around tour of the development teams, led by the executive sponsor:

 

  • Marketplace for app components that can be discovered and utilized by other developers
  • Major incident auto-voice bridge setup and recording (using Twilio) and Outlook meeting notice using on-call rotation
  • Voice-activated incident creation and bio-metric (thumbprint) re-authentication
  • Genius bar - badge scanner for asset inventory, incident creation, position in queue and wait times

 

The hackathon coding ends on 9/29 (Day #2), followed by pre-judging on 9/30 and Final Judging on 10/1. Nick will be covering that in another blog post so stay tuned for that! Good luck to all the teams, and happy hacking! I can't wait to see who the winner is.

 

Read part 2 of the blog here>>>

 

dev_team9_hackathon.png

Martin Barclay
Director, Product Marketing
App Store and ISVs
ServiceNow
Santa Clara, CA

Sometimes your Client Scripts, UI Policies, and other client side scripting doesn’t work as expected.  This can be especially frustrating when you don’t have access to debugging tools like your browser’s developer console, which allows you to see any errors being thrown and what's triggering them.  Fortunately there are a number of ways to see what’s going on under the hood in the mobile UI.  Listed below are a few ways to see what’s happening in your scripts.


Accessing the Mobile and Tablet UI from a Desktop Browser

Most of the time, an issue occurring on your mobile UI will be reproducible by using the Mobile UI on your desktop browser.  Using the mobile UI on a desktop browser will allow you to find any potential issues you may encounter within the mobile UI.  This is also useful as it allows troubleshooting on a full sized monitor and keyboard, as well as eliminating the need for a physical mobile device when testing.  This is also the simplest way to see any client side errors being generated while using the mobile UI.  Since we’re on a desktop browser, you have access to the developer console and tools that are available in all major browsers.

 

The mobile and tablet UI can be accessed by using the following URLs:

  • Mobile UI:  https://<instance_name>.service-now.com/$m.do
  • Tablet UI:  https://<instance_name>.service-now.com/$tablet.do

 

At some point, you’re going to want your desktop UI back.  The following link will get you back from either mobile or tablet.

Desktop UI:  https://<instance_name>.service-now.com?sysparm_device=doctype

 

While the look and feel won’t be exactly the same, you will be able to interact with the instance in the same way you would on a mobile device.  Right-clicking can be used to simulate a “tap and hold” action.

 

A Note on Impersonation

Impersonation options are only visible in the desktop UI.  This does not mean that will not be able to impersonate, you’ll just need to do so before switching to an alternate UI.  Impersonate as normal, then browse to one of the above links.  Alternately, you can impersonate on a physical mobile device by first using the desktop link, impersonating, and then using one of the mobile links to get back.  While doing this does allow you to see the desktop UI on a mobile device, it’s not recommended that you do so for anything other than impersonation.  The full desktop UI is not designed to run on the more limited mobile hardware, and is not supported.

 

 

The Developer Console

Now that you're in your UI of choice, you can use the developer console to check for any client side errors you may be getting.  You may already be familiar with your browser’s dev console, but if not, here’s how to find it on our supported browsers.

 

Chrome

PC:  Click ≣ → More Tools → Developer tools, or press Control + Shift + I

Mac:  Click ≣ → More Tools → Developer tools, or press Option + ⌘ + J

 

Firefox

PC and Mac:  Click ≣ → Developer → Browser Console


Internet Explorer

Press the F12 key or Click "F12 Developer Tools” under the Tools menu

 

Opera

From the menu, open Tools → Advanced → Developer tools

 

 

Mobile Device Emulation

If you’d prefer a closer match for the appearance of a mobile device for testing.  There are a couple of options that can be used to more closely emulate a mobile device.

 

Chrome (Mac and PC)

 

Chrome has mobile device emulation built into it’s developer tools.  This  This can be accessed using the following steps:

1. Access the developer tools:

     In Windows: Control + Shift + I

     In Mac: Press option + ⌘

2. Press escape, then select the “Emulation” tab.

3. Here there are a number of options, but the “Model” selector will allow you to quickly emulate a number of devices.

 

XCode (Mac Users)

XCode is an IDE suite for OSX and iOS that includes an iOS simulator. Due to an issue with the way XCode handles it’s user string, this option may not work on Eureka Instances, but will perform as expected in Fuji, as well as instances still using the legacy mobile UI.

 

 

Debugging a Physical Mobile Device with a Desktop Computer.

It’s rare, but you at some point encounter an issue that is only reproducible on a physical mobile device.  Fortunately, iPad, iPhone, and Android mobile devices can be connected to a desktop via a USB cable, using the desktop browser’s console tools to troubleshoot issues.

 

iOS

iOS devices can be connected to a PC/Laptop and debugged with the Safari browser.  Unfortunately  was introduced in Safari 6, after Apple stopped updating Safari on Windows.  So, you’ll need a mac using OSX 10.7.4 or later to do this.

  1. Connect your iPhone / iPad to your computer using a USB cable.
  2. Open Safari on your Mac
  3. Browse to Safari → Preferences
  4. Click on the Advanced tab
  5. Check the checkbox next to “Show Develop menu in menu bar"
  6. On your iPhone, open your browser and log onto your instance.
  7. On your Mac, Click on Develop → iPhone → <site name>

This will open the Web Inspector, while will allow you to inspect elements of the page, as well as view console logs.


Android

Android devices can also be connected to PC/Laptop via USB and debugged using the Chrome desktop browser

 

 

The above information should help identify mobile problems you encounter. Finding where specifically an issue is occurring will help speed the process troubleshooting any client side issues you run into while developing your mobile and tablet UI.

Continuing in the vein of workflow looping I found that I wanted a better Turnstile Activity.  One that would take a variable rather than a “hard-coded” integer.   To do this would require modifying the out-of-the-box Turnstile Activity to accept a variable.

 

NOTE: BEST PRACTICE: Try to avoid modifying and saving back down over the top of an existing out-of-the-box anything in ServiceNow. Any future upgrade to the software will be skipped to preserve your changes.  Always make a copy and use that instead.

 

We will be doing the following for this lab:

 

  1. Make a copy of the existing Turnstile Activity
  2. Modifying the Turnstile Activity to accept a variable of our choosing rather than an integer.
  3. Creating a workflow to demonstrate looping using our enhanced Turnstile Activity (we will be using the initialize activity from my previous lab.

 

Assumption: That you have worked with and created ServiceNow Workflows before.  Also, you should probably play with this in your personal instance until you get proficient with the concept.  Just saying.

 

NOTE: We are going to create all of our activities first and wire them all together as the last step.

 

NOTE: I highly recommend creating an Update Set to capture your enhanced Turnstile Activity so that you can port it around if you want.  I have placed everything from this lab out on the share if you want to download it.

 

 

Lab 1.1 – Enhancing the Turnstile Activity

 

1. Navigate to Workflow → Administration Activity Definitions. This will open the Activity Definitions list view.

 

2. Search for the Turnstile Activity, and click on the link to open the activity.  This will open the activity definition form.

    a. Change the name field to be: Turnstile (Enhanced)

    b. Right-Click on the form header to display the context menu.

    c. Click on Insert and Stay.  Make sure you do this or you will be modifying the original!  This will make a copy of the original activity that will be yours to modify.  Otherwise you will "own" the out-of-the-box version, and it WILL be skipped on your next ServiceNow upgrade.  If you do save down over the original simply revert to the previous version and try again.

 

 

    d. In the Script field scroll down and change script to the following:

 

var Turnstile__Enhanced_ActivityHandler = Class.create();
Turnstile__Enhanced_ActivityHandler.prototype = Object.extendsObject(WFActivityHandler, {
      
            initialize: function() {
                        var schpdName = 'iterations_' + activity.activity.sys_id;
                        WFActivityHandler.prototype.initialize.call(this);
                        if (!workflow.scratchpad[schpdName]) {
                                    workflow.scratchpad[schpdName] = 1;
                        }
            },
      
            onExecute: function() {
                        // implement activity definition code here
                        var schpdName = 'iterations_' + activity.activity.sys_id;
                  
                        var iterCountVar = parseInt(workflow.scratchpad[schpdName], 10);
                        var iterAllowedVar = parseInt(this.js(activity.vars.iterations), 10);

                        if (iterCountVar > iterAllowedVar) {
                                    //You've iterated too many times...stop iterating
                                    activity.result = 'cancel';
                              
                        } else {
                                    //Still iterating
                                    iterCountVar ++;
                              
                                    workflow.scratchpad[schpdName] = iterCountVar;
                              
                                    activity.result = 'continue';
                        }
            },
      
            type: 'Turnstile__Enhanced_ActivityHandler'
});
   

 

NOTE:  If you look careful you will notice that the following line has been modified:

 

var iterAllowedVar = parseInt(this.js(activity.vars.iterations), 10);

 

The this.js(…) will take whatever variable is fed to it and convert it to the value contained in that variable.  This will be important when we go to use the new activity in our workflow.

 

    e. Right-click on the form menu to bring up the context menu.  Click save to save the script change.

 

3. Now go to the bottom of our Activity definition.  You will notice that there are no variables or conditions defined.  These were not copied with our activity, and will need to be put back (with a very slight modification).

 

4. On the Activity Variables tab click the New button.

 

5. Fill out the form with the following:

 

    a. Column Name: iterations

    b. Label: Allowed Iterations

    c. Type: String     <- Here is our “slight” modification.  Previously this was Integer

    d. Order: 100

    e. Max length: 100  <- This is important as we will be putting a variable in this field now.

    f. Click the Submit button to save your new variable.

 

6. Click on the Condition Defaults tab.

 

7. Click on the New button.  We will be creating two new defaults.

 

8. Fill out the form with the following:

 

    a. Name: Continue

    b. Activity definition: Turnstile (Enhanced)

    c. Condition: activity.result == 'continue'

    d. Order: 100

    e. Short Description: Continue iterating

    f. Right-click on the form header to bring up the context menu and click Save to save your new Continue condition default.

 

9. Now change the form to the following:

 

    a. Name: Cancel

    b. Activity definition: Turnstile (Enhanced)

    c. Condition: activity.result == 'cancel'

    d. Order: 200

    e. Short Description: You've looped too many times...cancel it...

    f. Right-click on the form header to bring up the context menu and click Insert to save your new Cancel condition default.

 

You have now completed your new enhanced Turnstile Activity!  Congratulations!

 

 

Lab 1.2 – Using the Turnstile Activity

 

1. Navigate to Workflow Workflow Editor.  This will open the workflow editor.

 

    a. In the Workflow Navigation column click on the plus symbol to create a new workflow

 

 

2. Create a new workflow

 

a. Name: Enhanced Turnstile Example

b. Table: Global

c. Take all other defaults

d. Click the Submit button to create the workflow

 

3. On the Workflow Core tab, navigate to Utilities

 

4. Drag out a Run Script Activity on to the desktop.

 

Here we will be initializing our loop.  We will load a specified number of records from the Incident table.  Store these into a scratchpad variable.  The number of times to loop will be the number of records.  We will start with 0 as the first number.  Notice that there is a slight tweak to the count variable from what we did in the last lab.  This is because the Turnstile is acting on the “end” of things and loops one more time than we would like.  No biggy.

 

    a. Name: Initialize

    b. Script:

 

var identifier = context.name + '.' + activity.name;  // introducing just a bit more sophistication!

var incidentRecords = new GlideRecord('incident');
incidentRecords.addQuery('state', '!=', 7); // closed
incidentRecords.setLimit(workflow.inputs.u_numberofrecords); // note that this comes from your inputs
incidentRecords.orderByDesc('number');
incidentRecords.query();

var incident = {};
var incidentList = []; // this will be an array of objects

while (incidentRecords.next()) {
            incident = {};
            incident.sys_id = incidentRecords.sys_id + '';
            incident.number = incidentRecords.number + '';
            incident.assigned_to = incidentRecords.assigned_to.getDisplayValue() + '';
            incident.short_description = incidentRecords.short_description + '';
            incident.state = incidentRecords.state.getDisplayValue() + ''; // notice that we get the label
            incidentList.push(incident);
}

workflow.scratchpad.count = incidentList.length - 1; // the overall count available
workflow.scratchpad.incidentList = incidentList; // the list of incident objects
workflow.scratchpad.counter = 0;  // our counter
workflow.scratchpad.message = '';  // the cumulative message

gs.info('---> [WF:{2}] Total number of records to loop: {0}, test value {1}', workflow.scratchpad.count, workflow.scratchpad.incidentList[workflow.scratchpad.counter].number, identifier);
   

 

c. Click the Submit button to create the Activity

 

5. Drag out a Run Script Activity on to the desktop.

 

a. Name: Compile Message

b. Script:

 

Here we will save up a cumulative message which we will print out each time to the System Log (in the Do Work Script Activity). Note that we are reference the “current” incident record using our object array, and our counter.  This is part of the magic!

 

var incident = workflow.scratchpad.incidentList[workflow.scratchpad.counter];

workflow.scratchpad.message += '[' + workflow.scratchpad.counter + ']' 
            + ' Number: ' + incident.number 
            + ' - ' + incident.short_description 
            + ' - ' + incident.assigned_to
            + ' - ' + incident.state
            + '\n';
   

 

c. Click the Submit button to create the Activity

 

6. On the Workflow Core tab, navigate to Timers

 

7. Drag out a Timer Activity.

 

     Here we will wait one second so that the logs are written down in order.  Otherwise there is no need for this activity.

 

a. Name: Wait a Sec

b. Timer Based On: A user specified duration

c. Duration: 1 seconds

d. Click on the Submit button to create the Activity

 

8. Copy the Wait a Sec Activity (we will need two for our workflow).

 

9. On the Workflow Core tab, navigate to Utilities

 

10. Drag out a Script Activity on to the desktop.

 

a. Name: Do Work

b. Script:

 

var identifier = context.name + '.' + activity.name;  

gs.info('---> [WF:{2}]\nLoop Count: {0} - Message so far:\n{1}', workflow.scratchpad.counter, workflow.scratchpad.message, identifier);  
   

 

11. On the Workflow Core tab, navigate to Utilities.  You will now see your new Turnstile (Enhanced) activity present in the list!

 

12. Drag out a Turnstile (Enhanced) Activity on to the desktop.

 

We can now enter a variable instead of an integer! Cool huh?!

 

       a. Name: Loop Check

       b. Allowed Iterations:  ${workflow.scratchpad.count}

       c. Click the Submit button to create the Activity

 

13. On the Workflow Core tab, navigate to Utilities

 

14. Drag out a Run Script Activity on to the desktop.

 

Here we will log down the final Message to the System Logs.

 

a. Name: Log Message

b. Script:

 

var identifier = context.name + '.' + activity.name;

gs.info('---> [WF:{1}]\nFINAL Message:\n{0}', workflow.scratchpad.message, identifier);
   

 

c. Click the Submit button to create the Activity

 

15. Wire everything up to look like the following:

 

3.turn_workflow.JPG

 

16. Click on the triple bar in the upper left of the Desktop, then click on Edit Inputs. We will use this to define a single test input variable for our workflow.

 

17. Add a new input variable:

 

a. Name: Number of Records

b. Column Name:   u_numberofrecords
c. Type: Integer
d. Order: 100
e. Length: 40
f. Default Value: 5

g. Click the Submit button to create the new variable.

 

16. Close the Workflow Inputs form.

 

Now we are ready to test our new Turnstile Activity!

 

 

Testing

 

1. Now click the Run Workflow button on the top right. The Start Workflow form will appear.

 

2. Click the Start button to run the workflow.

 

 

3. Accept the default of five loops.

 

4. The workflow will loop through five times and then exit.

 

 

5. From your ServiceNow browser tab Navigate to System Logs → All. This will open the System Logs list view.

 

6. Filter for all messages starting with --->

 

7. Order by date Created descending.  You should see something like the following. There should be five entries showing the counting up from zero to four, then a “FINAL” message should be printed out with all of the entries.

 

 

And now you have your very own Turnstile Activity.

 

I have placed the Enhanced Turnstile Activity and the example from this lab out on the share for you to download if you wish.

 

Steven Bell

If you find this article helps you, don't forget to log in and "like" it!  I would also like to encourage you to become a member of our blog!

CS_logo.jpgMVP-logo.jpeg

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

 


for Click for More Expert Blogs and also Find Expert Events!


  

 

I was exploring ways to do looping, and wanted a method similar to a “for” loop in JavaScript.  There was simply nothing out-of-the-box in the way of activities that gave me what I wanted, and that would give me the control I was looking for.  The Turnstile Activity, while useful, did not allow for a variable to be plugged in for the value, and was in essence hard-coded. 

 

Thinking it through I was able to come up with a relatively straightforward solution. I am providing this lab as an example.

 

So what we will be creating will be something like the following only implemented with workflow activities:

 

for (var counter = 0; counter < incidentList.length; counter++) {
    //… do some work
}



 

Assumption: That you have worked with and created ServiceNow Workflows before.  Also, you should probably play with this in your personal instance until you get proficient with the concept.  Just saying.

 

NOTE: We are going to create all of our activities first and wire them all together as the last step prior to testing.

 

 

Lab 1.1 – Implementing a Loop in a Workflow

 

1. Navigate to Workflow → Workflow Editor. This will open the workflow editor


    a. In the Workflow Navigation column click on the plus symbol to create a new workflow

 

8.sub-workflow_add workflow.JPG

2. Create a new workflow


    a. Name: Loop Example

    b. Table: Global

    c. Take all other defaults

    d. Click the Submit button to create the workflow

 

3. On the Workflow Core tab, navigate to Utilities

 

4. Drag out a Run Script Activity on to the desktop.

 

Here we will be initializing our loop.  We will load a specified number of records from the Incident table.  Store these into a scratchpad variable.  The number of times to loop will be the number of records.  We will start with 0 as the first number.

 

    a. Name: Initialize

    b. Script:

 

var identifier = context.name + '.' + activity.name;  // introducing just a bit more sophistication!

var incidentRecords = new GlideRecord('incident');


incidentRecords.addQuery('state', '!=', 7); // state of closed
incidentRecords.setLimit(workflow.inputs.u_numberofrecords); // note that this comes from your inputs
incidentRecords.orderByDesc('number');
incidentRecords.query();



var incident = {};
var incidentList = []; // this will be an array of objects

while (incidentRecords.next()) {
            incident = {};
            incident.sys_id = incidentRecords.sys_id + '';
            incident.number = incidentRecords.number + '';
            incident.assigned_to = incidentRecords.assigned_to.getDisplayValue() + '';
            incident.short_description = incidentRecords.short_description + '';
            incident.state = incidentRecords.state.getDisplayValue() + ''; // notice that we get the label
            incidentList.push(incident);
}

workflow.scratchpad.count = incidentList.length; // the overall count available
workflow.scratchpad.incidentList = incidentList; // the list of incident objects
workflow.scratchpad.counter = 0;  // our counter
workflow.scratchpad.message = '';  // the cumulative message

gs.info('---> [WF:{1}] Total number of records to loop: {0}', workflow.scratchpad.count, identifier);



 

     c. Click the Submit button to create the Activity

 

5. Drag out a Run Script Activity on to the desktop.


Here we will save up a cumulative message which we will print out each time to the System Log (in the Do Work Script Activity). Note that we are reference the “current” incident record using our object array, and our counter.  This is part of the magic!


    a. Name: Compile Message

    b. Script:

 

var incident = workflow.scratchpad.incidentList[workflow.scratchpad.counter];



workflow.scratchpad.message += '[' + workflow.scratchpad.counter + ']' 


            + ' Number: ' + incident.number 
            + ' - ' + incident.short_description 
            + ' - ' + incident.assigned_to
            + ' - ' + incident.state
            + '\n';




    c. Click the Submit button to create the Activity

 

6. On the Workflow Core tab, navigate to Timers

 

7. Drag out a Timer Activity.

 

Here we will wait one second so that the logs are written down in order.  Otherwise there is no need for this activity.

 

     a. Name: Wait a Sec

     b. Timer Based On: A user specified duration

      c. Duration: 1 seconds

     d. Click on the Submit button to create the Activity

 

8. Copy the Wait a Sec Activity (we will need two for our workflow).

 

9. On the Workflow Core tab, navigate to Utilities

 

10. Drag out a Script Activity on to the desktop.


      a. Name: Do Work

      b. Script:

 

var identifier = context.name + '.' + activity.name;

gs.info('---> [WF:{2}]\nLoop Count: {0} - Message so far:\n{1}', workflow.scratchpad.counter, workflow.scratchpad.message, identifier);


11. On the Workflow Core tab, navigate to Conditions

 

12. Drag out an If Activity on to the desktop.

 

Here is where we do the check!  The other part of the magic.  This is the condition and increment portions of the “for”.

 

     a. Name: Check Counter

     b. Advanced: Checked

     c. Script:

 

answer = ifScript();

function ifScript() {
            var check = 'no';
            workflow.scratchpad.counter++;  // increment the counter
        
            // check the counter and see if we need to go again or stop
            if (workflow.scratchpad.counter < workflow.scratchpad.count) {
                        check = 'yes';
            }
        
            return check;
}




    d. Click the Submit button to create the Activity

 

13. On the Workflow Core tab, navigate to Utilities

 

14. Drag out a Run Script Activity on to the desktop.

 

Here we will log down the final Message to the System Logs.

 

    a. Name: Log Message

    b. Script:

 

var identifier = context.name + '.' + activity.name;

gs.info('---> [WF:{1}]\nFINAL Message:\n{0}', workflow.scratchpad.message, identifier);




    c. Click the Submit button to create the Activity

 

15. Wire everything up to look like the following:

 

2.Workflow_For Loop.JPG

 

16. Click on the triple bar in the upper left of the Desktop, then click on Edit Inputs. We will define a single test input variable for our workflow.

 

17. Add a new input variable:

 

    a. Name: Number of Records

    b. Column Name:   u_numberofrecords
    c. Type: Integer
    d. Order: 100
     e. Length: 40
    f. Default Value: 5

    g. Click the Submit button to create the new variable.

 

18. Close the Workflow Inputs form.

 

So what do we have so far?  Everything we need to test the “For” loop concept.  So, now let’s test!

 

 

Testing

 

1. Now click the Run Workflow button on the top right. The Start Workflow form will appear.

 

19.sub-workflow_run workflow.JPG

 

2. Click the Start button to run the workflow.

 

3. Accept the default of five loops.

 

4. The workflow will loop through five times and then exit.

 

3.Looping.JPG

 

5. From your ServiceNow browser tab Navigate to System Logs → All. This will open the System Logs list view.

 

6. Filter for all messages starting with --->

 

7. Order by date Created descending.  You should see something like the following.  There should be five entries showing the counting up from zero to four, then a “FINAL” message should be printed out with all of the entries.

 

4.Results.JPG

 

Pretty neat, actually, how it works.

 

Steven Bell

 


If you find this article helps you, don't forget to log in and "like" it!

If you've used ServiceNow for a bit then you are probably familiar with using Dot-walking to access data on related records. In case you are new to the concept here is the definition and a link to product documentation.


From the product docs:

"Dot-walking in ServiceNow provides access to fields on related tables from a form, list, or script. If the current table contains a reference to another table, any field on the referenced table can be accessed using dot-walking."


I use dot-walking all the time in ServiceNow to help me view data about related records without needing to navigate to the referenced record. ServiceNow makes it really easy to dot-walk via the UI in lists and scripts but how can I leverage this functionality when using the REST Table API?


The REST Table API (available in the Eureka release and after) supports dot walking for GET requests via the sysparm_fields parameter. This parameter allows you (the requester) to provide a comma-separated list of field names that will be returned in the response. The most common use of the sysparm_fields parameter is to limit the fields that will be returned in the response. For example if you are using the ServiceNow REST Table API to query incident records if you make the following request you will receive a response that contains all the fields that you have access (permissions) to read in ServiceNow (on most systems this result in 40+ fields being returned.


Sample Request to a ServiceNow Demo Instance - All Fields

Sample Request Table API to GET an Incident · GitHub

 

GET /api/now/table/incident?sysparm_limit=1 HTTP/1.1

Authorization: Basic <removed on purpose>

Host: demo003.service-now.com

Connection: close

 

Sample Response - All Fields

Sample Response · GitHub

 

HTTP/1.1 200 OK

{

  "result": [

    {

      "skills": "",

      "upon_approval": "",

      "location": {

--- response cut off to save space --- see the GitHub Gist for full response content.

 

We can use the sysparm_fields parameter to limit the fields returned in the response. For example we can only include the number and location field by adding the sysparm_fields parameter as shown in the following request/response.


Sample Request to a ServiceNow Demo Instance - using sysparm_fields to limit response

sample request using sysparm_fields · GitHub

 

GET /api/now/table/incident?sysparm_limit=1&sysparm_fields=location,number HTTP/1.1

Authorization: Basic YWRtaW46YWRtaW4=

Accept: application/json

Host: demo003.service-now.com

Connection: close


Sample Response - using sysparm_fields to limit response

Sample Response - using sysparm_fields to limit response · GitHub

 

HTTP/1.1 200 OK

{

  "result": [

    {

      "location": {

        "link": "https://demo003.service-now.com/api/now/table/cmn_location/1083361cc611227501b682158cabf646",

        "value": "1083361cc611227501b682158cabf646"

      },

      "number": "INC0000001"

    }

  ]

}

 

When using the sysparm_fields parameter we were able to limit the response to only include the location and number field from the incident. Location is a reference field on Incident and refers to a record in the cmn_location table. cmn_location records have multiple fields including a name and city field but they are not included in the response by default. So how can we have those fields included in our response? That is where dot-walking comes in. You can dot-walk in the REST Table API using the sysparm_fields. If I add the name and city to the sysparm_fields parameter I can have them included in the response as is shown in the request and response below.

 

Sample Request to a ServiceNow Demo Instance - using sysparm_fields dot-walk

Sample Request to a ServiceNow Demo Instance - using sysparm_fields dot-walk · GitHub


GET /api/now/table/incident?sysparm_limit=1&sysparm_fields=number%2Clocation%2Clocation.name%2Clocation.city HTTP/1.1

Authorization: <removed>

Accept: application/json

Host: demo003.service-now.com

Connection: close

 

Sample Response to a ServiceNow Demo Instance - using sysparm_fields dot-walk

Sample Response to a ServiceNow Demo Instance - using sysparm_fields dot-walk · GitHub

 

HTTP/1.1 200 OK

{

  "result": [

    {

      "location.name": "Oklahoma",

      "location": {

        "link": "https://demo003.service-now.com/api/now/table/cmn_location/1083361cc611227501b682158cabf646",

        "value": "1083361cc611227501b682158cabf646"

      },

      "number": "INC0000001",

      "location.city": "Oklahoma"

    }

  ]

}

Let's talk about

  • Activity formatter
  • Setting activity formatter to show "send/recieved emails"
  • Testing the emails send and received that may not show on the activity formatter

Activity formatter

 

The activity formatter provides an easy way to track items not saved with a field in the record, such as journal fields like comments and work notes.

Journals with Activity formatter not showing Send/receive emails do not show ignored, failed or skipped emails.

 

whereiswally.png

 

Setting activity formatter to show "send/recieved emails"

 

Here is an example of a journal with activity formatter that does not show all sent and received emails. I tested this on an incident. I've slightly changed the email address on the blog when compared to the screenshots. On this example, I opened the incident table and then configured the activities to include "Send/Received Emails".

 

2015-09-23_1619.png

I configured the activities in the incident table to show "Send/Received Emails" by adding it to the selected list:

2015-09-23_1620.png

Testing the emails send and received that may not show on the activity formatter

 

After the sending and receiving a few emails, I managed to have 4 sent emails and 2 received on the email table as follow:

 

Email table

Email Type

Does is shows on

Possible explanation

Email send 1

sent

YES

Sent ok

Email send 2

sent-ready

NO

SMTP disabled

Email received 1

received

YES

Received ok

Email received 2

received-ignored

NO

Matches junk header

Email send 3

sent-ignored

NO

Email weight

Email send 4

sent

YES

Sent ok

 

 

Here is the final activity formatter displaying the emails on the incident form.

Note that there are only the sent and received correctly* emails which are shown.

*correctly means email with the field 'Type' of received or sent on the email table.

 

Also note, activity formatter only shows emails that match the displayed record

On the email table, the field 'target' holds the related record. This TARGET field needs to contain the record you have opened.

On our example, incident INC0010010

 

2015-09-23_1650.png

Conclusion

 

Journals with Activity formatter with "Send/received emails" enabled will not show ignored, failed or skipped emails and will only show record where the email target field matches the current open record.

 

2015-09-23_1633.png

What have we learn

After reading this blog, you show know:

  • Why some journals do not show 'send/receive emails.
  • Where to find the target on an email
  • Where the final send and received emails are stored

 

 

More information:

 

Note: Tested on Fuji, using Firefox

An inbound email action script has access to various pieces of an inbound email through script variables. The script variables email.from and email.origemail can be different.

 

email.from

email.from contains an email address according to the following conditions. If the address listed in the email Headers field matches an existing user's Email address, this variable contains the user's Email address. If the address listed in the email Headers field does not match an existing user's Email address, this variable contains the address listed in the email Headers field (starting with Eureka Patch 5) or the Guest user's Email address (in versions prior to Eureka Patch 5).

 

email.origemail

Now, email.origemail contains the email sender's address as listed in the email Headers.

 

returntosender.png

 

Here is an example to show the difference between email.from and email.origemail

I've created an inbound email action 'Create Incident - JS', Type=New

email.from.jpg

 

The script is:

 

gs.log("-email.origemail: " + email.origemail + " -email.from: " +  email.from + " - - JS");

current.caller_id = gs.getUserID();
current.comments = "-email.origemail: " + email.origemail + " -email.from: " +  email.from + "\n\n" + email.body_text;
current.short_description = email.subject;
current.insert();
   

 

Then I've created a user with

-userid=test.user@abc.com, email=test3.user@abc.com

NOTE the (number "3" on the email).

 

2015-09-23_1529.png

 

Then with the user test.user@abc.com, you sent the email to the instance. Once the email is recieved, you will notice the headers contain > From:Test.User@abc.com.

 

inbound email action.jpg

Now, on the logs and incident comment created, you will see:

-email.origemail: Test.User@abc.com-email.from: test3.user@abc.com

 

2015-09-23_1536.png

 

More information here:

 

Seeing double? Double mobile icons that is. Users on a Eureka or Fuji release may be experiencing duplicate icons in their ServiceNow instances on mobile. Icons such as Help, Service Catalog, My Incidents, Contacts, Follow-up, and High Priority, may appear more than once when you are in the mobile UI. The issue is caused by the unintentional creation of duplicate records on the following tables:

  • sys_ui_home_favorite table
  • sys_ui_home_section table
  • label records

 

Duplicate icons.jpg

When users are removed the records are not getting cleaned up so duplicates are generated in the system.

You may experience upwards of 1,000 duplicate icons if things get messy. Now, that is a sight for sore eyes. Well I've got two troubleshooting paths that will help you remove the home favorite records, home section records, and label records.

 

To revert your icons to their unique buttons:

Delete the sys_ui_home_favorite records:

  1. Navigate to System Mobile UI > Home Page Favorites.
  2. Filter the list for records where the User field matches the affected user. The filtered list shows the user's favorites. There may be multiple copies of each favorite.
  3. Delete the extra records.

Delete the sys_ui_home_section records:

  1. Navigate to System Mobile UI > Home Page Sections.
  2. Filter the list for records where the User field matches the affected user. The filtered list shows the user's sections. There may be multiple copies of each section.
  3. Delete the extra records.

Delete the label records:

  1. Navigate to System Definition > Tags. (Tags were originally called labels. While the terminology has changed, these records are still stored on the "label" table).
  2. Filter the list for records where the Owner field matches the affected user. The filtered list shows the user's tags. There may be multiple copies of each tag.
  3. Delete the extra records.

 

This step can also be done by simply running a script in the Scripts- Background.

(function() {
deleteDuplicates('sys_ui_home_section', ['user', 'table', 'label']);
 deleteDuplicates('sys_ui_home_favorite', ['user', 'table', 'label', 'icon']);
 deleteDuplicates('label', ['owner', 'name', 'short_description', 'background_color', 'color', 'icon']);
// table :: string, fields :: [string]
 function deleteDuplicates(table, fields) {
 var current = new GlideRecord(table),
 prior = new GlideRecord(table);
// ordering will not work on records created by snc hop access due to missing sys_user records
 current.addQuery('sys_created_by', 'DOES NOT CONTAIN', '@snc');
 prior.addQuery('sys_created_by', 'DOES NOT CONTAIN', '@snc');
// ditch if there are no fields
 if(fields.length < 1) {
 gs.log('deleteDuplicates requires at least one field');
 return false;
 }

// order by array of fields passed in
 for(i = 0; i < fields.length; i++) {
 current.orderBy(fields[i]);
 prior.orderBy(fields[i]);
 }

current.query();
 prior.query();

// bail if the query returns nothing
 if(!current.hasNext()) {
 gs.log('no records returned');
 return true;
 }
// current will always be one ahead of prior
 current._next();
// loop through...
 var dups = 0;
 while(current._next() && prior._next()) {
 var i;
 // ...and compare adjacent records
 for(i = 0; i < fields.length; i++) {
 if( current.getValue(fields[i]) != prior.getValue(fields[i]) ) break;
 }
 if(i == fields.length) {
 //gs.log('duplicate detected: current[' + current.sys_id + '] prior[' + prior.sys_id + ']');
 //gs.log('deleting [' + prior.sys_id + ']');
 prior.deleteRecord();
 dups++;
 }
 }

gs.log("Removed " + dups + " duplicates from " + table);

return true;
 }

 })();








 

 

This business rule can also be loaded into the affected instance as an XML file to prevent a re-occurrence of the issue until a fix is released. You can download the file from here.

 

To Import records as XML data:

  1. Sign in as an admin to the instance that should receive the data.
  2. Navigate to any list in the system.
    Any list can be used because the XML file contains the destination table name for the records.
  3. Right-click the list header and select Import XML.
  4. In the import screen, click Choose File and select the previously exported XML file.

    Import.png

  5. Click Upload.

 

Now that you've fixed your duplicate icons you can cancel that eye appointment use the mobile app with ease!

This video demonstrates how to configure outbound SOAP web service messages to consume third-party web services from the ServiceNow platform. It applies to all supported releases as of Fuji.

 

Role required: web_service_admin

 

For best video quality, increase your player resolution to 1080p.

 

 

 

Topics covered in this video:

 

For more information on consuming third-party SOAP web services, see:

 

ServiceNow product documentation:

Outbound SOAP Web Service

SOAPMessageV2 API

SOAP Web Service

 

ServiceNow Community:

Introduction to Web Services

SOAP Web Service and time zones examples

 

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

I have been poking around with this for quite awhile, but had not really come up with something that I liked.  Recently I was reading, cover-to-cover, the book "The Principles of Object-Oriented JavaScript" by Nicholas C. Zakas (ISBN-13: 978-1593275402).  I turned a page and lo! The clouds parted!  The light shone down (on the book), and the angels sang!  The answer was there!  On the page!  The Holy Grail!  I had found it!  Well...okay, maybe not so dramatic, but I was excited.

 

So, what I was needing was not only a way to iterate through the properties of a GlideRecord (without knowing what they all were), but also then pulling the values of the GlideRecord and stuffing them into a modifiable object.  GlideRecords, by design, are immutable (a fancy term for unmodifiable).  Frankly, I have needed a way to rapidly convert a GlideRecord into a mutable object array.

 

Grabbing my penny pencil and a scrap of napkin I quickly figured out what I needed to do and began to model it in Scripts - Background.  After getting the model mostly there, I then moved to a Fix Script and really went to work.  47 hours later...um, 1 hour later I had my solution.  I then updated my CSTableUtils Script (CurrentFactory) Include to incorporate the new functionality (I have made that available here on the ServiceNow Share if your are interested).

 

Here is my Fix Script:

 

test();

function test() {
    var incidentRecords = new GlideRecord('incident');
    incidentRecords.addQuery('state', '!=', 7); // closed
    incidentRecords.setLimit(5); // note that this comes from your inputs
    incidentRecords.orderByDesc('number');
    incidentRecords.query();

    var incidentList = []; // this will be an array of objects

    while (incidentRecords.next()) {
        incidentList.push(toObject(incidentRecords));
        //incidentList.push(CSTableUtils.toObject(incidentRecords));
    }

    gs.print(incidentList.length);

    for (var i=0; i < incidentList.length; i++) {
        gs.info('[{0}] number: {1} - description: {2} - assigned to: {3}',
            i,
            incidentList[i].number,
            incidentList[i].short_description,
            incidentList[i].assigned_to);
    }
}

// ----------------------------------------------
// GlideRecord to Object Converter
//
function toObject(recordToPackage) {
    var packageToSend = {};

    for (var property in recordToPackage) {
        try {
            packageToSend[property] = recordToPackage[property].getDisplayValue();
        }
        catch(err){}
    }

    return packageToSend;
}



 

The toObject function spins through all of the properties in the GlideRecord.  There were a couple that refused to cough up with info and actually caused an error situation, but they were not really important for general usage; so I skipped them with the Try/Catch.  As I noted in a previous article you can reference any given property value by using the object's array reference capability.

 

And there you have it!  BTW, I will be doing a code-review article later on Zakas' book.

 

Steven Bell

 

 

If you find this article helps you, don't forget to log in and "like" it! 

You can create new modules and set up favorites on the Mobile UI by configuring the URLs that they navigate to. Using specific URLs formats in ServiceNow, you can create modules, favorites, and UI Actions that route to the correct pages.

 

When using the mobile interface to interact with your instance, you will notice a certain URL format which is kept throughout the whole navigation: type_of_link/table/parameters. Think of this format as the main ingredient to your configured navigation URLs you create for your phone or tablet. You can add other "ingredients" to compliment your main ingredient to build your customized navigation.

 

Customized navigation is used when creating:

Menu Modules: define which modules are available to mobile

users at the application menu or module level.

Screen Shot 2015-09-02 at 3.01.39 PM.png

Homepage Favorites:  is a list of icons that appears on your mobile homepage.

These can be used to create a shortcut to any record, list, form or catalog item.

Screen Shot 2015-09-02 at 3.01.57 PM.png
UI Actions: using the function action.setRedirectURL()Screen Shot 2015-09-02 at 3.01.12 PM.png

 

More information can be found here: Configuring the Smartphone Interface - ServiceNow Wiki

 

You can use the different navigations to create direct links to important aspects of your instance. This allows you to manage the helm while on-the-go, in a meeting, on a call, or while out to lunch from within the Mobile UI:

 

FormatDescriptionExample
list/{tableName}/q:{QUERY}Directing to a list with the applied query (optional)

list/incident/q:active=true^priority=1^EQ

List of incidents where active=true and priority=1

streams/{tableName}/q:{QUERY}Directing to Journal streams of the table records with the applied query (optional)

streams/incident/q:active=true^priority=1^EQ

Journal stream of the incident table where active=true and priority=1

form/{tableName}/{sysId}Directing to a record form

form/incident/a9a16740c61122760004fe9095b7ddca

Record form of incident where sys_id=a9a16740c61122760004fe9095b7ddca

form/{tableName}/-1Directing to a new record form

form/incident/-1

Creating a new incident record

catalog/home/{catalogSysId}Directing to a specific catalog

home/e0d08b13c3330100c8b837659bba8fb4

Catalog homepage of the catalog where sys_id=home/e0d08b13c3330100c8b837659bba8fb4 (Service Catalog)

catalog/category/{categorySysId}/

{categoryName}

Directing to a specific catalog category

catalog/category/d258b953c611227a0146101fb1be7c31/

Hardware

Catalog category 'Hardware'

catalog_item/item/{categorySysId}/

{categoryName}/{itemSysId}

Directs to a specific catalog item

catalog_item/item/d258b953c611227a0146101fb1be7c31/

Hardware/0d08837237153000158bbfc8bcbe5d02

Catalog item form category 'Hardware' where item sys_id=0d08837237153000158bbfc8bcbe5d02

help/{mobileHelpPath}Directs to a Mobile Help page

help/defining_tags

Mobile Help page 'Defining Tags'

view/{UIMacroName}Renders a customized UI Macro

view/test_mobile

Rendering the UI Macro named test_mobile


When directing to a list or stream (the first two options), you can add a query in order to get filtered records. It is quite tricky to build the query yourself so here is a tip to add the query: Always use the module form in order to create the query.

 

Using the Module form to Create a query:

1. Go to System Mobile UI > Navigator Apps. Enter one of the already existing Applications and create a new module in the Modules related list.

sn module query1.jpg

2. Select the table in the Table field, and build the required filter using the Filter Builder. Then save your changes.

sn module query 2.jpg

3. In the Mobile user interface, navigate to the temporary module you just created, and copy the URL part that you need form the browser address bar.

Screen Shot 2015-09-02 at 3.01.39 PM.png

example url.jpg

4. Now you can use this url also as a favorite link. For example:  list/incident/q:caller_idDYNAMIC90d1921e5f510100a9ad2572f2b477fe%5Eshort_descriptionLIKETTT%5EEQ

 

A few things to note...

  • When having a query in the URL, you have probably notices that it can be dynamic, by containing a javascript function.

     For example: list/incident/q:sys_created_onONThis%20month@javascript:gs.beginningOfThisMonth()@javascript:gs.endOfThisMonth()%5EEQ

      • It means, list of incidents which were created this month.
  • The url always starts with an exclamation mark '!'. The exclamation mark is being added by the platform, therefore you don't need to add it.
    • For example:http://<instance_name>.service-now.com/$m.do#/!list/incident/q:active=true%5EEQ
  • When navigating by navigation menu modules or favorites, the platform mechanism was designed in a way that you are kept always inside the instance.

        Therefore, you always have to use relative URLs and not absolute ones.

    • For example: list/incident/q:active=true%5EEQ
      • And not:http://<instance_name>.service-now.com/$m.do#/!list/incident/q:active=true%5EEQ
  • You can not direct navigation menu modules and favorites to external links. UI Actions are a different story, since you are using the function action.setRedirectURL(), where you can send an absolute URL as the function parameter.

 

In this world where we are being stretched thinner and are expected to have a great output, we need to utilize the resources we have. ServiceNow mobile and tablet capabilities are a great way to get more done in the office and still venture away from your desk.

 

Additional resources on using the ServiceNow mobile interface:

Debugging the Mobile UI from your Desktop

List Filtering in the Mobile UI

Mobile View Basics (KB0551074)

This video in our Web Services series demonstrates how to configure outbound REST web service messages to consume third-party web services from the ServiceNow platform. It applies to all supported releases as of Fuji.

 

Role required: web_service_admin

 

For best video quality, increase your player resolution to 1080p.

 

 

This video covers:

 

For more information on consuming third-party REST web services, see:

 

ServiceNow product documentation:

Getting Started with REST

Outbound REST Web Service

RestMessageV2 API

REST API Explorer

 

Knowledge Base:

REST API FAQs

Table API FAQs

PRB637497: REST message test run Parameters field is truncated

 

ServiceNow Community:

Leveraging Web Services for App Integration

Introduction to Web Services

 

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

This video explains, with use case examples, how web services work in ServiceNow instances. It also provides a brief overview of the System Web Services application.


 

Topics:

 

 

For more information on web services, see:

Web Services - ServiceNow Product Documentation

SOAP Web Service and time zones examples

 

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

If you’re using the Mobile UI for the first time, you'll want to make sure your custom scripts function the way you expect as part of your testing process.  There are some limitations in the Mobile UI that may require changes in any custom created scripts, or scripts that have been modified from their out of box versions.


Where to Look

The differences between the two UIs are on the client side (scripts running on your browser as opposed to the server).  The following elements make use of client side scripting, and may need changes to work as expected in the Mobile UI.

 

  • Client Scripts (Including Catalog Client Scripts)
  • UI Policies
  • Navigator Modules
  • UI Actions

 

Note that the out of box versions of these elements have either been updated to work in the Mobile UI, or marked to only run in the UI in which they function.  When you start using the Mobile UI, you'll be looking for customized  elements, which won't necessarily be compatible.

 

 

What to Look For

The first thing we need to do is find deprecated methods, unsupported methods,  and unsupported browser objects in the existing scripts.

 

Deprecated Methods

  • getControl
  • getElement
  • getFormElement
  • getOption

Unsupported Methods

  • showRelatedList
  • hideRelatedList
  • showRelatedLists
  • hideRelatedLists
  • flash
  • getSections
  • enableAttachments
  • disableAttachments
  • setReadonly (Note that setReadOnly is available)
  • getParameter

Unsupported Browser Objects

  • Window
  • jQuery or Prototype ($, $j, or $$)
  • Document

 

Multiple Functions

The Mobile UI expects a single function within a client script.  Each Client script contains an onChange, onCellEdit, onLoad, or onSubmit function, depending on it's type.  All script in a client script needs to be contained within this function.  Additional functions are allowed, as long as they're contained within the top level script.

 


What To Do About It

Deprecated Methods

Deprecated methods are not available in the Mobile UI, but their functionality has been replaced with new methods.  Unsupported Methods and Objects do not have replacement methods, and should not be used in the Mobile UI.  For example, while getControl cannot be used, methods like g_form.hasField g_form.addDecoration can be used to achieve the same goals.

 

The complete list of methods added for use in the Mobile UI can be found in the Mobile GlideForm API Reference in the wiki.

 

Unsupported Methods

Unsupported are method that cannot be used due to the limitations of the Mobile UI.  When run in mobile, these methods will perform no action, so nothing will happen when they are run.  Unlike the deprecated methods, there are not mobile equivalents of these methods.

 

Unsupported Objects

Direct access to the Document and HTML elements are not allowed in the Mobile UI, as a result, the objects listed above are not supported.  Attempts to get values from these objects will return values of 'undefined'.

 

Multiple Functions

Functions of than the onLoad function (or whichever one is used) can be created, but need to be contained within the top level function.

 

Specifying Desktop or Mobile UI (or Both)

While the Mobile UI has it's own Navigator Modules and UI Actions, it shares it’s Client Scripts and UI Policies with the Desktop UI.  Fortunately, you can specify which UI Type these execute in.  You can have these execute in both UI types, which will save you time when a script is valid in both Mobile and Desktop.   You can also set scripts only to run in one UI or the other.  That way you can have a script that uses the full functionality of the Desktop UI, and maintain a separate script for the Mobile UI that works within Mobile's limitations.

 

Client Scripts

Client scripts have a field called 'UI Type' which can be set to Desktop, Mobile, or Both.


UI Policies

UI Policies have a field called "Run scripts in UI type" which can be set to Desktop, Mobile, or Both.

 

 

Let's Take an example script that has been made usable in the Mobile UI.  The Example we're using is the out of box client script  "(BP) Set Location to User."  This script is triggered when the Caller field on the incident form is changed.  It checks the for the new Caller's location, and updates the Location field on the incident form to match.

 

This has already been fixed in Dublin and up, but here's how it originally looked in Calgary, back before the Mobile UI was introduced:

 

function onChange(control, oldValue, newValue, isLoading) {
   if (isLoading)
      return;

   if (newValue == '') {
      g_form.setValue('location', '');
      return;
   }

   if (!g_form.getControl('location'))
      return;

   var caller = g_form.getReference('caller_id', setLocation);
}

function setLocation(caller) {
   if (caller)
       g_form.setValue('location', caller.location);
}






 

 

There are a few things that needed to be changed to make this compatible with the Mobile UI.  Here's what we did.

 

 

Replace getControl

Our First issue is on line 10, we check to see if the location field is on the form using getControl.  This is one of those methods we can't use in mobile.  Fortunately there's a working alternate called hasField we can use instead.  So, we need to change line as shown below.  (Note that in instances Fuji, this has already been done).

 

From:

if (!g_form.getControl('location'))






 

To:

if (!g_form.hasField('location'))






 

Make getReference calls Asynchronous

The next issue is on lines 13-19.  On line 13, the script uses getReference to get the value of the caller_id field.  It a function called setLocation as a callback function.  This is good idea in general, but especially important in mobile, where it is required. The function itself is on lines 16-19.

 

While the asynchronous call is already handled, there's an issue, which is that the setLocation function is outside the onChange function, which is another thing that we can't do in Mobile.  The easiest solution is to just copy the setLocation function into the onChange function, so it appears after the place where it's called on line 13, but before the bracket that ends the onChange function on line 14.

 

Change the UI Type

Now that we've got a compatible script, we need to change the UI Type field.  By default, it's set to "Desktop," but now that we've made our changes we can set it to "Both" so it will run on either UI.

 

Here's how the script looks with the changes in place:

 

function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading)
        return;

    if (newValue == '') {
        g_form.setValue('location', '');
        return;
    }

    if (!g_form.hasField('location'))
        return;

    var caller = g_form.getReference('caller_id', setLocation);



    function setLocation(caller) {
        if (caller)
            g_form.setValue('location', caller.location);
    }
}






This is a simple example, but Illustrates a lot of the above points.  The links below provide additional information and instruction, as well as a few documented problems to be aware of.

 

 

Further Reading

Product Documentation

 

Knowledge Base

  • KB0551285:  Using Asynchronous Calls in Mobile UI Scripting
  • KB0551074:  Mobile View Basics
  • KB0549860:  Client Script or UI Policy is not running.
  • KB0551212:  Mobile UI limitations

 

Community

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, and Workflows in ServiceNow

 

UPDATED:  Was missing step 5 & 6

____________________________________________________________________________

 

 

One question I get a lot when consulting on Workflows is: How do you do parallel processing, or parallel threads?

 

Simply put:  I use a Branch Activity, and a Join Activity. I will give a how-to example here with a couple of pitfalls to watch out for.

 

Assumption: That you have worked with and created ServiceNow Workflows before.

 

Lab 1.1: Workflows – Branch and Join Example

 

1. Navigate to Workflow → Workflow Editor. This will open the workflow editor

    a. In the Workflow Navigation column click on the plus symbol to create a new workflow

 

1. New Workflow.JPG

2. Create a new workflow

    a. Name: Branch and Join Example

    b. Table: Global

    c. Take all other defaults

    d. Click the Submit button to create the workflow

 

3. On the Workflow Core tab, navigate to Utilities

    a. Drag out a Branch Activity on to the form.

        i. Name: Run Parallel Processes

    b. Drag out a Join Activity on to the form.

        i. Name: Pull Together Processes

    c. Drag out a Run Script Activity on to the form.

        i. Name: Set the Check

        ii. Script: workflow.scratchpad.check = 'true';


4. On the Workflow Core tab, navigate to Timers

    a. Drag out a Timer Activity.

        i. Name: Timer For Branch 1

        ii. Timer Based On: A user specified duration

        iii. Duration: 5 seconds

    b. Drag out a Timer Activity.

        i. Name: Timer For Branch 2

        ii. Timer Based On: A user specified duration

        iii. Duration: 10 seconds

    c. Drag out a Timer Activity.

       i. Name: Timer For Branch 3

        ii. Timer Based On: A user specified duration

        iii. Duration: 15 seconds

 

5. On the Workflow Core tab, navigate to Utilities

    a. Drag out a Run Script Activity

       i. Name: Set the Check

       ii. Script:  workflow.scratchpad.check = 'true';

 

6. On the Workflow Core tab, navigate to Conditions

    a. Drag out an If Activity.

       i. Name: Something Happened

        ii. Advanced: checked

        iii. Script:

 

answer = ifScript();

function ifScript() {
  if (workflow.scratchpad.check == 'true') {
      return 'yes';
  }
  return 'no';
}

 

7. Wire everything up to look like the following:

 

2. Final Workflow.JPG

 

8. Now click the Run Workflow button on the top right. The Start Workflow form will appear.

 

3. Run Workflow.JPG

 

9. Click the Start button to run the workflow.

 

10. Observe the workflow. The timers all fire at the same time, but don't allow flow to continue until each has completed. Then all end up at the Join until the last one is done, and then get released to the end.

 

4. Running Workflow.JPG

 

However! If you look closely at the Join you will see that the Incomplete branch fired! What's up with that?!

 

5. Incomplete Branch.JPG

 

Well there is a bug in our workflow. The Branch Activity has three branches. There has to be the same number of branches flowing into the Join. So look carefully at the If Activity and you will see there are two there, plus one for each of the other timers! So four! That causes the Join Activity to fire the Incomplete.

 

11. So let us adjust the workflow slightly by adding a Script Activity after the If Activity, but prior to the Join activity. It's purpose will be to bring the two If Activity branches back together and then there will be only one flowing into the Join.

 

12. On the Workflow Core tab, navigate to Utilities

    a. Drag out a Run Script Activity on to the form.

        I. Name: Write to Logs

        ii. Script: gs.log('---> There was a problem!', 'WF: Branch and Join Example');

 

Your Workflow should look something like this after rewiring:

 

6. Combining the flows.JPG

 

13. Re-run the workflow again. Now the Join Activity fires the Complete branch. Whew!

 

The number of branches coming out of the Branch Activity must equal the number of branches going into the Join Activity.

 

7. Complete Branch.JPG

 

14. So, let's play a little game with it and see if it likes things changed up a bit.

 

15. Rewire your Workflow

    a. The Timer 2 is connected to the Write To Logs activity.

    b. The If Activity “Yes” branch is still wired to the Write To Logs activity.

    c. The If Activity “No” branch is now wired directly to the Join.

 

16. Your workflow should now look like this (I highlighted the rewired Activities):

 

8. Final workflow.JPG


17. Rerun your workflow, and observe what happens.  

 

9. Rewire and run.JPG


18. Notice that there are still only three branches coming into the Join? That meets the criteria and the Complete branch fires.  Even though only two were actually utilized during runtime.

 

That is it! You have now wired up your first Branch and Join Workflow. Even though this had only three branches you could do more. I recommend keeping it as simple as possible; as you can see, it can get difficult to tell what is happening pretty quick!

 

Steven Bell

 

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!

Awhile back I wrote a couple of articles where I tried to do a comprehensive overview of Database Views.  I am asked quite often by our clients how to construct these, and what to watch out for.  So I thought I would make these available here on the community.

 

ServiceNow Admin 101: Observations on Database Views, Part I

ServiceNow Admin 101: Observations on Database Views, Part II

 

Steven Bell.

 

If you find this article helps you, don't forget to log in and "like" it!

CS_logo.jpgMVP-logo.jpeg

 

One of the important differences to note about scripting in the Mobile UI is that synchronous javascript or glideRecord calls are not allowed.  In practice, it's pretty easy to script around, and the product documentation provides examples why this is done, and how it works.

 

All Javascript run on a browser shares a single thread, meaning everything that executes has to wait in line.  If something in a script takes a second or two to complete, your browser needs to wait.  If this goes on long enough, it may seem like your browser is hung.

 

The way to get around this is to make an Asynchronous call.  This allows us to move requests we've made to another thread.  Your browser has a pool of threads it uses specifically for asynchronous calls like this.  Meanwhile, your browser can move on to it's next task.  When the request is finished, a callback function is triggered to handle the results of the request.  Here's an example of the mobile platform not allowing synchronous JavaScript calls. The g_form.getReference() method must now have the callback parameter defined.

 

 

var userName = g_form.getReference('assigned_to').user_name;
g_form.setValue('u_assigned_user_name', userName);

 

 

Above, we're making a g_form.getReference  call to get the a user name based on the contents of the assigned_to field.  Once that's done, we're setting the u_assigned_user_name field to that value.  The problem with this is that we're waiting for that getReference call to process.  This might only be a fraction of a second, but it might go a bit longer. But, you might have more that one of these in your script, and several scripts that execute on your form.   This can add up!

 

Here's the asynchronous version:

 

 

g_form.getReference('assigned_to', function(gr) {
     g_form.setValue('u_assigned_user_name', gr.user_name);
});

 

 

Here, we've added a second argument to getReference.  This second argument is an anonymous function, and has an argument called gr, which stores the results of our getReference call.  Within that function, a setValue call assigns a value to u_assigned_user_name, just like the second line of our first example.  This function will execute whenever the getReference call finishes.  Since this is now in another thread, the browser can move on to other things.

 

 

Further Reading

Mobile Client GlideForm (g_form) Scripting

Client Script Best Practices

Mobile GlideForm (g_form) API Reference

Are you having problems with your Mobile UI policies, maybe you have some fields showing that should be hidden or maybe one of your read-only fields is editable? This is where troubleshooting your mobile UI policies will come in handy. UI Policies are rules that apply to forms that dynamically change form information or the form itself. These are great becasue they allow you to add controls without having to write any scripts.

 

We use UI Policies to set fields on a form to:

  • Optional or Mandatory
  • Visible or Hidden
  • Editable or Read-only

 

When troubleshooting your Mobile UI Policies there are 3 plans of action we recommend you take:

  1. Confirm UI Policy scripts are set to run in the mobile UI
  2. Make sure any client side scripts are compliant with the mobile UI
  3. Make sure any other element aren’t causing the change

 

Confirm your scripts are running in the Mobile UI

The Run Scripts in UI Type field was added to UI Policy records when the Mobile UI was launched and it provides the options: Desktop, Mobile, or both. But you probably won’t automatically see this field because it isn’t defaulted to appear on the UI policy form so you’ll need to configure your form by:

  1. Right-click the form header and select the appropriate option for your version:
    • Fuji or later: Configure > Form Layout
    • Eureka or earlier: Personalize > Form Layoutpersonalizeform.jpg
  2. Using the slushbucket, select the Run Scripts in UI Type from the available box and add it to the Selected box sluchbucket.jpg
  3. Click Save
  4. Select the UI Policy record
  5. Change the Run Scripts in UI Type to either Mobile or Both

 

 

 

Confirm client side script is compliant with the Mobile UI

Because there are many limitations in the mobile interface, you may be required to make changes to any custom scripts. Scripts using asynchronous calls or unsupported methods may break down, and prevent forms from working the way you intended. This is important for scripts within your UI Policies as well as any other client side scripts that execute on the same form. When client side errors are triggered on a form, scripts stop executing. So if there's a faulty client script executing on the form before your UI policy, then it can potentially stop your UI policies from running.

 

You should Debug the Mobile UI from your desktop to make sure the client side scripts are compliant with the mobile user interface and to track down any errors is using you browser's developer console

 

Make sure other elements aren't causing changes

UI Polices are the recommended method to make a field mandatory, visible and conditionally read only but these aren't the only way to control this. Client scripts execute before UI policies so they should override these calls, which can explain why a field is invisible after you've disabled a UI policy that hides it.

 

Check the client scripts running on the form for any:

  • setVisible
  • setMandatory
  • setReadOnly

 

These might be conflicting with your policies. It should also be noted that a field could be made mandatory at the dictionary level by using the checkbox on the field's dictionary entry. Also be aware we have a known error related to this if this step still isn't working for you see [Mobile UI] UI Policy executes before client scripts.

 

Hopefully one of these steps will helps you identify what is causing your UI Policies errors in the Mobile UI. If you need more information refer to the Smartphone Interface and Creating UI Policies pages.

I was looking for a way to update the image of Knowledgebase article in Fuji or Eureka to something custom (not available in any of the icon lists provided).  This field is locked down in a couple of ways.  First in the KB form it is limited to choices out-of-the-box from ServiceNow, and In the past the list-view was editable and you could simply insert your image name in the image field.  However, in Fuji this field is locked down and not editable from the list view.  Well, frankly, I needed a way to update that field for some specialized KB's I had created.  This is that way.

 

NOTE: Since the modifications are against data they will not record into your update set.  Therefore put this script into a Fix Script to be run when you move to production.

 

1. First load your favorite jpg into the ServiceNow Image table.

    a. Navigate to System UI -> Images.  This will display a list view of the images table.

    b. You may want to personalize your list view to include the Updated field. Order by updated date descending.

    c. Click the New button.  This will display the new Image form.

    d. Fill in the following fields:

        i. Active: checked.

        ii. Category: Knowledge Management.  This is optional.

        iii. Name: <<name of your image>>.<<jpg,gif,png>>

        iv. Image: upload your image (Click to add..).

    e. Click the Submit button.  Your image will be added to the instance image library with the name you indicated.

 

1.mini-lab - kb images.JPG

 

2. Create a new Fix Script, or go to Scripts - Background and run the following script.  I have filled it in with what I last messed with.  You will have to add your own information obviously.

 

The numberList variable will contain a comma delimited list of the KB articles you want to have the image field modified for.

The addQuery 'title' value is optional, and I only have it in the query to help further constrain the returned values.
The kbRecords.image field will contain the image in the images table that you want inserted into the image field.

 

 

// this would be the list of KB articles you want to have the custom image added to.
var numberList = ['CSKB00001','CSKB00002','CSKB00003','CSKB00004','CSKB00005'];

var kbRecords = new GlideRecord('kb_knowledge');
kbRecords.addQuery('knowledge_base.title', 'Discovery EXCELerator');
kbRecords.addQuery('number', 'IN', numberList);
kbRecords.query();

while (kbRecords.next()) {
    kbRecords.image = 'DiscoEXCELerator_50.jpg';  // your image name would go here
    kbRecords.update();
}

 

 

3. Navigate to Knowledge -> Articles -> All.  This will display a list view of all articles in the KB.

    a. Open one of the articles you changed the icon for.

    b. The Image field should now contain your new icon/image.

 

3.mini-lab - kb images.JPG


4. The image fields for the KB articles you specified will be changed.  In your browser change your link from:

 

        https://<<instance name>>.service-now.com/navpage.do

 

  To:

 

        https://<<instance name>>.service-now.com/kb_home.do

 

You should see your new icons appear in the Knowledgebase view.


4.mini-lab - kb images.JPG

 

So, ok, that one is solved.  However, it does not appear correctly for Knowledgebase v3.  This is something I have researched, but not to the point of diving into the code to try to figure it out. 

 

The real question is:  Does anyone know of a way to change these icons in KB v3?  Is it some undocumented property?  Inquiring minds want to know!  :-)

 

I would appreciate any feedback on that question.  Meanwhile I will be looking at it when I have the time.

 

Thanks,

 

 

Steven Bell

 

 

If you find this article helps you, don't forget to log in and "like" it!

Jason McKee

Introducing LiquorBot

Posted by Jason McKee Employee Sep 16, 2015

Knowledge is always an awesome opportunity to see what other people are doing with the platform and get inspired for things you can do.  For me, Knowledge 15 and CreatorCon were no different.  Fruition brought FruBot, which would drive up and serve you a beer, initiated by service catalog request.  And Joshua Bray put together an awesome lab for the Creator Con Hack Zone that interfaced ServiceNow with an Arduino to perform alerts and actions.

 

Looking at those two projects, I started thinking about how I could take those concepts and take them further. Since I was in Vegas and the iPad driven drink menus were everywhere, the answer hit me: make a ServiceNow powered bartender robot to mix drinks!

 

After learning how to develop on the Arduino (prior to Josh’s HackZone lab, I had never played with an Arduino before) and a bunch of cutting, drilling, grinding, welding, plumbing and painting, I had a Wi-Fi-enabled bartender robot with a very basic REST API that take a simple list of how many ounces to dispense from each of it’s 8 bottles.

 

LiquorBot at Santa Clara.jpeg

 

The vast majority of what makes LiquorBot tick is all built in ServiceNow.  There is a database of over 10,000 drink recipes (loaded from a terrible, terrible comma and pipe separated flat file full of bad formatting and errors, but that’s a story for another day), and 1700 potential ingredients.  There’s a Script Include to parse and validate recipe text like “1 ½ oz fresh orange juice” into discrete “1.5”/”oz”/”orange juice” fields as well as convert different units of measure into ounces.  For example, it turns out that a “dash” and “splash” actually have precise volumetric definitions (0.03125oz and 0.125oz in case you were wondering).  And another Script Include for making calls to LiquorBot’s API using RESTMessageV2.

 

On the bot side of things, the pumps are fairly consistent in terms of X number of seconds of run-time moves Y volume of liquid (1990ms per oz for those of you wondering).  When the API receives “bottle 1 gets 0.33 oz”, that translates to “turn on the relay for bottle 1 for 657ms”.  I wasn’t sure how accurate the pumps would actually be until I got the bot all put together and started testing, but I was pleasantly surprised to find that it could indeed accurately dispense a single dash of Vermouth (I like my martinis very dry).  After all the ingredients have been dispensed, a pump flushes the output plumbing with air to clear the output tubing so no ones tequila sunrise gets mixed up with the last person’s whiskey sour.

 

 

To setup LiquorBot to start pouring drinks, you put the feed tubes from the pumps into the bottles of alcohol and mixers and load them into LiquorBot.  You then configure select the ingredients in the ServiceNow interface and set the dispenser number for each ingredient.  In the background, changing the dispenser field on an ingredient fires a series of events causing every recipe with that ingredient to be checked to see if it has all of it’s ingredients available on a dispense or not and updates it’s “mixable” flag.  In addition to checking for exact ingredients, the “mixability validation” logic also takes into consideration substitutions, so if you tell LiquorBot that you have “vodka” on bottle #1, it will pull in recipes that call for Kettle One and Gray Goose vodka as well.

 

Screen Shot 2015-09-15 at 7.18.38 PM.JPG

 

Today interaction with LiquorBot is via UI Actions on the List and Form views.  There are actions to prime the pumps (pulling just enough liquid in to fill the feed tubes) and run a clean cycle to flush the system with water.  The most important UI Action is Vend.  You may browse/search recipes using a List view (want a non-alcoholic drink? Filter on the category field) or pull up the Form view to see the ingredients that go into the drink.  When you’ve made your selection, click the Vend action under Related Links (or right-click in the list view to select Vend) and your selected recipe will be converted to ounces, packaged up into a RESTMessage and sent to LiquorBot’s API via a MID Server.  Within a few seconds LiquorBot will have pumped the perfect amounts into your glass and your drink is ready to enjoy.

 

 

LiquorBot recently had its first outing at our Santa Clara headquarters and is also being demo-ed at our Custom Application Development Workshops in Houston and Dallas this week.  What’s next for LiquorBot?  Adding a Service Catalog interface, reporting on drinks that were served, inventory control, like any good project the list of fun things to do never ends.  Interested in seeing what makes LiquorBot tick?  There's lots of moving parts between the ServiceNow code and Arduino code, not to mention that actual building of the bot itself (for example, lesson learned: 1/8" wall steel tubing may be what's laying around the garage, but is massive overkill.  LiquorBot tips the scales at a touch over 30lbs without any bottles loaded).  Let me know what's interesting in the comments below and I'll make sure to cover it in a "making of" post.

 

download_20150903_185754.jpg

We're doing a bunch of Custom App Dev workshops this week in Altanta, San Diego, Houston and Dallas.  If you're signed up for one of these workshops, the files you need are attached to this post.  If you're interested in what events may be happening near you, be sure to check our events calendar.

 

And if you're not signed up for a workshop this week, good news!  The workshop guidebook is attached as well and you can grab a free instance from the developer portal and work through the material at your leisure.

So here is a couple of techniques I found in the out-of-the-box code base, and thought I would bring them into the light.  You can constrain what fields are displayed on a form with a System Property, a Display Rule and a g_scratchpad variable.

 

Lab 1.1 – Limit Field and Section Display

 

1. Create a new system property

 

a. Navigate to the System Properties list view by typing the following into the navigation field: sys_properties.list

b. Click the New button.  The new System Property form will be displayed.

c. Fill in the form with the following:

      1. Name: incident.show.subcategory
      2. Description: Turn off Subcategory
      3. Choices: true/false
      4. Type: true | false
      5. Value: true

d. Right-click on the form header to display the context menu, and click Save.

e. At the bottom of the form in Related Links click on the New button.  The new Category form will appear.

f. Fill in the form with the following:

      1. Name: Incident
      2. Leave everything else as default

g. Click the Submit button

 

1.mini-lab - Incident Properties_a.JPG

 

2. Create a new Property Module For Incidents


a. Navigate to the Incidents application

b. Right-click on the application header to display the context menu, and pick Edit Application Menu.  The Incident Application module List View will be displayed.

c. Click the New button to create a new module to display the new Module form.

d. Fill in the form with the following:

i. Title: Properties

ii. Application Menu: Incident (should already be filled in)

iii. Order: 900 or something way down.

iv. Hint: Incident Properties

v. Visibility:

        1. Roles: Admin
        2. Active: Checked

vi. Link Type:

        1. Link Type: URL (from arguments)
        2. Arguments: system_properties_ui.do?sysparm_title=Incident&sysparm_category=Incident

 

e. Click the Submit button to save.  The Properties module should immediately appear in the Incident Application.

f. Click on the new Properties link and see that the new property is displayed and defaulted to checked.

 

1.mini-lab - Incident Properties.JPG

 

 

3. Create a Display Business Rule

 

    1. Navigate to System Definition -> Business Rules
    2. Click the New button to create a new Business Rule.  A blank Business Rule form will be displayed.
    3. Fill out the form with the following:
      1. Name: Show Subcategory
      2. Table: Incident
      3. Order: 900 or some such down toward the bottom
      4. When to run:
        1. When: display
      5. Advanced:
        1. Script:

 

function onDisplay(current, g_scratchpad) {
   g_scratchpad.showSubCategory = gs.getProperty('incident.show.subcategory', true);
}


4. Create a Client Script to react to the g_scratchpad variable

 

    1. Navigate to System Definition -> Client Scripts.  The client scripts list view will be displayed.
    2. Click the new button.  A blank client script form will be displayed.
    3. Fill in the form with the following:
      1. Name: Show Subcategory
      2. Table: Incident
      3. UI Type: Desktop
      4. Type: onLoad
      5. Active: checked
      6. Global: checked
      7. Script:

 

function onLoad() {
     g_form.setVisible('subcategory', g_scratchpad.showSubCategory == 'true'); 

     // for grins let’s turn off the Task SLAs section (tab) at the bottom of the form to show it can be done!
     if (g_scratchpad.showSubCategory == 'false') {
            g_form.hideRelatedList('task_sla');
     }
}

 

 

5. Test

 

    1. Our initial test will be to take the default of the property: true.  And make sure that normal functionality is there.
    2. Navigate to Incident -> Open, and open an existing incident.  The Subcategory field should be present and the Task SLAs tab at the bottom of the form should be there.
    3. Now Navigate to Incident -> Properties, and uncheck the Turn off Subcategory check box.
    4. Click the Save button.
    5. Navigate back to Incident -> Open, and open and existing incident.  The Subcategory field should be missing, and the Task SLAs tab will be gone as well!

 

 

Conclusion:


So you see how using the combination of a System Property, a Display Business Rule, a g_scratchpad variable and a Client Script can be used to control what is displayed on a form. Cool huh?!

 

Ok…now go out and turn it all off or you will get into trouble with your management.  :-)

 

Steven Bell


If you find this article helps you, don't forget to log in and "like" it!

 

Mobile UI actions and desktop UI actions are two different entities; the key to using mobile UI actions is to first understand the differences between the two interfaces. When using UI actions, here are a couple of things to note:

  • The UI actions on your mobile are not the same as what you see on your desktop.
  • The UI actions you’ve created in the desktop UI will not be visible in the mobile UI, and vice versa.

 

Here’s where you can find them:

  • Desktop UI actions are stored on the [sys_ui_action] table and can be seen by navigating to System UI > UI Actions.
  • Mobile UI actions are stored on the [sys_ui_ng_action] table and are accessible by navigating to System Mobile UI > UI Actions - Mobile.

 

Types of UI actions.jpg

 

Mobile UI Action Visibility & Locations

 

In the desktop UI, there’s a related list on the UI action form called UI Action Visibility that's used to restrict specific views. When using Mobile UI Actions, keep in mind:

  • Showing a desktop UI action on the mobile view will not give a desktop UI action visibility in the mobile UI.
  • There is no equivalent related list for the Mobile UI Action form. This is because the mobile UI does not support multiple views. It will only use the Mobile view, and all mobile UI actions will automatically use this.

 

As with the desktop UI, Mobile UI Actions buttons can appear in different spots when you select the check boxes on the UI Action form. These mobile locations include:

  • List button: The UI Action appears on record lists for the table specified in the Table field.
  • Form button: The UI Action appears on forms for records on the table specified in the Table field.
  • Form more item: The UI Action appears in the More button, which appears as an ellipsis in the lower right corner of the mobile form.

 

List buttons.jpg

 

Mobile UI Actions & Scripting

 

Again, as with desktop UI Actions, mobile UI actions can contain scripts in both the condition and script fields; however, there are some differences and limitations for client-side scripting for the mobile UI. One change in particular is the difference between Mobile and Desktop URL structure, which you'll need to know when using actions like action.setRedirect.

 

You can find out more about scripting and mobile UI actions here: Mobile Client GlideForm (gfrom) Scripting.

 

Need more help with mobile UI issues?

 

KB0551387 - The action.setRedirectURL method is not working for Mobile UI actions. (Fixed as of Eureka Patch 10, Fuji Patch 3.)

KB0535114 - Mobile UI Actions appear on new records when condition has current. (Fixed as of  Eureka.)

Mobile means more than just being on-the-go — it's the ability to get more work done while moving freely about the office. The mobile user interface and the tablet user interface offer different views. It’s very important to understand the capabilities as well as the limitations you will see while using ServiceNow out of the default view and on your mobile devices (both smartphone and tablet).

 

First, let's start with what the default view actually is. This is the view that is given to all forms and lists if no other view is specified. For example, you might have an incident form with a default view that has 10 or more different fields that are editable, but this isn't very aesthetically pleasing for mobile users. Creating a mobile view can allow you to select the fields that are most important to you and your organization, making forms more user friendly.

 

Incident form.jpg

Mobile incident form new.jpg

 

The mobile view is the default view for forms and list layouts on all mobile and tablet devices. Mobile views need to be set up for all tables or the default view will show on your mobile devices but keep in mind there are unsupported features in the mobile view. Creating a mobile view allows you to do away with unexpected behavior for mobile users by removing the unsupported features you have in your default views. A good example of this is certain variable types are not compatible with the mobile view. Removing the fields that have these unsupported variables is a good practice for ensuring users don't come across any errors.

 

Important things to remember about the Desktop View vs. Mobile View:

  • Other views for forms are not available (the only exception is when you don’t set up your mobile view so the default view is used).
  • Additional custom views cannot be created.
  • Some features are unsupported (HTML fields, chat, VTB, and embedded lists, etc.).

 

These UI limitations are built into the mobile UI to limit the amount of information loaded due to the lack of screen real estate. The mobile view allows you to only see the first section on a form. You do have the ability to add additional sections to your form, though it should be noted that this could cause some errors or the inability to save if you do so. This is because you won’t be able to see the additional sections. If you have a mandatory field, you won’t be able to edit it and save. This can also happen if you’re using a default view and there’s a mandatory field on a section other than the main one.

 

If you don’t want to run into any of these pesky problems, it’s extremely important to set up views for all of the tables you commonly access on your mobile devices.

 

The UI limitations are also due to limited hardware on mobile devices, as well as making sure we have a set of features that will function on multiple mobile platforms.

 

The tablet user interface shadows the same rules as mobile. The mobile view is used if it’s been set up and the default view is used otherwise. The big difference between the mobile and tablet views is that the tablet shows more form sections! Don’t get too excited here because the mobile view and tablet view are the same thing. Make sure you still aren’t putting any of those mandatory fields in these additional sections because your mobile users will get an error and won’t be able to save.

 


ServiceNow-Fuji.png

 

When we need information on a known problem that is affecting customers or our own instances, we visit our extensive Known Error Knowledge Base, which is updated every day. These articles help identify issues that we are aware of and are either investigating, fixing, or offering a workaround solution. Since our latest upgrades are usually at the forefront of customer's minds, we make sure we have the latest information on them documented. Most of the known errors and issues in this list involve UI challenges in Fuji. If you see something that affects you, don't forget to subscribe to the Known Error and get real time updates as they are resolved.

 

Here are some of the top Known Error articles on Fuji User Interface issues that ServiceNow employees referenced in August:

 

Issue: Scheduled job "Event Management - create/resolved incidents by alerts" triggers cache flush every 11 seconds

Seen in: Fuji Patch 3, Fuji Patch 6, Fuji Patch 7

What happens: After upgrading to Fuji Patch 7, some instances are reporting slowness due to a defect that is repeatedly triggering a cache flush. Although this does not alter any data, the constant flush and rebuild of the cache is causing a significant load on the system.

Workaround: If the instance was upgraded to an affected version, implementing this workaround brings relief until the instance can be upgraded to a patched version. To implement the workaround, set the Ignore Cache flag to truefor the evt_mgmt.last_calculated_alert_job property. This prevents the cache flush on updates to the property by the Event Management - create/resolved incident job...(click to view the rest of the workaround)


Issue: Upgrading to Fuji causes catalog UI pages to lose styling (CSS) and catalog CMS pages to appear cut off at the bottom of the page

Seen in: Fuji Patch 1, Fuji Patch 3, Fuji Patch 7

What Happens: Catalog UI pages lose their style when the instance is upgraded to Fuji. This is due to a change introduced in Fuji where the call to the CSS file is done within each page, rather than globally. If customers modify the UI page before upgrading, they won't get the latest change, so their pages won't render properly.

Workaround: To overcome most of the CSS issues, each one of the customized pages must include lines to call the missing scripting files...(click to see the rest of the workaround)

Issue: Users without the admin role are unable to create knowledge articles

Seen in: Fuji Patch 6, Fuji Patch 7

What happens: When a knowledge admin (or anyone who should be able to create knowledge) attempts to use the Knowledge => Articles => Create new module, they are brought to a blank knowledge form and are unable to fill in any information and thus can't create articles.

Workaround: Remove the condition from sys_security_acl_65bbd9bcd71221004792a1737e610389... (Click to see the rest of the workaround)

Issue: CMS buttons disappear when entering text into multi-line variable

Seen In: Fuji Patch 1, Fuji Patch 3, Fuji Patch 5, Fuji Patch 6

What happens: As you type new lines of text into the multi-line variable, it will push the Cancel and Submit buttons for the record producer within the iFrame.

Workaround:If you are able to upgrade, review the Fixed Infield to determine the versions that have a permanent fix to this issue. You can also subscribe to this known error article (click Subscribebutton at the top of the article) to receive notifications when more information is available about this issue.

Issue: Reference fields made read-only via UI Policy or Client Script can still be edited using the lookup icon

Seen In: Fuji Patch 1, Fuji Patch 2, Fuji Patch 3

What happens: Reference fields made read-only through a UI Policy or a Client Script can still be edited using the lookup icon (magnifying glass).

Workaround:Make the affected fields read-only through the ACL or make them read-only in the dictionary.

Normally you cannot pass complex objects in the parameters of gs.eventQueue.  It simply won't reconstitute them on the Script Action side.  For example, let's create a simple Script Action.

 

Lab 1.1 - Events: Passing Object Problem

 

1. Navigate to System Policy -> Events -> Registry.  The Registry list view will appear.

 

2. Click the New button.  A blank Event Registration form will appear.  Fill in the form with the following:

    a. Name: object.passing

    b. Table: ObjectPassing

    c. Fired by: Scripts background or fix script

    d. Description: Test of object passing

    e. Click the Submit button to save.

 

1.mini-lab - events-passing objects.JPG

 

3. Navigate to System Policy -> Events -> Script Actions.  The Script Actions list view will appear.

 

4. Click the New button.  A blank Script Action form will appear.  Fill in the form with the following:

    a. Name: ObjectPassing

    b. Event Name: object.passing

    c. Order: 100

    d. Active: checked

    e. Script:

 

  var myObject = event.parm1;

  var message = '---> \n';
  message += 'Name: ' + myObject.name;
  message += '\nNumber: ' + myObject.number;

  gs.log(message, 'SA: ObjectPassing');



 

    f. Click the Submit button to save.

 

5. Click the lock button next to your name in the upper left of the ServiceNow screen.  Elevate your privileges to Security Admin.

 

6. Navigate to System Definition -> Scripts - Background.  The Run script screen will appear.  Type in the following script:

 

var myObject = {name:'test', number:'1234abc'};

gs.eventQueue('object.passing', null, myObject, '');



 

    NOTE: If you are not passing a GlideRecord then the second parameter may be set to null.  Normally this is where current would be placed.

 

    NOTE: I suggest copying this script to notepad as we will be using it later.

 

7. Click the Run script button at the bottom of the form.  A blank results screen will be displayed with the number seconds it took to execute the script.

 

8. Navigate to System Logs -> System Log -> All.  A list view of all log records for today will appear.

    a. Sort by Created descending.

    b. Search for Message begins with --->

    c. It may take a few seconds for the log entry to appear.  When it does the Message field should look something like this:

 

  --->

  Name: undefined

  Number: undefined

 

As you can see our simple object was not passed correctly!

 

Now, to correct this we will need to use JSON.

 

 

Lab 1.2 - Events: Using JSON With an Event

 

1. Navigate to System Policy -> Events -> Script Actions.

2. Edit our ObjectPassing Script Action.

    a. Change line 1 of the script to add the JSON decode:

 

  var myObject = new global.JSON().decode(event.parm1);

  var message = '---> \n';
  message += 'Name: ' + myObject.name;
  message += '\nNumber: ' + myObject.number;

  gs.log(message, 'SA: ObjectPassing');



 

    b. Click the Update button to save.

 

    NOTE: The decode method will be used to reconstitute our object from binary to the original format that was passed.

 

2.mini-lab - events-passing objects.JPG

 

3. Navigate to System Definition -> Scripts - Background. Enter in the following script; which now contains our JSON encode.  Note the addition of a JSON encode.  This changes the object into a simple variable (binary) that can now be passed.

 

var myObject = {name:'test',number:'1234abc'};

gs.eventQueue('object.passing', null, new global.JSON().encode(myObject), '');



 

4. Click the Run script button at the bottom of the form.  A blank results screen will be displayed with the number seconds it took to execute the script.

 

5. Navigate to System Logs -> System Log -> All.

    a. Sort by Created descending.

    b. Search for Message begins with --->

    c. It may take a few seconds for the log entry to appear.  When it does the Message field should look something like this:

 

  --->

  Name: test

  Number: 1234abc

 

Now our object is being passed correctly!

 

This can be done with any complex object you want to pass to the event call (GlideRecord objects in addition to or other than current, object arrays, etc.).  Interestingly you don't have to JSON arrays as they are passed as comma-delimited strings.  However, my recommendation is to JSON them as well just to be safe.

 

Steven Bell.

 

If you find this article helps you, don't forget to "like" it!

Because JavaScript does it for us we don't often have to consider memory management.  However, I bring this up because I have run into a problem with server resources myself; from time-to-time.  It is possible to run out of memory!  If you are working on a really large object array (several million records); you must remember that all of that is in the server's memory.  If you keep adding to it you risk the possibility of running into problems with your instance such as impacting performance.  Perhaps severely.

 

For example, let's say you are working with pulling data from a number of GlideRecord record sets.

 

Pseudo code:

 

1. Get GlideRecord Incident (all records)

2. Loop through all records and store lots of data into an object array.

3. Get GlideRecord Change (all records)

4. Loop through all records and store lots more data into our object array.

5. Get CMDB_CI (all records)

6. Loop through all records and store lots more data into our object array.

 

and so on.  Remember this is hypothetical and not necessarily something you would really want to do!  :-)

 

Now ALL of this stuff is in memory, and it could be literally millions of records!

 

The poor server on the ServiceNow side is trying to keep up with you, paging memory to disk, juggling things around to improve performance, but you are merciless, and keep throwing more stuff for it to manipulate...and it slowly goes to it's metaphorical knees.

 

So, what do you do?  You free up what you no longer need!

 

Back to our Pseudo code:

 

1. Get GlideRecord Incident (all records)

2. Loop through all records and store lots of data into an object array.

3. Set the Incident GlideRecord to null.

4. Get GlideRecord Change (all records)

5. Loop through all records and store lots more data into our object array.

6. Set the Change GlideRecord to null.

7. Get CMDB_CI (all records)

8. Loop through all records and store lots more data into our object array.

9. Set the CMDB_CI GlideRecord to null.

 

So here is an example:

 

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

var stuff = {};
var stuffList = [];

while (incidentRecords.next()) {
    stuff = {};
    stuff.number = incidentRecords.number + '';
    stuff.status = incidentRecords.status + '';
    stuff.assigned_to = incidentRecords.assigned_to.getDisplayValue();
    stuffList.push(stuff);
}

incidentRecords = null; // This frees up the memory that incidentRecords is using.



 

Obviously this is a best practice when manipulating large objects in memory, and a good general practice otherwise.

 

Steven Bell

ACL's have the perception of being hard because they aren't easy to see in action, and have several layers of interactivity.  This blog won't make you a master, but it should ramp up your troubleshooting competency in a hurry.  Before reading you should...

 

Understand how to impersonate users

Understand ACL basics

 

So you're minding your own business, impersonating a user for a new feature you're building when...

SecurityConstraints.png

or...

FieldSecurity.png

 

STEP 1 - TWO BROWSERS / TWO MODES

I usually have two Chrome browsers open, with one in Incognito mode, but you could easily do one FireFox / one Chrome.  The idea here is to allow two different sessions so one browser can simulate the problem, and the other can be used as admin.  We'll call these Debug Browser and Admin Browser respectively.

 

First, as an admin on the Debug Browser, turn on Debug Security.  You'll know its on when it gives you the following message

DebugSecurity.pngDebugSecurityConf.png

Then impersonate the user experiencing security issues on the Debug Browser

 

 

STEP 2 - EXPERIENCE THE ISSUE

Get to the same spot you were previously experiencing issues.  You should now see a TON of new information at the bottom of your screen.  For this post, as beginners, we're only interested in a small portion of it.

 

DebugSecurity-Info.png

 

 

STEP 3 - MOUSE OVER ICONS TO DISCOVER FAILURE POINTS

Lets look at just one of the entries:

DebugSecurity-Entry.png

This tells us three things immediately

  • Which record (context) was evaluating via the hyperlink on the first line
  • That it was evaluating a record ACL on the Problem table for the Read operation.
  • That there were two possible read ACLs, but both failed.

 

But we can learn so much more!  Hover over the icons beside each of the ACL evaluations and it tells you *how* it failed.  Lets hover over the icons on the second ACL that evaluated...

 

Here we're being told the ACL was expecting the user to have no roles.  Our logged in user has no roles, so that part of the ACL was a success!

2DebugSecurity-Hover1.png

The next icon tells us that there was a condition on the ACL.  Whatever it was, we passed.  Success!

2DebugSecurity-Hover2.png

But the third icon tells us the ACL had a script condition, which resulted in false, which caused the ACL to fail.

2DebugSecurity-Hover3.png

 

In table ACLs, all we need is one full ACL success to get the rights, but in this case both ACLs failed, which means the whole record evaluation failed.

 


STEP 4 - INVESTIGATE THE ACLs SPECIFICALLY

Notice anything interesting about the text after the icons that tell us the failure points?  They look like hyperlinks... because they are.

Unfortunately they're useless hyperlinks at this point because we're impersonating a user experiencing an ACL issue.  That user is likely NOT a security admin.  But by Crom, we're going to find out why these two ACLs failed

 

This is why we need two browsers.  First, copy one of the link addresses...

CopyLinkAddress.png

... then paste it into your Admin Browser.  Presto... the *exact* ACL that failed.

ACL.png

 

Having the two browsers open (one in Incognito mode) will allow you to both experience and research the issue simultaneously in real time.  Without this method, you could spend hours bouncing back and forth between users and screens.

 

Hope that was of some value to you.  With all the time you're going to save, why not drop a bookmark or a like.

 

Oh, and remember to disable your security debugging too.

Disable.png

Steven Bell

Mini-Lab - Events

Posted by Steven Bell Sep 3, 2015

The purpose of this lab is demonstrate how to create then call an event that triggers a Script Action that calls a Script Include!  Additionally I wanted to show how you could simulate a Business Rule call in Scripts - Background.

 

CAVEAT: I did not go into extreme how-to detail, and there is a certain expectation of proficiency with the ServiceNow platform to successfully complete this lab.

 

Mini-Lab - Events.jpg

1. Install the currentFactory Script Include from Share

 

NOTE: I repackaged my currentFactory code into it's own Script Include.  You can download the XML here: Share.

 

To load it up into your instance do the following:

 

a. Download the currentFactory file from ServiceNow share.

b. In your instance raise yourself to Security Admin (click the lock symbol next to your name, check the box, and click the OK button).

c. Navigate to System Definition -> Script Includes.  A list view of all Script Includes will be displayed.

d. Right-Click next to the name field to bring up the context menu.

e. Click the Import XML link at the bottom of the context menu.

f. Click the Choose File button.

g. Navigate to your download folder and pick the file labeled: sys_script_include_CSTableUtils.xml and click the Ok button.  You will be returned to the import form.

h. Click the Upload button. The new Script Include will be added to your Script Include list.

 

2. Create a Script Include to do something!

 

a. Navigate to System Definition -> Script Includes.  You may still be there.

b. Click New.  The a blank Script Include form will be displayed.

c. Fill in the following:

 

NOTE: After filling in the name field then tabbing off the form a template with your Script Include name will be autofilled into the Script field.

 

Name: MyIncidentUtils

Accessable From: All application scopes

Client callable: unchecked

Active: checked

Protection Policy: -- None --

Script:

 

var MyIncidentUtils = Class.create();

MyIncidentUtils.prototype = {
    initialize: function() {
    },

    addTenDaysToOpenedAt : function(incident) {
        gs.log('---> ' + incident.number + ' - Opened At: ' + incident.opened_at, 'SI: MyIncidentUtils.AddTenDaysToOpenedAt');
        var openedAt = new GlideDateTime(incident.opened_at.getDisplayValue());
        var opened_at = openedAt.getDate();
        opened_at.addDays(10);

        incident.opened_at = opened_at;
        incident.update();

        gs.log('---> Date after: ' + incident.opened_at, 'SI: MyIncidentUtils.AddTenDaysToOpenedAt');
    },

    type: 'MyIncidentUtils'
};


 

d. Click the Submit button.  NOTE: Always check your work, and click the syntax to make sure there were no problems.

 

 

3. Create a new event - Register the Event name

 

a. Navigate to System Policy -> Events -> Registry.  The Event Registry list view will be displayed.

b. Click on the New button.  The Event Registration form will be displayed.

c. Fill the following in:

 

    Event name: incident.opened_at.add_ten_days

    Table: Global (this would be Incident if you were firing the event from an Incident Business Rule)

    Fired by: Scripts - Background

    Description: Add ten days to the incident

 

d. Click the Submit button to save.

 

4. Create a Script Action to fire the Script Include

 

a. Navigate to System Policy -> Events -> Script Actions.   The Script Actions list view will be displayed.

b. Click the New button.  The new Script Action form will be displayed.

c. Fill in the form with the following:

 

    Name: AddTenDaysToOpenedAt

    Event name: incident.opened_at.add_ten_days

    Active: Checked

    Script:

 

gs.log('---> ' + current.number + ' - Opened At: ' + current.opened_at, 'SA: AddTenDaysToOpenedAt');

// Try/Catch...just in case
try {
    new MyIncidentUtils().addTenDaysToOpenedAt(current);
}
catch(err) {
    gs.log('---> ERROR: ' + err, 'SA: AddTenDaysToOpenedAt');
}


 

d. Click the Submit button to save.

 

5. Using currentFactory fire the new event from Scripts - Background

 

a. Type the following script into your Run Script field:

 

var current = CSTableUtils.currentFactory('incident', 'Ascending', 'active=true');

// did we get one back, and we want to know what the value was before it was changed
gs.print(current.number + ' - Opened At: ' + current.opened_at);

gs.eventQueue('incident.opened_at.add_ten_days', current);


 

b. Click the Run script button.

c. Note the Incident Number and the Date.

 

Example response:

 

  *** Script: INC0000002 - Opened At: 2014-11-19 23:07:12

 

6. Verify

 

a. Navigate to System Policy -> Events -> Event Log

b. Order the Events List View by Created descending.

    i. Search the Name field for: incident.opened_at.add_ten_days

    ii. Parm1 of the record should contain the Incident Number.  Write this number down.

    iii. Navigate to Incident -> Open.

    iv. Search for the Incident number listed in the Event log.

c. Open that incident and see if the Opened At date has been advanced ten days.

d. Navigate to System Logs -> System Log -> All

    i. Order by Created descending

    ii. In the messages field search for --->

    iii. Observe the records written there.

 

And that is all there is to it!  Have fun!

 

Steven Bell.

 

If you find this article helps you, don't forget to "like" it!

As you may have noticed when navigating the smartphone mobile interface, the URL format is significantly different from what’s used in the Desktop and Tablet UI.  This is important for a few reasons.  In additional to being able to identify where you're looking on the instance using the URL, this information can also be used to create links and modules specifically for use in the Mobile UI, as will be shown in the examples below.  I hope to better explain the URL structure, including information on how to apply filter options and auto-populate field data using the URL. Filter options and field data in the URL can be helpful both when viewing existing URLs to understand their content or in creating your own URLs to use in scripts.

 

 

This is the basic schema for a mobile URL:

https://<base URL>/$m.do#/<list/form>/<table>/q:<query>^<query>^EQ

 

Elements of a mobile URL include:

$m.do#This is where the mobile interface is specified.  Browsing to $m.do in any supported browser will display the Mobile UI.
<list/form>Using either “list” or “form” here will specify whether a list of form will be displayed by the URL.
<table>This is the table being accessed, such as “incident” or “change_request."
q:<query>^This is how queries are created.  These begin with q: and are separated by the carat (^) symbol. Examples below:
  • /$m.do#/list/incident: displays a list of all incidents
  • /$m.do#/list/incident/q:active=true: displays a list of all incidents where the “active" field is true
  • /$m.do#/!list/incident/q:active=true^priority=1:  displays a list of all incidents where the “active" field is true and the priority field is 1.
  • /$m.do#/form/incident/d71da88ac0a801670061eabfe4b28f77Displays the form for the incident record where the sys ID matches the sys ID “d71da88ac0a801670061eabfe4b28f77”.  As with desktop URLs, form URLs in mobile use the record’s sys ID.
  • /$m.do#/form/incident/-1:  As with the Desktop UI, a -1 can be used in place of a sys ID to create a new record.
^EQThe EQ denotes the end of the Query.

 

 

Generating a Record with a URL Link

Forms in mobile can be displayed with pre-populated fields, as they can in the desktop UI.  This can be useful, for example, when creating modules for your mobile UI.  For, example, take the "Create New" mobile module used to create new incidents in the mobile UI. 

 

You can find this on your instance by navigating to System Mobile UI → Navigator Apps, then selecting the "Incident" Application Menu.  In the Modules related list you'll see "Create New"

 

If you look at the Path field for this item, you'll see the URL used to create a new incident in the Mobile UI.

 

form/incident/-1

 

 

Let's say, for example, we wanted to create a module that created new incidents with pre-populated priority, short description, and assignment group.  This is an example URL we could use to accomplish that:

 

/$m.do#/form/incident/-1/priority=2^short_description=MY TEST^assignment_group=d625dccec0a8016700a222a0f7900d06

 

 

Let me break this down for you a bit:

/$m.do#/form/incident/-1/As with the desktop URL navigation, using a sys_id of -1 directs the instance to a new record form.
priority=2This sets the priority to "2 - High."  When setting values for choices, the value (2) is used rather than the label.
^As with list filters, each field is separated by a caret(^) symbol.
short_description=MY TESTThis sets the short description to "MY TEST."  Spaces are allowed, but characters that have meaning within a URL, for example a " \ " character, will not work and will break the URL.
assignment_group=d625dccec0a8016700a222a0f7900d06This last field is a reference field.  In this case, we're setting the Assignment Group to "Service Desk."  For a reference field like this one, we use the sys_id of the records being referenced, which, in this case is the is a sys_user_group record.

 

 

An Example of Use within ServiceNow

For an example of when something like this can be helpful, let’s take a look at "Create Incident” record producer.  On this record producer is a script that first detects whether or not the scripts is being run in the Mobile UI, and then provides an appropriate link based on that information.

 

var isMobile = GlideMobileExtensions.getDeviceType() == 'm';

var link = isMobile ? '#/list/incident/q:active=true%5Ecaller_id=javascript:gs.user_id()%5EEQ' : 'home.do';

 

In the above script, when the Mobile UI is detected, a link using the URL formatting for mobile is selected. This will link to a list of active incidents where the Caller ID field matches the current user:

generate record.jpeg

 

As we've seen above, knowing the URL format for mobile will allow you to create links and modules for use in the Mobile UI.  With this information, you'll be able to create content on your instance specifically for mobile use, and provide a better experience for your mobile users.

 

A quick note about javascript used within mobile links:  While the example above works as expected, the Mobile UI does not support dynamic urls which direct to a form (specific record).

Slava

The new old search

Posted by Slava Sep 2, 2015

What I particularly like about ServiceNow is that there are plenty of features to discover even after many years of exposure to the product. Not long ago I stumbled upon a global out-of-box UI Action named Search that dated back to February 2008 and strangely contained no script, no condition, no action name – no nothing. The only hint was a comment that read: Search button on our search screens. Search screens? Honestly, I had never heard of them before. Although Wiki makes mention of this feature in Introduction to Searching and Administering Applications and Modules, the information provided is too scarce to catch the readers' attention.

 

In essence, appending _search to the name of any table (e.g., sys_user_search.do) will take you to what looks like a normal form for that table, though with a few important differences:

 

  • No form UI actions (except the Search button)
  • No mandatory fields
  • No read-only fields
  • No default values
  • No hidden fields
  • Dropdowns instead of checkboxes (in order to differentiate false from not selected)

 

In other words, the form that is normally used for inserting, updating or viewing individual records turns into an advanced search form. Instead of scrolling up and down the endless list of fields in the condition builder, all you have to do is fill in a couple of fields on a familiar form, press the Search button and – voila! – you have the filtered list you were after. Isn’t that a good time-saver? Yes, it is, though as usual there are some trade-offs to bear in mind:

 

  • No contains or equals search (starts with is the default and only operator for text fields)
  • You cannot search for an empty value or --None--
  • Due to reference qualifiers, you cannot search for values that are not normally selectable in a given reference field.

 

Nonetheless, the feature seems to be quite handy. From my own experience, there are a number of situations where it really helps:

 

  • When working on a multi-language instance, it is impossible to remember the localized labels for all fields. With the search screen feature, there is no need to first look the label up on the form and then go back to the list to do a search.
  • Sometimes people right nasty client scripts that dynamically change field labels on the form. As a result, trying to find the field in the list filter can drive you crazy. With search screens, however, you do not have to worry about it at all.
  • And last but not least, search screens seem to be the quickest way to explore the form layout as no fields are hidden. Unlike form layout configuration page where you have to switch between form sections, on a search screen you have the entire form at your fingertips.

 

I am sure you will find your own use cases. Do not hesitate to share them in comments!

Filter Blog

By date: By tag: