Добрый день! Недавно для одного веб-приложения на 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