Read-Only and Mandatory workaround

xiaix
Tera Guru

Became victim of the famous Read-Only & Mandatory field problem.   But... I found a workaround (albeit a hack) and am curious if this is the best solution.   I'm worried about future compatibility with instance upgrades.

Here's the deal...

We have an HR form that has an "Employee ID" field, and a "Caller" (sys_user reference) field.   HR does not want to enter the name of the employee into the Caller field, but rather enter their Employee ID, upon which will auto-fill in the Caller field.

So, we needed the "Caller" field to be mandatory but read-only also.

The solution was to put a String field (not reference) on the form and make THAT read-only, not mandatory.   We then made the reference Caller field mandatory, and although we couldn't make it a read-only, we simply hid it via element.style.display = "none" in an onLoad client script.

The frustrating part was HR also wanted the "i" hover button to appear next to the Caller field so they could hover over it to get more Employee info.   Well that was a head-scratcher, so here's what we did.

Allow me to explain all steps:

First, the onChange client script that makes the User ID field auto fill in the hidden Caller field:

find_real_file.png

function onChange(control, oldValue, newValue, isLoading)

{

      if (isLoading)

              return;

      if (newValue == '')

      {

              g_form.setValue('u_caller', '');

              g_form.setValue('u_caller_display', '');

              return;

      }

      if (!g_form.getControl('u_caller'))

              return;

      var gr = new GlideRecord('sys_user');

      gr.addQuery('employee_number', g_form.getValue('u_userid'));

      gr.query();

      if (gr.next())

      {

              g_form.setValue('u_caller', gr.sys_id);

              g_form.setValue('u_caller_display', gr.name);

      }

      else

      {

              g_form.setValue('u_caller', '');

              g_form.setValue('u_caller_display', '');

              g_form.showErrorBox('u_userid', 'Invalid UserID');

      }    

}

Now, the onLoad client script that hides the reference Caller field, and copies the "i" info hover button up to the string Caller field and places it perfectly:

find_real_file.png

function onLoad()

{

      var outer = '';

      // First, capture the "i" reference icon code and stuff it into the "outer" variable

      var info = gel('view.u_human_resources.u_caller');

      if (info)

              outer = info.outerHTML;

      // Hide the real reference field that's mandatory, but not read-only

      var a = gel('element.u_human_resources.u_caller');

      if (a)

              a.style.display = "none";

     

      // Create the "i" hover button shell.   (it's just an <a> tag)

      var caller_info_hover_button = document.createElement('a');

      if (outer != '')

      {

              // Create an object of the String version of the Caller.

              // This should be a <div>, which contains other divs, the last one being the placeholder

              // for the "i" button icons to hover over.

              var u_caller_display_FIELD = gel('element.u_human_resources.u_caller_display');

             

              // Get the last <div> of the string Caller element

              var lastChild = u_caller_display_FIELD.lastElementChild;

             

              if (lastChild)

              {

                      // Append our "i" hover button shell we created above.

                      lastChild.appendChild(caller_info_hover_button);

                     

                      // Take all that copied info we did at the start and stuff it into this shell

                      caller_info_hover_button.outerHTML = outer;

              }

      }

}

End Result:

find_real_file.png

find_real_file.png

find_real_file.png

18 REPLIES 18

Taking your advice, Oliver, I am now using GlideAjax:



Client Script:


function onChange(control, oldValue, newValue, isLoading)


{


      if (isLoading)


              return;




      if (newValue == '')


      {


              g_form.setValue('u_caller', '');


              g_form.setValue('u_caller_display', '');


              return;


      }




      if (!g_form.getControl('u_caller'))


              return;


     


      var ga = new GlideAjax('HR_Return_User_SysID_by_Employee_Number');


      ga.addParam('sysparm_name', 'getUserSysID');


      ga.addParam('sysparm_employeeNumber', g_form.getValue('u_userid'));


      ga.getXMLWait();


      var answer = ga.getAnswer();


     


      if (answer != "false" && answer != "null" && answer)


      {


              var vals = answer.split('|');


             


              g_form.setValue('u_caller', vals[0]);


              g_form.setValue('u_caller_display', vals[1]);


      }


      else


      {


              g_form.setValue('u_caller', '');


              g_form.setValue('u_caller_display', '');


              g_form.hideErrorBox('u_userid');


              g_form.showErrorBox('u_userid', 'Invalid UserID');


      }


}









Script Include:


var HR_Return_User_SysID_by_Employee_Number = Class.create();


HR_Return_User_SysID_by_Employee_Number.prototype = Object.extendsObject(AbstractAjaxProcessor, {




      getUserSysID: function()


      {


              var employeeNumber = this.getParameter('sysparm_employeeNumber');


             


              var gr = new GlideRecord('sys_user');


              gr.addQuery('employee_number', employeeNumber);


              gr.query();


             


              if (gr.next())


                      return (gr.getValue('sys_id').toString() + "|" + gr.getValue('name').toString().trim());


              else


                      return "false";


      }


});





Tested and working.   All in the name of 'best practices'.  


oliverschmitt
ServiceNow Employee
ServiceNow Employee

Hi David,



Unfortunately I need to disappoint you It is still not considered best practice and will pop up as well.



GlideRecord will retrieve one or a bunch of full records to the client synchronously, which blocks the browser and could potentially end up in UX struggles for the user.


GlideAjax.getXMLWait() does basically the same, just for on record.


GlideAjax.getXML() is async and does NOT block the browser and used in combination with JSON you can decide every character that's been exchanged between client and server, which makes it perfect to exchange the minimal set of information.



That said only "GlideAjax.getXML()" is considered best practice. I though I wrote that into the Best Practices and will double check with a colleague what happend.



http://wiki.servicenow.com/index.php?title=Client_Script_Best_Practices#Example:_Asynchronous_GlideA...


Fixed:



function onChange(control, oldValue, newValue, isLoading)


{


      if (isLoading)


              return;




      if (newValue == '')


      {


              g_form.setValue('u_caller', '');


              g_form.setValue('u_caller_display', '');


              return;


      }




      if (!g_form.getControl('u_caller'))


              return;


     


      var ga = new GlideAjax('HR_Return_User_SysID_by_Employee_Number');


      ga.addParam('sysparm_name', 'getUserSysID');


      ga.addParam('sysparm_employeeNumber', g_form.getValue('u_userid'));


      ga.getXML(bestPracticesArePainsInTheArse);


}




function bestPracticesArePainsInTheArse(response)


{


      var answer = response.responseXML.documentElement.getAttribute("answer");


     


      if (answer != "false" && answer != "null" && answer)


      {


              var vals = answer.split('|');


             


              g_form.setValue('u_caller', vals[0]);


              g_form.setValue('u_caller_display', vals[1]);


      }


      else


      {


              g_form.setValue('u_caller', '');


              g_form.setValue('u_caller_display', '');


              g_form.hideErrorBox('u_userid');


              g_form.showErrorBox('u_userid', 'Invalid UserID');


      }


}









oliverschmitt
ServiceNow Employee
ServiceNow Employee

Awesome work David! I appreciate to see that some people actually follow up on the best practice. It is too often left behind!



Great work!!!


The irony is that I'm utilizing a best practice to execute code in a hack.