Yii 1.1: How to create/save more Model inputs and make them repeatable with jQuery

16 followers

This is the first real problem that I encountered using Yii, trying to make a Model input repeatable in the form, validate and save them.

First of all you have to know that Yii don't support this scenario by default so we have to change manually our code :)

Let's view an example. We have two tables: Event and PhotoEvent. When we want to create an Event we want to add some photos to it. This is the database tables example: Alt text

How can we add N photos input field in the form dynamically?

First of all you have to create your IMG_DIR_PATH directory where to save the images and give it the rights to write inside.

Second, if you need to use the images, resize them, play with them you have to remember to add this to the config/main.php

'image'=>array(
    'class'=>'application.extensions.image.CImageComponent',
    // GD or ImageMagick
    'driver'=>'GD',
    // ImageMagick setup path
    'params'=>array('directory'=>'/opt/local/bin'),
)

This is how is made the html page to collect the inputs:

<h2>New Event</h2>
<div class="actionBar">
    [<?php echo CHtml::link('Event List',array('list')); ?>]
    [<?php echo CHtml::link('Manage Event',array('admin')); ?>]
</div>
<div class="yiiForm">
    <p>
    Fields with <span class="required">*</span> are required.
    </p>
    <?php echo CHtml::form('','post',array('enctype'=>'multipart/form-data')); ?>
 
    <?php echo CHtml::errorSummary(array_merge( array($event), $photosEvent)); ?>
 
    <div class="simple">
        <?php echo CHtml::activeLabelEx($event,'name'); ?>
        <?php echo CHtml::activeTextField($event,'name'); ?>
    </div>
    <div class="simple">
        <?php echo CHtml::activeLabelEx($event,'description'); ?>
        <?php echo CHtml::activeTextField($event,'description'); ?>
    </div>
    <div class="simple">
        <?php echo CHtml::activeLabelEx($event,'expireDate'); ?>
        <?php echo CHtml::activeTextField($event,'expireDate'); ?>
    </div>
 
    <?php echo CHtml::button('add Photos', array('name'=>'addPhotos', 'id'=>'addPhotos')); ?>
 
    <?php foreach($photosEvent as $i => $photo): ?>
    <div id="photo-<?php echo $i ?>">
        <div class="simple">   
            <?php echo CHtml::activeLabelEx($photo,'photoUrl'); ?>
            <?php echo CHtml::activeFileField($photo, "photoUrl[$i]"); ?>
        </div>
        <br />
    </div>
    <?php endforeach; ?>
 
    <div class="row action">
        <?php echo CHtml::submitButton($update ? 'Save' : 'Create', array('name'=>'submitDatas')); ?>
    </div>
</form>
</div>
 
<script type="text/javascript">
// I need to know how many photos I've already added when the validate return FALSE
var photosAdded = <?php echo $photosNumber; ?>;
 
// Add the event to the period's add button
$('#addPhotos').click(function () {
    // I'm going to clone the first div containing the Model input couse I don't want to create a new div and add every single structure
    var divCloned = $('#photo-0').clone();      
    // I'm attaching the div to the last input created
    $('#photo-'+(photosAdded++)).after(divCloned);
    // Changin the div id
    divCloned.attr('id', 'photo-'+photosAdded);
    // Initializing the div contents
    initNewInputs(divCloned.children('.simple'), photosAdded);
});
 
function initNewInputs( divs, idNumber ) {
    // Taking the div labels and resetting them. If you send wrong information,
    // Yii will show the errors. If you than clone that div, the css will be cloned too, so we have to reset it
    var labels = divs.children('label').get();
    for ( var i in labels )
        labels[i].setAttribute('class', 'required');      
 
    // Taking all inputs and resetting them.
    // We have to set value to null, set the class attribute to null and change their id and name with the right id.
    var inputs = divs.children('input').get();      
 
    for ( var i in inputs  ) {
        inputs[i].value = "";
        inputs[i].setAttribute('class', '');
        inputs[i].id = inputs[i].id.replace(/\d+/, idNumber);
        inputs[i].name = inputs[i].name.replace(/\d+/, idNumber);
    }
}
</script>

And this is how is made the actionCreat to handle the datas and to manage it.

public function actionCreate()
{
    // Adding jQuery to the view page.
    Yii::app()->getClientScript()->registerCoreScript('jquery');
 
    $event = new Event;
 
    // Adding an empty PhotoEvent to the form
    $photosEvent = array( new PhotoEvent, );
 
    // If everything is setted I will check and process the inputs
    if( isset( $_POST['submitDatas'] ) && isset( $_POST['PhotoEvent'], $_POST['Event'] ) )
    {
        $event->attributes = $_POST['Event'];
        // DB date layout
        $event->expireDate = date_format(date_create($event->expireDate), 'Y-m-d');   
        $valid = $event->validate();
 
        // This is the crappy part of the script that I need to improve and make it more elegance
        // I've to add to the photoEvent array a new PhotoEvent for every istance in $_POST['PhotoEvent']
        foreach ( $_POST['PhotoEvent'] as $i => $photo ) {
            $photosEvent[$i] = new PhotoEvent;   
            if ( isset( $_POST['PhotoEvent'][$i] ) )
                $photosEvent[$i]->photoUrl = CUploadedFile::getInstance($photosEvent[$i], "photoUrl[$i]" );
            //$photosEvent[$i]->photoUrl = CUploadedFile::getIstance($photosEvent[$i], "photoUrl[$i]" );
            $valid = $valid && $photosEvent[$i]->validate();               
        }
 
        if( $valid && $event->save(false) ) {
            // You will need this if you want to resize and play around images :)
            //Yii::import('application.extensions.image.Image');
 
            // Saving each period ( i've to change the date format couse in Italy we use dd/mm/yyyy
            foreach ( $photosEvent as $i => $photo ) {
                $photo->eventId = $event->id;
                // Taking and saving the image in the filesystem in the IMG_DIR_PATH/ directory path.
                // In this case IMG_DIR_PATH is in the root of the 
                // site and you need to give it rights to write the image :)
                $photo->photoUrl->saveAs('IMG_DIR_PATH/'.$photo->photoUrl->getName());
                /* If you need to resize the image use this path of code
                   $image = new Image('IMG_DIR_PATH/'.$photo->photoUrl->getName());
                   $image->resize(200, 200);
                   $image->save();
                 */
                $photo->save(false);
            }
            $this->redirect(array('show','id'=>$event->id));
        }
    }
    $this->render('create', array(
        'event' => $event,
        'photosEvent' => $photosEvent,
        'photosNumber' => isset($_POST['PhotoEvent']) ? count($_POST['PhotoEvent'])-1 : 0, //How many PhotoEvent the user added
        'update' => false,
    ));
}

I wish you will enjoy my guide :) Please gimme some feedback to improve it :)

See ya! Emanuele "StErMi" Ricci.

Total 3 comments

#15864 report it
RakhiKasat at 2013/12/24 09:07am
query

Hello, I wanted to know is this code is working ? why half lines are commented ? I dont understand. I am working on Cmultiupload & its not working..

#2376 report it
Werewolf at 2010/12/19 05:30am
jquery :last
$('#photo-'+(photosAdded++)).after(divCloned);

It's better to use css class and :last selector instead numbers. When you create delete links you will face promblems with that numbers.

$('.photos:last').after(divCloned);
#1622 report it
rosko at 2009/05/31 05:00pm
some

On some servers needs next jquery-code:

$('form').submit(function () {
    var f = $(":file").get();
    for (var i in f) {
        if (f[i].value == '') $(f[i]).parent().remove();
    }
});

This snippet removes all empty file input fields. Otherwise POST-request will be incorrect.

Leave a comment

Please to leave your comment.

Write new article
  • Written by: qiang
  • Updated by: Yang He
  • Category: Tutorials
  • Yii Version: 1.1
  • Votes: +12
  • Viewed: 31,205 times
  • Created on: Apr 21, 2009
  • Last updated: Jun 29, 2012