Before I begin, I want to point out that the title of this blog is far more complex-sounding than what I'm actually going to demonstrate here. GMail has an interesting way to handle large mail threads. When you view it, only the most recent few emails will be visible. The rest will be collapsed and are loaded as you click them.
Here is a screen shot showing a thread from a public listserv I'm on.
They also do a bit more collapsing in the middle of the thread as well. Overall though the idea is to show you the most up to date content first and then let you decide if you need the older information. You can also click a button to expand all the messages at once. I thought it might be fun to rebuild this myself. Just because. This isn't necessarily rocket science but I thought it might be fun to share. Note that this code could be refactored, made more impressive, etc. As I said - I just built this for fun.
So, let's start with the HTML. As this is a one page demo, I've got one page of HTML.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>GMail Test Thingy</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<link type="text/css" rel="stylesheet" href="styles.css">
</head>
<body>
<div id="content"></div>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script src="app.js"></script>
</body>
</html>
There is absolutely nothing here worth our time discussing, but I will point out the content div where I'll be laying out my data. Now let's take a look at app.js.
/* global $,document */
$(document).ready(function() {
//used to store current set of messages
var messages;
//where I'll be rendering content
var $div = $("#content");
//Fetch the thread content
$.get("./thread.cfc?method=getThread", function(res) {
var html = "";
for(var i=0, len=res.length; i<len; i++) {
var message = res[i];
html += "<div class='message' data-message-id='" + message.id + "'>";
html += "<div class='messageTitle'>"+message.title+"</div>";
if(message.body) {
html += "<div class='messageBody'>"+message.body+"</div>";
} else {
html += "<div class='messageBody'></div>";
}
html += "</div>";
}
$div.html(html);
//copy so we can look em later
messages = res;
},"json");
//listen for click events to (potentially) load a message
$("body").on("click", ".messageTitle", function(e) {
var messageId = $(this).parent().data("message-id");
var $body = $(this).next();
//Simple logic - if we already have the message, don't do anything, otherwse load it
for(var i=0, len=messages.length; i<len; i++) {
var m = messages[i];
if(m.id === messageId && !m.body) {
$.get("./thread.cfc?method=getMessage", {messageId:m.id}, function(res) {
$body.html(res.body);
messages[i].body = res.body;
},"json");
break;
}
}
});
});
OK, let's break this down bit by bit. My first real piece of functionality is the $.get call to fetch my email thread. I'm using ColdFusion on the back end but the data is completely static. I'm going to return an array of Message objects where each has an ID and a Title. But - the server recognizes that it is a big thread and will only fill the Body property for the last few. So the front end has to recognize this. You can see where I loop over the array and generate my content. I'm using a few div classes that will come into play in a bit. Also note how I embed the message ID in a data attribute. This is valid HTML and is an easy way to embed metadata into the DOM.
So the end result of this is a rendered list of messages:
Don't hate me for my epic CSS design skills. The next part of the demo is handling click events on the titles. You can see my click handler there based on the class I used for the title. I fetch the message ID and then look into my message's data to see if I've loaded the body already. If I haven't, I call the server again, fetch the body, display it, and cache it. Here is a screen shot with a few messages loaded.
And that's it. Not exactly rocket science, but, practical I suppose, and something folks may want to rip into their own code. You can demo this yourself below.