import jQuery.*; import js.html.*; enum Mode{ Single; Prompt; } enum Upload{ Empty; //Canceled; //Errored; Pending(file:File); Processing(file:File); Finished(file:File, response:Dynamic); } enum FileUploaderType{ Droparea(droparea:String); Selector(field:String, button:String); DropareaAndSelector(droparea:String, field:String, button:String); } typedef FileUploadParam = { action : String, method : String, query : String, } typedef Filter = { type : String, size : Int, } typedef Callback = { after_upload : String, } typedef Option = { filter : Filter, callback : Callback, } class FileUploader extends Havior { public static function main(){ Havior.start("system/file_uploader"); } public override function onLoad(){ var mode = Single; var type = DropareaAndSelector("#droparea","#file_field","#upload_button"); var param = { action: getParam("action"), method: getParam("method"), query: getParam("query"), }; var option:Option = { filter: { type: getParam("type_filter"), size: haxe.Json.parse( getParam("size_filter") ), }, callback: { after_upload: getParam("after_upload"), }, }; initialize(mode, type, param, option); } private var debug = true; private var mode : Mode; private var param : FileUploadParam; private var option : Option; private var request : XMLHttpRequest; private var queue : Array; private var results : Array< Upload >; private var uploading : Upload; public function initialize(mode: Mode, type:FileUploaderType, param:FileUploadParam, ?option:Option){ this.mode = mode; this.request = new XMLHttpRequest(); this.param = param; this.queue = new Array(); this.results = new Array< Upload >(); this.uploading = Empty; this.option = option; setRequestEvents(); setFileUploaderEvents(type); } private function setRequestEvents(){ this.request.upload.onprogress = function(ev){ var rate = (ev.loaded * 1.0) / ev.total; onProgressed(rate, ev.loaded, ev.total); }; this.request.upload.onload = function(ev){ onUploaded(); }; this.request.onreadystatechange = function(ev){ if(this.request.readyState == 4){ var json = haxe.Json.parse(this.request.responseText); finishUploading(json); onResponsed(json); callback(getOption(this.option,"callback.after_upload"), json); } }; } public function setFileUploaderEvents(type:FileUploaderType){ switch(type){ case Droparea(d): setDropareaEvents(d); case Selector(f,b): setSelectorEvents(f,b); case DropareaAndSelector(d,f,b): setDropareaEvents(d); setSelectorEvents(f,b); } } private function setDropareaEvents(droparea:String){ // for miss drop new JQuery(js.Browser.window).on("dragenter dragover dragexit dragleave drop", function(ev){ ev.stopPropagation(); ev.preventDefault(); }); var area = new JQuery(droparea); area.on("dragenter dragover", function(ev){ ev.stopPropagation(); ev.preventDefault(); if(mode==Single && this.uploading != Empty){ onEnteredForbidden(); }else{ onEnteredDropArea(); } }); area.on("dragexit dragleave drop", function(ev){ ev.stopPropagation(); ev.preventDefault(); if(mode==Single && this.uploading != Empty){ onExitedForbidden(); }else{ onExitedDropArea(); } }); area.on("drop", function(ev){ ev.stopPropagation(); ev.preventDefault(); var files = ev.originalEvent.dataTransfer.files; if(mode==Single && this.uploading != Empty){ onDroppedForbidden(files); }else{ onDroppedFiles(files); setFiles(files); } }); } private function setSelectorEvents(field:String, button:String){ new JQuery(button).click(function(ev){ ev.stopPropagation(); ev.preventDefault(); var files = cast(new JQuery(field).prop("files"), FileList); if(files.length > 0){ onPressedButton(files); setFiles(files); } }); } private function setFiles(files:FileList){ this.request.onload = function(ev){ }; // reset callback switch(this.mode){ case Single: sendFile(files.item(0)); case Prompt: enqueueFiles(files); if(this.uploading == Empty){ this.results = new Array< Upload >(); this.request.onload = function(ev){ promptUpload(); }; promptUpload(); } } } private function enqueueFiles(files:FileList){ for(file in files){ if(applyFilter(file)) queue.push(file); } refreshView(); } private function finishUploading(json: Dynamic){ switch(this.uploading){ case Processing(file): var x = Finished(file, json); this.results.push(x); this.uploading = Empty; refreshView(); default: } } // for Single private function sendFile(file:File){ if(this.uploading == Empty){ this.request.onload = function(ev){ onFinished(this.results); }; this.uploading = Pending(file); this.results = new Array< Upload >(); refreshView(); send(this.uploading); } } // for Prompt private function promptUpload(){ if(queue.length > 0){ this.uploading = Pending(queue.shift()); refreshView(); send(this.uploading); }else{ onFinished(this.results); } } // send a file private function send(file:Upload){ switch(file){ case Pending(f): this.uploading = Processing(f); this.request.open(this.param.method, this.param.action, true); var token = new JQuery("meta[name=csrf-token]").attr("content"); this.request.setRequestHeader("X-CSRF-Token", token); var query = this.param.query; var fd = buildFormData([{name:query, value:f}]); trace(fd); this.request.send(fd); default: } } private function buildFormData(xs:Array<{name:String,value:Dynamic}>){ var fd = untyped __js__("new FormData()"); untyped __js__("for(var x of xs){ fd.append(x.name, x.value); }"); return fd; } // filters private function applyFilter(file:File){ var fs = [ typeFilter, sizeFilter, customFilter ]; for(f in fs){ if(!f(file)) return false; } return true; } private function typeFilter(file:File){ var type = getOption(this.option, "filter.type"); if(type != null){ var r = new EReg(type, ""); if(!r.match(file.type) ){ return false; } } return true; } private function sizeFilter(file:File){ var size = getOption(this.option, "filter.size"); if(size != null){ return file.size <= size; } return true; } private function refreshView(){ refresh(this.results, this.uploading, this.queue); } /// callbacks public function refresh(results:Array, uploading:Upload, queue:Array){ if(debug) trace("refresh view"); } public function customFilter(file:File){ if(debug) trace("apply custom filter"); return true; } public function onProgressed(rate:Float, loaded:Int, total:Int){ if(debug) trace("progress:" + (Math.ceil(rate*100)) + "%"); children("#status").html(Math.ceil(rate*100) + "%"); } public function onUploaded(){ if(debug) trace("uploaded, now saving."); } public function onResponsed(response:Dynamic){ if(debug) { trace("responsed:"); trace(response); } if(response.result == "success"){ children("#image").html(""); } } public function onFinished(results:Array< Upload >){ if(debug) { trace("finished:"); trace(results); } } public function onEnteredDropArea(){ if(debug) trace("enterd"); } public function onExitedDropArea(){ if(debug) trace("exited"); } public function onDroppedFiles(files:FileList){ if(debug){ trace("dropped"); trace(files); } } public function onPressedButton(files:FileList){ if(debug) trace("pressed button"); } public function onEnteredForbidden(){ if(debug) trace("forbidden entered"); } public function onExitedForbidden(){ if(debug) trace("forbidden exited"); } public function onDroppedForbidden(files:FileList){ if(debug){ trace("forbidden dropped"); trace(files); } } }