Scripted Printing: How to Print Records from ServiceNow with Special Printing Requirements

We published a blog post about our PDF Generator Application recently that showed how custom PDF forms can be created using ServiceNow to be downloaded as attachments or sent as email notifications that cover a large percentage of use cases. However, some custom needs and special scenarios might need to be followed for printing.

Imagine you need to print out something in a custom format. It might be a specific asset tag, perhaps with a barcode or QR code, a simple guest card for visitors in your facilities, a standard layout of your conference room tags for your offices, etc. You just need to connect your printer to a dedicated computer and print the content you have generated using ServiceNow.

In this blog post, we will show you how you can print something custom-made using ServiceNow, which can be applied to any custom content both on the back-end and on the Service Portal.

So, how can you achieve custom printing from ServiceNow?

Let us take an example scenario and assume that we need to print license plates from ServiceNow.

For that, first we need to create a table, let’s simply name it License plate, and add a single field ID to it. This will hold the license number.

Then let us create a sample record that we will play with:

We will need a Formatter (System UI > Formatters):

We can now add the formatter to the Form:

Nothing is visible yet since we have not added any content to the formatter. We also need to create a UI Macro, whose name needs to be put to the Formatter field of the Formatter appended with “.xml”:

After we do these, the test content will appear in the Form:

Now we can start working on the license plate layout. Please note: As mentioned in the introduction, this article has been created for educational purposes, so our license plate is an example only.

Next steps:

  • Create the layout.
  • Use some CSS (for arrangement, colours and font).
  • Add an EU logo. For the record, at the time of releasing this blog post, the UK is still a member of the EU.

Please note:

Simply linking an external image or font might be a quick solution, but there are things to consider here. First, the CORS setting of the third party might change over time, so the image or font you have been pulling in from there might unexpectedly become unavailable via linking. Second, should that link become slow or unavailable or should the content be removed for any reason, your feature would stop working, as expected. And third, we would be using other people’s bandwidth for our purposes even if the font and the logo consume only a few hundred kilobytes in our current case.

So, how can we overcome this challenge? Let us upload the font and the logo to our web server. Oh wait, we do not have a web server where we can store files. Should we use AWS or Azure or a dedicated CDN? Well, that won’t be necessary because we actually DO HAVE a web server, which is operated by ServiceNow. We can upload the font and the logo as an attachment (e.g. to the UI Macro itself) and pull it directly from there:

We can access the attachment from /sys_attachment.do?sys_id=[ATTACHMENT_SYS_ID]

Let us see how our license plate looks like in ServiceNow after applying some HTML+CSS:

Now that we have the design ready, let us start the printing itself. We need a UI Action. How does that work?

  • Let us grab the HTML content from the UI Macro’s HTML tag with the styling that we did.
  • Append a tiny script to it that will print the page.
  • Open a new window with the content we just assembled.
  • From the print form of the OS shown, we can select and configure the printer.
  • Let us close the window and not leave anything hanging around.

Simple, is not it? Let’s then create a UI Action, which will be a client-side Form button. We need it to be available on update only.

Troubleshooting if the Form gets broken.

However, there seems to be something wrong with our UI Action. When we open the Console, we can see the following error message:

SyntaxError: “” literal not terminated before end of script u_license_plate.do:354:42

The following line is linked as the source of the error:

var htmlSuffix = “<script>window.print();</script></body></html>”;

While this a totally valid JS string, it seems like the “</script>” part confuses the rendering engine. So let’s trick the engine by simply creating our string via concatenation:

var htmlSuffix = “<script>window.print();<” + “/script></body></html>”;

Now that we don’t have an explicit “</script>” in the string, the Form looks as it should:

The following line is linked as the source of the error:

var htmlSuffix = “<script>window.print();</script></body></html>”;

While this a totally valid JS string, it seems like the “</script>” part confuses the rendering engine. So let’s trick the engine by simply creating our string via concatenation:

var htmlSuffix = “<script>window.print();<” + “/script></body></html>”;

Now that we don’t have an explicit “</script>” in the string, the Form looks as it should:

We troubleshoot if we get further errors.

TypeError: $ is not a function[Learn More] u_license_plate.do:522:16

And the following line is linked as the source of the error:

var content = $(“print_section”).outerHTML;

Something unusual seems to happen when we paste

“$(“print_section”).outerHTML;”

to the Console, but it just works fine. What is going on then? In newer ServiceNow releases, scripts are isolated by default. It means they cannot use jQuery (nor $, not even document) – all of these will be null and we are shown an error like the one above. We need to un-check the Isolate script field of the UI Action; however, it is not available on the Form out-of-the-box. So we either need to add it to the Form or customize the List view and change it there. After that, everything works fine, the print preview is opened in a new tab and our license plate is ready to be printed:

Once the OS’s print dialog is closed, our browser tab gets closed automatically.

I have done a similar custom printing previously in Firefox and configured everything for the print preview, but let us just cross-check things. Is that also working in Chrome? And indeed, we have just run into another issue – the page seems to look empty in Chrome:

Troubleshooting:

It is easy to discover that we need to tick Background graphics to print the colours; however, the text is still missing:

Could the problem be the custom font? Let us comment on it and see how that goes:

Now we have the text, which means that it was the custom font that caused the issue. It looks that when we immediately print the document, the printing happens before the rendering engine would actually render any text or load the Print preview form, and because of this, the custom font does not get loaded, and the text becomes invisible.

This solution adds some delay to the automated printing:

setTimeout(function() { window.print(); }, 500);

instead of simply using

window.print();

Side note: 500 ms might not be enough to render the document under all circumstances, and so, onload might be a better place to trigger the printing. However, we want to keep this example as simple as possible for now.

Chrome has now rendered the document correctly, and so, the print preview looks fine:

You should check out some additional things, such as paper layout and size, margins, scaling, headers and footers, but those should be straightforward. Also, do not forget to double-check if the feature works in all the browsers used.

We have seen how simple the custom printing from ServiceNow is; however, there can be some challenges to it. This article contains an example for custom printing and can be adapted to any custom requirements. It is easy to use other font types, a 1D-barcode; additionally, some QR/2D code generator libraries are also available.

A remark on the Service portal usage. As UI Macros cannot be re-used “as is” on the Service Portal, you need to do some additional work to make the functionality available there. On the other hand, it is simpler to copy the features to the Service Portal, as you can copy-paste:

  • The style from the UI Macro to the CSS.
  • The layout from the UI Macro to the HTML template.
  • The logic from the UI Action to the Client Script.

Resources used in our example custom printing scenario:

license_plate_barcode UI Macro XML

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
    <div id="barcode">
        <style>
            @font-face {
                font-family: 'Code128';
                src:url('/sys_attachment.do?sys_id=0f1561ab4fa7230069b9121f9310c7c9') format('truetype');
            }

            #barcode {
                margin: auto;
                display: table;
                text-align: center;
            }

            #bars {
                font-family: 'Code128';
                font-size: 10rem;
                line-height: 12rem;
            }
            
        </style>
        <div id='bars'><g>&#160;$[current.u_id]&#160;</g></div>
        <g>$[current.u_id]</g>
    </div>
</j:jelly>

license_plate UI Macro XML

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
    <div id="print_section">
        <style>
            @font-face {
            font-family: 'Charles Wright';
            src:url('/sys_attachment.do?sys_id=5dc1db5f4f23230069b9121f9310c7ae') format('truetype');
            
            }

            #print_section {
                font-family: 'Charles Wright';
                display: table;
                margin: auto;
            }
            
            #print_section img {
                width: 10rem;
            }
            
            #print_section div {
                display: table-cell;
            }

            #eu {
                background-color: #039;
                color: #FFED00;
                font-size: 5rem;
                text-align: center;
                border-radius: 2rem 0 0 2rem;
            }
            
            #idText {
                background-color: #FFED00;
                font-size: 15rem;
                border-radius: 0 2rem 2rem 0;
            }
        </style>
        <div id='eu'>
            <img src='/sys_attachment.do?sys_id=a6ee4dab4f67230069b9121f9310c7d7' style='display: block; margin: auto;' />
            GB
        </div>
        <div id='idText'><g>&#160;$[current.u_id]&#160;</g></div>
    </div>
</j:jelly>

Print UI Action script

Onclick: printPlate()
Script:

function printPlate() {
    var htmlPrefix = "<html><body>";
    //var content = $("print_section").outerHTML;
    var content = document.getElementById("print_section").outerHTML;
    //var htmlSuffix = "<script>window.print();<" + "/script></body></html>";
    var htmlSuffix = "<script>setTimeout(function() { window.print(); window.document.close(); window.close(); }, 1000);<" + "/script></body></html>";
    var win = window.open('', 'print_content');
    win.document.write(htmlPrefix + content + htmlSuffix);
    //win.document.close();
    //win.close();
}

Widget HTML template:

<div>

  <div id="print_section">
    <div id='eu'>
      <img src='/sys_attachment.do?sys_id=a6ee4dab4f67230069b9121f9310c7d7' style='display: block; margin: auto;' />
      GB
    </div>
    <div id='idText'><g>&#160;{{data.ID}}&#160;</g></div>
  </div>

  <div id="barcode">
    <div id='bars'><g>{{data.ID}}</g></div>
    <g>{{data.ID}}</g>
  </div>
 
  <button id="print" class="btn btn-default">
    Print
  </button>              

</div>


Widget CSS template:

@font-face {
  font-family: 'Code128';
  src:url('/sys_attachment.do?sys_id=0f1561ab4fa7230069b9121f9310c7c9') format('truetype');
}

@font-face {
  font-family: 'Charles Wright';
  src:url('/sys_attachment.do?sys_id=5dc1db5f4f23230069b9121f9310c7ae') format('truetype');

}
#barcode {
  margin: auto;
  display: table;
  text-align: center;
}

#bars {
  font-family: 'Code128';
  font-size: 10rem;
  line-height: 12rem;
}


Widget server script template:

(function() {
    var gr = new GlideRecord('u_license_plate');
    gr.get($sp.getParameter("sys_id"));
    data.ID = gr.u_id.toString();
})();

Widget client controller template:

function() {
  /* widget controller */
  var c = this;
    
    $("#print").on('click', print);
}

function print() {
    var htmlPrefix = "<html><body>";
    var content = document.getElementById("print_section").outerHTML;
    var htmlSuffix = "<script>setTimeout(function() { window.print(); window.document.close(); window.close(); }, 1000);<" + "/script></body></html>";
    var win = window.open('', 'print_content');
    win.document.write(htmlPrefix + content + htmlSuffix);
}

About the Author

David Tereanszky is a Lead ServiceNow Developer focusing on back-end development and integrations, experienced in web services, agile practices, enterprise-scaled development and scalable solutions. He has worked on numerous international projects in various roles including remote development, and business analysis within multinational teams for customers in Finland, Sweden, Malta, the UK and CEE countries.

Updates

Blog