Tuesday 1 November 2011

CakePHP: An edit form with a cancel button (1.3 and 2.x)

When developing database CRUD applications with CakePHP, sooner or later you end up writing view code looking more or less like this
<div class="my model form">
<?php echo $this->Form->create('MyModel');?>
 <fieldset>
  <legend></legend>
 <?php
  echo $this->Form->input('id');
  echo $this->Form->input('name');
 ?>
 </fieldset>
<?php echo $this->Form->end(__('Submit', true));?>
</div>
This creates a nice form with a submit button at the end that every self respecting user can press to create a new record or modify the data of an existing one. It is also logical that you place a link to the index page somewhere near the form, so your users know where to go in case they change their mind about altering the database data.
With my users this time is was different. The form had to contain a cancel button. So how does one do it? If you are using the cake bake script and wish to have two nice round green buttons right at the bottom of your form then replace the last two lines of the previous code fragment with the following:
     ...
     <div class="submit">
         <?php echo $this->Form->submit(__('Submit', true), array('name' => 'ok', 'div' => false)); ?>
         <?php echo $this->Form->submit(__('Cancel', true), array('name' => 'cancel','div' => false)); ?>
     </div>
     </fieldset>                   
 <?php echo $this->Form->end();?>
The next thing to know from inside the controller code, is which button was pressed before the data were posted and that is available inside the 'form' array of the Controller::params property.
So a simple modification like the one on the following code :
        public function edit($id = null)
        {
            if (!$id && empty($this->data)) {
               $this->Session->setFlash(__('Invalid property', true));
               $this->redirect(array('action' => 'index'));
            }
            if (!empty($this->data)) {
                // abort if cancel button was pressed  
                if (isset( $this->params['form']['cancel'])) {
                    $this->Session->setFlash(__('Changes were not saved. User cancelled.', true));
                    $this->redirect( array( 'action' => 'index' ));
                }

                // proceed to save changes as usual
        }
... and everyone is happy.

Edit: Alternatively, if working with CakePHP version 2.4 then the sane info is inside the data array of the controller's request property.
So the previous code gets rewritten like this
        public function edit($id = null)
        {
            if (!$this->MyModel->exists($id)) {
                throw new NotFoundException(__('Invalid record'));
            }
            if ($this->request->is(array( 'post','put'))) {
                if (isset($this->request->data['cancel'])) {
                    $this->Session->setFlash(__('Changes were not saved. User cancelled.'));
                    return $this->redirect( array( 'action' => 'index' ));
                }

                // proceed to save changes as usual
        }
There is one last thing though... the cancel button in your form should also indicate that no form validation should be performed at the browser level. This can be accomplished by setting the 'formnovalidate' key of the Form::input options parameter to TRUE. So the whole cancel button creation tag should now look like this:
        <div class="submit">
            <?php echo $this->Form->submit(__('Create Account'), array('name' => 'ok', 'div' => FALSE)); ?>
            <?php echo $this->Form->submit(__('Cancel'), array('name' => 'cancel', 'formnovalidate' => TRUE, 'div' => FALSE)); ?>
        </div>
    Form->end(); ?>

4 comments :

gong said...

nice to see another greek cakephp developer...

hope to stay in touch!

Lucas Miranda said...

so... what to do if you form handle a entity with validated (notEmpty) fields?

Anonymous said...

I tried our solution and looks like you missed the "name" part? Should that be:

Form->submit(__('Cancel'), array('formnovalidate' => TRUE, 'name' => 'cancel', 'div' => FALSE)); ?>

Athanassios Bakalidis said...

@VCD Correction added. Thanks