Tuesday, 17 March 2009
Flickr was the first site to widely introduce the concept of annotating images with user comments. This concept was first pioneered by the Fotonotes JavaScript library, and has since been further popularised by Web 2.0 sites like Facebook.
When my current client asked my to integrate simlar functionality into there site, I started to look around for a jQuery plugin that could provide this, but none was available. The Image Annotate library for Drupal is based upon jQuery UI however, so taking this for a base, I adopted it to just use jQuery 1.2.6.
You can see a live example here.
Using the plugin is straight forward, simply hookup the plugin an call the imageAnnotate function on the image you would like to annotate.
<html>
<head>
<style type="text/css" media="all">@import "/css/annotation.css";</style>
<script type="text/javascript" src="/js/jquery-1.2.6.min.js"></script>
<script type="text/javascript" src="/js/jquery-ui-1.5.3.min.js"></script>
<script type="text/javascript" src="/js/jquery.annotate.js"></script>
<script language="javascript">
$(window).load(function() {
$("#toAnnotate").annotateImage({
editable: true,
useAjax: false,
notes: [ { "top": 286,
"left": 161,
"width": 52,
"height": 37,
"text": "A read only annotation",
"editable": false },
{ "top": 134,
"left": 179,
"width": 68,
"height": 74,
"text": "An editable annotation",
"editable": true } ]
});
});
</script>
</head>
<body>
<div class="main-content">
<img src="/images/annotated.jpg" id="toAnnotate" />
</div>
</body>
</html>
The plugin requires jQuery 1.2.6 and jQuery UI with resiable and draggable options included. A saveAsHtml method is available in order to generate a dynamic HTML form with which to save the annotations back to the server.
AJAX Version
EDIT: see this post for server side code.
As well as operating in a static HTML mode, the plugin can load, update and delete annotations using AJAX calls. Specifying Get, Save and Delete URLs, plus setting the useAjax flag to true.
$(window).load(function() {
$("#toAnnotate").annotateImage({
getUrl: "get.rails",
saveUrl: "save.rails",
deleteUrl: "delete.rails",
useAjax: true
});
});
The Get url expects data to be returned as an JSON stream.
[ { "top": 286,
"left": 161,
"width": 52,
"height": 37,
"text": "Small people on the steps",
"id": "e69213d0-2eef-40fa-a04b-0ed998f9f1f5",
"editable": false },
{ "top": 134,
"left": 179,
"width": 68,
"height": 74,
"text": "National Gallery Dome",
"id": "e7f44ac5-bcf2-412d-b440-6dbb8b19ffbe",
"editable": true } ]
Window Load Event
When calling the plugin it is important to use the $(window).load() event. This event fires after the page an all it's images have loaded. The $(document).ready() event fires after the page has loaded, but before the browser has retrieved all the images. Calling the plugin in this event will result in the plugin rending a blank DIV as the image hasn't loaded.
Friday, 13 March 2009
Good .Net developers should know that a DateTime has a larger range than a SqlDateTime, and that care needs to be taken when storing them in databases. For the current project I'm working on, my client wanted an even bigger data range than .Net allows - for dates going back to prehistoric times.
After digging around it was clear that .Net had no such support built into the framework. Using custom date formatting you can retrieve the Era (AD or BC) by using the "gg" code. Whilst this isn't actually be useful for my purposes (since .Net can only go back as far as the year 1) it does provide a safe way to translate this information depending on your current locale.
After some thought I realised that with the increasing range requirement of the date, the need for accuracy fell - I could get away with just storing the year needed for the date. Enter the HistoricalDate class, a class that stores just the year and era of a date.
namespace Cogworks.Domain
{
///
/// Represents a historical date.
/// Used for object from and to periods.
///
public class HistoricalDate
{
///
/// Gets or sets the year.
///
/// The year.
public int Year { get; set; }
///
/// Gets or sets the era.
///
/// The era.
public Era Era { get; set; }
}
///
/// Represents an Era for a HistoricalDate.
///
public enum Era
{
BC,
AD
}
}
Before integrating this new class into the production system, I wrote a series of unit tests to ensure the class operated correctly. This included parsing string representations...
[Test]
public void TestParseValidBCString()
{
var date = HistoricalDate.Parse("50 BC");
Assert.AreEqual(Era.BC, date.Era);
Assert.AreEqual(50, date.Year);
}
...checking equality and less than/greater than operators...
[Test]
public void TestEqualsWhenNotEqual()
{
var date1 = HistoricalDate.Parse("2000 AD");
var date2 = "test";
Assert.IsFalse(date1.Equals(date2));
}
[Test]
public void TestADLessThanWhenTrue()
{
var date1 = HistoricalDate.Parse("1999 AD");
var date2 = HistoricalDate.Parse("2000 AD");
Assert.IsTrue(date1 < date2);
}
... and the ToString() method.
[Test]
public void TestToString()
{
var date = new HistoricalDate("2009 ad");
Assert.AreEqual("2009 AD", date.ToString());
}
A common operation that I needed was the Parse() and TryParse() functions for reading user input. For this I created a NullDate class that is used to represent the input when it is not a valid date.
namespace Cogworks.Domain
{
public class NullDate : HistoricalDate
{
public override string ToString()
{
return string.Empty;
}
}
}
...with relevant tests round it...
[Test]
public void TestParseValueWithNoEra()
{
Assert.AreEqual(new NullDate(), HistoricalDate.Parse("1000"));
}
Another problem I needed to solve was representing the current date. For this I created a PresentDate class.
namespace Cogworks.Domain
{
///
/// Represents the present date in HistoricalDate format.
///
public class PresentDate : HistoricalDate
{
///
/// Gets the year.
///
/// The year.
public new int Year
{
get { return DateTime.Now.Year; }
}
///
/// Gets the era.
///
/// The era.
public new Era Era
{
get { return Era.AD; }
}
}
}
This could then be returned from a static Now() function on the HistoricalDate class.
///
/// Gets the present date.
///
/// The present date.
public static HistoricalDate Now
{
get
{
return new PresentDate();
}
}
With the new code tested with 28 test cases, I now have a handy class to use in production. I've also made the source available in case anyone else finds a use for it.
Labels: ASP.NET, C#
Monday, 9 March 2009
I've recently been working on a series of CRUD screens for a client using the MVC pattern. Each of my controllers is validated on the server side by an instance of a separate class implementing the IValidator interface, enabling me to test the validation separately from the controller logic.
namespace Cogworks.Web
{
public abstract class AbstractController : Controller
{
///
/// Gets the validator for this controller.
///
/// The validator.
public virtual IValidator Validator { get; private set; }
///
/// Initializes a new instance of the AbstractController class.
///
public AbstractController()
{
Validator = new NullValidator();
}
}
}
In the abstract base class I set the validator to be an instance of NullValidator, as not all controllers require validation. The NullValidator always returns true and means that my controller code isn't concerned with checking for null reference errors before calling the validator (see the Null Object Pattern).
namespace Cogworks.Web
{
public class NullValidator : IValidator
{
public void Validate(AbstractWebContext context)
{
}
public bool Valid
{
get { return true; }
}
public string Message
{
get { return string.Empty; }
}
}
}
When coding however, the intellisense comes up with the error "Do not call overridable methods in constructors" when I assign the NullValidator as the default IValidator type. After a bit of research, this article on MSDN explains why - doing so could call the base property from the base constructor under certain conditions.
Refactoring the code, I new up a NullValidator on the explicit call to the property, nicely avoiding the above scenario.
namespace Cogworks.Web
{
public abstract class AbstractController : Controller
{
///
/// Gets the validator for this controller.
///
/// The validator.
public virtual IValidator Validator { get { return new NullValidator(); } }
}
}
Now, inheriting controllers can override the property and assign there own custom validation.
Whilst this approach is certainly better than the first, it could still be improved upon by using the IoC container to automatically set the validator properties for me - enabling me to set the validation through configuration rather than hard coding it. Unfortunatley the system I'm using doesn't allow me to pull the controllers out of configuration, so I'll have to settle for this implementation.
Labels: ASP.NET