Multi Editable Fields with jEditable
With the current project I am working on, the need to have inline editing for multiple records per page was a task I thought would be daunting. However, I was wrong.
Using jQuery , jEditable , and CakePHP , I have been able to complete a complex editing project with minimal headaches. Thanks to all those who contributed to these libraries to make this possible. I have also used references from other places, but main inspiration came from http://cakebaker.42dh.com/2008/02/24/edit-in-place-with-jquery-and-cakephp/
A complete demo of this can be used at multi-editable.mentalramblings.info/ and the whole project can be downloaded from multi-editable.mentalramblings.info/files/multi-editable.tar.bz2 .
I will be discussing in this post about the techniques I used to create the demo, and various implementations used.
Preparation
First, we need to setup our javascript by placing everything at the bottom of the page we will be working on. I like to set it all down at the bottom due to speed increase, as well as bubbling of the DOM and jQuery behaves better at the bottom. So we place the following at the bottom of ourpage. In the case of our demo, on default.ctp in the layouts.
Javascript Setup:
echo $cakeDebug;
echo $scripts_for_layout;
echo $javascript->link('jquery/jquery');
echo $javascript->link('jquery/jeditable');
?>
<script type="text/javascript">
$(function() {
$('.editable').editable($('base').attr('href')+('/widgets/updateField').substr(1),
{
cssclass : 'editing',
height : '25',
indicator : '<?php echo $html->image("ani_round.gif")?>',
submit : 'Save',
type : 'text',
tooltip : 'Click to edit.',
width : '5',
submitdata : function(value, settings) {
return {
model : $(this).attr("data-model"),
id : $(this).attr("data-id"),
field : $(this).attr("data-field")
}
}
}
);
});
</script>
</body>
</html>
As you can see it's a fairly straight forward editable call. The important thing to recognize here is the submitdata section. We will be setting the attributes data-model, data-id, and data-field in the view, which in our case is in widgets/index.ctp.
View Setup
Next, we need to set up the afore mentioned dataSet attributes. Why do I use these you might ask? Well it's part of the HTML5 spec, that will be released. You can read more about it here, Basically it states that a custom data-* attribute can be set on any DOM element, and be used for site specific scripting. How powerful is that? No more having to assign invalid id's to elements just to get javascript to work, and pass along our data, like was described in cakebaker. The technique he described there was to set the id and name options of jeditable to data[Post][id] and data[Post][title] and then to set the id of the DOM element to be edited to the id of the Post record i.e. 1 . Well, this isn't valid html, as DOM element id's need to be unique. Also, what happens if we have multiple records with multiple fields that we want to edit? The answer is as follows.
widgets/index.ctp
<h2><?php __('Widgets');?></h2>
<div class="actions">
<ul>
<li><?php echo $html->link(__('Generate New Widgets', true), array('controller' => 'widgets', 'action' => 'generate'), array('title' => __('Generate New Widgets.', true)))?></li>
</ul>
</div>
<p>
<?php
echo $paginator->counter(array(
'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true)
));
?></p>
<table cellpadding="0" cellspacing="0">
<tr>
<th><?php echo $paginator->sort('id');?></th>
<th><?php echo $paginator->sort('name');?></th>
<th><?php echo $paginator->sort('type');?></th>
<th><?php echo $paginator->sort('view');?></th>
<th><?php echo $paginator->sort('part_number');?></th>
</tr>
<?php
$i = 0;
foreach ($widgets as $widget):
$class = null;
if ($i++ % 2 == 0) {
$class = ' class="altrow"';
}
?>
<tr<?php echo $class;?>>
<td><?php echo $widget['Widget']['id']; ?></td>
<td data-field="name" data-model="Widget" data-id="<?=$widget['Widget']['id']?>" class="editable"><?php echo $widget['Widget']['name']; ?></td>
<td data-field="type" data-model="Widget" data-id="<?=$widget['Widget']['id']?>" class="editable"><?php echo $widget['Widget']['type']; ?></td>
<td data-field="view" data-model="Widget" data-id="<?=$widget['Widget']['id']?>" class="editable"><?php echo $widget['Widget']['view']; ?></td>
<td data-field="part_number" data-model="Widget" data-id="<?=$widget['Widget']['id']?>" class="editable"><?php echo $widget['Widget']['part_number']; ?></td>
</tr>
<?php endforeach; ?>
</table>
</div>
<div class="paging">
<?php echo $paginator->prev('<< '.__('previous', true), array(), null, array('class'=>'disabled'));?>
| <?php echo $paginator->numbers();?>
<?php echo $paginator->next(__('next', true).' >>', array(), null, array('class' => 'disabled'));?>
</div>
The important section is in the foreach loop where each table row is built. In the next section, we'll tackle the controller, and the form data being recieved. We also need to setup the update_field view for rendering.
widgets/update_field.ctp
Controller Setup
Now we come to the meat and potatoes of the whole deal. In my project that I'm working on, I have several model data sets displayed on one page, as they are all related to one base model, seven in total, and need to be able to edit most of the fields from within one page. This is why we set the data-model="Widget" in the previous code, so that we can have multiple model records on one page, and allow them to still be unique entities. So we make a ajax call via the $.editable call to widgets/updateField and submit the data via POST. That data then can be accessed via $this->params['form'] and then parsed out to the specific models saveField function.
* Update data fields for Widget Entry
*
* Generalized update for various fields found within the widgets index
* This is information sent to the controller via AJAX calls from jeditable
*
*/
function updateField()
{
if($this->params['form'])
{
Configure::write('debug', 0);
App::import('Core', 'sanitize');
$aFormData = Sanitize::clean($this->params['form']);
$sModel = $aFormData['model'];
$iId = $aFormData['id'];
$sValue = $aFormData['value'];
$sField = $aFormData['field'];
$this->loadModel($sModel);
$this->$sModel->id = $iId;
if($this->$sModel->saveField($sField, $sValue))
{
$this->set('msg', $sValue);
}
else
{
$this->set('msg', 'An error has occurred.');
}
}
}
Conclussion
That's it. Nothing to complex in terms of fanciness, but it allows us to remain extremely flexible in our application of jEditable with CakePhp. Let me know your thoughts, ideas, complaints, praises, etc...


Zach Said:
Thanks for the info! It helped me out a lot. I had a difficult time having the text in the view update after the updateField() method executed successfully. I realized instead of setting the $msg variable and having the view output it that I simply needed to echo $sValue or echo "My Error Message" and then it updated just fine. Not sure why that is the case as the text is printed in the view either way but only the later method worked for me.