Scripted Value Columns, Part II

“Progress always involves risks. You can’t steal second base and keep your foot on first.”
Frederick B. Wilcox

Last time, we introduced the concept of Scripted Value Columns, created a sample script for testing, and built the pop-up editor widget. Now we need to modify the Content Selector Configurator widget to accommodate the new configuration option. As usual, we can start with the HTML, and as we have done before, we can copy one of the existing sections and then alter it to meet our new list of properties for this type of column.

<div id="label.svcarray" class="snh-label" nowrap="true">
  <label for="svcarray" class="col-xs-12 col-md-4 col-lg-6 control-label">
    <span id="status.svcarray"></span>
    <span class="text-primary" title="Scripted Value Columns" data-original-title="Scripted Value Columns">${Scripted Value Columns}</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;">${Script Include}</th>
      <th style="text-align: center;">${Edit}</th>
      <th style="text-align: center;">${Delete}</th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="svc in tbl[state.name].svcarray" ng-hide="svc.removed">
      <td data-th="${Label}">{{svc.label}}</td>
      <td data-th="${Name}">{{svc.name}}</td>
      <td data-th="${Heading}">{{svc.heading}}</td>
      <td data-th="${Table}">{{svc.script}}</td>
      <td data-th="${Edit}" style="text-align: center;"><img src="/images/edittsk_tsk.gif" ng-click="editScriptedValueColumn(svc)" alt="Click here to edit this Scripted Value Column" title="Click here to edit this Scripted Value Column" style="cursor: pointer;"/></td>
      <td data-th="${Delete}" style="text-align: center;"><img src="/images/delete_row.gif" ng-click="deleteScriptedValueColumn(svc, tbl[state.name].svcarray)" alt="Click here to delete this Scripted Value Column" title="Click here to delete this Scripted Value Column" style="cursor: pointer;"/></td>
    </tr>
  </tbody>
</table>
<div style="width: 100%; text-align: right;">
  <button ng-click="editScriptedValueColumn('new', tbl[state.name].svcarray, tbl);" class="btn btn-primary ng-binding ng-scope" role="button" title="Click here to add a new Scripted Value Column">Add a new Scripted Value Column</button>
</div>

Basically, this is just another table of configuration properties with labels and action buttons. To support the buttons we can copy similar functions for the other types of configurable columns, but before we do that, let’s jump into the Server script and see what we need to do there. We have called our new array of column configurations svcarray, and we can search the scripts for one of the existing arrays such as aggarray and basically cut and paste the existing code to support the new addition.

The only mention of aggarray on the server side is in the code that rebuilds the script being edited from data collected by the editor. We can make a quick copy of that section and then modify it for our purpose.

script += "',\n				svcarray: [";
var lastSeparator = '';
for (var v=0; v<tableTable[tableState.name].svcarray.length; v++) {
	var thisScriptedValue = tableTable[tableState.name].svcarray[v];
	script += lastSeparator;
	script += "{\nname: '";
	script += thisScriptedValue.name;
	script += "',\nlabel: '";
	script += thisScriptedValue.label;
	script += "',\nheading: '";
	script += thisScriptedValue.heading;
	script += "',\nscript: '";
	script += thisScriptedValue.script;
	script += "'\n				}";
	lastSeparator = ",";
}
script += "]";

While we are in the Server script, there is one more thing that we should do. Since this is a new configuration item, if you attempt to edit an existing item that was built using an earlier version, this array will not be present in the existing script that you will be editing. To make sure that we do not run into any null pointer issues with code expecting this array to exist, we should do a quick check when we load the script for editing and make sure that all of the configuration objects are present. There is already code in there to help initialize the data once the script has been loaded. Right now it looks like this:

var instantiator = new Instantiator(this);
var configScript = instantiator.getInstance(data.scriptInclude);
if (configScript != null) {
	data.config = configScript.getConfig($sp);
	for (var persp in data.config.table) {
		for (var tbl in data.config.table[persp]) {
			if (!data.config.table[persp][tbl].displayName) {
				data.config.table[persp][tbl].displayName = getItemName(data.config.table[persp][tbl].name);
			}
		}
	}
}

We are looping through every table in every perspective already, so all that we need to do is loop through every state in every table and check all of the objects that will need to be there for things to work. That will make the above section of code now look like this:

var instantiator = new Instantiator(this);
var configScript = instantiator.getInstance(data.scriptInclude);
if (configScript != null) {
	data.config = configScript.getConfig($sp);
	for (var persp in data.config.table) {
		for (var tbl in data.config.table[persp]) {
			if (!data.config.table[persp][tbl].displayName) {
				data.config.table[persp][tbl].displayName = getItemName(data.config.table[persp][tbl].name);
			}
			for (var st in data.config.state) {
				var thisState = data.config.table[persp][tbl][data.config.state[st].name];
				if (!thisState) {
					thisState = {};
					data.config.table[persp][tbl][data.config.state[st].name] = thisState;
				}
				thisState.svcarray = thisState.svcarray || [];
				thisState.aggarray = thisState.aggarray || [];
				thisState.btnarray = thisState.btnarray || [];
				thisState.refmap = thisState.refmap || {};
				thisState.actarray = thisState.actarray || [];
			}
		}
	}
}

That takes care of the Server script. Now let’s do the same sort of searching on the Client script. The first thing that we find is actually a problem that was created when we added the aggregate columns. Whenever the user selects the Add a New Table button, we create an object to store the configuration for the new table. That code currently looks like this:

$scope.newTable = function(perspective) {
	var shared = {};
	spModal.open({
		title: 'Table Selector',
		widget: 'table-selector',
		shared: shared
	}).then(function() {
		for (var st=0; st<c.data.config.state.length; st++) {
			shared[c.data.config.state[st].name] = {btnarray: [], refmap: {}, actarray: []};
		}
		c.data.config.table[perspective].push(shared);
	});
};

For each state of the new table, we are creating empty objects for the buttons, the reference map, and the bulk actions. When we added the aggregate columns, we neglected to add an empty object for that new array, which may actually cause a problem with certain actions. So we need to add the new svcarray as well as the aggarray to make things right. That will make this code now look like this:

$scope.newTable = function(perspective) {
	var shared = {};
	spModal.open({
		title: 'Table Selector',
		widget: 'table-selector',
		shared: shared
	}).then(function() {
		for (var st=0; st<c.data.config.state.length; st++) {
			shared[c.data.config.state[st].name] = {svcarray: [], aggarray: [], btnarray: [], refmap: {}, actarray: []};
		}
		c.data.config.table[perspective].push(shared);
	});
};

The next thing that you fill find on the client side are the functions that handle the clicks for the Edit and Delete buttons. Copying those and modifying them for Scripted Value Columns gives us this new block of code:

$scope.editScriptedValueColumn = function(scripted_value, svcArray) {
	var shared = {script: {value: '', displayValue: ''}};
	if (scripted_value != 'new') {
		shared.label = scripted_value.label;
		shared.name = scripted_value.name;
		shared.heading = scripted_value.heading;
		shared.script = {value: scripted_value.script, displayValue: scripted_value.script};
	}
	spModal.open({
		title: 'Scripted Value Column Editor',
		widget: 'scripted-value-column-editor',
		shared: shared
	}).then(function() {
		if (scripted_value == 'new') {
			scripted_value = {};
			svcArray.push(scripted_value);
		}
		scripted_value.label = shared.label || '';
		scripted_value.name = shared.name || '';
		scripted_value.heading = shared.heading || '';
		scripted_value.script = shared.script.value || '';
	});
};

$scope.deleteScriptedValueColumn = function(scripted_value, svcArray) {
	var confirmMsg = '<b>Delete Scripted Value Column</b>';
	confirmMsg += '<br/>Are you sure you want to delete the ' + scripted_value.label + ' Scripted Value Column?';
	spModal.confirm(confirmMsg).then(function(confirmed) {
		if (confirmed) {
			var a = -1;
			for (var b=0; b<svcArray.length; b++) {
				if (svcArray[b].name == scripted_value.name) {
					a = b;
				}
			}
			svcArray.splice(a, 1);
		}
	});
};

It looks like there is nothing else after that, so all we need to do at this point is to save the changes and give it a try. Let’s pull up one of our test configuration scripts in the editor and see what the new Scripted Value Columns section looks like.

New Scripted Value Columns section of the editor

Well, it appears on the screen, so that’s a good start. Now let’s hit that Add button and see if our pop-up editor works.

Pop-up Scripted Value Column editor

That looks good as well. Now let’s hit that Save button, which should return us to the main editor and our new scripted value column should show up on the list.

Scripted Value Columns section of the editor after adding a new entry

And there it is. Good deal. After saving the script and examining the resulting Script Include, everything looks good. So now we can create configurations for our new scripted value columns. Of course, the actual data table widgets have no idea what to do with this information, but we can start working on that in our next installment.