Best way to implement ActiveModel mixed fields

I have two ActiveRecord models:

  • User

  • UserProfile

Also a FormModel:

  • LoginForm

I have one action that persists the User and UserProfile models.

My form has a Birth Date separated just like Facebook: Three drop down lists, one for the day, one for the month, one for the year.

But, I persist the birth date as one single UserProfile.Birthdate attribute.

I have added this to the UserProfileModel:




    public $BirthDay;

    public $BirthMonth;

    public $BirthYear;


    public function beforeValidate()

    {

        $this->DateOfBirth = $this->BirthYear.'-'.$this->BirthMonth.'-'.$this->BirthDay;

        //$this->DateOfBirth = $this->BirthDay.'/'.$this->BirthMonth.'/'.$this->BirthYear;

            return parent::beforeValidate();

    }



in rules:




            array( 'BirthDay, BirthMonth, BirthYear', 'safe', 'on'=>'register' ),

            array( 'DateOfBirth', 'type', 'type' => 'date', 'dateFormat' => 'yyyy-m-d', 'message' => '{attribute} no es una fecha válida', ),

            array( 'DateOfBirth', 'validateDate' ),






    /*

        Validator function to be used from rules() 

    */

    public function validateDate( $attribute, $params )

    {

        if(

            ( $this->BirthDay > 28 &&

                $this->BirthMonth == 2 &&

                $this->BirthYear % 4 != 0 ) // 29/feb on a non-leap year

            ||

            ( $this->BirthDay > 30 &&

                in_array( $this->BirthMonth, array( 4,6,9,11 ) ) ) // 31 on a month with 30 days

            ||

            ( $this->BirthDay > 29 && $this->BirthMonth == 2 ) // later than 29/feb

            )

        {

            $this->addError( $attribute,'La fecha de nacimiento no es una fecha válida');

        }

    }




In the views, I have to do this:




            <?php echo $form->labelEx($userProfileModel, 'DateOfBirth'); ?>

            


            

            <?php

                $days = array();

                for( $i = 1; $i <= 31; ++$i )

                    $days[ $i ] = $i;

                

                $months = array();

                for( $i = 1; $i <= 12; ++$i )

                    $months[ $i ] = $i;

                

                $top_year = date( 'Y' ) - 12;

                $years = array();

                for( $i = $top_year; $i >= 1900; $i-- )

                    $years[ $i ] = $i;                


                echo CHtml::activeDropDownList( $userProfileModel, 'BirthDay', $days,

                    array( 'empty' => 'día',

                        'options'=> array ( 

                            $_POST['UserProfile']['BirthDay'] => 

                                array( 'selected' => true ) ) ) );

                    

                echo CHtml::activeDropDownList( $userProfileModel, 'BirthMonth', $months,

                    array( 'empty' => 'mes',

                        'options'=> array ( 

                            $_POST['UserProfile']['BirthMonth'] => 

                                array( 'selected' => true ) ) ) );

                

                echo CHtml::activeDropDownList( $userProfileModel, 'BirthYear', $years,

                    array( 'empty' => 'año',

                        'options'=> array ( 

                            $_POST['UserProfile']['BirthYear'] => 

                                array( 'selected' => true ) ) ) );

            ?>



How would you do this? Is this the "Yii" way of dealing with this kind of situation?

Regards!

Luis


array( 'DateOfBirth', 'type', 'type' => 'date', 'dateFormat' => 'yyyy-m-d', 'message' => '{attribute} no es una fecha válida', ),

Should be enough to check a date’s correctness.

I mean your validateDate() method does the same job.

another option is to use a CMaskTextField

read about here

andy_s: yes, thanks I have removed that after posting, and it worked great.

mbi: thanks for that info! I didn’t know we had a Mask Edit.

So, it seems my approach is good after all, isnt it?

If I’ll need 3 dropdowns for a date, then I’ll choose this approach.

Just one note: if a date is incorrect, then dropdowns won’t be highlighted. You can do it by setting errors in afterValidate() method (if there is a error after validating DateOfBirth).

Yes you are right. I’ll implement that. This may later turn into a Widget or something like that.

In the mean time, I’ll share what I get.




<?php


class Util

{

	public static function getRange($start, $end, $associative = true)

	{

		$range = array();

		if($start < $end)

		{

			for($i = $start; $i <= $end; $i++)

			{

				if($associative)

					$range[$i] = $i;

				else

					array_push($range,$i);

			}

		}else{

			for($i = $start; $i >= $end; $i--)

			{

				if($associative)

					$range[$i] = $i;

				else

					array_push($range,$i);

			}

		}

		return $range;

	}

}






<?php


class UserProfile extends CActiveRecord

{

        public $birthday_field;

	.......


	/**

	 * @return array validation rules for model attributes.

	 */

	public function rules()

	{

		// NOTE: you should only define rules for those attributes that

		// will receive user inputs.

		return array(

			.......

			array('birthday_field','validateBirthday'),

			.......

		);

	}


	.......

	

	public function validateBirthday($attribute, $params = array())

	{

		$birthday = $this->birthday_field;

		

		if(isset($birthday['month'],$birthday['day'],$birthday['year']))

		{

			if(!checkdate($birthday['month'], $birthday['day'], $birthday['year']))

				$this->addError('birthday_field','Select a valid birthday');

		}

	}

	

	.......

}






					<div class="row bottom clearfix">

						<?php echo CHtml::activeLabel($model,'birthday_field'); ?>

						<?php echo CHtml::activeDropDownList($model,'birthday_field[month]', array(0=>Yii::t('application','Month:')) + Yii::app()->locale->getMonthNames('abbreviated'), array('class'=>'select')); ?>

						<?php echo CHtml::activeDropDownList($model,'birthday_field[day]', array(0=>Yii::t('application','Day:')) + Util::getRange(1,31), array('class'=>'select')); ?>

						<?php echo CHtml::activeDropDownList($model,'birthday_field[year]', array(0=>Yii::t('application','Year:')) + Util::getRange(2000, 1950), array('class'=>'select')); ?>

						<?php echo CHtml::error($model, 'birthday_field'); ?>

					</div>



You can also hilight fields by doing this




$this->addError('birthday_field[day]','error message for day');



Hi, I work with Luis, I coded validateDate() because Yii’s validation validated only the date format, but not its validity, I.E. it allowed dates like february 30th.

I guess it only checks that the year is some 4 digit number, the month is <= 12 and the day is <= 31, but not more than that.

there’s a php function for that

checkdate()

Hi,

I’m trying to use same approach but in combination with client side validation won’t work, anyone who had this problem?

Thanks,