Overview top

As of version 1.2 we have implemented a simple API to enable developers to make their Fieldtype compatible with Better Workflow.

There are three elements to the API:

  1. A session variable which can be used for conditional branching within existing methods in your FieldType controller
  2. Custom methods which can be added to your FieldType controller
  3. The ability to bind JavaScript callbacks to the Bwf object

If your FieldType stores its data in the standard exp_channel_data table you can employ the Simple Compatibility process, this requires just the use of the session variable. If it uses its own table(s) you will need to use the Advanced Compatibility process which utilises all three elements.

Naming conventionstop

Anywhere in these docs where we refer to a Draft we mean a BWF Draft, as in the copy of the Entry which Better Workflow creates when you are editing a live entry.

Within Better Workflow we also have a status of draft, this is not the same thing. By default both an Entry and a Draft are created with a status of draft, as you Submit for Approval or Publish this status is updated.

In the context of the Better Workflow API you only need to concern yourself with the first of these concepts, the BWF Draft. The $this->EE->session->cache['ep_better_workflow']['is_draft'] session variable is only set when processing a BWF Draft and it is only in this context that you need to alter your code.

Simple compatibility top

If you are saving the values from your FieldType in the exp_channel_data table and your FieldType performs some data processing after form submission, it may be necessary to make some modifications to your code.

For example, if your save() method receives an array and converts it into a comma delimited list which it returns to be saved in the database. As BWF uses it own saving process, in this context the data would be saved as the full array and when the Live Draft was next loaded into the publish view or rendered by the template, the FieldType's methods would receive the data in an unexpected format.

There are two ways to resolve this:

  • Ask Better Workflow to call you save() method when it saves a Live Draft and format it there
  • Set some conditional branching in your existing methods to check if they are being send data from BWF

Call your existing save() method

To do this you just need to add a public property to your fieldType.

this->ep_better_workflow_use_save_method = true;

Update display_field() method

When editing a draft, if you need to convert the data format sent by BWF simply add the following conditional code to the top of the display_field() method. Using this you can manipulate the data into whatever format is expected.

display_field() example - to convert to a string

// Check to see if we are loading a draft into the publish view
if (isset($this->EE->session->cache['ep_better_workflow']['is_draft']) && $this->EE->session->cache['ep_better_workflow']['is_draft']) {
  if (is_array($data)) $data = implode($data, ',');
}

Update replace_tag() method

When previewing a draft BWF you can add the same code to the replace_tag() method.

replace_tag() example - to convert to a string

// Check to see if we are loading a draft into the publish view
if (isset($this->EE->session->cache['ep_better_workflow']['is_draft']) && $this->EE->session->cache['ep_better_workflow']['is_draft']) {
  if (is_array($field_data)) $field_data = implode($field_data, ',');
}

If you use the pre_process() method you can put this code in here instead.

Advanced Compatibility top

If your FieldType stores its data in its own table the process is a little more complex but should not require significant alterations to your code. In additon to the leveraging the three elements of the API you will also need to make a small change to the way you store your data.

Database update

You will need to create a method of differentiating between 'entry' data and 'draft' data. The quickest way to do this is to add an extra column to your table to store an is_draft flag. This can easily be done in the upgrade process using the dbforge->add_column() method

Example

$field = array(
  'is_draft' => array('type' => 'TINYINT', 'constraint' => '1', 'unsigned' => TRUE, 'default' => 0),
);
$this->dbforge->add_column('my_table', $field);

Custom methods

You will need to add some custom methods to your FieldType controller so BWF can tell it to create, update, discard and publish a draft. These are:

  1. draft_save()
  2. draft_discard()
  3. draft_publish()

draft_save()

Whenever BWF creates or updates a draft this method is called. You should process the data exactly as you would using the post_save() method except you save your data with the is_draft flag set to true.

To avoid code duplication it might to a good idea to abstract the saving process and call the same private method from both post_save() and draft_save().

Arguments

Type Description
$data Array Field data as submitted from publish form
$draft_action String Either 'create' or 'update'

Other variables

Type Description
$this->settings Array entry_id, field_id and channel_id

Return

[Updated] field data (Mixed)

Example

public function draft_save($data, $draft_action)
{
  // Some Vars
  $entry_id = $this->settings['entry_id'];
  $channel_id = $this->settings['channel_id'];
  $field_id = $this->settings['field_id'];

  // We are creating a new draft
  if ($draft_action == 'create')
  {
    foreach ($data['item'] as $order => $item)
    {
      // Insert each item to your table with is_draft set to true
    }
  }
  else // We are updating a draft
  {
    foreach ($data['item'] as $order => $item)
    {
      // Is this an existing item
      if(isset($item['id']))
      {
        // Update this item's record in your table
      }
      else
      {
        // Insert this item into your table with is_draft set to true
      }
    }
  }

  return 'data_updated';
}

draft_discard()

This method is called when BWF discards a draft and should be used to delete all content for the field which is flagged as draft.

Other variables

Type Description
$this->settings Array entry_id, field_id and channel_id

Return

Void

Example

public function draft_discard()
{
  // Delete all the current draft content
  $this->EE->db->delete('my_table', array('entry_id' => $this->settings['entry_id'], 'field_id' => $this->settings['field_id'], 'is_draft' => 1));

  return;
}

draft_publish()

In BWF publishing a draft involves replacing the current live entry's data with the draft data and then deleting the draft record.

In the context of a third party FieldType that means deleting all the data where the is_draft flag is currently set to false (as in the live entry's data) and then updating the remaining data, switching the the is_draft flags from true to false.

Other variables

Type Description
$this->settings Array entry_id, field_id and channel_id

Return

Void

Example

public function draft_publish()
{
  // Some Vars
  $entry_id = $this->settings['entry_id'];
  $field_id = $this->settings['field_id'];

  // Delete all the current live content
  $this->EE->db->delete('my_table', array('entry_id' => $entry_id, 'field_id' => $field_id, 'is_draft' => 0));

  // Update the current draft content to be live
  $this->EE->db->where('entry_id', $entry_id);
  $this->EE->db->where('field_id', $field_id);
  $this->EE->db->where('is_draft', 1);
  $this->EE->db->update('my_table', array('is_draft' => 0));

  return;
}

Conditional branching

As per the Simple compatibility method, you need to leverage the is_draft session variable to tell your FieldType's display_field() and replace_tag() methods to return the 'draft' data when we are editing or previewing a draft.

Example

public function display_field($data)
{
  // Set a default value of false for the is_draft flag
  $is_draft = 0;

  // If we are loading a draft into the publish page update the flag to true
  if (isset($this->EE->session->cache['ep_better_workflow']['is_draft']) && $this->EE->session->cache['ep_better_workflow']['is_draft'])
  {
    $is_draft = 1;
  }

  // Now select yout data passing the flag's value
  $this->EE->db->select(*);
  $this->EE->db->where('entry_id', $entry_id);
  $this->EE->db->where('field_id', $field_id);
  $this->EE->db->where('is_draft', $is_draft);
  $this->EE->db->get('my_table');

  // Do the rest of your code as normal...
}

If your FieldType uses its own tags to call a module you can use the same session variable there to select your draft data for previews.

JavaScript Callbacks

When BWF creates an in-line preview it first runs an Ajax save of the entry so that the preview exactly mirrors the current state of the entry or draft. For FieldTypes which use their own database tables this means new rows are created. When the preview window is closed the publish page needs to be updated so that it reflects the new state of the database.

Having updated the publish page's DOM any JavaScript behaviour which is attached to your FieldType will need to be re-instantiated.

You can use the Bwf.bind() method to tell it that your FieldType needs updating and also define a Callback Function to be triggered once the DOM has been updated.

Example

Bwf.bind('my_field', 'previewClose', function(){
  // my code to init my field type
});

Events

BWF supports the following events:

Event Description Example
ajaxSave Called just before the publish form data is submitted on a 'Save and Preview' Used to perform and field pre-processing prior to submission, updating the hidden field on a WYSIWYG field for example
previewClose Called after the preview window has closed Used to re-instantiate any field which needs to be updates after a save

Status transition properties

Information about the current state of an EE Entry or BWF Draft is available from the Status Transition object at any time. This can be easily accessed in your JavaScript code

Type Description
Bwf._transitionInstance.entryExists boolean Is there a valid EE Entry
Bwf._transitionInstance.entryStatus string Current status of EE Entry
Bwf._transitionInstance.draftExists boolean Does the current EE Entry have a BWF Draft version
Bwf._transitionInstance.draftStatus string Current status of BWF Draft version
Bwf._transitionInstance.draftTemplate string URL of preview template
Bwf._transitionInstance.userRole string Current user role (e.g. Publisher)

Examples

Bwf.bind('my_field', 'previewClose', function(){
  if(Bwf._transitionInstance.draftExists) {
    // Do some code specific to a draft
  } else {
    // Do something else
  }
});

Extension Hooks top

At the moment Better Workflow only has one extension hook, bwf_notify_users.

bwf_notify_users

This hook is called every time an entry is submitted for approval. This hook can be used to create custom notification rules so that approval emails can be sent to specific people/groups.

Parameters

This hook is called from within the existing _notify_users() method in the ep_status_transition.php library file and is triggered every time an entry is submitted for approval. It passes the current $channel_id, $entry_id, $entry_title to an external method to process as required.

Return values

If the external method returns true Better Workflow will continue with its internal notification process, it if returns false Better Workflow will exit the function.