4

I created a custom form from a buildForm function. In this form, I would like add an image field.

I can do that via this code :

$form['main']['image'] = array(
    '#type' => 'text_format',
    '#title' => t('image'),
    '#default_value' => array(10),
);

I can upload and remove the image from my form. However, when I upload an image, I haven't this preview. I mean, when I create a content via the Drupal UI. I can add a preconfigured "image" field. When I upload an image via this "image" field, I have a preview of the image.

And here, when I create the field element programmatically, I haven't a preview of the image when I upload her. How use api Drupal for have the preview of the image when I upload her via my "image" field ?

spacecodeur
  • 2,206
  • 7
  • 35
  • 71

5 Answers5

12

So here is how I got my form to display the thumbnail image. What I basically did was take the code in ImageWidget::process, put it in a theme preprocessor and set the #theme property of the element to image_widget.

Your image element in your form class should look like this:

$form['profile_image'] = [
   '#type' => 'managed_file',
   '#title' => t('Profile Picture'),
   '#upload_validators' => array(
       'file_validate_extensions' => array('gif png jpg jpeg'),
       'file_validate_size' => array(25600000),
   ),
   **'#theme' => 'image_widget',**
   **'#preview_image_style' => 'medium',**
   '#upload_location' => 'public://profile-pictures',
   '#required' => TRUE,
];

In your name_of_default_theme.theme file, you need the following:

function name_of_default_theme_preprocess_image_widget(&$variables) {
    $element = $variables['element'];

    $variables['attributes'] = array('class' => array('image-widget', 'js-form-managed-file', 'form-managed-file', 'clearfix'));

    if (!empty($element['fids']['#value'])) {
        $file = reset($element['#files']);
        $element['file_' . $file->id()]['filename']['#suffix'] = ' <span class="file-size">(' . format_size($file->getSize()) . ')</span> ';
        $file_variables = array(
            'style_name' => $element['#preview_image_style'],
            'uri' => $file->getFileUri(),
        );

        // Determine image dimensions.
        if (isset($element['#value']['width']) && isset($element['#value']['height'])) {
            $file_variables['width'] = $element['#value']['width'];
            $file_variables['height'] = $element['#value']['height'];
        } else {
            $image = \Drupal::service('image.factory')->get($file->getFileUri());
            if ($image->isValid()) {
                $file_variables['width'] = $image->getWidth();
                $file_variables['height'] = $image->getHeight();
            }
            else {
                $file_variables['width'] = $file_variables['height'] = NULL;
            }
        }

        $element['preview'] = array(
            '#weight' => -10,
            '#theme' => 'image_style',
            '#width' => $file_variables['width'],
            '#height' => $file_variables['height'],
            '#style_name' => $file_variables['style_name'],
            '#uri' => $file_variables['uri'],
        );

        // Store the dimensions in the form so the file doesn't have to be
        // accessed again. This is important for remote files.
        $element['width'] = array(
            '#type' => 'hidden',
            '#value' => $file_variables['width'],
        );
        $element['height'] = array(
            '#type' => 'hidden',
            '#value' => $file_variables['height'],
        );
    }

    $variables['data'] = array();
    foreach (Element::children($element) as $child) {
        $variables['data'][$child] = $element[$child];
    }
}

The solution works well, but the image module is missing a class with the @FormElement annotation. That is why the element isn't rendered properly.

Je Suis Alrick
  • 3,404
  • 2
  • 16
  • 23
  • Is there a way to make it work for multiple files? I mean, when you add `'#multiple' => TRUE` to managed field the code above shows preview only for the first image. – PolGraphic Jan 01 '17 at 20:47
  • I'm not sure, but given a collection of files, maybe something like this could work: $element['preview'][n] = array('#theme', ...). – Je Suis Alrick Jan 19 '17 at 22:55
  • 1
    NOTE: Use the below for the preprocess function, many Element classes in D8. Drupal\Core\Render\Element; – user25794 Jan 03 '19 at 21:30
3

Can you try with managed_file type?

'#type' => 'managed_file'
Nish
  • 301
  • 1
  • 9
2

First some clarifications, then some observations and then the code that worked for me.

Clarifications:

  1. I am using Drupal 9, but the solution proposed below may work also for Drupal 8.
  2. By "default temporary directory" I mean the directory defined in the following settings

file

[DrupalrootDirectory]/sites/default/settings.php

using

$settings['file_temp_path'] = '/The/Absolute/Path/To/Your/Temporary/Directory'; 

or defined by your operating system and that you can check in the Drupal administration menu:

[YourSiteWebAddress]/admin/config/media/file-system
  1. When something is written inside square brackets [], like this, [DrupalrootDirectory], it means that you should replace it with the actual name in your system, Drupal installation or custom module/file, without the square brackets.

Observations:

  1. No need to create apriory any directories inside the default temporary directory. They will be created automatically.
  2. When you click the Remove button (which appears automatically as soon you upload a file in your custom form), the image file and the thumbnail files are deleted from the default temporary directory.
  3. It does not matter if you define your default temporary directory inside the default site location,

e.g.,

[DrupalrootDirectory]/sites/default/files

or outside it, for example,

[DrupalrootDirectory]/sites/other/files

In my case, I defined it to:

[DrupalrootDirectory]/sites/default/files/temp
  1. No need to make changes to .htaccess (in Apache server) file inside the default temporary directory or

inside

[DrupalrootDirectory]/sites/default/files

Keep them as Drupal created them.

  1. No need to change file rights to "777" inside the default temporary directory, "775" is enough.

  2. Check that your Drupal installation is actually creating thumbnails for images uploaded using the

administration menu:

 [YourSiteWebAddress]/admin/content/media 

when using the "Add media" link

[YourSiteWebAddress]/media/add/image
  1. No need to make any other changes inside Drupal settings

file

[DrupalrootDirectory]/sites/default/settings.php

The code that worked for me:

It is based in the code published above by "Je Suis Alrick". But his code was not working for me, neither I could not find in which part of his code the thumbnail image was actually created. In my search for it, this post helped

Generate programmatically image styles with Drupal 8

So, here is the final code:

  1. In your custom module, in the custom form file, most probably located

in:

[DrupalrootDirectory]/modules/custom/[NameOfYourCustomModule]/src/Form/[NameOfYourCustomForm].php

you add the element to be used to upload the image file:

$form['profile_image'] = [
   '#type' => 'managed_file',
   '#title' => t('Profile Picture'),
   '#upload_validators' => array(
       'file_validate_extensions' => array('gif png jpg jpeg'),
       'file_validate_size' => array(25600000),
   ),
  '#theme' => 'image_widget',
  '#preview_image_style' => 'medium',

   '#required' => TRUE,
];

So far, the same as in the code of "Je Suis Alrick", except that I deleted the definition for '#upload_location', so the default temporary directory will be used avoiding security complaints.

The important part here is:

  • the definition of the '#theme', which may be any name, but in this case is 'image_widget';
  • and the definition of '#preview_image_style', which must be the machine name of one of the image styles defined in your Drupal installation, that you can check in your administration

menu

[YourSiteWebAddress]/admin/config/media/image-styles

For this case the style 'medium' will be used, which is one of the image styles created by default by Drupal.

  1. Then, in the module file of your custom module, named [NameOfYourCustomModule].module and most probably located

in:

[DrupalrootDirectory]/modules/custom/[NameOfYourCustomModule]/

You will need to paste the following code:

<?php

use Drupal\image\Entity\ImageStyle;

function [NameOfYourCustomModule]_preprocess_image_widget(&$variables) {
    $element = $variables['element'];

    $variables['attributes'] = array('class' => array('image-widget', 'js-form-managed-file', 'form-managed-file', 'clearfix'));

    if (!empty($element['fids']['#value'])) {
      $file = reset($element['#files']);
      $element['file_' . $file->id()]['filename']['#suffix'] = ' <span class="file-size">(' . format_size($file->getSize()) . ')</span> ';
      $file_variables = array(
        'style_name' => $element['#preview_image_style'],
        'uri' => $file->getFileUri(),
      );

      // Determine image dimensions.
      if (isset($element['width']['#value']) && isset($element['height']['#value'])) {
        $file_variables['width'] = $element['width']['#value'];
        $file_variables['height'] = $element['height']['#value'];
      } else {
        $image = \Drupal::service('image.factory')->get($file->getFileUri());
        
        if ($image->isValid()) {
          $file_variables['width'] = $image->getWidth();
          $file_variables['height'] = $image->getHeight();
          $style = ImageStyle::load($file_variables['style_name']);
          $image_uri = $file->getFileUri();
          $destination = $style->buildUri($image_uri);
          $style->createDerivative($image_uri, $destination);
        }
        else {
          $file_variables['width'] = $file_variables['height'] = NULL;
        }
      }

      $element['preview'] = array(
        '#weight' => -10,
        '#theme' => 'image_style',
        '#width' => $file_variables['width'],
        '#height' => $file_variables['height'],
        '#style_name' => $file_variables['style_name'],
        '#uri' => $file_variables['uri'],
      );

      // Store the dimensions in the form so the file doesn't have to be
      // accessed again. This is important for remote files.
      $element['width'] = array(
        '#type' => 'hidden',
        '#value' => $file_variables['width'],
      );
      $element['height'] = array(
        '#type' => 'hidden',
        '#value' => $file_variables['height'],
      );
    }

    $variables['data'] = array();
    foreach (\Drupal\Core\Render\Element::children($element) as $child) {
      $variables['data'][$child] = $element[$child];
    }
}

You should note that at the end of the name of the function, the name of the theme 'image_widget' is included, which tells Drupal to process your form element using the defined above function: [NameOfYourCustomModule]_preprocess_image_widget

What have I added?

  • The line at the

top:

 use Drupal\image\Entity\ImageStyle;
  • And the following four lines that actually create the image thumbnail inside the default temporary directory:

            $style = ImageStyle::load($file_variables['style_name']);
            $image_uri = $file->getFileUri();
            $destination = $style->buildUri($image_uri);
            $style->createDerivative($image_uri, $destination);
    

With the addition of these five lines I got it working!

riopiedra
  • 150
  • 1
  • 6
0

Nice answer thx, only change I'd make is using Token for upload location, and Bytes for file upload size, although I guess it depends on the use case. Something like the below (stripped out most of the code for the sake of simplicity.

namespace Drupal\my_module\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Component\Utility\Bytes;
use Drupal\Core\Utility\Token;

class SampleForm extends FormBase
{

  protected $currentUser;

  protected $token;


  public function __construct(AccountProxyInterface $current_user, Token $token) {
    $this->currentUser = $current_user;
    $this->token = $token;
  }


  public static function create(ContainerInterface $container)
  {
    return new static(
      $container->get('current_user'),
      $container->get('token')
    );
  }


  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {

    $form['sample_image'] = [
        '#type' => 'managed_file',
        '#title' => t('Profile Picture'),
        '#upload_validators' => array(
          'file_validate_extensions' => array('gif png jpg jpeg'),
          'file_validate_size' => file_upload_max_size() / pow(Bytes::KILOBYTE, 2) . 'M',
        ),
      '#theme' => 'image_widget',
      '#preview_image_style' => 'medium',
      '#upload_location' => $this->token->replace('private://[date:custom:Y]-[date:custom:m]'),
      '#required' => TRUE,
    ];

    return $form;
  }
}
user25794
  • 716
  • 7
  • 14
0

Theme it to work on multiple file upload. From @Je Suis Alrick answer above.

    function themename_preprocess_image_widget(&$variables) {
    $element = $variables['element'];

    $variables['attributes'] = array('class' => array('image-widget', 'js-form-managed-file', 'form-managed-file', 'clearfix'));
   
    if (!empty($element['fids']['#value'])) {

        foreach ($element['#files'] as $file) {
         
            $element['file_' . $file->id()]['filename']['#suffix'] = ' <span class="file-size">(' . format_size($file->getSize()) . ')</span> ';
            $file_variables = array(
                'style_name' => $element['#preview_image_style'],
                'uri' => $file->getFileUri(),
            );

            // Determine image dimensions.
            if (isset($element['#value']['width']) && isset($element['#value']['height'])) {
                $file_variables['width'] = $element['#value']['width'];
                $file_variables['height'] = $element['#value']['height'];
            } else {
                $image = \Drupal::service('image.factory')->get($file->getFileUri());
                if ($image->isValid()) {
                    $file_variables['width'] = $image->getWidth();
                    $file_variables['height'] = $image->getHeight();
                }
                else {
                    $file_variables['width'] = $file_variables['height'] = NULL;
                }
            }

            $element['preview'][] = array(
                '#weight' => -10,
                '#theme' => 'image_style',
                '#width' => $file_variables['width'],
                '#height' => $file_variables['height'],
                '#style_name' => $file_variables['style_name'],
                '#uri' => $file_variables['uri'],
            );

            // Store the dimensions in the form so the file doesn't have to be
            // accessed again. This is important for remote files.
            $element['width'] = array(
                '#type' => 'hidden',
                '#value' => $file_variables['width'],
            );
            $element['height'] = array(
                '#type' => 'hidden',
                '#value' => $file_variables['height'],
            );
        }
    }


    $variables['data'] = array();
    foreach (\Drupal\Core\Render\Element::children($element) as $child) {
        $variables['data'][$child] = $element[$child];
    }
}
mana
  • 1,075
  • 1
  • 24
  • 43