Annotations Tutorial: Collecting orphaned per-paragraph comments in buckets

In this part of our per-paragraph commenting annotations app, we create tabs at the bottom of the article to collect all annotations created on the post as a way to deal with orphaned annotations.


So far, we started with a very simple stacked layout and following a mobile-first design approach, loaded annotations via jQuery with fixtures for Ajax calls and associated them with their respective containers. Moreover, we neatly tucked the annotations created by other users in a bucket toggled by a button. Now, we would like to go through the last iteration of our front-end design before we can move into the backend design.

So we would like to:

  1. Make a tabbed bucket at the bottom to collect annotations
  2. Dealing with orphaned notes.

Creating tabs in pure CSS without Javascript(Image Credits)

It might so happen that the author of the post revises the content and the paragraph that you made an annotation on no longer exists. Owing to the design of our blogging engine, the paragraph IDs will never be reused. Thus, if complete paragraphs are gone, the annotations for the non-existent paragraph will be orphaned. Since the author did not create the annotations, he must not be able to delete them, but leave it to the sweet will of the one who made the annotation. Thus, we would want to decouple the fate of the paragraphs and annotations. There remains an issue of 'what if the paragraph content was drastically modified?', well, that is an issue right now, but I think that can be taken care of by revisioning. However, given the scope of our site, the paragraphs are not expected to alter significantly (unless something conceptually wrong was written, and your annotation pointed that out). So, we will, for now deal only with the first problem of orphaned annotations.

First, create a bucket at the bottom of the article (building upon the markup and code from the previous iteration).

<div class="article-adjunct">
<nav class="article-adjunct-nav">
<ul class="article-adjunct-nav--list">
<li class="nav-tab article-adjunct-nav--item active"><a href="#article-adjunct-tab-notes">Notes</a></li>
<li class="nav-tab article-adjunct-nav--item"><a href="#article-adjunct-tab-discuss">Discussions</a></li>
<li class="nav-tab article-adjunct-nav--item"><a href="#article-adjunct-tab-questions">Questions</a></li>
</ul>
</nav>
<div class="article-adjunct-block">
<div class="article-adjunct-tab active" id="article-adjunct-tab-notes">
Some placeholder text
</div>
<div class="article-adjunct-tab" id="article-adjunct-tab-discuss">
The concept of group messaging shall allow more people to join in the conversation should the two (original people in conversation) oblige unanimously.
Unanimous vote is required because even one person who does not oblige will be offended, and if that is so, we have a problem.
</div>
<div class="article-adjunct-tab" id="article-adjunct-tab-questions">
Thus, annotations that are aimed at starting conversations must result into conversations, but not on the post, but in the privacy of message boxes.
Say, a person made a public annotation on an article, and somebody wanted to respond to it. He can respond to it via a message.
</div>
</div>
</div>

We're making three tabs, one of which would contain our annotations. The others can be used for other purposes, for example, one of them may contain comments, while the other may contain references or other related stuff. We've marked up the list items in the navigation to their different tab contents using their IDs.

Next, we want that only one of them (the one we clicked on) is active at a time. For this, we'll use this simple Javascript script (reference):

/*
* Handle tabs
*/
$('.article-adjunct-nav--item a').on('click', function(e)  {
var currentAttrValue = $(this).attr('href');
// Show/Hide Tabs
$(currentAttrValue).show().siblings().hide();
// Change/remove current tab to active
$(this).parent('li').addClass('active').siblings().removeClass('active');
e.preventDefault();
});

As simple as that. If you don't get what has been done, you could follow the hyperlink, and it has been explained line by line. If you just don't understand the chained methods, they're easy. Rather than receiving the result of one method in a variable and then applying another method on it, they've simply called them in sequence. All that the script does is finds out the target ID from the tab that was clicked on, unhides it, and hides the rest of its siblings.

The CSS (or rather, the SASS) is more or less simple.

.article-adjunct{
display: block;
position: relative;
width: 95%;
@media(min-width: $screen-md){
width: 55rem;
}
margin: 0 auto;
line-height: 1.5;
letter-spacing: 1px;
font-size: 1.5rem;
text-align: justify;
.article-adjunct-nav{
padding: 10px 0 10px 0;
border: 1px solid $default-border-color;
border-left: none;
border-right: none;
box-shadow: none;
display: table;
width: 100%;
a {
text-decoration: none;
color: $link-default;
}
.article-adjunct-nav--list{
list-style: none;
display: table-row;
overflow: hidden; 
position: relative;
width: 100%;
padding: 5px 10px;
}
.article-adjunct-nav--item{
display: table-cell;
width: 33%;
position: relative;
background-color: darken($body-background-color, 30%);
border: 1px solid $button-border-color;
border-left: none;
text-align: center;
&:first-child{
border-left: 1px solid $button-border-color;
}
}
}
.article-adjunct-block{
display: block;
position: relative;
border: 1px solid $default-border-color;
border-top: none;
border-left: none;
border-right: none;
@include border-radius(3px, 3px);
.article-adjunct-tab{
display: none;
position: relative;
&.active{
display: block;
}
}  
}
}

The tabs must be equidistant, and since we know for sure that there are going to be 3 tabs only, we format them as table cells of equal width. Rest, we've just reused out comment-containers from the previous markup along with little reshuffling of their hierarchies. Clean?

So, now it would be fair to say that our app's frontend design is complete, and we can now dive into the backend and then tie the two things up? Of course, we haven't implemented the 'Delete Annotation' method. But, we'll get back to that later.

Except 1 thing. What if the user is a guest? Easy, just add one OR condition where annotations are loaded:

...
/* Also add to main container if the user is a guest. */
if((parseInt(data[i]['user']['user_id']) === parseInt(annotations.currentUser['user_id']))||                                        
(parseInt(annotations.currentUser['user_id'])===0)){
...

The finished page looks like this pen below. I know the tabs at the bottom are a bit crude, but you could do the styling, no?

There! On to backend!