Tuesday, 15 April 2014

Editable Select Widget in List View

The default Xataface list view is a read-only table that shows the records of the current table.  Occasionally I get requests to make some of the columns editable by way of a drop-down box.  For example, suppose we have built an issue tracker in Xataface.  The list page for the "issues" table likely has columns like "title", "status", "assignee", etc..  It might be helpful, in this case, if we could change the status of an issue directly in list view without having to go to the edit form (or the update form).  One solution is to change the "status" column to include a drop-down select widget rather than just a read-only string.  This is actually quite easy to do, in Xataface, even though it doesn't have this type of functionality built-in.

The Solution

Our solution has three parts:
  1. Implement the status__renderCell() method in the issues table delegate class to display the <select> list.
  2. Set the noLinkFromListView flag in the fields.ini file definition for the "status" field.
  3. Implement a Javascript handler to save changes via AJAX when the select list value is changed.

Custom cell content with status__renderCell()

The status__renderCell() method should return an string containing HTML.  Xataface looks for this method to populate the content of the "status" method in list view.

In this snippet we have hard-coded the possible status values.  But it would be better to use a Xataface valuelist for these values.  I leave this for you to do as an exercise.  Notice that I added a "data-record-id" attribute to the <select> tag.  We will use this later when we add our Javascript functionality.

If you reload the interface, you should now see a select list appear in the "status" column of each row.  Unfortunately when you click on it, to try to change the value, you will be taken to the details page for that row.  This is because the <select> list is rendered inside of an <a> tag, which Xataface generates for all cells in list view (to make them clickable).  You can disable this by setting the noLinkFromListView flag in the fields.ini file

Now, if you reload the page, you should be able to select different options in your select list without the page changing on you.  It doesn't actually "work" yet, but at least it looks right.  The next step is to add functionality to the drop-down lists.

Adding Save Functionality

In order to add save functionality, we will need to step into the world of Javascript.  Xataface provides a rich set of tools for developing javascript functionality.  Our first order of business is to create an empty Javascript file and have it included in our application at the appropriate time.   
If your application doesn't already have a directory named "js" in its root, create it now. 

Note: It is important that the directory is named "js" because this is where the Dataface_JavascriptTool is preset to look for scripts. 

Try adding a script with a simple "alert()" message, then we'll be able to tell whether the script is included properly in our App.

Including the Javascript File In Our App

To include the js file in our app, let's modify the earlier snippet we created to display the select list:

Notice that we added a call to Dataface_JavascriptTool::import() to import the script we created.  You might be concerned that this piece of code is executed several times per page, and thus many copies of our script will be included in the final HTML page.  No need to worry, though, as the Dataface_Javascript tool import() method checks for duplicates to make sure that the script is only included once.

Before proceeding, you should now try to reload the list view if your app to make sure that the "alert()" message fires.  If it does not, that means that something is wrong so you'll need to back track to figure out why the file is not being included.

If the alert() message fired properly, then we can move on to actually implement our javascript.  I always begin by creating a closure to wrap all of the javascript code.  This way my variables won't bleed into the global namespace.

Importing Javascript Libraries

Another nice thing about the Dataface_Javascript tool is that it provides linking/compiling functionality to be able to include other javascript libraries in a similar manner to the way you would import them in server-side languages like C, Java, Ruby, and Python.  The syntax is easy.  For our purposes here, we'll be using the jQuery library (which is included with Xataface), and the xataface.IO library, which provides convenience methods for reading and writing database records over AJAX.  Therefore, I'll place the following require statements at the beginning of my file.

These "require" statements instruct Xataface to search in the javascript include paths for files named "jquery.packed.js" and "xataface/IO.js" and include the contents of the first match for each of them.  The search paths, by default are {yourapp}/js, {xataface}/js but this can be augmented.

Now for the actual code:

If you're not familiar with jQuery, this code may look daunting.  Have no fear, jQuery is actually quite easy to understand once you have done a 10 minute tutorial.  In this case we are making use of our "status-drop-down" class that we added to the <select> tag in our status__renderCell() method to identify all of the drop-downs.  We then attach a listener to each select's "change" event.

We use the xataface.IO.update() method to actually update the the record.  The update() method expects a Xataface record ID as the first parameter.  This is a unique string that defines a record.  It is composed in the form {tablename}?key1=val1&key2=val2...&keyn=valn.  We don't really need to worry about how it is formed here, because we embedded the key as the <select> tag's data-record-id attribute in the status__renderCell() method so that we can just access it here.


  1. Excellent tutorial, thanks! It seems when using this method to update records, the standard functions don't seem to apply, such as the history tool, beforeSave/afterSave, etc. Is it possible to integrate these functions with this type of inline editing?

  2. Standard functions should still apply here. It still runs through the standard workflow (beforeSave/afterSave etc...).

  3. The script is working perfectly in terms of updating the database. The "updated successfully" message appears. There are no errors in the web server log or the browser console. However, nothing appears in the history log, and the beforeSave actions are not occurring for me as they do when the record is edited normally. Any suggestions on what to check?

    1. Not sure why this would be happening for you. The xataface.IO.update() method (which is used in this example for saving) uses the ajax_save action to perform the actual save. It just calls the Dataface_Record::save() method, which follows the normal workflow.

      Are you 100% sure that the beforeSave trigger is not called? Can you show some code samples?

  4. BlueHost is definitely the best web-hosting company for any hosting plans you might require.