Content Selector Configuration Editor, Part IV

“Everyone’s time is limited. What matters most is to focus on what matters most.”
Roy Bennett

Now that we have built out all of the easy parts of our new Content Selector Configuration Editor, it’s time to dive into the more complex of the three major sections, the Tables section. In the Tables section, there are configuration properties for every State of every Table in every Perspective, which is a lot of data to put on the page all at once. To help organize this data in a more manageable format, my intent is to leverage the Tabs directives from UI Bootstrap, which are already available on the ServiceNow platform. Immediately under the Tables section header, I would like to see a tab for each Perspective, so that you could focus on one Perspective at a time by selecting that specific tab. Within the tab for each Perspective, I want to have a list of Tables, and then for each Table, another set of nested tabs, one for each State. The contents of the State tab, then, would be all of the properties for that State of that Table in that Perspective, which would include such things as the list of Fields, the query Filter, and any configured Buttons, Icons, or Reference page mappings. At the highest level, it should looks something like this:

Typical Table section layout

It’s still relatively complicated, but the tabs help cut down on the clutter, as you are only looking at one specific Perspective/State at any given time. And it is relatively easy to implement, thanks to the UI Bootstrap components. Here is the basic HTML configuration of the Tables section:

<div>
  <h4 class="text-primary">${Tables}</h4>
</div>
<uib-tabset active="active">
  <uib-tab ng-repeat="persp in c.data.config.perspective" heading="{{persp.label}}">
    <div ng-repeat="tbl in c.data.config.table[persp.name]" style="padding-left: 25px;">
      <h4 style="color: darkblue">{{tbl.displayName}} ({{tbl.name}})</h4>
      <uib-tabset active="active">
        <uib-tab ng-repeat="state in c.data.config.state" heading="{{state.label}}">
          <div style="padding-left: 25px;">

<!-----  all of the specific properties are defined here  ----->

          </div>
        </uib-tab>
      </uib-tabset>
    </div>
  </uib-tab>
</uib-tabset>

Basically it is a DIV within a Tab within a Tabset, which is all tucked into a repeating DIV within a Tab within a Tabset. The innermost DIV will then contain all of the properties for the specific State of the specific Table of the specific Perspective selected. Those properties include the following:

Fields

This is a comma-separated list of the fields to be displayed on a row. To collect this data, I have just provided a single text input element. I would really like to pick these fields from a list, but an sn-record-picker would only select primary fields, and would not give you the option of selecting dot-walked fields from other tables. I could build my own Field Selector widget that supported dot-walking, or maybe there is already one out there that I could borrow, but that seemed like a separate project, so I resisted the temptation to go down that road and just used a single text input element for now. This will work.

Filter

This is just your typical GlideRecord encoded query, and again, I thought about having some kind of query builder UI here that was based on the associated table, but there are plenty of places to go on the platform to build a query, so in the end I decided to take the lazy way out and just define a simple text input element here for now.

Buttons/Icons

In my enhanced version of the Data Table widget, you can define Buttons and Icons to appear on the row with the table data, and the JSON configuration object has a place for an Array of these definitions. For input, we can display these in a simple table, much like we did for the Perspectives and the States, with a pop-up editor to make any changes.

Reference Pages

In the stock Data Table widget, the entire line is a link to the details of the record on that row. In my enhanced version, the first column takes you to that record, but any other columns where the data type is Reference will link you to that Reference record. The standard page for that is the generic form page, but you can override that by specifying your own page for any given table. As I did with the Buttons and Icons, I built a simple table for those, with an associated pop-up editor.

Here, then, is the HTML for a single State tab within a specific table.

<snh-form-field
  snh-label="Fields"
  snh-model="tbl[state.name].fields"
  snh-name="fields"
  snh-required="true"/>
<snh-form-field
  snh-label="Filter"
  snh-model="tbl[state.name].filter"
  snh-name="filter"
  snh-required="true"/>
<div id="label.btnarray" class="snh-label" nowrap="true">
  <label for="btnarray" class="col-xs-12 col-md-4 col-lg-6 control-label">
    <span id="status.btnarray"></span>
    <span title="Buttons/Icons" data-original-title="Buttons/Icons">${Buttons/Icons}</span>
  </label>
</div>
<table class="table table-hover table-condensed">
  <thead>
    <tr>
      <th style="text-align: center;">${Label}</th>
      <th style="text-align: center;">${Name}</th>
      <th style="text-align: center;">${Heading}</th>
      <th style="text-align: center;">${Icon}</th>
      <th style="text-align: center;">${Icon Name}</th>
      <th style="text-align: center;">${Color}</th>
      <th style="text-align: center;">${Hint}</th>
      <th style="text-align: center;">${Edit}</th>
      <th style="text-align: center;">${Delete}</th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="btn in tbl[state.name].btnarray" ng-hide="btn.removed">
      <td data-th="${Label}">{{btn.label}}</td>
      <td data-th="${Name}">{{btn.name}}</td>
      <td data-th="${Heading}">{{btn.heading}}</td>
      <td data-th="${Icon}" style="text-align: center;">
        <a ng-if="btn.icon" href="javascript:void(0)" role="button" class="btn-ref btn btn-{{btn.color || 'default'}}" title="{{btn.hint}}" data-original-title="{{btn.hint}}">
          <span class="icon icon-{{btn.icon}}" aria-hidden="true"></span>
          <span class="sr-only">{{btn.hint}}</span>
        </a>
      </td>
      <td data-th="${Icon Name}">{{btn.icon}}</td>
      <td data-th="${Color}">{{btn.color}}</td>
      <td data-th="${Hint}">{{btn.hint}}</td>
      <td data-th="${Edit}" style="text-align: center;"><img src="/images/edittsk_tsk.gif" ng-click="editButton(btn)" alt="Click here to edit this Button/Icon" title="Click here to edit this Button/Icon" style="cursor: pointer;"/></td>
      <td data-th="${Delete}" style="text-align: center;"><img src="/images/delete_row.gif" ng-click="deleteButton(btn, tbl[state.name].btnarray)" alt="Click here to delete this Button/Icon" title="Click here to delete this Button/Icon" style="cursor: pointer;"/></td>
    </tr>
  </tbody>
</table>
<div style="width: 100%; text-align: right;">
  <button ng-click="editButton('new', tbl[state.name].btnarray, tbl);" class="btn btn-primary ng-binding ng-scope" role="button" title="Click here to add a new Button/Icon">Add a new Button/Icon</button>
</div>
<div id="label.refmap" class="snh-label" nowrap="true">
  <label for="refmap" class="col-xs-12 col-md-4 col-lg-6 control-label">
    <span id="status.refmap"></span>
    <span title="Reference Pages" data-original-title="Reference Pages">${Reference Pages}</span>
  </label>
</div>
<table class="table table-hover table-condensed">
  <thead>
    <tr>
      <th style="text-align: center;">${Reference Table}</th>
      <th style="text-align: center;">${Target Page}</th>
      <th style="text-align: center;">${Edit}</th>
      <th style="text-align: center;">${Delete}</th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="(key, value) in tbl[state.name].refmap">
      <td data-th="Reference Table">{{key}}</td>
      <td data-th="Target Page">{{value}}</td>
      <td data-th="Edit" style="text-align: center;"><img src="/images/edittsk_tsk.gif" ng-click="editRefMap(key, tbl[state.name].refmap)" alt="Click here to edit this Reference Page" title="Click here to edit this Reference Page" style="cursor: pointer;"/></td>
      <td data-th="Delete" style="text-align: center;"><img src="/images/delete_row.gif" ng-click="deleteRefMap(key, tbl[state.name].refmap)" alt="Click here to delete this Reference Page" title="Click here to delete this Reference Page" style="cursor: pointer;"/></td>
    </tr>
  </tbody>
</table>
<div style="width: 100%; text-align: right;">
  <button ng-click="editRefMap('new', tbl[state.name].refmap);" class="btn btn-primary ng-binding ng-scope" role="button" title="Click here to add a new Reference Page">Add a new Reference Page</button>
</div>

Of course, that’s just the HTML. There are several ng-click functions referenced here that will need to be coded out, along with a number of pop-up editor widgets. And of course, we will need some code to save the whole mess once you get everything configured the way that you would like. Let’s not get too far ahead of ourselves just yet, though. Here we just put one foot in front of the other, taking things on one at a time and focusing on the task at hand. The next task will be some client-side functions to handle all of these newly specified ng-clicks. That sounds like a good place to start, next time out.