Yii 1.1: Additional form data with XUpload

14 followers

In this article you'll learn how to send additional form data when uploading files using XUpload widget

This are the steps that we need:

  1. Duplicate and edit the upload form template, so we can render the fields for the additional data
  2. Duplicate and edit the download form template, so we can render the additioanl data
  3. Create a controller action that will handle the file upload, and the additional data.
  4. Adjust the widget in our view, and add the corresponding javascript

Before getting started, note that we will be using a custom model named Image, which will hold all the information submitted by the widget. in our case the additional attributes defined in this model are title and description.

Duplicate and edit the upload form template

We need our own version of the upload template, so we can add the new fields for the additional data that we want to send along with the file. to do that, copy the upload.php template included with the plugin, to your controller's views folder.

Assuming a default installation, the path to the template should be:

protected/extensions/xupload/views/upload.php

Assuming your controller its SiteController, your copy of the template should go to:

protected/views/site/upload.php

To add our new fields, add a new column ( td ) to the template, so it looks like this:

<!-- The template to display files available for upload -->
<script id="template-upload" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
    <tr class="template-upload fade">
        <td class="preview">
            <span class="fade"></span>
        </td>
        <td class="name">
            <span>{%=file.name%}</span>
        </td>
        <td>//This is the new column, and below we add 2 new fields
            <label>Title</label>
            <input type="text" name="title" required /><br />
            <label>Description</label>
            <textarea name="description" rows="3" required ></textarea><br />
        </td>
        <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
        {% if (file.error) { %}
            <td class="error" colspan="2"><span class="label label-important">{%=locale.fileupload.error%}</span> {%=locale.fileupload.errors[file.error] || file.error%}</td>
        {% } else if (o.files.valid && !i) { %}
            <td>
                <div class="progress progress-success progress-striped active"><div class="bar" style="width:0%;"></div></div>
            </td>
            <td class="start">{% if (!o.options.autoUpload) { %}
                <button class="btn btn-primary">
                    <i class="icon-upload icon-white"></i>
                    <span>{%=locale.fileupload.start%}</span>
                </button>
            {% } %}</td>
        {% } else { %}
            <td colspan="2"></td>
        {% } %}
        <td class="cancel">{% if (!i) { %}
            <button class="btn btn-warning">
                <i class="icon-ban-circle icon-white"></i>
                <span>{%=locale.fileupload.cancel%}</span>
            </button>
        {% } %}</td>
    </tr>
{% } %}
</script>

We added to new fields to our template - title and description - the rest of the template remains the same

Duplicate and edit the download form template

To show the info in those fields after the files have been uploaded, we need to adjust our download template.

Assuming a default installation, the path to the template should be:

protected/extensions/xupload/views/download.php

Assuming your controller its SiteController, your copy of the template should go to:

protected/views/site/download.php

Now, the server returns a Json with the info of the uploaded file, in the following step we will create the action in the controller that returns that json including the additional fields. but for now, lets assume they are there so we can edit our template acordingly.

<!-- The template to display files available for download -->
<script id="template-download" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
    <tr class="template-download fade">
        {% if (file.error) { %}
            <td></td>
            <td class="name"><span>{%=file.name%}</span></td>
            <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
            <td class="error" colspan="2"><span class="label label-important">{%=locale.fileupload.error%}</span> {%=locale.fileupload.errors[file.error] || file.error%}</td>
        {% } else { %}
            <td class="preview">{% if (file.thumbnail_url) { %}
                <a href="{%=file.url%}" title="{%=file.name%}" rel="gallery" download="{%=file.name%}"><img src="{%=file.thumbnail_url%}"></a>
            {% } %}</td>
            <td class="name">
                <a href="{%=file.url%}" title="{%=file.name%}" rel="{%=file.thumbnail_url&&'gallery'%}" download="{%=file.name%}">{%=file.name%}</a>
            </td>
            <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
            <td>{%=file.title%}</td>//Here we show the file title
            <td>{%=file.description%}</td>//and the file description
        {% } %}
        <td class="delete">
            <button class="btn btn-danger" data-type="{%=file.delete_type%}" data-url="{%=file.delete_url%}">
                <i class="icon-trash icon-white"></i>
                <span>{%=locale.fileupload.destroy%}</span>
            </button>
            <input type="checkbox" name="delete" value="1">
        </td>
    </tr>
{% } %}
</script>

We only added the title and the description of the file to the template, nothing else was changed.

Create a controller action

Now we need a controller actions that will not only handle the file upload, but also initialize our model's attributes and return the json with the additional data.

The code here is just a copy of the contents of the XUploadAction, so it doesn't save anything to the database, or does anything fancy.

public function actionUploadAdditional(){
        header( 'Vary: Accept' );
        if( isset( $_SERVER['HTTP_ACCEPT'] ) && (strpos( $_SERVER['HTTP_ACCEPT'], 'application/json' ) !== false) ) {
            header( 'Content-type: application/json' );
        } else {
            header( 'Content-type: text/plain' );
        }
 
        if( isset( $_GET["_method"] ) ) {
            if( $_GET["_method"] == "delete" ) {
                $success = is_file( $_GET["file"] ) && $_GET["file"][0] !== '.' && unlink( $_GET["file"] );
                echo json_encode( $success );
            }
        } else {
            $this->init( );
            $model = new Image;//Here we instantiate our model
 
            //We get the uploaded instance
            $model->file = CUploadedFile::getInstance( $model, 'file' );
            if( $model->file !== null ) {
                $model->mime_type = $model->file->getType( );
                $model->size = $model->file->getSize( );
                $model->name = $model->file->getName( );
                //Initialize the ddditional Fields, note that we retrieve the
                //fields as if they were in a normal $_POST array
                $model->title = Yii::app()->request->getPost('title', '');
                $model->description  = Yii::app()->request->getPost('description', '');
 
                if( $model->validate( ) ) {
                    $path = Yii::app() -> getBasePath() . "/../images/uploads";
                    $publicPath = Yii::app()->getBaseUrl()."/images/uploads";
                    if( !is_dir( $path ) ) {
                        mkdir( $path, 0777, true );
                        chmod ( $path , 0777 );
                    }
                    $model->file->saveAs( $path.$model->name );
                    chmod( $path.$model->name, 0777 );
 
                    //Now we return our json
                    echo json_encode( array( array(
                            "name" => $model->name,
                            "type" => $model->mime_type,
                            "size" => $model->size,
                            //Add the title 
                            "title" => $model->title,
                            //And the description
                            "description" => $model->description,
                            "url" => $publicPath.$model->name,
                            "thumbnail_url" => $publicPath.$model->name,
                            "delete_url" => $this->createUrl( "upload", array(
                                "_method" => "delete",
                                "file" => $path.$model->name
                            ) ),
                            "delete_type" => "POST"
                        ) ) );
                } else {
                    echo json_encode( array( array( "error" => $model->getErrors( 'file' ), ) ) );
                    Yii::log( "XUploadAction: ".CVarDumper::dumpAsString( $model->getErrors( ) ), CLogger::LEVEL_ERROR, "xupload.actions.XUploadAction" );
                }
            } else {
                throw new CHttpException( 500, "Could not upload file" );
            }
        }
    }

Thats all we need to get the additional fields we sent from out widget, saving to the database or changing the folders where you wish your files to be saved, or generating thumnails, etc. its up to you, you can check the XUpload workflow wiki for examples on that.

Adjust the widget, and add javascript

Now that we have everything setup, we just need to configure our widget to use our templates, and point to our action. also we need some additional javascript as explained in the original jquery plugin wiki

<?php
        $this->widget('xupload.XUpload', array(
            'url' => Yii::app()->createUrl("site/uploadAdditional", array("parent_id" => 1)),
            'model' => $model,//An instance of our model
            'attribute' => 'file',
            'multiple' => true,
            //Our custom upload template
            'uploadView' => 'application.views.site.upload',
            //our custom download template
            'downloadView' => 'application.views.site.download',
            'options' => array(//Additional javascript options
                //This is the submit callback that will gather
                //the additional data  corresponding to the current file
                'submit' => "js:function (e, data) {
                    var inputs = data.context.find(':input');
                    data.formData = inputs.serializeArray();
                    return true;
                }"
            ),
        ));
        ?>

Thats it, if you need additional help don't hesitate asking in the official forum thread.

Total 7 comments

#16811 report it
Extj at 2014/03/31 01:22am
Add id to widget

Don't forget add id to widget via htmlOptions:

<?php
$this->widget('xupload.XUpload', array(
    /* other attributes */
    'htmlOptions' => array('id'=>'somemodel-form'),
));
?>

Without it appending new file to form after choosing won't work. Please change it in last example.

#16153 report it
lutek at 2014/01/23 09:25am
@vincentlkl xupload in CActiveform

@vincentlkl add this option to your widget 'xupload.XUpload'

'showForm' => false,
#11089 report it
vincentlkl at 2012/12/15 11:32pm
xupload in CActiveform

I have been trying to insert the Xupload extension into CActiveform but I am not able to submit the form.

<?php
    $form = $this->beginWidget('CActiveForm', array(
          'id' => 'form-form',
          'enableAjaxValidation' => false,
            //This is very important when uploading files
          'htmlOptions' => array('enctype' => 'multipart/form-data'),
        ));
      ?>    
        <div class="row">
            <?php echo $form->labelEx($model,'username'); ?>
            <?php echo $form->textField($model,'username'); ?>
            <?php echo $form->error($model,'username'); ?>
        </div>
        <!-- Other Fields... -->
        <div class="row">
            <?php echo $form->labelEx($model,'attachment_photo'); ?>
 
            <?php
        $this->widget('xupload.XUpload', array(
            'url' => Yii::app()->createUrl("form/upload", array("parent_id" => 1)),
            'model' => $photos,//An instance of our model
            'attribute' => 'file',
            'multiple' => false,
            'htmlOptions' => array('id'=>'form-form'),
            //Our custom upload template
            'uploadView' => 'application.views.site.upload',
            //our custom download template
            'downloadView' => 'application.views.site.download',
            'options' => array(//Additional javascript options
                //This is the submit callback that will gather
                //the additional data  corresponding to the current file
                'submit' => "js:function (e, data) {
                    var inputs = data.context.find(':input');
                    data.formData = inputs.serializeArray();
                    return true;
                }"
            ),
        ));
        ?>
 
        </div>
        <button type="submit">Submit</button>
    <?php $this->endWidget(); ?>

any problem with that?

#10080 report it
Asgaroth at 2012/10/03 03:32pm
@Hesam Khaki

thank you very much, I'm glad to see that this is actually being useful to people.

#10079 report it
Hesam K at 2012/10/03 03:22pm
just want to say thanks!

thank you very much Asgaroth! that plugin, wrapped in your extension, supported by your workflow wiki and now this one, all beside your great and kind responses in forum, made such a great resource and tool for everyone who is interested in ajax uploading. I have so much respect for you :)

#10035 report it
Asgaroth at 2012/10/01 12:14pm
@Maxxer

this is not php code, its javascript code, you'll notice as it is wrapped in <script> tags.

The original jquery plugin uses Javacript Templates as its templating engine. this has nothing to do with PHP.

If you want to change the javascript templating engine you can take a look this article

#10034 report it
Maxxer at 2012/10/01 12:05pm
php syntax?

maybe you'd better replace {% with the more standard <?php ?> tags?

Leave a comment

Please to leave your comment.

Write new article