in

Dot Net Mafia

Group site for developer blogs dealing with (usually) .NET, SharePoint, Office 365, Mobile Development, and other Microsoft products, as well as some discussion of general programming related concepts.

Not Necessarily Dot Net

Real World Dojo part Two: File Upload

In my last post, I wrote about my research into doing client-side validation with Dojo (disclaimer, in case you haven't seen this a billion times before: this can never be trusted server-side...this is only a convenience for the client, not a security thing).

There's a long story in that post, but the short version is that we came up with this form:

<html>
<head>
    <title>Validation Test</title>

    <link id="themeStyles" rel="stylesheet"
    href="http://ajax.googleapis.com/ajax/libs/dojo/1.2.0/dijit/themes/tundra/tundra.css">

    <script type="text/javascript"
    src="http://ajax.googleapis.com/ajax/libs/dojo/1.2.0/dojo/dojo.xd.js"
    djConfig="parseOnLoad: true, isDebug: true"></script>
    <script type="text/javascript">
        dojo.require("dijit.form.ValidationTextBox");
        dojo.require("dijit.form.Form");
        dojo.require("dijit.form.Button");
    </script>
</head>
<body class="tundra">
    <form action="/beta/test/client_validated" method="POST"
    id="TheTest" encType="multipart/form-data" dojoType="dijit.form.Form"
    validate();" onReset="return false;">
        Name: <input dojoType="dijit.form.ValidationTextBox" type="textbox"
        name="Name" id="Name" required="true" trim="true"
        intermediateChanges="true" /><br />

        URL: <input dojoType="dijit.form.ValidationTextBox" type="textbox"
        regExp="(https?|ftp)://[A-Za-z0-9-_]+\.[A-Za-z0-9-_%&\?\/\.=]+"
        required="true" name="Url" id="Url" /><br />

        File: <input type="file" name="File" /><br />

        <button id="Submit" dojoType="dijit.form.Button">OK
            <script type="dojo/method" event="onClick">
                console.dir(dijit.byId("TheTest").attr('value'));
            </script>
            <script type="dojo/method" event="startup">
                var form = dijit.byId("TheTest");
                // set initial state
                this.attr("disabled", !form.isValid());
                this.connect(form, "onValidStateChange", function(state){
                    this.attr("disabled", !state);
                });
            </script>
        </button>
        <button dojoType="dijit.form.Button" type="reset">Reset
            <script type="dojo/method" event="onClick">
                dojo.byId("Name").value="";
                dojo.byId("Url").value="";
                dijit.byId("Submit").attr("disabled", true);
            </script>
        </button>
    </form>
</body>
</html>

(Yes, you'd think the VS "Copy as HTML Plugin" would let me do all the syntax highlighting and such.  It doesn't).

Anyway.  Aside from the fact that the Submit button does absolutely nothing, I've been totally ignoring the file input box. I could repeat countless lame-ass tutorials that show you how to post back the two fields I'm really using, but then I'd have to start over to show you how to really use the file input.  Besides, I did title this thing "Real World," and I don't want to waste anyone's time.

For reference, I'm stealing pretty much all of this from the original announcement about the Dojo Multiple FileUpload Dijit.  I'm just trying to put that into some sort of perspective for real-world use.  i.e. Something I can come back to next year, scoop up, and slap into place.

 Uploading files with Dojo is still pretty raw.  It seems like this is something that should be pretty well tamed by now, but...everything in life's a trade-off.

 Start by declaring the file uploader:

        dojo.require("dojox.form.FileUploader");

N.B. In case you haven't dug into Dojo at all, the various dojox pieces are experimental things they're considering adding to the main set of dojo widgets (dijits), in some future version.  The documentation is horrible, and you get frequent warnings that the API is subject to change without notice.  Use at your own risk.

Add an event for after the page loads to wire up the actual upload pieces:

        dojo.addOnLoad(function(){
            // Only allow uploading certain file types
            var fileMask = [
                ["Jpeg File",     "*.jpg;*.jpeg"],
                ["GIF File",     "*.gif"],
                ["PNG File",     "*.png"],
                ["All Images",     "*.jpg;*.jpeg;*.gif;*.png"]
            ];
            var selectMultipleFiles = false;

            // assign a file uploader to the appropriate button
            var uploader = new dojox.form.FileUploader({
                button:dijit.byId("chooseFile"),
                degrabable : true,
                uploadUrl: "/beta/test/upload",
                uploadOnChange: false,
                selectMultipleFiles:selectMultipleFiles,
                fileMask:fileMask,
                isDebug:true
            });

            // Actually upload the data
            doUpload = function(){
                console.log("doUpload");
                // FIXME: This really isn&#8217;t enough. Have to deal w/ metadata
                uploader.upload();
            };
        }); 

Then change the file input to this:

        <div id="chooseFile" class="browse"
        dojoType="dijit.form.Button">Select image...</div><span
        id="fileName"></span><br />

And hit the snag: using the CDN, there's a cross-site scripting issue with the flash object.  When you try to run that, you get an "this.flashMovie.setFileMask is not a function" error.

No big deal.  Forget the CDN for now and extract the full dojo distribution somewhere on my site I can access the files statically.  This changes the reference line to something like:

<script type="text/javascript" src="/static/dojo/dojo/dojo.js" djConfig="parseOnLoad: true, isDebug: true"></script>

(The actual path depends on how you have things set up on your server, of course)

Update the onClick declaration to:

            <script type="dojo/method" event="onClick">
                console.dir(dijit.byId("TheTest").attr('value'));
                doUpload();
            </script>

and the doUpload method gets called, but I don't see any evidence that it's contacting the server.  Time to dig deeper into the source code.

The problem's in the FileInputFlash constructor.  For the sake of the uploader SWF, it's converting the relative path (I'm using /beta/test/upload) to the bogus absolute path http://localhost:8080/beta/test//beta/test/upload.

It's annoying that the console isn't reporting that bogus request, but there you are.

I've submitted a bug report and a [horrible] patch to the dojo developers, but I wouldn't count on it making it into a release anytime soon.  The workaround is to keep the upload URL in the same subdirectory and just specify the last entry in the path (i.e. uploadUrl="upload").  If you really need to specify a different subdirectory, I'll be happy to share my "fix" (it's basically just hard-coding the path, but it should work for every case that springs to my mind, and it certainly works for me).

For anyone who's interested, I've attached the full HTML file.

Comments

No Comments

Leave a Comment

(required)  
(optional)
(required)  
Add
2015 dotnetmafia.
Powered by Community Server (Non-Commercial Edition), by Telligent Systems