Tuesday, February 28, 2012

Memahami Threaded Comments Blogger

Sejak Blogger meluncurkan Threaded Comments, banyak sekali para pengguna BlogSpot berbondong-bondong beralih ke komentar versi "Balas" ini. Namun tidak sedikit yang berakhir kecewa karena ternyata Threaded Comments tidak bisa bekerja dengan baik, bahkan ada yang sampai akhirnya beralih ke pihak ketiga penyedia layanan komentar karena sulitnya integrasi Threaded Comments pada template Blogger. Apa dan bagaimana sebenarnya Threaded Comments? Kita akan bahas (tuntas) untuk bisa memahami tentang fasilitas Threaded Comments pada Blogger dengan harapan mempermudah para blogger untuk melakukan modifikasi kode pada templatenya.
Fix Problem Reply Threaded Comments
Sebelum membahas tentang kode-kode yang memusingkan, mari kita pahami dulu perbedaan mendasar antar template yang telah dikeluarkan oleh Blogger. Blogger telah mengeluarkan beberapa versi template. Untuk mempermudahnya maka kita definiskan; versi kesatu kita sebut sebagai template Tata Letak yang menggunakan HTML4; dan versi kedua kita sebut sebagai template Perancang yang sudah menggunakan HTML5. Sedangkan versi Classic, versi Dynamic View dan versi Mobile tidak akan dibahas agar materi tidak terlalu melebar.

Dibawah ini akan diulas sekelumit tentang perbedaan mendasar namun penting untuk diketahui, antara template blogger versi Tata Letak (HTML4) dengan template versi Perancang (HTML5).

Header Document

Inilah perbedaan awal template blogger, jika kita menggunakan template versi Tata Letak berarti kita menggunakan HTML4. Jika kita menggunakan template versi Perancang berarti kita menggunakan HTML5. Silahkan perhatikan Header Document dibawah ini:
Versi Tata Letak - HTML4
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html expr:dir='data:blog.languageDirection' xmlns='http://www.w3.org/1999/xhtml' xmlns:b='http://www.google.com/2005/gml/b' xmlns:data='http://www.google.com/2005/gml/data' xmlns:expr='http://www.google.com/2005/gml/expr'>
Versi Perancang - HTML5
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html>
<html b:version='2' class='v2' expr:dir='data:blog.languageDirection' xmlns='http://www.w3.org/1999/xhtml' xmlns:b='http://www.google.com/2005/gml/b' xmlns:data='http://www.google.com/2005/gml/data' xmlns:expr='http://www.google.com/2005/gml/expr'>
Catatan : Mengenai detail tag yang digunakan pada HTML5 akan dibahas pada artikel terpisah

CSS Widget Default

Perbedaan selanjutnya adalah stylesheet default yang disertakan (tanpa penawaran) pada template.
Versi Tata Letak
Stylesheet versi Tata Letak yang disertakan pada template tidak mengandung kode css untuk Threaded Comments.
<link type='text/css' rel='stylesheet' href='http://www.blogger.com/static/v1/widgets/2002994779-widget_css_bundle.css' />
Versi Perancang
Stylesheet versi Perancang yang disertakan pada template sudah terintegrasi kode css untuk Threaded Comments.
<link type='text/css' rel='stylesheet' href='http://www.blogger.com/static/v1/widgets/4132898751-widget_css_2_bundle.css' />

Kode Internal Template Threaded Comments

Sebenarnya antara template versi HTML4 dengan versi HTML5 tidak terlalu banyak perubahan dari sisi code (X)HTML-nya untuk fasilitas Threaded Comments. Perhatikan kode Threaded Comments dibawah ini dan bandingkan.

Versi Tata Letak - HTML4
<b:includable id='threaded_comment_js' var='post'>
<b:includable id='comment-form' var='post'>
<b:includable id='threaded_comments' var='post'>
<b:includable id='threaded-comment-form' var='post'>
<b:includable id='threaded_comment_css'>
<b:includable id='comments' var='post'>
Versi Perancang - HTML5
<b:includable id='threaded_comment_js' var='post'>
<b:includable id='comment-form' var='post'>
<b:includable id='threaded_comments' var='post'>
<b:includable id='threaded-comment-form' var='post'>
<b:includable id='comments' var='post'>
Dari perbandingan diatas maka akan ditemukan satu includable saja yang berbeda. Pada versi HTML4 terdapat <b:includable id='threaded_comment_css'> sedangkan pada versi HTML5 tidak ada karena stylesheet-nya sudah terintegrasi pada stylesheet default bawaan template (lihat penjelasan CSS Widget Default). Sedangkan ketika kita buka satu-satu bagian includable tersebut diatas maka ada perbedaan mendasar hanya pada bagian dalam kode dari <b:includable id='threaded_comment_js' var='post'> seperti dijelaskan selanjutnya.

Lokasi Penempatan Threaded Comments

Dimana lokasi penempatan Threaded Comments? Sebelum beralih ke Threaded Comments kita telusuri lebih dulu lokasi penempatan comments sebelum keluar versi Threaded Comments.
Lokasi Kode Sebelum Versi Threaded Comments
<b:if cond='data:blog.pageType == &quot;static_page&quot;'>
<b:include data='post' name='comments'/>
</b:if>

<b:if cond='data:blog.pageType == &quot;item&quot;'>
<b:include data='post' name='comments'/>
</b:if>
Perhatikan kode <b:include data='post' name='comments'/> itu adalah lokasi penempatan komentar dan form komentar. Pada template, kode tersebut terdapat pada 4 (empat) lokasi, jadi bukan hanya 2 (dua).

Lokasi Kode Sesudah Versi Threaded Comments
<b:if cond='data:blog.pageType == &quot;static_page&quot;'>
<b:if cond='data:post.showThreadedComments'>
<b:include data='post' name='threaded_comments'/>
<b:else/>
<b:include data='post' name='comments'/>
</b:if>
</b:if>

<b:if cond='data:blog.pageType == &quot;item&quot;'>
<b:if cond='data:post.showThreadedComments'>
<b:include data='post' name='threaded_comments'/>
<b:else/>
<b:include data='post' name='comments'/>
</b:if>
</b:if>
Perhatikan kode diatas lalu bandingkan dengan versi sebelum Threaded Comments, ada penambahan beberapa kode. Jika kita telusuri pada template, maka akan ditemukan 2 (dua) lokasi penempatan komentar.
Jika kode komentar template anda masih menggunakan versi awal (sebelum threaded comments) maka kode tersebut harus diganti dengan kode versi Threaded Comments. Namun ingat, bahwa kode tersebut terdapat pada 2 (dua) lokasi, gantilah kedua-duanya. Seperti dijelaskan pada artikel sebelumnya.

JavaScript Threaded Comments

Bagian inilah yang memiliki perbedaan sangat menonjol setelah dibuka dan dibandingkan. Namun tidak akan dibahas mendetail (lain waktu jika tidak malas). Dan ini merupakan salah satu solusi yang ditemukan Blogger Tune-Up ketika mendapati masalah Reply Threaded Comment tidak bekerja dengan baik, yaitu menukar JavaScript versi HTML4 dengan JavaScript versi HTML5.
Versi Tata Letak - HTML4
<b:includable id='threaded_comment_js' var='post'>
<script defer='defer' expr:src='data:post.commentSrc' type='text/javascript'/>
<script type='text/javascript'>
(function() {
var items = <data:post.commentJso/>;
var msgs = <data:post.commentMsgs/>;
var postId = &#39;<data:post.id/>&#39;;
var feed = &#39;<data:post.commentFeed/>&#39;;
var authorName = &#39;<data:post.author/>&#39;;
var authorUrl = &#39;<data:post.authorUrl/>&#39;;
var blogId = &#39;<data:top.id/>&#39;;
var baseUri = &#39;<data:post.commentBase/>&#39;;

// <![CDATA[
feed += '?alt=json&v=2&orderby=published&reverse=false&max-results=50';
var cursor = null;
if (items && items.length > 0) {
cursor = parseInt(items[items.length - 1].timestamp) + 1;
}

var bodyFromEntry = function(entry) {
if (entry.gd$extendedProperty) {
for (var k in entry.gd$extendedProperty) {
if (entry.gd$extendedProperty[k].name == 'blogger.contentRemoved') {
return '<span class="deleted-comment">' + entry.content.$t + '</span>';
}
}
}
return entry.content.$t;
}

var parse = function(data) {
cursor = null;
var comments = [];
if (data && data.feed && data.feed.entry) {
for (var i = 0, entry; entry = data.feed.entry[i]; i++) {
var comment = {};
// comment ID, parsed out of the original id format
var id = /blog-(\d+).post-(\d+)/.exec(entry.id.$t);
comment.id = id ? id[2] : null;
comment.body = bodyFromEntry(entry);
comment.timestamp = Date.parse(entry.published.$t) + '';
if (entry.author && entry.author.constructor === Array) {
var auth = entry.author[0];
if (auth) {
comment.author = {
name: (auth.name ? auth.name.$t : undefined),
profileUrl: (auth.uri ? auth.uri.$t : undefined),
avatarUrl: (auth.gd$image ? auth.gd$image.src : undefined)
};
}
}
if (entry.link) {
if (entry.link[2]) {
comment.link = comment.permalink = entry.link[2].href;
}
if (entry.link[3]) {
var pid = /.*comments\/default\/(\d+)\?.*/.exec(entry.link[3].href);
if (pid && pid[1]) {
comment.parentId = pid[1];
}
}
}
comment.deleteclass = 'item-control blog-admin';
if (entry.gd$extendedProperty) {
for (var k in entry.gd$extendedProperty) {
console.log(entry.gd$extendedProperty[k].name + ' - ' + entry.gd$extendedProperty[k].value);
if (entry.gd$extendedProperty[k].name == 'blogger.itemClass') {
comment.deleteclass += ' ' + entry.gd$extendedProperty[k].value;
}
}
}
comments.push(comment);
}
}
return comments;
};

var paginator = function(callback) {
if (hasMore()) {
var url = feed;
if (cursor) {
url += '&published-min=' + new Date(cursor).toISOString();
}
window.bloggercomments = function(data) {
var parsed = parse(data);
cursor = parsed.length < 50 ? null
: parseInt(parsed[parsed.length - 1].timestamp) + 1
callback(parsed);
window.bloggercomments = null;
}
url += '&callback=bloggercomments';
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
document.getElementsByTagName('head')[0].appendChild(script);
}
};
var hasMore = function() {
return !!cursor;
};
var getMeta = function(key, comment) {
if ('iswriter' == key) {
var matches = !!comment.author
&& comment.author.name == authorName
&& comment.author.profileUrl == authorUrl;
return matches ? 'true' : '';
} else if ('deletelink' == key) {
return baseUri + '/delete-comment.g?blogID=' + blogId + '&postID=' + comment.id;
} else if ('deleteclass' == key) {
return comment.deleteclass;
}
return '';
};

var replybox = null;
var replyUrlParts = null;
var replyParent = undefined;

var onReply = function(commentId, domId) {
if (replybox == null) {
// lazily cache replybox, and adjust to suit this style:
replybox = document.getElementById('comment-editor');
if (replybox != null) {
replybox.height = '250px';
replybox.style.display = 'block';
replyUrlParts = replybox.src.split('#');
}
}
if (replybox && (commentId !== replyParent)) {
document.getElementById(domId).insertBefore(replybox, null);
replybox.src = replyUrlParts[0]
+ (commentId ? '&parentID=' + commentId : '')
+ '#' + replyUrlParts[1];
replyParent = commentId;
}
};

var tok = 'comment-form_';
var hash = window.location.hash || '';
var startThread = hash.indexOf(tok) == 1 ? hash.substring(tok.length + 1) : undefined;

// Configure commenting API:
var configJso = {
'maxDepth': 2
};
var provider = {
'id': postId,
'data': items,
'loadNext': paginator,
'hasMore': hasMore,
'getMeta': getMeta,
'onReply': onReply,
'rendered': true,
'initReplyThread': startThread,
'config': configJso,
'messages': msgs
};

var render = function() {
if (window.goog && window.goog.comments) {
var holder = document.getElementById('comment-holder');
window.goog.comments.render(holder, provider);
}
};

// render now, or queue to render when library loads:
if (window.goog && window.goog.comments) {
render();
} else {
window.goog = window.goog || {};
window.goog.comments = window.goog.comments || {};
window.goog.comments.loadQueue = window.goog.comments.loadQueue || [];
window.goog.comments.loadQueue.push(render);
}
})();
// ]]>
</script>
</b:includable>
Versi Perancang - HTML5
<b:includable id='threaded_comment_js' var='post'>
<script async='async' expr:src='data:post.commentSrc' type='text/javascript'/>
<script type='text/javascript'>
(function() {
var items = <data:post.commentJso/>;
var msgs = <data:post.commentMsgs/>;
var config = <data:post.commentConfig/>;

// <![CDATA[
var cursor = null;
if (items && items.length > 0) {
cursor = parseInt(items[items.length - 1].timestamp) + 1;
}

var bodyFromEntry = function(entry) {
if (entry.gd$extendedProperty) {
for (var k in entry.gd$extendedProperty) {
if (entry.gd$extendedProperty[k].name == 'blogger.contentRemoved') {
return '<span class="deleted-comment">' + entry.content.$t + '</span>';
}
}
}
return entry.content.$t;
}

var parse = function(data) {
cursor = null;
var comments = [];
if (data && data.feed && data.feed.entry) {
for (var i = 0, entry; entry = data.feed.entry[i]; i++) {
var comment = {};
// comment ID, parsed out of the original id format
var id = /blog-(\d+).post-(\d+)/.exec(entry.id.$t);
comment.id = id ? id[2] : null;
comment.body = bodyFromEntry(entry);
comment.timestamp = Date.parse(entry.published.$t) + '';
if (entry.author && entry.author.constructor === Array) {
var auth = entry.author[0];
if (auth) {
comment.author = {
name: (auth.name ? auth.name.$t : undefined),
profileUrl: (auth.uri ? auth.uri.$t : undefined),
avatarUrl: (auth.gd$image ? auth.gd$image.src : undefined)
};
}
}
if (entry.link) {
if (entry.link[2]) {
comment.link = comment.permalink = entry.link[2].href;
}
if (entry.link[3]) {
var pid = /.*comments\/default\/(\d+)\?.*/.exec(entry.link[3].href);
if (pid && pid[1]) {
comment.parentId = pid[1];
}
}
}
comment.deleteclass = 'item-control blog-admin';
if (entry.gd$extendedProperty) {
for (var k in entry.gd$extendedProperty) {
if (entry.gd$extendedProperty[k].name == 'blogger.itemClass') {
comment.deleteclass += ' ' + entry.gd$extendedProperty[k].value;
}
}
}
comments.push(comment);
}
}
return comments;
};

var paginator = function(callback) {
if (hasMore()) {
var url = config.feed + '?alt=json&v=2&orderby=published&reverse=false&max-results=50';
if (cursor) {
url += '&published-min=' + new Date(cursor).toISOString();
}
window.bloggercomments = function(data) {
var parsed = parse(data);
cursor = parsed.length < 50 ? null
: parseInt(parsed[parsed.length - 1].timestamp) + 1
callback(parsed);
window.bloggercomments = null;
}
url += '&callback=bloggercomments';
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
document.getElementsByTagName('head')[0].appendChild(script);
}
};
var hasMore = function() {
return !!cursor;
};
var getMeta = function(key, comment) {
if ('iswriter' == key) {
var matches = !!comment.author
&& comment.author.name == config.authorName
&& comment.author.profileUrl == config.authorUrl;
return matches ? 'true' : '';
} else if ('deletelink' == key) {
return config.baseUri + '/delete-comment.g?blogID='
+ config.blogId + '&postID=' + comment.id;
} else if ('deleteclass' == key) {
return comment.deleteclass;
}
return '';
};

var replybox = null;
var replyUrlParts = null;
var replyParent = undefined;

var onReply = function(commentId, domId) {
if (replybox == null) {
// lazily cache replybox, and adjust to suit this style:
replybox = document.getElementById('comment-editor');
if (replybox != null) {
replybox.height = '250px';
replybox.style.display = 'block';
replyUrlParts = replybox.src.split('#');
}
}
if (replybox && (commentId !== replyParent)) {
document.getElementById(domId).insertBefore(replybox, null);
replybox.src = replyUrlParts[0]
+ (commentId ? '&parentID=' + commentId : '')
+ '#' + replyUrlParts[1];
replyParent = commentId;
}
};

var hash = (window.location.hash || '#').substring(1);
var startThread, targetComment;
if (/^comment-form_/.test(hash)) {
startThread = hash.substring('comment-form_'.length);
} else if (/^c[0-9]+$/.test(hash)) {
targetComment = hash.substring(1);
}

// Configure commenting API:
var configJso = {
'maxDepth': config.maxThreadDepth
};
var provider = {
'id': config.postId,
'data': items,
'loadNext': paginator,
'hasMore': hasMore,
'getMeta': getMeta,
'onReply': onReply,
'rendered': true,
'initComment': targetComment,
'initReplyThread': startThread,
'config': configJso,
'messages': msgs
};

var render = function() {
if (window.goog && window.goog.comments) {
var holder = document.getElementById('comment-holder');
window.goog.comments.render(holder, provider);
}
};

// render now, or queue to render when library loads:
if (window.goog && window.goog.comments) {
render();
} else {
window.goog = window.goog || {};
window.goog.comments = window.goog.comments || {};
window.goog.comments.loadQueue = window.goog.comments.loadQueue || [];
window.goog.comments.loadQueue.push(render);
}
})();
// ]]>
</script>
</b:includable>
Demikianlah saya coba memahami apa yang ingin disampaikan oleh kode-kode (X)HTML Blogger. Mohon koreksinya jika ada kesalahan pemahaman. Selamat berpusing ria dan Happy Blogging :)