A better custom GlideModal with autoresize and preserved UI Policies and Client Scripts

Vegard S
Kilo Sage

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 + '&amp;sysparm_view=' + sysparm_view_name + '&amp;sysparm_form_only=true&amp;sysparm_clear_stack=true&amp;sysparm_view_forced=' + sysparm_view_forced;
</g:evaluate>

The end result:

VegardS_0-1666699805618.png

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 + '&amp;sysparm_view=' + sysparm_view_name + '&amp;sysparm_form_only=true&amp;sysparm_clear_stack=true&amp;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. 

 

0 REPLIES 0