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:
- Create the server-side logic that creates the comment
- Connect that logic to the client, using /base
- Do simpel form markup, no runat="server" attributes
- Write some simpel jquery to post values to /base
- prettify the form
- 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:
- Get values from HttpContext request collection
- Check they are valid
- Test the blog post exists and it's the right content type
- Create the document and fill in the properties
- Save and publish
- 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:

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/