Building an ExpressionEngine Fieldtype

For my turn driving the EE blog, I thought I walk you through the creation of a new fieldtype.  Before we get started, I should probably give you a heads up about the approach I’m taking.  One of my professors once described an absolutely brilliant lecture he’d attended where a physicist was explaining some uber-high level ‘physicy’ stuff to an audience of laymen.  He did it by starting with the simplest of analogies.  Of course, the simplest of analogies was totally wrong.  But once his audience grasped the logic of the simplest analogy, he would then draw a new, slightly less simple analogy.  Which—was also wrong.  And he kept building upon all of these simple, but wrong, analogies until the audience could grasp the basics that were NOT wrong.

Or to quote Terry Pratchett, “Actually that sentence is wrong in every particular, but it’s quite a useful lie.” (Night Watch)

So with that in mind, let’s start building our super simple fieldtype.  Let’s say you build lots of sites where your clients need to enter addresses into entries: the street address, city, state and zip code.  Rather than creating a custom field for each element, you’d really rather have a single fieldtype you can drop in and customize to your own needs.

Let’s call our new fieldtype an ‘Address Group’.  First, you’ll create a folder called ‘address_group’ and drop it into system/expressengine/third_party/.  Inside that folder, we’ll need a fieldtype file called ft.address_group.php.  We’ll also want a language file, so we can quickly accommodate different languages.  For that, we’ll add language/english/address_group_lang.php inside our ‘address_group’ folder.

With our files in place, we’ll begin to flesh out our ft.address_group.php file.  We need to be able to display our fields on the publish page (display_field()), prep the returned data so it can be saved in a usable format (save()), and display the field contents on the fronted (replace_tag()).  We also need to fill in some basic information, such as the fieldtype name and version number.  For this fieldtype, I’m also going to add in an array of fields that will make up my address group field.  This will save me replicating the field names in a number of spots and make it easier to add new ones if I ever need to do so.

The resulting file looks like this:

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class 
Address_group_ft extends EE_Fieldtype {
    
    
var $info = array(
        
'name'        => 'Address Group',
        
'version'    => '1.0'
    
);

    var 
$address_fields = array('street_address''city''state''zip');

    
// --------------------------------------------------------------------
    
    /**
     * Display Field on Publish
     *
     * @access    public
     * @param    existing data
     * @return    field html
     *
     */
    
function display_field($data)
    
{

    }

    
// --------------------------------------------------------------------
        
    /**
     * Replace tag
     *
     * @access    public
     * @param    field data
     * @param    field parameters
     * @param    data between tag pairs
     * @return    replacement text
     *
     */
    
function replace_tag($data$params = array(), $tagdata FALSE)
    
{

    }
    
    
// --------------------------------------------------------------------
    
    /**
     * Prep data for saving
     *
     * @access    public
     * @param    submitted field data
     * @return    string to save
     */
    
function save($data)
    
{

    } 

Now it’s just a matter of filling in our methods.  Let’s start by figuring out how we’re going to store our data.  We’re going to be dumping the results of several different fields (street, city, etc) into our single content field in channel_data.  A simple way to do this is to pipe delimit our information and store it that way.  We can use the save() method to prep the data and return it in a string, suitable for saving in the channel_date field.

$address = array();
        
        foreach(
$this->address_fields as $key)
        
{
            $address[] 
$this->EE->input->post($key.'_field_id_'.$this->field_id);
        
}
        
        
return implode('|'$address); 

That’ll work.  We just need to add a way to collect the data.  That’s where the display_field() method comes in.  The display_field method is passed the existing value of our field, which we need for editing.  So if $data exists?  We’ll break that string apart and assign the resulting elements to our address variables.  If it doesn’t, we’ll set those address variables to empty strings.

if ( ! empty($data))
        
{
            
list($street_address$city$state$zip) = explode('|'$data);
        
}
        
else
        
{
            
foreach($this->address_fields as $key)
            
{
                
$$key '';
            
}
        } 

Now we have values for each of our address fields.  We’ll plug those into our form fields, and output the group in a table.  Since we’re using some custom labels for the fields that make up our address grouping, we’ll also want to load our language file.  Once that’s all set, simply return the resulting string:

$this->EE->lang->loadfile('address_group');
        
$form '<table>';
        
        foreach(
$this->address_fields as $key)
        
{
            $form 
.= '<tr><td>';
            
$form .= lang($key$key.'_field_id_'.$this->field_id);
            
$form .= '</td><td>';
            
$form .= form_input($key.'_field_id_'.$this->field_id, $$key);
            
$form .= '</td></tr>';
        
}

        $form 
.= '</table>';
        return 
$form

We can now save and edit address data.  What we can’t do is display it using tags.  Well, that’s a lie.  An Address Group field will display just fine at this point but it will display a pipe delimited string, which is not what we’re going for.  Let’s use the replace_tag() method to make things a bit nicer.

if ( ! empty($data))
        
{
            
list($street_address$city$state$zip) = explode('|'$data);
            
$ret '<p>'.$street_address.'<br />'.$city.', '.$state.' '.$zip.'</p>';
        
}
        
        
return $ret

Now any Address Group type fields will display a nice paragraph of formatted data, without a bunch of random pipes scattered in it. 

And we’re done!  To a given value of ‘done’, anyway.

Let’s keep going.  One reason we needed a custom field was for increased control.  And one thing we wanted to do with our address group is require that all of the address fields be filled out, but ONLY if at least one of the fields is.

In other words, I don’t want to have to require an address.  But if an address IS entered, I want to require the FULL address.  We can do this by using the validate() method.

function validate($data)
    
{
        $this
->EE->lang->loadfile('address_group');
        
$total count($this->address_fields);
        
$valid 0;

        foreach(
$this->address_fields as $key)
        
{
            $post_data 
$this->EE->input->post($key.'_field_id_'.$this->field_id);
            
$valid += ( ! empty($post_data)) ? 0;
        
}
        
        
if ($total == OR $total == $valid)
        
{
            
return TRUE;
        
}

        lang
('no_partial_addresses');
    

Ta da!  Now we’re really done.  Except… it would be kind of nice if we could put some default values into our address fields.  Most of the times those addresses are in the same state, often the same zip code.  Let’s make it possible to save some time by allowing us to set some per field defaults.  To set a default, we’ll need to add the display_settings() method.

function display_settings($data)
    
{
        $this
->EE->lang->loadfile('address_group');
        
$prefix 'address_group';
    
        foreach(
$this->address_fields as $key)
        
{
            $data[$key] 
= (isset($data[$key])) ? $data[$key] ''
            
            
$this->EE->table->add_row(
                
lang($key$key), form_input($key$data[$key])
                );
        
}
    } 

That’s all there is too it.  The values will be available in the $this->settings array (and the per field setting will override any global settings if you have them).  We need to tweak our display_field() method to use the settings array where appropriate and we’ll have default values in our publish fields!

If you want to see the slightly less simplified final product, I’ve pushed it to a CodeSnips repo on GitHub.  There’s also a fieldtype template that I use as a basis for starting off my ft.fieldtype.php files- I fill in the methods I need and remove the ones I don’t.  You’ll find that in the ‘templates’ folder as ft_template.php.  I hope this proves useful to those of you just striking out into EE fieldtypes!

.(JavaScript must be enabled to view this email address) or share your feedback on this entry with @ellislab on Twitter.