Yii 1.1: Update/delete model with CJuiDialog (works in CGridView)

25 followers

Introduction

This is based on this article.

This tutorial will show you how to create Ajax dialog which allows to create new model, update or delete existing model. It works with simple links, CGridView button column links, adds a minimal amount of code and degrades gracefully with JavaScript turned off.

Extension now available: EUpdateDialog
Important: For newest code updates you should check the extension. As it takes some time to update two similar but a little different texts, I probably won't be updating this tutorial (unless it's an important update). So my suggestion would be to read this tutorial to get an idea how it works, and then check extensions article and source code. Sorry for the inconvenience, but I think this time would be better spend updating extension itself.

Controller code

Create action

Modify your code to look similarly to this:

public function actionCreate()
{
  $model = new ModelName;
  if( isset( $_POST['ModelName'] ) )
  {
    $model->attributes = $_POST['ModelName'];
    if( $model->save() )
    {
      if( Yii::app()->request->isAjaxRequest )
      {
        // Stop jQuery from re-initialization
        Yii::app()->clientScript->scriptMap['jquery.js'] = false;
 
        echo CJSON::encode( array(
          'status' => 'success',
          'content' => 'ModelName successfully created',
        ));
        exit;
      }
      else
        $this->redirect( array( 'view', 'id' => $model->id ) );
    }
  }
 
  if( Yii::app()->request->isAjaxRequest )
  {
    // Stop jQuery from re-initialization
    Yii::app()->clientScript->scriptMap['jquery.js'] = false;
 
    echo CJSON::encode( array(
      'status' => 'failure',
      'content' => $this->renderPartial( '_form', array(
        'model' => $model ), true, true ),
    ));
    exit;
  }
  else
    $this->render( 'create', array( 'model' => $model ) );
}

This simply saves the model and depending on request type displays proper content. The important points are to disable jQuery using scriptMap to stop it from re-initialization and setting renderPartial $processOutput parameter to true if you use JavaScript in form view.

Update action

Update action is the same as create action, just rather than creating new model you need to load it, and you need to change success message.

Modify your code to look similarly to this:

public function actionUpdate()
{
  $model = $this->loadModel();
  if( isset( $_POST['ModelName'] ) )
  {
    $model->attributes = $_POST['ModelName'];
    if( $model->save() )
    {
      if( Yii::app()->request->isAjaxRequest )
      {
        // Stop jQuery from re-initialization
        Yii::app()->clientScript->scriptMap['jquery.js'] = false;
 
        echo CJSON::encode( array(
          'status' => 'success',
          'content' => 'ModelName successfully updated',
        ));
        exit;
      }
      else
        $this->redirect( array( 'view', 'id' => $model->id ) );
    }
  }
 
  if( Yii::app()->request->isAjaxRequest )
  {
    // Stop jQuery from re-initialization
    Yii::app()->clientScript->scriptMap['jquery.js'] = false;
 
    echo CJSON::encode( array(
      'status' => 'failure',
      'content' => $this->renderPartial( '_form', array(
        'model' => $model ), true, true ),
    ));
    exit;
  }
  else
    $this->render( 'update', array( 'model' => $model ) );
}

Delete action

Rather than default Yii action of displaying JavaScript confirmation box, it displays normal HTML form, this way even if JavaScript is turned off you will be able to delete model.

Modify your code to look similarly to this:

public function actionDelete()
{
  $model = $this->loadModel();
  if( Yii::app()->request->isAjaxRequest )
  {
    // Stop jQuery from re-initialization
    Yii::app()->clientScript->scriptMap['jquery.js'] = false;
 
    if( isset( $_POST['action'] ) && $_POST['action'] == 'confirmDelete' )
    {
      $model->delete();
      echo CJSON::encode( array(
        'status' => 'success',
        'content' => 'Deleted succussfully',
      ));
      exit;
    }
    else if( isset( $_POST['action'] ) )
    {
      echo CJSON::encode( array(
        'status' => 'canceled',
        'content' => 'Deletion canceled',
      ));
      exit;
    }
    else
    {
      echo CJSON::encode( array(
        'status' => 'failure',
        'content' => $this->renderPartial( 'delete', array(
          'model' => $model ), true, true ),
      ));
      exit;
    }
  }
  else
  {
    if( isset( $_POST['confirmDelete'] ) )
    {
      $model->delete();
      $this->redirect( array( 'admin' ) );
    }
    else if( isset( $_POST['denyDelete'] ) )
      $this->redirect( array( 'view', 'id' => $model->id ) );
    else
      $this->render( 'delete', array( 'model' => $model ) );
  }
}

This action checks if it's Ajax request, if it is, then it checks if user confirmed/denied deletion or it should render a view with confirmation form. Delete confirmation view at least needs to contain two submit buttons with confDelete and denyDelete names. If browser has JavaScript turned off, confirmation will be rendered normally allowing to delete a model without JavaScript.

// You need to have a form in your delete view file!
<?php $form = $this->beginWidget( 'CActiveForm', array(
  'id' => 'location-delete-form',
  'enableAjaxValidation' => false,
  'focus' => '#confirmDelete',
)); ?>
 
<div class="buttons">
  <?php 
  echo CHtml::submitButton( 'Yes', array( 'name' => 'confirmDelete', 
    'id' => 'confirmDelete' ) );
  echo CHtml::submitButton( 'No', array( 'name' => 'denyDelete' ) ); 
  ?>
 
  <?php
  /* !!! Or you can use jQuery UI buttons, makes no difference !!!
  $this->widget( 'zii.widgets.jui.CJuiButton', array(
    'name' => 'confirmDelete',
    'caption' => 'Yes',
  ));
  $this->widget( 'zii.widgets.jui.CJuiButton', array(
    'name' => 'denyDelete',
    'caption' => 'No',
  ));*/
  ?>
</div>
 
<?php $this->endWidget(); ?>

View Code

If you want to use dialog with CGridView widget you need to make it look similarly to this:

<?php $this->widget( 'zii.widgets.grid.CGridView', array(
  // # your widget settings here #
  'columns' => array(
    // # your columns #
    array(
      'class' => 'CButtonColumn',
      'header' => 'Action',
      'deleteButtonUrl' => 'Yii::app()->createUrl( 
        "/admin/location/delete", 
        array( "id" => $data->primaryKey ) )',
      'buttons' => array(
        'delete' => array(
          'click' => "function( e ){
            e.preventDefault();
            $( '#update-dialog' ).children( ':eq(0)' ).empty(); // Stop auto POST
            updateDialog( $( this ).attr( 'href' ) );
            $( '#update-dialog' )
              .dialog( { title: 'Delete confirmation' } )
              .dialog( 'open' ); }",
        ),
        'update' => array(
          'click' => "function( e ){
            e.preventDefault();
            $( '#update-dialog' ).children( ':eq(0)' ).empty(); // Stop auto POST
            updateDialog( $( this ).attr( 'href' ) );
            $( '#update-dialog' )
              .dialog( { title: 'Update' } )
              .dialog( 'open' ); }",
        ),
      ),
    ),
  ),
)); ?>

This changes delete button to redirect to delete confirmation so it will work without JavaScript and it also replace delete and update buttons click property with custom function. It will stop link from executing its action, clear dialog content, sent link to the content for dialog and opens dialog with proper title.

<?php
$this->beginWidget( 'zii.widgets.jui.CJuiDialog', array(
  'id' => 'update-dialog',
  'options' => array(
    'title' => 'Dialog',
    'autoOpen' => false,
    'modal' => true,
    'width' => 550,
    'resizable' => false,
  ),
)); ?>
<div class="update-dialog-content"></div>
<?php $this->endWidget(); ?>

This code initialize CJuiDialog so it will be available then we need it.

<?php
$updateJS = CHtml::ajax( array(
  'url' => "js:url",
  'data' => "js:form.serialize() + action",
  'type' => 'post',
  'dataType' => 'json',
  'success' => "function( data )
  {
    if( data.status == 'failure' )
    {
      $( '#update-dialog div.update-dialog-content' ).html( data.content );
      $( '#update-dialog div.update-dialog-content form input[type=submit]' )
        .die() // Stop from re-binding event handlers
        .live( 'click', function( e ){ // Send clicked button value
          e.preventDefault();
          updateDialog( false, $( this ).attr( 'name' ) );
      });
    }
    else
    {
      $( '#update-dialog div.update-dialog-content' ).html( data.content );
      if( data.status == 'success' ) // Update all grid views on success
      {
        $( 'div.grid-view' ).each( function(){ // Change the selector if you use different class or element
          $.fn.yiiGridView.update( $( this ).attr( 'id' ) );
        });
      }
      setTimeout( \"$( '#update-dialog' ).dialog( 'close' ).children( ':eq(0)' ).empty();\", 1000 );
    }
  }"
)); ?>

This code saves ajax call inside php variable. On failure, which means what we have a form to display it adds retrieved code to dialog, removes all live event handlers for form submit buttons (otherwise next time you open the dialog it will make two requests, then three and so on), and then reataches live event handlers to submit buttons. Then user clicks submit button, it stops the form from submiting and sends his name attribute to update function.

If returned status is not a 'failure', it will add retrieved message to dialog. If status is 'success' which means the model was deleted/updated/created it will update all grid view widgets on page. When it adds timeout function which will close and clean dialog.

<?php
Yii::app()->clientScript->registerScript( 'updateDialog', "
function updateDialog( url, act )
{
  var action = '';
  var form = $( '#update-dialog div.update-dialog-content form' );
  if( url == false )
  {
    action = '&action=' + act;
    url = form.attr( 'action' );
  }
  {$updateJS}
}" ); ?>

This function does all the updates. First it sets needed variables, then checks if url parameter was provided. If url parameter is set then it displays proper form. Otherwise if url is false this means form was submitted so it will set action variable, get url from form and will make proper ajax call.

<?php
Yii::app()->clientScript->registerScript( 'updateDialogCreate', "
jQuery( function($){
    $( 'a.update-dialog-create' ).bind( 'click', function( e ){
      e.preventDefault();
      $( '#update-dialog' ).children( ':eq(0)' ).empty();
      updateDialog( $( this ).attr( 'href' ) );
      $( '#update-dialog' )
        .dialog( { title: 'Create' } )
        .dialog( 'open' );
    });
});
" );
?>

To add dialog functionality to simple links you just need to add this script which attaches handlers to click event for all links with update-dialog-create class.

Gii Code Generator

This won't work with default code generated through Gii. You will have to add $id parameter to methods or modify loadModule method to look similarly to this.

public function loadModel()
{
  if( $this->_model === null )
  {
    if( isset( $_GET['id'] ) )
      $this->_model = ModelName::model()->findByPk( (int)$_GET['id'] );
    if( $this->_model === null )
      throw new CHttpException( 404, 'The requested page does not exist.' );
  }
  return $this->_model;
}

Summary

Hope this tutorial will be helpful to you. If you spot bugs, have some tips or questions let me know.

Total 6 comments

#4711 report it
ifdattic at 2011/08/06 05:45am
#update-dialog-content

My way is more restrictive, as it select div element which is inside dialog, but this is the typo as it should be $( '#update-dialog div.update-dialog-content' ).html( data.content );

I will need to update this tutorial a little then I have some free time.

#4707 report it
Aladdin at 2011/08/06 12:13am
#update-dialog-content

this wrong, I guess. $( '#update-dialog-content div.update-dialog-content' ).html( data.content );

this worked for me: $( 'div.update-dialog-content' ).html( data.content );

#4706 report it
Aladdin at 2011/08/06 12:11am
$_POST['action']

on delete, $_POST['action'] always appears to be null.

#4522 report it
Fan_Ye at 2011/07/16 10:49pm
@andrew2mar

Thanks,now it works,

#4521 report it
ifdattic at 2011/07/16 08:22am
@Fan_Ye

It's probably because you are using default loadModule code generated by Gii code generator. Because I use a little modified version of it, it breaks the code. I found that yesterday and didn't had time to update this article. I do it now, but for you might want to check the extension because it has newest code. If you still have this problem let me know.

#4517 report it
Fan_Ye at 2011/07/16 04:43am
It does not work!

Hello,this ig a good idea. I follow your step but it does not work.there is nothing in the Dialog???

Leave a comment

Please to leave your comment.

Write new article