📜 ⬆️ ⬇️

SonataAdminBundle + AJAX file upload

All a pleasant time of day. In this article, I want to consider 2 ways of not quite the usual file uploads that I had to implement on one project. The task was this: it is necessary to implement Drag & Drop uploading files to the admin part of the site, which was placed on framefork'e Symfony 2.3. * + SonataAdminBundle. For several reasons, I omit the part in which the Sonata was set (if it becomes necessary, you can fill this gap). So, I believe that you have already installed the Sonata and created at least one entity in the folder Entity. If not, let's do it. Welcome under the cat:

// MyFolder / MyBundle / Entity / Name
<?php namespace MyFolder\MyBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** * Table * * @ORM\Table(name="table") * @ORM\Entity */ class Table { /** * @var integer * * @ORM\Column(name="id", type="integer") * @ORM\Id */ private $id; /** * @var string * * @ORM\Column(name="name", type="string", length=255, nullable=true) */ private $filePath; }     .   /: 


$ app / console doctrine: generate: entities MyFolder / MyBundle / Entity / Name

After the getters and setters have been generated, we proceed to the Sonata. So, the code of our sonat file will be like this:
')
// MyFolder / MyBundle / Admin / Name
 <?php namespace MyFolder\MyBundle\Admin; use Sonata\AdminBundle\Admin\Admin; use Sonata\AdminBundle\Datagrid\ListMapper; use Sonata\AdminBundle\Datagrid\DatagridMapper; use Sonata\AdminBundle\Form\FormMapper; use Sonata\AdminBundle\Show\ShowMapper; class NameAdmin extends Admin { protected function configureFormFields(FormMapper $formMapper) { $formMapper ->add('name'); } protected function configureDatagridFilters(DatagridMapper $datagridMapper) { $datagridMapper ->add('name') } protected function configureListFields(ListMapper $listMapper) { $listMapper ->addIdentifier('name') } } 


We don’t have to do anything else here. Let's stop for a minute and follow this link - github.com/weixiyen/jquery-filedrop . Here we are interested in the library, there is only one js file, so you won't miss :).

So. The most interesting begins, because we need to implement Drag & Drop, let's implement it. To do this, we will do the following, in the folder MyBundle / Resources / view / Admin (If this is not there, create confusion then there will be less) create the twig template file - sonata_admin_base_layout.html.twig with the following content:

// MyBundle / Resources / view / Admin / sonata_admin_base_layout.html.twig
 {% extends 'SonataAdminBundle::standard_layout.html.twig' %} {% block stylesheets %} {{ parent() }} <link href="{{ asset('bundles/mybundle/css/admin/style.css') }}" rel="stylesheet" type="text/css" /> {% endblock %} {% block javascripts %} {{ parent() }} <script src="{{ asset('bundles/mybundle/js/admin/jquery.filedrop.js') }}"></script> <script src="{{ asset('bundles/mybundle/js/admin/js.fileDropBlock.js') }}"></script> <script src="{{ asset('bundles/mybundle/js/admin/js.fileLoadByDefault.js') }}"></script> <script src="{{ asset('bundles/mybundle/js/admin/init.js') }}"></script> {% endblock %} 


After we go to config.yml and redefine the main template of the sonata
// app / config / config.yml
 sonata_admin: title: My Admin Panel templates: ## default global templates layout: MyFolderMyBundle:Admin:sonata_admin_base_layout.html.twig 


So, what we have done, we have redefined the main sonata pattern to be able to embed our files in it. Naturally, you could replace these 4 lines:

 <script src="{{ asset('bundles/mybundle/js/admin/jquery.filedrop.js') }}"></script> <script src="{{ asset('bundles/mybundle/js/admin/js.fileDropBlock.js') }}"></script> <script src="{{ asset('bundles/mybundle/js/admin/js.fileLoadByDefault.js') }}"></script> <script src="{{ asset('bundles/mybundle/js/admin/init.js') }}"></script> 


These files actually live in this way:

MyBundle / Resources / public / js / admin / jquery.filedrop.js
MyBundle / Resources / public / js / admin / js.fileDropBlock.js
MyBundle / Resources / public / js / admin / js.fileLoadByDefault.js
MyBundle / Resources / public / js / admin / js.init.js.

To note that everything would be correct, I strongly recommend that you create in the public folder an admin folder in it to create the files:

  1. js.fileDropBlock.js
  2. js.fileLoadByDefault.js
  3. init.js.


As you could understand, the first file from 4 is our previously downloaded from the gita, so do not be shy and throw it into the admin folder, and then make assets. In the console we type

$ app / console assets: install web --symlink if everything is correct, then a copy of your public folder should appear in your / bundle / mybundle / folder, appear? Drive on. Now for each file in order. So, the file number 1 (js.fileDropBlock.js) and its code:

// MyBundle / Resources / public / js / admin / js.fileDropBlock.js
 function fileDropBlock(block, type) { var allowType = { 'img': ['image/jpeg', 'image/png', 'image/gif'] }; block.filedrop({ url: '/upload-file', # url         paramname: 'file', # .     name  input  fallbackid: 'upload_button', maxfiles: 1, # -  maxfilesize: 2, #    mb #   .      error: function (err, file) { switch (err) { case 'BrowserNotSupported': console.log('Old browser'); break; case 'FileTooLarge': console.log('File Too Large'); break; case 'TooManyFiles': console.log('Only 1 file can be downloader'); break; case 'FileTypeNotAllowed': console.log('Wrong file type'); break; default: console.log('Some error'); } }, allowedfiletypes: allowType[type], #      dragOver: function () { block.addClass('active-drag-block'); }, dragLeave: function () { block._removeClass('active-drag-block'); }, uploadFinished: function (i, file, response) { block.find('input[type="text"]').val(response.filePath); #       } }) } 


file number 2 (js.fileLoadByDefault.js) and its code:
// MyBundle / Resources / public / js / admin / js.LoadByDefault.js

 var arrayType = { 'img': [ 'image/png', 'image/jpg', 'image/jpeg' ], 'pdf': [ 'application/pdf', 'application/x-pdf' ] }; function fileLoadByDefault(selector, type, block) { var input = document.getElementById(selector), formdata = false; input.click(); } 


Not much, is it? We need it later. So, finally, file number 3 (init.js) and its code:

// MyBundle / Resources / public / js / admin / init.js
 (function ($) { $(document).ready(function () { $.fn.uploadFile = function (type) { var blockText = { 'img': {'text': ['Drag Image File Here'], 'name': ['img'], 'id': ['imguploadform']} }; this.append('<p>' + blockText[type].text + '</p>'); this.append('<input type="file" class="upload-file" name="' + type + 'file" id="' + type + 'uploadform" data-type="'+ blockText[type].name +'">'); this.addClass('drag_n_drop--' + type + 'Path'); $('input', this).hide(); fileDropBlock(this, type); }; var imgBlock = $('div', 'div[id$="_coverPath"]'); imgBlock.uploadFile('img'); $('input[type="file"]').on("change", function () { var $_this = $(this), type = $_this.data('type'), reader, file; file = this.files[0]; if (window.FormData) { formdata = new FormData(); } if (window.FileReader) { reader = new FileReader(); reader.readAsDataURL(file); } if (formdata) { formdata.append("file", file); } if (!$.inArray(file.type, arrayType[type])) { $.ajax({ url: "/upload-file", type: "POST", data: formdata, processData: false, contentType: false, success: function (res) { var userData = jQuery.parseJSON(res); $_this.parent().find('input[type="text"]').val(userData.filePath); } }); } else { alert('Wrong type') } }); imgBlock.click(function () { fileLoadByDefault('imguploadform', 'img', this); }); }); })(jQuery); 


Let's clarify what is happening and analyze the code in parts. Previously, we had only one input on the page, and we need an area for Drag & Drop.

 $.fn.uploadFile = function (type) { var blockText = { 'img': { 'text': ['Drag Image File Here'], 'name': ['img'], 'id': ['imguploadform'] } }; this.append('<p>' + blockText[type].text + '</p>'); this.append('<input type="file" class="upload-file" name="' + type + 'file" id="' + type + 'uploadform" data-type="'+ blockText[type].name +'">'); this.addClass('drag_n_drop--' + type + 'Path'); $('input', this).hide(); fileDropBlock(this, type); }; 


the function which as the only parameter takes the type of the document that we are going to load (throw) into a specific area.

The function took it and returned to us, in fact, already a new html code. Which includes an abbreviation with the text:

 this.append('<p>' + blockText[type].text + '</p>'); 


The download button, it will then play an important role:

 this.append('<input type="file" class="upload-file" name="' + type + 'file" id="' + type + 'uploadform" data-type="'+ blockText[type].name +'">'); 


Add a class to the element to understand what it is

 this.addClass('drag_n_drop--' + type + 'Path'); 


And hide all the input:

 $('input', this).hide(); 


Add colors css:

// MyBundle / Resources / public / css / style.css
 .drag_n_drop--imgPath{ width: 150px; height: 100px; cursor: pointer; border: 2px solid #e0e0e0; background: #f9f9f9; } 


In the end, after all such manipulations, you should have something like this
image

If so, then all is well.

Further on the file init.js

Such code:

 var imgBlock = $('div', 'div[id$="_name"]'); #        imgBlock.uploadFile('img'); #     uploadFile 


Next, here is a small piece of code:

 $('input[type="file"]').on("change", function () { #   ,     var $_this = $(this), type = $_this.data('type'), reader, file; file = this.files[0]; #      file if (window.FormData) { formdata = new FormData(); #   (     js) } if (window.FileReader) { reader = new FileReader(); #   reader.readAsDataURL(file); } if (formdata) { formdata.append("file", file); #     } if (!$.inArray(file.type, arrayType[type])) { #        $.ajax({ url: "/upload-file", #    type: "POST", data: formdata, #     processData: false, contentType: false, success: function (res) { var userData = jQuery.parseJSON(res); #   $_this.parent().find('input[type="text"]').val(userData.filePath); #              } }); } else { alert('Wrong type'); #   alert   } }); 


Further, the code:

 imgBlock.click(function () { fileLoadByDefault('imguploadform', 'img', this); }); 


Here it is simple, when we click on our diva, which is designed to throw pictures at it, the fileLoadByDefault function in it triggers 3 arguments. 1 - input input id with file type. 2 - the type of file we want to download. 3 - the actual element itself is the parent for which the click occurred.

Actually here, the attentive reader might have noticed that, in fact, our code implements 2 methods of loading. The first is Drag & Drop (that's what they streamed to), and the second is a click on the container to call the standard form upload'a file, which is intended for Drup & Drop. Essentially 2 is a side effect, such a nice side effect.

I would not want to upset you, but we have done only half the work ... Then more fun, let's look at php now ?!

So, we remember that we refer to the link [/ upload-file] for any event, be it a Drop file or direct download.

We need to determine the route for this case:

// MyFolder / MyBundle / Resourses / config / rounting.yml
 my_file_upload: pattern: /upload-file defaults: { _controller: MyFolderMyBundle:Default:uploadFile } 


Take a look at the code of the [uploadFile] method:

// MyFolder / MuBundle / Contraller / Default.php
 <?php namespace MyFolder\MyBundle\Controller; use Symfony\Component\HttpFoundation\File\File; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\File\UploadedFile; class DefaultController extends Controller { public function uploadFileAction() { $filename = $_FILES['file']; #    $uploadPath = $this->upload($filename); #    /** *   .     */ return null === $uploadPath ? new Response(json_encode(array( 'status' => 0, 'message' => 'Wrong file type' ) ) ) : new Response(json_encode(array( 'status' => 1, 'message' => $filename, #   'filePath' => $uploadPath #     ) ) ); } private function getFoldersForUploadFile($type) { $fileType = $this->returnExistFileType($type); #       if ($fileType !== null) { return array( 'root_dir' => $this->container->getParameter('upload_' . $fileType . '_root_directory'), #       'dir' => $this->container->getParameter('upload_' . $fileType . '_directory'), #     ); } else { return null; } } #   ()     private function returnExistFileType($type) { $typeArray = array( 'img' => array( 'image/png', 'image/jpg', 'image/jpeg', ), 'pdf' => array( 'application/pdf', 'application/x-pdf', ) ); foreach ($typeArray as $key => $value) { if (in_array($type, $value)) { return $key; } } return null; } #     . ,  ,    private function upload($file) { $filePath = $this->getFoldersForUploadFile($file['type']); if (null === $this->getFileInfo($file['name']) || $filePath === null) { return null; } $pathInfo = $this->getFileInfo($file['name']); $path = $this->fileUniqueName() . '.' . $pathInfo['extension']; $this->uploadFileToFolder($file['tmp_name'], $path, $filePath['root_dir']); unset($file); return $filePath['dir'] . DIRECTORY_SEPARATOR . $path; } #       (    ) private function getFileInfo($file) { return $file !== null ? (array)pathinfo($file) : null; } #    private function fileUniqueName() { return sha1(uniqid(mt_rand(), true)); } #      private function uploadFileToFolder($tmpFile, $newFileName, $rootFolder) { $e = new File($tmpFile); $e->move($rootFolder, $newFileName); } 


As it turned out, the devil is not so bad. Perhaps I missed some point ... But you, dear reader, are free to write in the comments your questions, suggestions on the code and its optimization. Actually in this article I showed the easiest way to implement my bootloader in symfony. Naturally, these methods should be brought into service and called only him. Soon we will do it, but this is another story.

PS The path to the folder for downloading files looks like this

// app / config / config.yml
 parameters: upload_img_root_directory: %kernel.root_dir%/../web/upload/img upload_img_directory: upload/img 


Upload folders and img folders inside it are not there, you need to create them.

Also, you are free to write a method that will do it under the condition that these folders do not exist. Do not forget to set write permissions for them.

Source: https://habr.com/ru/post/199482/


All Articles