A server side AJAX handler for Image Annotations

Tuesday, 21 April 2009

I’ve had a lot of questions asking for more details on writing a server-side AJAX component for the jQuery Image Annoation plugin. I’ve written a basic version of this for the online demo in C# using Monorail, however it should easily translate into other languages.

The first thing we need is a class in which to store our annotations, for this I use the Annotation class.

namespace Cogworks.Core.Domain
{    
    /// <summary>    
    /// An <see cref="Annotation"/> stores some text in a specific
    /// location on an image.
    /// </summary>    
    public class Annotation
    {
        public string Id { get; set; }

        public int Left { get; set; }

        public int Top { get; set; }

        public int Width { get; set; }

        public int Height { get; set; }

        public string Editable { get; set; }

        public string Text { get; set; }

    }
}

Next, I have method on a controller that creates some sample annotations:

/// <summary>
/// Gets a list of <see cref="Annotation"/>
/// </summary>
[Layout(BlankLayoutKey)]
public void Get(){    
    if (Session[AnnotationsKey] == null)    
    {        
        Annotations = AnnotationFactory.CreateSampleAnnotations();    
    }
    PropertyBag[AnnotationsKey] = Annotations;
}

These annotations are then rendered for the browser using the NVelocity view engine to create a JSON stream:

[
#foreach($annotation in ${annotations})
   { "top": ${annotation.top},
     "left": ${annotation.left},
     "width": ${annotation.width},
     "height": ${annotation.height},
     "text": "${annotation.text}",
     "id": "${annotation.id}",
     "editable": ${annotation.editable}
 }
#between 
    ,
#end
]

Saving the annotations is a bit more complex. Each annotation has a unique Id that is sent back and forth to the browser. When an annotation is saved, it is important to delete the existing version from the collection before saving the new one, otherwise the old annotations will never get removed.

/// <summary>
/// Saves the specified annotation.
/// </summary>
public void Save(string id, int top, int left, int height, int width, string text)
{
    DeleteAnnotation(id);

    Annotations.Add(AnnotationFactory.Create(text, left, top, width, height));

    CancelView();
}

The save request doesn’t care what data is returned from the server – as long as the HTTP response is OK (200). The current state of the annotations is maintained by the plugin on the client side.

In non-rails based web frameworks, the code to get the changed variables will probably look something like this:

public void Save()
{
    var id = Request.Form["id"];
    var top = Request.Form["top"];
    var left = Request.Form["left"];
    var height = Request.Form["height"];
    var width = Request.Form["width"];
    var text = Request.Form["text"];

    DeleteAnnotation(id);

    Annotations.Add(AnnotationFactory.Create(text, left, top, width, height));
}

Deleting an annotation is much the same as saving, the plugin will manage the state on the client and won’t error as long as the HTTP response is 200. The code to delete the annotation on the server just removes it from the collection.

/// <summary>
/// Deletes the specified annotation.
/// </summary>
/// <param name="id">The id.</param>
public void Delete(string id)
{
   DeleteAnnotation(id);

   CancelView();
}

When the page is fully refreshed, the new annotations will be re-displayed based upon the state of the data on the server.

An Extensible Markdown Class in C#

The Markdown Format is widely used on the web to enable users to enter basic HTML formatting without the need to verify full mark-up. Various C# implementations of the standard exist online, however for I only needed to implement some of the specification, whilst adding bespoke formatting options.

The implementations of the standard I found online had a couple of problems with them: firstly, there were no unit tests against them so I couldn’t verify their correct operation. Secondly, in order to extend them, I’d have to crack open the implementation and start messing around with the code.

Using the existing code as a base, I decided to re-write an implementation with a specification (unit tests) and points for future extensibility. I wanted to enable the resulting code to be closed to modification of its existing behaviour, but open to extension without requiring the need to dig into the existing codebase (the Open-Closed principle).

I decided to implement this using the visitor pattern. This defines a series of individual visitors, or marklets, which will each pass over the markdown input to transform it into mark-up. Because there is a chain of visitors, it is easy to append new visitors to the chain and extend the functionality of the class.

namespace Cogworks
{
    /// <summary>
    /// Converts Markdown into Markup
    /// </summary>
    static public class Markdown
    {
        private static readonly IList<abstractmarklet> Marklets;

        /// <summary>
        /// Initializes the <see cref="Markdown"/> class.
        /// </summary>
        static Markdown()
        {
            // You could initilize this from an IoC container if you wanted.
            Marklets = new List<abstractmarklet>             
                                {   
                                    new CleanupMarklet(),
                                    new PreMarklet(),
                                    new ItalicMarklet(),
                                    new BoldMarklet(),
                                    new NewParagraphMarklet(),
                                    new BreakMarklet()
                                };
        }


        /// <summary>
        /// Converts the given <see cref="markdown"/>
        /// </summary>
        /// <param name="markdown">The markdown.</param>
        /// <returns></returns>
        public static string ToMarkup(this String markdown)
        {
            foreach(var marklet in Marklets)
            {
                markdown = marklet.Markup(markdown);
            }

            return markdown;
        }
    }
}

As each marklet is a unique class, it is smaller and easier to understand, test and debug.

namespace Cogworks
{
    /// <summary>
    /// Converts bold Markdown into HTML markup.
    /// </summary>
    /// <example>
    /// ##bold text##
    /// </example>
    public class BoldMarklet : AbstractMarklet
    {
        /// <summary>
        /// Converts the given markdown into markup.
        /// </summary>
        /// <param name="value">The markdown input.</param>
        /// <returns>HTML markup</returns>
        public override string Markup(string value)
        {
            var markdown = string.Empty;

            if (!string.IsNullOrEmpty(value))
            {
                markdown = Replace(value, @"\#\#(.*?)\#\#", Evaluator);
            }

            return markdown;
        }

        private static string Evaluator(Match match)
        {
            return string.Format("<strong>{0}</strong>", match.Groups[1].Value);
        }
    }
}

The unit tests represent the specification of the markdown language:

[TestFixture]
public class BoldMarkletTest
{
    private BoldMarklet marklet;

    [SetUp]
    public void SetUp()
    {
        marklet = new BoldMarklet();
    }

    [Test]
    public void TestMarkdownWhenNull()
    {
        var markup = marklet.Markup(null);

        Assert.AreEqual(string.Empty, markup);
    }

    [Test]
    public void TestMarkdownPlainText()
    {
        var markup = marklet.Markup("Plain Text");

        Assert.AreEqual("Plain Text", markup);
    }

    [Test]
    public void TestMarkdownBoldText()
    {
        var markup = marklet.Markup("Some ##Bold## Text");

        Assert.AreEqual("Some <strong>Bold</strong> Text", markup);
    }

    [Test]
    public void TestMarkdownUnclosedBoldText()
    {
        var markup = marklet.Markup("Some ##Bold Text");

        Assert.AreEqual("Some ##Bold Text", markup);
    }

    [Test]
    public void TestMarkdownMultipleBoldText()
    {
        var markup = marklet.Markup("Some ##1## ##2## Text");

        Assert.AreEqual("Some <strong>1</strong> <strong>2</strong> Text", markup);
    }

A large 1,000+ line class can be shrunk down to a collection of 10 line classes.

Extensibility can also be achieved through an Inversion of Control container, meaning changes can be made without the need to re-compile.

Image Annotation plugin updated

Thursday, 2 April 2009

I've updated the jQuery Image Annotation Plugin and moved it over to the Google Code site. Hopefully this will simplify the updating process.

The plugin now has a bug fixed that caused an error when loading an image with no previous annotations, and also has been tested with jQuery 1.3.2 (although there are a few CSS quirks that need to be ironed out).

I have a plan to develop the plugin further, adding support for toggling annotations on and off, plus enabling multiple annotated images on the same page.