Displaying "Time Ago" Stamps Using ASP.NET AJAX

Introduction

Most of the websites dealing with dynamic content store date information of various pieces of content that are managed by the system. Blog engines, micro-blogging applications, content management systems and social networking sites are a few such examples. Though these applications store dates in database-specific formats while displaying dates to the end user, such applications often format it differently. One such popular format is "time ago" wherein the time span elapsed since the content was published is displayed. Some examples of how Facebook displays time ago stamps are -- "5 hours ago", "Yesterday at 6:10 am" and "October 10 at 6:20 am". In this article you will learn how to display such "time ago" stamps using ASP.NET and AJAX.

Example Scenario

Let's assume that you have a simple Guestbook on your blog or website where readers can post feedback and comments. The Guestbook page allows the readers to submit their name, email, subject and message. Upon submission the message is displayed with the "time ago" stamp along with the previously submitted entries. 

Software Needed

In order to work through the example that follows you need to have ASP.NET 3.5 or later. Though the example is developed using Visual Studio 2008, you can use Visual Studio 2010 or Visual Web Developer Express Edition. Additionally, you need SQL Server 2005 / 2008 database as our data will be stored there.

Creating a Database Table

To begin, create a database table in SQL Server named Guestbook. The structure of Guestbook table is shown in Figure 1.

creating a database table
Figure 1

The Guestbook table consists of six columns named Id, FullName, Email, Subject, Message and PostedOn. Notice that the Id column is an identity column.

Creating a Website

Now create a new website in Microsoft Visual Studio. Name the site TimeAgoDemo (Figure 2).

create a new website in Microsoft Visual Studio
Figure 2

Open the default Web form and place two SQL Data Source (SDS) controls on it. Configure one of the SDS to INSERT records into the Guestbook table. Figure 3 shows the relevant configuration of SDS.

Figure 3  shows the relevant configuration of SDS
Figure 3

Configuring SDS this way will generate the following SDS markup in the .aspx file (unnecessary markup has been omitted for the sake of clarity).

<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
ConnectionString="<%$ ConnectionStrings:TestDbConnectionString %>" 
InsertCommand="INSERT INTO [Posts] ([FullName], [Email], [Subject], [Message], [PostedOn]) 
VALUES (@FullName, @Email, @Subject, @Message, @PostedOn)" 
<InsertParameters>
<asp:Parameter Name="FullName" Type="String" />
<asp:Parameter Name="Email" Type="String" />
<asp:Parameter Name="Subject" Type="String" />
<asp:Parameter Name="Message" Type="String" />
<asp:Parameter Name="PostedOn" Type="DateTime" />
</InsertParameters>
</asp:SqlDataSource>

Similarly, configure the other SDS to select all records from the Guestbook table and also add an ORDER BY clause, as shown in Figure 4. This way, the latest entries will be displayed on top. 

select all records from the Guestbook  table
Figure 4

The following markup shows the markup of a second SDS.

<asp:SqlDataSource ID="SqlDataSource2" runat="server" 
ConnectionString="<%$ ConnectionStrings:TestDbConnectionString %>" 
SelectCommand="SELECT * FROM [Posts] ORDER BY [PostedOn] DESC">
</asp:SqlDataSource>

Notice how the database connection string is referred from the web.config file.

Displaying "Time Ago" Stamps Using ASP.NET AJAX

Accepting Data

Now that you have configured both the SQL Data Source controls, let's provide a facility for the readers to submit their comments. Drag and drop a FormView control on the Web form and set its data source to the first SDS (Figure 5).

[Figure05.jpg]
Figure 5

Design the Insert Item Template of the FormView as shown in Figure 6.

[Figure06.jpg]
Figure 6

The Insert Item Template simply binds textboxes to the FullName, Email, Subject and Message database columns. Clicking the submit button will trigger ItemInserting and ItemInserted events. The former event is raised before adding data to the database table, and the later event is raised after the record has been inserted successfully. The event handlers of these events are shown below:

    
    protected void FormView1_ItemInserting(object sender, FormViewInsertEventArgs e)
    {
        e.Values["PostedOn"] = DateTime.Now;
    }
    protected void FormView1_ItemInserted(object sender, FormViewInsertedEventArgs e)
    {
        DataList1.DataBind();
    }

The ItemInserting event handler sets the PostedOn parameter value to the current date and time. The ItemInserted event rebinds the DataList (you will add it in a minute) so that the latest entry is displayed. 

Displaying Data

Next, drag and drop a DataList control below the FormView you designed earlier. Set its data source to the second SDS control. Design the ItemTemplate of the DataList as shown in Figure 7.

[Figure07.jpg]
Figure 7

The ItemTemplate displays Subject, Message and PostedOn values in Label5, Label6 and Label8 respectively. The hyperlink HyperLink1 displays FullName and points to the email address of the poster. Once you design the template as shown in Figure 7, open the DataBindings editor for Label8 (i.e. PostedOn field) and specify a custom binding expression as shown in Figure 8.

[Figure08.jpg]
Figure 8

What you are doing is binding Label8 with the return value of a method GetTimeAgo(). You will code the GetTimeAgo() method soon. The GetTimeAgo() method accepts the original PostedOn value as returned by Eval() data binding expression and returns "time ago" stamp as per your logic.

Now select HyperLink1 and bind its Text property to FullName column. The NavigateUrl property needs to be bound with Email column. To do this, open Databindings editor as before and select NavigateUrl property (Figure 9).

[Figure09.jpg]
Figure 9

In the custom binding expression, format the output of Eval() to include "mailto:" so that the resultant hyperlinks are formatted as email links.

The GetTimeAgo() Method

Now comes the important part -- GetTimeAgo() method -- actually generates the "time ago" stamps. This method is part of the web form class and is shown below:

    
 public string GetTimeAgo(object postedOn)
    {
        DateTime currentDate = DateTime.Now;
        DateTime postDate = Convert.ToDateTime(postedOn);

        TimeSpan timegap = currentDate.Subtract(postDate);

        if (timegap.Days >= 7)
            return string.Concat("Posted on ", postDate.ToString("MMMM dd, yyyy"), " at ", postDate.ToString("hh:mm tt"));
        else if (timegap.Days > 1)
            return string.Concat("Posted ", timegap.Days, " days ago");
        else if (timegap.Days == 1)
            return "Posted yesterday";
        else if (timegap.Hours >= 2)
            return string.Concat("Posted ", timegap.Hours, " hours ago");
        else if (timegap.Hours >= 1)
            return "Posted an hour ago";
        else if (timegap.Minutes >= 60)
            return "Posted more than an hour ago";
        else if (timegap.Minutes >= 5)
            return string.Concat("Posted ", timegap.Minutes, " minutes ago");
        else if (timegap.Minutes >= 1)
            return "Posted a few minutes ago";
        else
            return "Posted less than a minute ago";
    }

The GetTimeAgo() method accepts an object and returns a string. Recollect that you are passing the result of Eval() call to GetTimeAgo() method. Eval() always returns an object irrespective of the actual data type. So, the GetTimeAgo() method accepts an object and internally converts it into a DateTime variable (postDate). The difference between current DateTime and PostedOn DateTime is found using Subtract() method. The returned TimeSpan instance is checked in a series of if...else if... steps. The following table lists the checks along with an example:

Time elapsed since message was published Example Output
7 or more days have elapsed Posted on October 10, 2010
Greater than 1 day but less than 7 days Posted 5 days ago
1 day Posted yesterday
Between 2 hrs. to 1 day Posted 3 hours ago
1 hour. Posted an hour ago
Between 1 hr. to 2 hrs. Posted more than an hour ago
Between 5 min. to 60 min. Posted 20 minutes ago
Between 1 min. to 5 min. Posted a few minutes ago
Less than 1 min. Posted less than a minute ago

Test the Guestbook Web Form

Now build the website and run the Web form. Figure 10 displays a sample run of the form. Notice how "time ago" stamps are displayed.

[Figure10.jpg]
Figure 10

Displaying "Time Ago" Stamps Using ASP.NET AJAX

Refreshing "Time Ago" Stamps Using UpdatePanel and Timer

Though the Guestbook Web form you just developed displays "time ago" stamps as expected, it doesn't update them unless the browser window is refreshed. You can overcome this limitation using UpdatePanel and Timer controls. To use them, first drag and drop a ScriptManager control on the Web form. Also add an UpdatePanel control on the form. The UpdatePanel control enables partial page rendering for your Web form. Place a Timer control inside the UpdatePanel and set its Interval property to 5000 milliseconds. This way the UpdatePanel will be refreshed automatically after every 5 seconds. Now, place a the DataList control that displays the Guestbook entries inside the UpdatePanel control. You need to databind the DataList again when UpdatePanel refreshes itself. To do this, handle Load event of the UpdatePanel and call DataBind() method on the DataList. The following code shows the Load event handler of UpdatePanel.

 
protected void UpdatePanel1_Load(object sender, EventArgs e)
 {
   DataList1.DataBind();
 }

If you run the Web form now, you will observe that the "time ago" stamps automatically refresh after every 5 seconds.

Refreshing "Time Ago" Stamps Using Page Methods

The UpdatePanel and Timer approach works well, but it has its own disadvantage. Your intention is to refresh "time ago" stamp alone but since you have placed the DataList inside the UpdatePanel, the entire DataList will be recreated and databound  again and again with every UpdatePanel refresh. You can avoid this overhead by updating only the "time ago" Labels and not the entire DataList. One clever way to do this is to use AJAX to call server side Page Method. Let's see how this can be done.

First remove the UpdatePanel and Timer control that you added to the Web form previously (ScriptManager is still required) so that the DataList is directly placed on the form. Next, modify the GetTimeAgo() method as shown below:

    
[WebMethod]
    public static string GetTimeAgo(object postedOn)
    {
      ...
    }

Notice that the GetTimeAgo() method is now marked as static and is decorated with [WebMethod] attribute. This way GetTimeAgo() becomes what is called as a Page Method in AJAX terms. A page method is a server side method that can be called from client side AJAX code. Page methods must be static and should have [WebMethod] attribute. The code inside the GetTimeAgo() method remains unchanged.

Now, go to the markup (.aspx) of the DataList control and locate the Label that displays "time ago" stamps. Modify the Label markup as shown below:

<asp:Label ID="Label8" runat="server" 
Text='<%# GetTimeAgo(Eval("PostedOn")) %>' 
ToolTip='<%# Eval("PostedOn") %>'></asp:Label>

Notice that earlier you only bound the Text property with GetTimeAgo() method. Now you are also binding ToolTip property to the actual PostedOn value. The Label control gets rendered as a <SPAN> tag and the tooltip property gets rendered as the Title attribute of that <SPAN> tag. The following example shows how this rendering happens:

<span id="DataList1_ctl00_Label8" title="10/27/2010 9:30:16 AM">

You need the actual PostedOn value on the client side so that you can periodically convert it into a "time ago" stamp. Next, add a <SCRIPT> block somewhere below the ScriptManager markup and code a JavaScript function named GetLatestTimeAgo() as shown below:

        
	function GetLatestTimeAgo() {
            var labels = document.getElementsByTagName("span");
            for (i = 0; i < labels.length; i++) {
                if (labels[i].title != "") {
                    PageMethods.GetTimeAgo(labels[i].title, OnSuccess, OnError, labels[i].id);
                }
            }
        }

The GetLatestTimeAgo() function first retrieves a list of <SPAN> elements from the Web page. It then iterates through this list. If title of the <SPAN> is set, it calls the GetTimeAgo() page method using PageMethods object. The value of title attribute i.e. the actual PostedOn value is passed to the GetTimeAgo() method. The OnSuccess function will be called upon successful completion of the page method call. In case there is any error OnError function will be called. The last parameter of the GetTimeAgo() method call is user context and you pass the client side ID of the <SPAN> under consideration. This way OnSuccess function will know which <SPAN> to refresh. The OnSuccess and OnError functions are shown below:

        
	function OnSuccess(result, userContext, methodName) {
            $get(userContext).innerHTML = result;
        }

        function OnError(err) {
            alert(err.get_message());
        }

The OnSuccess function receives the "time ago" stamp as the first parameter. The ID of the <SPAN> passed earlier is received as the second parameter. The third parameter is the method name (GetTimeAgo in your case). Inside we simple get hold of the <SPAN> tag matching the supplied ID and set its innerHTML property to the latest "time ago" stamp. The OnError function simply displays the error message if any.

Finally, handle pageLoad() event as shown below:

        
	function pageLoad() {
            window.setInterval(GetLatestTimeAgo, 5000);
        }

The pageLoad() function will be called by AJAX framework when the Web page loads in the browser. Inside you use setInterval() method of the window object to repeatedly call GetLatestTimeAgo() JavaScript function. The second parameter of the setInterval() method specifies the interval in milliseconds between multiple GetLatestTimeAgo() calls.

You can now run the Web form and see how the "time ago" stamps refresh periodically. Notice that this time only the "time ago" Labels are refreshed and not the entire DataList.

Note:

  • In the above code you call GetTimeAgo() page method merely by checking the title attribute. It is possible that some other <SPAN> tags outside of DataList also have title attribute set. If so the page method call will fail. You can put additional checks to ensure that only the <SPAN> tags displaying the "time ago" stamps are considered while calling the page method.
  • Since you already have "time ago" stamp generation logic on the server, you used AJAX to call the server side method. You can also build a JavaScript based version of GetTimeAgo() page method so that no server side calls are needed.

 

Summary

Displaying "time ago" stamps is a popular way of displaying the time elapsed since a post was made. In this article you learned how to generate such "time ago" stamps. We also discussed various approaches to refreshing the "time ago" stamps. The first approach refreshes the "time ago" stamps only after page refresh or postback. This approach is suitable where displaying the latest elapsed time is not critical. A simple way to automatically refresh "time ago" stamps periodically is to use UpdatePanel and Timer controls. The downside, however, is that the DataList is bound again and again. If you wish to avoid this overhead you can just refresh the "time ago" Labels by making GetTimeAgo() method as a page method and calling it from the client side AJAX code.

 

Related Articles



About the Author

Bipin Joshi

Bipin Joshi is a blogger and writes about apparently unrelated topics - Yoga & technology! A former Software Consultant by profession, Bipin has been programming since 1995 and has been working with the .NET framework ever since its inception. He has authored or co-authored half a dozen books and numerous articles on .NET technologies. He has also penned a few books on Yoga. He was a well known technology author, trainer and an active member of Microsoft developer community before he decided to take a backseat from the mainstream IT circle and dedicate himself completely to spiritual path. Having embraced Yoga way of life he now codes for fun and writes on his blogs. He can also be reached there.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Not all enterprise applications are created equal. Sophisticated applications need developer support but other more basic apps do not. With the right tools, everyone is a potential app developer with ideas and a perspective to share. Trends such as low-code development and model driven development are fundamentally changing how and who creates applications. Is your organization ready? Read this report and learn: The seven personas of enterprise app delivery How application ownership is spreading to the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds