in

Dot Net Mafia

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

Not Necessarily Dot Net

October 2008 - Posts

  • Real World Dojo part 5: Custom Components

    Introduction

    It turns out that the file upload piece from last time (the User Feedback article) is going to be used over and over.  And that I need to attach a drop-down to let the uploader specify what kind of license is associated with the file.  In the dot net world, I'd be tempted to slap the code into a user control and keep moving.  That won't work for me, and, anyway, it's probably the wrong way to do things where dojo's concerned.

    Dojo has its own support/library system for creating reusable components.  For background on this, check out the official Dojo book's chapters on creating your own widgets (or just skip that and read this if you're in a hurry for the nutshell version...that article's a bit out of date, and it leaves out a ton of important details). There's a lot going on here, which is why it looks as convoluted as it does.  Things get more complex when the developers make them more flexible.

    Infrastructure

    Start by adding something similar to the following in a script block in the head of your html:

    dojo.registerModulePath("internal", "../internal");
    dojo.require("internal.composites.PictureUploader");

    where internal (or whatever you choose to call it) is a subdirectory of your Dojo installation parallel to the dojo, dijit, and dojox directories.

    If you try to load the page now, it tries to load the file internal/composites/pictureUploader.js and errors out. So the next step is to create that.

    The skeleton pieces look like this:

    dojo.provide("internal.composites.PictureUploader");

    dojo.require("dojox.form.FileUploader");
    dojo.require("dijit.form._FormWidget");
    dojo.require("dijit._Templated");

    dojo.declare("internal.composites.PictureUploader",
        [dijit.form._FormWidget, dijit._Templated],
        {
            //  summary: A composite picture uploader and license selector
            //
            // description: A control that lets you choose an image file, associate a
            // license, and then upload them. You'll have to deal with the license
            // yourself
        });

    If you've looked at dojo modules at all, this is pretty straightforward.  In case you haven't, I'll break that down:

    What is this file "providing"?

    dojo.provide("internal.composites.PictureUploader");

    Which other pieces/controls do we need? This will also allow you to remove the reference from your page.

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

    More required bits.  These are specific to creating your own widget:

    dojo.require("dijit.form._FormWidget");
    dojo.require("dijit.form._Templated");

    Then define your widget's class.  The first parameter is the class name (with name spaces).  The second is the parent class and whichever mixins it gets.  The third is a hash table of class members.

    dojo.declare("internal.composites.PictureUploader",
        [dijit.form._FormWidget, dijit._Templated],
        {
            //  summary: A composite picture uploader and license selector
            //
            // description: A control that lets you choose an image file, associate a
            // license, and then upload them. You'll have to deal with the license
            // yourself
        });
     

    You don't actually have to include the summary and description comments, but it's probably a good idea to stick with dojo's coding standards.

    Some Member Variables

    Inside that hash table, add some variables that let me customize a few things (I'm still hard-coding far too much, but I'm really not going for anything general-purpose here).

            // The title of the "Browse for file" button
            browse_label: "Browse...",

            // ID of the Button to replace
            upload_button_id: "chooseFile",

            // Where to post to when uploading
            upload_url: "you must specify this",

            // ID of whichever element to use to indicate the name of the file
            file_name: "fileToUpload",

            // ID of the element that tracks upload progress
            upload_progress: "uploadProgress",

            // ID of the element that shows the results
            upload_results: "uploadResult",

     And specify where the "template" file is located:

            templatePath: dojo.moduleUrl("internal.composites",
            "PictureUploader/PictureUploader.html"),

    Note that those lines are all separated by commas.  You're declaring a dictionary, not writing javascript statements.

    The Template File

    Now we need to create that template file. It's really just a matter of cutting and pasting the relevant portions from our existing form.

    <div class="InternalPictureUploader">
            <div id="${upload_button_id}" class="browse"
            dojoType="dijit.form.Button">${browse_label}</div><span
            id="fileName"></span><br />
            <div id="${file_name}"></div>
    </div>
    <div class="InternalPictureUploaderProgress">
        <div id="${upload_progress}"></div>
        <div id="${upload_results}"></div>
    </div>

    The ${} things are replaced by member variables of the UploadPicture widget.

    Add some more dojo magic stuff, to make the control more flexible (and stylable) when it's declared:

        <div class="InternalPictureUploaderFileName" dojoAttachPoint="fileName"
            id="${file_name}"></div>
        <div class="InternalPictureUploaderProgress" dojoAttachPoint="progress"
            id="${upload_progress}">
        <div class="InternalPictureUploaderResults" dojoAttachPoint="result"
            id="${upload_results}"></div>

    (I'm not going into the why's/wherefore's of dojoAttachPoint here.  It's a huge topic, the documentation about it is pretty scanty, and I don't feel qualified to comment on it at this point).

    At this point, I had to comment out all the pieces in the head of my HTML that referred to any of these pieces.  Basically, all the code that has anything at all to do with uploading.

    Declaring Your Widget

    In your HTML, replace the pieces we've refactored into the control with a control declaration:

            <div dojoType="internal.composites.PictureUploader"></div>

    Debugging and Gotchas

    At this point, the page errors out trying to load ValidationTextBox.  The error message in the console is "too much recursion," but it happens right after it loads the FileUploader, which seems suspicious.  Besides, the error goes away when I comment out that control.

    Looking at the stack trace, the problem starts when dijit.form._FormWidget tries to call dijit._Templated.create().

    A quick google revealed that the problem came from using that old tutorial I recommended at the beginning as my basis.  dijit.form._FormWidget now mixes in dijit._Templated.  When I tried to mix it in as well, it caused Bad Things®.

    Fixing that left me with a "node is undefined" error. The error originally seemed coming from my templating file.  When I switched to a hard-coded template string in the class members dictionary, the HTML did get rendered, but the error did not go away. Adding some logging to the relevant dojo source code revealed that the error happens when I (or dojo's internal magic, rather) try to set the id attribute of the widget.

    More specifically, it was trying to set an attributes named id and tabIndex to the value specified in my template file (or something it magically generates).  That attribute is actually trying to get attached to a DOM node associated with the 'command' focusNode.

    (Not that focusNode is not actually a command.  It's the value of the dojoAttachPoint attribute that needs to be assigned to some focusable DOM node).

    Adding that value to the file upload button in my template made the errors go away:

        <div id="${upload_button_id}" class="browse" dojoAttachPoint="focusNode"
            dojoType="dijit.form.Button">${browse_label}</div><span
            id="fileName"></span><br />
     

    Making it Do Something

    That seems like a ridiculous amount of trouble to get a single visible div that does absolutely nothing.  It's time to restore the code that does the uploading.

    Again, that's mostly a matter of cut and paste.  Cut the pieces that were commented out of the HTML and paste them into a function associated with a key named startup in the control's "class body."

            startup:function(){
            // ...all that code
            },

    Then replace all those ID strings that we'd been hard-coding with the names of the new member variables.  (e.g.                         dojo.byId(this.fileName).innerHTML += "File to upload: " +
                                d.name+" " + Math.ceil(d.size*.001)+"kb \n";
    becomes                         dojo.byId(this.file_name).innerHTML += "File to upload: " +
                                d.name+" " + Math.ceil(d.size*.001)+"kb \n";

    Add a wrapper around the file upload control:

                upload = function(){
                    this.uploader.upload();
                }

    And update your HTML's doUpload() method to call that:

                doUpload = function(){
                    // Actually upload the file
                    var uploader = dijit.byId("pictureUploader");
                    uploader.upload();

                    // And submit the metadata
                    metaDataSubmit();
                };

    And run headfirst into a brick wall.  No matter what I tried, the button widget was returning as null when I tried to access it in my startup method.

    So I whittled away everything extraneous and took it to #dojo on IRC.  neonstalwart and slightlyoff (thanks again!) were kind enough to look at my mess and straighten me out.

    In the example that I gave them, I had my widget declared with these "parents":

    [dijit._Templated, dijit._Widget],

    which was completely backwards.  dijit._Widget is designed as a base class. dijit._Templated is a mixin that adds functionality. Trying to use it as the "actual" base class causes all sorts of nastiness. (Yes, I switched from deriving from FormWidget. This just seemed cleaner).

    Since I want widgets in my template to be expanded, I also needed to set dijit._Templated.widgetsInTemplate to true.  This isn't done by default, for performance reasons.

    Finally, using a widget's ID the way I was is considered a horrible practice.  The correct way to do this is to set a string as a dojoAttachPoint (I mentioned that thing's important, didn't I?), declare that as a member variable in my widget (defaulting to null), and just reference by name as needed:

    [dijit._Widget, dijit._Templated],
        {
            uploadButton: null,
            uploader: null,
            widgetsInTemplate: true,

            templateString: "<div id=${id} ><div " +
            "dojoAttachPoint='focusNode, uploadButton'" +
            "class='browse' " +
            "dojoType='dijit.form.Button'>" +
            "Browse...</div></div>",

            //startup: function(){
            postCreate: function(){

            var btn = this.uploadButton;
           console.debug('Upload Button: ' + btn);
            ...

    Getting Back to Making it Do Something

    Now, all of my events are wired up incorrectly. The methods are all getting bound to dojo's global context rather than my widget.Changing dojo.connect to this.connect fixes that problem.

    Also, it might not be quite as efficient (in terms of bandwidth), but it feels cleaner to me to make the event handlers into member variables rather than anonymous inline functions

    For example:

    _onChange: function(dataArray){
      // ...all the event-handling code
    },

    And call

    this.connect(this.uploader, "onchange", "_onChange"); in postCreate()

    That is actually a shortcut for dojo.connect(this.uploader, "onChange", dojo.hitch(this, "_onChange"));. dojo.hitch() is an incredibly important function that connects methods to a specific context (or object). I've run across several occasions where things you'd expect to "just work" need dojo.hitch() because something else changed the meaning of this. (The first two that bit me were forEach() and these event wireups).  I don't know yet whether this is a dojo wart or just a javascript limitation.

    The different pictures that my end-users might upload can be associated with various Creative Commons licenses.  I added a combo box to let the user decide which license is appropriate for a given picture.  It feels odd to have something like that list hard-coded (an excuse for anyone who looks at my source), but it's not as if the different possible choices will be changing very often.

    I ran across one final gotcha when I was working on some final cleanup. I tried to seperate the "requires" in the template file by including a script block and specifying which widgets I was using there, as opposed to the ones that I referenced in the .js file.  This led to my widget silently failing to load.

    For anyone who's wants to scan over the final version, I'm attaching the source to my widget (and its template) and a test file that uses it.

  • Real World Dojo part Four: User Feedback

    So now we have a simple form that uses AJAX to upload a file and submits some metadata for the server to associate with that file.

    It doesn't really give any useful feedback, though.  No real end-user's going to read the console, I'm not actually doing anything with the file upload progress, and using an alert to show that the metadata uploaded is incredibly lame.

    Easy part first.  The response from the XHR. Add a div for feedback:

         <div id="metadataFeedback"></div>

    And let's make the response handler do something vaguely interesting:

                            if(typeof data == "error"){
                                alert("Error!");
                                console.log(args);
                            }else{
                                dojo.fadeOut({node: box, duration:0}).play();
                                box.innerHTML = data;
                                dojo.fadeIn({node: box, duration:5000, delay:500}).play();
                            }

    (I did warn you that it was only vaguely interesting).

    The point to doing the fadeOut first is to avoid animation flicker while the DOM is being updated.

    Feedback about the file upload is a tad bit more involved.

    Add some divs for tracking upload progress:

        <div id="uploadProgress"></div>
        <div id="uploadResult"></div>

    So far, I've limited these articles to using events assigned in the markup.  Now we have to scrape the surface of Dojo's rich event system.  The nutshell version is that, in an addOnLoad method (or whenever else seems appropriate) you connect various named events to whatever function you want to fire when that event happens.

    For  starters, let's inform the user that we realize they've selected a file:

                dojo.connect(uploader, "onChange", function(data){
                    dojo.forEach(data, function(d){
                        if(selectMultipleFiles){
                            dojo.byId("fileToUpload").innerHTML += "File to upload: " +
                                d.name+" " + Math.ceil(d.size*.001)+"kb \n";
                        }else{
                            dojo.byId("fileToUpload").innerHTML = "File to upload: " + d.name
                                + " " + Math.ceil(d.size*.001)+"kb \n";
                        }
                    });
                });

    Letting the user know that the upload is complete should be this simple:

                 dojo.connect(uploader, "onComplete", function(data){
                    console.log("Upload complete");
                    console.dir(data);
                    dojo.forEach(data, function(d){
                        dojo.byId("uploadProgress").innerHTML = d.name;

                        // FIXME: Actually, want to display the uploaded picture
                        dojo.byId("uploadResult").innerHTML = "Finished: " + d;
                        console.dir(d);
                    });
                });

    For whatever reason, that event isn't firing.  That forces me to shoehorn things in the progress event:

                dojo.connect(uploader, "onProgress", function(data){
                    dojo.byId("uploadProgress").innerHTML = "";
                    // Think the forEach is for handling multiple files
                    dojo.forEach(data, function(d){
                        var progress = "(" + d.percent + "%) " + d.name;
                        dojo.byId("uploadProgress").innerHTML += progress;

                        var movie = uploader.flashMovie;

                        // Kludge because onComplete isn&#8217;t getting called:
                        if(d.percent == 100){
                            // Do another AJAX postback to get the URL to the image

                            dojo.xhrGet({
                                url: "/beta/test/get_url?id=some_guid",
                                handleAs: "text",
                                handle: function(data, args){
                                    var box = dojo.byId("metadataFeedback");

                                    if(typeof data == "error"){
                                        // Unfortunately, w/ web2py, we never actually get here
                                        alert("Error!");
                                        console.log(args);
                                    }else{
                                        console.log("URL: " + data);
                                        var result = dojo.byId("uploadResult");
                                        result.innerHTML = '<img src="' + data + '" />'
                                    }
                                }
                            });
                        }
                    });
                });

    Obviously, your server should be handling the file upload and be able to return a URL to the newly added picture.

    Have I mentioned before that Dojo's file-handling abilities seem to leave a bit to be desired?

     

  • Real World Dojo part Three: AJAX

    When we finished up last time, we had an AJAX-ified form that uploads an image file.

    The problem now is that the "metadata" (the name and URL) are being completely ignored.  It's ugly, but try adding them as GET variables to the upload path:

    It seems like I should just be able to update the uploadUrl right before calling doUpload():

                    var name = dojo.byId("Name").value;
                    var url = dojo.byId("Url").value;
                    uploader.uploadUrl += "?name=" + escape(name);
                    uploader.uploadUrl += "&url=" + escape(url);

    but that doesn't work.  The SWF is given its upload URL when it's created.  The File Uploader object doesn't really have all that much interaction with it after that.

    Oh, well.  It's not like that would be a valid long-term fix anyway (the real page that this is a proof-of-concept for has too many variables to put into the GET part of the URL).

    So it's time to do the "AJAX thing."  After all, Dojo started out life as an AJAX library, right?  (Actually, I'm not at all sure of that.  They very well might have been planning a full-featured javascript library from Day One.  After all, the AJAX stuff is really just a tiny portion of what Dojo does).

    It's not like there's much to this:

                var metaDataSubmit = function(){
                    dojo.xhrPost({
                        url: "/beta/test/assign_metadata",
                        form: "TheTest",
                        handleAs: "text",
                        handle: function(data, args){
                            if(typeof data == "error"){
                                alert("Error!");
                                console.log(args);
                            }else{
                                alert(data);
                            }
                        }
                    });
                };

    and add a call to that around the time you call uploader.upload();

    url is where the postback goes to.  form is the ID of the form that holds the values of interest.  handleAs is where things get interesting.  Change it to "json" and you can actually return javascript objects.  handle is the function that gets called after the postback gets a response.

    Of course, this implementation's complete nonsense.  In the real world, you need to assign some sort of ID (and some sort of security validation) to the requests so you can coordinate them.  Otherwise, how would you know which file went with which metadata?

    Since that's really server-side stuff, I'll ignore actually generating that for now.

    I feel odd writing such a short post, but that's really all there is to this.

  • 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.
  • Real World Dojo part One: Form Validation

    Real World Dojo, part One (Basic Validation)

    The Scenario:

    I’ve kind of been nibbling around the edges of Dojo for a while, but I’m at a place in this project where I really need to buckle down and learn it. Since I’m having so much trouble finding real-life examples of the basics, I figured I’d share what I’m coming across.

    I have a very simple proof-of-concept form that shows the basics of the one that forced me to buckle down. It looks like this:

    <head>
    <title>Validation Test (raw HTML)</title>
    </head>
    <body>
    <form action="/beta/test/client_validated" method="POST">
    Name: lt;input type="textbox" name="Name" /><br />
    URL: <input type="textbox" name="Url" /><br />
    File: <input type="file" name="File" /><br />
    <input type="Submit" value="OK" />
    </form>
    </body>
    </html>

    (For now, the server action just verifies that the Name and URL fields have values).

    In the real-world example, there’s a required field that’s very easy to forget. And it’s easy to forget that the database is picky about URL’s.

    If this were an ASP.NET application, I’d just toss in a couple of validators and move on. That isn’t an option for the platform we’re using, so here we go.

    Switch to Dojo Fields

    My first stab was just blindly following the tutorials I could find, adding in the Dojo infrastructure and marking a couple of fields as required:

    <html>
    <head>
    <title>Validation Test (switch to Dojo)</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");
    </script>
    </head>
    <body>
    <form action="/beta/test/client_validated" method="POST">
    Name: <input dojoType="dijit.form.TextBox" type="textbox" name="Name"
    required="true" trim="true" intermediateChanges="true" /><br />

    URL: <input dojoType="dijit.form.ValidationTextBox" type="textbox"
    dojo.regExpGen="dojox.regexp.url" required="true" name="Url" /><br
    />

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

    <input type="Submit" value="OK" />
    </form>
    </body>
    </html>

    Switch to Dojo Form

    Sadly, it wasn’t that simple, so I took the next step and switched everything except the file element to a Dojo widget (a.k.a. dijit):

    <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" required="true" trim="true" intermediateChanges="true" /><br />

    URL: <input dojoType="dijit.form.ValidationTextBox" type="textbox"
    dojo.regExpGen="dojox.regexp.url" required="true" name="Url" /><br />

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

    <button 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</button>
    </form>
    </body>
    </html>

    I snagged the actual validation pieces from Dojo’s online unit test suite (non-automated). The important pieces were at Dojo Test Form Validation State. That portion of their website is one of the best resources I’ve found as far as seeing how to actually do something. (If all else fails, read the source code...Dojo’s source code is extremely well-written and -commented, but it’s lacking when it comes to usage examples).

    The URL regular expression validator isn’t working correctly (it’s accepting anything I input), and the Reset button doesn’t do anything. But it’s getting closer.

    Change the Reset button’s declaration to:

    <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>

    (I don’t know why that didn’t work automatically...I’m probably declaring the button wrong) and the regular expression part of the URL input to

    [http|https|ftp]://[A-Za-z0-9-_]+\\.[A-Za-z0-9-_%&\?\/.=]+
    (dojox.regexp.Url is in the development trunk, but it didn’t make it into 1.2.0 apparently), and we have something. (Yes, that regexp is pretty weak. But it’s good enough for what I’m doing).

     The final version looks like:

    <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>

    I’m tempted to take out the intermediate steps, so this isn’t so
    long. But I think they might be useful to anyone who was having the same
    problems I did.

    Of course, this form doesn’t actually do anything. I’ll save that for next time...this bad boy’s too long as it is.

  • Manipulating the DOM with Dojo

    This is the area where jQuery rules the roost. Or so everything I've read tells me.  So, how does Dojo stack up?

    Still running this in parallel with Kyle's series:

    1. Setting the contents of an element
      // Convenience function to hide query details
      var element = dojo.byId('whatever');
      element.innerHTML = "Change to this";
      Or you could chain it jQuery style:
      dojo.byId('whatever').innerHTML = "Change to this";
      Really just a matter of personal preference and what you find more readable.
    2. Adding a new html element
      var l = dojo.byId("list");
      var li = document.createElement("li");
      li.innerHTML="Three";
      l.appendChild(li);
    3. Removing content
      li = dojo.byId("garbage");
      dojo._destroyElement(li);
    4. Setting css properties (actually, there are a ton of helper functions for setting individual styles, but here's the generic case):
      node = dojo.byId("whatever");  // or use dojo.query
      dojo.style(node, "color", "salmon");
      
    5. Iterating over the matched set
      When you use a dojo query (using CSS3 selectors), you can manipulate all the results automatically:
      l = dojo.query("li");
      l.style("border", "medium double black");

    jQuery wins this one hands down.  But you probably wouldn't be doing much of this sort of thing with dojo anyway.  You'd be too busy working with higher level abstractions.

  • Selectors in Dojo

    Selecting elements in dojo is really a very tiny piece of what it does.  It uses CSS 3 selectors.  If you haven't looked into them, check out CSS 3 selectors explained. It's a little disheartening, because IE still doesn't support most of these features (the IE 8 beta seems to have limited and buggy CSS 2.1 support), but dojo does.

    Actually, anything I write about dojo selectors would be redundant.  Glance over the list at Dojo Selectors to get an idea of just how flexible and powerful this approach is. Some of the comments on the page tell you that it definitely isn't 100%, but it's still pretty impressive.

     Just to stay parallel with Kyle's post, though, here are a couple of examples:

    // Set the text color of the element with an id of "one" to blue:
    var one = dojo.query("#one");
    one.style("color", "blue");
    // Give all div elements with class foo a red background
    var foo = dojo.query("div.a");
    foo.style("backgroundColor", "red");
    /* Take everything that has an attribute custom with a value of whosit and give it a border:*/
    custom = dojo.query("*[custom='whosit']");
    custom.style("border", "medium double black");

    There really isn't much significant difference between dojo and jquery on this front, except that jquery uses its magic $ function, and dojo loops through selected elements automatically, when you're using it this way.

    Edit: It strikes me as a little ironic that I decided to write this just now, because I just found out that dojo released version 1.2 today.

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