Jump to content
Larry Ullman's Book Forums

Using A Single Form With Multiple Models - Cactiveform And Renderpartials ?


talofo
 Share

Recommended Posts

Hello all,

 

First of all, thanks a trillion for the book. It covers a lot of nice topics.

 

This question is related with a difficulty that I (and perhaps others) may have, regarding a multi model form implementation.

 

Let's say we have a form that should allow a TEAM SUBSCRIPTION and it's members. Obviously, Team is one table, members are another. They do have a relation.

Tricky part: This form as a "add new member" button that should visually, display a new member fieldset (render partial?) to be added with more members fields.

 

(And with this, comes a lot of assumptions like):
When user hits submit, but a TEAM name already exists (ajax validation), the user informations should be kept. (if they already added two users to that team, those two users should appear again on the form).

 

Without asking for the code, I'm really not getting what should we use on Yii in order to help us out on this scenario. 

I've read about tabular input, but nothing that explains an overall view using that with Partial Views and Ajax Calls.

Can I please have a clue about a possible implementation of this ?

 

This seems to be a very common scenario. You want to subscribe a team of variable members, so you wish to add those members live. Or, you wish to sell a product with some extra optional items, and you wish to allow the user to select has many extra things as they can get.

 

Despite being common, I don't see an overall view regarding such scenarios...

 

If there's a need for a better clarification of the propose, please drop me a reply.

 

Thanks in advance.

Link to comment
Share on other sites

I thought I've found the solution. I was wrong.

I can more or less deal with it, if I use a select field so that the user chooses first, how many members they want to add.

 

If I try to make the "add more" button instead, I need to tell Yii that those new added fields need also to be validated.

 

Scenarios...

 

Need to try and try again - argthh.

 

 

Cheers.

Link to comment
Share on other sites

Sir Larry, ok... this will be big. Hope you (and others) have the patience and nervers to all of this "newbilities".

 

The good parts (if any) I learned from your Book. (like the relations on models).

The bad parts... totally uppon my creation.

 

Let's imagine a form with this structure, where [ ] are buttons on that form:

TEAM
 team name:
 team address:
 MEMBERS
   member name:
   member phone:
   [ADD NEW MEMBER]

[SUBSCRIBE TEAM]

Here are the goals:

1) Validations should happen both, client and server side; (on team and member fields)

2) A flashMessage should appear at the very end.

3) Upon refresh or user miss use, the members fieldset should retain the members information already inserted (if any).

 

This is what I came up with:

 

Model Team:

public function relations()
    {
        return array(
            'teamMembers' => array(self::HAS_MANY, 'TeamMember', 'team_id'),
            'country' => array(self::BELONGS_TO, 'Country', 'id'),
        );
    }

Model TeamMember:

public function relations()
    {
        return array(
            'team' => array(self::BELONGS_TO, 'Team', 'team_id'),
        );
    }

Controller Team:

public function actionMember($index) {
            $model = new TeamMember();
            for($i = 0;$i<$index;$i++){
                $this->renderPartial('_member',array(
                    'model'=> $model, 'index'=> $i
                ));
            }
        }
        
public function getExtraModelMember()
{
    $model = new TeamMember;
                
    return $model;
}

public function actionCreate()
    {
           $model=new Team;
                $member=$this->getExtraModelMember();

        if(isset($_POST['Team']))
        {
            $model->attributes=$_POST['Team'];
                            
            if(isset($_POST['TeamMember']) ? $member = $_POST['TeamMember'] : $member = null);
                        

             if($model->save())
                        {
                            foreach($member as $team_member)
                                    {
                                        $mem = new TeamMember();
                                        $mem->attributes=$team_member;
                                        $mem->team_id = $model->id;
                                        if($mem->validate())
                                        {
                                            $mem->save();
                                        }
                                    }
                                    
                                //create team(equipa) name:
                                $equipa=somethingThatFormatsStrings());            
                                
                                //Create team user
                                $eq= new User;
                                $eq->username=$equipa;
                                    
                                    if($eq->validate())
                                          $eq->save();  
                                   
                                //send email
                                somethingThatSendsEmail();

                                Yii::app()->user->setFlash('success', "YOUR REGISTRATION WAS SUBMITED WITH SUCCESS!");
                                
                                $this->redirect(array('team/create','id'=>$model->id,'#'=>'message'));
                        }
        }

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

Now the even more messy/durty/ugly part of all this thing.

 

 

On my Team View _form.php I have this:

<?php $form=$this->beginWidget('CActiveForm', array(
    'id'=>'team-form',
        'enableAjaxValidation'=>false,
        'enableClientValidation'=>true,
        'clientOptions'=>array(
            'validateOnSubmit'=>true,
            'beforeValidate'=>'js:function(){
                var i=true;
                $(".errors").empty();
                $("#Team_0_email_em_").hide().empty();
                $("#Team_1_email_em_").hide().empty();
                $("#Team_2_email_em_").hide().empty();
                if($("#Team_name").val()==="") i=false;
                
                if($("#Team_city").val()==="") i=false;
                
                if($(".row-member0").is(":visible")){
                    if($("#TeamMember_0_name").val()==="") i=false;
                    if($("#TeamMember_0_email").val()==="") i=false;
                    if($("#TeamMember_0_phone").val()==="")i=false;
                    if($("#TeamMember_0_birthdate").val()==="") i=false;
                    if( !validateEmail($("#TeamMember_0_email").val())) {
                        i=false;
                    }    
                }

                if($(".row-member1").is(":visible")){
                    if($("#TeamMember_1_name").val()==="") i=false;
                    if($("#TeamMember_1_email").val()==="") i=false;
                    if($("#TeamMember_1_phone").val()==="") i=false;
                    if($("#TeamMember_1_birthdate").val()==="") i=false;
                    if( !validateEmail($("#TeamMember_0_email").val())) {
                        i=false;
                    }
                }
                
                if($(".row-member2").is(":visible")){
                    if($("#TeamMember_2_name").val()==="") i=false;
                    if($("#TeamMember_2_email").val()==="") i=false;
                    if($("#TeamMember_2_phone").val()==="") i=false;
                    if($("#TeamMember_2_birthdate").val()==="") i=false;
                    if( !validateEmail($("#TeamMember_2_email").val())) {
                        i=false;
                    }
                }
                if(i===false)
                $(".errors").append("<p>You must enter all required fields.</p>");
                return i;
                
            }'
        ),
        'htmlOptions' => array('enctype' => 'multipart/form-data'),
)); ?>

And then this:

<div class="row">
  <?php echo $form->label($model,'name'); ?>
  <?php echo $form->textField($model,'name',array('size'=>60,'maxlength'=>255)); ?>
  <?php echo $form->error($model,'name'); ?>
</div>
<div class="row">
  <?php echo $form->label($model,'city'); ?>
  <?php echo $form->textField($model,'city',array('size'=>60,'maxlength'=>255)); ?>
  <?php echo $form->error($model,'city'); ?>
</div>
<div class="row">
  <?php echo $form->label($model,'instituition'); ?>
  <?php echo $form->textField($model,'instituition',array('size'=>60,'maxlength'=>255)); ?>
  <?php echo $form->error($model,'instituition'); ?>
</div>
<div class="row">
  <?php echo $form->label($model,'country_id'); ?>
  <?php echo $form->dropDownList($model,'country_id',Country::items(),array('empty'=>'--Select a country--')); ?>
  <?php echo $form->error($model,'country_id'); ?>
</div>

I was unable to code a "add more button" to work here, so this is no good:

<fieldset>
            <legend>Members</legend>
            <label for="numeroMembros">Number of Members <span class="tip">(max 3)</span></label>
            <select name="membros" id="numeroMembros" onchange="addRemoveFieldsets();">
                <option value="1" selected="selected">1</option>
                <option value="2">2</option>
                <option value="3" >3</option>
            </select>
            <div id="membros">
                <?for ($index=0;$index<3;$index++):?>
                <div class="row-member<?php echo $index; ?>">
                    <h3>Member <?php if ($index+1==1){echo $index+1 . '(captain)';} else echo $index+1; ?></h3>
                    <div class="row">
                      <?php echo $form->label($member, "[$index]name",array('class'=>'member')); ?>
                      <?php echo $form->textField($member, "[$index]name",array('class'=>'member member-required')); ?>
                      <?php echo $form->error($model,"[$index]name"); ?>
                    </div>
                    <div class="row">
                      <?php echo $form->label($member, "[$index]email",array('class'=>'member')); ?>
                      <?php echo $form->textField($member, "[$index]email",array('class'=>'member mail ')); ?>
                      <?php echo $form->error($model,"[$index]email"); ?>
                    </div>
                    <div class="row">
                      <?php echo $form->label($member, "[$index]phone",array('class'=>'member')); ?>
                      <?php echo $form->textField($member, "[$index]phone",array('class'=>'member member-required')); ?>
                      <?php echo $form->error($model,"[$index]phone"); ?>
                    </div>
                    <div class="row">
                      <?php echo $form->label($member, "[$index]birthdate",array('class'=>'member')); ?>
                      <?php echo $form->textField($member, "[$index]birthdate",array('class'=>'member birthdate member-required')); ?>
                      <?php echo $form->error($model,"[$index]birthdate"); ?>
                    </div>
                </div>
                <?endfor;?>
            </div>
        </fieldset>

Display the flash messages at the very end on the form:

     <?php
    foreach(Yii::app()->user->getFlashes() as $key => $message) {
        echo '<div class="flash-' . $key . '">' . $message . "</div>\n";
    }
?>

Finally, this is how I deal with the member validation and behavior:

<script>
    $(document).ready(function() {
                            $('.row-member1').hide();
                            $('.row-member2').hide();
//                
    $( "#TeamMember_0_birthdate" ).datepicker({
            showOn: "button",
            buttonImage: "/images/calendar.gif",
            buttonImageOnly: true,
                         dateFormat: 'dd/mm/yy',
                         changeYear: true,
                         yearRange: '1988:1998'
                     });
                     $( "#TeamMember_1_birthdate" ).datepicker({
            showOn: "button",
            buttonImage: "/images/calendar.gif",
            buttonImageOnly: true,
                         dateFormat: 'dd/mm/yy',
                         changeYear: true,
                         yearRange: '1988:1998'
                     });
                     $( "#TeamMember_2_birthdate" ).datepicker({
            showOn: "button",
            buttonImage: "/images/calendar.gif",
            buttonImageOnly: true,
                         dateFormat: 'dd/mm/yy',
                         changeYear: true,
                         yearRange: '1988:1998'
                     });

    });
    
    function validateEmail($email) {
        var emailReg = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/;
        if( !emailReg.test( $email ) ) {
          return false;
        } else {
          return true;
        }
      }
    
    
    $('.mail').each(function(){
        $(this).focusout(function(){
            
            if($(this).val()===""){
                $(this).next('.errorMessage').hide().empty();
                $(this).next('.errorMessage').show().append("<p>This field cannot be blank.</p>");
            }
            else if(validateEmail($(this).val())===false){
                $(this).next('.errorMessage').hide().empty();
                $(this).next('.errorMessage').show().append("<p>You must enter a valid email.</p>");
            }
            else{
                $(this).next('.errorMessage').empty();
            }
            
        });
    });
    
    $('.member-required').each(function(){
        $(this).focusout(function(){
            if($(this).val()===""){
                
                $(this).next('.errorMessage').hide().empty();
                $(this).next('.errorMessage').show().append("<p>This field cannot be blank.</p>");
            }
            else{
                $(this).next('.errorMessage').empty();
            }
        });
    });
    
    function addRemoveFieldsets(){
        var select = $('#numeroMembros');
                            $('.row-member0').hide();
                            $('.row-member1').hide();
                            $('.row-member2').hide();
        for (i = 0; i <= select.val()-1; i++) {
                        $('.row-member'+i).show();
        }    
    }
        
   
</script>

The bad parts:

 - That beforeValidate is huge and a pain to debug maintain.

 - There's not "add more" button. Instead we have a select box. (if the user needs to feel 10 members, he sees 10 field set members at once to fill in!!

 - The member validation is... unpronounceable... I'm just wondering there why I'm using Yii at all, if I came with such a bad thing.

 

 

If I'm happy with the model, and the controller is not bad, when I arrive on the views, I get my hair pulled off all the time.

 

By taking all this into consideration what, in our opinion, would be a proper way for dealing with those bad parts ?

 

 

 

Hope you still there.

 

Talofo

Link to comment
Share on other sites

 Share

×
×
  • Create New...