Tuesday, 22 April 2014

Adding More Interactivity to Your Forms

How to use Xataface, AJAX, and the export_json action to dynamically load data into your forms fields

The Xataface new record and edit record forms provide you with an easy (code-free) way to insert and update records in your database.   The fields.ini file enables you to customize which fields should be editable, and which widgets should be used to edit them.  In some cases you may find that you need to add more interactivity to the form than is provided out of the box.  One of the most common requests, for example, is to make a set of select widgets whose options are dependent on one another (e.g. If you have a "country" and "province" field, you want to automatically filter the options in the "province" select list to only show those provinces in the selected country).   I ultimately developed the depselect widget to address this use case, but there are still edge cases where the depselect might not be a perfect fit.

The Problem

Another request I sometimes get is to enable the user to load data into some fields of the form that is dependent of some partial data is has already been filled in.  Take, for example, a university job posting application where departments advertise their TA and Sessional Instructor positions each semester.  Every semester, the department administrator would add a new record to their "job postings" table that includes instructions for applicants, deadlines, etc...  In order to make the department administrator's job a little bit easier, they requested to have a button added to the form that would copy the posting instructions from a previous job posting into the current form's instructions field.  Then they could just make a few small changes to the instructions rather than starting from scratch each semester (or having to manually copy and paste from a previous record).

One option that is *very* easy to implement in Xataface is provide a default value for the instructions field.  This would almost solve the problem, but not completely.  Default values are loaded when form is generated.  But we may not know which instructions to "copy" into our field until the user has entered some data into the form (e.g. the department, semester, job category, etc...).  We really needed to add some dynamic behaviour to the form to allow the instructions to be populated after the form is generated, but before it is submitted.  Xataface provides the tools to achieve this without great difficulty also.

The Solution

  1. Use the before_instructions_widget block (read more about blocks and slots) to insert a button into the form.
  2. Add a javascript "click" handler to our button that takes some of the values entered into the form, makes an AJAX request to retrieve appropriate instructions, and replace the contents of the "instructions" field with the instructions that were retrieved.

Adding the Button



Reloading the form for the job_postings table reveals that the button has been added.


Notice, on line 2 of the above snippet, that we use the Dataface_JavascriptTool class (read more about JavascriptTool here) to include a custom javascript file that we will create next.

The Custom Javascript




There's a fair bit of code here, so let's go through it line by line.

Lines 1 to 3 show our "require" statements.  These are a special feature of the Dataface_Javascript tool to be able to use libraries that are included in Xataface, installed modules, and the application.  Here we require jQuery (because it just makes everything easier), the xatajax.form.core.js library (which provides some convenience functions for interacting with Xataface forms), and ckeditor.js (because the instructions field in this app happens to use the CKeditor widget and we need to use its API for setting the data in that field properly).

Line 4 is just the opening of our anonymous Javascript function which is used to provide a local scope to our Javascript.  This is best practice so that you don't pollute the global namespace.  This function is closed and executed on line 66.

Lines 5 and 6 is where we make local references to some global objects and functions.  Xataface's version of jQuery doesn't assign the jQuery object to the "$" alias by default.  This is to maintain compatibility with other libraries that may use this alias.  To make our lives easier, we assign a reference to the jQuery object that is only accessible to our local scope.

The Xataface.form.findField function (line 6) is a convenient Xataface function for finding the HTML fields on a given form that correspond with fields in the database.

Line 8 shows another level of wrapping and scoping inside the registerXatafaceDecorator() function.  This function takes a function as a parameter which will be executed when the body is finished loading in the browser.  It is very similar to jQuery's $(document).ready() method except that it is called when a document fragment is loaded by AJAX as well as when the document is initially loaded in the browser.  Notice that the callback function that is passed to registerXatafaceDecorator takes a single "node" parameter.  This represents the root node of the document fragment that was loaded.  This allows you to target your document decoration to only include the fragment that was loaded - rather than the entire document.

On line 9, we obtain a reference to the button that we added in the before_instructions_widget block.

On Lines 10-14, we obtain references to the fields in the form that we will need for building our AJAX request.  Notice that we use the findField() function (we made a reference to this on line 6) to obtain these fields.  In this case we make references to the semester_id, job_type, and department_id fields because we may need to pass these values as part of our AJAX request to determine which instructions we want to load.  E.g. If the job posting is for the "CMPT" department, then it wouldn't make sense to load previous instructions for the MATH department.

On line 16, we attach a click handler to the import button.  This handler is what will be called when the user clicks on the button to load instructions.  These is where we build the AJAX request to load the previous instructions.

Lines 17-27 show the basic GET parameters that I plan to pass to as part of the AJAX request.  Let's take a look at these parameters and discuss what they do.

The -action parameter (line 18) shows that we intend to pass this request to the built-in Xataface action "export_json", which will return the found set as a JSON data structure.  This is a very useful action for serving AJAX requests.  In some cases you may still need to create your own custom actions to return the data that your AJAX request requires, but in most cases this built-in action should do just fine.  When using the export_json action, you can just follow standard Xataface URL conventions for specifying which records to include in the result set.  E.g. here we specify that we want to fetch records from the "job_postings" table (line 19) , we only want one record (line 20),  and the results should be sorted by semester id in descending order (line 22) -- effectively this means we want only the most recent match.

The export_json action also takes some additional parameters.   For a full list, and some documentation, you can check out the action's source itself.   In this example, we set "-mode" to "browse" (line 21) so that the instructions won't be truncated.   By default export_json uses list mode which only returns the first 255 characters of text fields.  Also, since we only need the "instructions" of the job posting, we use the "--fields" directive to declare that we only need this one field.  We could have listed multiple fields here separated by spaces.

On lines 23-25 we initialize query values for the department_id, job_type, and semester_id fields.  This is optional and only added for readability.  Passing a blank value for a query field (following Xataface URL conventions) doesn't affect the query at all.  We set these query parameters to more specific values based on the current form data on lines 29-44.

On lines 29-31, we check to see if the semester_id field is filled in.  If it is, we add a filter to the query to only return job_postings in semesters earlier than the current one.  Notice the use of the '<' less than prefix in the query value to indicate a search for semesters less than the given semester.

Lines 32-27, we check if the department id field has been filled in.  If so, we add it to the filter parameters of the query.  If not, we throw an error .

Lines 39-44 show similar handling for the job type field.

Finally Lines 46-56 show the actual AJAX request.  The response will be an array of objects, where each object contains an 'instructions' property.  The following is a screenshot from the Chrome network inspector to show what the response will look like.



In this handler we first check that the response exists and that its length is greater than 0 (line 47).   On line 48 we use the CKEDITOR api to get the CKeditor instance for the 'instructions' field.  If it can be found, we set the data using the CKeditor setData() method.

Now, if we load the form, enter the required fields, and press the button, it should load the instructions field with the data that we want.




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.





Monday, 14 April 2014

Detecting When Values Are Changed

Xataface provides hooks that allow you to respond to many events in the workflow of your application.  The most fundamental hooks involve saving records.  These include:

  • beforeSave - called just before a record is saved to the database.
  • afterSave - called just after a record is saved to the database.
  • beforeInsert - called just before a record is inserted into the database.
  • afterInsert - called just after a record is inserted into the database.
  • beforeUpdate - called just before an existing record is updated in the database.
  • afterUpdate - called just after an existing record is updated in the database.
Notice that before/afterSave is called every time a record is saved (i.e. for both updates and inserts).  One common requirement is to perform an action only if a particular value was changed in the record.  For example, suppose you have a "status" field in your table, and if this field is set to "broken", an email should be sent to the site administrator to "fix" whatever is broken.  A first attempt at solving this problem might be:
This will send an email to the admin (assume that ADMIN_EMAIL is a constant defined elsewhere) every time a record is saved and the 'status' is set to 'broken'.  But there is a major problem with this solution.  This will send an email *every* time the record is saved.  So if the record was set to broken, then a user went in to make additional changes, it would send another email after each save.  Ideally we want to just send the email when the value of 'status' first changed to 'broken'.

Detecting Field Value Changes

Dataface_Record has a valueChanged() method that makes it easy to detect if a field has been modified since the record was last loaded.  With this knowledge, you might try to modify your afterSave() method as follows:
Unfortunately, if you try to run this code, you'll find that the mail is now *never* sent. This is because the "changed" flags on Dataface_Record objects are reset before any of the afterXXX() methods are called. If you want to detect a change, you need to do so in the beforeXXX() methods of the save cycle. E.g.
Now if you try out the application it should work the way that you expect. If the value of the 'status' field is changed, an email will be sent as expected. But this still isn't ideal because the email is technically sent before the "save" cycle is finished. So there is a possibility that a field validator causes the save to be canceled or fail. That may seem like a slim possibility, but it is better to be sure.
So, our ideal solution is to use the afterSave() hook, but to only send the email if the status field has changed.

Passing Data from beforeSave to afterSave Callback

The solution, then, is to use the beforeSave() handler to detect the change, and then somehow tell the afterSave() callback to perform the update in the case that a change is found.  There are many ways to pass data between these methods, but my favourite solution is to use the "pouch" of the Dataface_Record object.
The "pouch" is just an associative array that is a member of a Dataface_Record object.  There you can store arbitrary data to be passed around and between contexts of your application.   In the following example, we use the beforeSave() handler to detect the change in the 'status' field.  We then register a flag in the record's pouch which will be detected in the afterSave() method.
This version works exactly as required. Notice that after detecting the 'send_broken_email' flag in the afterSave() method, it clears this flag so that it doesn't affect future cycles, in case the same record object is saved multiple times during the same HTTP request.

Using PHP Anonymous Functions


The previous version works fine, but as a matter of style, I prefer to use PHP's anonymous functions (available since 5.3) to define callbacks in the beforeSave() handler which can be called in the afterSave() handler.

The thing I like about this style is that all of the business logic will then reside in the beforeSave() method. If I create additional rules that require some action in the afterSave() method, I can just add more callbacks. The afterSave() method doesn't need to change.. it just faithfully executes the list of handlers. This could even be extended to add callbacks from other areas of your app. But I'll leave that for another post.

Saturday, 12 April 2014

Context-Specific CSS Rules

One of the most frequent questions on the Xataface forum is how to hide parts of the UI in a context-specific way.  E.g. You might want to hide the search box only for a specific table, or for a specific class of user, or only when logged in.  As with most things in Xataface, there are many ways to achieve this, but one of the most flexible methods is using CSS.

If you haven't already added a custom CSS file to your application, you can do so by way of the Dataface_Application::addHeadContent() method.  Usually I place this inside my application delegate class' beforeHandleRequest() method.  E.g.

For this example, you would create a file named style.css and place it in the root directory of your application.  Because the beforeHandleRequest() method is called at the beginning of every request, this will result in the style.css stylesheet being included in every page of the site.  Now, if we wanted to hide the search box on the top of the UI (assuming we're using the g2 theme), we could add the following rule to our CSS file:

(This is because the "id" attribute of the search form is "top-search-form".  See the template source here).

This solution hides the search box throughout the entire site.  But what if you want to only hide it for certain tables.  E.g. I recently received a question on the forum about hiding the search box only in the "dashboard" table.  Two possible solutions for this come to mind:

  1. Create a separate stylesheet for the dashboard table and only include it if the request is for the dashboard table.

  2. Add CSS classes to the HTML <body> tag conditionally, and then use these to add CSS rules that only apply for desired context:

    This solution makes use of the block__body_atts to add a class attribute to the <body> tag and adds a CSS class of the form "table-{tablename}" to the body tag which can be targeted from the CSS file. In the case of the dashboard table, the <body> tag will have a "table-dashboard" class.
I personally prefer the second approach because it is much more flexible, and we can keep all of our styles in a single stylesheet.  For example, if we wanted to hide the search form for both the dashboard and foobar tables, we would just change our rule to:
This strategy can be expanded to mark more than just table contexts. For example, you could also add classes to mark whether the user is logged in or the role of the user.  Then you could create CSS rules that only apply to a particular role of user or whether or not the user is logged in.