GooglePickerAPI in ServiceNow UIPage

Kingstan M
Kilo Sage

Greetings, ServiceNow Community.

 

I am facing a challenge in GooglePickerAPI on ServiceNow UI Page. Well, the code renders but the popUP for auth., won't come. But when i save the code as html doc n run on my liveServer then it is all fine. I do not know where am wrong. Any advise?

 

UIPageCodeBelow

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">

	<html>

	<head>
		<title>Picker API Quickstart</title>
		<meta charset="utf-8" />
	</head>

	<body>
		<p>Picker API API Quickstart</p>

		<!--Add buttons to initiate auth sequence and sign out-->
		<button id="authorize_button" onclick="handleAuthClick()">Authorize</button>
		<button id="signout_button" onclick="handleSignoutClick()">Sign Out</button>

		<pre id="content" style="white-space: pre-wrap;"></pre>
		<div id="pickerDiv" class="picker-dialog"
			style="height: 500px; width:800px; border: 1px solid red; position: relative; top: 50%;"></div>

		<script type="text/javascript">
			const SCOPES = 'https://www.googleapis.com/auth/drive.metadata.readonly';

        const CLIENT_ID =  // clientID
        const API_KEY = // apiKey
        const APP_ID =  // projectNumber

        let tokenClient;
        let accessToken = null;
        let pickerInited = false;
        let gisInited = false;


        document.getElementById('authorize_button').style.visibility = 'hidden';


        function gapiLoaded() {
            gapi.load('picker', intializePicker);
        }


        function intializePicker() {
            pickerInited = true;
            maybeEnableButtons();
        }


        function gisLoaded() {
            tokenClient = google.accounts.oauth2.initTokenClient({
                client_id: CLIENT_ID,
                scope: SCOPES,
                callback: '', // defined later
            });
            gisInited = true;
            maybeEnableButtons();
        }


        function maybeEnableButtons() {
            if (pickerInited &amp; gisInited) {
                document.getElementById('authorize_button').style.visibility = 'visible';
            }
        }


        function handleAuthClick() {
            tokenClient.callback = async (response) => {
                if (response.error !== undefined) {
                    throw (response);
                }
                accessToken = response.access_token;
                document.getElementById('signout_button').style.visibility = 'visible';
                document.getElementById('authorize_button').innerText = 'Refresh';
                await createPicker();
            };

            if (accessToken === null) {
                tokenClient.requestAccessToken({ prompt: 'consent' });
            } else {
                tokenClient.requestAccessToken({ prompt: '' });
            }
        }





        function createPicker() {


            const view = new google.picker.View(google.picker.ViewId.DOCS);
            view.setMimeTypes("application/pdf,image/png,image/jpeg,image/jpg,image/gif,image/svg+xml,application/vnd.ms-excel,application/vnd.google-apps.spreadsheet,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/msword,application/vnd.google-apps.document,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,video/x-flv,video/mp4,video/quicktime,video/mpeg,video/x-matroska,video/x-ms-asf,video/x-ms-wmv,video/avi,audio/mpeg,audio/wav,audio/ac3,audio/aac,audio/ogg,audio/oga,audio/x-m4a,application/zip")

            const picker = new google.picker.PickerBuilder()
                .enableFeature(google.picker.Feature.NAV_HIDDEN)
                .enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
                .setDeveloperKey(API_KEY)
                .setAppId(APP_ID)
                .setOAuthToken(accessToken)
                .addView(view)
                .addView(new google.picker.DocsUploadView())
                .setCallback(pickerCallback)

            picker.build();

            const myDiv = document.getElementById('pickerDiv');
            const iframe = document.createElement('iframe');
            iframe.setAttribute("src", picker.toUri());
            iframe.style.width = "100%";
            iframe.style.height = "100%";
            myDiv.appendChild(iframe)

        }


        function pickerCallback(data) {

            if (data.action === google.picker.Action.PICKED) {

                document.getElementById('content').innerText = JSON.stringify(data, null, 2);

            }
        }
		</script>
		<script async='true' src="https://apis.google.com/js/api.js" onload="gapiLoaded()"></script>
		<script async='true' src="https://accounts.google.com/gsi/client" onload="gisLoaded()"></script>

		<style>
			.picker-dialog {
				border: 1px solid red;
			}
		</style>
	</body>

	</html>


</j:jelly>

 

UIPageTryIt.png

 

Many thanks,

Kingstan.

 

 

1 ACCEPTED SOLUTION

-O-
Kilo Patron

To provide an example, this is how I would do it:

- create a record in table content_css, where field Style contains:

.x-content {
	white-space: pre-wrap;
}

.x-picker-dialog {
	border: 1px solid red;
	height: 500px;
	margin: auto;
	position: relative;
	top: 50%;
	width: 800px;
}

- create record in table sys_ui_script (UI Script) where field API Name contains googleLibrary and field Script contains the script originally in field HTML of the UI Page:

const SCOPES = 'https://www.googleapis.com/auth/drive.metadata.readonly';
const CLIENT_ID = '1';
const API_KEY = '1';
const APP_ID = '1';

...

function pickerCallback (data) {
	if (data.action === google.picker.Action.PICKED) {
		document.getElementById('content').innerText = JSON.stringify(data, null, 2);
	}
}

- modify the UI Page's field HTML to contain:

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
	<html>

	<head>
		<!-- use external style sheet (sys_id e8fba397978a3150594ffbc71153af5d is that of a record in table content_css) -->
		<link href="e8fba397978a3150594ffbc71153af5d.cssdbx?v=1.0" rel="stylesheet" />
		<title>${HTML: gs.getMessage('Picker API Quickstart') }</title>
		<meta charset="utf-8" />
	</head>

	<body>
		<!-- use external script (moniker googleLibrary is field name of the record in table sys_ui_script that contains the script -->
		<script src="googleLibrary.jsdbx?v=1.0" type="text/javascript"></script>

		<p>${HTML: gs.getMessage('Picker API API Quickstart') }</p>

		<!-- Add buttons to initiate auth sequence and sign out -->
		<button id="authorize_button" onclick="handleAuthClick()">${HTML: gs.getMessage('Authorize') }</button>
		<button id="signout_button" onclick="handleSignoutClick()">${HTML: gs.getMessage('Sign Out') }</button>

		<pre id="content" class="x-content"></pre>

		<div id="pickerDiv" class="x-picker-dialog"></div>

		<script async="true" onload="gapiLoaded()" src="https://apis.google.com/js/api.js"></script>
		<script async="true" onload="gisLoaded()" src="https://accounts.google.com/gsi/client"></script>
	</body>

	</html>
</j:jelly>

Note that I have updated the class names to not conflict with already defined class names (e.g picker-dialog to x-picker-dialog). I have also added Jelly expressions to translate texts. The keyword HTML in the Jelly expressions (${HTML: ...}) indicates to the (Jelly) processor that the expression's result should be escaped for HTML. The GUID e8fba397978a3150594ffbc71153af5d used in attribute href of the link element is the Sys ID of the style record in table content_css. File extensions .cssdbx and .jsdbx are the way to indicate to ServiceNow that it should look up a record by Sys ID in table content_css and by API Name in table sys_ui_script respectively. Also adding tags like html, head, title, meta and body only make sense if you mark the UI Page as a Direct one.

View solution in original post

12 REPLIES 12

It may be necessary to configure a CORS policy in ServiceNow to allow the google API to fetch resources from ServiceNow. But this is a best guess as I don't know, I only assume what exactly has been requested by whom.

You should have a look at the network transactions in the browser console to catch which XMLHttpRequest causes the error and configure a CORS exception (in ServiceNow) for apis.google.com based on what you find out.

 

Define a CORS rule

Adam Syed
Tera Contributor

Actually it is the restriction from Google @Kingstan M Have you faced this issue?

Access to XMLHttpRequest at 'https://apis.google.com/js/api.js' from origin 'https://<instancename>.service-now.com'

Adam Syed
Tera Contributor

First of all, thanks for this information. I am facing below error when implementing this in our dev environment. I have tried it in my personal and company google developer consoles, but the issue is same. Can you please help on how to resolve this issue with CORS policy?

Access to XMLHttpRequest at 'https://apis.google.com/js/api.js' from origin 'https://<instancename>.service-now.com' has been blocked by CORS policy: Request header field x-transaction-source is not allowed by Access-Control-Allow-Headers in prefliight response.