Difference between #4 and #5 of Update/delete model with CJuiDialog (works in CGridView)

unchanged
Title
Update/delete model with CJuiDialog (works in CGridView)
unchanged
Category
Tutorials
unchanged
Tags
AJAX, juidialog, user interface, CGridView
changed
Content
Introduction
------------------
This is based on this
[article](http://www.yiiframework.com/wiki/145/cjuidialog-for-create-new-model/
"").

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](http://www.yiiframework.com/extension/eupdatedialog/
"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:

~~~
[php]
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:

~~~
[php]
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:

~~~
[php]
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''confDelete' )
    {
      $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']$_POST['confDelete']
) )
    {
      $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.



~~~
[php]
// 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]
<?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]
<?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]
<?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'#update-dialog-content
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]
<?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]
<?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.

~~~
[php]
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.