Tuesday, 10 September 2013

Excel-Friendly CSV Files

Xataface has supported CSV export since the beginning, but I have recently been made aware that the CSV files that Xataface produces haven't been "MS Excel" friendly when it comes to UTF-8 special characters.  You may have noticed that if you export a data set as CSV from Xataface where some records include special characters (like é and å), and try to open it with MS Excel, the characters become garbled.  These CSV files work fine with Open Office, Google Docs, and pretty much every other spreadsheet program out there.  It's just that MS Excel doesn't seem to support UTF-8 encoded CSV files.

(Note: You can still import them into Excel using its import features, where you can specify the encoding explicitly, but if you try to simply open the CSV file with Excel, it will assume that the encoding is Mac Roman or Windows 1252 depending on whether you're on Mac or Windows).

Many solutions were tried to try to make these CSV files work nicely with Excel, but it seems that Excel simply can't handle UTF-8!

Today I implemented an experimental feature in Xataface to be able to export data to CSV that Excel can handle.  It works by using UTF-16 encoding instead of UTF-8 as well as some other settings specifically designed to cater to Excel (e.g. tab delimiter instead of commas, and a different mimetype).

You can enable this feature by adding the following to your application's conf.ini file:


[export_csv]
    format=excel

Then you can just use the export CSV action as normal (e.g. from list view click "Export" > "CSV" if using the g2 theme, or click the "Export CSV" icon in the upper right of the list view if using the default theme).

This change is currently in the master repository on GitHub and will be included in the 2.0.3 release.

Friday, 6 September 2013

Making Adding/Removing Related Records Easier


Xataface has supported 1:n (i.e. one-to-many) and n:m (i.e. many-to-many) relationships from the beginning.  By default, when a relationship is defined, it causes a corresponding tab to appear in the UI for each record of the originating table.  E.g. If I define a relationship named "Courses" on the `Students` table that represents all of the courses for which a student is registered (i.e. a 1:n relationship from the `Students` table to the `Courses` table), it causes a "Courses" tab to appear inside the details view of a Student record.

The relationship tab will include a list of all records in the relationship as well as buttons to add or remove related records.
The "Courses" tab showing the records in the courses relationship for a particular student.

View the source for this example application on GitHub

Editing Relationships from the Edit/New Record Form

Adding related records one at a time using the "Add New Related Record" form can be cumbersome, especially in cases where you routinely need to add multiple records to the relationship for every record in the originating table.  Xataface provides the ability to add and remove related records directly on the "edit" and "new" forms of the originating table.  In cases where the size of the relationship is small, this makes a lot of sense, as it puts all of the "editing" functionality in one place for the user.

The following widget types can be used to edit relationships:

Widget Type Supports 1:n Supports n:m
gridYesYes
checkboxNoYes
taggerNoYes

Using the grid Widget

The grid widget allows you to add, remove, and edit related records in a grid with one row per record.    The configuration happens in the originating table of the relationship, inside its fields.ini file.  The fields.ini definition a grid widget looks like:


[Courses]
    transient=1
    widget:type=grid
    relationship=Courses
    widget:columns="subject,course_number,course_title"


The key elements of this definition are:

  • The "transient" directive tells Xataface that this field is not backed by an actual column in the Students table.  (It is sort of a way of saying to XF "No, I'm not loopy!  I realize that there is no such column, but I want this anyways!)
  • The "relationship" directive specifies the relationship that should be used to populate the grid.  It happens to be the same as the column name we've chosen for the transient field, but it doesn't necessarily have to.  We could have named this field anything we like.
  • The "widget:columns" directive is a comma-delimited list of the columns in the related table that should appear in the grid.
(Please note that the example here using a 1:n relationship from students to courses is somewhat unrealistic - it assumes that the course records are not entities themselves, but just value records that are entirely owned by a student.  In the real world you would probably have a much more complex table structure - and most certainly would be using a n:m relationship between students and courses).

After adding this configuration, let's visit the "Edit" form of a Student record.  It should display our grid widget as follows:

Using the grid widget to edit the Courses relationship.  In this case the courses relationship is a 1:n relationship so it doesn't allow you to add existing records.


When you enter a value into any field of the last row, it will create a new empty row at the end of the widget for you to add a new record.  In this way, you can add as many related records as you like.

Using Checkboxes

If you are using a n:m relationship and you want to provide a simple way for the user to indicate which records from table A are related to which records in table B, you can appropriate the checkbox widget to generate a simple checkbox grid from which the user can select the related records.  The configuration for this might look as follows (assuming that the "Courses" relationship is now an n:m relationship):


[Courses]
    transient=1
    widget:type=checkbox
    relationship=Courses


Notice that this definition uses the same key elements as the grid widget, but it doesn't require a "widget:columns" directive because it is only displaying the "title" of each related record - no need to specify columns.

After adding this configuration, we can visit the "Edit" form for a Student record, and it should display our widget as follows:

Using the checkbox widget to edit the courses in the Student's courses relationship.

Now you can specify which courses a user is registered in by simply checking the courses' checkboxes on the Student edit form.

View the source for this example on GitHub

Using the Tagger Widget

The tagger widget can also be used to manage n:m relationships.   It provides an intuitive "Tagging" interface for the user.  To use it, you will need to install the tagger module.   For more information about the tagger widget see this post.

Caveats

This article glossed over a few items that you will likely face if you set up your relationships to be edited by a widget.  Some of these include:

  • Transient widgets are displayed as one of the first fields on the form by default.  You may want to adjust the field order to move it to a more appropriate place.
  • Now that you are providing a way for users to add and remove related records on the edit form, you may want to disable the standard way of adding records in the related tab.
  • For some relationships, it is sufficient to just have it displayed in the edit form.  You may want to hide the default relationship tab altogether, or to move the related list inside the "View" tab as a section.
You can find information on most of these issues in the Xataface forum and wiki.  And I hope to write more articles on these in the future so stay tuned.

Source Code

All of the source code for this article is available on Github.  The repository includes 4 branches:

  1. Master - The default application with a single 1:n relationship from Students to Courses with standard relationship management.
  2. grid_widget_1_to_n : Version of the application that uses a grid widget to edit the Students->Courses relationship on the Student edit form.  Still using a 1:n relationship.
  3. grid_widget_n_to_m : Changes the table structure and relationship to n:m; still using grid widget for editing the relationship.
  4. checkbox_widget : Same as the grid_widget_n_to_m branch except using a checkbox widget for adding/removing related records on the Student edit form.


Thursday, 16 May 2013

Using the "category" Directive to Place Actions in the Xataface UI

Actions are a core concept of Xataface.  They can be viewed from two perspectives:

  1. An action can be manifested as a button or link in the user interface.  E.g. The "New Record" button is an action as it is rendered in the user interface.
  2. An action can be thought of as an HTTP request handler.  E.g. When the URL's query parameters include -action=new, the new record action is called upon to handle the request.   This will render a new record form or process the input from a previously rendered new record form depending on the other parameters of the HTTP request.
In this tutorial, I want to focus on the first aspect:  how and where actions are rendered in the Xataface UI.

Creating an Action

To start out, we'll need to create a new action.  

Steps:

  1. Create a file named actions.ini inside the main directory of your application (if it doesn't already exist).
  2. Add the following anywhere inside the actions.ini file:
  3. Now try opening your application and notice the "Hello" button appear along with the other table actions. 
    After adding the hello action to the table_actions category, it appears with other buttons in the table actions (upper left).  This example shows the button using the g2 theme.  It would appear differently in different themes.
The placement of the action (in the upper left) was dictated by the category directive.  I chose to place the action in the "table_actions" category.  This category is intended to be used for actions that don't pertain to a specific record, but may be accessed at any time for a table.  Some examples include:
  • The new record action
  • The Import action
Currently clicking on the "Hello" button doesn't actually do anything because we haven't assigned it a URL (the url property).   At this point I just want to demonstrate how to show, hide, and locate the action buttons.  Future articles may discuss other aspects of actions like how to specify the URL that they point to.

Alternative Action Placement

You may be wondering what other possible positions are there for an action.  We've already seen the "table_actions" category, but there are many others.  In fact you can even create your own categories and embed actions inside the Xataface UI using your own custom templates.
Some other built-in categories include:
  • result_list_actions
    This category is intended for actions that operate on records in the list view.  They are rendered at the top and bottom of the list view.

  • record_actions
    This category is intended for actions that operate on the current record shown in details view.  They are rendered along the top bar of the record details view (which is used for rendering the output of most actions that pertain to a single record).
  • related_list_actions
    This category is intended for actions that operate on a list of related records.  They are rendered at the top and bottom of a related list.  This is very similar to the result_list_actions category, except that it is used for related lists.
    Notice that the "hello" action is rendered inside the "More" menu.  This drop-down is automatically created by Xataface to house excess actions in the case that there are too many actions in the category to fit the space nicely.
  • find_actions
    This category is intended for alternative "find" actions.  When a user enters a search in the top search field, they will be sent to the list view by default with a filter on the results.  You can provide optional responses to the search if you like, but creating your own custom search actions.
  • history_record_actions
    These are actions that are intended to operate on history snapshots of a record.  This is only relevant if your application has history enabled.  It will appear next to the record version in the history tab for the record.
    Notice the "hello" label appears next to the history snapshot ID 3.
  • personal_tools
    This category is intended for actions that pertain to a user's personal preferences or account.  These will be rendered in the user's personal menu in the upper right  along with actions like "Edit Profile", and "Log Out". 
  • management_actions
    This category is intended for actions that help administrators manage system settings.   They are rendered in the upper right in the "Control Panel" menu.
  • top_right_menu_bar
    This category is intended for actions that have global significance.  They are rendered in the top right on every page.  The only actions that are rendered here by default are the personal tools menu and the control panel menu. (g2 theme only)

  • top_left_menu_bar
    This category renders the action in the top left menu bar of the application, along with the list of tables in the app.   It is a convenient way of adding additional options to the tables menu.
    (g2 theme only).
    Action added to the top_left_menu_bar category.  Because there are already a lot of actions in this category, it has been added to the "More" drop-down menu.  Had there been fewer existing actions, this would have been rendered in the top menu bar directly.
  • list_export_actions
    This category is intended for actions that export the current result list in a different format.  It will be rendered inside the "Export" drop-down menu above and below the list view, along with actions like "Export CSV" and "Export XML".
  • record_export_actions
    This category is intended for actions that export the current record (shown in details view) in a different format.  It will be rendered in the "Export" drop-down menu of the record details page, along with actions like "Export CSV" and "Export XML". 
  • related_export_actions
    This category is just like the list_export_actions category except it is for actions that export the current related record list in a different format.  It is rendered in the "Export" menu of the related records view.
There are more actions than just these.  A good way to discover what action categories are available to you is to open the Xataface actions.ini file and the g2 module actions.ini file and see what categories are used in the existing actions.

More information about actions

This is just the tip of the iceberg for Xataface actions.  This post has likely created as many questions as it has answered.  There is more information about actions in the Xataface wiki and in the Getting Started guide.  E.g.

Monday, 13 May 2013

Best Practice for Xataface Permissions : NO ACCESS Unless ....

Xataface has a rich permissions system that offers quite a bit of flexibility in how you implement permissions.  A typical pattern for implementing permissions in Xataface is to add a "role" field to the users table to specify the user's role, and use this inside your getPermissions() methods to help determine what permissions should be granted to the user.

Many developers will take this pattern and then implement a getPermissions() method in their application delegate class similar to the following:

To summarize this strategy, it entails:

  1. Returning the permissions assigned to the current user's role (which would be defined in the permissions.ini file).
  2. Returning no permissions if either the user isn't logged in or they don't have value in their 'role' field.
While this approach may work, it could open up some security holes if you're not very careful.  Since you have defined the permissions in the application delegate class, these will be used as the default permissions for every table in the database.  If you have some tables that include private information (which will likely be the majority of your tables), you would need to explicitly provide more limited permissions on each of those tables individually by implementing getPermissions() methods in their respective delegate classes.

A Better Approach : Default NO ACCESS; Grant More As Needed

A better approach would be to grant NO ACCESS to all users, except for administrative users, at the application level.  Then you can grant additional permissions as needed at the table level.  E.g.
This grants users with a role of 'ADMIN', all permissions in the system, and no access to anyone else.

Saturday, 11 May 2013

How to use Chrome and CSS to Customize the Xataface Look & Feel


Xataface provides you (the developer) with many tools to customize the user interface.  Some options include:

  • Implementing Blocks & Slots in delegate classes.
  • Overriding templates.
  • Configuration directives in the various INI files.
  • Preferences.
  • Permissions & Authentication.
  • CSS
  • Javascript
All of these strategies have a time and place.  Before rolling up your sleeves and delving into templates, blocks, slots, preferences, and the like, I recommend seeing how far you can get with just pure CSS by creating your own custom stylesheet.

Using CSS, you can change the logo, hide components on the page, change color schemes, and even modify the layout to a certain extent.  Using a modern web browser like Chrome that includes some developer tools, it is fairly easy to peruse the DOM and come up with the appropriate CSS to modify it to your liking.

In this post I will show you how to change the logo and hide unnecessary sections of the UI by:
  1. Adding a custom CSS stylesheet to your Xataface application.
  2. Using Chrome's developer tools to identify the parts of the page that you want to modify or hide.
  3. Modifying or hiding portions of the UI that we identified in the previous step by adding CSS rules to our CSS file.

Including a Custom Stylesheet

We start by creating a blank CSS file in our application's directory.  We'll call it "style.css".  Then we need to tell Xataface to include this file in the <head> of all pages that are rendered in the app.   We do this inside the beforeHandleRequest() method of the Application delegate class (since this is run before every request is handled so we can ensure that the code will be executed:
We make use of the Dataface_Application class's addHeadContent() method that allows you to embed arbitrary content in the <head> section.  We use it here to add a <link> tag which includes our stylesheet.

Changing the Logo

In this example, I'm using the G2 theme.  Instructions for other themes will be slightly different because the logo is displayed in different positions using different methods.  

Steps:
  1. Open your Xataface application in Chrome.
  2. Right click on the Xataface logo, and select "Inspect Element".
  3. This will open the document inspector so that you can actually see the HTML document tree.  If you got here by right-clicking on the Xataface logo, then it should show up with the xataface logo div tag selected.
    The Chrome element inspector allows you to view the DOM tree.  Here you see the xf-logo div seelcted.  On the right you can see some of the CSS rules that apply to this element, including the background-image directive which specifies the image file to use for the logo.
  4. With the xf-logo div selected you should be able to review the CSS rules that apply to that tag.  The first matched rule (seen in the lower right of the above screenshot) specifies that the background-image is at images/small-xataface-logo.png.  This gives us a clue as to how to craft our own CSS rule to override this background-image.  Let's add a CSS rule in our style.css file to specify a different image file.

    #xf-logo {
        background-image: url(images/custom-logo.png);
    }


    Note that URLs in a CSS file are relative to the location of the stylesheet.  Therefore, we need to place our custom-logo.png file inside the "images" directory which should be at the same level as the style.css file (i.e. in the main site directory).
  5. Reload our page in the browser and see if the change worked:

  6. It looks like it worked, but since our new logo is a different size than the old one, we need to change the dimensions of the xf-logo div tag, so let's make the following change in the style.css file:
  7. And now refresh the page to see the change:

  8. You may want to tinker a little more with the CSS to adjust the position of the logo, but we're pretty much there.

Hiding Sections of the Page

Another common request is to hide sections of the page that your users don't need to access.  Keep in mind that there is a difference between your users not needing to access a feature and not being permitted to access a feature is very important.
Never rely on CSS to hide features that the user shouldn't have access to.  Use permissions for this.  Only use CSS to hide sections that aren't needed.

That said, let's take a look at the "View" page for a record, and make some decisions about which parts of the page are necessary, and which parts we want to keep. 



As an example, let's take an extreme approach and hide everything except what appears inside the "Details" portlet.  No menus.  No logos.  No search box, etc...

Removing the Left Column

  1. Let's use Chrome again to inspect the document so we can figure our how to target the left column.  We just need to right click somewhere in the left column and select "Inspect Element" option to open the element inspector. We can then hover over the various tags and see some feedback in the browser panel as the representation of tags on which you hover become selected.

    Hovering over the left_column div tag in the element inspector causes the  left column to appear selected in the browser panel.  This allows you to hover around and identify the tags that you want to hide or modify.
  2. We see that the id of the left column <td> tag is "left_column", so we can hide this in our style.css file by adding:

    Now, reloading the page we see that the left column is now gone. 


  3. Now, employing similar investigation techniques, we remove the top menu, the search box, the internal left column (i.e. with the tree menus and the record search), by adding the following to our CSS file:
    And the result is:
    With all of the sections removed, we are left with just the portions of the page that we want to display.
There is much more that can be done with CSS to customize the look and feel of a Xataface application.  We have only scratched the surface here.  I'll let you run with it from here to see what your creativity spawns.

Create Your Own Maillist Manager using the Xataface Email Module


Suppose you have a "contacts" table in your database that contains contact information of all of your customers or users.  It is probable that you intend to contact these by email - e.g. for a mailing list.  The Xataface Email module makes it a simple matter to use this table as a mailing list.

What it Does

The Email module adds a "Send Email" action to all designated tables that allows you to send email to all records in the current found set.

Features

  • HTML Email
  • Attachments
  • Email templates
  • Compatible with all existing found sets.  (E.g. you can perform a search for any subset of rows, and the module will send to the found set).

Prerequisites

Installation

  1. Create a modules directory inside your application's directory, if it doesn't exist already:

    $ cd /path/to/my/app
    $ mkdir modules
  2. Check out the latest SVN trunk of the ckeditor module into the modules directory:

    $ cd modules
    $ svn co http://weblite.ca/svn/dataface/modules/ckeditor/trunk ckeditor
  3. Check out the latest SVN trunk of the Email module into the modules directory

    $ svn co http://weblite.ca/svn/dataface/modules/Email/trunk Email
  4. Activate both the ckeditor and Email modules in the conf.ini file by adding the following to the [_modules] section:

    modules_ckeditor=modules/ckeditor/ckeditor.php
    modules_Email=modules/Email/Email.php
  5. Designate the tables on which you want the "Send Email" action to be available by marking them as implementing the Person ontology.  You do this by adding the following to the beginning of the fields.ini file of each table that should be designated:

    [__implements__]
        Person=1
  6. In addition, you may need to specify which field of the table contains the email address.  You do this by adding the email=1 directive to the field's definition in the fields.ini file.  E.g:

    [my_email_field]
        email=1


    If you don't do this, Xataface will have to try and guess which field to use - and it may be wrong!

Enabling Attachments

If you want to be able to send attachments with your email messages,  you will need to follow these additional installation steps:
  1. Install the ajax_upload module.  You can do this by navigating to the modules directory, and checking out the latest from SVN:

    $ cd modules
    $ svn co http://weblite.ca/svn/dataface/modules/ajax_upload/trunk ajax_upload
  2. Enable the ajax_upload module in your conf.ini file by adding the following to the [_modules] section:

    modules_ajax_upload=modules/ajax_upload/ajax_upload.php
  3. Specify the location of the directory to store the uploaded files, by adding the following to your conf.ini file:

    [modules_Email]
        attachments=path/to/attachments
        attachments_url=url/to/attachments

Sending Email

Now that we have the module installed, let's try to send some email.  In our example application, I have a "contacts" table with all of my contacts.
The "List" view for the contacts table.  Notice the "Send Email" button, which is present because the Email module is installed and the contacts table is designated as a "Person" entity.

You can see from the result descriptor in the upper right that the current found set has 1155 records.  If you want to send an email to all 1155 contacts, just click the "Send Email" button at the top.  This will open the email form:
The email form.  The Message body uses CKeditor to allow you to craft an HTML email in WYSIWYG fashion.


Before proceeding with sending the email, you should double check that you are sending to the found set that you intend.  Look at the description at the top of the form that says "Please complete the form below to send email to the 1155 email addresses...", and ensure that the number looks right.  You should also click the "Show Email Addresses" link to reveal a list of the email addresses that will be mailed.  The worst thing you can do is accidentally send an email to thousands of people.
If you accidentally click "Send", you can still cancel or pause the email queue at any time - so there are some fail safes in place.

So let's send a simple email to all of our found set:
A simple email by filling in the subject, from, and message body fields.  Notice that the "From" field should include the email address in the standard email notation: Name <address>.
Now that we're ready, click the "Send Now" button:
This will show an email progress form to allow you to track the progress of the mailout:

You can cancel/pause the mailout at any time by clicking the "Cancel" button.  Clicking this button will take you to a "Job Paused" screen:
You can resume the job by clicking "Resume Job", and you can also view the mail log to see a detailed log of all of the email that has already been sent (by clicking "View Mail Log"):
The email history log shows every email that has been sent, along with status information about the Email.


In my example email here, it sent out 96 emails successfully before I paused it.  Logging into my email program, I can view the email:
The resulting email as opened in my Email reader

Opting Out of the Mail List

Notice that the mailer automatically added a line at the bottom of the email to give the recipient the option to "Opt out" of the mailing list.  This "opt-out" feature is built into the Email module and is handled automatically for you.  The module keeps a "black list" of email addresses that should not receive email.  When a batch email is created, it automatically skips any recipient addresses on this black list (unless you explicitly tell the Email module to ignore the black list on the email form).

Just to see what happens, I'm going to click on the link to opt out:

Form to opt out of receiving email notifications

This form gives the user an opportunity to reconsider opting out.  If they are sure that they don't want to receive email from this list, they can click the button on this form.  

They can opt back in at any time by clicking the same link that is in the email.

Customizing the Opt-Out Instructions

In some cases you might not want the user to see this opt-out message, or you may want to customize it to fit your purposes.  This can be done using either a conf.ini  file directive or a delegate class method.  For this example, let's just use the conf.ini file directives.  You can simply add the following section, and sub-directives to your conf.ini file:

opt_out_html = "<hr/><p>Click <a href='$url'>here</a> to opt out of our list.</p>"
opt_out_text = "\r\n\r\n----------------------\r\n To opt out, go to $url \r\n"

You can specify any text you want here.  Just embed the $url variable in the content where you want the link to the opt-out page to appear.

Wednesday, 8 May 2013

Updating Xataface Applications to Use the G2 Theme

NOTE:  As of version 2.1, the g2 theme is installed by default so the steps described in this article are no longer necessary.

The default Xataface theme was built on the old Plone 2.0 stylesheet from 2005.  Safe to say, it looks its age.   Clearly, Xataface needs a face lift.  The only question was how to introduce the modifications.  There were two primary options to choose from:

  1. Modify the core Xataface templates and stylesheets.
  2. Create an optional module that overrides the default templates and stylesheets.
In order to cause minimal disruption to existing Xataface applications, I chose the latter, and the solution is embodied in the g2 module - which includes new Javascripts, Stylesheets, and templates that look a little more modern.

The default Xataface theme.  Notice the tables list on the left and vertical because there are more than 10 tables included in the tables menu.
Same page using the G2 theme.  Notice the tables listed horizontally along the top.  The "More" drop-down menu lists the remaining tables.


Some features of the g2 module include:
  • jQuery.  jQuery is now included by default - so it is available to all scripts and Javascript modules.
  • Advanced Find form.  The advanced find form is now accessible using a dynamic Javascript pull-down effect so that users don't have to navigate away from the existing list or details page in order to perform a find.
    The Advanced find form appears via pull-down.
  • Selected Record Actions.  Result list actions are now all dialed in to operate on the records that are "checked" in list view.
    The list view now shows result list actions across the top and bottom of the list.  These all operate on the selected rows (i.e. the rows whose checkboxes are checked).
  • Result controller.  The result controller is now less cluttered, and only gives users option to change the "limit" and "start" when they click on the result details.
    The result list controller is more streamlined.  The "1-11 of 11" label is all that appears.  Clicking on that displays the pop-up dialog to modify the "skip" and "limit" settings.
  • More actions...  Action lists are now limited to 7 actions in a particular category.  Additional actions, are then included in an 8th "More" drop-down menu.  This makes the interface far more extensible when there are large numbers of tables.
    Now excess tables are listed in the "More..." menu automatically.  This is the case with all action groups.

How To Install

  1. Create a "modules" directory in your application if one doesn't already exist.
    $ cd /path/to/myapp
    $ mkdir modules
  2. Check out the latest g2 module from the subversion repository, into the "modules" directory.
    $ svn co http://weblite.ca/svn/dataface/modules/g2/trunk g2
  3. Add the following to the [_modules] section of your application's conf.ini file.:
    modules_g2=modules/g2/g2.php
  4. Clean out the templates_c directory to remove old cached templates.
    $ rm -rf templates_c/*

Video Tutorial


Tuesday, 7 May 2013

Using the Xataface Tagger Widget for n:m relationships

The Xataface tagger widget allows you to add and remove records from a many-to-many relationship with a simple "Tag" interface.  This widget is meant to be used with a transient field (similar to the grid widget) so that users can manipulate the records in the relationship directly on the edit/new record form.

The user is able to type into a text field free form, and is assisted by auto-complete suggestions.  If they select one of the auto-complete suggestions, then they will be adding a link to an existing record.  If they don't choose one of the suggestions, they will be creating a new record in the related table and adding it to the relationship.

Since a single string isn't sufficient (in most cases) to create a new record (e.g. which field should the string be saved in?), you may need to implement that fieldname__addTag() method in the table delegate class to help guide how a tag should be added.

After the tag has been created, the user can double-click the tag to open the record's edit form and make further modifications.

Prerequisites

Installation

  1. Create a directory named "modules" inside your application's folder, if it doesn't already exist.  (e.g. /path/to/my/app/modules).
    $ cd /path/to/my/app
    $ mkdir modules
  2. Change directories to the modules directory that you just created:
    $ cd modules
  3. Check out the latest source of the tagger module from the SVN trunk:
    $ svn co http://weblite.ca/svn/dataface/modules/tagger/trunk tagger
    When you are done you should have the tagger source in the directory modules/tagger.  Ensure that you have modules/tagger/tagger.php, etc....  If not, you need to move it or rename it so that the path is correct.
  4. Add a [_modules] section to your application's conf.ini file, if it doesn't already exist.
  5. Add a line in the [_modules] section of your conf.ini file as follows:
    modules_tagger=modules/tagger/tagger.php 

Configuring a Relationship to use the Tagger Widget

Now that the tagger module is installed, we can set up our n:m relationships to be editable via a tagger widget on the edit form of the parent record.
For example, suppose we have a database with a "cities" table and a "articles" table, where cities can be associated with multiple articles and vice-versa.  I.e. There is an n:m relationship between cities and articles.
The cities relationship is defined in the articles table relationships.ini file as follows:

[cities]
    cities.id = articlecity.city_id
    articlecity.article_id = "$id"

You can see that this is a n:m relationship (among other things) because it makes use of a join table named articlecity to join them together.

Steps to Set up Tagger Widget on Cities relationship:

  1. Add a transient field to the articles table's fields.ini file as follows:
    [cities]
        widget:type=tagger
        relationship=cities
        transient=1
        tagger_label=name
    

    This says that we are creating a transient field (i.e. a field that doesn't really exist in the database, but should appear on the edit and new record forms) named "cities" using the "tagger" widget.  The relationship directive sets the widget to work on the "cities" relationship.  The tagger_label directive specifies which field from the "cities" table should be used as the text that is displayed in each tag.
  2. Load the "New Article" form in your browser to make sure that the tagger field shows up.  If you receive errors, you should check your PHP error log to find out what the error is.  If it worked it will look almost like a normal text field:
    The "Cities" field rendered with the tagger widget.
    When you start typing in the field, it will give you auto-complete suggestions as follows:
    Auto-complete in tagger widget.  If it finds any matching records in the related table, it will show them here.

  3. Implement the cities__addTag() method in the articles delegate class.  At this point (before implementing cities__addTag()), you should be able to add existing cities as tags without error.  But if you try to enter a new city, you'll receive a little error indicator because it wasn't able to add the city to the cities table.  This is because the cities table has other NOT NULL fields that need to be filled in, other than the city name.  E.g. it needs a province or state.
    The cities__addTag() method.  Our method looks like:

    function cities__addTag(Dataface_Record $record, $value){
        $city = new Dataface_Record('cities', array());
        $city->setValues(array(
            'prov_state_id' => DEFAULT_PROV_ID,
            'name' => $value,
            'url' => strtolower($value)
        ));
        return $city;
    }
    

    This method takes the "article" record as the first parameter, and the string value of the tag to add as the 2nd parameter.  In this case, we are creating a new Dataface_Record for the cities table with the name specified by $value, and a default province ID.
  4. Test out adding a new tag.   You can do this by clicking on "New Article", then start to type in the "Cities" field.
  5. You should also be able to double-click the tag to reveal the edit form for the city:

Video Tutorial