AJAXUpload Java Web非同期アップロードファイル


前のプロジェクトは異歩アップロードを使いました。そしてネットで関連資料を探し始めました。最初にAJAX FileUpload.jsを見つけました。
しかし、AJAX FileUpload.jsの最大の問題は対応バージョンのjqueryが必要ですが、プロジェクトの中のjqueryはまだ早いjquery 1.3.1ですので、使えません。そして最新バージョンを探し始めましたが、公式サイトがなくなっています。
周を探してAJAXUploadを見つけましたが、ネット上ではAJAXUploadのPHPの下の応用例しかありません。javaの下の応用例についてはあまりありません。
一時期の調整を経て、javaでどうやって使うかが分かりましたので、メモしておきます。
まず、jspのアップロード部分のコードです。
<script type="text/javascript" src="<%=path%>/js/jquery.js"></script>
<script type="text/javascript" src="<%=path%>/js/ajaxupload.js"></script>
<div >
       <input type="hidden" name="mt.cover" id="cover"/>
       <input type="button" value="    " id="upload_button">
</div>
AJAXUploadはtype=fileは必要ありません。一つのbuttonだけでいいです。
jsコード
$(document).ready(function(){
        var button = $('#upload_button');  //           ,       button 
        var fileType = "png",fileNum = "one";   //           ,       onSubmit  js     
        new AjaxUpload(button,{  
            action: '<%=path%>/materialAction/uploadPicture.action',  
            name: 'cover',   //    <input type = "cover" name = "cover"/> 
            onSubmit : function(file, ext){  
            if (!(ext && /^(jpg|JPG|png|PNG|gif|GIF)$/.test(ext))) {
                alert("          ,     !");
                        return false;
                 }
            },
            onComplete: function(file, response){ //         
                   var rtn = response.replace('<pre>', '').replace('</pre>', '');
                   rtn = eval("("+rtn+")");
                   if(rtn.mess==1||rtn.mess=="1"){
                       alert("      ");
                       $("#cover").attr("value",rtn.img);
                   }else{
                       alert("      ,        ");
                   }

            }  
        });  
    });
注意してください。var fileType="png"fileNum="one"ここで定義されているPNGは機能しません。任意に定義されています。役割を果たしているのはonSubmitの中の方法制限のフォーマットです。
そしてactionです
public String uploadPicture() {
        Map<String, String> mess = new HashMap<String, String>();

        if (coverFileName != null) {
        // String filepath = ServletActionContext.getServletContext().getRealPath("/upload");//     
            String filepath = SystemConstUtil.uploadRootPath + "upload";//      

            FileOutputStream fos = null;
            FileInputStream fis = null;
            try {
                Date dt=new Date();
                SimpleDateFormat format=new SimpleDateFormat("yyyyMMdd");
                filepath+="/"+format.format(dt);
                String localpath = "/" + UUID.randomUUID().toString().replaceAll("-", "") + coverFileName.substring(coverFileName.lastIndexOf("."));
                String coverpath = filepath + localpath;

                //          ,      
                File f = new File(filepath);
                if (!f.exists()) {
                    f.mkdirs();
                }
                fos = new FileOutputStream(coverpath);
                mess.put("img", ("upload" +"/"+format.format(dt)+ localpath));
                mess.put("mess", "1");
                fis = new FileInputStream(cover);
                byte[] buffer = new byte[1024];
                int len = 0;
                while ((len = fis.read(buffer)) > 0) {
                    fos.write(buffer, 0, len);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                close(fos, fis);
            }
        }else{
            mess.put("img", "");
            mess.put("mess", "0");
        }
        String json = new Gson().toJson(mess);
        HttpServletResponse response = ServletActionContext.getResponse();
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/html");
        try {
            response.getWriter().print(json);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
注意:レスポンス.set ContentType(「text/html」)この文は、AJAXUploadでは、レスポンスタイプをtext/htmlとして設定し、jsonまたは他の設定では、AJAXUploadが正しく解析できなくなります。
最後はAJAXUpload.jsのソースコードです。
    /** * AJAX Upload ( http://valums.com/ajax-upload/ ) * Copyright (c) Andris Valums * Licensed under the MIT license ( http://valums.com/mit-license/ ) * Thanks to Gary Haran, David Mark, Corey Burns and others for contributions */  
    (function () {  
        /* global window */  
        /* jslint browser: true, devel: true, undef: true, nomen: true, bitwise: true, regexp: true, newcap: true, immed: true */  

        /** * Wrapper for FireBug's console.log */  

        function log() {  
            if (typeof(console) != 'undefined' && typeof(console.log) == 'function') {  
                Array.prototype.unshift.call(arguments, '[Ajax Upload]');  
                console.log(Array.prototype.join.call(arguments, ' '));  
            }  
        }  

        /** * Attaches event to a dom element. * @param {Element} el * @param type event name * @param fn callback This refers to the passed element */  

        function addEvent(el, type, fn) {  
            if (el.addEventListener) {  
                el.addEventListener(type, fn, false);  
            } else if (el.attachEvent) {  
                el.attachEvent('on' + type, function () {  
                    fn.call(el);  
                });  
            } else {  
                throw new Error('not supported or DOM not loaded');  
            }  
        }  

        /** * Attaches resize event to a window, limiting * number of event fired. Fires only when encounteres * delay of 100 after series of events. * * Some browsers fire event multiple times when resizing * http://www.quirksmode.org/dom/events/resize.html * * @param fn callback This refers to the passed element */  

        function addResizeEvent(fn) {  
            var timeout;  

            addEvent(window, 'resize', function () {  
                if (timeout) {  
                    clearTimeout(timeout);  
                }  
                timeout = setTimeout(fn, 100);  
            });  
        }  

        // Needs more testing, will be rewriten for next version 
        // getOffset function copied from jQuery lib (http://jquery.com/) 
        if (document.documentElement.getBoundingClientRect) {  
            // Get Offset using getBoundingClientRect 
            // http://ejohn.org/blog/getboundingclientrect-is-awesome/ 
            var getOffset = function (el) {  
                var box = el.getBoundingClientRect();  
                var doc = el.ownerDocument;  
                var body = doc.body;  
                var docElem = doc.documentElement; // for ie 
                var clientTop = docElem.clientTop || body.clientTop || 0;  
                var clientLeft = docElem.clientLeft || body.clientLeft || 0;  

                // In Internet Explorer 7 getBoundingClientRect property is treated as physical, 
                // while others are logical. Make all logical, like in IE8. 
                var zoom = 1;  
                if (body.getBoundingClientRect) {  
                    var bound = body.getBoundingClientRect();  
                    zoom = (bound.right - bound.left) / body.clientWidth;  
                }  

                if (zoom > 1) {  
                    clientTop = 0;  
                    clientLeft = 0;  
                }  

                var top = box.top / zoom + (window.pageYOffset || docElem && docElem.scrollTop / zoom || body.scrollTop / zoom) - clientTop,  
                    left = box.left / zoom + (window.pageXOffset || docElem && docElem.scrollLeft / zoom || body.scrollLeft / zoom) - clientLeft;  

                return {  
                    top: top,  
                    left: left  
                };  
            };  
        } else {  
            // Get offset adding all offsets 
            var getOffset = function (el) {  
                var top = 0,  
                    left = 0;  
                do {  
                    top += el.offsetTop || 0;  
                    left += el.offsetLeft || 0;  
                    el = el.offsetParent;  
                } while (el);  

                return {  
                    left: left,  
                    top: top  
                };  
            };  
        }  

        /** * Returns left, top, right and bottom properties describing the border-box, * in pixels, with the top-left relative to the body * @param {Element} el * @return {Object} Contains left, top, right,bottom */  

        function getBox(el) {  
            var left, right, top, bottom;  
            var offset = getOffset(el);  
            left = offset.left;  
            top = offset.top;  

            right = left + el.offsetWidth;  
            bottom = top + el.offsetHeight;  

            return {  
                left: left,  
                right: right,  
                top: top,  
                bottom: bottom  
            };  
        }  

        /** * Helper that takes object literal * and add all properties to element.style * @param {Element} el * @param {Object} styles */  

        function addStyles(el, styles) {  
            for (var name in styles) {  
                if (styles.hasOwnProperty(name)) {  
                    el.style[name] = styles[name];  
                }  
            }  
        }  

        /** * Function places an absolutely positioned * element on top of the specified element * copying position and dimentions. * @param {Element} from * @param {Element} to */  

        function copyLayout(from, to) {  
            var box = getBox(from);  

            addStyles(to, {  
                position: 'absolute',  
                left: box.left + 'px',  
                top: box.top + 'px',  
                width: from.offsetWidth + 'px',  
                height: from.offsetHeight + 'px'  
            });  
        }  

        /** * Creates and returns element from html chunk * Uses innerHTML to create an element */  
        var toElement = (function () {  
            var div = document.createElement('div');  
            return function (html) {  
                div.innerHTML = html;  
                var el = div.firstChild;  
                return div.removeChild(el);  
            };  
        })();  

        /** * Function generates unique id * @return unique id */  
        var getUID = (function () {  
            var id = 0;  
            return function () {  
                return 'ValumsAjaxUpload' + id++;  
            };  
        })();  

        /** * Get file name from path * @param {String} file path to file * @return filename */  

        function fileFromPath(file) {  
            return file.replace(/.*(\/|\\)/, "");  
        }  

        /** * Get file extension lowercase * @param {String} file name * @return file extenstion */  

        function getExt(file) {  
            return (-1 !== file.indexOf('.')) ? file.replace(/.*[.]/, '') : '';  
        }  

        function hasClass(el, name) {  
            var re = new RegExp('\\b' + name + '\\b');  
            return re.test(el.className);  
        }  

        function addClass(el, name) {  
            if (!hasClass(el, name)) {  
                el.className += ' ' + name;  
            }  
        }  

        function removeClass(el, name) {  
            var re = new RegExp('\\b' + name + '\\b');  
            el.className = el.className.replace(re, '');  
        }  

        function removeNode(el) {  
            el.parentNode.removeChild(el);  
        }  

        /** * Easy styling and uploading * @constructor * @param button An element you want convert to * upload button. Tested dimentions up to 500x500px * @param {Object} options See defaults below. */  
        window.AjaxUpload = function (button, options) {  
            this._settings = {  
                // Location of the server-side upload script 
                action: 'upload.php',  
                // File upload name 
                name: 'userfile',  
                // Additional data to send 
                data: {},  
                // Submit file as soon as it's selected 
                autoSubmit: true,  
                // The type of data that you're expecting back from the server. 
                // html and xml are detected automatically. 
                // Only useful when you are using json data as a response. 
                // Set to "json" in that case. 
                responseType: false,  
                // Class applied to button when mouse is hovered 
                hoverClass: 'hover',  
                // Class applied to button when AU is disabled 
                disabledClass: 'disabled',  
                // When user selects a file, useful with autoSubmit disabled 
                // You can return false to cancel upload 
                onChange: function (file, extension) {},  
                // Callback to fire before file is uploaded 
                // You can return false to cancel upload 
                onSubmit: function (file, extension) {},  
                // Fired when file upload is completed 
                // WARNING! DO NOT USE "FALSE" STRING AS A RESPONSE! 
                onComplete: function (file, response) {}  
            };  

            // Merge the users options with our defaults 
            for (var i in options) {  
                if (options.hasOwnProperty(i)) {  
                    this._settings[i] = options[i];  
                }  
            }  

            // button isn't necessary a dom element 
            if (button.jquery) {  
                // jQuery object was passed 
                button = button[0];  
            } else if (typeof button == "string") {  
                if (/^#.*/.test(button)) {  
                    // If jQuery user passes #elementId don't break it 
                    button = button.slice(1);  
                }  

                button = document.getElementById(button);  
            }  

            if (!button || button.nodeType !== 1) {  
                throw new Error("Please make sure that you're passing a valid element");  
            }  

            if (button.nodeName.toUpperCase() == 'A') {  
                // disable link 
                addEvent(button, 'click', function (e) {  
                    if (e && e.preventDefault) {  
                        e.preventDefault();  
                    } else if (window.event) {  
                        window.event.returnValue = false;  
                    }  
                });  
            }  

            // DOM element 
            this._button = button;  
            // DOM element 
            this._input = null;  
            // If disabled clicking on button won't do anything 
            this._disabled = false;  

            // if the button was disabled before refresh if will remain 
            // disabled in FireFox, let's fix it 
            this.enable();  

            this._rerouteClicks();  
        };  

        // assigning methods to our class 
        AjaxUpload.prototype = {  
            setData: function (data) {  
                this._settings.data = data;  
            },  
            disable: function () {  
                addClass(this._button, this._settings.disabledClass);  
                this._disabled = true;  

                var nodeName = this._button.nodeName.toUpperCase();  
                if (nodeName == 'INPUT' || nodeName == 'BUTTON') {  
                    this._button.setAttribute('disabled', 'disabled');  
                }  

                // hide input 
                if (this._input) {  
                    // We use visibility instead of display to fix problem with Safari 4 
                    // The problem is that the value of input doesn't change if it 
                    // has display none when user selects a file 
                    this._input.parentNode.style.visibility = 'hidden';  
                }  
            },  
            enable: function () {  
                removeClass(this._button, this._settings.disabledClass);  
                this._button.removeAttribute('disabled');  
                this._disabled = false;  

            },  
            /** * Creates invisible file input * that will hover above the button * <div><input type='file' /></div> */  
            _createInput: function () {  
                var self = this;  

                var input = document.createElement("input");  
                input.setAttribute('type', 'file');  
                input.setAttribute('name', this._settings.name);  

                addStyles(input, {  
                    'position': 'absolute',  
                    // in Opera only 'browse' button 
                    // is clickable and it is located at 
                    // the right side of the input 
                    'right': 0,  
                    'margin': 0,  
                    'padding': 0,  
                    'fontSize': '480px',  
                    'cursor': 'pointer'  
                });  

                var div = document.createElement("div");  
                addStyles(div, {  
                    'display': 'block',  
                    'position': 'absolute',  
                    'overflow': 'hidden',  
                    'margin': 0,  
                    'padding': 0,  
                    'opacity': 0,  
                    // Make sure browse button is in the right side 
                    // in Internet Explorer 
                    'direction': 'ltr',  
                    //Max zIndex supported by Opera 9.0-9.2 
                    'zIndex': 2147483583  
                });  

                // Make sure that element opacity exists. 
                // Otherwise use IE filter 
                if (div.style.opacity !== "0") {  
                    if (typeof(div.filters) == 'undefined') {  
                        throw new Error('Opacity not supported by the browser');  
                    }  
                    div.style.filter = "alpha(opacity=0)";  
                }  

                addEvent(input, 'change', function () {  

                    if (!input || input.value === '') {  
                        return;  
                    }  

                    // Get filename from input, required 
                    // as some browsers have path instead of it 
                    var file = fileFromPath(input.value);  

                    if (false === self._settings.onChange.call(self, file, getExt(file))) {  
                        self._clearInput();  
                        return;  
                    }  

                    // Submit form when value is changed 
                    if (self._settings.autoSubmit) {  
                        self.submit();  
                    }  
                });  

                addEvent(input, 'mouseover', function () {  
                    addClass(self._button, self._settings.hoverClass);  
                });  

                addEvent(input, 'mouseout', function () {  
                    removeClass(self._button, self._settings.hoverClass);  

                    // We use visibility instead of display to fix problem with Safari 4 
                    // The problem is that the value of input doesn't change if it 
                    // has display none when user selects a file 
                    input.parentNode.style.visibility = 'hidden';  

                });  

                div.appendChild(input);  
                document.body.appendChild(div);  

                this._input = input;  
            },  
            _clearInput: function () {  
                if (!this._input) {  
                    return;  
                }  

                // this._input.value = ''; Doesn't work in IE6 
                removeNode(this._input.parentNode);  
                this._input = null;  
                this._createInput();  

                removeClass(this._button, this._settings.hoverClass);  
            },  
            /** * Function makes sure that when user clicks upload button, * the this._input is clicked instead */  
            _rerouteClicks: function () {  
                var self = this;  

                // IE will later display 'access denied' error 
                // if you use using self._input.click() 
                // other browsers just ignore click() 

                addEvent(self._button, 'mouseover', function () {  
                    if (self._disabled) {  
                        return;  
                    }  

                    if (!self._input) {  
                        self._createInput();  
                    }  

                    var div = self._input.parentNode;  
                    copyLayout(self._button, div);  
                    div.style.visibility = 'visible';  

                });  


                // commented because we now hide input on mouseleave 
                /** * When the window is resized the elements * can be misaligned if button position depends * on window size */  
                //addResizeEvent(function(){ 
                // if (self._input){ 
                // copyLayout(self._button, self._input.parentNode); 
                // } 
                //}); 

            },  
            /** * Creates iframe with unique name * @return {Element} iframe */  
            _createIframe: function () {  
                // We can't use getTime, because it sometimes return 
                // same value in safari :( 
                var id = getUID();  

                // We can't use following code as the name attribute 
                // won't be properly registered in IE6, and new window 
                // on form submit will open 
                // var iframe = document.createElement('iframe'); 
                // iframe.setAttribute('name', id); 

                var iframe = toElement('<iframe src="javascript:false;" name="' + id + '" />');  
                // src="javascript:false; was added 
                // because it possibly removes ie6 prompt 
                // "This page contains both secure and nonsecure items" 
                // Anyway, it doesn't do any harm. 
                iframe.setAttribute('id', id);  

                iframe.style.display = 'none';  
                document.body.appendChild(iframe);  

                return iframe;  
            },  
            /** * Creates form, that will be submitted to iframe * @param {Element} iframe Where to submit * @return {Element} form */  
            _createForm: function (iframe) {  
                var settings = this._settings;  

                // We can't use the following code in IE6 
                // var form = document.createElement('form'); 
                // form.setAttribute('method', 'post'); 
                // form.setAttribute('enctype', 'multipart/form-data'); 
                // Because in this case file won't be attached to request 
                var form = toElement('<form method="post" enctype="multipart/form-data"></form>');  

                form.setAttribute('action', settings.action);  
                form.setAttribute('target', iframe.name);  
                form.style.display = 'none';  
                document.body.appendChild(form);  

                // Create hidden input element for each data key 
                for (var prop in settings.data) {  
                    if (settings.data.hasOwnProperty(prop)) {  
                        var el = document.createElement("input");  
                        el.setAttribute('type', 'hidden');  
                        el.setAttribute('name', prop);  
                        el.setAttribute('value', settings.data[prop]);  
                        form.appendChild(el);  
                    }  
                }  
                return form;  
            },  
            /** * Gets response from iframe and fires onComplete event when ready * @param iframe * @param file Filename to use in onComplete callback */  
            _getResponse: function (iframe, file) {  
                // getting response 
                var toDeleteFlag = false,  
                    self = this,  
                    settings = this._settings;  

                addEvent(iframe, 'load', function () {  

                    if ( // For Safari 
                    iframe.src == "javascript:'%3Chtml%3E%3C/html%3E';" ||  
                    // For FF, IE 
                    iframe.src == "javascript:'<html></html>';") {  
                        // First time around, do not delete. 
                        // We reload to blank page, so that reloading main page 
                        // does not re-submit the post. 

                        if (toDeleteFlag) {  
                            // Fix busy state in FF3 
                            setTimeout(function () {  
                                removeNode(iframe);  
                            },  
                            0);  
                        }  

                        return;  
                    }  

                    var doc = iframe.contentDocument ? iframe.contentDocument : window.frames[iframe.id].document;  

                    // fixing Opera 9.26,10.00 
                    if (doc.readyState && doc.readyState != 'complete') {  
                        // Opera fires load event multiple times 
                        // Even when the DOM is not ready yet 
                        // this fix should not affect other browsers 
                        return;  
                    }  

                    // fixing Opera 9.64 
                    if (doc.body && doc.body.innerHTML == "false") {  
                        // In Opera 9.64 event was fired second time 
                        // when body.innerHTML changed from false 
                        // to server response approx. after 1 sec 
                        return;  
                    }  

                    var response;  

                    if (doc.XMLDocument) {  
                        // response is a xml document Internet Explorer property 
                        response = doc.XMLDocument;  
                    } else if (doc.body) {  
                        // response is html document or plain text 
                        response = doc.body.innerHTML;  

                        if (settings.responseType && settings.responseType.toLowerCase() == 'json') {  
                            // If the document was sent as 'application/javascript' or 
                            // 'text/javascript', then the browser wraps the text in a <pre> 
                            // tag and performs html encoding on the contents. In this case, 
                            // we need to pull the original text content from the text node's 
                            // nodeValue property to retrieve the unmangled content. 
                            // Note that IE6 only understands text/html 
                            if (doc.body.firstChild && doc.body.firstChild.nodeName.toUpperCase() == 'PRE') {  
                                response = doc.body.firstChild.firstChild.nodeValue;  
                            }  

                            if (response) {  
                                response = eval("(" + response + ")");  
                            } else {  
                                response = {};  
                            }  
                        }  
                    } else {  
                        // response is a xml document 
                        response = doc;  
                    }  

                    settings.onComplete.call(self, file, response);  

                    // Reload blank page, so that reloading main page 
                    // does not re-submit the post. Also, remember to 
                    // delete the frame 
                    toDeleteFlag = true;  

                    // Fix IE mixed content issue 
                    iframe.src = "javascript:'<html></html>';";  
                });  
            },  
            /** * Upload file contained in this._input */  
            submit: function () {  
                var self = this,  
                    settings = this._settings;  

                if (!this._input || this._input.value === '') {  
                    return;  
                }  

                var file = fileFromPath(this._input.value);  

                // user returned false to cancel upload 
                if (false === settings.onSubmit.call(this, file, getExt(file))) {  
                    this._clearInput();  
                    return;  
                }  

                // sending request 
                var iframe = this._createIframe();  
                var form = this._createForm(iframe);  

                // assuming following structure 
                // div -> input type='file' 
                removeNode(this._input.parentNode);  
                removeClass(self._button, self._settings.hoverClass);  

                form.appendChild(this._input);  

                form.submit();  

                // request set, clean up 
                removeNode(form);  
                form = null;  
                removeNode(this._input);  
                this._input = null;  

                // Get response from iframe and fire onComplete event when ready 
                this._getResponse(iframe, file);  

                // get ready for next request 
                this._createInput();  
            }  
        };  
    })();