RavenDB - The Image Gallery Project (XVI) - Adding Edit Functionality (I)

Published on 2010-11-23

We have thus far covered the creation of new documents, and the querying of those documents to create views for specific pages in our system. We haven’t actually demonstrated the editing of existing documents and how that actually fits into the application lifecycle covered at the beginning of this series.

This series has been written with the intention of it being a full vertical slice through a modern web application using RavenDB, instead of just being a thin technical demo of RavenDB and we are going to look at how we might write an interface

Moving away from CRUD

A typical CRUD data screen might display all the fields for a document, and have a single save button at the end for dumping all of this data back into the store.

I’m not a big fan of this, we now live in the 21st century and have rich client functionality everywhere – not to mention that this in no way really captures the intent of the user and what it is they actually want to achieve.

We are therefore going to have a screen that displays all the data for a particular image, and allows the user to edit the fields in place. In doing this,  our interface can send one-way commands via JSON and not have to rely on data being returned from the server – this is not only a great experience for the user, but allows us to keep our commands as one-way enactors of change.

For a further bit of fun, we will actually use the same screen to display the image to users who don’t own the image, but not allow changes to it if they aren’t meant to. We could then display “related” images to one side and allow further browsing from this point.

Okay, I admit we’re at risk of copying Flickr here, but I did say we were doing this as a largely real world project, and imitation is the finest form of flattery.

The web bit

Just like with our home-page image browser, we won’t be pre-populating a view model in the controller and will instead expose more JSON endpoints to retrieve data about a specific image and do more client-side template magic. The great thing about this is that it also gives us an API to expose to third parties with no extra effort. (Did I mention we were living in the 21st century?)

Here is the template used on my view page (Images/View.Spark)

<script id="focused-image-template" type="text/x-jquery-tmpl"> 
  <div class="focused-image"> 
    <h4 id="title">{{= Title}}</h4> 
    <img src="/Resources/Image/{{= Filename}}" alt="{{= Title}}" /> 
    <span id="tags">{{= Tags }}</span> 
  </div> 
</script>
 
<div id="image-placeholder"> 
    
</div>
 

We can populate this on start-up in the same way we populated the image browser, by making a call to the server via JavaScript and asking for a ‘view’ based on the parameter in the query string.

updateView: function (imageId) { 
        $.ajax({ 
        dataType: "json", 
        url: '/Image/_GetImage?imageId=' + imageId, 
        error: function (xhr, ajaxOptions) { 
            alert(xhr.status + ':' + xhr.responseText); 
        }, 
        success: function (data) { 
            $('#image-placeholder').html(''); 
            $('#focused-image-template') 
                .tmpl(data) 
                .appendTo('#image-placeholder');
 
        } 
    }); 
}, 
 

Following this through, we have

 

public ActionResult _GetImage(ImageViewInputModel input) 
{ 
    var model = viewRepository.Load<ImageViewInputModel, ImageView>(input); 
    return Json(model, JsonRequestBehavior.AllowGet); 
} 

with

public class ImageViewInputModel 
{ 
    public string ImageId { get; set; } 
} 

and

public class ImageView 
{ 
    public string Filename { get; set; } 
    public string[] Tags { get; set; } 
    public string Title { get; set; } 
} 

All that is left for us to do is create the view, and the ‘view’ functionality of this page will be complete.

The RavenDB Bit

Because we have the id of the document already, we don’t need to do any sort of query and can just request the document directly from the session, flattening it into the view we want.

The test (assuming I’ve saved a document with these properties)

using (var s = Store.OpenSession()) 
{ 
    ImageViewFactory factory = new ImageViewFactory(s); 
    var results = factory.Load(new ImageViewInputModel() 
    { 
         ImageId = "knownId" 
    });
 
    Assert.AreEqual("title", results.Title); 
    Assert.AreEqual("filename", results.Filename); 
    Assert.AreEqual(new[] { "tagOne", "tagTwo" }, results.Tags); 
} 
 

*Yes yes, I know, more than one assert in a single test, may the keepers of good code have mercy on my compiler)

The code

public class ImageViewFactory : IViewFactory<ImageViewInputModel, ImageView> 
{ 
    private IDocumentSession documentSession;
 
    public ImageViewFactory(IDocumentSession documentSession) 
    { 
        this.documentSession = documentSession; 
    } 
    public ImageView Load(ImageViewInputModel input) 
    { 
        var doc = documentSession.Load<ImageDocument>(input.ImageId); 
        return new ImageView( 
            doc.Filename, 
            doc.Tags.Select(tag => tag.Name).ToArray(), 
            doc.Title); 
    } 
} 
 

There we go, a fully functional ‘view’ element to our website (okay, we’re lacking a great deal of functionality, but we will get there in the end, covering some more RavenDB features as we go.

In other news, I’m beginning to think that with all the simplicity I’m displaying at the moment that I should have gone for the simple option of having my entities as the documents and not wrapping them up, I might do a u-turn on that very shortly with a post on re-factoring.

PS: I’m aware that the rest of the entries aren’t navigable to, I’ll fix the template right after publishing this…

2020 © Rob Ashton. ALL Rights Reserved.