Service Portal Form Fields, Part X

“No matter how far you have traveled down the wrong road, turn back.”
— Turkish Proverb

Recently, it was brought to my attention that my little form field tag experiment did not provide the same functionality for a “choice list” that you get with the out-of-the-box sn-choice-list tag that is shipped with ServiceNow. I went ahead and dug into the source code behind the sn-choice-list, and it definitely does a lot more and provides quite a bit more flexibility. It was definitely far superior to my own feeble offering. I wanted mine to be able to do all of that.

My first thought was to just grab all of the code and stuff it into my own directive, tweaking the attribute names to conform to the snh- prefix that I had been using with all of the others. Unfortunately, that turned out to be quite a bit more of an adventure than I had originally anticipated. After further review, I made the cowardly decision to revert my code back to it’s pre-adventure state, and determined that it was time for a different approach.

My second thought was that my first thought was rather ill considered, particularly since it suddenly occurred to me that I could just wrap the existing sn-choice-list tag just exactly the way that was already wrapping the existing sn-record-picker tag. Why copy the code when you can just reference it in place and leave the future maintenance to someone else? If I were a smarter guy, that would have been my first thought and I would have saved myself a lot of pointless work that I just ended up throwing out the window. Oh, well.

I still wanted to keep my own simple choicelist option, though, so I ended up renaming that one to select, and then creating a new version of choicelist. As you can see, the new choicelist is pretty much a letter for letter copy of reference, which is my implementation of the sn-record-picker tag.

if (type == 'radio' || type == 'inlineradio') {
	htmlText += buildRadioTypes(attrs, name, model, required, type);
} else if (type == 'select') {
	htmlText += buildSelect(attrs, name, model, required);
} else if (SPECIAL_TYPE[type]) {
	htmlText += buildSpecialTypes(attrs, name, model, required, type, fullName, label);
} else if (type == 'reference') {
	htmlText += "      <sn-record-picker field=\"" + model + "\" id=\"" + name + "\" name=\"" + name + "\"" + passThroughAttributes(attrs) + (required?' required':'') + "></sn-record-picker>\n";
} else if (type == 'choicelist') {
	htmlText += "      <sn-choice-list sn-model=\"" + model + "\" id=\"" + name + "\" name=\"" + name + "\"" + passThroughAttributes(attrs) + (required?' required':'') + "></sn-choice-list>\n";
} else if (type == 'textarea') {
	htmlText += "      <textarea class=\"snh-form-control\" ng-model=\"" + model + "\" id=\"" + name + "\" name=\"" + name + "\"" + passThroughAttributes(attrs) + (required?' required':'') + "></textarea>\n";
} else {
	htmlText += "      <input class=\"snh-form-control\" ng-model=\"" + model + "\" id=\"" + name + "\" name=\"" + name + "\" type=\"" + type + "\"" + passThroughAttributes(attrs) + (required?' required':'') + "/>\n";
}

I basically copied two lines of code and then made a few edits on the copied lines. That’s it! That was definitely easier than the path that I had started on when I first set out to solve this problem. Now, I just needed to test it. I kept my original choicelist option under its new name (select), so I wanted to be able to test both. I pulled up my little test widget and copied the line that I used to test the original choicelist and made a copy. Then I tweaked one to test the select option and the other to test out the new choicelist option.

<snh-form-field
  snh-model="c.data.select"
  snh-name="select"
  snh-label="Select"
  snh-type="select"
  snh-required="true"
  snh-choices='[{"value":"1", "label":"Choice #1"},{"value":"2", "label":"Choice #2"},{"value":"3", "label":"Choice #3"},{"value":"4", "label":"Choice #4"}]'/>
<snh-form-field
  snh-model="c.data.choicelist"
  snh-name="choicelist"
  snh-label="Choice List"
  snh-type="choicelist"
  snh-required="true"
  sn-value-field="value"
  sn-text-field="label"
  sn-items="c.data.choicelistchoices"/>

Since the whole purpose of bringing in the stock sn-choice-list in the first place was to allow for the use of a variable for the choices, I went ahead and defined a variable for that purpose and populated it in the server-side code:

data.choicelistchoices = [{"value":"1", "label":"Choice #1"},{"value":"2", "label":"Choice #2"},{"value":"3", "label":"Choice #3"},{"value":"4", "label":"Choice #4"}];

All that was left now was to bring up the test page and see how things turned out.

Select option and Choice List option rendered on the page

Well, it all seems to work. I guess it’s time to post yet another Update Set.

Service Portal Form Fields

“Everything begins with an idea.”
Earl Nightingale

Even though I tend to prefer the Service Portal environment over the original ServiceNow UI, the one thing that you just can’t beat on the original side of the house is the built-in support for form fields. As soon as you create a Table in ServiceNow, a corresponding form is also created, built using a generic infrastructure that is driven off of the embedded data dictionary. Every field on the form is formatted in a consistent way using consistent components supporting consistent features. So far, I have not seen anything comparable to that in the Service Portal environment.

On a standard ServiceNow UI form, every field on the page will have the same set of standard elements based on the same basic template, customized to the specifications of the specific field as contained in the data dictionary. For example, here is the HTML from the Incident ID field on the Incident form:

   <div id="element.incident.number" class="form-group " style="">
      <div class="" data-type="label" choice="0" type="string" id="label.incident.number" nowrap="true"><label onclick="return labelClicked(this);" for="incident.number" dir="ltr" class=" col-xs-12 col-md-3 col-lg-4 control-label"><span id="status.incident.number" data-dynamic-title="" mandatory="false" oclass="" class=" label_description" aria-label=""></span><span title="" class="label-text" data-html="false" data-original-title="">Number</span></label></div>
      <div class="col-xs-10 col-sm-9 col-md-6 col-lg-5 form-field input_controls">
         <div ng-non-bindable="" class="hidden"><input id="sys_original.incident.number" name="sys_original.incident.number" value="INC0010037" type="hidden"></div>
         <input id="incident.number" name="incident.number" aria-required="false" onchange="onChange('incident.number');" maxlength="40" value="INC0010037" style="; " autocomplete="off" ng-non-bindable="" class="form-control" spellcheck="false" type="text">
      </div>
      <div class="col-xs-2 col-sm-3 col-lg-2 form-field-addons"></div>
   </div>

Looking through the code, you can spot a number of unique standard elements, each with its own specific purpose, and many with a standard ID:

  • element.incident.number
  • label.incident.number
  • status.incident.number
  • sys_original.incident.number
  • incident.number

So, in theory, it seems as if you could use the above block of HTML as a guide and set up some kind of AngularJS component to replicate that entire concept over on the Service Portal side of the house. Unfortunately, I’m not all that familiar with AngularJS, but it sounds like an interesting challenge. Who knows … maybe someone has already put this together. Surely someone smarter than I am has already thought of doing this, and maybe has already figured all of it out. Even if that’s true, it would still be fun to try on my own. You know what I always ask myself: how hard could it be?