Last active
May 29, 2024 12:22
-
-
Save thisnameissoclever/69b4c537f7897c163fd86d2fc795f5ac to your computer and use it in GitHub Desktop.
ServiceNow Animated Progress Message with Collapsible Details
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Show an animated loading message such as "Loading...", where the dots will be | |
* animated with the interval specified in msInterval; first showing "Loading.", then | |
* "Loading..", then "Loading...", up to the number of dots indicated in maxDots. | |
* Once maxDots is reached, the message will be reset to "Loading." and the animation | |
* will repeat until stopAnimatedLoadingMessage() is called. | |
* | |
* @param {String} fieldName - The name of the field on which to show the loading message. | |
* @param {String} messageText - The loading message to be shown, which will be followed | |
* by animated dots (or whatever character is specified in dotChar, if specified). | |
* @param {"info"|"warning"|"error"} [messageType="info"] - The message type | |
* ('info', 'warning', or 'error'). | |
* @param {Number} [maxDots=3] - The maximum number of "dots" to show before resetting to | |
* 1, and repeating the animation. | |
* @param {Number} [msInterval=180] - The number of milliseconds between animation increments. | |
* In the example code shown below for example, the string "Loading." will be shown in the | |
* form message for 170 milliseconds, after which the message will be cleared and | |
* "Loading.." will be shown for a further 170 seconds, then "Loading...", and then - because | |
* maxDots is set to 3, the message will be reset and "Loading." will be shown again for 170ms. | |
* @param {String} [dotChar="."] - The character to add to the end of messageText and animate | |
* up to the number of characters indicated in maxDots (or up to 3, if maxDots is not | |
* specified). | |
* @returns {number} - The interval ID for the animation process. You can save this in a | |
* variable and pass that variable into stopAnimatedLoadingMessage(), but that isn't strictly | |
* necessary since this interval ID is also stored in the client data object. | |
* | |
* @example | |
* showAnimatedLoadingFieldMessage( | |
* 'name', | |
* 'Loading', | |
* 'info', | |
* 3, | |
* 170, | |
* '.' | |
* ); | |
* | |
* //Do some stuff that may take a while... | |
* | |
* stopAnimatedLoadingMessage(); | |
* showExpandingFormMessage( | |
* 'This text is shown immediately.', | |
* 'This text is hidden until the expand link is clicked.', | |
* 'Show more', | |
* 'Hide details' | |
* ); | |
*/ | |
function showAnimatedLoadingFieldMessage( | |
fieldName, | |
messageText, | |
messageType, | |
maxDots, | |
msInterval, | |
dotChar | |
) { | |
/* | |
Storing this in the closure scope as an object and accessing it by reference. | |
This is to preserve the state (the number of dots following the message text) | |
across executions of the below anonymous function throughout each interval. | |
*/ | |
var intervalDots = {'count' : 1}; | |
var originalMessageTest = messageText; | |
messageType = (typeof messageType === 'undefined') ? 'info' : messageType; | |
msInterval = (typeof msInterval === 'undefined') ? 180 : msInterval; | |
maxDots = (typeof maxDots === 'undefined') ? 3 : maxDots; | |
dotChar = (typeof dotChar === 'undefined') ? '.' : dotChar; | |
var intervalID = setInterval(function() { | |
var i; | |
var messageText = originalMessageTest; | |
if (intervalDots.count > maxDots) { | |
intervalDots.count = 1; | |
} else { | |
intervalDots.count++; | |
} | |
//Starting i at 1 since the first loop execution will add one dot. | |
for (i = 1; i < intervalDots.count; i++) { | |
messageText += dotChar; | |
} | |
g_form.hideFieldMsg(fieldName); | |
g_form.showFieldMsg(fieldName, messageText, messageType); | |
}, msInterval, intervalDots); | |
g_user.setClientData('loading_message_interval_id', intervalID); | |
return intervalID; | |
} | |
/** | |
* Stop showing an animated loading message that was initiated by calling the | |
* showAnimatedLoadingMessage() function. | |
* This function will stop the animation, and clear all form messages. | |
* Unfortunately, ServiceNow does not provide us with the ability to clear only a | |
* specific form message, so *all* messages will be cleared when this function executes. | |
* | |
* @param {String} fieldName - The name of the field on which the animated loading message is | |
* currently being shown. Any existing messages on this field will be cleared. | |
* @param {Number} [intervalID=g_user.getClientData('loading_message_interval_id')] - | |
* Optionally specify the interval ID returned from the showAnimatedLoadingMessage() function. | |
* The showAnimatedLoadingMessage() method stores the interval ID in the client data object | |
* so if this argument is not specified, then the interval ID will be retrieved from there. | |
* | |
* @example | |
* showAnimatedLoadingMessage('Loading', 3, 170, '.'); | |
* //Do some stuff that may take a while... | |
* stopAnimatedLoadingMessage(); | |
*/ | |
function stopAnimatedLoadingFieldMessage(fieldName, intervalID) { | |
intervalID = (typeof intervalID === 'undefined') ? | |
g_user.getClientData('loading_message_interval_id') : | |
intervalID; | |
if (!intervalID) { | |
throw new Error( | |
'Unable to stop interval. Invalid interval ID specified, and previous ' + | |
'interval ID cannot be found in client data object.' | |
); | |
} | |
clearInterval(intervalID); | |
g_form.hideFieldMsg(fieldName); | |
} | |
/** | |
* Display a simple animated loading message using CSS animations. | |
* This loading message can be cleared by simply calling g_form.clearMessages() when loading | |
* has finished. | |
* | |
* @param {String} [loadingMsgText="Loading"] - The loading message text. | |
* This text will be followed by an animated ellipses in the message that's shown at the top | |
* of the form. | |
* @param {Number} [msAnimationDuration=800] - The number of milliseconds for which the | |
* animation should last. | |
* This is the TOTAL duration of the animation, not the interval between each dot in the | |
* ellipses being animated. | |
* @param {Number} [steps=5] - The number of "steps" for the animation to take over the | |
* duration specified in msAnimationDuration. | |
* 4-5 is recommended. The default value is 5. | |
* | |
* @example | |
* //With no arguments, will show "Loading..." with the ellipses being animated over 800ms. | |
* showSimpleLoadingMessage(); | |
* | |
* //With one argument, the message you specify will be shown followed by an animated "...". | |
* showSimpleLoadingMessage('Processing request'); | |
* | |
* //With two arguments, you can specify the duration - this animation will be slower. | |
* showSimpleLoadingMessage('Processing', 1500); | |
* | |
* //With three arguments, you can specify the number of "steps" the animation will take over | |
* // the specified number of milliseconds. This will be slower and take more animation steps. | |
* showSimpleLoadingMessage('Processing', 1200, 8); | |
*/ | |
function showSimpleLoadingMessage(loadingMsgText, msAnimationDuration, steps) { | |
//Set default values | |
loadingMsgText = (typeof loadingMsgText === 'undefined') ? 'Loading' : loadingMsgText; | |
msAnimationDuration = (typeof msAnimationDuration !== 'number') ? 800 : msAnimationDuration; | |
steps = (typeof steps !== 'number') ? 5 : steps; | |
var loadingMsg = '<style>\n' + | |
'.loading:after {\n' + | |
' overflow: hidden;\n' + | |
' display: inline-block;\n' + | |
' vertical-align: bottom;\n' + | |
' -webkit-animation: ellipsis steps(' + steps + ', end) ' + msAnimationDuration + 'ms infinite;\n' + | |
' animation: ellipsis steps(' + steps + ', end) ' + msAnimationDuration + 'ms infinite;\n' + | |
' content: "\\2026";\n' + //ascii code for the ellipsis character | |
' width: 0px;\n' + | |
'}\n' + | |
'\n' + | |
'@keyframes ellipsis {\n' + | |
' to {\n' + | |
' width: 20px;\n' + | |
' }\n' + | |
'}\n' + | |
'\n' + | |
'@-webkit-keyframes ellipsis {\n' + | |
' to {\n' + | |
' width: 20px;\n' + | |
' }\n' + | |
'}\n' + | |
'</style>'; | |
loadingMsg += '<span class="loading">' + loadingMsgText + '</span>'; | |
g_form.addInfoMessage(loadingMsg); | |
} | |
/** | |
* Display an expandable form message. This message will be shown at the top of whatever form | |
* this code is executed on. The text in firstLine will be shown, but the text in flyoutText | |
* will be hidden until the user clicks the 'expand' link. | |
* | |
* @param {String} firstLine - The first line of text in the message, which will be shown | |
* immediately when this code executes. Unlike the text in flyoutText, this text will not | |
* be hidden. | |
* @param {String|HTML_TEXT} flyoutText - This text will be hidden by default, but will be shown | |
* once the user clicks the 'expand' link (which you can customize by setting expandLinkText). | |
* @param {String} [expandLinkText="Show more"] - Optionally specify the text to be shown as | |
* a clickable link, which will cause the form message to expand and display the text | |
* specified in flyoutText. | |
* @param {String} [collapseLinkText="Hide details"] - Optionally specify the text to be shown | |
* after the user clicks the 'expand' link text (specified in expandLinkText). | |
* This text will be shown when the message is expanded and the text specified in flyoutText | |
* is shown. Upon clicking this text, the message will be collapsed, flyoutText will be hidden, | |
* and the link text will revert back to that specified in expandLinkText. | |
* | |
* @example | |
* showExpandingFormMessage( | |
* 'This message expands', | |
* flyoutListHTML, | |
* 'Show more', | |
* 'Hide details' | |
* ); | |
*/ | |
function showExpandingFormMessage(firstLine, flyoutText, expandLinkText, collapseLinkText) { | |
var formMsg = firstLine; | |
expandLinkText = (typeof expandLinkText !== 'string') ? 'Show more' : expandLinkText; | |
collapseLinkText = (typeof collapseLinkText !== 'string') ? 'Hide details' : collapseLinkText; | |
formMsg += '<div>'; | |
formMsg += '<p><a href="#" onclick="javascript:jQuery(this.parentNode).next().toggle(200); ' + | |
'this.innerText = ((this.innerText === \'' + expandLinkText + '\')?\'' + collapseLinkText + | |
'\':\'' + expandLinkText + '\');">' + expandLinkText + '</a></p>'; | |
formMsg += '<div style="display: none;">'; | |
formMsg += flyoutText; | |
formMsg += '</div>'; | |
formMsg += '</div>'; | |
g_form.addInfoMessage(formMsg); | |
} | |
/** | |
* Convert an array into an HTML list, using the specified list style-type. | |
* | |
* @param {Array} msgArray - An array of strings to return as HTML list elements. | |
* @param {String} [listStyle=disc] - Optionally specify the list-style-type | |
* Valid style-types can be found at the following link: | |
* https://www.w3schools.com/cssref/playdemo.asp?filename=playcss_list-style-type | |
* For example, for a simple bulleted list, you can this to 'disc' (or not specify a value, as | |
* disc is the default value). For an ordered numerical list, you can set this to 'decimal'. | |
* For roman numerals, you can set this to 'upper-roman' or 'lower-roman'. | |
* You can even have no list bullet type, which just shows the elements in a list without a | |
* bullet, by setting this value to the string 'none'. | |
* @param {String} [listElementPrefixStr=] - Optionally specify a string to show at the beginning | |
* of each element in the array, in the returned HTML. | |
* For example, if you have an element like "Element one", and you set this value to the string | |
* "- ", then the returned HTML list will contain an element with the string "- Element one". | |
* @returns {string} - An HTML list of all the elements in msgArray. | |
* @example | |
* var arrListElements, flyoutListHTML; | |
* showSimpleLoadingMessage('Loading'); | |
* | |
* //Do some stuff that may take a while... | |
* | |
* arrListElements = ['First list element', 'Second list element', 'Third list element']; | |
* flyoutListHTML = getHTMLListFromArray( | |
* arrListElements, | |
* 'disc', | |
* 'Element: ' | |
* ); | |
* g_form.clearMessages(); | |
* showExpandingFormMessage( | |
* 'This message expands', | |
* flyoutListHTML, | |
* 'Show more', | |
* 'Hide details' | |
* ); | |
*/ | |
function getHTMLListFromArray(msgArray, listStyle, listElementPrefixStr) { | |
var i, msgText; | |
listStyle = (typeof listStyle !== 'string') ? 'disc' : listStyle; | |
listElementPrefixStr = (typeof listElementPrefixStr !== 'string') ? '' : listElementPrefixStr; | |
if (msgArray.length <= 0) { | |
return ''; | |
} | |
msgText = '<ul style="list-style: ' + listStyle + '">'; | |
for (i = 0; i < msgArray.length; i++) { | |
msgText += '<li>' + listElementPrefixStr + msgArray[i] + '</li>'; | |
} | |
msgText += '</ul>'; | |
return msgText; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment