Templates in Service-now are a great feature that can save youAdvanced Templates and other users a lot of time. The out-of-box template features are documented here. As I’ve worked with customers that used templates in their implementations, I’ve noticed a few things that I think make templates in Service-now more flexible and easier to work with. Most of these things deal with the way that templates are accessed, secured, and applied. This article shows how you can apply the same customizations to your Service-now implementation that I use for my clients. This entire customization has also been packaged into an ‘Advanced Templates’ update set to save time in implementing.

This customization includes the following features:

  • Improved template selection interface through the use of reference field popup
  • Context-sensitive template filtering available by using a reference qualifier
  • Improved Templates security setup

This article is provided mostly for documentation purposes. It’s probably best just to use the ‘Advanced Templates’ update set. After applying the update set you will need to make sure to add the ‘Template’ field to any form that you want to use templates on. It may also be necessary to flush your system cache after personalizing the forms so that the client scripts work properly. The update set makes templates work for the task, cmdb_ci, and kb_knowledge tables (and their extended tables) as long as the ‘Template’ field has been added to the forms for those tables.
—The method of applying templates does not change after applying these modifications. Users still access all template options from the ‘Templates’ context menu items on a form.—

1) Create your ‘Template’ fields

You’ll need to create a field called ‘Template (u_template)’ on any table that you want to apply templates to. The field needs to be a reference field that references the ‘Template (sys_template)’ table. In my setup, this requires the creation of a ‘Template’ field on the following 3 tables.

  • Task (task)
  • Configuration item (cmdb_ci)
  • Knowledge(kb_knowledge)

Because the ‘Template’ table is a system table it won’t display if you try to create the reference field from the ‘Personalize form’ or ‘Personalize list’ UI. There is a system property that you can change (glide.ui.permitted_tables) to make this table visible, but I’ve found that it’s easier to simply create the dictionary entries directly rather than having to change the system property.

You can create the fields (along with their corresponding labels) by creating a dictionary entry for each table EXACTLY as shown below. The ONLY thing that should change is the table that you add the field to. This ensures that you end up with a field called ‘u_template’ with a label of ‘Template’ and a reference qualifier that helps filter the available templates for a table.

2) Add ‘Template’ fields to their respective forms

Because this solution relies on an ‘onChange’ client script to identify the template, you’ll need to add the ‘Template’ field to the form of any table that you want to apply templates to. We’ll create a UI Policy in the next step to hide the field, but it does need to be a part of the form.

If you’re using the ‘Advanced Templates’ update set, you’ll still need to perform this manual step after applying the update set. I purposely don’t modify any form or list layouts so that your current layouts don’t get completely overwritten.

3) Create a UI Policy to hide the ‘Template’ field

You probably won’t want your users to even see the ‘Template’ field, but it still needs to be on the form. You can create a UI Policy to hide the field by creating a UI Policy on each table with settings as shown in the screen shot below. I created 3 UI Policies with the ‘Inherit’ checkbox selected — one for ‘task’, ‘cmdb_ci’, and ‘kb_knowledge’.

4) Create an onChange client script to apply the template

You’ll need an onChange client script that looks for a change to the ‘Template’ field and then applies the template record that gets populated. Here are the settings you’ll want for your client script. Again you’ll need a client script for each table that you will be using templates on. I used 3 client scripts with the ‘Inherited’ checkbox checked — one for ‘task’, ‘cmdb_ci’, and ‘kb_knowledge’.

ApplyTemplate Client Script
Name: ApplyTemplate
Active: True
Global: True
Type: onChange
Table: Task
Inherited: True
Field name: Template
Script:

function onChange(control, oldValue, newValue, isLoading) {
   if(isLoading)
      return;
   try{
      var template = g_form.getValue('u_template');
      if(template != ''){
         //Apply the selected template
         applyTemplate(template);
         //Wait for the template to be applied and then clear the template field
         window.setTimeout(clearTemplate,2000);
      }
   }
   catch(e){
      //alert(e);
   }
}

function clearTemplate(){
   g_form.setValue('u_template', '');
}

5) Modify the ‘template_context’ UI Macro

Note that you may choose just to create a UI action button as described below rather than modifying this macro. In some ways, the UI action button may be better so that you don’t modify the macro and lose future updates to it.
Change the ‘XML’ field value on the ‘template_context’ UI Macro to this…

<?xml version="1.0" encoding="utf-8"?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
   
    <!--
      We need to get the list of templates that can be applied so that the context submenu of
      available templates can be built.  This context menu is used for each section of a form.
      The code below gets the list of templates and saves them in a ChoiceList as value=sys_id
      and label=name.  Since this operation can be costly when there are lots of templates and
      we use the exact same list for each section of the form, we build the ChoiceList one time
      and then save it in the 'jvar_templates' variable to be used for the other sections.
     
      The jvar_templates variable is cleared in form.xml after it has been used.
     
       Note: it would be better to build this list on-demand, as it is not used in the 99% case,
             but doing that requires some changes to the way that the context menus work.
   -->
    <j2:if test="$[empty(jvar_templates)]">
        <g2:evaluate var="jvar_templates" jelly="true" object="true">
            <!-- generate a query that is
             table='incident' and active=true and user=null and group=null
                OR
             table='incident' and active=true and user=me or user=one of my groups
         
             Only way to do this currently is with an encoded query.  The NQ (new query)
             indicates the OR condition.
          -->
            if (templates_list == null) {
               if (typeof GlideChoiceList != 'undefined')
                  var templates_list = new GlideChoiceList();
               else
                  var templates_list = new Packages.com.glide.choice.ChoiceList();
               var tt = new GlideRecord('sys_template');
               var common = 'table=' + ${ref}.getTableName() + '^active=true';
               var global = 'global=true';
               var mine = 'user=javascript:gs.getUserID()^ORgroup=javascript:getMyGroups()';
               var query = common + "^" + global + "^NQ" + common + "^" + mine;
               tt.addQuery(query);
               tt.orderBy('name');
               tt.setWorkflow(false);
               tt.query();
               while (tt.nextRecord()) {
                  var roles = tt.roles;
                  if (jelly.jvar_session.hasRole(roles))
                     templates_list.add(tt.sys_id + '', tt.name + '');
               }
            }  
           
            templates_list;
        </g2:evaluate>
    </j2:if>
    <script>
        var mTemplate = new GwtContextMenu('context_templatesub${jvar_section_id}');
        mTemplate.clear();
       
        var mApplyTemplates = new GwtContextMenu('context_applytemplatesub${jvar_section_id}');
        mApplyTemplates.clear();
    </script>
    <j2:if test="$[!jvar_templates.isEmpty()]">
        <script>
            mTemplate.addHref("${gs.getMessage('Apply Template')}", "openTemplateSelector()");

            function openTemplateSelector(){
               //Check to make sure the 'u_template' field is present on the form
               if(g_form.getControl('u_template')){
                  //Get the table name
                  var tbl = g_form.getTableName() + '.u_template';
                  var tblDisp = 'sys_display.' + tbl;
                  var e = gel(tblDisp);
                  if (!e.ac){
                     new AJAXReferenceCompleter(e, tbl, 'null', 'true');
                  }
                  reflistOpen(tbl, 'u_template', 'sys_template', 'null', 'false', 'true');
               }
               else{
                  alert("There is no 'Template' field on the form.\nPlease add the 'Template [u_template]' field to the form to use templates.");
               }
            }
        </script>
    </j2:if>
    <j2:if test="$[${ref}.isNewRecord() == false]">
        <j2:if test="$[jvar_session.hasRole('template_editor')]">
            <input type="hidden" name="sysverb_save_as_template" id="sysverb_save_as_template"/>
            <script>
                mTemplate.addHref("${gs.getMessage('Save as Template')}", "TemplateRecord.save(document.getElementById('sysverb_save_as_template'))");
            </script>
            <j2:if test="$[jvar_session.hasRole('admin')]">
                <input type="hidden" name="sysverb_save_all_as_template" id="sysverb_save_all_as_template"/>
                <script>
                    mTemplate.addHref("${gs.getMessage('Save All as Template')}", "TemplateRecord.save(document.getElementById('sysverb_save_all_as_template'))");
                </script>
            </j2:if>
        </j2:if>
    </j2:if>
    <g2:evaluate var="jvar_personalize_templates" expression="gs.hasRole('template_editor,template_editor_group,template_editor_global')"/>
    <j2:if test="$[jvar_personalize_templates == true ]">
        <script>
            mTemplate.addHref("${gs.getMessage('Edit Templates')}", "showList('sys_template', 'table.active', '${ref}.true');");
        </script>
    </j2:if>
    <j2:if test="$[jvar_personalize_templates == true || !jvar_templates.isEmpty() ]">
        <script>
            gcm.addMenu("${gs.getMessage('Templates')}", mTemplate);
        </script>
    </j2:if>
</j:jelly>

6) Secure your template records

Create a business rule on the ‘Global’ table. This business rule will be an advanced reference qualifier for the ‘Template’ field.

filterTemplates Business Rule
Name: filterTemplates
Table: Global
Active: True
Script:

function filterTemplates() {
var answer = '';
//Optional switch statement can be used to apply reference qualifiers on a per-table basis
/*switch (current.getTableName()) {
case 'incident':
answer = answer + 'templateLIKEcmdb_ci=' + current.cmdb_ci + '^';
break;
default: answer = '';
}*/

var common = 'table=' + current.getTableName() + '^active=true';
answer = answer + common;
return answer;
}

Modify the ‘SNC Template Query’ business rule so that it has the following script

SNC Template Query Business Rule

roTemplates();
function roTemplates(){
//Enforce row-level read permissions for system templates
if(gs.getSession().isInteractive()){
var answer = '';
//Check to see if the user can read all templates
if(!gs.hasRole('template_editor_global')){
//Check to see if the user can read some templates
if(gs.hasRole('template_editor_group') || gs.hasRole('template_editor')){
//User can read templates for themselves, their groups, or global
answer = 'global=true^ORuser=javascript:gs.getUserID()^ORgroup=javascript:getMyGroups()';
current.addEncodedQuery(answer);
}
//Else user cannot read any templates
else{
current.addEncodedQuery('active=true^active=false');
}
}
}
}

Modify the ‘sys_template’ write and delete ACL rules by removing any ‘Requires role’ related list entries and adding the following script

‘sys_template’ write/delete ACL script

var answer = false;
if(gs.hasRole('template_editor_global')){
answer=true;
}
else{
if(gs.hasRole('template_editor') && current.user == gs.getUserID()){
answer = true;
}
if(gs.hasRole('template_editor_group') && (current.user == gs.getUserID() || gs.getUser().isMemberOf(current.group))){
answer = true;
}
}

—OPTIONAL—

Some users would rather have a more prominent option for selecting templates to apply. It is very simple to create a UI Action button to add to your forms. This has been added one to the ‘task’, ‘cmdb_ci’, and ‘kb_knowledge’ tables in the update set. The UI Actions should have the following settings…

‘Apply Template’ UI Action Button
Name: Apply Template
Table: Task
Active: True
Client: True
Form Button: True
onClick: templatePop()
Condition: gs.hasRole(‘template_editor’)
Script:

function templatePop(){
//Get the table name
var tbl = g_form.getTableName() + '.u_template';
var tblDisp = 'sys_display.' + tbl;
var e = gel(tblDisp);
if (!e.ac){
new AJAXReferenceCompleter(e, tbl, 'null', 'true');
}
reflistOpen(tbl, 'u_template', 'sys_template', 'null', 'false', 'true');
}

Related Links: