Веб-разработка / Drag’n’drop файлов во Flex, используя HTML5 File API

в 14:15, , рубрики: drag and drop, flex, html5 file api, метки: , ,

Добрый день! Недавно для одного веб-приложения на Flex'e потребовалось сделать drag'n'drop загрузку фотографий. Flash не позволяет напрямую это реализовать, хотя в приложениях AIR такая фунциональность присутствует. Для решения задачи потребовалось применить HTML5 File API.

Таким образом решение задачи разбивается на несколько этапов. Первый этап — обработка drag'n'drop файлов с помощью File API. Все загруженные файлы добавляются в список из которого потом будут передаваться во Flash.

  $(document).ready(function() {      var dropZone = $('div#dropZone');      // Проверка поддержки браузером      if (typeof(window.FileReader) == 'undefined') {          dropZone.text('Не поддерживается браузером!');          dropZone.addClass('error');      }      // Обрабатываем событие Drop      dropZone[0].ondrop = function(event)       {          event.preventDefault();          dropZone.removeClass('hover');          dropZone.addClass('drop');                 displayFiles(event.dataTransfer.files);              };  });  //--------------------------------------------------------------------------------------------------  var fileList = new Array();  function displayFiles(files)   {       $.each(files, function(i, file)       {              var imgList = $('ul#img-list');          var maxFileSize = 20*1000000; // максимальный размер фалйа - 1 мб.            if (!file.type.match(/image.*/))           {            // Отсеиваем не картинки            return true;          }                  // Создаем элемент li и помещаем в него название, миниатюру          // а также создаем ему свойство file, куда помещаем объект File (при загрузке понадобится)              var li = $('<li/>').appendTo(imgList);          $('<div/>').text(file.name).appendTo(li);                  // Проверяем размер файла          if (file.size > maxFileSize) {              $('<div/>').text('Файл слишком большой!').appendTo(li);          }          else          {              var img = $('<img/>').appendTo(li);                  li.file = file;                // Создаем объект FileReader и по завершении чтения файла, отображаем миниатюру и обновляем              // инфу обо всех файлах                 var reader = new FileReader();                    reader.onload = (function(aImg, file) {                  return function(e) {                  aImg.attr('src', e.target.result);                  aImg.attr('width', 75);  		                  file.f_data = e.target.result;                  //добавляем файл в список файлов на передачу                  fileList.push(file);                        };              })(img, file);                    reader.readAsDataURL(file);           }      });  }  //--------------------------------------------------------------------------------------------------  

HTML код для вставки flex приложения, а так же div, в который мы и будем скидывать файлы.

  <div style="float:left">  </div>  <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="https://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=10,0,0,0" id="web-client" width="800px" align="middle" height="600px">  <param name="allowScriptAccess" value="sameDomain">  <param name="allowFullScreen" value="false">  <param name="movie" value="dd_test.swf"><param name="quality" value="high"><param name="bgcolor" value="#cccccc"><embed src="test.swf" quality="high" bgcolor="#cccccc" name="web-client" allowscriptaccess="sameDomain" allowfullscreen="false" type="application/x-shockwave-flash" pluginspage="https://www.adobe.com/go/getflashplayer" width="800" align="middle" height="600">  </object>    <div id="dropzone" style="float:left">      <p>Для загрузки, перетащите файл сюда.</p>      <ul id="img-list"></ul>  </div>  


Второй этап — передача загруженного файла во Flash. Для связи с внешним миром во Flash есть класс ExternalInterface, он позволяет вызывать JS функции, а так же делать доступными для вызова Flash функции. К сожалению вызов функций из Flash у меня стабильно не заработал, поэтому пришлось запускать внутри таймер и вызывать JS функцию для передачи уже загруженных файлов.
Основной проблемой стала форма передачи фотографии. Внутри Flash'a мне было удобно работать с фотографией как с объектом класса ByteArray. Так как в JS нет аналога класса ByteArray, то пришлось при передачи использовать дополнительное преобразование данных в base64. В File API есть соответсвующая функция — readAsDataURL(), которая возвращает файл в виде строки в кодировке base64. Полученную строку мы передаем во flash и с помощью Base64Decoder получаем объект класса ByteArray, содержащий нашу исходную фотографию.

  //код Flex приложения, ответсвенный за приём файла  var dragTimer:Timer;  var file_count_already_have:int;  			  //Вызвать из флеш ф-ции из JS не удалось, будем опрашивть JS ф-ции по таймеру  public function init_draginteface():void  {  	dragTimer = new Timer(3*1000,0);	    //раз в 3 секунды  	dragTimer.addEventListener(TimerEvent.TIMER, dragTimerEvent);  	dragTimer.start();  	file_count_already_have = 0;	  }  //---------------------------------------------------------------------------  public function dragTimerEvent(e:TimerEvent):void  {  	dragTimer.stop();  	if(ExternalInterface.available)  	{  		var file_count:int = ExternalInterface.call("dropFileCount");    //Вызов JS ф-ции, которая возращает количество загруженных файлов  		if (file_count>0)  		{  			for(var i:int = file_count_already_have ; i<file_count; i++)  			{  				dragImageFromJS(i);  			}	  			file_count_already_have = file_count;  		}  		dragTimer.start();  	}  	  }  //---------------------------------------------------------------------------  public function dragImageFromJS(i:int):void  {  	var file_size:int = ExternalInterface.call("getFileSize", i);  // Получаем размер файл   	var file_name:String = ExternalInterface.call("getFileName", i);  // Его имя  	var file_ba:ByteArray = new ByteArray;  	var base64dec:Base64Decoder = new Base64Decoder();  				  	var start_load:uint = getTimer();  	base64dec.decode(ExternalInterface.call("getFile", i));    	//и собственно сам файл перекодированный в строку base64  	var load_time:uint = getTimer() - start_load;  			  	file_ba = base64dec.toByteArray();  				  	if(file_ba != null)  	{  		trace("");  		trace("add new item [file_name: "+file_name+"] [file_size:"+file_size.toString()+"] [fr_ba_size: "+file_ba.length + "] [load_time: "+load_time.toString()+"]");  		  		loadFromByteArray(file_ba);  //отрисовываем изображение внутри Flex приложения  	}  }  


  //JS ф-ции, которые вызываются из Flex приложения  function dropFileCount() //возращает количество файлов      {          return fileList.length;      }  //--------------------------------------------------------------------------------------------------  function getFile(i)  //сами файлы в виде строки       {          var data = fileList[i].f_data;          var data_cat = data.substr(23);          return data_cat;      }  //В начале строки содержится информация, которую необходимо отрезать, её длина постоянная  //data:image/jpeg;base64,/9j/4U3ARXhpZgAASUkq  //--------------------------------------------------------------------------------------------------  function getFileSize(i)      {          return fileList[i].size;      }  //--------------------------------------------------------------------------------------------------  function getFileName(i)      {          return fileList[i].name;      }  //--------------------------------------------------------------------------------------------------  


В итоге мы получаем drag'n'drop загрузку фотографий во Flex приложение. Небольшой кусочек кода для вывода фотографий из ByteArray.

  <mx:Script>  <![CDATA[  //код Flex приложения отвественный за вывод изображения из ByteArray  public function loadFromByteArray(data:ByteArray):void  {  	_loader = new Loader();  	_loader.contentLoaderInfo.addEventListener(Event.COMPLETE,_load_loader_complete);  	_loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, errorOnLoad);  	_loader.loadBytes(data);  }    public function _load_loader_complete(e:Event):void  {  	_loader.removeEventListener(Event.COMPLETE,_load_loader_complete);  	var bitmapIm:Bitmap = Bitmap(e.target.content);  	this.itemIm.addChild(bitmapIm);  }  ]]>  </mx:Script>    <mx:Image source="" id="itemIm" maxWidth="100" maxHeight="100" horizontalAlign="left" verticalAlign="top"/>  


У данного решения есть один существенный минус, при загрузке фотографий большого размера, браузер ощутимо нагружает процессор. Возможно есть более оптимальный способ, но я его не нашел.

Автор: Horus20

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js