Using the Ajax Helper for Inplaceeditor and Inplacecollectioneditor Fields

By Paul Marshall (abalonepaul)
Although there is quite a bit of information available for using the Scriptaculous inplaceeditor, there is not much information for using it with CakePHP. In additon, there is very little information on using inplacecollectioneditor for select boxes. I have a contact form where all of the fields are using inplace editors. The form displays contact information as a combined view and editing form. If you have ever used Goldmine, you will know what I'm talking about.

Much of what you will find in this tutorial is similar to what you will find elsewhere, with the exception of the update methode I wrote. Most of the update methods work fine with text fields, however, they return key and not the value when updating a select box. I will cover the use of both the inplaceeditor for text fields, as well as the inplacecollectioneditor for select boxes. The collection feature is now integrated into the editor function.
Ok we are going to use a simple contact form for our example. The form will have a text field for the contacts name and a select box for the contacts state. We need two models, Contact and State, a contacts_controller.php and a view.ctp for your form.

We'll assume you already have a contacts table with id, name, and state_id fields and a states tables with an id and name field. The state_id field is a foreign key to the states id. Now let's move on to the model. Your Contact model should look something like this:

Model Class:

Download code <?php 
class Contact extends AppModel {

    var 
$name 'Contact';

    var 
$belongsTo = array(
            
'State' => array('className' => 'State',
                
'foreignKey' => 'state_id',
                
'conditions' => '',
                
'fields' => '',
                
'order' => ''
            
)
        );
}
?>

Your State model should look something like this:

Model Class:

Download code <?php 
class State extends AppModel {

    var 
$name 'State';
}

?>

Now let's move on to your contacts_controller which should start out looking something like this:

Controller Class:

Download code <?php 
    
class ContactsController extends AppController {

        var 
$name 'Contacts';
        var 
$uses = array('Contact''State');
    var 
$helpers = array('Html''Form''Ajax''Javascript');
}
?>

What is important to note here is that we need to make sure that we include the Ajax helper. Now lets add following method to your contacts_controller. This function is responsible for updating the database and returning the updated value back to the view. This is where most of the other methods choke on the select boxes. Instead of returning the state's name, they return the state's id. The database gets update properly, however, the state's id is sent back to the view. I modified this method to work with CakePHP and the common scenario of handling foreign key associations in a select box. Let's add the ajax_update method and then talk about it.

Controller Class:

Download code <?php 
    
function ajax_update($id,$sub){ 
//Step 1. Update the value in the database
        
$value $this->params['form']['value']; //new value to save 
        
$this->Contact->id $id
        if (!
$this->Contact->saveField($sub$value,true)) { // Update the field
             
$this->set('error'true); 
        } 
        
$contact $this->Contact->read(array($sub), $id); 
//Step 2. Get the display value for the field if the field is a foreign key
                // See if field to be updated is a foreign key and set the display value
        
if (substr($sub,-3) == '_id'){
            
// Chop off the "_id"
            
$new_sub substr($sub,0,strlen($sub)-3); 
            
// Camelize the result to get the Model name
            
$model_name Inflector::camelize($new_sub);
                        
// See if the model has a display name other than default "name"; 
            
if (!empty($this->$model_name->display_field)){
                
$display_field $this->$model_name->display_field;
            }else {
                
$display_field 'name';
            }
                        
// Get the display value for the id
            
$value $this->$model_name->field($display_field,array('id' => $value));
        }

//Step 3. Set the view variable and render the view.
                
$this->set('value',$value); 
        
$this->beforeRender();
        
$this->layout 'ajax'
    } 

?>

This method is passed the id of the contact record to update and the updated value. There are three steps to this method. First, it updates the database with the new value. Then, we check to see if the field that was just updated is a foreign key select box, if so, we either get the Model's $display_field or the default display field "name." The last step is setting the view variable and rendering the view.

Now we also need a method for handling the view so we also add the following method the controller:

Controller Class:

Download code <?php 
    
function view($id null) {
        if (!
$id) {
            
$this->Session->setFlash(__('Invalid Contact.'true));
            
$this->redirect(array('action'=>'index'));
        }
        
$this->set('contact'$this->Contact->read(null$id));
                
// Build the states array and set the view variable
        
$states $this->State->find('list');
        foreach (
$states as $key => $value) {
            
$stateListAjax[] = array($key,$value);
        }
        
$this->set('stateListAjax'$stateListAjax);

    }       

?>

This method creates the array of states to be used for the select box and sets the view variables.

Now, we need to include the RequestHandler component and disable debuging output for the AJAX calls, so modify your app_controller so it looks like this:

Controller Class:

Download code <?php 
class AppController extends Controller {
    
    var 
$components = array('RequestHandler');

    function 
beforeRender() {
        if(
$this->RequestHandler->isAjax() || $this->RequestHandler->isXml()) { 
            
Configure::write('debug'0); 
        } 
    }
}
?>


Now we can move on to the view where we need to include the Protoype and Scriptaculous libraries and add two
s to hold our contact's name and state select box.

View Template:

Download code
<?php echo $javascript->link('prototype');echo $javascript->link('scriptaculous'); ?>
<label for="name"><?php __('Name');?>:</label><div id="name"><?php echo $contact['Contact']['name'];?></div>
<label for="state_id"><?php __('State');?>:</label><div id="state_id"><?php echo $contact['State']['name'];?></div>

Now we need to add the actuall inplaceeditor calls.

View Template:

Download code

<?php echo $ajax->editor('name'// This is the id of the contact name <DIV>.
'/path/to/contacts/ajax_update/'.$contact['Contact']['id'].'/name'// Path to the update method.
array("okButton" => "false"// Disable the submit button and use submitOnBlur
"cancelLink" => "false"// Disable the cancelLink (Looks neater)
"submitOnBlur" => "true")); // Enable Submit on Blur

<?php echo $ajax->editor('state_id'// The id of the State <DIV>
'/admin/contacts/ajax_update/'.$contact['Contact']['id'].'/state_id'//Path to the update method
array("okButton" => "true"// This time we need the OK button.
"cancelLink" => "false"// Disable the cancelLink (Looks neater)
"submitOnBlur" => "false"// Does not work with collection editor, so we disable it here
"collection" => $stateListAjax)); //Here we pass the array of states to display in the select box.
?> 

In the first call, we tell the editor that we are updating the Contact name, then pass the url to the update method passing the contact's id and id of the field to be updated. We disable the OK button and cancel links for a smoother look and then enable the submitOnBlur feature that will submit the form when you hit ENTER or click away from the field.

The second call is for the select box which look almost the same with the exception that we have enabled the OK button because we can't use submitOnBlur with a select box, and we pass the array of states to be used when rendering the select box full of states.

Now just browse to the url of the view, passing a contact id to see the form. When you click the text box and change the value the value is updated and pushed back to the view. When you click the State field, the select box appears and you select a state. The table is update with the state id and state name is pushed back to the view.

Occasionally, you wil have a field that is empty. This can wreak havok on your layout. There are two solutions to this. You can either use CSS to set the height or min-height of that
, or you can test for an empty value and fill the
with a comment. You would do the Name field like this.

View Template:

Download code
<label for="name"><?php __('Name');?>:</label><div id="name"><?php if (!empty($contact['Contact']['name'])){echo $contact['Contact']['name'];}else{echo 'Click to add...'}?></div>

 

Comments 738

CakePHP Team Comments Author Comments
 

Comment

1 successfully implemented

I try this and it works porperly.

I only had to add a ajax_update.ctp view with this code

View Template:


<?
    echo trim($value);
?>

to show properly the updated value.
I dont know if it is really needed, but works for me.

And modify this lines

Controller Class:

<?php 
 
if (!empty($this->$model_name->display_field)){
                
$display_field $this->$model_name->display_field
?>
with

Controller Class:

<?php 
 
if (!empty($this->$model_name->displayField)){
                
$display_field $this->$model_name->displayField;
?>
Posted Nov 20, 2008 by edwinallenz
 

Question

2 problem

works good, but when i try to edit my text a second time (without reloading the site), i have many whitespaces before the text. the first time its an input field and the second time its a textarea.
what could be the problem?
Posted May 14, 2009 by Timo
 

Comment

3 A companion demo-page would be awesome!

Being new to Cake, tutorials and samples with the endless, cryptic PHP code gets a little daunting. I've been reading for days and have yet to see a real web-page!

Including a demo-page where one could go back and forth - PHP to real-world would really tie it all together.

The blow-by-blow comments in your code is extremely helpful - That technique should be used in all Cake tutorial docs.

=Alan R.
Posted May 22, 2009 by alan reinhart
 

Comment

4 make sure you remove white spaces (e.g. tabs) from your view

@Timo, I had the same problem. Just remove any whitespace characters surrounding the output var in your view. It worked for me. No need to use trim(), either.
Posted Jun 2, 2009 by Kevin DeCapite