Thursday, December 03, 2009 by Per Ploug Hansen

While Niels is tinkering with the client part of the skinning, I thought I would go back to web developement basics and do some old-fashion Html and javascript.

The current blog comment form is a bit dated, and lacks some polish. I decided to rewrite it, to first of all get some cleaner html, but also to add the following details:

  • Nice clean validation with jquery.validation
  • ajax form posting
  • Gravatar preview "as you type"
  • Compatibility for screen-readers

The plan is to:

  1. Create the server-side logic that creates the comment
  2. Connect that logic to the client, using /base
  3. Do simpel form markup, no runat="server" attributes
  4. Write some simpel jquery to post values to /base
  5. prettify the form
  6. Make it work without javascript

The server-side logic:

For this I created a simple static method with a single parameter, the ID of the blog post. It creates a comment and fills in the different properties: email, name, comment etc.

The way it receives input is through the HttpContext request collection, those who remember Asp 3.0 classic will recognise this approach, so no view-state hell. I've added the entire method below. It contains a couple of comments to guide you. The main points are:

  1. Get values from HttpContext request collection
  2. Check they are valid
  3. Test the blog post exists and it's the right content type
  4. Create the document and fill in the properties
  5. Save and publish
  6. Return the ID of the page created

public static int CreateComment(int parentId)
{

//here we find form values posted to the current page
    HttpRequest post = HttpContext.Current.Request;
    string email = post["email"];
    string comment = post["comment"];
    string name = post["name"];
    string website = post["website"];


    //if all values are there + valid email.. we start to create the comment
    if (!string.IsNullOrEmpty(email) && isValidEmail(email) && !string.IsNullOrEmpty(comment) && !string.IsNullOrEmpty(name))
    {

        Document blogpost = new Document(parentId);

        //if parent is actually a blogpost
        if(blogpost != null && blogpost.ContentType.Alias == "BlogPost"){
            DocumentType dt = DocumentType.GetByAlias("BlogPostComment");
            Document d = Document.MakeNew("RE: " + blogpost.Text + " by: " + name, dt, new umbraco.BusinessLogic.User(0), blogpost.Id);
            d.getProperty("comment").Value = comment;
            d.getProperty("email").Value = email;
            d.getProperty("name").Value = name;
            d.getProperty("website").Value = website;
            d.Save();
            d.Publish(new umbraco.BusinessLogic.User(0));

            umbraco.library.UpdateDocumentCache(d.Id);
            return d.Id;
        }
    }


    //if nothing gets created, we return zero
    return 0;
}

Connecting the serverside logic to the client

Now that we have some code that can create the comment, we want to make it accessible directly from the browser. For that we use the built in /base, which in short works as an automatic proxy between server-side code and the browser by creating urls that can execute this server side code.

The connection happens in the file /config/restExtensions.config

In here we map the dll Umlaut.Umb.Blog.dll in a <ext> element, add a nice alias, and then add the method CreateComment to the manifest as well:

<ext assembly="/bin/Umlaut.Umb.Blog" type="Umlaut.Umb.Blog.Library.Base" alias="Blog4Umbraco">
    <permission method="CreateComment" returnXml="false" allowAll="true" />
</ext>

this means that we can now invoke the method by going to the url /base/Blog4Umbraco/CreateComment/[pageId].aspx we will use this later when jquery will post the form.

Form markup

The great about doing the form without any asp.net controls is that you rediscover how simple and great html actually is. Install the blog package from: http://nightly.umbraco.org/Blog4Umbraco/2.0.3/ and view the /usercontrols/ajaxcommentform.ascx to see the full markup.

The below sample is the markup for a single field:

<p class="commentField emailField">
    <label for="tb_email" class="fieldLabel">
        <%= Umlaut.Umb.Blog.BlogLibrary.Dictionary("#Email","Email address") %>:
    </label>
    <input type="text" id="tb_email" name="email" class="input-text required email" />
</p>

Things to notice: We have dictionary support for labels (first parameter is the key, and the second is the value to return if the key doesn't exist), and we use jquery.validation for validating the form on the client. We do this by adding the classes "required" and "email" to this field. Which ensures that the field is filled out and is a correct email.

Post values to /base url with jQuery

Finally, the core functionality! When the form has been filled out we want to post it with ajax to the /base url, which will handle the comment creation. We do this with jQuery.Post

var url = "/base/Blog4Umbraco/CreateComment/<umbraco:Item field="pageID" runat="server"/>.aspx";

jQuery.post(url, { name: jQuery("#tb_name").val(), email: jQuery("#tb_email").val(), website: jQuery("#tb_website").val(), comment: jQuery("#ta_comment").val() },
                   function(data){
                     jQuery("#commentPosted").show();
                   }
                   });

First we build a url variable. As you ca see it points to /base/Blog4Umbraco/CreateComment/ and we then insert the current page Id on the server side. jQuery then posts a collection of values (email,name,etc) to the /base url. and displays some feedback.

Presto, we have an ajax form.

Prettify

While we are at it, we'll also add a gravatar preview, so when you enter your email, you will see your gravatar image to the right:

image

This is also done with jquery and /base:

jQuery("#tb_email").blur(function(){
                var email = jQuery("#tb_email").val();
                if(email != ""){
                    var url = "/base/Blog4Umbraco/GetGravatarImage/" + email + "/80.aspx";
                    jQuery.get(url, function(data){
                        if(data != ""){
                             jQuery("#gravatar").css( "background-image","url(" + data + ")" ).show();
                        }else{
                            jQuery("#gravatar").hide();
                        }
                    });
                }
          });

Again, we send simple request to a /base method, this time with 2 parameters, email and image size. The call returns a url to the gravatar image associated with the email.

Make it work without javascript

The great thing about having the form as an ajax form is that automated comment spam doesn't get through. However, neither does people using screenreaders. To ensure that screenreaders can use the form, we need to ensure that it works with javascript turned off. Luckily, we already have the serverside code that expects a collection of values in the Request collection. And when javascript is turned off, the form will post back to it's own url, due to the standard asp.net form. This means that we can execute the /base serverside logic on the page load event in the AjaxCommentForm.ascx like this:

protected void Page_Load(object sender, EventArgs e)
        {
            if (Page.IsPostBack)
            {
                Library.Base.CreateComment(umbraco.presentation.nodeFactory.Node.GetCurrent().Id);
            }
        }

Notice it is the exact same method, it uses the request collection so it works no matter if its executed when a form is post to a /base url, or if the form is post to the page with the macro on.

More

There has just been posted 2 videos to umbraco.tv about umbraco /base and a video walkthrough of the code explained above. So to get even more info on how to do ajax and umbraco, go to:

http://umbraco.tv/documentation/videos/for-developers/base-and-ajax-development

Also, the code is on codeplex: http://blog4umbraco.codeplex.com/ and as a nightly build: http://nightly.umbraco.org/Blog4Umbraco/2.0.3/

9 comment(s) for “Blog 4 umbraco 2.0.3 – Ajax Comment Form”

  1. Gravatar ImageWarren Buckley Says:

    Great work Per.
    As I was thinking about doing this for my own blog a little while ago.

    http://screenr.com/hj7

    But then I got busy I never fully implmented it into my blog.

    Thsi blog package is gonna kick ass, will be using this for my new site. It will save me alot of time.

    Warren :)

  2. Gravatar ImageLee Kelleher Says:

    Ace! It's really starting to come together! Good work so far.

    Like the Gravatar previews - along with all the other Ajax stuff!

  3. Gravatar ImageChris Houston Says:

    Hi Per,

    All sounds great, I look forward to seeing the finally results of the team's hard work :)

    Just wondered why for the Gravitar you need to post back to your own site to then retrieve the URL for the image, which I assume actually comes from Gravitar?

    I assume it's something to do with security or a client ID that needs to be passed to Gravitar?

    Cheers,

    Chris

  4. Gravatar ImagePer Ploug Hansen Says:

    @chris there is no reason other then javascript by default does not have a md5 hashing method, so I send it back to the server to hash the email.

    You can do it with pure javascript, but need a big chunk of code to do it.

  5. Gravatar ImageLee Says:

    This is awesome... Videos are brilliant! Thanks

  6. Gravatar ImagePetr Snobelt Says:

    Hi, nice tutorial. Will be great if there is some check for double post of comment, otherwise F5 or doubleclick on submit button can cause doublepost

  7. Gravatar ImageTim Says:

    I got some problems with the gravatar preview. At first I want to change to default image to something else, how can I do this?

    Where can I see what the "/base/Blog4Umbraco/GetGravatarImage/" method does? What parameters can it take?

    The second think is, that I can't get a preview image of any existing gravatar image, I can only see the default "G" image when I type in a fantasy email adress. But if I add my real adress, which is registered with gravatar I can't see my image and I'm not able to post my comment. I'm only getting: "Your comment could not be posted, we're very sorry for the inconvenience"

  8. Gravatar ImageKenneth Blomgren Says:

    I got big problems to finde the cs file in umbraco, that solve the problem with gravatar.

  9. Gravatar Imageaion gold Says:

    Hi, nice tutorial. Will be great if there is some check for double post of comment, otherwise F5 or doubleclick on submit button can cause doublepost

Leave comment:


Brilliant umbraco hosting provided by FAB-IT