18

Im building a file upload with jQuery, but Im getting a jQuery error trying to set the attributes of the form:

$(document).ready(function () {
    $("#formsubmit").click(function () {

        var iframe = $('<iframe name="postframe" id="postframe" class="hidden" src="about:none" />');

        $('div#iframe').append(iframe);

        $('#theuploadform').attr("action", "/ajax/user.asmx/Upload")
        $('#theuploadform').attr("method", "post")
        $('#theuploadform').attr("userfile", $('#userfile').val())
        $('#theuploadform').attr("enctype", "multipart/form-data")
        $('#theuploadform').attr("encoding", "multipart/form-data")
        $('#theuploadform').attr("target", "postframe")
        $('#theuploadform').submit();
        //need to get contents of the iframe

        $("#postframe").load(
            function () {
                iframeContents = $("iframe")[0].contentDocument.body.innerHTML;
                $("div#textarea").html(iframeContents);
            }
        );
    }
);


<div id="uploadform">
    <form id="theuploadform" action="">
        <input id="userfile" name="userfile" size="50" type="file" />
        <input id="formsubmit" type="submit" value="Send File" />
    </form>
</div>

<div id="iframe" style="width: 0px; height: 0px; display: none;">
</div>

<div id="textarea">
</div>
ptCoder
  • 2,229
  • 3
  • 24
  • 38
  • What is the purpose of this line? `$('#theuploadform').attr("userfile", $('#userfile').val())` the browser doesn't actually have access to the value of file inputs due for security reasons, it will return some bogus path. – Kevin B Oct 26 '11 at 21:35
  • You are wrong, you can read the value, but not change it. –  Oct 26 '11 at 21:39
  • 1
    What does the value return? I did say that it will return a value, just not the actual path to the file. – Kevin B Oct 26 '11 at 21:52
  • 2
    http://stackoverflow.com/questions/5903323/cant-get-value-of-input-type-file –  Oct 26 '11 at 22:08

5 Answers5

54

I found the solution. This code works:

<script type="text/javascript">

    $(document).ready(function () {

        $("#formsubmit").click(function () {

            var iframe = $('<iframe name="postiframe" id="postiframe" style="display: none"></iframe>');

            $("body").append(iframe);

            var form = $('#theuploadform');
            form.attr("action", "/upload.aspx");
            form.attr("method", "post");

            form.attr("encoding", "multipart/form-data");
            form.attr("enctype", "multipart/form-data");

            form.attr("target", "postiframe");
            form.attr("file", $('#userfile').val());
            form.submit();

            $("#postiframe").load(function () {
                iframeContents = this.contentWindow.document.body.innerHTML;
                $("#textarea").html(iframeContents);
            });

            return false;

        });

    });

</script>


<form id="theuploadform">
    <input id="userfile" name="userfile" size="50" type="file" />
    <input id="formsubmit" type="submit" value="Send File" />
</form>

<div id="textarea">
</div>
Zemljoradnik
  • 2,672
  • 2
  • 18
  • 24
  • thank you so much I spent ages trying to work it out, for those who may find it now, note that you should use e.preventDefault not return false and remove the iFrame at the start of the function if it exists if you plan to upload multiple files using this – Gabriel Sadaka Jul 11 '13 at 05:17
  • 1
    Thank you, simple code and do the job, better than all the tutorials i've read! – IgorOliveira Oct 03 '13 at 16:53
  • Doesn't appear that the form.attr("file", $('#userfile').val()) does anything. It contains the name of the file, but that gets posted by the browser automatically. – geekinit May 28 '14 at 12:35
  • For the people copying this code - iframe should have a closing tag, it's not self-closing, although it may work like this in some browsers. – Zemljoradnik Aug 22 '14 at 12:14
  • Just for the sake of completeness, the ability to read the iframe's content is only possible if you are accessing it in the same domain. Other domains will throw a security error – intractve Oct 25 '14 at 17:31
  • With this method, is there a way to pass a csrf_token like in django? I tried this: form.attr('csrfmiddlewaretoken', $("#csrf").val()); to no avail – user875139 Aug 04 '15 at 15:44
3

It's not an official plugin, however here's an example on how you could wrap the form's submitting logic into a plugin.

Example:

<form method="post" enctype="multipart/form-data">
    <input name="file" type="file" />

    <input type="text" name="test" />

    <button type="submit">Submit</button>
</form>

<script>
    $('form').submit(function (e) {

        //prevent default submit
        e.preventDefault();

        //submit through frame
        $(this).frameSubmit({
            done: function (form, frame, options) {
                console.log('done!');
            },
            fail: function (form, frame, options) {
                console.log('fail!');
            },
            always: function (form, frame, options) {
                console.log('always!');
            }

            //custom hasError implementation if needed
            //by default if the frame's body HTML contains the text "unexpected error" or "server error"
            //it is treated as an error
            /*,hasError: function (frame) {
                return false;
            }*/
        });
    });

</script>

PLUGIN

!function ($, doc) {
    var _frameCount = 0,
        _callbackOptions = ['done', 'fail', 'always'],
        _hasFailed = function (frame) {
            var frameHtml = $(frame).contents().find('body').html();

            return /(server|unexpected)\s+error/i.test(frameHtml);
        },
        _createFrame = function () {
            return $('<iframe>').prop('name', 'jq-frame-submit-' + _frameCount++).hide().appendTo(doc.body);
        };

    $.fn.extend({
        frameSubmit: function (options) {

            return this.each(function () {
                var deferred = $.Deferred(),
                    form = this,
                    initialTarget = form.target,
                    hasTarget = form.hasAttribute('target'),
                    hasFailed = options.hasFailed || _hasFailed,

                    //The initial frame load will fire a load event so we need to
                    //wait until it fires and then submit the form in order to monitor
                    //the form's submission state.
                    $frame = _createFrame().one('load', function () {
                        $frame.one('load', function () {
                            deferred[hasFailed(this) ? 'reject' : 'resolve'](form, this, options);
                            $frame.remove();
                        });

                        form.submit();

                        //restore initial target attribute's value
                        if (hasTarget) form.target = initialTarget;
                        else form.removeAttribute('target');
                    });

                //attach handlers to the deferred
                $.each(_callbackOptions, function (i, optName) {
                    options[optName] && deferred[optName](options[optName]);
                });

                //make sure the form gets posted the to iframe
                form.target = $frame.prop('name');
            });
        }
    });
}(jQuery, document);
plalx
  • 42,889
  • 6
  • 74
  • 90
1

Your form target should be the same as iframe name, for example:

<form target="frame" 
      action="http://posttestserver.com/post.php?dir=example" 
      method="post"
      enctype="multipart/form-data">
    <input name="file" type="file"/>
</form>

<iframe name="frame"></iframe>

And after this you can attach event to input button to listen for 'change'. Furthemore you can get progress from server using jsonp and all of this will work in any browser event IE3+. Something like this:

$('input').change(function () {
    $('form').submit();
});

$.getJSON('/echo/jsonp/?callback=?', function(e, progress) {
    console.log(progress);
});
AuthorProxy
  • 7,946
  • 3
  • 27
  • 40
1

This is a good plugin to upload files using ajax

http://jquery.malsup.com/form/#file-upload

Ajouve
  • 9,735
  • 26
  • 90
  • 137
0

You know instead of fetching the same item multiple times, why not just reuse it:

var form = $('#theuploadform');
form.attr("action", "/ajax/user.asmx/Upload");
form.attr("method", "post");
// and so on

What kind of error are you having? can you post it?

UPDATE

Since you cannot set the attribute yourself here is a work around:

Put the form in an iframe, and attach an onchange event to the input button. when the user select a file, you trigger the necessary code to upload the file (submit), then the parent window can close the iframe.

Ibu
  • 42,752
  • 13
  • 76
  • 103