Published by Mijingo

movie icon image

ExpressionEngine How-to Articles

Topics range from beginner to advanced, but are all born out of real world, professional, day-to-day use of ExpressionEngine. Need more information? Get training videos and ebooks on ExpressionEngine from Mijingo.

Edit SAEF in EE 2

Earlier this year, Ty Wangsness wrote a great article over at EMarketSouth which established the basics for enabling members of a site to edit channel entries outside the EE 2 control panel. While it was an excellent primer, his article only included instructions for enabling text input and text area fields, which left a lot of us wondering how to make richer fieldtypes work. Additionally, there were some sections which might have been unfamiliar for those of us who don’t have experience with EE 1.x SAEFs.

With that in mind, I’ve built on Ty’s article to write a more complete guide to creating an edit SAEF from start to finish, including how to get checkbox fields, radio buttons and image uploads working. To me, the lack of edit SAEF support is one of EE 2’s more glaring flaws, but this is a very solid if somewhat tedious workaround that will largely correct it.

Note: Barrett Newton Interactive have recently released SafeCracker, a commercial extension that enables edit functionality for SAEFs as well as a host of other good things. In fact, it lets users who aren’t even registered members post channel entries, enabling you to do really cool stuff with contact forms, comments/reviews and more. Though it’s still in beta, it looks mighty promising and you can pick it up for only $50 over on Devot-ee.

Why use an edit SAEF?

This tutorial assumes that we’re creating an Edit SAEF to enable members of a site who do not have access to the control panel to edit a single entry using the Channel module, which has a far richer variety of fieldtype options than is available with the more limited Custom Member Fields. While the edit SAEF has a ton of potential uses, this is by far the most common application I’ve seen for it, and it’s exactly what you need when developing a member-driven site that needs richer data for the member’s profile than is attainable with the default Custom Member Fields.

In the example site for this article (based closely on a real project I did recently), paying advertisers will be given a set of login credentials which grant access to a front end member’s area. Within the member’s area they can create and edit a business profile containing information about their company which will be displayed on the public part of the site. This tutorial encompasses only the edit portion of the workflow.

Ty’s Core Hack

Start by making sure you’re running EE 2.1 and then perform the core hack that Ty discusses in his article, which will enable the edit functionality on SAEFs.

Open up expressionengine/modules/channel/mod.channelstandalone.php_, line 227 as of EE 2.1.0. You’ll need to change the following line:

$success $this->EE->api_channel_entries->submit_new_entry($channel_id$data); 

to this:

$success = isset($data['entry_id']) ? 
  
$this->EE->api_channel_entries->update_entry($data['entry_id']$data) :
  
$this->EE->api_channel_entries->submit_new_entry($channel_id$data); 
Measure Twice, Code Once

To get all these fieldtypes working, as well as to maintain granular control over the styling of the edit form, it almost goes without saying that we are going to need to hard-code the form. If you add, remove or edit fields within this channel in your CP, you’ll need to manually replicate those changes on your edit SAEF form template. So before you start make sure you’ve planned thoroughly and finalized field labels, short names and of course fieldtypes. Leevi Graham gave an awesome lecture at EECI 2010 explaining the importance of planning channels and fields before you build out your site. I highly suggest checking it out. In the spirit of his talk I’ve compiled example documentation for my business profile channel.

                                                                                                                                                                                                                                                           
IDLabelShort NameTypeFormatting TypeOptions
2Phone Numberbusiness-phoneText Inputnone 
3Addressbusiness-addressText Inputnone 
4Description of Servicesbusiness-servicesTextareaXHTML 
5Payment Typesbusiness-payment-typesCheckboxesnoneCash, Check, Visa, MasterCard, American Express, Discover, Financing Options Available
6BBB Membershipbusiness-bbbRadio ButtonsnoneYes, No
7Logobusiness-logoFile Uploadnone 

The information in this table should look familiar to anyone who’s worked with EE 2’s channels and custom fields, with the possible exception of the first column: the custom field ID. In addition to having a label for reference in the CP and a short name that you use to call the data in your templates, each field has a permanent field ID which is assigned by EE when the field is created and kept hidden from you, unless you know where to look for it. It’s this field ID which is the key to building a working edit SAEF.

If you haven’t already done so, create the custom fields and then record each field’s ID once they’ve all been created. The easiest way to find a particular field’s ID is to navigate in the CP to Tools › Data › SQL Manager › Manage Database Tables, then find exp_channel_fields and click Browse next to it. This will bring up a table containing every custom channel field stored in your site’s database. The first column in the table contains the field IDs, which are integers counting up from 2.

P.S. The SQL Manager trick is a handy reference tool anytime you want an overview of all your fields at a glance, regardless of the field group to which they belong. It’s much easier than clicking into each field’s edit page to see important attributes like fieldtype, format or instructions. When I’m developing a site I usually add it as a shortcut tab at the top of my CP for quick access.

Building the Form

Having recorded the IDs for our custom fields, we can now begin coding the form. We’ll start with the {exp:channel:entry_form} tag, which will create some of the necessary hidden fields to get the form working. Since this is an edit form and not a publish form, we need to have something in the fields to edit, and there’s nary a better way to do that than by pulling the entry data right into the custom fields’ value attributes using the {exp:channel:entries} tag. So in effect, an edit SAEF is little more than a channel entries tag wrapped in a publish SAEF. Simple but effective.

Inside the form, there are a few hidden fields that we’ll want to manually specify which include “meta” information about the entry as a whole, including the title, URL title, entry ID and whether or not comments are enabled. On my example site, visitors will be able to write reviews of businesses in the form of comments, so I’ve enabled comments by default and kept the field hidden to prevent the members from accidentally closing their profile entry to comments.

{if logged_in}<!--checks to make sure that the user is logged in before displaying the form-->

 <
h1>Edit My Profile</h1>
            
 
{exp:channel:entry_form channel="profiles" return="/profile/success"}
  {exp
:channel:entries channel="profiles" author_id="CURRENT_USER" status="Open|Closed" show_expired="yes" show_future_entries="yes" use_live_url="no" limit="1"}
            
   
<!--storing entry metadata in hidden fields-->
            
    <
input type="hidden" name="entry_id" value="{entry_id}" />
    <
input type="hidden" name="author_id" value="{author_id}" />
    <
input type="hidden" name="url_title" value="{url_title}" /><!--we dont want them to be able to change the URL title in case it is linked to from off site-->
    <
input type="hidden" name="allow_comments" value="y" />
            
    <!--
title field (text input)-->
            
    <
label for="title">Company Name</label>
    <
em class="instructions">Your company's name</em>
    <input type="text" name="title" id="title" value="{title}" size="50" maxlength="100"  />
            
    <hr /> 

With all that in place, we can now embed the custom fields. Each custom field in an ExpressionEngine SAEF has two parts, a visible input which contains the editable data, as well as a hidden input which stores that field’s formatting type. Existing data is pulled into the data input’s value attribute using either the custom field’s short name or ID (bet you didn’t know you could call custom fields by ID!). The name attribute of the input is what must match the custom field ID for it to submit properly. For example, given a custom field whose ID is 2, the name attributes for the data input and formatting type input would be field_id_2 and field_ft_2, respectively.

Text Inputs and Textareas

As you can see in the code below, enabling the text input and textarea fieldtypes is quite straightforward. The only potentially tricky part is remembering to manually specify the formatting type if it’s set to something other than “none” (this is especially common for the textarea). As an example my textarea is set to “XHTML”, meaning that any carriage returns, including the first one, will start a new paragraph when the field data is called into a template.

<!--Phone (text input)-->

<
label for="field_id_2">Phone</label>
<
em class="instructions">Enter your company's phone number.</em>
<input type="text" name="field_id_2" id="field_id_2" value="{field_id_2}" size="50" maxlength="100"  />
<input type="hidden" name="field_ft_2" value="none" />

<hr />

<!--Address (text input)-->

<label for="field_id_3">Company Address</label>
<em class="instructions">Enter your company'
s street address.</em>
<
input type="text" name="field_id_3" id="field_id_3" value="{field_id_3}" size="50" maxlength="100"  />
<
input type="hidden" name="field_ft_3" value="none" />

<
hr />

<!--
Description of Services (textarea)-->

<
label for="field_id_4">Description of Services</label>
<
em class="instructions">Describe what types of services your company offers.</em>
<
textarea name="field_id_4" id="field_id_4" rows="10" cols="100">{field_id_4}</textarea>
<
input type="hidden" name="field_ft_4" value="xhtml" /><!--remember to set your formatting type correctly-->

<
hr /> 
Checkboxes

Checkboxes require a little more trickery, but the extra effort is worth the convenience of forcing users into a preset list of options which you can format consistently across the site. In this example, I’m using checkboxes to allow members to specify which payment types they accept. As you can see, the name attribute is slightly different from the other fieldtypes with a open and close square bracket at the end to signify an array. However, the real secret to this fieldtype is in EE’s conditional statements. We’ve hardcoded the entire list of empty checkboxes, then used a conditional statement within each checkbox to test if that item is checked off in the saved entry by using an if equal to statement. If a match is found, then we print the checked=“checked” attribute in that checkbox, accurately populating the checkboxes which were checked off in the saved entry and leaving the others unchecked.

<!--Payment Types (checkboxes)-->

<
label>Payment Types Accepted</label>
<
em class="instructions" style="margin-bottom: 10px;">Let potential clients know what types of payment you accept. (Check all that apply)</em>
<
label><input name="field_id_5[]" id="field_id_5" type="checkbox" {business-payment-types}{if item == "Cash"}checked="checked"{/if}{/business-payment-types} value="Cash">Cash</label>
<
label><input name="field_id_5[]" id="field_id_5" type="checkbox" {business-payment-types}{if item == "Check"}checked="checked"{/if}{/business-payment-types} value="Check">Check</label>
<
label><input name="field_id_5[]" id="field_id_5" type="checkbox" {business-payment-types}{if item == "Visa"}checked="checked"{/if}{/business-payment-types} value="Visa">Visa</label>
<
label><input name="field_id_5[]" id="field_id_5" type="checkbox" {business-payment-types}{if item == "MasterCard"}checked="checked"{/if}{/business-payment-types} value="MasterCard">MasterCard</label>
<
label><input name="field_id_5[]" id="field_id_5" type="checkbox" {business-payment-types}{if item == "American Express"}checked="checked"{/if}{/business-payment-types} value="American Express">American Express</label>
<
label><input name="field_id_5[]" id="field_id_5" type="checkbox" {business-payment-types}{if item == "Discover"}checked="checked"{/if}{/business-payment-types} value="Discover">Discover</label>
<
label><input name="field_id_5[]" id="field_id_5" type="checkbox" {business-payment-types}{if item == "Financing Options Available"}checked="checked"{/if}{/business-payment-types} value="Financing Options Available">Financing Options Available</label>
<
input type="hidden" name="field_ft_5" value="none" />

<
hr /> 
Radio Buttons

Radio buttons are pretty similar to checkboxes with a couple of exceptions. For one, we don’t need the open and close square brackets at the end of the name attribute. Secondly, the conditional statement used to check if a radio button is selected in the saved entry is structured slightly differently. Other than than the same ideas apply.

<!--BBB Membership (radio buttons)-->

<
label>Are you a BBB Member?</label>
<
em class="instructions">Indicate whether or not you are a BBB member.</em>
<
label><input name="field_id_6" id="field_id_6" type="radio" {if business-bbb == "Yes"}checked="checked"{/if} value="Yes">Yes</label>
<
label><input name="field_id_6" id="field_id_6" type="radio" {if business-bbb == "No"}checked="checked"{/if} value="No">No</label>
<
input type="hidden" name="field_ft_6" value="none" />

<
hr /> 
File Uploads

File uploads require the most patience, but once you’ve got one working you can easily add more. Each file upload actually consists of three fields: an HTML file input, a hidden input describing the field’s formatting type (usually none) and a third input for specifying the upload directory.

Since this is an edit form, we have to account for the fact that the user could have already uploaded a file, but there is also the possibility that the field is empty. They need to have the option to leave the field unchanged whether it’s empty or full, overwrite an existing file with a new one, or upload a new file where none was before. This is quite a few different flows but it’s very manageable given the tools at our disposal.

The first thing to do is check for an existing file using a conditional statement. If it’s found, it’s displayed so the user has a reference of what they’ve already uploaded.

We also need to make sure that if the user doesn’t make any changes, an existing file will be re-saved on submit. This is the trickiest part as the HTML file input for adding a new image must contain the name attribute of our custom field ID to work. Even if the user submits the form without choosing a new file to upload, the existing value will be replaced with the value of the file upload input, even if that value is null (blank). This in effect deletes the existing image from the entry against the user’s wishes.

To rectify this, we create a hidden input to store the value of the existing file (if it exists), and then reassign the name attribute to the visible file input only if it contains something when the user submits the form. To do this we’ll need a little help from jQuery. We start with an HTML file input with no name attribute. We then write a simple function which listens for a change event on the file input. If the file input has a value which is not equal to null on change (i.e. the user has put something into the field), the function assigns the appropriate name attribute to the file input. In this way, the file input only becomes “active” if the user selects a file to upload. If they don’t, any existing file data is preserved on submit by the hidden field.

We also must remember to include the field that specifies which upload directory that the custom field is tied to. It can appear in one of two forms, according to your preferences.

Option one, the simplest, is to include it as a hidden input with the value attribute preset to equal the ID of the file upload directory to which you want to send the file. This will send all file uploads from this field to the specified directory without the ability to for the user to choose. This is probably appropriate for most cases, as you don’t want to present your member with the option to choose a directory when they are unlikely to know what it means and equally less likely to choose the “correct” option to suit your internal organizational purposes for creating multiple directories.

Option two is to include it as an HTML select element, with the value of the option tags set to equal the IDs of whichever upload directories you’d like the user to be able to choose from. Anyone with the ability to specify file directories should probably have access to the CP, making this option almost a null issue but it works if you want to use it.

<!--Logo (file upload)-->

{if business-logo != ''<!--checking to see if a file already exists, and if it doesdisplay it-->
    <
label>Current Logo</label>
    <
img src="{business-logo}" />
    
{business-logo}<p><a target="_blank" href="{path}{filename}.{extension}">{filename}.{extension}</a></p>{/business-logo}
{
/if}

{if business
-logo != ''<!--customising the instructions to remind the user that an existing file will be overwritten-->
     <
label for="field_id_8">Upload A New Logo - <strong style="color: red;">this will overwrite your existing image in this slot</strong></label>
{if:else}
    
<label for="field_id_8">Upload A Logo</label>
{/if}

<input type="file" id="field_id_8" /><!--the file upload elementwithout a name by default-->
<
input type="hidden" name="field_id_8_directory" value="1" />

{if business-logo != ''}
<input type="hidden" name="field_id_8_hidden" value="{field_id_8}" /><!--creating hidden input to store existing file path if it exists-->
{/if}

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
    $(
document).ready(function() {
        
$('input#field_id_8').change(function() {
            
var $logo = $('input#field_id_8');
            if (
$logo.val() != '')
            
{
                
$(this).attr("name","field_id_8");
            
}
        }
);
    
});
</script>

<hr /> 
Conclusion

That pretty much sums it up. You now have a working edit SAEF that allows members without access to the CP to edit channel entries using a rich array of fieldtype options, including text inputs, textareas, radio buttons, checkboxes and file uploads. Keep in mind that this is not officially supported by EllisLab and the core hack may get overwritten if you upgrade to a later version in the future, though hopefully EllisLab will release support for edit SAEFs in later versions of EE 2. If you’re going to be needing a lot of edit SAEFs on your site, or need the ability to change the fields often and easily, you’ll probably want to check out SafeCracker.

Chris Brauckmuller
About Chris Brauckmuller

I am a University of Florida grad from Gainesville, FL specializing in illustrated Web layouts and ExpressionEngine development. I