A better custom GlideModal with autoresize and preserved UI Policies and Client Scripts
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
10-25-2022 05:09 AM - edited 10-25-2022 05:10 AM
Background and case needs:
I had a need to let agents easily close Incidents, Requested Items (or any ticket type) from a UI Action, and wanted to open the record in a specific view. This is easier in Configurable Workspace, but not so elegant in UI16.
My needs was pretty simple, I wanted to be able to open a record(or create a new one) with a specific view, but strip away everything but the form itself. So I came up with a UI Page which is dynamic, resizeable and only has two buttons, Cancel and Save.
After looking into GlideModal, GlideModalForm and GlideModalWindow, I figured that GlideModalv3 was the best bet. GlideModal takes certain parameters such as UI Page, however, this has several drawbacks, such as UI Policies and Client Scripts won't be preserved. Opening GlideModal without specifying a UI Page opens the record in form view, which does preserve UI Policies and Client scripts. But contains a bunch of unecessary elements that clutter the experience.
GlideModal also doesn't have a dedicated onLoad event handler (that I could find). And I needed functionalities such as closing the modal when a save operation has been made.
The solution:
I made a UI Page which is pretty straight forward, but technically quite advanced.
It takes certain parameters from the UI action and passes those into the UI Page.
Disclaimers:
This is merely a proof of concept.
Parameters are:
- sysparm_table_name
- sysparm_sys_id
- sysparm_view_name
- sysparm_view_forced
Each of these are parsed at the UI Page and builds a url string which is then passed into an iframe.
<g:evaluate jelly="true"> var sysparm_table_name = RP.getParameterValue('sysparm_table_name'); var sysparm_sys_id = RP.getParameterValue('sysparm_sys_id'); var sysparm_view_name = RP.getParameterValue('sysparm_view_name'); var sysparm_view_forced = RP.getParameterValue('sysparm_view_forced'); var url = sysparm_table_name + '.do?sys_id=' + sysparm_sys_id + '&sysparm_view=' + sysparm_view_name + '&sysparm_form_only=true&sysparm_clear_stack=true&sysparm_view_forced=' + sysparm_view_forced; </g:evaluate>
The end result:
The modal auto resizes whenever the content within the form changes
UI PAGE DETAILS:
Name: form_view
HTML Section:
<g:evaluate jelly="true"> var sysparm_table_name = RP.getParameterValue('sysparm_table_name'); var sysparm_sys_id = RP.getParameterValue('sysparm_sys_id'); var sysparm_view_name = RP.getParameterValue('sysparm_view_name'); var sysparm_view_forced = RP.getParameterValue('sysparm_view_forced'); var url = sysparm_table_name + '.do?sys_id=' + sysparm_sys_id + '&sysparm_view=' + sysparm_view_name + '&sysparm_form_only=true&sysparm_clear_stack=true&sysparm_view_forced=' + sysparm_view_forced; </g:evaluate> <g:evaluate var="sysparm_title" jelly="true"> (jelly.RP.getWindowProperties().get('title') || '') + ''; title = new GlideStringUtil().unEscapeHTML(title); </g:evaluate> <style> iframe { height: 0; visibility: hidden; transition: opacity .35s ease-in-out .15s; opacity: 0; } iframe.visible { height: auto; visibility: visible; opacity: 1; } .loader { width: 8px; height: 8px; border-radius: 50%; display: block; margin:15px auto; position: relative; background: #FFF; box-shadow: -18px 0 #FFF, 18px 0 #FFF; box-sizing: border-box; animation: shadowPulse 2s linear infinite; } @keyframes shadowPulse { 33% { background: #FFF; box-shadow: -18px 0 RGB(var(--now-button--secondary--color,var(--now-color--neutral-18,22,27,28))), 18px 0 #FFF; } 66% { background: RGB(var(--now-button--secondary--color,var(--now-color--neutral-18,22,27,28))); box-shadow: -18px 0 #FFF, 18px 0 #FFF; } 100% { background: #FFF; box-shadow: -18px 0 #FFF, 18px 0 RGB(var(--now-button--secondary--color,var(--now-color--neutral-18,22,27,28))); } } </style> <section class="iframe"> <iframe id="iframe_record" src='${url}' width='100%' height='100%' frameborder="0" onLoad="load_iframe()"> Please Enable iFrames</iframe> <span class="loader"></span> </section> <section class="button_buttons"> <g:ui_form onsubmit="return invokeConfirmCallBack('ok');"> <table width="100%"> <tr> <td colspan="2" align="right"> <g:dialog_buttons_ok_cancel ok_text="${gs.getMessage('Submit')}" ok_title="${gs.getMessage('Submit')}" ok="invokePromptCallBack('ok');" ok_type="button" cancel_text="${gs.getMessage('Cancel')}" cancel_title="${gs.getMessage('Cancel')}" cancel="invokePromptCallBack('cancel')" cancel_type="button" /> </td> </tr> </table> </g:ui_form> </section>
Client Script:
function load_iframe() { // Ready event for UI Action to listen to var glideModal = GlideModal.get(); glideModal.fireEvent('onload'); // Ready iFrame var iframe = this.$j("#iframe_record")[0]; var iframeWindow = iframe.contentWindow ? iframe.contentWindow : iframe.contentDocument.defaultView; // Remove uncessary elements iframeWindow.$j('.section_header_div_no_scroll.form_title').remove(); iframeWindow.$j('.form_action_button_container').remove(); iframeWindow.$j('.related-links-wrapper').remove(); // Page timing div is loaded after setTimeout(function(){ iframeWindow.$j('#page_timing_div').remove(); }, 200); // Clean ups to the DOM iframeWindow.$j('.form_body>.tabs2_section.tabs2_section_0').css('margin-block-end', '0'); iframeWindow.$j('HTML[data-doctype=true] .section_header_content_no_scroll').css('padding-bottom', '0').css('overflow', 'hidden'); iframeWindow.$j('.section_header_content_no_scroll').css('background-color', 'transparent'); // Set iframe height to height of the content var formHeight = iframeWindow.$j('form').outerHeight(); iframe.style.height = formHeight + "px"; // Make iframe visible iframe.classList.add('visible'); // Observe if form resizes var iframeForm = iframeWindow.$j('form.form_body'); var resize_ob = new ResizeObserver(function(entries) { // since we are observing only a single element, so we access the first element in entries array var rect = entries[0].contentRect; // current height var height = rect.height; iframe.style.height = height + "px"; }); // start observing for resize resize_ob.observe(iframeForm[0]); // Remove loader indicator $j('.loader').remove(); } function invokePromptCallBack(type) { // Ready iframe var iframe = this.$j("#iframe_record")[0]; var iframeWindow = iframe.contentWindow ? iframe.contentWindow : iframe.contentDocument.defaultView; // Ready a copy of g_form for the form inside the iframe var d_form = iframeWindow.g_form; var gdw = GlideModal.get(); if (type == 'ok') { d_form.save(); setTimeout(function() { gdw.destroy(); location.reload(); }, 200); } else { gdw.destroy(); } }
UI Action code example:
function showCloseNotesForm(){ //Get the table name and sys_id of the record var tableName = g_form.getTableName(); var sysID = g_form.getUniqueValue(); var view_name = 'close_modal'; var view_forced = 'true'; var title = 'Resolve ' + g_form.getValue('number'); var gm = new GlideModal('form_view'); gm.setTitle(title); gm.setWidth(700); gm.setPreference('focusTrap', true); gm.setPreference('sysparm_table_name', tableName); gm.setPreference('sysparm_sys_id', sysID); gm.setPreference('sysparm_view_name', view_name); gm.setPreference('sysparm_view_forced', view_forced); gm.on("onload", onLoad); gm.render(); function onLoad() { var iframe = gm.$window.find("iframe")[0]; var iframeWindow = iframe.contentWindow ? iframe.contentWindow : iframe.contentDocument.defaultView; var gm_form = iframeWindow.g_form; // Set values to the form gm_form.setValue('state', '6'); gm_form.setValue('close_code', 'Resolved – Permanent Fix Applied'); } }
Additional comments:
The UI Action opens a glideModal with the UI Page named form_view.
Furthermore, it has an onLoad parameter which is defined in the client script of the UI Page.