Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use ViewScripts on Zend_Form File Elements?

I am using this ViewScript for my standard form elements:

<div class="field" id="field_<?php echo $this->element->getId(); ?>">
   <?php if (0 < strlen($this->element->getLabel())) : ?>
      <?php echo $this->formLabel($this->element->getName(), $this->element->getLabel());?>
   <?php endif; ?>
   <span class="value"><?php echo $this->{$this->element->helper}(
      $this->element->getName(),
      $this->element->getValue(),
      $this->element->getAttribs()
   ) ?></span>
   <?php if (0 < $this->element->getMessages()->length) : ?>
       <?php echo $this->formErrors($this->element->getMessages()); ?>
   <?php endif; ?>
   <?php if (0 < strlen($this->element->getDescription())) : ?>
      <span class="hint"><?php echo $this->element->getDescription(); ?></span>
   <?php endif; ?>
</div>

Trying to use that ViewScript alone results in an error:

Exception caught by form: No file decorator found... unable to render file element

Looking at this FAQ revealed part of my problem, and I updated my form element decorators like this:

'decorators' => array(
   array('File'),
   array('ViewScript', array('viewScript' => 'form/field.phtml'))
)

Now it's rendering the file element twice, once within my view script, and extra elements with the file element outside my view script:

<input type="hidden" name="MAX_FILE_SIZE" value="8388608" id="MAX_FILE_SIZE" />
<input type="hidden" name="UPLOAD_IDENTIFIER" value="4b5f7335a55ee" id="progress_key" />
<input type="file" name="upload_file" id="upload_file" />
<div class="field" id="field_upload_file">
    <label for="upload_file">Upload File</label>
    <span class="value"><input type="file" name="upload_file" id="upload_file" /></span>
</div>

Any ideas on how to handle this properly with a ViewScript?


UPDATE: Based on Shaun's solution, here's my final code:

Form Element:

$this->addElement('file', 'upload_file', array(
    'disableLoadDefaultDecorators' => true,
    'decorators' => array('File', array('ViewScript', array(
        'viewScript' => '_form/file.phtml',
        'placement' => false,
    ))),
    'label' => 'Upload',
    'required' => false,
    'filters' => array(),
    'validators' => array(array('Count', false, 1),),
));

View Script:

<?php
$class .= 'field ' . strtolower(end(explode('_',$this->element->getType())));
if ($this->element->isRequired()) {
    $class .= ' required';
}
if ($this->element->hasErrors()) {
    $class .= ' errors';
}
?>
<div class="<?php echo $class; ?>" id="field_<?php echo $this->element->getId(); ?>">
    <?php if (0 < strlen($this->element->getLabel())): ?>
        <?php echo $this->formLabel($this->element->getFullyQualifiedName(), $this->element->getLabel());?>
    <?php endif; ?>
    <span class="value"><?php echo $this->content; ?></span>
    <?php if ($this->element->hasErrors()): ?>
        <?php echo $this->formErrors($this->element->getMessages()); ?>
    <?php endif; ?>
    <?php if (0 < strlen($this->element->getDescription())): ?>
        <p class="hint"><?php echo $this->element->getDescription(); ?></p>
    <?php endif; ?>
</div>
like image 617
Sonny Avatar asked Jan 26 '10 23:01

Sonny


4 Answers

The answer is relatively simple as it happens. All you need do is specify the File decorator first, create a specific view script for the file input and specify false for the placement in the viewScript decorator arguments, this will effectively inject the output from the File decorator into the viewScript decorator e.g.

$element->setDecorators(array('File', array('ViewScript', array('viewScript' => 'decorators/file.phtml', 'placement' => false))));

Then in the new file element view script you simply echo $this->content in the script where you'd like the file input markup to be placed. Here's an example from a recent project, so ignore the markup if it looks a little odd, hopefully it will illustrate the point.

<label for="<?php echo $this->element->getName(); ?>" class="element <?php if ($this->element->hasErrors()): ?> error<?php endif; ?>" id="label_<?php echo $this->element->getName(); ?>"> 
<span><?php echo $this->element->getLabel(); ?></span>

<?php echo $this->content; ?>

<?php if ($this->element->hasErrors()): ?>

    <span class="error">
        <?php echo $this->formErrors($this->element->getMessages()); ?>
    </span>

<?php endif; ?>

</label>

When rendered you will see this html for the element

<label for="photo" class="element" id="label_photo"> 
<span>Photo</span>

<input type="hidden" name="MAX_FILE_SIZE" value="6291456" id="MAX_FILE_SIZE">
<input type="file" name="photo" id="photo">

</label>
like image 125
Shaun Rowe Avatar answered Oct 05 '22 05:10

Shaun Rowe


This is not a simple or ideal solution because it requires an extension of the File decorator... but it's rather frustrating that they didn't make the effort to separate the hidden element generation logic from the file input generation logic. I'm not sure if the file view helper handles the issue of an element being an array (that seems to be the reason they did it this way.)

Extension of File Decorator: (the commented out part is what causes the extra input to be generated.)

<?php

class Sys_Form_Decorator_File extends Zend_Form_Decorator_File {

  public function render($content) {

    $element = $this->getElement();
    if (!$element instanceof Zend_Form_Element) {return $content;}

    $view = $element->getView();
    if (!$view instanceof Zend_View_Interface) {return $content;}

    $name = $element->getName();
    $attribs = $this->getAttribs();
    if (!array_key_exists('id', $attribs)) {$attribs['id'] = $name;}

    $separator = $this->getSeparator();
    $placement = $this->getPlacement();
    $markup = array();
    $size = $element->getMaxFileSize();

    if ($size > 0) {

      $element->setMaxFileSize(0);
      $markup[] = $view->formHidden('MAX_FILE_SIZE', $size);

    }

    if (Zend_File_Transfer_Adapter_Http::isApcAvailable()) {

      $apcAttribs = array('id' => 'progress_key');
      $markup[] = $view->formHidden('APC_UPLOAD_PROGRESS', uniqid(), $apcAttribs);

    }

    else if (Zend_File_Transfer_Adapter_Http::isUploadProgressAvailable()) {

      $uploadIdAttribs = array('id' => 'progress_key');
      $markup[] = $view->formHidden('UPLOAD_IDENTIFIER', uniqid(), $uploadIdAttribs);

    }

    /*

    if ($element->isArray()) {

      $name .= "[]";
      $count = $element->getMultiFile();

      for ($i = 0; $i < $count; ++$i) {

        $htmlAttribs = $attribs;
        $htmlAttribs['id'] .= '-' . $i;
        $markup[] = $view->formFile($name, $htmlAttribs);

      }

    }

    else {$markup[] = $view->formFile($name, $attribs);} 

    */

    $markup = implode($separator, $markup);

    switch ($placement) {

      case self::PREPEND: return $markup . $separator . $content;
      case self::APPEND:
      default: return $content . $separator . $markup;

    }

  }

 }

?>

Form setup in controller action:

$form = new Zend_Form();
$form->addElement(new Zend_Form_Element_File('file'));
$form->file->setLabel('File');
$form->file->setDescription('Description goes here.');

$decorators = array();
$decorators[] = array('File' => new Sys_Form_Decorator_File());
$decorators[] = array('ViewScript', array('viewScript' => '_formElementFile.phtml'));
$form->file->setDecorators($decorators);

$this->view->form = $form;

In action view:

<?php echo $this->form; ?>

In element script:

<div class="field" id="field_<?php echo $this->element->getId(); ?>">

<?php if (0 < strlen($this->element->getLabel())) : ?>
<?php echo $this->formLabel($this->element->getName(), $this->element->getLabel());?>
<?php endif; ?>

<span class="value">
<?php 

echo $this->{$this->element->helper}(

  $this->element->getName(),
  $this->element->getValue(),
  $this->element->getAttribs()

);

?>
</span>

<?php if (0 < $this->element->getMessages()->length) : ?>
<?php echo $this->formErrors($this->element->getMessages()); ?>
<?php endif; ?>

<?php if (0 < strlen($this->element->getDescription())) : ?>
<span class="hint"><?php echo $this->element->getDescription(); ?></span>
<?php endif; ?>

</div>

Output should be:

<form enctype="multipart/form-data" action="" method="post">
<dl class="zend_form">
<input type="hidden" name="MAX_FILE_SIZE" value="134217728" id="MAX_FILE_SIZE" />
<div class="field" id="field_file">
<label for="file">File</label>
<span class="value"><input type="file" name="file" id="file" /></span>
<span class="hint">Description goes here.</span>
</div>
</dl>
</form>

A problem with this solution is that the hidden elements don't render within the viewscript; this might be a problem if you're using the div as a selector in a client-side script...

like image 33
Robin M. Canaday Avatar answered Oct 05 '22 04:10

Robin M. Canaday


What I've realized is, a custom decorator class will handle most fields except File fields. Make sure your class implements the interface like so:

class CC_Form_Decorator_Pattern 
extends Zend_Form_Decorator_Abstract 
implements Zend_Form_Decorator_Marker_File_Interface

This worked for me.

like image 2
Christof Coetzee Avatar answered Oct 05 '22 04:10

Christof Coetzee


This helped me fix my problem. I adjusted the code to wrap the file element inside a table. To make it work, simply remove the label from the viewdecorator and add the file element as follows:

$form->addElement('file', 'upload_file', array(
        'disableLoadDefaultDecorators' => true,
        'decorators' => array(
            'Label',
            array(array('labelTd' => 'HtmlTag'), array('tag' => 'td', 'class' => 'labelElement')),
            array(array('elemTdOpen' => 'HtmlTag'), array('tag' => 'td', 'class' => 'dataElement','openOnly' => true, 'placement' => 'append')),
            'File',
            array('ViewScript', array(
            'viewScript' => 'decorators/file.phtml',
            'placement' => false,
            )),
            array(array('elemTdClose' => 'HtmlTag'), array('tag' => 'td', 'closeOnly' => true, 'placement' => 'append')),
            array(array('row' => 'HtmlTag'), array('tag' => 'tr'))
        ),
        'label' => 'Upload',
        'required' => false,
        'filters' => array(),
        'validators' => array(array('Count', false, 1), ),
    ));
like image 1
Marvin Avatar answered Oct 05 '22 03:10

Marvin