“If you concentrate on small, manageable steps you can cross unimaginable distances.”
— Shaun Hick
Before I was sidetracked by my self-inflicted issues with my Configurable Data Table Widget Content Selector, I was just about to dive into the red meat of my new Content Selector Configuration Editor. Now that those issues have been resolved, we can get back to the fun stuff. As those of you who have been following along at home will recall, there are three distinct sections of the JSON object used to configure the content selector: 1) Perspective, 2) State, and 3) Table. Both the Perspective and State sections are relatively simple arrays of objects, but the Table section is much more complex, having properties for every State of every Table in every Perspective. Since I like to start out with the simple things first, my plan is to build out the Perspective section first, work out all of the kinks, and then pretty much clone that working model to create the State section. Once we get through all of that, then we can deal with the more complicated Table section.
As usual, we will start out with the visual components first and try to get things to look halfway decent before we crawl under the hood and wire everything together. Perspectives have only three properties, a Label, a Name, and an optional list of Roles to limit access to the Perspective. We should be able to lay all of this out in a relatively simple table, with one row for each Perspective. Here is what I came up with:
<div>
<h4 class="text-primary">${Perspectives}</h4>
</div>
<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;">Roles</th>
<th style="text-align: center;">Edit</th>
<th style="text-align: center;">Delete</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in c.data.config.perspective">
<td data-th="Name">{{item.label}}</td>
<td data-th="Label">{{item.name}}</td>
<td data-th="Roles">{{item.roles}}</td>
<td data-th="Edit" style="text-align: center;"><img src="/images/edittsk_tsk.gif" ng-click="editPerspective($index)" alt="Click here to edit the details of this Perspective" title="Click here to edit the details of this Perspective" style="cursor: pointer;"/></td>
<td data-th="Delete" style="text-align: center;"><img src="/images/delete_row.gif" ng-click="deletePerspective($index)" alt="Click here to permanently delete this Perspective" title="Click here to permanently delete this Perspective" style="cursor: pointer;"/></td>
</tr>
</tbody>
</table>
</div>
<div style="width: 100%; text-align: right;">
<button ng-click="editPerspective('new')" class="btn btn-primary ng-binding ng-scope" role="button" title="Click here to add a new Perspective">Add a new Perspective</button>
</div>
In addition to the columns for the three properties, I also added a column for a couple of action icons, one to edit the Perspective and one to delete the Perspective. I thought about putting input elements directly in the table for editing the values, but I decided that I would prefer to keep everything read-only unless you specifically asked to edit one of the rows. If you do want to edit one of the rows, then I plan on popping up a simple modal dialog where you can make your changes (we’ll get to that a little later).
I also added a button down at the bottom that you can use to add a new Perspective, which should pop up the same modal dialog without any existing values. Here’s what the layout looks like so far:
That’s not too bad. I think that it looks good enough for now. Our action icons reference nonexistent client-side functions right now, though, so next we ought to build those out. The Delete process looks like it might be the simplest of the two, so let’s say we start there. For starters, it’s always a good practice to pop up a confirm dialog before actually deleting anything, and I always like to use the spModal confirm option for that as opposed to a simple Javascript confirm. Since deleting the Perspective will also wipe out any Table information defined for that Perspective, we will want to warn them of that as well. Here is what I came up with:
$scope.deletePerspective = function(i) {
var confirmMsg = '<b>Delete Perspective</b>';
confirmMsg += '<br/>Deleting the ';
confirmMsg += c.data.config.perspective[i].label;
confirmMsg += ' Perspective will also delete all information for every State of every Table in the Perspective.';
confirmMsg += '<br/>Are you sure you want to delete this Perspective?';
spModal.confirm(confirmMsg).then(function(confirmed) {
if (confirmed) {
c.data.config.table[c.data.config.perspective[i].name] = null;
c.data.config.perspective.splice(i, 1);
}
});
};
If the operator confirms the delete action, then we first null out all of the Table information for that Perspective, and then we slice out the Perspective from the list. We have to do things in that order. If we removed the Perspective first, then we would lose access to the name, which is needed to null out the Table data. Here is what the confirm dialog looks like on the page:
That takes care of the easy one. Now on to the Edit action. For this one, we will use spModal as well, but instead of the confirm method we will be using the open method to launch a small Perspective Editor widget. The open method has an argument called shared that we can use to pass data to and from the widget. Here is the code to launch the widget and collect the updated data when it closes:
$scope.editPerspective = function(i) {
var shared = {roles:{}};
if (i != 'new') {
shared.label = c.data.config.perspective[i].label;
shared.name = c.data.config.perspective[i].name;
shared.roles.value = c.data.config.perspective[i].roles;
shared.roles.displayValue = c.data.config.perspective[i].roles;
}
spModal.open({
title: 'Perspective Editor',
widget: 'b83b9f342f3320104425fcecf699b6c3',
shared: shared
}).then(function() {
if (i == 'new') {
c.data.config.table[shared.name] = [];
i = c.data.config.perspective.length;
c.data.config.perspective.push({});
} else {
if (shared.name != c.data.config.perspective[i].name) {
c.data.config.table[shared.name] = c.data.config.table[c.data.config.perspective[i].name];
c.data.config.table[c.data.config.perspective[i].name] = null;
}
}
c.data.config.perspective[i].name = shared.name;
c.data.config.perspective[i].label = shared.label;
c.data.config.perspective[i].roles = shared.roles.value;
});
};
Since this function is intended to be used for both new and existing Perspectives, we have to check to see which one it is in a couple of places. Before we open the widget dialog, we will need to build a new object if this is a new addition, and after the dialog closes, we will have to establish a Table array for the new Perspective as well as establish an index value and an empty object at that index in the Perspective array. Also, if this is an existing Perspective and they have changed the name of the Perspective, then we need to move all of the associated Table information from the old name to the new name and get rid of everything under the old name. Other than that, the new and existing edit processes are pretty much the same.
This takes care of the client side function to launch the widget, but we still need to build the widget. That might get a little involved, and we have already covered quite a bit, so this may be a good place to stop for now. We’ll tackle that Perspective Editor widget first thing next time out, which should wrap up the Perspective section. Maybe we will even have time to clone it all and finish up the State section as well.