I've only made three posts here so far and already I've lost a bunch of posts due to my session timing out, or me accidentally brining the server down mid post. Needless to say I think it's time I did something about this because there are few things worse than doing a whole bunch of work and then having it deleted in an instant. So here goes!

The solution could involve submitting to the server every so often to update an unpublished temporary post, but that relies on the internet connection. So I guess we could utilize local storage? How about using good old cookies? It was a great idea. I had this working too until I tried saving a fairly large post and the cookies would refuse to update the cookie. There has to be something else... 

After a quick searching around I discovered localStorage. This stuff is amazing! You just use it as a javascript object to save strings:

// Save the subject
localStorage.savedSubject = "How to not lose your blog post";

// Later on - recover the subject
$('#Subject').val(localStorage.savedSubject);

It appears you can only save strings in the objects. But lucky for JavaScript you can serialize and deserialize objects at will:

// Save the post
var savedBlogPost = {
 Subject: "How not to lose your blog post",
 Body: "I've only made about..."
};
localStorage.savedPost = JSON.stringify(savedBlogPost);

// Later on - recover the post
var recoveredBlogPost = JSON.parse(localStorage.savedPost);
$('#Subject').val(recoveredBlogPost.Subject);
$('#Body').val(recoveredBlogPost.Body);

So what's the down side? It's not in a cookie so it isn't sent to the server at all. That's fine though, we can get around that.

What we do is when you submit your form to add or update the blog post, we create a cookie that says "there is temporary data saved" and at that point we store the post data into a localStorage variable.

$('form').submit(function () { 
    utils.createCookieAndLocalStorage('postTempData', { 
        Subject: $('#Subject').val(), 
        Body: $('#Body').val()
    }); 
    return true; 
});

On the server side, after the changes are saved successfully we remove the cookie postTempData that indicates the temporary saved data. This completes the success scenario.

For the fail scenario, we imagine the server never gets the call to save the post, or there is an error saving the post. When we recover from the error (by logging out, restarting browser or whatever) we go back to the "make a blog post page" and a check is performed to see if the temporary saved data indicator cookie exists. If it does then we pull the data out of the cookie and place it into the form. If it doesn't then we just ignore whatever may be in the localStorage variable.

$(function() {
    var data = utils.getLocalStorageIfCookie('postTempData');
    if (data) {
        $('#Subject').val(data.Subject); 
        $('#Body').text(data.Body); 

        // Notify user that the post was restored
        $('#restoreWarning').show().click(function () {
            // Clear cookie and local storage data when restore is cancelled
            utils.removeCookie('postTempData'); 
        });
    }
});

The cookie utils code looks like this:

var utils = (function() { 
    this.cookieLocalStorageIndicator = "__localStorage"; 

    this.createCookieAndLocalStorage = function (name, value, days) { 
        if (value != null) {
            value = JSON.stringify(value); 
            localStorage.setItem(name, value); 
            value = utils.cookieLocalStorageIndicator;
        }

        var exdate = new Date(); 
        exdate.setDate(exdate.getDate() + days);
        document.cookie = name + "=" + escape(value) + "; path=/" + ((days == null) ? "" : "; expires=" + exdate.toUTCString()); 
    };

    this.getLocalStorageIfCookie = function (name) {
        // ridiculous looking code to get the cookie value
        var c_value = document.cookie;
        var c_start = c_value.indexOf(" " + name + "="); 
        c_start = c_start == -1 ? c_value.indexOf(name + "=") : c_start; 
        if (c_start == -1) c_value = null; 
        else { 
            c_start = c_value.indexOf("=", c_start) + 1; 
            var c_end = c_value.indexOf(";", c_start);
            c_value = unescape(c_value.substring(c_start, c_end == -1 ? c_value.length : c_end)); 
        }

        // if the cookie is in local storage, grab it from there
        if (c_value == utils.cookieLocalStorageIndicator) {
            c_value = localStorage.getItem(name);
        } else { 
            // otherwise, remove the item from local storage
            localStorage.removeItem(name); 
        } 

        try {  c_value = JSON.parse(c_value);  } catch (err) {   } 
        return c_value; 
    };
    return this; 
})();

So that's it. I should never lose a blog post again... in theory. I have so much faith in this system I am going to hit "Submit" now without copying this post...

I lied, I totally copied it.

Cheers, Rich
By Richard Rout
Feed
0 Comments
Only used for Gravatar avatar.

I thought a lot about putting comments on this site. The main question is "Do I go pre-built or build my own?". I built my own, but that doesn't mean you should too. It really depends on your preferences and willingness to spend time on making your own and maintaining it. Here are the pros for going with a pre-built system such as Disqus;

  1. It just works out of the box
  2. Users are familiar with it and may already have an account
  3. Spam filtering is built in

Here are the cons (and ultimately why I decided to build my own):

  1. The data is not owned by you
  2. You might not be able to make the changes you want
  3. It relies on an external system which can go down

Number 1 and 2 are the deciding factors for me. I like control and I can't do that with an external system. So here's how I did it myself - it's fairly simple and hopefully it can help someone make their own decision on this matter.

The server side code is pretty trivial and just reads and inserts into a database, so I won't bore you with that. I went with a JavaScript heavy implementation, I had thought about using Knockout JS but I'm not currently using it on the site and can't justify a 14kb library of awesomeness just for this one small feature. So I went with just jQuery, and these are the two main functions for the comment system.

blog.loadComments = function (node) { 
   if (node.children('form').is(':visible')) { 
      node.children('form').hide(); 
      node.children('.comments-list').hide(); 
   } else { 
      $.ajax({ 
         url: "/Blog/Comments/" + node.data('id'), 
         method: 'get' 
      }).done(function(data) { 
         blog.displayComments(node, data); 
         node.children('form').slideDown(); 
      }); 
   } 
}; 
 
blog.displayComments = function (node, data) { 
   node.children('.comments-list').empty().show(); 
   for (var i = 0; i < data.length; i++) { 
      var comment = data[i]; 
      node.children('.comments-list').append( 
         $('<div></div>') 
            .addClass("comment") 
            .append($('<img />').addClass("thumbnail").attr('src', comment.ProfilePicture).attr('alt', comment.Name).attr('title', comment.Name)) 
            .append($('<span></span>').addClass("name").text(comment.Name)) 
            .append($('<span></span>').addClass("date").text(comment.Date)) 
            .append($('<p></p>').addClass("body").text(comment.Text)) 
      ); 
   } 
};

So that's it, really. They are fairly self explanatory functions, blog.loadComments is responsible for pulling the comments from the database and calling blog.displayComments which is responsible for iterating through the data and filling the correct element on the site. We have one other small piece that kicks off the whole thing:

// Comments loading code 
var commentNodes = $('.post .post-comments'); 
if (commentNodes.length == 1) { 
   // load comments if there is only one 
   blog.loadComments(commentNodes); 
} else if (commentNodes.length > 1) { 
   // attach the click to the a 
   $('a', commentNodes).click(function () { 
      blog.loadComments($(this).parent()); 
   }); 
}

If we are just looking at one post, then the comments will pull from the database and display automatically. However if we are looking at page with multiple posts on it (i.e. the home page) the comments will not load, instead we just attach a click even to the comments button to bring them up.

Cheers, Rich
By Richard Rout
Feed
0 Comments
Only used for Gravatar avatar.