Tampilkan postingan dengan label JSON Blogger. Tampilkan semua postingan
Tampilkan postingan dengan label JSON Blogger. Tampilkan semua postingan

Rabu, 18 Februari 2015

Parser JSON untuk Blogger: Posting dan Halaman Statis

Berikut adalah generalisasi fungsi untuk mengubah data JSON posting dan halaman statis Blogger menjadi markup HTML sesuai dengan keinginan. Di sini Saya membagi fungsinya menjadi dua macam yaitu generatePostsData untuk menangani posting dan halaman dalam bentuk daftar dan generatePostData untuk menangani posting dan halaman dalam bentuk tunggal:

Bentuk Daftar

function generatePostsData(json) {

// Poor configuration settings, develop them yourself!
var config = {
containerID: 'result-container', // Container ID to show the generated data
avatarSize: 50, // Default avatar size
noThumbnail: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABIAQMAAABvIyEEAAAABGdBTUEAALGPC/xhBQAAAAZQTFRFOjo6JycnNkxyjQAAADZJREFUKM9j+A8DDFRnNTCAACP9WewPGNgfkMmiwN7GH0CGfCPdWZT5V/7/DwZyWeSHFa1SHQDDGF2E0US40gAAAABJRU5ErkJggg==',
text: {
anon: 'Anonymous',
untagged: 'Untagged',
monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
}
};

var html = "",
item = "",
w = window,
d = document,
feed = json.feed,
container = d.getElementById(config.containerID),
postTotal = +feed.openSearch$totalResults.$t, // The post/page feeds' total (all)
postStartIndex = +feed.openSearch$startIndex.$t, // The post/page feeds' start index
postPerPage = +feed.openSearch$itemsPerPage.$t, // The post/page feeds' max results per page or per feed request
blogID = /\:blog-?(\d+)(\.|$)/.exec(feed.id.$t) ? /\:blog-?(\d+)(\.|$)/.exec(feed.id.$t)[1] : false, // The blog ID
blogTitle = feed.title.$t, // The post/page feeds' title
blogTags = false, // The available post/page tags (all)
blogSubTitle = feed.subtitle.$t, // The post/page feeds' subtitle
blogAuthorName = feed.author[0].name ? feed.author[0].name.$t : config.text.anon, // The blog/post author name
blogAuthorAvatar = feed.author[0].gd$image.src.replace(/\/s\d+(\-c)?\//, '/s' + config.avatarSize + '-c/'), // The blog/post author profile avatar URL
blogGeneratorName = feed.generator.$t, // The blog generator name (Blogger)
blogGeneratorURL = feed.generator.uri; // The blog generator URL (http://www.blogger.com)

// Remove the leading `http://` or `https://` in blog/post author profile avatar URL
// blogAuthorAvatar = blogAuthorAvatar.replace(/^https?\:/, "");

// No container found
if (!container) {
alert('Container not found.');
return;
}

// Getting the blog tags
if (feed.category && feed.category.length) {
blogTags = [];
for (var h = 0, hen = feed.category.length; h < hen; ++h) {
blogTags.push(feed.category[h].term);
}
// Sort the blog tags alphabetically
blogTags = blogTags.sort();
}

// No posts/pages yet
if (!feed.entry || feed.entry.length === 0) {
container.innerHTML = '<p>No posts/pages yet.</p>';
return;
}

// Building the markup ...
html += '<h1>' + blogTitle + '</h1>';
html += '<h2>' + blogSubTitle + '</h2>';
html += '<p><b>Blog ID:</b> ' + blogID + '</p>';
html += '<p><b>Blog Tags:</b> ' + (blogTags !== false ? blogTags.join(', ') : config.text.untagged) + '</p>';
html += '<p><b>Blog Author Name:</b> ' + blogAuthorName + '</p>';
html += '<p><b>Blog Author Avatar URL:</b> ' + blogAuthorAvatar + '</p>';
html += '<p><b>Total Posts:</b> ' + postTotal + '</p>';
html += '<p><b>Posts Per Page:</b> ' + postPerPage + '</p>';
html += '<p><b>Posts Start Index:</b> ' + postStartIndex + '</p>';
html += '<hr>';
html += '<ol>';

var posts = feed.entry;

for (var i = 0, ien = posts.length; i < ien; ++i) {

var post = posts[i], // A single post/page object
postID = post.id.$t, // The post/page ID
postPublish = post.published.$t, // The post/page publishing time in ISO format
postUpdate = post.updated.$t, // The post/page updating time in ISO format
postDate = postPublish, // The post/page publishing time in human-readable format
postURL = false, // The post/page URL
postTags = false, // The post/page tags
postCommentTotal = post.thr$total ? +post.thr$total.$t : 0, // The post/page comments total
postCommentFeedURL = false, // The post/page comments feed URL
postThumbnail = post.media$thumbnail ? post.media$thumbnail.url : config.noThumbnail, // The post/page thumbnail
postAuthorName = post.author[0].name ? post.author[0].name.$t : config.text.anon, // The post/page author name
postAuthorURL = post.author[0].uri ? post.author[0].uri.$t : false, // The post/page author profile URL
postAuthorAvatar = post.author[0].gd$image.src.replace(/\/s\d+(\-c)?\//, '/s' + config.avatarSize + '-c/'), // The post/page author profile avatar URL
postTitle = post.title.$t, // The post/page title
postContent = post.content ? post.content.$t : post.summary.$t.replace(/<br *\/?>|[\s]+/gi, ' ').replace(/<.*?>|[<>]/g, ""), // The post/page content
postEditURL = false; // The post/page edit URL

// Generate human-readable post/page date format
var date = postDate.split('T')[0].split('-');
postDate = date[2] + ' ' + config.text.monthNames[(+date[1]) - 1] + ' ' + date[0];

// Remove the leading `http://` or `https://` in post/page thumbnail URL
// postThumbnail = postThumbnail.replace(/^https?\:/, "");

// Remove the leading `http://` or `https://` in post/page author profile avatar URL
// postAuthorAvatar = postAuthorAvatar.replace(/^https?\:/, "");

for (var j = 0, jen = post.link.length; j < jen; ++j) {
item = post.link[j];
if (item.rel == 'self') {
// Getting the original post/page ID
postID = item.href.split('/').pop();
// Getting the post/page edit URL
postEditURL = item.href.replace(/\/feeds\/(\d+)\/(post|page)s?\/(default|summary)\/(\d+)/, '/$2-edit.g?blogID=$1&$2ID=$4');
}
// Getting the post/page URL
if (item.rel == 'alternate') {
postURL = item.href;
}
// Getting the post/page comment feed URL
if (item.rel == 'replies' && item.type == 'application/atom+xml') {
postCommentFeedURL = item.href;
}
}

// Trying to get the external image URL from post/page content
if (post.content && postThumbnail == config.noThumbnail) {
var image = /<img +(.*?)src=(['"])([^'"]+?)(['"])(.*?) *\/?>/i.exec(post.content.$t);
postThumbnail = image && image[3] ? image[3] : config.noThumbnail;
}

// Getting the post/page tags
if (post.category && post.category.length) {
postTags = [];
for (var k = 0, ken = post.category.length; k < ken; ++k) {
postTags.push(post.category[k].term);
}
// Sort the post/page tags alphabetically
postTags = postTags.sort();
}

// Building the markup ...
html += '<li>';
html += '<p><b>ID:</b> ' + postID + '</p>';
html += '<p><b>Publish:</b> ' + postPublish + '</p>';
html += '<p><b>Update:</b> ' + postUpdate + '</p>';
html += '<p><b>Date:</b> ' + postDate + '</p>';
html += '<p><b>URL:</b> ' + postURL + '</p>';
html += '<p><b>Tags:</b> ' + (postTags !== false ? postTags.join(', ') : config.text.untagged) + '</p>';
html += '<p><b>Comment Total:</b> ' + postCommentTotal + '</p>';
html += '<p><b>Comment Feed URL:</b> ' + postCommentFeedURL + '</p>';
html += '<p><b>Thumbnail:</b> ' + postThumbnail + '</p>';
html += '<p><b>Author:</b> ' + postAuthorName + '</p>';
html += postAuthorURL !== false ? '<p><b>Author URL:</b> ' + postAuthorURL + '</p>' : "";
html += '<p><b>Author Avatar URL:</b> ' + postAuthorAvatar + '</p>';
html += '<p><b>Edit URL:</b> ' + postEditURL + '</p>';
html += '<p><b>Title:</b> ' + postTitle + '</p>';
html += '<p><b>Content:</b></p>';
html += '<div>' + postContent + '</div>';
html += '</li>';

}

// Building the markup ...
html += '</ol>';

// Show the generated data to the container ...
container.innerHTML = html;

}

Penggunaan

Urutannya dimulai dari penulisan HTML untuk menampung data yang akan digenerasikan oleh fungsi di atas, dilanjutkan dengan memasukkan fungsi di atas ke dalam tag <script>, lalu memanggil data JSON dengan menggunakan nilai parameter URL callback berupa generatePostsData, sesuai dengan nama fungsi di atas:

<div id="result-container">Loading&hellip;</div>
<script>
function generatePostsData(json) { … }
</script>
<script src="http://nama_blog.blogspot.com/feeds/posts/default?alt=json-in-script&orderby=published&callback=generatePostsData"></script>

Bentuk Tunggal

function generatePostData(json) {

// Poor configuration settings, develop them yourself!
var config = {
containerID: 'result-container', // Container ID to show the generated data
avatarSize: 50, // Default avatar size
noThumbnail: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABIAQMAAABvIyEEAAAABGdBTUEAALGPC/xhBQAAAAZQTFRFOjo6JycnNkxyjQAAADZJREFUKM9j+A8DDFRnNTCAACP9WewPGNgfkMmiwN7GH0CGfCPdWZT5V/7/DwZyWeSHFa1SHQDDGF2E0US40gAAAABJRU5ErkJggg==',
text: {
anon: 'Anonymous',
untagged: 'Untagged',
monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
}
};

var html = "",
item = "",
w = window,
d = document,
container = d.getElementById(config.containerID);

// No container found
if (!container) {
alert('Container not found.');
return;
}

var post = json.entry, // The post/page object
postID = post.id.$t, // The post/page ID
postPublish = post.published.$t, // The post/page publishing time in ISO format
postUpdate = post.updated.$t, // The post/page updating time in ISO format
postDate = postPublish, // The post/page publishing time in human-readable format
postURL = false, // The post/page URL
postTags = false, // The post/page tags
postCommentTotal = post.thr$total ? +post.thr$total.$t : 0, // The post/page comments total
postCommentFeedURL = false, // The post/page comments feed URL
postThumbnail = post.media$thumbnail ? post.media$thumbnail.url : config.noThumbnail, // The post/page thumbnail
postAuthorName = post.author[0].name ? post.author[0].name.$t : config.text.anon, // The post/page author name
postAuthorURL = post.author[0].uri ? post.author[0].uri.$t : false, // The post/page author profile URL
postAuthorAvatar = post.author[0].gd$image.src.replace(/\/s\d+(\-c)?\//, '/s' + config.avatarSize + '-c/'), // The post/page author profile avatar URL
postTitle = post.title.$t, // The post/page title
postContent = post.content ? post.content.$t : post.summary.$t.replace(/<br *\/?>|[\s]+/gi, ' ').replace(/<.*?>|[<>]/g, ""), // The post/page content
postEditURL = false; // The post/page edit URL

// Generate human-readable post/page date format
var date = postDate.split('T')[0].split('-');
postDate = date[2] + ' ' + config.text.monthNames[(+date[1]) - 1] + ' ' + date[0];

// Remove the leading `http://` or `https://` in post/page thumbnail URL
// postThumbnail = postThumbnail.replace(/^https?\:/, "");

// Remove the leading `http://` or `https://` in post/page author profile avatar URL
// postAuthorAvatar = postAuthorAvatar.replace(/^https?\:/, "");

for (var j = 0, jen = post.link.length; j < jen; ++j) {
item = post.link[j];
if (item.rel == 'self') {
// Getting the original post/page ID
postID = item.href.split('/').pop();
// Getting the post/page edit URL
postEditURL = item.href.replace(/\/feeds\/(\d+)\/(post|page)s?\/(default|summary)\/(\d+)/, '/$2-edit.g?blogID=$1&$2ID=$4');
}
// Getting the post/page URL
if (item.rel == 'alternate') {
postURL = item.href;
}
// Getting the post/page comment feed URL
if (item.rel == 'replies' && item.type == 'application/atom+xml') {
postCommentFeedURL = item.href;
}
}

// Trying to get the external image URL from post/page content
if (post.content && postThumbnail == config.noThumbnail) {
var image = /<img +(.*?)src=(['"])([^'"]+?)(['"])(.*?) *\/?>/i.exec(post.content.$t);
postThumbnail = image && image[3] ? image[3] : config.noThumbnail;
}

// Getting the post/page tags
if (post.category && post.category.length) {
postTags = [];
for (var k = 0, ken = post.category.length; k < ken; ++k) {
postTags.push(post.category[k].term);
}
// Sort the post/page tags alphabetically
postTags = postTags.sort();
}

// Building the markup ...
html += '<li>';
html += '<p><b>ID:</b> ' + postID + '</p>';
html += '<p><b>Publish:</b> ' + postPublish + '</p>';
html += '<p><b>Update:</b> ' + postUpdate + '</p>';
html += '<p><b>Date:</b> ' + postDate + '</p>';
html += '<p><b>URL:</b> ' + postURL + '</p>';
html += '<p><b>Tags:</b> ' + (postTags !== false ? postTags.join(', ') : config.text.untagged) + '</p>';
html += '<p><b>Comment Total:</b> ' + postCommentTotal + '</p>';
html += '<p><b>Comment Feed URL:</b> ' + postCommentFeedURL + '</p>';
html += '<p><b>Thumbnail:</b> ' + postThumbnail + '</p>';
html += '<p><b>Author:</b> ' + postAuthorName + '</p>';
html += postAuthorURL !== false ? '<p><b>Author URL:</b> ' + postAuthorURL + '</p>' : "";
html += '<p><b>Author Avatar URL:</b> ' + postAuthorAvatar + '</p>';
html += '<p><b>Edit URL:</b> ' + postEditURL + '</p>';
html += '<p><b>Title:</b> ' + postTitle + '</p>';
html += '<p><b>Content:</b></p>';
html += '<div>' + postContent + '</div>';
html += '</li>';

// Building the markup ...
html += '</ol>';

// Show the generated data to the container ...
container.innerHTML = html;

}

Penggunaan

Sisipkan ID posting/halaman statis setelah path default atau summary:

<div id="result-container">Loading&hellip;</div>
<script>
function generatePostData(json) { … }
</script>
<script src="http://nama_blog.blogspot.com/feeds/posts/default/1962799387619194999?alt=json-in-script&callback=generatePostData"></script>

Ekstra

Halaman Statis

Data halaman statis dapat dipanggil dengan menggunakan format URL seperti ini:

http://nama_blog.blogspot.com/feeds/pages/default?alt=json-in-script&orderby=published&callback=generatePostsData
http://nama_blog.blogspot.com/feeds/pages/default?alt=json-in-script&callback=generatePostData

Mode Ringkas

Mode ringkas dapat dipanggil dengan menggunakan format URL seperti ini:

http://nama_blog.blogspot.com/feeds/posts/summary?alt=json-in-script&orderby=published&callback=generatePostsData
http://nama_blog.blogspot.com/feeds/pages/summary?alt=json-in-script&orderby=published&callback=generatePostsData
http://nama_blog.blogspot.com/feeds/posts/summary?alt=json-in-script&callback=generatePostData
http://nama_blog.blogspot.com/feeds/pages/summary?alt=json-in-script&callback=generatePostData

Parser JSON untuk Blogger: Komentar

Berikut adalah generalisasi fungsi untuk mengubah data JSON komentar Blogger menjadi markup HTML sesuai dengan keinginan. Saya sudah mendefinisikan ulang data-data yang penting ke dalam variabel, sehingga Anda bisa memodifikasi skrip ini dengan lebih mudah:

Bentuk Daftar

function generateCommentsData(json) {

// Poor configuration settings, develop them yourself!
var config = {
containerID: 'result-container', // Container ID to show the generated data
avatarSize: 50, // Default avatar size
text: {
anon: 'Anonymous'
}
};

var html = "",
item = "",
w = window,
d = document,
feed = json.feed,
container = d.getElementById(config.containerID),
postCommentTotal = +feed.openSearch$totalResults.$t, // The comment feeds' total (all)
postCommentStartIndex = +feed.openSearch$startIndex.$t, // The comment feeds' start index
postCommentPerPage = +feed.openSearch$itemsPerPage.$t, // The comment feeds' max results per page or per feed request
blogID = /\:blog-?(\d+)(\.|$)/.exec(feed.id.$t) ? /\:blog-?(\d+)(\.|$)/.exec(feed.id.$t)[1] : false, // The blog ID
postID = /\.post-?(\d+)(\.|$)/.exec(feed.id.$t) ? /\.post-?(\d+)(\.|$)/.exec(feed.id.$t)[1] : false, // The current post ID (if any)
postURL = false, // The current post URL (if any)
blogTitle = feed.title.$t, // The comment feeds' title
blogAuthorName = feed.author[0].name ? feed.author[0].name.$t : config.text.anon, // The blog/post author name
blogAuthorAvatar = feed.author[0].gd$image.src.replace(/\/s\d+(\-c)?\//, '/s' + config.avatarSize + '-c/'), // The blog/post author profile avatar URL
blogGeneratorName = feed.generator.$t, // The blog generator name (Blogger)
blogGeneratorURL = feed.generator.uri; // The blog generator URL (http://www.blogger.com)

// Remove the leading `http://` or `https://` in blog/post author profile avatar URL
// blogAuthorAvatar = blogAuthorAvatar.replace(/^https?\:/, "");

// No container found
if (!container) {
alert('Container not found.');
return;
}

// Getting the current post URL (if any)
if (postID) {
for (var h = 0, hen = feed.link.length; h < hen; ++h) {
item = feed.link[h];
if (item.rel == 'alternate') {
postURL = item.href;
break;
}
}
}

// No comments yet
if (!feed.entry || feed.entry.length === 0) {
container.innerHTML = '<p>No comments yet.</p>';
return;
}

// Building the markup ...
html += '<h1>' + blogTitle + '</h1>';
html += '<p><b>Blog ID:</b> ' + blogID + '</p>';
html += '<p><b>Post ID:</b> ' + postID + '</p>';
html += '<p><b>Post URL:</b> ' + postURL + '</p>';
html += '<p><b>Blog Author Name:</b> ' + blogAuthorName + '</p>';
html += '<p><b>Blog Author Avatar URL:</b> ' + blogAuthorAvatar + '</p>';
html += '<p><b>Total Comments:</b> ' + postCommentTotal + '</p>';
html += '<p><b>Comments Per Page:</b> ' + postCommentPerPage + '</p>';
html += '<p><b>Comments Start Index:</b> ' + postCommentStartIndex + '</p>';
html += '<hr>';
html += '<ol>';

var comments = feed.entry;

for (var i = 0, ien = comments.length; i < ien; ++i) {

var comment = comments[i], // A single comment feed object
commentID = comment.id.$t, // The comment ID
commentPublish = comment.published.$t, // The comment publishing time in ISO format
commentUpdate = comment.updated.$t, // The comment updating time in ISO format
commentDate = commentPublish, // The comment publishing time in timestamp format you defined in the dashboard
commentAuthorName = comment.author[0].name ? comment.author[0].name.$t : config.text.anon, // The comment author name
commentAuthorURL = comment.author[0].uri ? comment.author[0].uri.$t : false, // The comment author profile URL
commentAuthorAvatar = comment.author[0].gd$image.src.replace(/\/s\d+(\-c)?\//, '/s' + config.avatarSize + '-c/'), // The comment author profile avatar URL
commentContent = comment.content ? comment.content.$t : comment.summary.$t.replace(/<br *\/?>|[\s]+/gi, ' ').replace(/<.*?>|[<>]/g, ""), // The comment content
commentParent = false, // The comment parent ID (if any, for child comments)
commentPermalink = false, // The comment permalink
commentIsAdmin = commentAuthorName === blogAuthorName || commentAuthorAvatar === blogAuthorAvatar, // Is this comment was created by the blog/post author?
commentDeleteURL = false; // The comment delete URL

// Remove the leading `http://` or `https://` in comment author profile avatar URL
// commentAuthorAvatar = commentAuthorAvatar.replace(/^https?\:/, "");

for (var j = 0, jen = comment.link.length; j < jen; ++j) {
item = comment.link[j];
if (item.rel == 'self') {
// Getting the original comment ID
commentID = item.href.split('/').pop();
// Getting the comment delete URL
commentDeleteURL = item.href.replace(/\/feeds\/(\d+)\/(\d+)\/comments?\/(default|summary)\/(\d+)/, '/delete-comment.g?blogID=$1&postID=$4');
}
// Getting the comment permalink URL
if (item.rel == 'alternate') {
commentPermalink = item.href;
}
// Getting the comment parent ID (if any)
if (item.rel == 'related') {
var parentID = item.href.split('/').pop();
commentParent = commentID !== parentID ? parentID : false;
}
}

// Getting the comment publishing time in timestamp format you defined in the dashboard
for (var k = 0, ken = comment.gd$extendedProperty.length; k < ken; ++k) {
item = comment.gd$extendedProperty[k];
if (item.name == 'blogger.displayTime') {
commentDate = item.value;
break;
}
}

// Building the markup ...
html += '<li>';
html += '<p><b>ID:</b> ' + commentID + '</p>';
html += '<p><b>Publish:</b> ' + commentPublish + '</p>';
html += '<p><b>Update:</b> ' + commentUpdate + '</p>';
html += '<p><b>Date:</b> ' + commentDate + '</p>';
html += '<p><b>Author:</b> ' + commentAuthorName + '</p>';
html += commentAuthorURL !== false ? '<p><b>URL:</b> ' + commentAuthorURL + '</p>' : "";
html += '<p><b>Avatar URL:</b> ' + commentAuthorAvatar + '</p>';
html += commentParent !== false ? '<p><b>Parent ID:</b> ' + commentParent + '</p>' : "";
html += '<p><b>Permalink:</b> ' + commentPermalink + '</p>';
html += '<p><b>Status:</b> ' + (commentIsAdmin ? 'admin' : 'guest') + '</p>';
html += '<p><b>Delete URL:</b> ' + commentDeleteURL + '</p>';
html += '<p><b>Message:</b></p>';
html += '<div>' + commentContent + '</div>';
html += '</li>';

}

// Building the markup ...
html += '</ol>';

// Show the generated data to the container ...
container.innerHTML = html;

}

Penggunaan

Urutannya dimulai dari penulisan HTML untuk menampung data yang akan digenerasikan oleh fungsi di atas, dilanjutkan dengan memasukkan fungsi di atas ke dalam tag <script>, lalu memanggil data JSON dengan menggunakan nilai parameter URL callback berupa generateCommentsData, sesuai dengan nama fungsi di atas:

<div id="result-container">Loading&hellip;</div>
<script>
function generateCommentsData(json) { … }
</script>
<script src="http://nama_blog.blogspot.com/feeds/comments/default?alt=json-in-script&reverse=false&orderby=published&callback=generateCommentsData"></script>

Ekstra

Memanggil Data Komentar untuk Posting Tertentu

Gunakan format URL seperti ini untuk membatasi pemanggilan data komentar hanya untuk komentar-komentar yang terdapat pada posting dengan ID 6427706034645358331:

http://nama_blog.blogspot.com/feeds/6427706034645358331/comments/default?alt=json-in-script&reverse=false&orderby=published&callback=generateCommentsData

Sayangnya Saya tidak berhasil menemukan referensi resmi mengenai JSON Blogger versi 1.0 di Google Developer. Dulu memang pernah ada tapi sekarang sudah tidak ada lagi. Yang ada sekarang lebih banyak ditujukan untuk API Blogger versi 3.0+ yang membutuhkan autentikasi.

Sabtu, 06 Juli 2013

Widget Artikel Terkait Blogger: 6 dalam 1

Pembaharuan 7 Februari 2015: Halaman proyek dipindah dari blogger-json-experiment ke dte-project untuk memperbaiki bug pada URL yang memiliki kueri ?m=0 atau ?m=1 di dalamnya.

Contoh tampilan widget artikel terkait Blogger yang paling populer
Contoh-contoh tampilan widget. Klik untuk memperbesar gambar.

Saat ini terdiri dari 6 jenis tampilan yang bisa Anda sesuaikan sendiri. Widget ini merupakan pengembangan dari konsep random post ringan yang pernah Saya tuliskan di sini. Hanya saja, karena pemanggilan feed posting difilter melalui label posting yang spesifik, maka widget random post ini sekarang bisa dijadikan sebagai widget artikel terkait (random post dengan sortir label otomatis berdasarkan posting).

Widget-widget artikel terkait yang lama kurang bersahabat menurut Saya, sehingga Saya membuatnya lagi secara pribadi. Dan untuk menyingkat jumlah posting, maka model-model widget artikel terkait yang sudah populer kini telah Saya gabungkan menjadi satu kesatuan. Tujuan utamanya tetap. Mencoba untuk menampilkan posting-posting lama dengan jarak waktu penerbitan yang tidak terbatas namun dengan tenaga sesedikit mungkin:

Klik tombol Preview & Get Code! Di situ nanti Anda akan mendapatkan kode CSS dan XML template. Masuk ke halaman editor HTML template kemudian letakkan kode CSS yang dihasilkan di atas kode ]]></b:skin>. Kode XML bisa Anda letakkan di mana saja di dalam elemen <b:includable id='post' var='post'/>, tapi Saya sarankan untuk meletakkan kode tersebut di atas kode ini karena posisinya yang mudah ditemukan dan lebih minim resiko:

<div class='post-footer'>

Tabel Pengaturan

Pengaturannya tidak banyak, dan karena Saya sudah membuat generatornya, jadi Anda sebenarnya tidak perlu lagi menyentuh variabel-variabel di dalam widget ini secara langsung. Tabel ini Saya buat untuk kepentingan dokumentasi saja:

OpsiKeterangan
widgetStyleKode model widget artikel terkait berupa angka. Mengubah nilai variabel ini akan mengubah markup HTML widget, sehingga kode CSS harus disesuaikan ulang. Lebih baik gunakan generator.
homePageDigunakan untuk menentukan URL blog Anda. Nilai sudah diotomatisasikan melalui elemen <data:blog.homepageUrl/>
widgetTitleMarkup HTML untuk judul/kepala widget. Bisa digunakan untuk menyisipkan kode HTML lain juga jika mau. Kode tersebut nantinya akan tampil di atas daftar item posting di dalam widget.
numPostsDigunakan untuk menentukan jumlah posting yang ingin ditampilkan.
summaryLengthDigunakan untuk menentukan panjang karakter ringkasan posting.
titleLengthDigunakan untuk memotong jumlah karakter judul posting sesuai dengan besar angka yang dituliskan. Ubah menjadi "auto" untuk menonaktifkan pemotongan karakter pada judul posting.
thumbnailSizeDigunakan untuk menentukan lebar/tinggi thumbnail posting yang berbentuk kotak.
noImageIsi dengan URL gambar cadangan untuk posting yang tidak memiliki gambar.
containerIdID elemen HTML untuk menampung daftar posting yang berhasil terpanggil.
newTabLinkUbah nilainya menjadi true untuk membuat tautan-tautan di dalam widget ini membuka di tab/jendela baru secara otomatis ketika diklik.
moreTextTeks “Baca Selengkapnya”.
callBackFungsi tambahan yang akan bekerja setelah daftar posting termuat ke dalam kontainer widget. Lihat penjelasannya di bawah.

Menerapkan Fungsi callBack Widget

Opsi ini berfungsi untuk menciptakan perilaku tambahan pada widget. Fungsi ini akan bekerja setelah widget berhasil dimuat. Cocok digunakan untuk menambahkan efek-efek khusus. Dasar penerapannya adalah seperti ini. Kode di bawah ini akan menampilkan pesan peringatan bahwa widget telah berhasil termuat jika widget berhasil termuat:

var relatedPostConfig = {
callBack: function() {
alert('Widget successfully loaded.');
}
};

Contoh 1: Membuat Efek Transisi Hover pada Tooltip

Set tampilan widget menjadi artikel terkait dengan tooltip, kemudian buat fungsi JQuery seperti ini:

var relatedPostConfig = {
...
widgetStyle: 4,
containerId: "random-post",
callBack: function() {
$('#' + this.containerId + ' li').hover(function() {
$('.related-post-item-tooltip', this).filter(':not(:animated)').animate({
marginBottom: -5,
opacity: "show"
}, 400);
}, function() {
$('.related-post-item-tooltip', this).animate({
marginBottom: 10,
opacity: "hide"
}, 200);
}).find('.related-post-item-tooltip').css('margin-bottom', 10);
}
};

Hapus efek :focus dan :hover pada CSS:

.related-post-style-4 .related-post-item:focus .related-post-item-tooltip,
.related-post-style-4 .related-post-item:hover .related-post-item-tooltip {display:block}

Contoh 2: Membuat Efek Sliding pada Tabir Judul

Set tampilan widget menjadi artikel terkait dengan tabir tooltip:

var relatedPostConfig = {
...
widgetStyle: 5,
titleLength: 50,
thumbnailSize: 130,
containerId: "random-post",
callBack: function() {
$('#' + this.containerId + ' li').hover(function() {
$('.related-post-item-tooltip', this).filter(':not(:animated)').animate({
marginTop: "50%"
}, 400);
}, function() {
$('.related-post-item-tooltip', this).animate({
marginTop: "100%"
}, 200);
}).find('.related-post-item-tooltip').css('margin-top', '100%');
}
};

Hapus deklarasi display:none pada selektor tooltip widget, hapus juga efek :focus dan :hover pada CSS:

.related-post-style-5 .related-post-item-tooltip {
display:block;
background-color:black;
background-color:rgba(0,0,0,.9);
position:absolute;
top:0;
right:0;
bottom:0;
left:0;
padding:10px;
line-height:normal;
font-style:italic;
color:white;
overflow:hidden;
display:none;
}
.related-post-style-5 .related-post-item:focus .related-post-item-tooltip,
.related-post-style-5 .related-post-item:hover .related-post-item-tooltip {display:block}

Contoh 3: Membuat Efek News Ticker

Set tampilan widget menjadi artikel terkait tipe thumbnail dan summary:

var relatedPostConfig = {
...
numPosts: 10,
widgetStyle: 2,
containerId: "random-post",
callBack: function() {
var $container = $('#' + this.containerId + ' > ul'),
$list = $container.find('li'),
innerHeight = $list.first().height(),
outerHeight = $list.first().outerHeight(true);
// Container & list height setup
$container.css({
"height": outerHeight * $list.length / 2,
"overflow": "hidden"
});
$list.css({
"height": innerHeight,
"overflow": "hidden"
});
// Animation
function newsTicker() {
window.setTimeout(function() {
$container.find('li:last').stop().animate({
opacity: 0
}, 1000, function() {
$(this).hide().prependTo($container).slideDown(1000, function() {
$(this).stop().animate({
opacity: 1
}, 1000, newsTicker);
});
});
}, 3000);
} newsTicker();
}
};

Dan seterusnya. Buat efek-efek baru yang lain sesuka hati melalui opsi callBack.

Catatan: Saat menerapkan beberapa fungsi di atas, mungkin templat Anda nanti menjadi tidak bisa disimpan. Cobalah untuk mengelilingi fungsi callBack dengan komentar CDATA seperti ini:

var relatedPostConfig = {
callBack: function() {
//<![CDATA[

Tuliskan fungsi di sini...

//]]>
}
};

Atau baca posting ini sebagai referensi tambahan.

Selasa, 02 April 2013

Widget Blogger untuk Menampilkan Daftar Posting Terbaru dalam Label-Label yang Spesifik

Screenshot of multiple recent post widget for Blogger.

Judulnya sangat panjang karena Saya sendiri tidak tahu harus memberi nama apa kepada widget ini. Pada intinya widget ini adalah widget recent post biasa, namun memiliki kemampuan untuk menggandakan diri agar bisa menampilkan daftar posting terbaru lebih dari satu buah berdasarkan label yang spesifik:

Pada awalnya Saya membuat widget ini agar bisa menampilkan posting-posting terbaru dari blog tunggal saja, namun mengingat ada juga beberapa blog yang suka menampilkan daftar posting dari blog lain, jadi sekarang widget ini tidak hanya berfungsi untuk menampilkan daftar posting dari satu feed blog, melainkan juga bisa menampilkan daftar posting dari feed blog Blogger yang lain.

Pemasangan Widget

Masuk ke halaman Tata Letak kemudian tambahkan sebuah widget HTML/JavaScript. Salin kode ini dan letakkan di dalam formulirnya:

<link rel="stylesheet" type="text/css" scoped="scoped" href="http://blogger-json-experiment.googlecode.com/svn/resources/multi-feed/multi-feed.min.css"/>
<style type="text/css" scoped="scoped">
/* Custom CSS */
.list-entries {
margin:5px;
width:270px; /* Lebar widget */
float:left;
font-size:11px;
}
</style>

<div id="feed-list-container"></div>
<div style="clear:both;"></div>

<script type="text/javascript">
var multiFeed = {
feedsUri: [
{
name: "Judul Widget 1",
url: "http://nama_blog-1.blogspot.com",
tag: "JQuery"
},
{
name: "Judul Widget 2",
url: "http://nama_blog-2.blogspot.com",
tag: "CSS"
},
{
name: "Judul Widget 1",
url: "http://nama_blog-3.blogspot.com",
tag: "Widget"
}
],
numPost: 4,
showThumbnail: true,
showSummary: true,
summaryLength: 80,
titleLength: "auto",
thumbSize: 72,
containerId: "feed-list-container",
readMore: {
text: "Selengkapnya",
endParam: "?max-results=20"
}
};
</script>
<script type="text/javascript" src="http://blogger-json-experiment.googlecode.com/svn/resources/multi-feed/multi-feed.min.js"></script>

Konfigurasi

OpsiKeterangan
feedsUriBerupa array objek dimana di dalamnya terdapat beberapa data berupa name, url dan tag: name digunakan untuk menentukan judul widget, url digunakan untuk menentukan URL feed, tag digunakan untuk menentukan tag/label posting yang ingin ditampilkan.
numPostDigunakan untuk menentukan jumlah posting yang ingin ditampilkan
showThumbnailGanti nilainya menjadi false untuk menyembunyikan gambar posting
showSummaryGanti nilainya menjadi false untuk menyembunyikan ringkasan posting
summaryLengthDigunakan untuk menentukan jumlah karakter ringkasan posting
titleLengthDigunakan untuk memotong karakter judul posting yang terlalu panjang. Ganti nilainya menjadi angka untuk memotong karakter sebanyak angka tersebut, atau set sebagai "auto" untuk membiarkan judul posting sebagai apa adanya
thumbSizeDigunakan untuk menentukan ukuran thumbnail posting
containerIdDigunakan untuk menentukan ID kontainer yang akan digunakan untuk memuat widget
readMoreTerdiri dari text dan endParam. text digunakan untuk menentukan label tautan “Baca Selengkapnya” yang terletak di bawah masing-masing widget dan endParam digunakan untuk menentukan parameter akhir tautan tersebut (abaikan jika tidak perlu).

Opsi feedsUri merupakan array objek dimana setiap objek akan mewakili masing-masing widget:

// Tiga objek = tiga widget
feedsUri: [{}, {}, {}]

Setiap objek berisi beberapa data yang digunakan untuk menentukan judul widget, sumber URL dan tag/kategori posting spesifik:

feedsUri: [
{
name: "Judul Widget 1",
url: "http://nama_blog-1.blogspot.com",
tag: "Blogger"
},
{
name: "Judul Widget 2",
url: "http://nama_blog-2.blogspot.com",
tag: "WordPress"
},
{
name: "Judul Widget 3",
url: "http://nama_blog-3.blogspot.com",
tag: "SEO"
}
]

Setelah semua konfigurasi sudah diatur dengan benar, klik Simpan Widget. Modifikasi tampilan bisa dilakukan di dalam tag <style> yang Saya tambahkan pada widget di atas.

Kode Sumber

  1. multi-feed.css
  2. multi-feed.min.css
  3. multi-feed.js
  4. multi-feed.min.js

Senin, 18 Maret 2013

Plugin Feed Rotator untuk Blogger

Image of Blogger Feed Rotator plugin demo

Plugin ini pada dasarnya merupakan perpaduan antara Basic JQuery Image Rotator dengan yang kemudian Saya jadikan sebagai semacam slideshow dengan konten yang akan diperbaharui secara otomatis berdasarkan posting yang sudah terbit. Saya memutuskan untuk membuat ini karena ada beberapa orang yang menanyakan melalui email mengenai bagaimana cara memadukan plugin Image Rotator dengan JSON Blogger.

Plugin JQuery Image Rotator ini juga sudah dimodifikasi hampir secara keseluruhan, khusus untuk diaplikasikan pada widget ini. Beberapa hal yang Saya tambahkan diantaranya adalah navigasi, efek animasi kontainer dan dukungan fungsi callback yang dapat dijalankan pada sesi-sesi tertentu.

Implementasi Dasar

Berikut ini adalah contoh implementasi paling mendasar yang bisa Anda lakukan untuk mengaktifkan plugin:

<link rel="stylesheet" type="text/css" href="http://blogger-json-experiment.googlecode.com/svn/resources/blogger-feed-rotator-plugin/default-style.min.css"/>
<div id="slider-rotator" class="slider-rotator loading"></div>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="http://blogger-json-experiment.googlecode.com/svn/resources/blogger-feed-rotator-plugin/blogger-feed-rotator.min.js"></script>
<script type="text/javascript">
makeSlider({
url: "http://nama_blog.blogspot.com" // URL blog Anda
});
</script>

Susun file CSS dan plugin Blogger Feed Rotator sesuai dengan urutan yang Saya perlihatkan di atas. Tambahkan juga elemen HTML yang berfungsi untuk menampilkan slideshow sebelum plugin. Setelah itu aktifkan fungsi makeSlider() dengan parameter berupa objek tunggal url yang diisi dengan URL blog Anda. Salin dan tempelkan di dalam formulir elemen halaman HTML/JavaScript. Dengan ini maka Anda sudah bisa menampilkan sebuah slideshow sederhana seperti yang terlihat pada halaman demo di atas.

Daftar Opsi Konfigurasi Dasar

Konfigurasi dasar Saya batasi pada opsi-opsi yang berhubungan dengan pengaturan posting:

{
url: "http://www.dte.web.id", // Your blog URL
numPost: 5, // Number of posts to display
newTabLink: false, // `true` to automatically open link in new window tab
labelName: null, // Show posts in specific label. Specify name of the post label, or `null` to show all posts
showDetail: true, // `false` to hide the blog post title & description
summaryLength: 60, // Length of post summary
titleLength: "auto", // Length of the post title. "auto" by default, or specify number to crop the post title characters and truncate it with `...`
showThumb: true, // `false` to hide the post thumbnails
thumbWidth: 250, // Width of post thumbnail in pixels
squareThumb: true, // Thumbnail mode: Square mode or use the scaled thumbnail with original width and height ratio
noThumb: "images/grey.png", // Fallback thumbnail for no picture posts
showNav: true, // `true` to show both next/prev navigation and navigation number, "next/prev" to show next/prev navigation only, "number" to show navigation number only, `false` to hide the navigation
navText: {
prev: "&lt;", // Label of the previous navigation
next: "&gt;" // Label of the next navigation
},
containerId: "slider-rotator" // HTML element ID that is used to display the slideshow/rotator
}
OpsiKeterangan
urlIsi dengan URL halaman muka blog Anda
numPostDigunakan untuk menentukan jumlah posting yang akan ditampilkan
newTabLinkUbah nilainya menjadi true untuk membuat tautan-tautan di dalam widget ini terbuka di jendela/tab baru saat diklik
labelNameDigunakan untuk menampilkan daftar posting dengan label/kategori tertentu. Nilai null berarti menampilkan semua posting terbaru. Ganti nilainya menjadi nama label untuk menampilkan posting dengan label tertentu. Misalnya labelName:"Foto" untuk menampilkan semua posting yang memiliki label Foto - Contoh
showDetailUbah nilainya menjadi false untuk menyembunyikan judul dan ringkasan posting. Berguna untuk membuat slideshow foto/gambar tanpa tulisan - Contoh
summaryLengthDigunakan untuk membatasi panjang ringkasan posting. Akhir pemotongan ringkasan posting akan digantikan dengan simbol “…”
titleLengthDigunakan untuk membatasi panjang judul posting. Secara normal bernilai "auto". Ubah nilainya dengan angka tertentu untuk memotong panjang judul jika Anda merasa judul posting yang tampil terlalu panjang. Akhir pemotongan judul posting akan digantikan dengan simbol “…”
showThumbUbah nilainya menjadi false untuk menyembunyikan gambar posting. Berguna untuk membuat slideshow teks - Contoh
thumbWidthDigunakan untuk menentukan lebar thumbnail posting dan juga sekaligus menentukan lebar slideshow
squareThumbOpsi untuk memilih mode thumbnail. true untuk menampilkan gambar dengan resolusi /sN-c, false untuk menampilkan gambar dengan resolusi /sN
noThumbIsi dengan URL gambar. Gambar ini akan digunakan sebagai cadangan jika posting yang tampil tidak memiliki gambar
showNavOpsi untuk menampilkan atau menyembunyikan navigasi slideshow. Ubah nilainya menjadi false untuk menyembunyikan navigasi. "number" untuk menampilkan navigasi angka saja. "next/prev" untuk menampilkan navigasi next/prev saja. true untuk menampilkan keduanya
navTextOpsi untuk menentukan label navigasi Next dan Previous
containerIdDigunakan untuk menentukan di mana slideshow akan ditampilkan. Isi dengan ID elemen HTML. Dan jangan lupa untuk menyertakan elemen HTML tersebut sebelum eksekusi plugin.

Pengaturan Animasi

Pengaturan animasi Saya batasi pada opsi-opsi yang berhubungan dengan animasi plugin:

{
interval: 6000, // Slideshow interval
speed: 1000, // Slideshow animation speed
hoverPause: true, // `false` to make the slideshow/rotator keep running on mouse-over
crossFade: false, // `true` if you want to make the slide change effect occurs simultaneously between disappearance and appearance
autoHeight: false, // Animate the rotator to adjust the height of the displayed item
autoSlide: true // `false` to run the slideshow manually (with navigation)
}
OpsiKeterangan
intervalDigunakan untuk menentukan rentang waktu perpindahan setiap item slide dalam satuan milidetik
speedDigunakan untuk menentukan kecepatan animasi dalam satuan milidetik
hoverPauseJika bernilai true maka slideshow akan berhenti bergerak saat pointer mouse berada di atasnya
crossFadeDigunakan untuk menentukan apakah efek penghilangan dan pemunculan slide dilakukan secara bergantian atau bersamaan dalam waktu yang sama tanpa ada jeda
autoHeightJika bernilai true, maka tinggi slideshow akan menyesuaikan diri berdasarkan tinggi item slide yang tampil dengan efek animasi - Contoh
autoSlideUbah nilainya menjadi false untuk membuat slideshow berjalan secara manual melalui aksi klik navigasi

Konfigurasi Lanjutan

Bagian ini Saya khususkan untuk menjelaskan fungsi-fungsi callback dan implementasi lanjutan, khususnya dalam hal modifikasi:

{
onInit: function() {}, // Callback function that will be executed when slideshow is starting
onHide: function(index) {}, // Callback function that will be executed when slideshow is start hiding the slide item
onShow: function(index) {} // Callback function that will be executed after the slideshow showing the slide item
}

onInit

Fungsi ini akan dijalankan pada saat pertama kali slideshow terbentuk. Saat slideshow tampil, maka fungsi ini akan langsung dieksekusi. Contoh penerapan:

makeSlider({
url: "http://nama_blog.blogspot.com",
onInit: function() {
alert('Slideshow ready!');
}
});

Kode di atas akan menampilkan pesan “Slideshow ready!” segera setelah slideshow tampil.

onHide

Fungsi ini akan dijalankan setiap kali item slide mulai menghilang. Saat item slide menghilang, maka fungsi ini akan tereksekusi. Contoh penerapan:

makeSlider({
url: "http://nama_blog.blogspot.com",
onHide: function() {
alert('Mengganti slide...');
}
});

Kode di atas akan menampilkan pesan “Mengganti slide...” setiap kali slide mulai berganti.

onShow

Fungsi ini akan dijalankan setiap kali item slide tampil. Saat item slide tampil, maka fungsi ini akan tereksekusi. Contoh penerapan:

makeSlider({
url: "http://nama_blog.blogspot.com",
onShow: function(index) {
alert('Slide ke ' + index + ' berhasil tampil.');
}
});

Kode di atas akan menampilkan pesan “Slide ke {nomor urut slide yang tampil} berhasil tampil.” setiap kali slide ditampilkan.

Parameter `index`

Parameter index hanya berlaku untuk onHide dan onShow. Parameter ini akan menampilkan indeks slide yang aktif:

makeSlider({
url: "http://nama_blog.blogspot.com",
onInit: function() {
console.log('Slideshow dimulai...');
},
onHide: function(index) {
console.log('Menyembunyikan slide ke ' + index);
},
onShow: function(index) {
console.log('Menampilkan slide ke ' + index);
}
});

Beberapa Contoh Modifikasi Dasar

Mengubah Tampilan

Dengan mengubah ukuran lebar pada gambar dan slideshow, ukuran tinggi pada slideshow dan mengubah tampilan deskripsi/detail posting sebagai caption, maka Anda bisa semakin memperkuat status plugin ini sebagai “slideshow”:

CSS

<link rel="stylesheet" type="text/css" href="http://blogger-json-experiment.googlecode.com/svn/resources/blogger-feed-rotator-plugin/default-style.min.css"/>
<style type="text/css">

/* Custom CSS... */

.slider-rotator {
width:600px;
height:240px;
padding:0 0;
background-color:white;
font:italic normal 12px/1.4 Georgia,Serif;
color:white;
border:1px solid black;
}

.slider-rotator .slider-item {
background-color:white;
height:240px; /* Same with `.slider-rotator` height */
padding:0 0;
}

.slider-rotator h4 {margin-top:0}
.slider-rotator p {margin:2px 0 0}

/* Slider details as caption */
.slider-rotator .detail-wrapper {
position:absolute;
top:auto;
right:0;
bottom:0;
left:0;
background-color:black;
background-color:rgba(0,0,0,.9);
padding:.5em 1em;
z-index:4;
}

</style>

JavaScript

makeSlider({
url: "http://nama_blog.blogspot.com", // Your blog URL
thumbWidth: 600 // Thumbnail width in pixels (same with slideshow width)
});

Memanfaatkan Fungsi Callback untuk Menganimasikan Caption

Dengan memanfaatkan parameter index pada onHide dan onShow, Anda bisa memberikan efek animasi pada caption slide yang aktif setiap kali slide berganti:

makeSlider({
url: "http://nama_blog.blogspot.com", // Your blog URL
thumbWidth: 600, // Thumbnail width in pixels (same with slideshow width)
containerId: "slider-rotator",
// Hide all captions on initiation...
onInit: function() {
$('#' + this.containerId).find('.detail-wrapper').hide();
},
// Hide the caption with `.slideUp()` effect when the slide item hides
onHide: function() {
$('#' + this.containerId).find('.detail-wrapper').slideUp();
},
// Show the caption with `.slideDown()` effect when the slide item appears
onShow: function(index) {
$('#' + this.containerId).children().eq(index).find('.detail-wrapper').slideDown();
}
});

Menampilkan Beberapa Slideshow Sekaligus

Karena eksekusi plugin ini dilakukan di luar, maka ini memungkinkan Anda untuk menampilkan beberapa slideshow sekaligus dalam satu halaman. Pertama-tama buatlah beberapa elemen HTML yang dibutuhkan untuk memuat slideshow:

<div id="container-1" class="slider-rotator loading"></div>
<div id="container-2" class="slider-rotator loading"></div>
<div id="container-3" class="slider-rotator loading"></div>

Kemudian jalankan fungsi makeSlider() sebanyak jumlah kontainer yang dibuat. Tentukan juga konfigurasi containerId yang berbeda-beda berdasarkan ID kontainer:

makeSlider({
url: "http://nama_blog-1.blogspot.com",
containerId: "container-1"
});
makeSlider({
url: "http://nama_blog-2.blogspot.com",
containerId: "container-2"
});
makeSlider({
url: "http://nama_blog-3.blogspot.com",
containerId: "container-3"
});

Mengatur Plugin dan Melihat Demo Secara Langsung

Saya sudah menyertakan halaman konfigurasi plugin yang bisa Anda gunakan untuk melakukan pengaturan dan melihat hasilnya secara langsung di sini:

Daftar Contoh

  1. Demo 1: Basic
  2. Demo 2: Show Posts under Category “Widget”
  3. Demo 3: About Thumbnails
  4. Demo 4: Thumbnail Only
  5. Demo 5: Text Only
  6. Demo 6: Multiple Feed Loading
  7. Demo 7: Casual Slideshow
  8. Demo 8: Example Callback Function to Animate the Slideshow Caption (1)
  9. Demo 9: Example Callback Function to Animate the Slideshow Caption (2)

Sabtu, 02 Maret 2013

Eksperimen Infinite Scroll dengan JSON Blogger

Blogger Infinite Scroll Experiment
Blogger Infinite Scroll Experiment

Eksperimen membuat infinite scroll dengan JSON Blogger. Sebuah sistem yang akan memperbaharui jumlah item posting setiap kali pengguna mencapai batas akhir layar ketika pengguna sedang menggulung halaman. Metode ini sebenarnya sudah pernah Saya tuliskan di sini. Saya mengganti kondisional batas akhir yang tadinya berupa perbandingan antara tinggi kontainer dengan batas akhir gulungan area seperti ini:

container.onscroll = function() {
if ((this.scrollTop + this.offsetHeight) == inner.offsetHeight) {
myFunction();
}
};

Menjadi berdasarkan perbandingan antara tinggi layar dengan batas akhir gulungan layar seperti ini:

window.onscroll = function() {
if (navigator.userAgent.toLowerCase().indexOf("chrome") > -1 || navigator.userAgent.toLowerCase().indexOf("safari") > -1) {
if (document.documentElement.scrollHeight == (document.body.scrollTop + document.documentElement.clientHeight)) {
myFunction();
}
} else {
if (document.documentElement.scrollHeight == (document.documentElement.scrollTop + document.documentElement.clientHeight)) {
myFunction();
}
}
};

Selengkapnya mengenai potongan kode untuk mengecek batas akhir gulungan layar bisa Anda baca di sini.

Silakan dipelajari dan dimodifikasi sesuka hati Anda dengan mengikuti persetujuan yang Saya tuliskan di dalam kode sumbernya. Eksperimen ini akan diperbaharui jika memang ada yang perlu diperbaharui. Begitu.

Jumat, 16 November 2012

Kotak Penelusuran Blogger dengan AJAX JQuery

Ajax Search Form with JQuery
Kotak Penelusuran Blogger dengan Ajax JQuery

Beberapa waktu yang lalu Saya pernah menuliskan cara menampilkan hasil penelusuran dengan JSON Blogger tanpa harus berpindah dari halaman awal menuju halaman hasil penelusuran dengan JavaScript (Anda bisa membacanya di sini).

Melalui JQuery $.ajax() kita bisa meniadakan penyisipan script callback dan langsung memanggil JSON dengan cara seperti ini:

$('#search-form').on("submit", function() {
$.ajax({
url: '/feeds/posts/summary?alt=json-in-script&q=KATA_KUNCI',
type: 'get',
dataType: 'jsonp',
success: function(json) {

}
});
});

Sehingga jika dijabarkan akan menjadi seperti ini:

HTML Formulir

<form action="/search" id="ajax-search-form">
<input type="text" name="q">
<input type="submit" value="Search">
</form>

JQuery

(function($) {
var $form = $('#ajax-search-form'),
$input = $form.find(':text');
$form.append('<div id="search-result"></div>');
var $result_container = $('#search-result');
$form.on("submit", function() {
var keyword = $input.val();
$result_container.show().html('Loading...');
$.ajax({
url: 'http://nama_blog.blogspot.com/feeds/posts/summary?alt=json-in-script&q=' + keyword + '&max-results=9999',
type: 'get',
dataType: 'jsonp',
success: function(json) {
var entry = json.feed.entry,
link, skeleton = "";
if (typeof entry !== "undefined") {
skeleton = '<h4>Search results for keyword &quot;' + keyword + '&quot;</h4>';
skeleton += '<a class="close" href="/">&times;</a><ol>';
for (var i = 0; i < entry.length; i++) {
for (var j = 0; j < entry[i].link.length; j++) {
if (entry[i].link[j].rel == "alternate") {
link = entry[i].link[j].href;
}
}
skeleton += '<li><a href="' + link + '">' + entry[i].title.$t + '</a></li>';
}
skeleton += '</ol>';
$result_container.html(skeleton);
} else {
$result_container.html('<a class="close" href="/">&times;</a><strong>No result!</strong>');
}
},
error: function() {
$result_container.html('<a class="close" href="/">&times;</a><strong>Error loading feed.</strong>');
}
});
return false;
});
$form.on("click", ".close", function() {
$result_container.fadeOut();
return false;
});
})(jQuery);

Cara Kerja

Pertama-tama kita tangkap beberapa elemen penting yaitu formulir pencarian dan elemen input kata kunci pencarian:

var $form = $('#ajax-search-form'), // Mendapatkan elemen formulir
$input = $form.find(':text'); // Mendapatkan elemen input bertipe teks (penampung kata kunci pencarian)

Sisipkan sebuah elemen HTML secara tidak langsung sebagai kontainer hasil pencaran:

$form.append('<div id="search-result"></div>');
var $result_container = $('#search-result');

Setelah itu kita berlakukan event .submit() atau .on("submit") pada formulir untuk kemudian kita bisa langsung memproses data JSON yang akan ditransfer pada saat yang bersamaan ketika kita menekan tombol Enter pada papan ketik atau mengeklik tombol penelusuran di dalam formulir:

$form.on("submit", function() {
$.ajax(url, type, dataType, success, error); // Dapatkan data dan proses data di sini...
return false; // <= Ini digunakan untuk mencegah formulir membawa kita menuju halaman hasil penelusuran saat kita men-submit kata kunci pencarian
});

Pengambilan data JSON dilakukan oleh JQuery $.ajax(), sehingga kita tidak perlu menyisipkan skrip callback ke dalam area <head> seperti dalam metode JavaScript mentah pada umumnya:

$.ajax({
url: 'http://nama_blog.blogspot.com/feeds/posts/summary?alt=json-in-script&q=' + keyword + '&max-results=9999',
type: 'get',
dataType: 'jsonp',
success: function(json) {
var entry = json.feed.entry,
link, skeleton = "";
if (typeof entry !== "undefined") {
skeleton = '<h4>Search results for keyword &quot;' + keyword + '&quot;</h4>';
skeleton += '<a class="close" href="/">&times;</a><ol>';
for (var i = 0; i < entry.length; i++) {
for (var j = 0; j < entry[i].link.length; j++) {
if (entry[i].link[j].rel == "alternate") {
link = entry[i].link[j].href;
}
}
skeleton += '<li><a href="' + link + '">' + entry[i].title.$t + '</a></li>';
}
skeleton += '</ol>';
$result_container.html(skeleton);
} else {
$result_container.html('<a class="close" href="/">&times;</a><strong>No result!</strong>');
}
},
error: function() {
$result_container.html('<a class="close" href="/">&times;</a><strong>Error loading feed.</strong>');
}
});

keyword adalah variabel. Nilainya diambil dari elemen input teks:

var keyword = $input.val();
// `/feeds/posts/summary?alt=json-in-script&q=keyword&max-results=9999`

Integrasi Widget ke Blogger

Widget ini hanya akan bekerja jika blog Anda sudah dilengkapi dengan JQuery.

Pertama-tama masuk ke halaman Tata Letak, kemudian tambahkan sebuah elemen halaman HTML/JavaScript. Salin kode ini dan letakkan di dalam formulirnya:

<style scoped="scoped">
#ajax-search-form {
position:relative;
font:normal normal 13px/normal Helmet,FreeSans,Sans-Serif;
}
#ajax-search-form a {
color:#741F27;
text-decoration:none;
}
#ajax-search-form input {
border:1px solid #ccc;
border-top-color:#999;
background-color:white;
font:inherit;
color:black;
margin:0 0;
padding:5px 5px;
width:180px;
}
#ajax-search-form input::-moz-focus-inner {
margin:0;
padding:0;
border:none;
outline:none;
}
#ajax-search-form input[type="submit"] {
width:auto;
background-color:#084670;
border-color:transparent;
color:#B4D8F0;
font-weight:bold;
cursor:pointer;
padding-left:7px;
padding-right:7px;
}
#ajax-search-form input[type="submit"]:hover,
#ajax-search-form input[type="submit"]:focus {background-color:#083E5F}
#search-result {
border:1px solid #bbb;
background-color:white;
padding:10px 15px;
margin:2px 0;
width:auto;
height:auto;
position:absolute;
top:100%;
left:0;
z-index:99;
-webkit-box-shadow:0 1px 3px rgba(0,0,0,.4);
-moz-box-shadow:0 1px 3px rgba(0,0,0,.4);
box-shadow:0 1px 3px rgba(0,0,0,.4);
display:none;
}
#search-result ol,
#search-result li,
#search-result h4 {
margin:0;
padding:0;
}
#search-result h4,
#search-result strong {
display:block;
margin:0 30px 10px 0;
}
#search-result ol {margin:0 0 10px 28px}
#search-result ol a:hover {text-decoration:underline}
#search-result .close {
display:block;
position:absolute;
top:6px;
right:10px;
line-height:normal;
color:#17950F;
}
#search-result strong {color:#B75252}
</style>
<form action="/search" id="ajax-search-form">
<input type="text" name="q"/>
<input type="submit" value="Search"/>
</form>
<script>
(function($) {

var $form = $('#ajax-search-form'),
$input = $form.find(':text');

// Append a search result container to the search form
$form.append('<div id="search-result"></div>');
var $result_container = $('#search-result');

// When the keyword is submitted…
$form.on("submit", function() {

// Get the input value
var keyword = $input.val();

// Show the search result container and insert a `Loading...` text
$result_container.show().html('Loading...');

// Get the blog JSON via $.ajax() to show the search result
// The URL format: http://blog_name.blogspot.com/feeds/posts/summary?alt=json-in-script&q={THE_KEYWORD}&max-results=9999
$.ajax({
url: 'http://nama_blog.blogspot.com/feeds/posts/summary?alt=json-in-script&q=' + keyword + '&max-results=9999',
type: 'get',
dataType: 'jsonp',

// If success, grab the search result list…
success: function(json) {
var entry = json.feed.entry,
link, skeleton = "";
if (typeof entry !== "undefined") {
skeleton = '<h4>Search results for keyword &quot;' + keyword + '&quot;</h4>';
skeleton += '<a class="close" href="/">&times;</a><ol>';
for (var i = 0; i < entry.length; i++) {
for (var j = 0; j < entry[i].link.length; j++) {
if (entry[i].link[j].rel == "alternate") {
link = entry[i].link[j].href;
}
}
skeleton += '<li><a href="' + link + '">' + entry[i].title.$t + '</a></li>';
}
skeleton += '</ol>';
$result_container.html(skeleton);
} else {
// If the JSON is empty … (entry === undefined)
// Show the `not found` or `no result` message
$result_container.html('<a class="close" href="/">&times;</a><strong>No result!</strong>');
}
},
error: function() {
// If error, show an error message
$result_container.html('<a class="close" href="/">&times;</a><strong>Error loading feed.</strong>');
}
});
return false;
});

// Fade out the search result container if the close button is clicked
$form.on("click", ".close", function() {
$result_container.fadeOut();
return false;
});

})(jQuery);
</script>

Ganti kode yang Saya beri tanda dengan URL blog Anda kemudian klik Simpan Widget.

Senin, 12 November 2012

Memperbaharui Pemuatan JSON berdasarkan Event Scroll

Sebuah kumpulan data dalam daftar akan menampilkan indikator sedang memuat dan akan memulai pemuatan data baru saat jarak gulungan telah mencapai titik maksimal
Sebuah kumpulan data dalam daftar akan menampilkan indikator sedang memuat dan akan memulai pemuatan data baru saat jarak gulungan telah mencapai titik maksimal.

Metode ini masih sama dengan metode-metode penundaan pemuatan JSON yang biasa Saya lakukan untuk widget-widget Saya yaitu dengan cara menyisipkan script callback secara tidak langsung ke dalam area <head> dengan ID tertentu. Kemudian, jika Saya ingin memperbaharui muatan JSON yang sudah ada, Saya tinggal menyingkirkan script callback yang lama kemudian menggantinya dengan duplikat baru dengan parameter yang sudah diperbaharui.

Bayangkan saja bahwa sudah terdapat sebuah script callback dengan ID foo di dalam area <head> seperti ini:

...
...
<script id="foo" src="../feeds/posts/summary?alt=json-in-script&callback=functionName" type="text/javascript"></script>
</head>

Kemudian Saya akan menyingkirkannya dengan cara menangkap ID elemen tersebut sebagai awalan untuk menyeleksi node induknya:

var a = document.getElementById('foo');
var parent = a.parentNode; // Mendapatkan elemen induk dari `foo`

Kemudian, dari induk tersebut Saya akan menyingkirkan elemen itu sendiri:

var a = document.getElementById('foo');
var parent = a.parentNode; // Mendapatkan elemen induk dari `foo`
parent.removeChild(a); // Singkirkan `foo` dari `parent`

Di sini, kita akan melakukan pekerjaan di atas berdasarkan event onscroll dengan batasan akhir berupa jarak gulungan maksimal kontainer. Kode di bawah ini akan menjalankan fungsi bernama myFunction() berdasarkan event onscroll dengan syarat jarak gulungan telah mencapai titk maksimal:

elem.onscroll = function() {
if ((this.scrollTop + this.offsetHeight) == inner.offsetHeight) {
myFunction();
}
};

Dimana elem adalah objek berupa kontainer.

Memulai Pekerjaan

Markup HTML

Kita mulai dengan pembuatan markup HTML sederhana seperti ini:

<div id="result-container" style="width:400px;height:400px;overflow:auto;">
<ol></ol>
<span class="loading">Memuat...</span>
</div>

Elemen #result-container digunakan sebagai area scroll, elemen ol digunakan sebagai kontainer data yang nantinya akan dihasilkan dari JSON, dan elemen span.loading digunakan sebagai indikator sedang memuat.

Membangun JavaScript

Pertama-tama kita buat script untuk memparse data JSON menjadi elemen HTML. Sederhana, seperti script recent post biasa yang Saya beri nama grabList(), dan hanya akan menghasilkan elemen <li> dengan tautan dan judul posting di dalamnya:

var elem = document.getElementById('result-container'), // Mendapatkan elemen `#result-container`
inner = elem.getElementsByTagName('ol')[0], // Mendapatkan elemen `ol` pertama
loading = elem.getElementsByTagName('span')[0]; // Mendapatkan elemen `span` pertama (dalam hal ini adalah elemen indikator sedang memuat)

// Bangun sebuah script untuk menampilkan daftar posting
function grabList(json) {

var list = json.feed.entry, link, skeleton = "";

// Jalankan loop hanya jika data JSON masih ada/dapat didefinisikan
if (typeof list !== "undefined") {
for (var i = 0; i < list.length; i++) {
for (var j = 0; j < list[i].link.length; j++) {
if (list[i].link[j].rel == "alternate") {
link = list[i].link[j].href; // Mendapatkan URL posting
break;
}
}

// Bangun beberapa elemen `<li>` yang berisi tautan dan judul posting...
skeleton += '<li><a href="' + link + '">' + list[i].title.$t + '</a></li>';

}

// ... kemudian sisipkan elemen tersebut ke dalam elemen `<ol>`
inner.innerHTML += skeleton;

// dan sembunyikan indikator sedang memuat.
loading.style.display = "none";

} else {

// Jika data JSON sudah tidak ada (list === "undefined"), tambahkan kelas baru kepada elemen indikator sedang memuat dengan nilai `the-end`
loading.className += ' the-end';

// kemudian ganti teks indikator sedang memuat dengan kata `Habis`
loading.textContent = 'Habis';

}
}

Setelah itu buat sebuah fungsi untuk memuat script callback secara tidak langsung. Tambahkan dua buah parameter untuk menangani start-index dan max-results:

function updateScript(i, max) {
var head = document.getElementsByTagName('head')[0],
script = document.createElement('script');
script.type = 'text/javascript';
script.id = 'load-on-scroll-end';
script.src = 'http://nama_blog.blogspot.com/feeds/posts/summary?alt=json-in-script&start-index=' + i + '&max-results=' + max + '&callback=grabList';
head.appendChild(script);
}

// Jalankan fungsi!
updateScript(1, 25);

Fungsi di atas akan menyisipkan sebuah script callback JSON Blogger dengan nilai parameter start-index berupa 1 dan max-results berupa 25, sehingga sebuah elemen <script> dengan parameter yang sudah diatur akan disipkan ke dalam area <head> secara tidak langsung seperti ini:

...
...
...
<script id="load-on-scroll-end" src="http://nama_blog.blogspot.com/feeds/posts/summary?alt=json-in-script&start-index=1&max-results=25&callback=grabList" type="text/javascript"></script>
</head>

Memperbaharui JSON Berdasarkan Jarak Maksimal Gulungan Area

Setelah itu kita tambahkan sebuah kondisional untuk menyingkirkan script callback lama jika script tersebut ada. Tidak perlu membuat fungsi baru, cukup gunakan fungsi tadi agar kita bisa menggunakannya untuk dua hal sekaligus, yaitu menyisipkan script baru dan/atau menyingkirkan script lama (jika ada):

function updateScript(i, max) {
var head = document.getElementsByTagName('head')[0],
script = document.createElement('script');
script.type = 'text/javascript';
script.id = 'load-on-scroll-end';
script.src = 'http://nama_blog.blogspot.com/feeds/posts/summary?alt=json-in-script&start-index=' + i + '&max-results=' + max + '&callback=grabList';
if (document.getElementById('load-on-scroll-end')) {
var oldScript = document.getElementById('load-on-scroll-end');
oldScript.parentNode.removeScript(oldScript);
}

head.appendChild(script);
}

updateScript(1, 25); // Jalankan fungsi!

Karena kita juga akan menjalankan fungsi updateScript() berdasarkan event onscroll, maka kita juga harus memasukkan updateScript() ke dalam event yang Saya tuliskan pertama kali:

elem.onscroll = function() {
if ((this.scrollTop + this.offsetHeight) == inner.offsetHeight) {
// Muat ulang JSON Blogger dengan `start-index` yang baru melalui parameter `i`
updateScript(i, 25);
// Kemudian tampilkan indikator sedang memuat
loading.style.display = "block";
}
};

Parameter i haru dinamis, dan harus bisa bertambah setiap kali gulungan kontainer berakhir. Untuk membuatnya menjadi dinamis, kita akan menggunakan variabel awalan dengan nilai 0, kemudian kita tingkatkan nilainya di dalam event onscroll setiap kali jarak gulungan telah mencapai titik maksimal:

var start = 0;
elem.onscroll = function() {
if ((this.scrollTop + this.offsetHeight) == inner.offsetHeight) {

// Tingkatkan nilai `start` dengan 1 (dari `start = 0` menjadi `start = 1`, `start = 2`, dst...)
start++;

// Muat ulang JSON Blogger dengan `start-index` yang telah diperbaharui...
// ... melalui `start` yang nilainya dikalikan dengan 25
updateScript(start*25, 25); // => dari `(1, 25)` menjadi `(25, 25)`, `(50, 25)`, `(75, 25)`, dst...

// Kemudian tampilkan indikator sedang memuat
loading.style.display = "block";

}
};

Produk Final

HTML

<div id="result-container">
<ol></ol>
<span class="loading">Memuat...</span>
</div>

CSS

#result-container {
height:400px;
width:400px;
overflow:auto;
margin:50px auto;
font:normal normal 12px 'Trebuchet MS',Trebuchet,Geneva,Arial,Sans-Serif;
}

#result-container ol {
margin:0 0;
padding:0 0;
background-color:#B5D68C;
}

#result-container li {
margin:0 0;
padding:0 0;
list-style:none;
}

#result-container li:nth-child(even) {background-color:#A2C179}

#result-container li a {
display:block;
padding:5px 10px;
font-weight:bold;
color:#396B18;
text-decoration:none;
}

#result-container li a:hover {
background-color:#396B18;
color:white;
text-decoration:none;
}

#result-container .loading {
display:block;
height:26px;
font:normal bold 11px/26px Arial,Sans-Serif;
color:white;
text-align:center;
background-color:#B75A6F;
border-top:2px solid #222;
}

#result-container .loading.the-end {background-color:#666}

JavaScript

var widget_config = {
home_page: 'http://nama_blog.blogspot.com', // Your blog homepage
container_id: 'result-container', // ID of the result container
script_id: 'load-on-scroll-end-script', // ID of the asynchronous script
max_result: 25, // Max result post at once script loading
end_text: 'Habis' // End text if all posts has been loaded
};

var elem = document.getElementById(widget_config.container_id),
inner = elem.getElementsByTagName('ol')[0],
loading = elem.getElementsByTagName('span')[0],
start = 0, // Dynamic start-index
max = widget_config.max_result;

function grabList(json) {
var list = json.feed.entry, link, skeleton = "";
if (typeof list !== "undefined") {
for (var i = 0; i < list.length; i++) {
for (var j = 0; j < list[i].link.length; j++) {
if (list[i].link[j].rel == "alternate") {
link = list[i].link[j].href;
break;
}
}
skeleton += '<li><a href="' + link + '">' + list[i].title.$t + '</a></li>';
}
inner.innerHTML += skeleton; // Insert the list to the container
loading.style.display = "none"; // Hide the loading indicator
} else {
// If the JSON is empty (list === "undefined"),
// add a new class to the loading indicator called `the-end`
loading.className += ' the-end';
// Replace the loading indicator text into `fully loaded!` for the example
loading.textContent = widget_config.end_text;
}
}

// Make an indirect script loader with two parameters: start-index and max-result post
function updateScript(a, b) {
var head = document.getElementsByTagName('head')[0],
script = document.createElement('script');
script.type = 'text/javascript';
script.id = widget_config.script_id;
script.src = widget_config.home_page + '/feeds/posts/summary?alt=json-in-script&start-index=' + a + '&max-results=' + b + '&callback=grabList';
// If there is an old script in the document...
if (document.getElementById(widget_config.script_id)) {
var oldScript = document.getElementById(widget_config.script_id);
// Remove the old script, and replace with the new one that has an updated start-index value
oldScript.parentNode.removeChild(oldScript);
}
head.appendChild(script);
}

// Start loading the callback script with start-index of 1
updateScript(1, max);

// When the container is being scrolled...
elem.onscroll = function() {
// ... check the scroll distance
if ((this.scrollTop + this.offsetHeight) == inner.offsetHeight) {
// If the distance equal to the height of the inner container...
start++; // Increase the start value by one
// then load the new script with an updated start-index
updateScript(start*max, max);
// and show the loading indicator
loading.style.display = "block";
}
};

Sabtu, 03 November 2012

XHTML Blogger, Utilitas Sistem Komentar V2

Dalam Konsep

JavaScript Thread Komentar

<b:includable id='threaded_comment_js' var='post'>
<script async='async' expr:src='data:post.commentSrc' type='text/javascript'></script>

<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;
} else if (entry.gd$extendedProperty[k].name == 'blogger.displayTime') {
comment.displayTime = 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>
DataKeteranganTampilan/Contoh Tampilan
data:post.commentSrcElemen ini akan menghasilkan URL sebuah script untuk keperluan fitur komentar//www.blogblog.com/dynamicviews/4224c15c4e7c9321/js/comments.js
data:post.commentJsoElemen ini akan menghasilkan JSON komentar yang telah diterbitkan di dalam posting terkaitSampel
data:post.commentMsgsElemen ini akan menghasilkan objek berupa variabel untuk menyatakan pesan-pesan tertentu yang akan tampil dalam sistem komentar versi ke duaSampel
data:post.commentConfigElemen ini akan menghasilkan nilai-nilai konfigurasi komentar, menyangkut ID blog, ID posting, URL feed, nama penulis administrator, URL profil administrator, URL Blogger dan kedalaman thread (beberapa tampaknya masih dalam konsep)Sampel

CSS Thread Komentar

<b:includable id='threaded_comment_css'>
<style>
.comments {}
.comments .comments-content {}
.comments .comment .comment-actions a, .comments .continue a {}
.comments .comment .comment-actions a:hover, .comments .continue a:hover {}
.comments .comments-content .comment-thread ol {}
.comments .comments-content .inline-thread {}
.comments .comments-content .comment-thread {}
.comments .comments-content .comment-thread:empty {}
.comments .comments-content .comment-replies {}
.comments .comments-content .comment {}
.comments .comments-content .comment:first-child {}
.comments .comments-content .comment:last-child {}
.comments .comments-content .comment-body {}
.comments .comments-content .user {}
.comments .comments-content .icon.blog-author {}
.comments .comments-content .datetime {}
.comments .comments-content .comment-header, .comments .comments-content .comment-content {}
.comments .comments-content .comment-content {}
.comments .comments-content .owner-actions {}
.comments .comments-replybox {}
.comments .comment-replybox-single {}
.comments .comment-replybox-thread {}
.comments .comments-content .loadmore a {}
.comments .thread-toggle {}
.comments .continue {}
.comments .comments-content .loadmore {}
.comments .comments-content .loadmore.loaded {}
.comments .thread-chrome.thread-collapsed {}
.comments .thread-toggle {}
.comments .thread-toggle .thread-arrow {}
.comments .thread-expanded .thread-arrow {}
.comments .thread-collapsed .thread-arrow {}
.comments .avatar-image-container {}
.comments .avatar-image-container img {}
@media screen and (max-device-width: 480px) {
.comments .comments-content .comment-replies {}
}
</style>
</b:includable>

Sudah tidak berlaku lagi?