IT

누가 jQuery File Upload 플러그인 구현 방법을 설명해 줄 수 있습니까?

itgroup 2023. 10. 15. 17:14
반응형

누가 jQuery File Upload 플러그인 구현 방법을 설명해 줄 수 있습니까?

편집(2019년 10월):

6년이 지난 지금도 jQuery File Upload는 사람들을 미치게 하고 있습니다.여기서 답변에서 약간의 위안을 찾는다면 현대적인 대안을 찾기 위해 NPM을 검색해 보십시오.그럴 가치가 없어요, 약속해요.

이전 편집에서 Uploadify를 추천했지만, 댓글 작성자가 지적한 것처럼 무료 버전을 제공하지 않는 것 같습니다.Uploadify는 어쨌든 2013년이었으니까요.


편집:

This still seems to be getting traffic so I'll explain what I ended up doing. I eventually got the plugin working by following the tutorial in the accepted answer. 하지만 jQuery File Upload는 정말 번거로우며, 더 간단한 파일 업로드 플러그인을 찾고 계신다면 [Uploadify](http://www.uploadify.com )를 적극 추천해 드립니다.한 답변이 지적한 대로 비상업적인 용도로만 무료로 사용할 수 있습니다.

배경

blueimp의 jQuery File Upload를 사용하여 사용자가 파일을 업로드 할 수 있도록 하려고 합니다.설치 지침에 따라 즉시 완벽하게 작동합니다.하지만 제 웹사이트에서 실용적으로 사용하기 위해서, 저는 몇 가지 일을 할 수 있기를 원합니다.

  • 기존 페이지에 업로더 포함
  • 업로드된 파일의 디렉토리 변경

플러그인의 모든 파일은 루트 아래의 폴더에 있습니다.

노력해봤어요...

  • 데모 페이지를 루트로 이동하고 필요한 스크립트의 경로 업데이트
  • 여기에 제시된 대로 UploadHandler.php 파일에서 'upload_dir' 및 'upload_url' 옵션을 변경합니다.
  • 데모 자바스크립트 두번째 줄 URL 변경

에서 합니다 Uncaught TypeError: Cannot read property 'files' of undefined수 가 어렵습니다 플러그인의 모든 부분이 어떻게 작동하는지 이해할 수 없어 디버깅이 어렵습니다.

코드

데모 페이지의 javascript:

$(function () {
'use strict';
// Change this to the location of your server-side upload handler:
var url = 'file_upload/server/php/UploadHandler.php',
    uploadButton = $('<button/>')
        .addClass('btn')
        .prop('disabled', true)
        .text('Processing...')
        .on('click', function () {
            var $this = $(this),
                data = $this.data();
            $this
                .off('click')
                .text('Abort')
                .on('click', function () {
                    $this.remove();
                    data.abort();
                });
            data.submit().always(function () {
                $this.remove();
            });
        });
$('#fileupload').fileupload({
    url: url,
    dataType: 'json',
    autoUpload: false,
    acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
    maxFileSize: 5000000, // 5 MB
    // Enable image resizing, except for Android and Opera,
    // which actually support image resizing, but fail to
    // send Blob objects via XHR requests:
    disableImageResize: /Android(?!.*Chrome)|Opera/
        .test(window.navigator.userAgent),
    previewMaxWidth: 100,
    previewMaxHeight: 100,
    previewCrop: true
}).on('fileuploadadd', function (e, data) {
    data.context = $('<div/>').appendTo('#files');
    $.each(data.files, function (index, file) {
        var node = $('<p/>')
                .append($('<span/>').text(file.name));
        if (!index) {
            node
                .append('<br>')
                .append(uploadButton.clone(true).data(data));
        }
        node.appendTo(data.context);
    });
}).on('fileuploadprocessalways', function (e, data) {
    var index = data.index,
        file = data.files[index],
        node = $(data.context.children()[index]);
    if (file.preview) {
        node
            .prepend('<br>')
            .prepend(file.preview);
    }
    if (file.error) {
        node
            .append('<br>')
            .append(file.error);
    }
    if (index + 1 === data.files.length) {
        data.context.find('button')
            .text('Upload')
            .prop('disabled', !!data.files.error);
    }
}).on('fileuploadprogressall', function (e, data) {
    var progress = parseInt(data.loaded / data.total * 100, 10);
    $('#progress .bar').css(
        'width',
        progress + '%'
    );
}).on('fileuploaddone', function (e, data) {
    $.each(data.result.files, function (index, file) {
        var link = $('<a>')
            .attr('target', '_blank')
            .prop('href', file.url);
        $(data.context.children()[index])
            .wrap(link);
    });
}).on('fileuploadfail', function (e, data) {
    $.each(data.result.files, function (index, file) {
        var error = $('<span/>').text(file.error);
        $(data.context.children()[index])
            .append('<br>')
            .append(error);
    });
}).prop('disabled', !$.support.fileInput)
    .parent().addClass($.support.fileInput ? undefined : 'disabled');
});

I'm surprised by the lack of documentation; it seems like it should be a simple thing to change. I would appreciate if someone could explain how to do this.

며칠 전에 비슷한 기능을 찾고 있었는데 튜토리얼진에서 좋은 튜토리얼을 발견했습니다.여기 작동 예가 있습니다.전체 튜토리얼은 여기에서 확인할 수 있습니다.

파일 업로드 대화를 유지하기 위한 간단한 양식:

<form id="upload" method="post" action="upload.php" enctype="multipart/form-data">
  <input type="file" name="uploadctl" multiple />
  <ul id="fileList">
    <!-- The file list will be shown here -->
  </ul>
</form>

파일을 업로드하기 위한 jQuery 코드는 다음과 같습니다.

$('#upload').fileupload({

  // This function is called when a file is added to the queue
  add: function (e, data) {
    //This area will contain file list and progress information.
    var tpl = $('<li class="working">'+
                '<input type="text" value="0" data-width="48" data-height="48" data-fgColor="#0788a5" data-readOnly="1" data-bgColor="#3e4043" />'+
                '<p></p><span></span></li>' );

    // Append the file name and file size
    tpl.find('p').text(data.files[0].name)
                 .append('<i>' + formatFileSize(data.files[0].size) + '</i>');

    // Add the HTML to the UL element
    data.context = tpl.appendTo(ul);

    // Initialize the knob plugin. This part can be ignored, if you are showing progress in some other way.
    tpl.find('input').knob();

    // Listen for clicks on the cancel icon
    tpl.find('span').click(function(){
      if(tpl.hasClass('working')){
              jqXHR.abort();
      }
      tpl.fadeOut(function(){
              tpl.remove();
      });
    });

    // Automatically upload the file once it is added to the queue
    var jqXHR = data.submit();
  },
  progress: function(e, data){

        // Calculate the completion percentage of the upload
        var progress = parseInt(data.loaded / data.total * 100, 10);

        // Update the hidden input field and trigger a change
        // so that the jQuery knob plugin knows to update the dial
        data.context.find('input').val(progress).change();

        if(progress == 100){
            data.context.removeClass('working');
        }
    }
});
//Helper function for calculation of progress
function formatFileSize(bytes) {
    if (typeof bytes !== 'number') {
        return '';
    }

    if (bytes >= 1000000000) {
        return (bytes / 1000000000).toFixed(2) + ' GB';
    }

    if (bytes >= 1000000) {
        return (bytes / 1000000).toFixed(2) + ' MB';
    }
    return (bytes / 1000).toFixed(2) + ' KB';
}

그리고 데이터를 처리하기 위한 PHP 코드 샘플은 다음과 같습니다.

if($_POST) {
    $allowed = array('jpg', 'jpeg');

    if(isset($_FILES['uploadctl']) && $_FILES['uploadctl']['error'] == 0){

        $extension = pathinfo($_FILES['uploadctl']['name'], PATHINFO_EXTENSION);

        if(!in_array(strtolower($extension), $allowed)){
            echo '{"status":"error"}';
            exit;
        }

        if(move_uploaded_file($_FILES['uploadctl']['tmp_name'], "/yourpath/." . $extension)){
            echo '{"status":"success"}';
            exit;
        }
        echo '{"status":"error"}';
    }
    exit();
}

위 코드는 기존 양식에 추가할 수 있습니다.이 프로그램은 이미지가 추가되면 자동으로 업로드 됩니다.이 기능은 변경할 수 있으며 기존 양식을 제출하는 동안 이미지를 제출할 수 있습니다.

내 대답을 실제 코드로 업데이트 했습니다.코드의 원작자에게 모든 공을 돌립니다.

출처 : http://tutorialzine.com/2013/05/mini-ajax-file-upload-form/

저는 jQuery Upload와 2시간 동안 싸웠지만 의존성의 양 때문에 포기했습니다(모든 벨과 휘파람을 얻기 위해 13개의 JS 파일이 포함되어 있었습니다).

조금 더 검색해보니 종속성이 없는 Dropzone.js라는 깔끔한 프로젝트가 있었습니다.

저자는 jQuery File Upload 플러그인에서 영감을 얻은 부트스트랩 데모도 만들었습니다.

다른 사람이 시간을 절약할 수 있기를 바랍니다.

저도 이 문제로 어려움을 겪었지만 UploadHandler에서 경로가 어떻게 작동하는지 알게 되자 작동하게 되었습니다.php: uppload_dir와 uppload_url는 작동하기 위해 살펴볼 수 있는 유일한 설정입니다.디버깅 정보에 대해서도 서버 오류 로그를 확인합니다.

dropper jquery 플러그인을 사용하여 이미지 미리보기와 함께 이미지 드래그 앤 드롭 업로더를 확인합니다.

HTML

<div class="target" width="78" height="100"><img /></div>

JS

$(".target").dropper({
    action: "upload.php",

}).on("start.dropper", onStart);
function onStart(e, files){
console.log(files[0]);

    image_preview(files[0].file).then(function(res){
$('.dropper-dropzone').empty();
//$('.dropper-dropzone').css("background-image",res.data);
 $('#imgPreview').remove();        
$('.dropper-dropzone').append('<img id="imgPreview"/><span style="display:none">Drag and drop files or click to select</span>');
var widthImg=$('.dropper-dropzone').attr('width');
        $('#imgPreview').attr({width:widthImg});
    $('#imgPreview').attr({src:res.data});

    })

}

function image_preview(file){
    var def = new $.Deferred();
    var imgURL = '';
    if (file.type.match('image.*')) {
        //create object url support
        var URL = window.URL || window.webkitURL;
        if (URL !== undefined) {
            imgURL = URL.createObjectURL(file);
            URL.revokeObjectURL(file);
            def.resolve({status: 200, message: 'OK', data:imgURL, error: {}});
        }
        //file reader support
        else if(window.File && window.FileReader)
        {
            var reader = new FileReader();
            reader.readAsDataURL(file);
            reader.onloadend = function () {
                imgURL = reader.result;
                def.resolve({status: 200, message: 'OK', data:imgURL, error: {}});
            }
        }
        else {
            def.reject({status: 1001, message: 'File uploader not supported', data:imgURL, error: {}});
        }
    }
    else
        def.reject({status: 1002, message: 'File type not supported', error: {}});
    return def.promise();
}

$('.dropper-dropzone').mouseenter(function() {
 $( '.dropper-dropzone>span' ).css("display", "block");
});

$('.dropper-dropzone').mouseleave(function() {
 $( '.dropper-dropzone>span' ).css("display", "none");
});

CSS

.dropper-dropzone{
    width:78px;
padding:3px;
    height:100px;
position: relative;
}
.dropper-dropzone>img{
    width:78px;
    height:100px;
margin-top=0;
}

.dropper-dropzone>span {
    position: absolute;
    right: 10px;
    top: 20px;
color:#ccc;


}

.dropper .dropper-dropzone{

padding:3px !important    
}

데모 제이피들

이것은 파일 업로드에 좋은 Angular 플러그인이며, 무료입니다!

각파일업로드

이것이 내가 사용한 최고의 멀티업로드 jquery 플러그인이라면 업로드를 사용할 수 있습니다.

구현은 쉽고 브라우저 지원은 완벽합니다.

나는 이 플러그인을 레일스에서 한동안 사용하다가 누군가가 내가 만든 모든 코드를 쓸모없게 만들어 버렸습니다.

레일즈에서는 사용하지 않는 것처럼 보이지만 사용하는 사람이 있다면 이 보석을 확인해 보십시오.소스가 여기 있습니다 --> jQueryFileUploadRails.

업데이트:

댓글 작성자를 만족시키기 위해 답변을 업데이트했습니다.본질적으로 "이 보석을 사용하라, 여기 소스 코드가 있다" 만약 그것이 사라지면 그것을 멀리하라.

안녕하세요 벨로우 링크를 시도해보세요. 매우 쉽습니다.저는 오랜 시간 동안 갇혀 있었고 그것이 제 문제를 몇 분 안에 해결해 줍니다.http://simpleupload.michaelcbrook.com/ #

여기에 딱 맞는 Droply.js.간단하며 기본적으로 작동하는 데모 사이트와 함께 사전 패키지로 제공됩니다.

2021년이 되었고, 무엇이든 업로드할 수 있는 환상적으로 쉬운 플러그인이 있습니다.

https://pqina.nl/filepond/ ?ref=pqina

요소 추가:

<input type="file" 
class="filepond"
name="filepond" 
multiple 
data-allow-reorder="true"
data-max-file-size="3MB"
data-max-files="3">

추가 플러그인 등록:

  FilePond.registerPlugin(
FilePondPluginImagePreview,  
FilePondPluginImageExifOrientation,  
FilePondPluginFileValidateSize,  
FilePondPluginImageEdit);

그런 다음 요소에 와이어를 연결합니다.

// Select the file input and use 
// create() to turn it into a pond
FilePond.create(
  document.querySelector('input'),

  // Use Doka.js as image editor
  imageEditEditor: Doka.create({
    utils: ['crop', 'filter', 'color']
  })
);

https://www.yoodu.co.uk 에서 이미지를 업로드하고 변환하기 위해 추가 Doka 이미지 편집기와 함께 사용합니다.

미친 듯이 간단하게 설정할 수 있고 그것을 운영하는 사람들은 지원을 잘합니다.

팬 보이니까요.

UI 플러그인의 경우 jsp 페이지와 Spring MVC를 사용합니다.

샘플 html.파일 업로드ID 속성을 가진 요소 내에 있어야 합니다.

    <!-- The fileupload-buttonbar contains buttons to add/delete files and start/cancel the upload -->
<div class="fileupload-buttonbar">
    <div>
        <!-- The fileinput-button span is used to style the file input field as button -->
        <span class="btn btn-success fileinput-button">
            <i class="glyphicon glyphicon-plus"></i>
            <span>Add files</span>
            <input id="fileuploadInput" type="file" name="files[]" multiple>
        </span>
        <%-- https://stackoverflow.com/questions/925334/how-is-the-default-submit-button-on-an-html-form-determined --%>
        <button type="button" class="btn btn-primary start">
            <i class="glyphicon glyphicon-upload"></i>
            <span>Start upload</span>
        </button>
        <button type="reset" class="btn btn-warning cancel">
            <i class="glyphicon glyphicon-ban-circle"></i>
            <span>Cancel upload</span>
        </button>
        <!-- The global file processing state -->
        <span class="fileupload-process"></span>
    </div>
    <!-- The global progress state -->
    <div class="fileupload-progress fade">
        <!-- The global progress bar -->
        <div class="progress progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100">
            <div class="progress-bar progress-bar-success" style="width:0%;"></div>
        </div>
        <!-- The extended global progress state -->
        <div class="progress-extended">&nbsp;</div>
    </div>
</div>
<!-- The table listing the files available for upload/download -->
<table role="presentation" class="table table-striped"><tbody class="files"></tbody></table>

<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/js/jquery-file-upload-9.14.2/css/jquery.fileupload.css">
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/js/jquery-file-upload-9.14.2/css/jquery.fileupload-ui.css">

<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-file-upload-9.14.2/js/vendor/jquery.ui.widget.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-file-upload-9.14.2/js/jquery.iframe-transport.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-file-upload-9.14.2/js/jquery.fileupload.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-file-upload-9.14.2/js/jquery.fileupload-process.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-file-upload-9.14.2/js/jquery.fileupload-validate.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-file-upload-9.14.2/js/jquery.fileupload-ui.js"></script>

<script type="text/javascript">
    $(document).ready(function () {
            var maxFileSizeBytes = ${maxFileSizeBytes};
        if (maxFileSizeBytes < 0) {
            //-1 or any negative value means no size limit
            //set to undefined
            //https://stackoverflow.com/questions/5795936/how-to-set-a-javascript-var-as-undefined
            maxFileSizeBytes = void 0;
        }

        //https://github.com/blueimp/jQuery-File-Upload/wiki/Options
        //https://stackoverflow.com/questions/34063348/jquery-file-upload-basic-plus-ui-and-i18n
        //https://stackoverflow.com/questions/11337897/how-to-customize-upload-download-template-of-blueimp-jquery-file-upload
        $('#fileupload').fileupload({
            url: '${pageContext.request.contextPath}/app/uploadResources.do',
            fileInput: $('#fileuploadInput'),
            acceptFileTypes: /(\.|\/)(jrxml|png|jpe?g)$/i,
            maxFileSize: maxFileSizeBytes,
            messages: {
                acceptFileTypes: '${fileTypeNotAllowedText}',
                maxFileSize: '${fileTooLargeMBText}'
            },
            filesContainer: $('.files'),
            uploadTemplateId: null,
            downloadTemplateId: null,
            uploadTemplate: function (o) {
                var rows = $();
                $.each(o.files, function (index, file) {
                    var row = $('<tr class="template-upload fade">' +
                            '<td><p class="name"></p>' +
                            '<strong class="error text-danger"></strong>' +
                            '</td>' +
                            '<td><p class="size"></p>' +
                            '<div class="progress progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">' +
                            '<div class="progress-bar progress-bar-success" style="width:0%;"></div></div>' +
                            '</td>' +
                            '<td>' +
                            (!index && !o.options.autoUpload ?
                                    '<button class="btn btn-primary start" disabled>' +
                                    '<i class="glyphicon glyphicon-upload"></i> ' +
                                    '<span>${startText}</span>' +
                                    '</button>' : '') +
                            (!index ? '<button class="btn btn-warning cancel">' +
                                    '<i class="glyphicon glyphicon-ban-circle"></i> ' +
                                    '<span>${cancelText}</span>' +
                                    '</button>' : '') +
                            '</td>' +
                            '</tr>');
                    row.find('.name').text(file.name);
                    row.find('.size').text(o.formatFileSize(file.size));
                    if (file.error) {
                        row.find('.error').text(file.error);
                    }
                    rows = rows.add(row);
                });
                return rows;
            },
            downloadTemplate: function (o) {
                var rows = $();
                $.each(o.files, function (index, file) {
                    var row = $('<tr class="template-download fade">' +
                            '<td><p class="name"></p>' +
                            (file.error ? '<strong class="error text-danger"></strong>' : '') +
                            '</td>' +
                            '<td><span class="size"></span></td>' +
                            '<td>' +
                            (file.deleteUrl ? '<button class="btn btn-danger delete">' +
                                    '<i class="glyphicon glyphicon-trash"></i> ' +
                                    '<span>${deleteText}</span>' +
                                    '</button>' : '') +
                            '<button class="btn btn-warning cancel">' +
                            '<i class="glyphicon glyphicon-ban-circle"></i> ' +
                            '<span>${clearText}</span>' +
                            '</button>' +
                            '</td>' +
                            '</tr>');
                    row.find('.name').text(file.name);
                    row.find('.size').text(o.formatFileSize(file.size));
                    if (file.error) {
                        row.find('.error').text(file.error);
                    }
                    if (file.deleteUrl) {
                        row.find('button.delete')
                                .attr('data-type', file.deleteType)
                                .attr('data-url', file.deleteUrl);
                    }
                    rows = rows.add(row);
                });
                return rows;
            }
        });

    });
</script>

샘플 업로드 및 삭제 요청 처리기

    @PostMapping("/app/uploadResources")
public @ResponseBody
Map<String, List<FileUploadResponse>> uploadResources(MultipartHttpServletRequest request,
        Locale locale) {
    //https://github.com/jdmr/fileUpload/blob/master/src/main/java/org/davidmendoza/fileUpload/web/ImageController.java
    //https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#using-jquery-file-upload-ui-version-with-a-custom-server-side-upload-handler
    Map<String, List<FileUploadResponse>> response = new HashMap<>();
    List<FileUploadResponse> fileList = new ArrayList<>();

    String deleteUrlBase = request.getContextPath() + "/app/deleteResources.do?filename=";

    //http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/multipart/MultipartRequest.html
    Iterator<String> itr = request.getFileNames();
    while (itr.hasNext()) {
        String htmlParamName = itr.next();
        MultipartFile file = request.getFile(htmlParamName);
        FileUploadResponse fileDetails = new FileUploadResponse();
        String filename = file.getOriginalFilename();
        fileDetails.setName(filename);
        fileDetails.setSize(file.getSize());
        try {
            String message = saveFile(file);
            if (message != null) {
                String errorMessage = messageSource.getMessage(message, null, locale);
                fileDetails.setError(errorMessage);
            } else {
                //save successful
                String encodedFilename = URLEncoder.encode(filename, "UTF-8");
                String deleteUrl = deleteUrlBase + encodedFilename;
                fileDetails.setDeleteUrl(deleteUrl);
            }
        } catch (IOException ex) {
            logger.error("Error", ex);
            fileDetails.setError(ex.getMessage());
        }

        fileList.add(fileDetails);
    }

    response.put("files", fileList);

    return response;
}

@PostMapping("/app/deleteResources")
public @ResponseBody
Map<String, List<Map<String, Boolean>>> deleteResources(@RequestParam("filename") List<String> filenames) {
    Map<String, List<Map<String, Boolean>>> response = new HashMap<>();
    List<Map<String, Boolean>> fileList = new ArrayList<>();

    String templatesPath = Config.getTemplatesPath();
    for (String filename : filenames) {
        Map<String, Boolean> fileDetails = new HashMap<>();

        String cleanFilename = ArtUtils.cleanFileName(filename);
        String filePath = templatesPath + cleanFilename;

        File file = new File(filePath);
        boolean deleted = file.delete();

        if (deleted) {
            fileDetails.put(cleanFilename, true);
        } else {
            fileDetails.put(cleanFilename, false);
        }

        fileList.add(fileDetails);
    }

    response.put("files", fileList);

    return response;
}

필요한 json 응답을 생성하기 위한 샘플 클래스

    public class FileUploadResponse {
    //https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#using-jquery-file-upload-ui-version-with-a-custom-server-side-upload-handler

    private String name;
    private long size;
    private String error;
    private String deleteType = "POST";
    private String deleteUrl;

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return the size
     */
    public long getSize() {
        return size;
    }

    /**
     * @param size the size to set
     */
    public void setSize(long size) {
        this.size = size;
    }

    /**
     * @return the error
     */
    public String getError() {
        return error;
    }

    /**
     * @param error the error to set
     */
    public void setError(String error) {
        this.error = error;
    }

    /**
     * @return the deleteType
     */
    public String getDeleteType() {
        return deleteType;
    }

    /**
     * @param deleteType the deleteType to set
     */
    public void setDeleteType(String deleteType) {
        this.deleteType = deleteType;
    }

    /**
     * @return the deleteUrl
     */
    public String getDeleteUrl() {
        return deleteUrl;
    }

    /**
     * @param deleteUrl the deleteUrl to set
     */
    public void setDeleteUrl(String deleteUrl) {
        this.deleteUrl = deleteUrl;
    }

}

https://pitipata.blogspot.co.ke/2017/01/using-jquery-file-upload-ui.html 참조

언급URL : https://stackoverflow.com/questions/17934689/can-someone-explain-how-to-implement-the-jquery-file-upload-plugin

반응형