Content Selector Configuration Editor, Part VIII

“Tinkering is something we need to know how to do in order to keep something like the space station running. I am a tinkerer by nature.”
Leroy Chiao

I know what you are thinking: Didn’t we wrap this series up last time with the release of the final Update Set? Well, technically you would be correct in that we finished building all of the parts and bundled them all up in an Update Set for those of you who like to play along at home, but … we really did not spend a whole lot of time on the purpose for all of this, so I thought it might be a good time to back up the truck just a tad and demo some of the features of the customized data table widget and the associated content selector widget. Mainly, I want to talk about the buttons and icons and how all of that works, but before we do that, let’s go all the way back and talk about the basic idea behind these customizations of some pretty cool stock products.

I really liked the stock Data Table widget that comes bundled with the Service Portal, but the one thing that annoyed me was that each row was one huge clickable link, which was a departure from the primary UI, where every column could potentially be a link if it contained a Reference field. So, I rewired it. Of course, once you start playing with something then you end up doing all kinds of other crazy things and before you know it, you have this whole subset of trinkets and doodads that only you know anything about. So, on occasion, I feel the need to stop talking about what I am building and how it is constructed, and spend a little time talking about how to use it and what it is good for. Hence, today’s discussion of buttons and icons on the customized Data Table widget.

Before we get started, though, I do need to confess that in all of the excitement around creating a way to edit the JSON configuration object for the Configurable Data Table Widget Content Selector, I completely forgot about one of the options for handling a button click: opening up a new portal page. When we first introduced the idea of having buttons and icons on the rows of the Data Table widget, we made allowances for adding a page_id property to the button definition, and if that property were valued, we would link to that page on a click; otherwise, we would broadcast the click details. We did not include the page_id property in either the Content Selector Configuration Editor widget or the Button/Icon Editor widget, so let’s correct that oversight right now. First, we will need to add that property to the HTML for the table of Buttons/Icons.

<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;">${Page}</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="${Page}">{{btn.page_id}}</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>

… and then we will need to pass it back and forth between the main widget and the pop-up Button/Icon Editor widget:

$scope.editButton = function(button, btnArray) {
	var shared = {page_id: {value: '', displayValue: ''}};
	if (button != 'new') {
		shared.label = button.label;
		shared.name = button.name;
		shared.heading = button.heading;
		shared.icon = button.icon;
		shared.color = button.color;
		shared.hint = button.hint;
		shared.page_id = {value: button.page_id, displayValue: button.page_id};
	}
	spModal.open({
		title: 'Button/Icon Editor',
		widget: 'button-icon-editor',
		shared: shared
	}).then(function() {
		if (button == 'new') {
			button = {};
			btnArray.push(button);
		}
		button.label = shared.label || '';
		button.name = shared.name || '';
		button.heading = shared.heading || '';
		button.icon = shared.icon || '';
		button.color = shared.color || '';
		button.hint = shared.hint || '';
		button.page_id = shared.page_id.value || '';
	});
};

… and then, of course, we need to update the Button/Icon Editor itself by dragging in the page selector form field from the Reference Page Editor widget and adding it to the fields on the Button/Icon Editor form.

<div>
  <form name="form1">
    <snh-form-field
      snh-model="c.widget.options.shared.label"
      snh-name="label"
      snh-required="true"/>
    <snh-form-field
      snh-model="c.widget.options.shared.name"
      snh-name="the_name"
      snh-label="Name"
      snh-required="true"/>
    <snh-form-field
      snh-model="c.widget.options.shared.heading"
      snh-name="heading"/>
    <snh-form-field
      snh-model="c.widget.options.shared.icon"
      snh-name="icon"
      snh-type="icon"/>
    <snh-form-field
      snh-model="c.widget.options.shared.color"
      snh-name="color"
      snh-type="select"
      snh-choices='[{"label":"default","value":"default"},{"label":"primary","value":"primary"},{"label":"secondary","value":"secondary"},{"label":"success","value":"success"},{"label":"danger","value":"danger"},{"label":"warning","value":"warning"},{"label":"info","value":"info"},{"label":"light","value":"light"},{"label":"dark","value":"dark"},{"label":"muted","value":"muted"},{"label":"white","value":"white"}]'/>
    <snh-form-field
      snh-model="c.widget.options.shared.hint"
      snh-name="hint"/>
    <snh-form-field
      snh-type="reference"
      snh-model="c.widget.options.shared.page_id"
      snh-name="page_id"
      placeholder="Choose a Portal Page"
      table="'sp_page'"
      display-field="'title'"
      display-fields="'id'"
      value-field="'id'"
      search-fields="'id,title'"/>
    <div id="element.example" class="form-group">
      <div id="label.example" class="snh-label" nowrap="true">
        <label for="example" class="col-xs-12 col-md-4 col-lg-6 control-label">
          <span id="status.example"></span>
          <span title="Example" data-original-title="Example">Example</span>
        </label>
      </div>
      <span class="input-group">
        <a ng-if="!c.widget.options.shared.icon" href="javascript:void(0)" role="button" class="btn-ref btn btn-{{c.widget.options.shared.color || 'default'}}" title="{{c.widget.options.shared.hint}}" data-original-title="{{c.widget.options.shared.hint}}">{{c.widget.options.shared.label}}</a>
        <a ng-if="c.widget.options.shared.icon" href="javascript:void(0)" role="button" class="btn-ref btn btn-{{c.widget.options.shared.color || 'default'}}" title="{{c.widget.options.shared.hint}}" data-original-title="{{c.widget.options.shared.hint}}">
          <span class="icon icon-{{c.widget.options.shared.icon}}" aria-hidden="true"></span>
          <span class="sr-only">{{c.widget.options.shared.hint}}</span>
        </a>
      </span>
    </div>
  </form>
  <div style="width: 100%; padding: 5px 50px; text-align: right;">
    <button ng-click="cancel()" class="btn btn-default ng-binding ng-scope" role="button" title="Click here to cancel this edit">Cancel</button>
    &nbsp;
    <button ng-click="save()" class="btn btn-primary ng-binding ng-scope" role="button" title="Click here to save your changes">Save</button>
  </div>
</div>

That should take care of that little oversight. Now, let’s get back to showing off the various ways in which you can use buttons and icons on the customized Data Table widget.

To start out, let’s use the tool that we just made to create a brand new configuration for a sample page where we can demonstrate how the buttons and icons work. Let’s click on our new Menu Item to bring up the tool, click on the Create a new Content Selector Configuration button, enter the name of our new Script Include, and then click on the OK button.

Creating a new configuration object using the new tool

When our new empty configuration comes up in the tool, let’s define a single Perspective called Button Test, and a couple of different States, Active and Not Active. We want to keep things simple at this point, mainly so as not to distract from our primary purpose here, which is to show how the buttons and icons work. Once we have defined our Perspective and States, click on the Add new Table button and select the Incident table from the list.

Selecting the Incident table from the pop-up selection list

Select just a couple of fields, say Number and Short Description, and set the filter for the Active State to active=true. Then we can start adding Buttons and Icons using the Add new Button/Icon button.

Adding a new Button/Icon

Those of you paying close attention will notice that the image above was taken before we added the page_id property to the Button/Icon configuration. The newer version has a pick list of portal pages below the Hint and above the Example. You will want to define at least one Button/Icon with a page value. Keep adding different buttons and icons until you have a representative sample of different things and then we can see how it all renders out in actual use.

New configuration with several different buttons and icons

Once you have a few set up for testing, save the new configuration and take a look at the resulting script, which should look something like this:

var ButtonTestConfig = Class.create();
ButtonTestConfig.prototype = Object.extendsObject(ContentSelectorConfig, {
	initialize: function() {
	},

	perspective: [{
		name: 'button_test',
		label: 'Button Test',
		roles: ''
	}],

	state: [{
		name: 'active',
		label: 'Active'
	},{
		name: 'not_active',
		label: 'Not Active'
	}],

	table: {
		button_test: [{
			name: 'incident',
			displayName: 'Incident',
			active: {
				filter: 'active=true',
				fields: 'number,short_description',
				btnarray: [{
					name: 'button1',
					label: 'Button #1',
					heading: '',
					icon: '',
					color: 'info',
					hint: 'This is Button #1',
					page_id: ''
				},{
					name: 'button2',
					label: 'Button #2',
					heading: 'Button #2',
					icon: '',
					color: 'warning',
					hint: 'This is Button #2',
					page_id: ''
				},{
					name: 'button3',
					label: 'Button #3',
					heading: 'B3',
					icon: '',
					color: '',
					hint: 'This is Button #3',
					page_id: 'ticket'
				},{
					name: 'icon1',
					label: 'Icon #1',
					heading: '-',
					icon: 'cross',
					color: 'danger',
					hint: 'Danger Will Robinson!',
					page_id: ''
				},{
					name: 'icon2',
					label: 'Icon #2',
					heading: 'I2',
					icon: 'download',
					color: 'success',
					hint: 'Click here to do something ...',
					page_id: ''
				}],
				refmap: {}
			},
			not_active: {
				filter: 'active=false',
				fields: 'number,short_description',
				btnarray: [],
				refmap: {}
			}
		}]
	},

	type: 'ButtonTestConfig'
});

Now that we have that all ready to rock and roll, we need to configure a new test page so that we can put it to use. From the list of Portal Pages, click on the New button and create a page called button_test and save it so that we can pull it up in the Page Designer.

Setting up the new page in the Page Designer

From the container list, drag over a 3/9 container and drag the Content Selector widget into the narrow portion and the SNH Data Table from URL Configuration widget into the wider portion. You shouldn’t have to edit the Data Table widget, but you will need to click on the pencil icon on the Content Selector widget so that you enter the name of your new configuration script.

Entering the name of the configuration script in the widget options

Once that has been completed, you should be able to pull up your new page in the Service Portal and see how it renders out. You can click on any of your buttons or icons at this point, and if you click on one with a page_id value, that page should come up with the record from that row. If you click on any of the other buttons or icons, through, nothing will happen, because we have not set up anything to react to the button clicks at this point. However, if everything is working as it should be, we are now ready to do just that.

When a button or icon is clicked on the customized Data Table widget, and there is no page_id value defined, all that happens is that the details are broadcast out. Some other widget on the page needs to be listening for that broadcast in order for something to happen. The secret, then, is to have some client-side code somewhere that looks something like this:

$rootScope.$on('button.click', function(e, parms) {
	// do something 
});

For demonstration purposes we can build a simple test widget that will do just that, and in response to a click event, display all of the details of that event in an spModal alert. Let’s call this widget Button Click Handler Example, and give it the following client-side code:

function ButtonClickHandlerExample(spModal, $rootScope) {
	var c = this;

	$rootScope.$on('button.click', function(e, parms) {
		displayClickDetails(parms);		
	});

	function displayClickDetails(parms) {
		var html = '<div>'; 
		html += ' <div class="center"><h3>You clicked on the ' + parms.button.name + ' button</h3></div>\n';
		html += ' <table>\n';
		html += '  <tbody>\n';
		html += '   <tr>\n';
		html += '    <td class="text-primary">Table: &nbsp;</td>\n';
		html += '    <td>' + parms.table + '</td>\n';
		html += '   </tr>\n';
		html += '   <tr>\n';
		html += '    <td class="text-primary">Sys ID: &nbsp;</td>\n';
		html += '    <td>' + parms.sys_id + '</td>\n';
		html += '   </tr>\n';
		html += '   <tr>\n';
		html += '    <td class="text-primary">Button: &nbsp;</td>\n';
		html += '    <td><pre>' + JSON.stringify(parms.button, null, 4) + '</pre></td>\n';
		html += '   </tr>\n';
		html += '   <tr>\n';
		html += '    <td class="text-primary">Record: &nbsp;</td>\n';
		html += '    <td><pre>' + JSON.stringify(parms.record, null, 4) + '</pre></td>\n';
		html += '   </tr>\n';
		html += '  </tbody>\n';
		html += ' </table>\n';
		html += '</div>';
		spModal.alert(html);
	}
}

Now that we have our widget, we can pop back into the Page Designer and drag the widget anywhere on the screen. It has no displayable content, so it really doesn’t matter where you put it as long as it is present on the page somewhere. Once that’s done, we can pull up our page on the portal again and start clicking around again to see what we get.

Button click results

In addition to the details of the record on that row and the button that was clicked, you also get the name of the table and the sys_id of the record. Any of this data can be used to perform any number of potential actions, all of which are completely outside of the two reusable components, the customized Data Table and the configurable Content Selector. You shouldn’t have to modify either of those widgets to create custom functionality; just configure the Content Selector with an appropriate JSON config object, and then add any custom click handlers to your page for any button actions that you would like to configure. Here is an Update Set that includes the page_id corrections as well as the button test widget so that you can click around on your own and see how everything plays together. There is an even better version here.