Пошаговая инструкция по реализации загрузки файлов на сервер без перезагрузки страницы на PHP + Javascript

от автора


Проблема

Недавно я столкнулся с вполне, на мой взгляд, распространённой задачей: нужно обеспечить пользователю возможность загрузить на сервер любое число, скажем, картинок с комментарием к каждой из них в рамках одного интерфейса. В моём случае это было: фото товара, его описание и количество. Для наглядности прикладываю скриншот интерфейса:


Идея и алгоритм решения

Так как описания и фотографии для получения конечного результата можно изменять очень много раз, решено было осуществить следующую схему работы: фотографии загружаются на сервер на одной при клике на фото-иконку, при этом в случае успеха сервер возвращает имя картинки, а при неуспехе — «error». Соответственно, в случае успеха, фото-иконка заменяется на миниатюру загруженного фото, а в скрытое поле формы соответствующей строки сохраняется её имя, а при неуспехе мы получаем фото-иконку и пустое скрытое поле формы соответствующей строки, отвечающее за имя фото. Текстовая же информация при изменении любого поля формы отправляется на сервер вся в формате массив [имяФото, описаниеДетали, количествоШт] — это наиболее универсально: один и тот же метод отвечает за полное обновление списка товаров при их редактировании или удалении. Как известно, AJAX не умеет отправлять файлы, поэтому реализуем процедуру загрузки с помощью обычной формы, в качестве target которой укажем скрытый фрейм, который и будет перезагружаться вместо страницы.

Практическая реализация

Итак, в нашем распоряжении HTML, PHP и Javascript. Поехали:

1. Верстаем на странице форму для загрузки фото. Она содержит только один input, который мы спрячем с помощью css:

<form enctype="multipart/form-data" action="<?=site_url('otherdetails/uploadOthDetPhoto')?>" method="post" id="othdetphotoform" target="hiddenframe"> <input type="file" id="photoloader" name="photo"/> </form> 

2. Создадим на странице скрытый iframe, который и будет перезагружаться в результате отправки формы с файлом:

<iframe id="hiddenframe" name="hiddenframe" style="width:0px;height:0px;border:0px"></iframe> 

3. Верстаем таблицу товаров, с которой и будет работать пользователь:

<table id="othdetails" class="table table-hover borderbottom">     <thead>         <tr>             <th>№ п/п</th>             <th>Фото</th>             <th>Описание</th>             <th>шт</th>             <th></th>         </tr>     </thead>     <tbody>         <tr>             <td class="col-xs-2 col-md-1" style="vertical-align:middle;"></td>             <td style="width:110px;">                 <img src="photo_icon.png" alt="Загрузить фото" class="img-circle othdet_photo"/>                 <input class="form-control" type="hidden" name="dform_oth_details_photo[]" value=""/>             </td>             <td><textarea name="dform_oth_details_descr[]" class="form-control" rows="3" placeholder="Описание детали"></textarea></td>             <td class="col-xs-2 col-md-1" style="vertical-align:middle;"><input name="dform_oth_details_cnt[]" type="number" class="form-control" value="1" placeholder="шт"/></td>             <td style="vertical-align:middle;text-align:right;"><span class="glyphicon glyphicon-remove removebtn" aria-hidden="true"></span></td>         </tr>     </tbody> </table> 

В строке товара мы имеем:

  • <img src="photo_icon.png" alt="Загрузить фото" class="img-circle othdet_photo"/> 

    — иконка, клик которой будет имитировать клик /> в форме загрузки фото,

  • <input class="form-control" type="hidden" name="dform_oth_details_photo[]" value=""/>

    — это наш скрытый input, отвечающий за имя фото,

  • <textarea name="dform_oth_details_descr[]" class="form-control" rows="3" placeholder="Описание детали"></textarea> 

    — описание,

  • <input name="dform_oth_details_cnt[]" type="number" class="form-control" value="1" placeholder="шт"/>

    — количество штук.

3. Пишем PHP-код загрузки файла:

public function uploadOthDetPhoto()     {         $this->isDformSet();         $config['upload_path'] = 'public/uploads/othdet/';         $config['allowed_types'] = 'gif|jpg|png';         $config['max_size'] = '10240';         $config['encrypt_name'] = true;         $this->load->library('upload', $config);         $this->upload->initialize($config);         $this->upload->do_upload('photo');         $arrErrors = $this->upload->display_errors();         if (!empty($arrErrors) > 0){             echo 'error';         }else{             $arrPhotoData = $this->upload->data();             $strFileName = $arrPhotoData['file_name'];             echo $strFileName;             $intMaxWidth = 800;             if ($arrPhotoData['image_width'] > $intMaxWidth){                 unset($config);                 $config['image_library'] = 'gd2';                 $config['source_image']	= $arrPhotoData['full_path'];                 $config['width'] = $intMaxWidth;                 $config['height'] = $intMaxWidth*$arrPhotoData['image_height']/$arrPhotoData['image_width'];                 $config['maintain_ratio'] = TRUE;                 $this->load->library('image_lib', $config);                  $this->image_lib->resize();             }         }     } 

Мой проект на CodeIgniter, поэтому код вот такой, но в целом, суть в следующем: мы просто загружаем и переименовываем полученный из формы файл, если всё проходит успешно, выводим в наш iframe его имя, если нет — «error».

4. Пишем Javascript, который будет контролировать весь процесс:

$(function(){     function rownumbers(){         $('#othdetails tbody tr').each(function(i) {             var number = i + 1;             $(this).find('td:first').text(number);         });     }          function update(){         var url = '<?=site_url('otherdetails/updateOthDet')?>';         var postData = $('.form-control').serialize();         $.post(url, postData, function(){}, 'json');     }          var curimg = '';     var intervalID = '';          function checkphotoname() {         var linkedFrame = document.getElementById('hiddenframe');         var content = linkedFrame.contentWindow.document.body.innerHTML;         var completed = false;         if (content === 'error'){             curimg.attr('src','<?=base_url()?>public/img/photo_icon.png');             $('#error').show(200).delay(6000).hide(200);             $('#error').html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> Фото не было загружено. Максимальный размер фото - 10 Мб, форматы: jpg,png,gif.');             completed = true;         }         else         if (content !== ''){             curimg.parent().children('.form-control').val(content);             curimg.attr('src','<?=base_url()?>public/uploads/othdet/' + content);                 completed = true;         }         if (completed === true){             update();             clearInterval(intervalID);             $('#hiddenframe').contents().find('body').html('');         }     }          rownumbers();          $(document).on('click', '.othdet_photo', function(){         $('#hiddenframe').contents().find('body').html('');         curimg = $(this);         curimg.attr('src','<?=base_url()?>public/img/photo_icon.png');         curimg.parent().children('.form-control').val('');         update();         $('#photoloader').click();     });          $('#photoloader').change(function(){         curimg.attr('src','<?=base_url()?>public/img/indicator.gif');         $('#othdetphotoform').submit();         $('#photoloader').val('');     });          $('#othdetphotoform').submit(function(){         intervalID = setInterval(checkphotoname, 500);     });          $(document).on('change', '.form-control', function(){         update();     });          $("#addnewdet").click(function(){         var emp = 0;         $(".form-control[name!='dform_oth_details_photo[]']").each(function(indx){             if ($(this).val() === ''){                 emp = 1;                 $(this).focus();              }             });             if (emp === 1){             $('#error').show(200).delay(2000).hide(200);             $('#error').html('<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> Пожалуйста, заполните все поля');         }else{             var row = '<tr><td class="col-xs-2 col-md-1" style="vertical-align:middle;"></td><td style="width:110px;"><img src="<?=base_url()?>public/img/photo_icon.png" alt="Загрузить фото" class="img-circle othdet_photo"/><input class="form-control" type="hidden" name="dform_oth_details_photo[]" value=""/></td><td><textarea name="dform_oth_details_descr[]" class="form-control" rows="3" placeholder="Описание детали"></textarea></td><td class="col-xs-2 col-md-1" style="vertical-align:middle;"><input name="dform_oth_details_cnt[]" type="number" class="form-control" value="1" placeholder="шт"/></td><td style="vertical-align:middle;text-align:right;"><span class="glyphicon glyphicon-remove removebtn" aria-hidden="true"></span></td></tr>';             $("#othdetails tbody").append(row);             rownumbers();             $('[name="dform_oth_details_descr[]"]').last().focus();         }         });          $(document).on('click', '.removebtn', function(){         $(this).parent().parent().remove();         update();         rownumbers();     });      }); 

Вот тут объясню подробнее:

  • function rownumbers()

    — просто расставляет порядковые номера в таблице,

  • function update()

    — отправляет на сервер все текстовые поля нашей таблицы товаров. Их обработку в рамках этой статьи упоминать смысла нет,

  • var curimg = '';     var intervalID = '';

    — указываем переменные, которые будут содержать ссылку на текущее фото и таймер. Подробнее об этом чуть позже;

  • function checkphotoname() 

    — одна из основных функций: отвечает за обработку результата загрузки фото на основе содержимого нашего скрытого iframe. Эта функцию запускается с интервалом при отправке формы фото и заканчивает своё выполнение подстановкой миниатюры фото вместо фото иконки или выдачей сообщения об ошибке при обнаружении изменения содержимого iframe, то есть по окончании обработки фото сервером;

  • rownumbers();

    — просто расставляет порядковые номера в таблице при загрузке страницы — я не включил в статью то, что эта страница используется как при первом добавлении товаров в список, так и при редактировании этого списка, то есть таблица товаров может быть изначально не пустой;

  • $(document).on('click', '.othdet_photo', function(){ ...

    — эмулируем клик input file в форме загрузки изображения при клике фото-иконки (так как пользователь может передумать использовать ранее загруженное фото, очищаем его перед выбором следующего);

  • $('#photoloader').change(function(){ ...

    — отправляем форму загрузки фото на сервер при изменении поля фото;

  • $('#othdetphotoform').submit(function(){ ...

    — вызываем с интервалом вышеописанную функцию обработки результата загрузки фото на сервер;

  • $(document).on('change', '.form-control', function() ...

    — при изменении любого поля (в том числе и имени фото) в списке товаров отправляем его весь на сервер;

  • $("#addnewdet").click(function(){ ...

    — добавляет строку в таблицу товаров;

  • $(document).on('click', '.removebtn', function(){ ...

    — удаляет строку из таблицы товаров.

Вот, собственно, и весь процесс.

Если возникнет такая необходимость, сделаю демо процесса отдельным блоком и прикреплю сюда ссылку, а так же дам исходники.

Спасибо за прочтение!

ссылка на оригинал статьи http://habrahabr.ru/post/256197/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *