Aggregate List Columns, Part VII

“The most effective way to do it is to do it.”
Amelia Earhart

Last time, we completed the modifications to the last of the wrapper widgets, so now it is time to tackle the Content Selector Configuration Editor. Before we do that, though, we need to make one small addition to the Configurable Data Table Widget Content Selector itself. This widget builds the URL for the SNH Data Table from URL Definition widget, and so we need to add support for aggregate columns to the process that constructs that URL. I found these lines in the Client controller related to the buttons and icons:

s.buttons = '';
if (tableInfo[state].btnarray && Array.isArray(tableInfo[state].btnarray) && tableInfo[state].btnarray.length > 0) {
	s.buttons = JSON.stringify(tableInfo[state].btnarray);
}

So I just made a copy of those lines and then modified the copy to work with aggregates instead of buttons.

s.aggregates = '';
if (tableInfo[state].aggarray && Array.isArray(tableInfo[state].aggarray) && tableInfo[state].aggarray.length > 0) {
	s.aggregates = JSON.stringify(tableInfo[state].aggarray);
}

That was another simple and easy fix. Now onto the editor, which I am sure will be quite a bit more involved. Starting with the easy part, which I always like to do, we can take a look at the HTML and hunt for a section related to the buttons and icons, and then copy it to use as a base for creating a similar section for aggregate columns. Here is the relevant code:

<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 class="text-primary" 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;">${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>
<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>

Copying that code, making a few text replacements here and there, and then adjusting some of the columns to meet our needs resulted in the following new section of HTML, inserted above the buttons and icons section:

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

Now we have referenced a few nonexistent functions at this point, but that should not prevent us from pulling up the page and seeing how it looks so far. We can’t really click on anything in the new aggregates section at the moment, but let’s just take a peek and see how it all renders on the screen.

Configuration file selection screen

Let’s select our second test configuration, since that one has a source property defined, and see how things look.

New HTML section for aggregate column definitions

So far, so good. Now we need to create those missing client-side functions referenced in the new HTML, which we should be able to do relatively easily by copying corresponding functions used for the buttons and icons. Let’s start with the Delete function, since that one should be the easiest. Here is the code to delete a button configuration:

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

… and here is the modified copy that we can use for the aggregates section:

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

Next is the Edit function, which is a little more complicated. Once again, we can use the Edit function for the button configurations as a starting point.

$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 || '';
	});
};

As we did with the HTML, we can make a few text replacements here and there and then adjust some of the columns to come up with a suitable function for aggregate specifications.

$scope.editAggregate = function(aggregate, aggArray) {
	var shared = {};
	if (aggregate != 'new') {
		shared.label = aggregate.label;
		shared.name = aggregate.name;
		shared.heading = aggregate.heading;
		shared.table = aggregate.table;
		shared.field = aggregate.field;
		shared.filter = aggregate.filter;
		shared.source = aggregate.source;
	}
	spModal.open({
		title: 'Aggregate Column Editor',
		widget: 'aggregate-column-editor',
		shared: shared
	}).then(function() {
		if (aggregate == 'new') {
			aggregate = {};
			aggArray.push(aggregate);
		}
		aggregate.label = shared.label || '';
		aggregate.name = shared.name || '';
		aggregate.heading = shared.heading || '';
		aggregate.table = shared.table || '';
		aggregate.field = shared.field || '';
		aggregate.filter = shared.filter || '';
		aggregate.source = shared.source || '';
	});
};

That was relatively painless, but now we have code that references an entire widget that does not yet exist. Once again, we can create the missing widget by cloning the existing widget for the buttons and icons, but that seems like it might involve a little bit of work. Let’s jump into that in our next installment.