Update/delete model with CJuiDialog (works in CGridView)

You are viewing revision #5 of this wiki article.
This is the latest version of this article.
You may want to see the changes made in this revision.

« previous (#4)

  1. Introduction
  2. Controller code
  3. View Code
  4. Gii Code Generator
  5. Summary

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.

15 0
25 followers
Viewed: 77 723 times
Version: Unknown (update)
Category: Tutorials
Written by: ifdattic
Last updated by: ifdattic
Created on: Jul 11, 2011
Last updated: 13 years ago
Update Article

Revisions

View all history

Related Articles