views:

45

answers:

2

Hello,

I have a zend form that lets the user fill out attributes of an object. For example, a music artist. The form has basic information about the artist, things like name, phone number, address, and what not, but it also has a file upload field. If the user uploads a file, its stored with this instance of the object in the database (essentially a row), if the user ever wants to edit this particular instance, the form is repopulated with the objects information. However the form does not repopulate the file upload information because its not going to reupload it.

How can I signal to the user that a file was already uploaded, indicate which file it was, and possibly even link to it? something similar to populating the form elements is preferred. I'd ideally like to be able to offer the user the ability to upload over the previously uploaded file, so the file upload element should stay. Any idea? Thanks a lot for reading.

Cheers, Joe Chin

+1  A: 

How can I signal to the user that a file was already uploaded, indicate which file it was, and possibly even link to it?

I would print out the previously uploaded file.

To accomplish this, you could use a form decorator. This is a sketch that needs to be modified and refactored with security in mind, but it does print out an image-tag. (If you'd rather display a list of files, there's a hint in the end.)

// has to override a File-decorator in my case
class ArtistDecorator extends Zend_Form_Decorator_File {
    private $_artistId;
    public function __construct($options = array()) {
        if(isset($options['artistId'])) {
            $this->_artistId = $options['artistId'];
            unset($options['artistId']);
        }
        return parent::__construct($options);
    }

    public function render($content) {
        return '<img src="/images/'.$this->_artistId.'.jpg" />'.$content;
    }
}

class Application_Form_Login extends Zend_Form{
    private $_artistId;

    public function __construct($options = array()) {
        if(isset($options['artistId'])) {
            $this->_artistId = $options['artistId'];
            unset($options['artistId']);
        }
        return parent::__construct($options);
    }

    public function init() {
        $this
            ->setAction('/')
            ->setMethod('post');
        $el = new Zend_Form_Element_File('image');
        if($this->_artistId) {
            $el->setDecorators(array(new ArtistDecorator(array('artistId' => 3))));
        }       
        $this->addElement($el);
    }
}

You should not populate the file-element since it you don't want it to upload the same file over and over and over again.

If the file(s) a mp3 or so: follow the same routine but use an output like:

<ul>
  <li><a href="/music/song_1.mp3">Song 1</a></li>
  <li><a href="/music/song_2.mp3">Song 2</a></li>
</ul>

Inject the "songs" to the form's constructor.

chelmertz
Hi, bare with me as I've been using Zend for about a month. In my controller I have the line $form->options = array('trackName' => 'trackFileName'); I get this error: "Message: No valid elements specified for display group" I believe this is because $options is not a defined form element. How do I push this array to the form for the decorator? Thanks
JoeChin
Hi, please disregard my last comment, with some more googling I found I could pass the array using: $form = new Zend_Form(array());
JoeChin
Yeah, you should try to move the form's logic out of your controllers so the form easily could be reused in multiple actions (even if it's not desired, it's very good practice of separation). Let us know how it works out.
chelmertz
Hi, Ok I got it working. One thing I need to be able to do though is still generate the form upload element incase the file needs to be over written with a new file. With the decorator in place I am able to show what files have been uploaded but the element itself disappears. This is the first time I've ever used a decorator, is there a simple way to prepend this list to content so its just before the file uploads elements label?
JoeChin
It's really messy but you need to replace my `ArtistDecorator::render()`-method with something like this: http://framework.zend.com/manual/en/zend.form.decorators.html#zend.form.decorators.custom
chelmertz
I see what you mean. I was hoping it would be as simple as saying render the element like normal and just append the markup from the decorator showing what was already uploaded. Thanks for all the help.
JoeChin
Hi Chelmertz, Just wanted to say thanks again. I was actually able to get the desired results by taking your original answer's constructor and combining it with the steps in this stackoverflow thread <http://stackoverflow.com/questions/2143462/how-do-i-use-viewscripts-on-zend-form-file-elements>. If this interests you, let me know, I'd be happy to share my code. Thanks again!
JoeChin
@JoeChin: nice! You could share your code by answering this question yourself and accept your own answer.
chelmertz
Thanks for the suggestion!
JoeChin
+2  A: 

Hello,

The fastest way I found to get this implemented was to combine the answer from Chelmertz with this thread http://stackoverflow.com/questions/2143462/how-do-i-use-viewscripts-on-zend-form-file-elements. Instead of defining a decorator to show the previously uploaded file, I am placing the link in the description for the element and then dumping it into a view script (at least I think thats what I am doing). The final solution looks like this:

Zend Form File:

class Application_Form_LabDetails extends Zend_Form{
private $_uploadPath;
private $_artistID;
private $_singleFile;

public function __construct($options = array()) {

    if(isset($options['uploadPath'])) {
        $this->_uploadPath = $options['uploadPath'];
        unset($options['uploadPath']);
    }

    if(isset($options['artistID'])) {
        $this->_artistID = sprintf("%06s",(string)$options['artistID']);
        unset($options['artistID']);
    }

    if(isset($options['singleFile'])) {
        $this->_singleID = $options['singleFile'];
        unset($options['singleFile']);
    }

    return parent::__construct($options);
}

public function init()
{
    $this->setName('artistDetails');

    ...

    $singleID = $this->createElement('file', '$singleFile');
    $singleID->setLabel('Current Single')
        ->setDestination('data/uploads')
        ->addValidator('count',true, 1)
        ->addValidator('Size', true, 5242880)
        ->addValidator('Extension', true, 'mp3');

    if($this->_cocFile){
        $singleID->setDescription(
            '<a href="/'.$this->_uploadPath.'/'.$this->_artistID
            .$this->_singleFile.'" taget="_blank">'
            .$this->_singleFile.'</a>')
        ->setDecorators(
            array('File',
            array('ViewScript',
            array('viewScript' => 'artist/file.phtml', 'placement' => false)
            )));
    }

    ...
}}

View script to handle file details and form element:

<!-- decorator through view script,  placed in 'views/scripts/controller/file.phtml' -->
<!-- outputs form label markup -->
<dt id="<?php echo $this->element->getName(); ?>-label">
    <label for="<?php echo $this->element->getName(); ?>" 
        class="<?php if ($this->element->isRequired()): ?>required<?php endif; ?>">
        <?php echo $this->element->getLabel(); ?></label><br />
    <span>Uploaded: <?php echo $this->element->getDescription(); ?></span>
</dt>

<!-- outputs form element markup -->
<dd id="<?php echo $this->element->getName(); ?>-element">
    <?php echo $this->content; ?>
    <?php if ($this->element->hasErrors()): ?>
        <ul class="error">
            <?php echo $this->formErrors($this->element->getMessages()); ?>
        </ul>
    <?php endif; ?>
</dd>

Lastly in the controller I have:

//$id is taken from the URI
if ($id > 0) {

    $artistDetails = new Application_Model_DbTable_ArtistDetails();
    $artistData = $artistDetails->getArtistDetails($id);
    $options = array(
        'uploadPath' => $config->uploads->labs->path,
        'artistID' => $artistData['a_id'],
        'singleFile' => $artistData['a_singleFile'],
        );

    $form = new Application_Form_LabDetails($options);

    ...

}

This should give you something that looks like:

alt text

If there are questions, let me know, I'm here to help.

Cheers, JoeChin

JoeChin