Using Access Control Entries on jTable rows

jTable is a jQuery plugin that is used to create AJAX based CRUD tables without coding HTML or Javascript. Tables presented by jTable can be completely read-only (R), or have various type of write access (CUD), but these accesses are granted to the whole jTable, even if not permitted on the server. The question was asked, how to control jTable Update and Delete on a row by row basis?

The method given here, is for the server to supply an Access Control Entry (ACE) with each data record. How to do that depends on the application / business, here we just cycle through the different options. Using standard features of jTable with just a little jQuery we use the ACE to determine how jTable responds to user requests. It should be understood that access control, like data validation, must always be implemented on the server, this approach just provides a polite user interface.

By definition the user must have Read access to the record for the server to send it to the user in the first place. Hence all the ACEs have at least Read access, and there is nothing to be implemented in the client.

The Create Access Right does not apply to an existing table row, so again there nothing for us to implement on a row by row basis. If the user cannot Create any records, then don't include the createAction in the jTable definition. One could use the Create Access Right of an individual record to control the presence or not of the createAction on child tables.

Below is the standard demonstration jTable code and resultant jTable. The only change so far is to request a different server page, one that will return an ACE with each record, but ignored for the moment. Please try it, to see the normal behaviour.

Standard demonstration jTable html
<div id="PeopleTableContainer" ></div>
	
Standard demonstration jTable definition
		$(document).ready(function () {
		    //Prepare jTable
			var PeopleJtable = {
				title: 'Demonstration of standard example',
				jqueryuiTheme: true ,
				actions: {
					listAction: 'PersonActionsACE.php?action=list',
					createAction: 'PersonActionsACE.php?action=create',
					updateAction: 'PersonActionsACE.php?action=update',
					deleteAction: 'PersonActionsACE.php?action=delete'
				},
				fields: {
					PersonId: {
						key: true,
						create: false,
						edit: false,
						list: false
					},
					Name: {
						title: 'Author Name',
						width: '40%'
					},
					Age: {
						title: 'Age',
						width: '20%'
					},
					RecordDate: {
						title: 'Record date',
						width: '30%',
						type: 'date',
						create: false,
						edit: false
					}
				}
			};
			$('#PeopleTableContainer').jtable(PeopleJtable);
			//Load person list from server
			$('#PeopleTableContainer').jtable('load');
	

Now we extend the definition to utilise several standard jTable callbacks and events to implement the access control. We use the jQuery $.extend function to build on existing definitions, so that we can isolate the new or changing code for simpler discussion.

jTable definition extensions for ACE control of Edit & Delete
			var PeopleJtable2 = $.extend(true,{},PeopleJtable, {
				title: 'Demonstration of ACE on edit & delete',
				fields: {
					ACE: {
						title: 'ACE',
						width: '10%',
						create: false,
						edit: false
					}
				},
				deleteConfirmation: function (data) {
					if (data.record.ACE.indexOf("D") == -1) {
						// user not authorised to delete this record
						data.cancel = true;
						data.cancelMessage = "You are not authorised to delete this record";
					}
				},
				formCreated: function (e, data) {
					if (data.formType == "edit") {
						// if the user is not authorised to edit this record, convert the dialog & form into a readonly view
						if (data.record.ACE.indexOf("U") == -1) {
							var formDialog = data.form.closest('.ui-dialog');
							// user not authorised to edit this record. convert the form into Read Only
							// hide the submit button - this is all that really counts
							formDialog.find('#EditDialogSaveButton').hide();
							// disable all the input and select elements so users cannot do anything
							data.form.find('input, select, textarea').attr('disabled', true);
							// Also give user some feedback by adding readonly to title. save the original for later restoration
							var titleSpan = formDialog.find('span.ui-dialog-title').first();
							if (titleSpan.data('originalTitle') == undefined) {
								titleSpan.data('originalTitle', titleSpan.text());
								titleSpan.append(' Read-Only');
							}
						}
					}				
				},
				formClosed: function (e, data) {
					if (data.formType == "edit") {
						var formDialog = data.form.closest('.ui-dialog');
						// restore any saved dialog title, to give user some feedback that record is updateable
						var titleSpan = formDialog.find('span.ui-dialog-title').first();
						if (titleSpan.data('originalTitle')!= undefined) {
							titleSpan.text(titleSpan.data('originalTitle'));
							titleSpan.removeData('originalTitle')
							// enable all the input and select elements so users cannot do anything
							data.form.find('input, select, textarea').attr('disabled', false);
							// make submit button visible in case hidden from previous use
							formDialog.find('#EditDialogSaveButton').show();
						}
					}				
				}
			});
				
			$('#PeopleTableContainer2').jtable(PeopleJtable2);
			//Load person list from server
			$('#PeopleTableContainer2').jtable('load');
	

We overwrite the title for our new table, and add the ACE field. In practice this would be a hidden field, but shown here as this is a demonstration. These demonstration ACE's are very simple, they contain the letters C,R,U & D, the presence of the letter indicates user is authorised to perform that operation.

The first meaningful section is the deleteConfirmation option lines 20 to 26. Inside this callback function, we check the ACE in the data record. If it includes the letter D, we do nothing, the normal delete confirmation dialog appears, and if confirmed by the user, the delete request is sent to the server. However if D is not present in the ACE, then the user is not authorised to delete this record, so the delete request is cancelled and the cancellation message set for jTable to use. jTable does nearly all the work for us.

Controlling UPDATE is just a little more complicated. We use the jTable events formCreated and formClosed to adjust the normal jTable flow. When the formCreated event handler is triggered the HTML form and containing dialog box have been created by jTable. We check the form type and if 'create' we don't need to do anything. If it is 'edit', we check the ACE in the original record for the letter U, and if NOT present then we can change the user experience.

We do three things to make the form read-only.

  1. Use jQuery to hide the Save Button. The actual code formDialog.find('#EditDialogSaveButton').hide(); may look odd to experienced jQuery developers looking at jTable for the first time. We suspect early versions of jTable shared the dialogs Error, Add & Create/Edit between all jTables on the page, so giving the buttons specific id's was a good thing. However in this demo with jTable version 2.4.0 each jTable has it's own set of dialogs, meaning that there are multiple #EditDialogSaveButton on the page. Nevertheless jQuery is smart enough to find the button by id within the dialog.
  2. Disable all the input elements in the form, so that the use cannot waste time entering data. data.form.find('input, select, textarea').attr('disabled', true);
  3. Give the user some feedback by adding the word Read-Only to the dialog title. We also save the original title, so that it can be restored later.

The last detail is the formClosed event. Here we check the dialog to see if it has a stored original title. If so we reverse the operations in the formCreated handler.

See the results below.

The original question asked how to hide the jTable Update and Delete icons, on a row by row basis. Firstly, that could look pretty messy. Secondly, it would likely generate a lot of support calls, with complaints that buttons were missing. The above approach provides a uniform appearance, and explains to the user why such actions cannot proceed.

Using the ACE to control record selection.

A more recent question asked about using record data to control row selection. The next demonstration does that.

Again starting with the standard demonstration, we extend it slightly. Firstly we set a new title, then some jTable options to activate multi-record selection with checkboxes. We also add the ACE field, just to make it visible. The technique works perfectly well without exposing the field to the user.

jTable definition extensions for ACE control of Row Selection
			var PeopleJtable3 = $.extend(true,{},PeopleJtable, {
				title: 'Demonstration of ACE on Row Selection',
				selecting: true,
				selectingCheckboxes: true,
				multiselect: true,
				fields: {
					ACE: {
						title: 'ACE',
						width: '10%',
						create: false,
						edit: false
					}
				},
				recordsLoaded: function(event, data) { //when tha table shows
					// NOTE. $(this) => "div#PeopleTableContainer(n)
					$(this).find('tbody tr').each(function () {
						record = $(this).data('record');
						if (record.ACE.indexOf('C') >=0 ) {
							var td = $(this).find('td.jtable-selecting-column').attr('title','Selection disabled because XYZ');
							td.find('input').attr('disabled', true);
						}
					});
					$(this).tooltip();
				}
			});
			$('#PeopleTableContainer3').jtable(PeopleJtable3);

			//Load person list from server
			$('#PeopleTableContainer3').jtable('load');
	

jTable kindly gives us the recordsLoaded event, which it triggers every time it has downloaded data and constructed a new table. From here on we have to use jQuery to modify the DOM itself, but in a simple way. All we do is loop through each row of the table, recover the associated record, and test if the row is selectable.

In this demo, we are going to misuse the flag C of the ACE to indicate that the record is not selectable. If C is present, we find use the td.jtable-selecting-column selector to find the cell containing the selection checkbox. We give the cell a title attribute, explaining why the row is not selectable. Then we disable the checkbox inside the cell.

Once all the rows have be processed, we turn all the element titles of the jTable into tooltips. The default jQuery styling, makes the tooltip a much more noticeable message than the default browser display of html titles.

If you've made it this far, we hope you've learnt something. We are available to offer local tutoring on a range of technologies, or to build you a website, standard or non-standard. The less standard the better! Contact us at tutor@swaleinternet.co.uk