페이지 전환을 일으키지 않고 ajax 같은 느낌으로 input:file을 포함하는 form data를 전송하고 결과값을 반환받는 방법은 역시 hidden iframe을 이용하는 방식 뿐이다.


이전 포스팅에서는 같은 방식을 이용해 파일전송만 하는 법을 남겼었는데,

이건 전체 form data를 포함하는 확장 형태다.

(무슨 병신짓인가 싶겠지만 살아가면서 언젠가 한 번쯤은 쓸 일이 생길지도 모름...;)


Chrome, IE11 만 테스트 해봤음.

input:file, select, textarea의 경우 단순 clone() 으론 값을 넘길 수 없어 예외처리를 해줘야 하는 부분이 빡침.


$('#frm_id')._submit() 같은 형태로 해놓으면 submit이 일어날 때 자동으로 이루어질 수 있도록,

$.fn._submit() 같은 형태로 만들었음.

내부 로직을 분석해보면 얼마든지 분해재조합이 가능.


<script>
$.fn._submit = function(){
    // uniqid 반환
    var _getUniqid = function(prefix){
        if (!prefix) prefix = '';

        return prefix+(new Date().getTime()).toString()+(Math.floor(Math.random() * 1000000) + 1).toString();
    };

    // iframe 뚫어서 데이터 전송
    var _post = function(el_list, action, callback){
        // form의 target이자 iframe의 name
        var target_name = _getUniqid('_submit_');

        // form 생성
        var form = $('<form action="'+action+'" method="post" enctype="multipart/form-data" style="display:none;" target="'+target_name+'"></form>');
        $('body').append(form);
        // 전송할 element를 갖다 붙임
        el_list.appendTo(form);

        // iframe 생성
        var iframe = $('<iframe src="javascript:false;" name="'+target_name+'" style="display:none;"></iframe>');
        $('body').append(iframe);

        // onload 이벤트 핸들러
        // action에 데이터 전송 후 결과값을 텍스트로 받아온다고 가정하고 iframe의 내부 데이터를 결과값으로 callback 호출
        iframe.load(function(){
            var doc = this.contentWindow ? this.contentWindow.document : (this.contentDocument ? this.contentDocument : this.document);
            var root = doc.documentElement ? doc.documentElement : doc.body;
            var result = root.textContent ? root.textContent : root.innerText;

            // 임시 form 제거 전 input:file 원상복구 (전송이 실패했을 수도 있으므로)
            el_list.each(function(){
                if ($(this).attr('type') != 'file') return;

                var tmp_uniqid = $(this).attr('name')+$(this).attr('uniqid');
                $('[uniqid="'+tmp_uniqid+'"]').replaceWith($(this));
            });

            // 전송처리 완료 후 임시 form, iframe 제거
            form.remove();
            iframe.remove();

            if (typeof callback === 'function') callback(result);
        });

        form.submit();
    };

    // form.submit
    $(this).submit(function(){
        var el_list = $();

        // clone()을 통해 value를 가져올 수 있는 것들
        el_list = $(this).find('input:text,input:checkbox,input:radio,input:hidden').clone();

        // input:file
        // clone()으로 value를 가져올 수 없으므로 input:file 모양만 박아놓고 나중에 원상복구
        $(this).find('input:file').each(function(){
            var uniqid = _getUniqid();
            var tmp_input_file = $(this).clone();

            $(this).attr('uniqid', uniqid);
            tmp_input_file.attr('uniqid', $(this).attr('name')+uniqid);

            el_list = el_list.add($(this));

            $(this).replaceWith(tmp_input_file);
        });

        // textarea - 이것도 clone으로 value를 가져오지 못하므로 명시적으로 세팅
        $(this).find('textarea').each(function(){
            var tmp_textarea = $(this).clone();
            tmp_textarea.val($(this).val());

            el_list = el_list.add(tmp_textarea);
        });

        // select - selected 유지
        $(this).find('select').each(function(){
            var tmp_select = $(this).clone();
            tmp_select.val($(this).val());

            el_list = el_list.add(tmp_select);
        });

        // iframe 뚫어서 데이터 전송
        _post(el_list, $(this).attr('action'), function(result){
            // 여기서 할 일을 하면 됨.
            // response를 JSON 형태로 변환해서 콘솔 출력
            console.log(JSON.parse(result));
        });

        return false;
    });
}


$(document).ready(function(){
    $('#frm')._submit();
});
</script>




Posted by bloodguy
,