Проблема
Недавно я столкнулся с вполне, на мой взгляд, распространённой задачей: нужно обеспечить пользователю возможность загрузить на сервер любое число, скажем, картинок с комментарием к каждой из них в рамках одного интерфейса. В моём случае это было: фото товара, его описание и количество. Для наглядности прикладываю скриншот интерфейса:
Идея и алгоритм решения
Так как описания и фотографии для получения конечного результата можно изменять очень много раз, решено было осуществить следующую схему работы: фотографии загружаются на сервер на одной при клике на фото-иконку, при этом в случае успеха сервер возвращает имя картинки, а при неуспехе — «error». Соответственно, в случае успеха, фото-иконка заменяется на миниатюру загруженного фото, а в скрытое поле формы соответствующей строки сохраняется её имя, а при неуспехе мы получаем фото-иконку и пустое скрытое поле формы соответствующей строки, отвечающее за имя фото. Текстовая же информация при изменении любого поля формы отправляется на сервер вся в формате массив [имяФото, описаниеДетали, количествоШт] — это наиболее универсально: один и тот же метод отвечает за полное обновление списка товаров при их редактировании или удалении. Как известно, AJAX не умеет отправлять файлы, поэтому реализуем процедуру загрузки с помощью обычной формы, в качестве target которой укажем скрытый фрейм, который и будет перезагружаться вместо страницы.
Практическая реализация
Итак, в нашем распоряжении HTML, PHP и Javascript. Поехали:
1. Верстаем на странице форму для загрузки фото. Она содержит только один input, который мы спрячем с помощью css:
1 2 3 |
<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, который и будет перезагружаться в результате отправки формы с файлом:
1 |
<iframe id="hiddenframe" name="hiddenframe" style="width:0px;height:0px;border:0px"></iframe> |
3. Верстаем таблицу товаров, с которой и будет работать пользователь:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<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> |
В строке товара мы имеем:
1 |
<img src="photo_icon.png" alt="Загрузить фото" class="img-circle othdet_photo"/> |
— иконка, клик которой будет имитировать клик /> в форме загрузки фото,
1 |
<input class="form-control" type="hidden" name="dform_oth_details_photo[]" value=""/> |
— это наш скрытый input, отвечающий за имя фото,
1 |
<textarea name="dform_oth_details_descr[]" class="form-control" rows="3" placeholder="Описание детали"></textarea> |
— описание,
1 |
<input name="dform_oth_details_cnt[]" type="number" class="form-control" value="1" placeholder="шт"/> |
— количество штук.
3. Пишем PHP-код загрузки файла:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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, который будет контролировать весь процесс:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
$(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(); }); }); |
Вот тут объясню подробнее:
1 |
function rownumbers() |
— просто расставляет порядковые номера в таблице,
1 |
function update() |
— отправляет на сервер все текстовые поля нашей таблицы товаров. Их обработку в рамках этой статьи упоминать смысла нет,
1 2 |
var curimg = ''; var intervalID = ''; |
— указываем переменные, которые будут содержать ссылку на текущее фото и таймер. Подробнее об этом чуть позже;
1 |
function checkphotoname() |
— одна из основных функций: отвечает за обработку результата загрузки фото на основе содержимого нашего скрытого iframe. Эта функцию запускается с интервалом при отправке формы фото и заканчивает своё выполнение подстановкой миниатюры фото вместо фото иконки или выдачей сообщения об ошибке при обнаружении изменения содержимого iframe, то есть по окончании обработки фото сервером;
1 |
rownumbers(); |
— просто расставляет порядковые номера в таблице при загрузке страницы — я не включил в статью то, что эта страница используется как при первом добавлении товаров в список, так и при редактировании этого списка, то есть таблица товаров может быть изначально не пустой;
1 |
$(document).on('click', '.othdet_photo', function(){ ... |
— эмулируем клик input file в форме загрузки изображения при клике фото-иконки (так как пользователь может передумать использовать ранее загруженное фото, очищаем его перед выбором следующего);
1 |
$('#photoloader').change(function(){ ... |
— отправляем форму загрузки фото на сервер при изменении поля фото;
1 |
$('#othdetphotoform').submit(function(){ ... |
— вызываем с интервалом вышеописанную функцию обработки результата загрузки фото на сервер;
1 |
$(document).on('change', '.form-control', function() ... |
— при изменении любого поля (в том числе и имени фото) в списке товаров отправляем его весь на сервер;
1 |
$("#addnewdet").click(function(){ ... |
— добавляет строку в таблицу товаров;
1 |
$(document).on('click', '.removebtn', function(){ ... |
— удаляет строку из таблицы товаров.
Источник: https://habrahabr.ru/post/256197/
https://corp2.info/razrabotka-i-sozdanie-sajtov-internet-magazinov-veb-proektov-kiev-1038.html
Leave a Reply