Best solution for: submitting a form, on success opening a modal box with another form in it, using values received from saving the first form

Hey guys,

I’m working on this for a while now and I just can’t find a good solution and very often having problems with opening the box, or receiving the variables from the first save process.

Here’s what I want to do:

  • Form 1 with some fields in it and a submit button (or ajaxsubmit… whatever here the choice would be free :-P).

  • on submit, the data of the form gets posted and saved in the database.

  • after a successful save process, a modal panel opens up with a new form in it.

  • this form needs one or more ids from the save process of the Form 1 --> the ids of the before saved data sets.

  • the submit button of the Form 2 (in the modal box) will do an update on some of the saved data of Form 1.

  • after submitting Form 2 and receiving a sucess message, the modal panel closes again and the view of Form 1 "reloads" the page (or even better just adds the newly postet content).

I tried couple of things so far with colorbox, CJuiDialog etc. but never came to a satisfying solution.

Maybe one of you Yii Gurus has had a similar problem, or just a good idea to do that in a good way?

Thanks in advance,

Mayjestic

This is pretty simple actually, here is my suggestion:

In your view file, where the initial form resides, make the form call like this:




$form=$this->beginWidget('CActiveForm', array(

	'id'=>'user-form',

	'enableAjaxValidation'=>true,

    'focus'=>array($model,'title'),

    'htmlOptions'=>array('enctype'=>'multipart/form-data'),

    'clientOptions'=>array(

       'validateOnSubmit'=>true,

       'validateOnChange'=>false,//this needs to stay on false always.

       'beforeValidate'=>"js:function(form){

            return true;

       }",

       'afterValidate'=>"js:function(form, data, hasError){

            if(hasError){

                //do smth if there is an error.   

            }else{

                // submit the data to your controller.

                $.ajax({

                	url: $(form).attr('action'),

                	type:'POST',

                	data:$(form).serialize(),

                	dataType:'json',

                	success:function(obj){

                        if( obj.result === 'success' ){

                                  $('#OtherModel_some_field1').val(obj.field_value1);

                                  $('#OtherModel_some_field2').val(obj.field_value2);

                                  $('.some_selector').dialog('open');  

                		}

                	}

                });

            }

            return false;

       }"

    ),

));

?>

//your form fields here

<?php $this->endWidget(); ?>



The above code is pretty self explanatory, so let’s move on.

In this page (where is the form) use the CJui Dialog widget to create a dialog and in the dialog a form.

The dialog is hidden by default, so this form won’t be visible until the ajax submission is completed and the dialog opening is triggered.

Now, the controller code that will handle the first form(not the one from dialog) :




public function actionCreate()

{

   $model=new User('save');

   $this->performAjaxValidation($model);

   $this->saveFormData($model);

   $this->render('create',array('model'=>$model));

}

protected function performAjaxValidation($model)

{

	if(isset($_POST['ajax']) && $_POST['ajax']==='user-form')

	{

		echo CActiveForm::validate($model); 

		Yii::app()->end();

	}

}


protected function saveFormData($model)

{

    if(isset($_POST['User']))

    {

        $model->attributes=$_POST['User'];//clean this array. 

        if($model->save())

        {

            //this array will be used in the success function of the ajax call.

            $return=array(

               'result'=>'success',

               'field_value1'=>$model->some_value1,

               'field_value2'=>$model->some_value2,

            );

            echo CJSON::encode($return);

            Yii::app()->end();

        }

    }

}



As you see , with the above code, we validate the first form and we return the data we need to use in the ajax success function .

With this step, we are done with the first form, so remember, when this form is submitted successfully and saved, it will return some data that we will use in the next form, but also, it will open the dialog containing the second form.

For the form within the dialog, the steps are easier.

Create the form just like we created the initial one, same thing for the controller, the only difference is that, in the success function of the ajax call, you will do other action, say, refresh the page or anything else.

The above data is some dummy data from one of my projects, but you get the point, right ?

Anyway, if you have questions, let me know :)

I would do something like this

create view




<div id='somediv'>

//form here

<?php echo CHtml::ajaxSubmitButton('test',$this->createUrl('create'),array('update'=>'#somediv'));?>

</div>



create action




if($model->save()){

   if(Yii::app()->getRequest()->isAjaxRequest){

  	$this->renderPartial('update',array('model'=>$model,'id'=>$model->id),false,true);

   }//else normal action

}



update view




$this->beginWidget('zii.widget.jui.CJuiDialog',array('id'=>'myDialog'));

  //form here

  echo CHtml::ajaxSubmitButton('test dialog',$this->createUrl('update',array('id'=>$id)),array('update'=>'#myDialog'));

$this->endWidget();



update action




if($model->save()){

   if(Yii::app()->getRequest()->isAjaxRequest){

    	echo 'success!!'.

        	CHtml::script('

            	$("#contentDiv").html("loading").load("'.$this->createUrl('view',array('id'=>$model->id)).'",{},function(){

                      	$("#myDialog").dialog("close");

            	});');//load the content and after loaded close the dialog 

   }//else normal action

}

not tested, I just wrote the code

this code obviously is not complete, is just to give you an idea

guys …

I just can say: YOU - ARE - AWESOME!

I finally could make it work, there are just a couple of little questions I have:

  • how can I remove the titlebar completely of CJuiDialog?

  • how can I make it possible to close the CJuiDialog when clicking beside it?

  • how can I modify the grey background around the CJuiDialog (make it a bit darker)

  • is it possible to remove the text input of Form 1 on success (at the same time like the CJuiDialog opens?

  • there is still a little bug, after the successful create of Form 1 --> the content of the CJuiDialog is visible for about half a second in the "view" page … then it disappears and the CJuiDialog opens with the content.

  • when submitting the Form2 (in the CJuiDialog) the new page loads in the CJuiDialog without closing it … any idea for this?

  • Is there a documentation for the CJuiDialog (couldn’t find anything like this) --> just to know, whats possible via parameters etc.

Thanks in advance and so far!

Here you’ll find all about the params jquery ui dialog accepts: http://jqueryui.com/demos/dialog/

Using the dialog options you will solve these:

  • how can I remove the titlebar completely of CJuiDialog?

  • how can I make it possible to close the CJuiDialog when clicking beside it?

  • how can I modify the grey background around the CJuiDialog (make it a bit darker)

Now, for: - is it possible to remove the text input of Form 1 on success (at the same time like the CJuiDialog opens?

Yes you can, on the success function of the initial form, just do smth like:




[...]

success:function(obj){

                        if( obj.result === 'success' ){

                                  [...]

                                  $('#OriginalModel_field1').val('');

                                  $('#OriginalModel_field2').val('');

                                  //or, all at once:

                                  $('form#yourFormId input').val('');

                                }

                        }

[...]



For: - there is still a little bug, after the successful create of Form 1 --> the content of the CJuiDialog is visible for about half a second in the "view" page … then it disappears and the CJuiDialog opens with the content.

The solution is simple, set style="display:none" on the container that holds your dialog.

- when submitting the Form2 (in the CJuiDialog) the new page loads in the CJuiDialog without closing it … any idea for this?

Not sure what you mean, but you should do an ajax submission on the form within the dialog.

If you want to close a dialog, just use $(’.selector’).dialog(‘close’);

- Is there a documentation for the CJuiDialog (couldn’t find anything like this) --> just to know, whats possible via parameters etc.

Yup, as i already pointed out: http://jqueryui.com/demos/dialog/

Thanks so much for your answer twisted1919. I’ve now solved most of the issues, the only 2 things I can’t figure out, are these:

1.)

My solution is at the moment thisone (last line @ create), but there must be a mistake somewhere:


$this->beginWidget('zii.widgets.jui.CJuiDialog', array(

    'id' => 'myDialog',

    // additional javascript options for the dialog plugin

    'options' => array(

        'title' => '<span style="font-size:40px; color:#fff; font-family:Arial;"><b>Start</b>regie Gast</span>',

        'autoOpen' => true,

        'modal' => true,

        'width' => '400px',

        'closeOnEscape' => true,

        'create' => 'js: function(event, ui){ $(".ui-widget-overlay").click(function() {$(".ui-dialog").dialog("close")});}',

    ),

));

2.)

After the second form is submitted (actionUpdate) I want to close the modal dialog (which works fine) and also reload the page (actionView). The best solution would be of course, just to reload the new parts via ajax (load the new comment in and update some count() values like the comment count etc.)

My actionUpdate function looks as follows at the moment:


public function actionUpdate($id)

    {

        $model = $this->loadModel($id);

        $entry = $model->entry;


        // Uncomment the following line if AJAX validation is needed

        $this->performAjaxValidation($model);


        if (isset($_POST['Comment'])) {

            $model->attributes = $_POST['Comment'];


            if ($model->autor != '' && $model->email != '') {

                // set status to approved

                $model->status = Comment::STATUS_APPROVED;


                if ($model->save()) {

                    if (Yii::app()->getRequest()->isAjaxRequest) {

                        echo CHtml::script('$("#myDialog").dialog("close")');


                       HERE I WANT TO RELOAD THE DIV e.g. for the comments (to show the new comment)


                    }//else normal action

                }

            }

        }

}

Thanks in advance guys!

For #1, try:




$this->beginWidget('zii.widgets.jui.CJuiDialog', array(

    'id' => 'myDialog',

    // additional javascript options for the dialog plugin

    'options' => array(

        'title' => '<span style="font-size:40px; color:#fff; font-family:Arial;"><b>Start</b>regie Gast</span>',

        'autoOpen' => true,

        'modal' => true,

        'width' => '400px',

        'closeOnEscape' => true,

        'create' => 'js: function(event, ui){ $(".ui-widget-overlay").click(function() {$("#myDialog").dialog("close")});}',

    ),

));



The idea is to obtain this kind of raw jquery event handler:




$('#DIALOG_SELECTOR').click(function(){

   $('.DIALOG_ID').dialog('close');

});



For the update method, i would recommend just reloading the page after you did all that ajax stuff, mainly because it will be easier for you at this stage.

Another thing is that you echo something in your controller method. Please remember, the controller SHOULD NEVER EVER spit HTML content, NEVER. Showing html content is the job a view should do.

And the last thing, please be sure you CLEAN the $_POST array() before you assign it to $model->attributes.

Take a look at the Input Sanitizer extension from my signature, that is the way to go, never assign values directly from $_POST array.

L.E: Maybe for #1, you should just put the javascript code separate, and not in the "create" param.

At the bottom of the view page, do something like i shown:




<script>

$(function(){

   $(".ui-widget-overlay").click(function() {

       $("#myDialog").dialog("close");

   });

});

</script>



Thanks for telling me … didn’t think about the “echo” thing :wink:

The only problem I have now, is that if I call a


$this->render();

after a successful update (where the modal panel should close and the page reload), it renders the page in the modal panel. Something like


$this->refresh();

doesn’t do anything.

So finally: I still don’t know, how to close the modal panel and reload the page after a successful update action. :frowning:

Thanks for this, I’ll check this right away.

Edit: I now installed the input component, but as soon as I register the component in the components array in main.php as follows:


'input' => array(

                'class' => 'CmsInput',

                'cleanPost' => false,

                'cleanGet' => false,

            ),

I receive the following exception?:

Property "CClientScript.input is not defined.


C:\xampp\htdocs\Yii\framework\YiiBase.php(215)


203             {

204                 unset($args[0]);

205                 $class=new ReflectionClass($type);

206                 // Note: ReflectionClass::newInstanceArgs() is available for PHP 5.1.3+

207                 // $object=$class->newInstanceArgs($args);

208                 $object=call_user_func_array(array($class,'newInstance'),$args);

209             }

210         }

211         else

212             $object=new $type;

213 

214         foreach($config as $key=>$value)

215             $object->$key=$value;

216 

217         return $object;

218     }

Any idea what my mistake is?

I now could finally make the closing CJuiDialog and reload the page work - but still very crappy. With Gustavo’s way.

My code now looks as follows:


echo CHtml::script('$("body").load("' . $this->createUrl('entry/view', array('id' => $entry->entry_id)) . '",{},function(){

                            $("#myDialog").dialog("close");

                        });');

The problem is, that the reload of the page loads stuff from the header in the <body> tag … I also tried to "load" the <html> tag … but this completely destoys the page ;-).

The second crappy thing is also the "echo" in the controller … so this might not be a solution to go with.? :frowning:

I’m so confused with this … even when I call a:


echo CHtml::script('$("#myDialog").dialog("close");');

and afterwards a “$this->reload” or something like that … it doesn’t even THINK to do what after the above js echo comes …

Is this really so hard to solve or am I just stupid as hell? :stuck_out_tongue:

Hey Mayjestic,

Like I said first, what is posted is just to give you a notion on how to do it, I wrote the code here while replying to your question, so it was not meant to be used "as is"

anyway, you could reload the page using JS, and instead of "echo" in the controller, register a script using CClientscript registerScript method

if you want to reload the whole page, register a script like




window.location.reload();



else want to reload the whole page, only part of it, the loaded content must not use layout, use ,instead of render, renderPartial

Gustavo

I have this problem too but I use yii2
Any idea how to to do this in yii2?
thanks in advance