민서네집

jQuery Multiple File Upload with Progress bar 본문

WEB (HTML, CSS)

jQuery Multiple File Upload with Progress bar

브라이언7 2013. 11. 5. 14:04

jQuery Upload File

https://github.com/hayageek/jquery-upload-file


jQuery Multiple File Upload with Progress bar Tutorial

 

[원문] http://hayageek.com/jquery-multiple-file-upload/

 

jQuery Upload File Plugin Demo

 

http://hayageek.com/docs/jquery-upload-file.php

 

Jquery Multiple File Upload Demo

 

http://hayageek.com/examples/jquery/jquery-multiple-file-upload/index.php

 

 

 

 

화면도 깔끔하고, 한가지 더 좋은 점은 업로드 하는 중간에 cancel 버튼이 있어서 cancel 를 할 수 있다는 것이다.

 

Spring MVC Framework 를 만들어서 이것을 적용해 보았는데,

Chrome 에서는 잘 되는데, IE 9 에서는 업로드가 완료 안되고, 결과를 download 받으려고 한다.

 

Multi File Uploader 를 초기화 시킬 때

returnType:"json"

옵션을 줘도 마찬가지다.

 

Fiddler 에서 HTTP header 정보를 보니 IE 9 에서는

Content-Type: application/json

이러면 문제가 없는것 같은데,

Content-Type: application/json;charset=UTF-8

이렇게 가면 결과를 다운로드 받으려고 한다.

 

IE 9 의 버그인지...


Sample 페이지를 IE9 에서 실행해 보면 잘 되는데, 업로드한 결과를 Content-Type: text/html 으로 보내주고 있었다.

 

지금 프로젝트에서는 Spring Framework 3.1.2 를 사용 중인데,

Spring 설정 파일에서

 

<!-- json result  -->
<bean id="jsonView" class="net.sf.json.spring.web.servlet.view.JsonView">
    <property name="contentType" value="application/json"/>
</bean>

 

이렇게 해도

Content-Type: application/json;charset=UTF-8

이렇게 Header 정보가 생긴다.

contentType 의 property 를 삭제해도 디폴트로 이렇게 Content-Type 이 생긴다.

 

IE 9 에서 Multiple File Upload Example 은 잘 실행된다.

( http://hayageek.com/examples/jquery/jquery-multiple-file-upload/index.php )

Fiddler 로 HTTP Header를 봐 보면

Content-Type: text/html

로 되어 있다.

 

그래서 jsonView 에서 Content-Type 을 application/json 대신 text/html 로 바꿨다.

구글에서 검색해보니 application/json 으로 하면 문제가 있는 것 같다.

 

[출처] http://stackoverflow.com/questions/5388893/ie9-json-data-do-you-want-to-open-or-save-this-file

In my case when contentType in response header is "application/json; charset=UTF-8", the IE 9 shows that Prompt. But changed to "text/html" then the prompt does not show, although all other browsers are fine with the "application/json; charset=UTF-8".

 

[출처] http://stackoverflow.com/questions/267546/correct-content-type-http-header-for-json

application/json is favored but usually web browsers don't know what to do with those headers and screws it up. For stuff like jquery, I have seen text/html recommended. If you are having errors pop up (e.g. download dialog box) then try text/html

 

Spring 설정 파일에서 다음과 같이 수정했다.

 <!-- json result  -->
 <bean id="jsonView" class="net.sf.json.spring.web.servlet.view.JsonView">
  <!-- <property name="contentType" value="application/json;charset=UTF-8"/> -->
  <property name="contentType" value="text/html; charset=UTF-8" />
 </bean>

 

HTTP Header 정보가

Content-Type: text/html;charset=UTF-8

이렇게 바뀐다. IE9 에서 jQuery File Upload 도 잘 되고, json 을 사용하는 데도 전혀 문제가 없었다.


대신에 jQuery 에서 $.ajax() 로 call 할 때 dataType 옵션을 안 줬을때 Content-Type 이 application/json 이었을 때는 받아온 문자열을 자동으로 json 객체로 parsing 했지만, Content-Type 을 text/html 로 바꾸게 되면 

data = $.parseJSON(data);

이렇게 한번 parsing 을 해줘야 json 객체로 인식한다.


[2015-04-03]

IE9 에서 업로드를 실행하고 나서 그 결과를 다운로드 받으려는 이유는 HTTP Request Header의 Accept 값 때문이었다.

IE9 에서도 jQuery의 $.ajax() 로 호출할 때, (dataType: "json" 옵션을 주고 실행) 정상적으로 json 데이터를 처리한다.


IE9에서 정상적으로 json 객체를 처리한 경우 (Fiddler Application 캡쳐)



아래 이미지는 jQuery Multiple File Upload 할 때, IE9 에서 요청을 보내고 다운로드 받으려고 할 때의 Fiddler 캡쳐한 이미지이다.





아래는 Chrome에서 jQuery Multiple File Upload 라이브러리에서 업로드 할 때의 패킷 캡쳐 이미지이다.



IE9 에서 업로드 하고 나서 결과를 다운로드 받으려 할 때는 Request Header의 Accept 값이 application/json, text/javascript 가 아니라 application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, *.* 으로 이상하게 된 것을 알 수 있다.


https://rawgithub.com/hayageek/jquery-upload-file/master/js/jquery.uploadfile.min.js

http://malsup.github.io/jquery.form.js


위 2개의 라이브러리 중 하나에서 IE9 에서 보낼 때는 Accept 헤더를 잘 못 보내는 버그가 있는 것 같다.


내가 이 버그를 찾아내서 고치기 보다는 그냥 jQuery Multiple File Upload 라이브러리를 이용해서 업로드 할 때만 Response의 Content-Type 을 applicatoin/json 이 아니라  text/html 로 보내서 해결하기로 했다.


Spring Framework의 Controller. (아래와 같이 하면 Response의 Header에서 Content-Type: application/json 으로 된다.)

@RequestMapping(value="/uploadLabExcelFile", method = RequestMethod.POST)
@ResponseBody
public Map<String , Object> uploadLabExcelFile(@RequestParam Map<String , Object> param, @RequestParam MultipartFile uploadFile) throws IOException {

	int cnt = labService.uploadLabExcelFile(uploadFile);

	logger.debug("########### Update Record Count: " + cnt);
	Map<String , Object> result = new HashMap<String , Object>();
	Map<String , Object> data = new HashMap<String , Object>();
	data.put("updateCount", cnt);

	result.put("message", new Message());
	result.put("data", data);

	return result;
}


아래와 같이 수정한다. 이렇게 하면 이 Controller 에서만 Response의 Header에서 Content-Type: text/html 으로 되면서 IE9 나 Chrome 에서 jQuery upload library가 다 잘 작동한다.


@RequestMapping(value="/uploadLabExcelFile", method = RequestMethod.POST, produces="text/html")
@ResponseBody
public String uploadLabExcelFile(@RequestParam Map<String,Object> param, @RequestParam MultipartFile uploadFile) throws IOException {

	int cnt = labService.uploadLabExcelFile(uploadFile);

	logger.debug("########### Update Record Count: " + cnt);
	Map<String , Object> result = new HashMap<String , Object>();
	Map<String , Object> data = new HashMap<String , Object>();
	data.put("updateCount", cnt);

	result.put("message", new Message());
	result.put("data", data);

	com.fasterxml.jackson.databind.ObjectMapper mapper = new ObjectMapper();

	return mapper.writeValueAsString(result);
}


[참고] How to Convert Java Object To/From JSON


http://www.mkyong.com/java/how-to-convert-java-object-to-from-json-jackson/


업로드 하기 전에 Check 하는 로직을 추가하려면...


$(document).ready(function() {

// Upload Setting

var settings = {

   url: CONTEXT+"/input-data/uploadLabExcelFile",

   method: "POST",

   returnType:"json",

   allowedTypes:"xls,xlsx",

   fileName: "uploadFile",

   multiple: false,

   onSubmit: function(files) {

    //files : List of files to be uploaded

    //return false; to stop upload

    if( $("#selectProjectList").val()=="no-select" ) {

    alert("먼저 Project를 선택해 주세요. [onSubmit]");

    return false;

    }

    $("#fileUploaderStatus").html("");

   },

   onSuccess: function(files,data,xhr)

   {

       if(data.message.rstCd == "0000") {

        $("#fileUploaderStatus").html("<font color='green'>실험 정보 업로드가 성공적으로 처리되었습니다. ( " + data.data.updateCount + " 건)</font>");

       } else {

        $("#fileUploaderStatus").html("<font color='red'>" + data.rstMsg + "</font>");

        alert(data.rstMsg);

        if( data.rstCd == "0103" ) {

        location.href = "/";

        }

       }

   },

   onError: function(files,status,errMsg)

   {

       $("#fileUploaderStatus").html("<font color='red'>업로드가 실패했습니다.</font>");

   }

};

$("#mulitplefileuploader").uploadFile(settings);


setTimeout(function() {

$("input[name=uploadFile]").click(function(){

    if( $("#selectProjectList").val()=="no-select" ) {

    alert("먼저 Project를 선택해 주세요.");

    return false;

    }

    return true;

});

}, 1000);

});


위에서처럼 setTimeout() 으로 1초를 줘야지 Chrome에서도 제대로 체크가 되었다.

onSubmit: function(files) { ... } 안에 체크 로직을 두면, Upload 버튼을 누를때 바로 체크되는 게 아니라 파일 선택 대화상자가 뜨고 나서 확인 버튼을 누르고 나서야 체크가 된다.


업로드 할 때 부가적인 정보 같이 보내기


[참조] https://github.com/hayageek/jquery-upload-file


Options 항목 중에

dynamicFormData 옵션을 이용하면 된다.

e.g. var settings = { dynamicFormData: function() { var data={"projectId": $('#selectProjectList').val() }; return data; } ... }


부가적인 정보가 바뀔 때마다 

$("div.ajax-upload-dragdrop").remove(); 를 하고,

var settings = { formData: "새로운값" , ... };

$("#mulitplefileuploader").uploadFile(settings);

이렇게 했더니, Chrome Browser에서는 잘 수행되지만,

IE9 에서는 아예 upload 가 되지 않았다.


[2015-04-07]

ContentNegotiatingViewResolver 를 이용해서 text/html 로 json 응답 보내기


Spring Framework에서 제공하는 org.springframework.web.servlet.view.BeanNameViewResolver 를 이용해서 text/html 로 json 응답을 보내고 싶었다. 왜냐하면 json 요청일 경우만 따로 처리하는 Exception Handler를 만들고 싶었기 때문이다.


json 요청일 경우는 로그인이 안되어 있는 경우나, 서버에서 에러가 난 경우도 에러 메시지를 json 형태로 줘야지 제대로 처리가 되므로...


그래서 Ajax로 요청을 보낼 때 url 뒤에 .json 을 붙였다. @ExceptionHandler 메서드에서는 request의 URL의 끝에 .json 이 있는지 확인해서 mav.setViewName("jsonView"); 이렇게 처리했다.


package com.examples.project.main.controller;

import javax.servlet.http.HttpServletRequest;

import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;

import com.examples.project.common.domain.Message;

@ControllerAdvice
public class GlobalDefaultExceptionHandler {

	public static final String DEFAULT_ERROR_VIEW = "error";

	@ExceptionHandler(value = Exception.class)
	public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
		// If the exception is annotated with @ResponseStatus rethrow it and let
		// the framework handle it - like the OrderNotFoundException example
		// at the start of this post.
		// AnnotationUtils is a Spring Framework utility class.
		if( AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
			throw e;
		}

		System.out.println(req.getRequestURL());

		ModelAndView mav = new ModelAndView();
		if( req.getRequestURL().toString().endsWith(".json") ) {
			mav.setViewName("jsonView");
			String errorCode = (String)req.getAttribute("errorCode");
			mav.addObject("message", new Message(errorCode, new String[]{e.getMessage()}));
		} else {
			// Otherwise setup and send the user to a default error-view.
			mav.addObject("exception", e);
			mav.addObject("url", req.getRequestURL());
			mav.setViewName("exception/" + DEFAULT_ERROR_VIEW);
		}

		return mav;
	}

}


Controller 에서 URL 뒤에 .json이 붙은 응답을 처리하기 위해서는 ContentNegotiatingViewResolver 를 안의 jsonView로 실행되어야 한다.

Controller Method 위에 @ResponseBody 를 붙이면 안되고, @RequestMapping 에서 produces="text/html" 도 넣으면 안된다.


@RequestMapping(value="/uploadLabExcelFile", method = RequestMethod.POST)
public String uploadLabExcelFile(@RequestParam Map<String,Object> param, @RequestParam MultipartFile uploadFile, HttpServletRequest request, Model model) throws IOException {

	String projectId = (String)param.get("projectId");

	request.setAttribute("errorCode", "0401");

	int cnt = labService.uploadLabExcelFile(uploadFile, projectId);

	logger.debug("########### Update Record Count: " + cnt);
	Map<String, Object> data = new HashMap<String, Object>();
	data.put("updateCount", cnt);

	model.addAttribute("message", new Message());
	model.addAttribute("data", data);

	return "jsonView";
}



Spring 설정 파일의 설정은 다음과 같다.


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:util="http://www.springframework.org/schema/util"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">

	<!-- Use spring servlet for all requests, including static resources -->
    <mvc:default-servlet-handler/>

	<!-- Use @MVC annotations -->
    <mvc:annotation-driven />

	<!-- User @Controller, @Service... annotations -->
    <context:component-scan base-package="com.examples.project" />

	<!-- **************************************************************** -->
	<!-- Configration -->
	<!-- **************************************************************** -->
	<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<!-- <value>classpath:config/jdbc.properties</value> -->
				<value>classpath:config/mail.properties</value>
			</list>
		</property>
	</bean>

	<util:properties id="config" location="classpath:config/config.properties" />
	<util:properties id="mail" location="classpath:config/mail.properties" />

	<!-- Application Message Bundle -->
	<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
		<property name="basename" value="classpath:/message/messages" />
		<property name="cacheSeconds" value="3000" />
	</bean>

	<bean id="jsonView" class="org.springframework.web.servlet.view.json.MappingJackson2JsonView">
		<!-- <property name="contentType" value="application/json;charset=UTF-8"/> -->
		<property name="contentType" value="text/html;charset=UTF-8"/>
	</bean>

	<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
		<property name="contentNegotiationManager">
			<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
				<property name="mediaTypes">
					<value>
						json=text/html;charset=UTF-8
					</value>
				</property>
			</bean>
		</property>
		<property name="viewResolvers">
			<list>
				<bean id="beanNameResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver" />

			    <bean id="viewResolver" class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
			        <property name="templateEngine" ref="templateEngine" />
			        <property name="characterEncoding" value="UTF-8" />
			    </bean>
			</list>
		</property>
		<property name="defaultViews">
			<list>
				<ref bean="jsonView" />
			</list>
		</property>
	</bean>

	<!-- Thymeleaf template engine -->
    <bean id="templateResolver" class="org.thymeleaf.templateresolver.ServletContextTemplateResolver">
        <property name="prefix" value="/WEB-INF/views/" />
		<property name="suffix" value=".html" />
        <property name="templateMode" value="HTML5" />
        <property name="characterEncoding" value="UTF-8" />
        <!-- Template cache is true by default. Set to false if you want -->
        <!-- templates to be automatically updated when modified.        -->
        <property name="cacheable" value="false" />
    </bean>

    <bean id="templateEngine" class="org.thymeleaf.spring4.SpringTemplateEngine">
        <property name="templateResolver" ref="templateResolver" />
        <property name="additionalDialects">
            <set>
                <bean class="org.thymeleaf.extras.springsecurity3.dialect.SpringSecurityDialect" />
                <bean class="nz.net.ultraq.thymeleaf.LayoutDialect" />
            </set>
        </property>
    </bean>

	<!-- **************************************************************** -->
	<!-- Include Spring Environment -->
	<!-- **************************************************************** -->
	<!-- <import resource="spring-exception.xml" /> -->

    <!-- Java Mail -->
    <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
		<property name="host" value="${mail.host}" />
		<property name="port" value="${mail.port}" />
		<property name="username" value="${mail.senderEmail}" />
		<property name="password" value="${mail.senderPassword}" />
		<property name="javaMailProperties">
			<props>
				<prop key="mail.transport.protocol">smtp</prop>
				<prop key="mail.smtp.auth">true</prop>
				<prop key="mail.smtp.starttls.enable">true</prop>
			</props>
		</property>
	</bean>

	<!-- **************************************************************** -->
	<!-- interceptors -->
	<!-- **************************************************************** -->
	<mvc:interceptors>
		<bean class="com.examples.project.common.interceptor.HandlerInterceptor" />
	</mvc:interceptors>

	<!-- For Multipart Upload -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />

</beans>


[참고] 

Spring 3.2로 오면서 바뀐 MVC쪽 설정


http://egloos.zum.com/cj848/v/372874


Spring 3 MVC ContentNegotiatingViewResolver Example

http://www.mkyong.com/spring-mvc/spring-3-mvc-contentnegotiatingviewresolver-example/


3.1.ContentNegotiatingViewResolver


Error Handling for REST with Spring

2. Solution 1 – The Controller level @ExceptionHandler



[2015-08-03]

Multifile Upload를 할 때, 같이 업로드 하는 파일들에게 어떤 동일한 Data를 부여하고 싶었다.
여러 개의 파일을 업로드 하기 전에 서버에서 어떤 Data를 가져오기 위해서는 어떤 이벤트를 이용하는 것이 좋을까?
onSubmit 이벤트는 여러 개의 파일들이 각자 업로드 될 때마다 불렸다.
이런 경우 가장 적당한 이벤트는 onSelect 이벤트였다.


onSelect 이벤트에서 Ajax로 Data(일련번호)를 가져와서, Hidden Type의 Input Control에 입력해 놓고, dynamicFormData 로 이 값을 서버로 넘겨주면 된다.




[2015-08-05]

onSelect 이벤트에서 어떤 조건에 의해 return false; 를 하게 되면, 동일한 파일을 다시 select 했을 때 onSelect 이벤트가 불리지 않는다. 그래서 onSelect event handler에 있던 return false; 구문을 onSelect event handler로 옮겼다.


멀티 파일 업로드 시에 onSubmit event handler에서 return false; 

를 하게 되면 다음 번 업로드 부터는 "afterUploadAll" event 가 불리지 않는다.

http://hayageek.com/docs/jquery-upload-file.php#doc 에 comment 를 달아 놓았다.



Comments