Sunday, November 18, 2012

Teknik-Teknik Durasi CSS Transisi

Teknik-Teknik Durasi CSS Transisi

Katakanlah kita mempunyai baris CSS ini untuk mengubah warna dan tinggi sebuah elemen <span> saat pointer berada di atasnya:

span {
  display:block;
  width:50px;
  height:120px;
  background-color:#080;
  margin:15px auto;
}

span:hover {
  height:150px;
  background-color:#900;
  margin:0 auto;
}

Hasilnya adalah seperti ini:

Kemudian Anda tambahkan sedikit CSS Transisi untuk memberikan efek animasi:

span {
  display:block;
  width:50px;
  height:120px;
  background-color:#080;
  margin:15px auto;transition:all .4s ease-out;}

Maka inilah yang akan Anda hasilkan:

Itu adalah dasarnya. Jika Anda ingin menciptakan efek animasi yang sedikit unik, Anda bisa mencoba untuk memindahkan deklarasi CSS transisi atau membuat perbedaan durasi transisi antara keadaan normal dan saat disentuh pointer. Sebagai contoh, jika Anda memindahkan deklarasi transisi dari selektor utama ke selektor keadaan :hover, maka efek transisi hanya akan terjadi saat pointer berada di atas elemen. Namun saat pointer keluar dari elemen tersebut, efek transisi akan menghilang dan menyisakan efek tersentak seperti biasa:

span {
  display:block;
  width:50px;
  height:120px;
  background-color:#080;
  margin:15px auto;
}

span:hover {
  height:150px;
  background-color:#900;
  margin:0 auto;transition:all .4s ease-out;}

Efek di atas tampak sedikit berbeda dari efek yang pertama karena transisi hanya terjadi saat pointer berada di atas elemen. Sedangkan saat pointer meninggalkannya, efek transisi tidak terjadi.

Untuk membuat efek transisi yang merupakan kebalikan dari efek di atas (yaitu efek transisi terjadi pada saat pointer keluar dari elemen, namun tidak terjadi pada saat pointer berada di atasnya), Anda bisa menggunakan cara seperti ini:

Pertama-tama, set efek transisi pada selektor utama dengan durasi tertentu:

span {
  display:block;
  width:50px;
  height:120px;
  background-color:#080;
  margin:15px auto;transition:all .4s ease-out;}

Kemudian set durasi transisi menjadi 0s pada keadaan :hover:

span:hover {
  height:150px;
  background-color:#900;
  margin:0 auto;transition-duration:0s;}

Sehingga hasilnya akan menjadi seperti ini:

Ini berlaku juga untuk pseudo kelas :focus dan :active.

Labels: ,

JavaScript Pengacak Warna

Kode Heksa Warna Acak

"#"+((1<<24)*Math.random()|0).toString(16);

Deret Warna Terdaftar

Mendapatkan warna secara acak berdasarkan barisan warna yang sudah terdaftar:

// Deret warna
var colors = ["#345", "#292222", "#990000", "black", "#ffa500", "#fff3db"];
// Mengambil warna secara acak dari array `colors`
// dan memasukkan hasilnya ke dalam variabel `a`
var a = colors[Math.floor(Math.random()*colors.length)];

Pembaharuan

Yang ini sedikit panjang, tapi hasilnya lebih stabil dibandingkan dengan yang pertama, serta bisa menghasilkan urutan warna yang lebih indah. Dalam beberapa kesempatan, Saya melihat kode pengacak warna yang pertama menghasilkan warna yang tidak valid (pengacak warna hanya menggenerasikan lima digit kode saja, bukannya enam digit seperti yang seharusnya):

function getRandomColor() {
    var letters = '0123456789ABCDEF'.split(''),
        color = '#';
    for (var i = 0; i < 6; ++i) {
        color += letters[Math.round(Math.random() * 15)];
    }
    return color;
}

Penggunaan

document.body.style.backgroundColor = getRandomColor();

Referensi: SO - Random Color Generator in JavaScript

Labels: ,

Saturday, November 17, 2012

JavaScript Popup Copy Code

Letakkan kode ini di atas </body>:

<script>
//<![CDATA[
function copy_code(id) {
    var ref = document.getElementById(id),
        code = ref.getElementsByTagName('code')[0].innerHTML,
        w_w = window.innerWidth,
        w_h = window.innerHeight,
        win = window.open('', '', 'left=' + ((w_w/2)-250) + ',top=' + ((w_h/2)-150) + ',width=500,height=300,scrollbars=0');
    win.document.write('<!DOCTYPE html><html><head><title>Source Code</title><style>*{margin:0;padding:0}body{padding:10px;text-aign:center}textarea{display:block;width:98%;height:270px;padding:1px 1px;margin:0 auto;text-align:left;overflow:auto}</style></head><body><textarea>' + code.replace(/<(.*?)>/g, "") + '</textarea><scr' + 'ipt>var a=document.getElementsByTagName(\'textarea\')[0];a.focus();a.select();</scr' + 'ipt></body></html>');
}

function add_copy_button() {
    var pre = document.getElementsByTagName('pre');
    for (var i = 0; i < pre.length; i++) {
        pre[i].id = 'code-' + i;
        pre[i].className += ' quick-copy';
        pre[i].innerHTML += '<a class="c_b" href="javascript:copy_code(\'code-' + i + '\');">Copy</a>';
    }
} add_copy_button();
//]]>
</script>

Lalu tambahkan kode CSS ini di atas ]]></b:skin> atau </style>:

pre {position:relative}
pre .c_b {
  display:block;
  position:absolute;
  top:0;
  right:0;
  padding:2px 5px;
}

Markup HTML

<pre><code> ... </code></pre>

Demo:

Labels: , ,

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);

Lihat Demo

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.

Lihat Demo Lihat Demo: Dengan Navigasi

Labels: , , , , ,

Tuesday, November 13, 2012

Plugin Dasar jQuery Masonry

Pembaharuan: Versi JavaScript mentah untuk kode ini sudah tersedia.

Desandro

Ini adalah sebuah plugin awal yang menjadi dasar dari jQuery Masonry. Plugin ini ditulis oleh Desandro di dalam gist GitHub dan Saya pikir ini cukup bermanfaat untuk sekedar menata layout seperti halnya saat kita menggunakan plugin Masonry yang sesungguhnya (yang sudah disempurnakan). Jadi Saya mencatat ulang kodenya di sini.

Saya sudah mengeditnya sedikit untuk beberapa keperluan:

(function($) {
    // Finding min and max values in array
    Array.prototype.min = function() {return Math.min.apply({},this);};
    Array.prototype.max = function() {return Math.max.apply({},this);};
    $.fn.masonry = function() {
        this.each(function() {
            var wall = $(this);
            if (wall.children().length > 0) { // Check if the element has anything in it
                if (wall.children('.masonry-wrap').length === 0) { // checks if the `.masonry-wrap` div is already there
                    wall.wrapInner('<div class="masonry-wrap"></div>');
                }
                var m_w = wall.children('.masonry-wrap'),
                    brick = m_w.children(),
                    b_w = brick.outerWidth(true),
                    c_c = Math.floor(m_w.width() / b_w),
                    c_h = [], this_col, i;
                for (i = 0; i < c_c; i++) {
                    c_h[i] = 0;
                }
                m_w.css('position', 'relative');
                brick.css({
                    'float':'none',
                    'position':'absolute',
                    'display':'block'
                }).each(function() {
                    for (i = c_c - 1; i > -1; i--) {
                        if (c_h[i] == c_h.min()) {
                            this_col = i;
                        }
                    }
                    $(this).css({
                        'top':c_h[this_col],
                        'left':b_w * this_col
                    });
                    c_h[this_col] += $(this).outerHeight(true);
                });
                m_w.height(c_h.max()).parent().addClass('start-transition');
            }
            return this;
        });
    };
})(jQuery);

Penggunaan

Sebenarnya plugin ini cukup diterapkan dengan cara seperti ini:

$('#container').masonry();

Tapi Saya sarankan untuk memicu plugin ini di dalam event .resize():

$(window).on("resize", function() {
    $('#container').masonry();
}).trigger("resize");

Tambahan

Untuk efek animasi, gunakan CSS transisi:

CSS

.start-transition .item {
  -webkit-transition:top .7s ease .5s, left .7s ease .5s;
  -moz-transition:top .7s ease .5s, left .7s ease .5s;
  -ms-transition:top .7s ease .5s, left .7s ease .5s;
  -o-transition:top .7s ease .5s, left .7s ease .5s;
  transition:top .7s ease .5s, left .7s ease .5s;
}

HTML

<div id="container">
    <div class="item"> ... </div>
    <div class="item"> ... </div>
    <div class="item"> ... </div>
    ...
    ...
</div>

Keterbatasan:

  1. Lebar setiap item harus sama
  2. Tidak ada fitur posisi di tengah secara otomatis.

Lihat Demo


Referensi: Early jQuery Masonry - gist:2208329

Labels: , , ,

Mengaktifkan Karakter Tab di dalam Textarea dengan Menekan Tombol

Saat kita mengetik tulisan di dalam <textarea>, kita tidak bisa menekan tombol tab untuk menyisipkan karakter tab karena saat kita melakukan itu, fokus kursor akan berpindah ke item formulir lain begitu saja. JavaScript ini bisa digunakan untuk menonaktifkan perpindahan formulir instan menggunakan tombol tab dan akan mengizinkan pengguna untuk menyisipkan karakter tab di dalam <textarea> dengan sekali tekan tombol tab:

function enableTab(id) {
    var el = document.getElementById(id);
    el.onkeydown = function(e) {
        if (e.keyCode === 9) { // tab was pressed

            // get caret position/selection
            var val = this.value,
                start = this.selectionStart,
                end = this.selectionEnd;

            // set textarea value to: text before caret + tab + text after caret
            this.value = val.substring(0, start) + '\t' + val.substring(end);

            // put caret at right position again
            this.selectionStart = this.selectionEnd = start + 1;

            // prevent the focus lose
            return false;

        }
    };
}

Penggunaan

enableTab('id-textarea');

Demo

Labels: , , ,

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"></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="//nama_blog.blogspot.com/feeds/posts/summary?alt=json-in-script&start-index=1&max-results=25&callback=grabList"></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";

    }
};

Lihat Hasil Akhir


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";
    }
};

Lihat Demo Dengan Thumbnail dan Ringkasan Posting

Labels: , , , ,

Monday, November 12, 2012

JavaScript untuk Mengecek Batas Akhir Gulungan Layar

Kode ini digunakan untuk mengecek apakah pengguna telah mencapai bagian akhir halaman atau tidak saat mereka sedang menggulung layar. Jika ya, lakukan sesuatu:

JavaScript

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)) {
            alert("End of the page!"); // for demonstration
        }
    } else {
        if (document.documentElement.scrollHeight == (document.documentElement.scrollTop + document.documentElement.clientHeight)) {
            alert("End of the page!"); // for demonstration
        }
    }
};

Demo

Catatan: Deklarasi <!DOCTYPE html> pada dokumen HTML harus dinyatakan.


Referensi: Stackoverflow: JavaScript Cross Browser Determine if User Scrolled to the Bottom of Page

Labels: , ,

Sunday, November 11, 2012

Widget Recent Comment dengan Sistem Notifikasi

Pembaharuan 22 April 2013: Mengintegrasikan widget dengan cookies, menambahkan opsi summary dan menyingkirkan opsi tt_id.

Recent Comments Widget for Blogger with Notification System

Widget ini bukan widget recent comment biasa yang biasanya Anda temukan di blog-blog lain, karena widget ini memiliki kemampuan untuk memberitahu kepada administrator bahwa terdapat komentar baru yang telah masuk.

Demonya bisa Anda lihat di sini, namun efeknya hanya akan terlihat apabila terdapat (paling tidak) satu komentar baru yang masuk di blog ini:

Lihat Demo Unduh JavaScript

Untuk memasang widget ini, pertama-tama tambahkan sebuah elemen halaman HTML/JavaScript. Salin kode ini kemudian letakkan di dalam formulirnya:

<style scoped="scoped">
.cm-outer {
  margin:0 auto;
  padding:0;
  font:normal normal 11px/normal Arial,Sans-Serif;
  border:1px solid;
  border-top:none;
}
.cm-outer li {
  margin:0;
  padding:7px 10px 12px;
  list-style:none;
  clear:both;
  border-top:1px solid;
}
.cm-outer .cm-header {margin:0 0 5px}
.cm-outer .cm-content {overflow:hidden}
.cm-outer img {
  display:block;
  float:left;
  margin:2px 10px 2px 0;
  border:4px solid black;
  background:#8fa2cb url('http://img1.blogblog.com/img/anon36.png') no-repeat 50% 50%;
  overflow:hidden;
}
</style>
<div id="comments-container">Loading&hellip;</div>
<script>
var cm_config = {
    home_page: "http://nama_blog.blogspot.com",
    max_result: 7,
    t_w: 32,
    t_h: 32,
    summary: 9999,
    new_tab_link: true,
    ct_id: "comments-container",
    new_cm: " Komentar Baru!",
    interval: 30000,
    alert: true
};
</script>
<script src="//dte-project.googlecode.com/svn/trunk/recent-comments-script-with-notification.js"></script>

Ganti kode yang Saya beri tanda dengan URL blog Anda kemudian klik Simpan. Hasil maksimal akan terlihat saat terdapat komentar baru yang masuk.

Konfigurasi Widget Lanjutan

Ada beberapa hal yang harus Anda ketahui mengenai konfigurasi lanjutan widget ini:

Opsi Keterangan
home_page Ganti nilainya dengan URL halaman muka blog Anda
max_result Digunakan untuk menentukan jumlah komentar yang akan ditampilkan pada widget ini
t_w Digunakan untuk menentukan lebar avatar
t_h Digunakan untuk menentukan tinggi avatar
summary Digunakan untuk menentukan jumlah karakter komentar yang ditampilkan
new_tab_link Jika bernilai true, seluruh tautan yang ada di dalam widget ini akan terbuka di tab/jendela baru saat diklik
tt_id ID kontainer total komentar (abaikan jika tidak perlu)
ct_id ID kontainer daftar komentar (abaikan jika tidak perlu)
new_cm Label teks yang akan muncul setelah jumlah komentar (Misalnya: 2 Komentar Baru!)
interval Lihat deskripsi di bawah
alert Lihat deskripsi di bawah

Opsi: interval

Digunakan untuk menentukan interval penyegaran feed komentar. Menggunakan satuan milidetik. Nilai 30000 artinya bahwa feed komentar akan disegarkan selama setengah menit sekali untuk memeriksa apakah terdapat komentar baru yang masuk atau tudak. Jika terdapat komentar baru yang masuk, maka widget ini akan menampilkan pesan bahwa komentar baru telah masuk.

Opsi: alert

Ini cuma opsi untuk menentukan gaya pemberitahuan pesan. Jika bernilai false, widget ini akan menampilkan pesan masuknya komentar baru pada title bar peramban seperti gambar 1. Sebaliknya, jika bernilai true, notifikasi akan muncul pada kotak peringatan seperti terlihat pada gambar 2:

Model Notifikasi 1
Model Notifikasi 1: alert: false
Model Notifikasi 2
Model Notifikasi 2: alert: true

Pembaharuan

Sekarang Anda bisa menampilkan teks notifikasi pada elemen HTML tertentu dengan cara mengubah nilai opsi alert menjadi sebuah fungsi seperti ini:

var cm_config = {
    ...
    alert: function(total, label) {
        // Lakukan sesuatu dengan `total` dan `label`
    }
};

total berfungsi untuk menampilkan total komentar baru, sedangkan label berfungsi untuk menampilkan nilai pada opsi new_cm. Sebagai contoh, buat sebuah elemen HTML seperti ini. Elemen ini akan digunakan sebagai penampil/kontainer teks total komentar:

<div id="show-total"></div>

Setelah itu, sisipkan teks total komentar ke dalam elemen tersebut dengan JavaScript innerHTML melalui fungsi di atas:

var cm_config = {
    ...
    alert: function(total, label) {
        document.getElementById('show-total').innerHTML = '<strong>'+total+' '+label+'</strong>';
    }
};

Jika terdapat notifikasi baru, maka teks notifikasi tersebut akan muncul di dalam elemen #show-total dan akan menghasilkan markup HTML seperti ini:

<div id="show-total"><strong>1 Komentar Baru!</strong></div>

Sebaiknya tambahkan juga atribut title dengan pesan tertentu dan satu buah fungsi kecil pada elemen di atas seperti ini:

<div id="show-total" title="Abaikan!" onclick="this.innerHTML='';"></div>

Fungsinya untuk menghilangkan teks total komentar apabila pengguna mengeklik elemen tersebut.

Labels: , ,

Wednesday, November 7, 2012

XHTML Blogger · Loop Label Posting

Elemen ini biasanya tampil di bagian footer posting dan berfungsi untuk menampilkan daftar label yang diterapkan pada posting tersebut:

<span class='post-labels'>
  <b:if cond='data:post.labels'>
    <data:postLabelsLabel/>
    <b:loop values='data:post.labels' var='label'>
      <a expr:href='data:label.url' rel='tag'><data:label.name/></a><b:if cond='data:label.isLast != &quot;true&quot;'>,</b:if>
    </b:loop>
  </b:if>
</span>
Data Keterangan Tampilan/Contoh Tampilan
data:post.labels Daftar label ???
data:postLabelsLabel Elemen ini akan menampilkan label sebelum daftar kategori posting Label:
data:label.url Elemen ini akan menghasilkan URL halaman label/kategori http://nama_blog.blogspot.com/search/label/Liburan
data:label.name Elemen ini akan menghasilkan nama label/kategori Liburan
data:label.isLast Boolean untuk menyatakan label terakhir true, false

Labels: ,

XHTML Blogger · Share Buttons

Dalam Konsep

<b:includable id='shareButtons' var='post'>
  <b:if cond='data:top.showEmailButton'>
    <a class='goog-inline-block share-button sb-email' expr:href='data:post.sharePostUrl + &quot;&amp;target=email&quot;' expr:title='data:top.emailThisMsg' target='_blank'>
      <span class='share-button-link-text'><data:top.emailThisMsg/></span>
    </a>
  </b:if>
  <b:if cond='data:top.showBlogThisButton'>
    <a class='goog-inline-block share-button sb-blog' expr:href='data:post.sharePostUrl + &quot;&amp;target=blog&quot;' expr:onclick='&quot;window.open(this.href, \&quot;_blank\&quot;, \&quot;height=270,width=475\&quot;); return false;&quot;' expr:title='data:top.blogThisMsg' target='_blank'>
      <span class='share-button-link-text'><data:top.blogThisMsg/></span>
    </a>
  </b:if>
  <b:if cond='data:top.showTwitterButton'>
    <a class='goog-inline-block share-button sb-twitter' expr:href='data:post.sharePostUrl + &quot;&amp;target=twitter&quot;' expr:title='data:top.shareToTwitterMsg' target='_blank'>
      <span class='share-button-link-text'><data:top.shareToTwitterMsg/></span>
    </a>
  </b:if>
  <b:if cond='data:top.showFacebookButton'>
    <a class='goog-inline-block share-button sb-facebook' expr:href='data:post.sharePostUrl + &quot;&amp;target=facebook&quot;' expr:onclick='&quot;window.open(this.href, \&quot;_blank\&quot;, \&quot;height=430,width=640\&quot;); return false;&quot;' expr:title='data:top.shareToFacebookMsg' target='_blank'>
      <span class='share-button-link-text'><data:top.shareToFacebookMsg/></span>
    </a>
  </b:if>
  <b:if cond='data:top.showOrkutButton'>
    <a class='goog-inline-block share-button sb-orkut' expr:href='data:post.sharePostUrl + &quot;&amp;target=orkut&quot;' expr:title='data:top.shareToOrkutMsg' target='_blank'>
      <span class='share-button-link-text'><data:top.shareToOrkutMsg/></span>
    </a>
  </b:if>
  <b:if cond='data:top.showPinterestButton'>
    <a class='goog-inline-block share-button sb-pinterest' expr:href='data:post.sharePostUrl + &quot;&amp;target=pinterest&quot;' expr:title='data:top.shareToPinterestMsg' target='_blank'>
      <span class='share-button-link-text'><data:top.shareToPinterestMsg/></span>
    </a>
  </b:if>
  <b:if cond='data:top.showDummy'>
    <div class='goog-inline-block dummy-container'><data:post.dummyTag/></div>
  </b:if>
</b:includable>
Data Keterangan Tampilan/Contoh Tampilan
data:top.showEmailButton Boolean true, false
data:top.showBlogThisButton Boolean true, false
data:top.showTwitterButton Boolean true, false
data:top.showFacebookButton Boolean true, false
data:top.showOrkutButton Boolean true, false
data:top.showPinterestButton Boolean true, false
data:top.showDummy Boolean true, false
data:post.sharePostUrl Elemen ini akan menghasilkan URL berbagi http://www.blogger.com/share-post.g?blogID=52518732703774XXXX2&postID=703794562479435XXXX
data:top.emailThisMsg Elemen ini akan menghasilkan judul/deskripsi tautan berbagi Kirimkan Ini lewat Email
data:top.blogThisMsg Elemen ini akan menghasilkan judul/deskripsi tautan berbagi BlogThis!
data:top.shareToTwitterMsg Elemen ini akan menghasilkan judul/deskripsi tautan berbagi Berbagi ke Twitter
data:top.shareToFacebookMsg Elemen ini akan menghasilkan judul/deskripsi tautan berbagi Berbagi ke Facebook
data:top.shareToOrkutMsg Elemen ini akan menghasilkan judul/deskripsi tautan berbagi Bagikan ke Orkut
data:top.shareToPinterestMsg Elemen ini akan menghasilkan judul/deskripsi tautan berbagi Bagikan ke Pinterest
data:post.dummyTag ??? ...

Labels: , ,

XHTML Blogger · Item Kontrol

Tombol Edit Cepat Posting

Biasanya berbentuk gambar/ikon pensil dan terletak di dalam footer posting:

<b:includable id='postQuickEdit' var='post'>
  <b:if cond='data:post.editUrl'>
    <span expr:class='&quot;item-control &quot; + data:post.adminClass'>
      <a expr:href='data:post.editUrl' expr:title='data:top.editPostMsg'>
        <img alt='' class='icon-action' height='18' src='http://img2.blogblog.com/img/icon18_edit_allbkg.gif' width='18'/>
      </a>
    </span>
  </b:if>
</b:includable>
Data Keterangan Tampilan/Contoh Tampilan
data:post.editUrl Elemen ini akan menghasilkan URL pengeditan posting http://www.blogger.com/post-edit.g?blogID=489094982896596XXXX&postID=622513084927827XXXX&from=pencil
data:top.editPostMsg Elemen ini akan menghasilkan deskripsi/pesan tautan pengeditan posting Edit Entri
data:post.adminClass Elemen ini akan menghasilkan kelas khusus administrator blog-admin pid-251761511

Tombol Edit Cepat Widget

Berbentuk gambar/ikon kunci inggris dan terletak pada bagian bawah widget:

<b:include name='quickedit'/>

http://img1.blogblog.com/img/icon18_wrench_allbkg.png

Tombol Penghapusan Komentar

Lihat pada XHTML Blogger, Ikon Penghapusan Komentar

Labels: , ,

XHTML Blogger · Elemen Halaman: Seksi dan Widget

Sebuah seksi merupakan elemen <b:section>, sedangkan widget adalah elemen <b:widget>. <b:widget> harus berada di dalam <b:section>, dan <b:section> harus berada di dalam tag <body>

<b:section> dan <b:widget> selalu dilengkapi oleh beberapa atribut. Beberapa merupakan atribut wajib dan beberapa merupakan pilihan. Namun, untuk mengaktifkan sebuah <b:section>, minimal kita hanya membutuhkan atribut id dengan nilai yang spesifik:

<b:section id='widget-area'/>

Elemen di atas nantinya akan ditampilkan sebagai bagian terpenting dalam halaman Tata Letak, yaitu berupa tab pengeditan, penambahan & penghapusan widget seperti ini:

Widget
Contoh tampilan <b:section> di halaman Tata Letak

Dan saat ditampilkan sebagai elemen HTML, <b:section> akan tampil sebagai elemen ini:

<div class='section' id='widget-area'></div>
Daftar Atribut yang Biasa Muncul di dalam Elemen <b:section>
Atribut Nilai/Contoh Nilai Keterangan
id section-1 Atribut wajib. Seperti ID dalam elemen HTML. Berupa huruf atau angka, dan harus spesifik.
class header Atribut opsional. Seperti kelas dalam elemen HTML. Tidak harus spesifik. Kelas-kelas standar dalam sebuah template blogspot adalah navbar, header, main, sidebar dan footer. Kelas-kelas standar tersebut sangat berguna bagi Blogger untuk mempermudah dalam menentukan tipe widget yang sesuai yang biasanya akan disisipkan/ditata ulang secara otomatis saat Anda mengunggah/mengganti template.
maxwidgets 1 Atribut opsional. Berupa jumlah. Atribut ini berguna untuk menentukan seberapa banyak widget yang bisa ditambahkan pada seksi terkait. Anda biasanya akan melihat atribut ini pada seksi header berupa maxwidgets='1', yang akan membatasi penambahan widget sebanyak satu buah. Setelah sebuah elemen halaman ditambahkan, elemen halaman baru tidak diizinkan untuk masuk ke dalam seksi tersebut. Ciri khususnya adalah, sebuah tombol penambahan widget akan tampil saat seksi tersebut masih mengandung jumlah widget kurang dari nilai maksimal, dan akan menghilang saat jumlahnya telah mencapai nilai maksimal.
showaddelement yes (default) Atribut opsional. Jika bernilai yes, tombol penambahan widget akan ditampilkan. Jika bernilai no, tombol penambahan widget akan menghilang (pemilik blog tidak diizinkan untuk menambahkan elemen halaman).
no
growth vertical (default) Atribut opsional. Digunakan untuk menentukan apakah widget-widget halaman akan disusun secara menyamping atau bertumpukan.
horizontal

Sebuah <b:widget> merupakan bagian dari <b:section> dan memiliki beberapa atribut wajib, yang akan terisi secara otomatis saat kita menambahkan elemen halaman/widget baru:

<b:widget id='BlogArchive1' locked='false' title='Blog Archive' type='BlogArchive'/>

Elemen di atas akan tampil sebagai item widget seperti ini saat kita berada di dalam halaman Tata Letak:

Widget Arsip
Contoh tampilan <b:widget> di halaman Tata Letak

Dan akan menjadi seperti ini saat tampil sebagai elemen HTML:

<div class='widget BlogArchive' id='BlogArchive1'>
  <h2>Blog Archive</h2>
  <div class='widget-content'>
    ...
  </div>
</div>
<div class='widget `type`' id='`id`'>
  <h2>`title`</h2>
  <div class='widget-content'>
    ...
  </div>
</div>
Daftar Atribut yang Biasa Muncul di dalam Elemen <b:widget>
Atribut Nilai/Contoh Nilai Keterangan
id BlogArchive1 Atribut wajib. Seperti ID dalam elemen HTML. Berupa huruf atau angka, dan harus spesifik.
type BlogArchive Atribut wajib. Nilai terdaftar[1] dan tidak bisa ditentukan sesuka hati. Digunakan untuk menentukan tipe widget.
locked true Atribut opsional. Digunakan untuk menentukan apakah widget bisa digeser-geser (diatur ulang susunannya) dan dihapus atau tidak. Ciri khusus tampak pada bagian bilah di samping item widget seperti ini
false
title Blog Archive Atribut opsional. Digunakan untuk menentukan judul widget.
pageType all Atribut opsional. Digunakan untuk membuat widget tampil pada halaman spesifik (???)
archive
main
item
mobile yes Atribut opsional. Jika yes, widget akan ditampilkan saat berada pada tampilan blog mobile. Jika no, widget tidak akan tampil saat berada pada tampilan blog mobile. Jika only, widget akan ditampilkan hanya pada tampilan blog mobile.
no
only

Labels: ,

Sunday, November 4, 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'></script>

  <script>
    (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>
Data Keterangan Tampilan/Contoh Tampilan
data:post.commentSrc Elemen ini akan menghasilkan URL sebuah script untuk keperluan fitur komentar //www.blogblog.com/dynamicviews/4224c15c4e7c9321/js/comments.js
data:post.commentJso Elemen ini akan menghasilkan JSON komentar yang telah diterbitkan di dalam posting terkait Sampel
data:post.commentMsgs Elemen ini akan menghasilkan objek berupa variabel untuk menyatakan pesan-pesan tertentu yang akan tampil dalam sistem komentar versi ke dua Sampel
data:post.commentConfig Elemen 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?

Labels: ,

XHTML Blogger · Formulir Komentar

Dalam Konsep

TemplatesV1

<b:includable id='comment-form' var='post'>
  <div class='comment-form' id='comment-form'>
    <h4 id='comment-post-message'><data:postCommentMsg/></h4>
    <p><data:blogCommentMessage/></p>
    <data:blogTeamBlogMessage/>
    <a expr:href='data:post.commentFormIframeSrc' id='comment-editor-src'/>
    <iframe allowtransparency='true' class='blogger-iframe-colorize blogger-comment-from-post' height='410' id='comment-editor' name='comment-editor' src='' width='100%'/>
    <data:post.friendConnectJs/>
    <data:post.cmtfpIframe/>
    <b:if cond='data:showCmtPopup'>
      <div id='comment-popup' style='width:100px;height:20px;'/>
    </b:if>
    <script>
      BLOG_CMT_createIframe(&#39;<data:post.appRpcRelayPath/>&#39;, &#39;<data:post.communityId/>&#39;);
    </script>
  </div>
</b:includable>
Data Keterangan Tampilan/Contoh Tampilan
data:postCommentMsg Elemen ini akan menghasilkan label judul komentar Poskan Komentar:
data:blogCommentMessage Elemen ini akan menghasilkan pesan di atas formulir komentar Be nice, No spam!
data:blogTeamBlogMessage ??? ...
data:post.commentFormIframeSrc Elemen ini akan menghasilkan URL formulir komentar mentah http://www.blogger.com/comment-iframe.g?blogID=29890010286969XXXX&postID=556779452392602XXXX
data:post.friendConnectJs ??? ...
data:post.cmtfpIframe Elemen ini akan menghasilkan script XXX-comment_from_post_iframe.js <script src="//www.blogger.com/static/v1/jsbin/2468562941-comment_from_post_iframe.js"></script>
data:showCmtPopup [?] -
data:post.appRpcRelayPath ??? http://www.blogger.com/rpc_relay.html
data:post.communityId ??? 0665491695519118XXXX

TemplatesV2

<b:includable id='threaded-comment-form' var='post'>
  <div class='comment-form' id='threaded-comment-form'>
    <b:if cond='data:mobile'>
      <p><data:blogCommentMessage/></p>
      <data:blogTeamBlogMessage/>
      <a expr:href='data:post.commentFormIframeSrc' id='comment-editor-src'/>
      <iframe allowtransparency='true' class='blogger-iframe-colorize blogger-comment-from-post' height='410' id='comment-editor' name='comment-editor' src='' style='display: none' width='100%'/>
    <b:else/>
      <p><data:blogCommentMessage/></p>
      <data:blogTeamBlogMessage/>
      <a expr:href='data:post.commentFormIframeSrc' id='comment-editor-src'/>
      <iframe allowtransparency='true' class='blogger-iframe-colorize blogger-comment-from-post' height='410' id='comment-editor' name='comment-editor' src='' width='100%'/>
    </b:if>
    <data:post.friendConnectJs/>
    <data:post.cmtfpIframe/>
    <script>
      BLOG_CMT_createIframe(&#39;<data:post.appRpcRelayPath/>&#39;, &#39;<data:post.communityId/>&#39;);
    </script>
  </div>
</b:includable>
Data Keterangan Tampilan/Contoh Tampilan
data:mobile Boolean untuk menyatakan apakah blog sedang berada dalam tampilan seluler atau tidak true, false

Labels: , ,

XHTML Blogger · Ikon Penghapusan Komentar

<b:includable id='commentDeleteIcon' var='comment'>
  <span expr:class='&quot;item-control &quot; + data:comment.adminClass'>
    <a expr:href='data:comment.deleteUrl' expr:title='data:top.deleteCommentMsg'>
      <img src='//www.blogger.com/img/icon_delete13.gif'/>
    </a>
  </span>
</b:includable>
Data Keterangan Tampilan/Contoh Tampilan
data:comment.adminClass Elemen ini akan menghasilkan kelas khusus administrator blog-admin pid-615478996
data:comment.deleteUrl Elemen ini akan menghasilkan URL penghapusan komentar http://www.blogger.com/delete-comment.g?blogID=672954121565214XXXX&postID=294509636837564XXXX
data:top.deleteCommentMsg Elemen ini akan menghasilkan deskripsi tautan penghapusan komentar Delete Comment, Hapus Komentar

Labels: ,

Saturday, November 3, 2012

XHTML Blogger · Seksi Komentar

Dalam Konsep

Prolog

Seksi komentar adalah elemen <b:includable>. Pada template versi pertama hanya terdiri dari <b:includable id="comments"> namun bertambah satu lagi yaitu <b:includable id="threaded_comments"> pada versi ke dua:

TemplatesV1:

<b:includable id='comments' var='post'>
  ...
</b:includable>

TemplatesV2:

<b:includable id='comments' var='post'>
  ...
</b:includable>
<b:includable id='threaded_comments' var='post'>
  ...
</b:includable>

Detail

TemplatesV1

Terdapat lima bagian utama dalam kerangka komentar pada template versi pertama yaitu header, navigasi, daftar komentar, footer komentar dan backlink:

<div class='comments' id='comments'>

  <!-- header -->
  <h4>0 Komentar:</h4>

  <!-- navigasi -->
  <span class='paging-control-container'> ... </span>

  <!-- daftar komentar -->
  <div id='Blog1_comments-block-wrapper'>
    <dl class='avatar-comment-indent' id='comments-block'>
      ...
    </dl>
  </div>

  <!-- navigasi -->
  <span class='paging-control-container'> ... </span>

  <!-- footer komentar -->
  <div class='comment-footer'> ... </div>

  <!-- kontainer backlink -->
  <div id='backlinks-container'> ... </div>

</div>

Header Komentar

Adalah sebuah elemen heading. Biasanya berupa elemen H4 dengan tulisan yang menunjukkan jumlah komentar:

<h4>
  <b:if cond='data:post.numComments == 1'>1
    <data:commentLabel/>:
  <b:else/>
    <data:post.numComments/>
    <data:commentLabelPlural/>:
  </b:if>
</h4>

<!--

Catatan: Seluruh elemen di atas juga bisa diwakili oleh satu elemen ini:
<h4><data:post.commentLabelFull/>:</h4>

-->
Data Keterangan Tampilan/Contoh Tampilan
data:post.numComments Elemen ini akan menghasilkan angka berupa jumlah komentar yang telah masuk 3
data:commentLabel Elemen ini akan menghasilkan label komentar singular Comment, Komentar
data:commentLabelPlural Elemen ini akan menghasilkan label komentar plural Comments, Komentar
data:top.commentLabel Sama dengan data:commentLabel Comment, Komentar
data:top.commentLabelPlural Sama dengan data:commentLabelPlural Comments, Komentar
data:post.commentLabelFull Sebuah paket elemen untuk mewakili semua markup di atas 0 Comment, 4 Comments, 4 Komentar

Navigasi Komentar

Berupa elemen <span> dengan kelas paging-control-container. Hanya akan muncul jika jumlah komentar sudah melebihi 200 buah (?)

<b:if cond='data:post.commentPagingRequired'>
  <span class='paging-control-container'>
    <a expr:class='data:post.oldLinkClass' expr:href='data:post.oldestLinkUrl'>
      <data:post.oldestLinkText/>
    </a>&#160;
    <a expr:class='data:post.oldLinkClass' expr:href='data:post.olderLinkUrl'>
      <data:post.olderLinkText/>
    </a>&#160;
    <data:post.commentRangeText/>&#160;
    <a expr:class='data:post.newLinkClass' expr:href='data:post.newerLinkUrl'>
      <data:post.newerLinkText/>
    </a>&#160;
    <a expr:class='data:post.newLinkClass' expr:href='data:post.newestLinkUrl'>
      <data:post.newestLinkText/>
    </a>
  </span>
</b:if>
Data Keterangan Tampilan/Contoh Tampilan
data:post.commentPagingRequired Boolean untuk menyatakan apakah navigasi komentar sudah layak ditampilkan atau tidak true, false
data:post.oldLinkClass Elemen ini akan menghasilkan kelas navigasi komentar “lebih lama” ...
data:post.newLinkClass Elemen ini akan menghasilkan kelas navigasi komentar “lebih baru” ...
data:post.olderLinkUrl Elemen ini akan menghasilkan URL komentar “lebih lama” ...
data:post.oldestLinkUrl Elemen ini akan menghasilkan URL komentar “paling lama” ...
data:post.newerLinkUrl Elemen ini akan menghasilkan URL komentar “lebih baru” ...
data:post.newestLinkUrl Elemen ini akan menghasilkan URL komentar “paling baru” ...
data:post.olderLinkText Label navigasi “komentar lama” Older
data:post.oldestLinkText Label navigasi “komentar paling lama” Oldest
data:post.newerLinkText Label navigasi “komentar lebih baru” Newer
data:post.newestLinkText Label navigasi “komentar paling baru” Newest
data:post.commentRangeText Elemen ini akan menghasilkan teks interval halaman komentar yang sedang aktif. 1 - 200

Daftar Komentar

Berupa daftar komentar yang telah masuk, biasanya dibangun dengan elemen definition list atau ordered list:

<div expr:class='data:post.postAuthorClass' expr:id='data:widget.instanceId + &quot;_comments-block-wrapper&quot;'>
  <dl expr:class='data:post.avatarIndentClass' id='comments-block'>
    <b:loop values='data:post.comments' var='comment'>
      <dt expr:class='&quot;comment-author &quot; + data:comment.authorClass'
      expr:id='data:comment.anchorName'>
        <b:if cond='data:comment.favicon'>
          <img expr:src='data:comment.favicon' height='16px' style='margin-bottom:-2px;'
          width='16px' />
        </b:if>
        <a expr:name='data:comment.anchorName'/>
        <b:if cond='data:blog.enabledCommentProfileImages'>
          <data:comment.authorAvatarImage/>
        </b:if>
        <b:if cond='data:comment.authorUrl'>
          <a expr:href='data:comment.authorUrl' rel='nofollow'><data:comment.author/></a> <data:commentPostedByMsg/>
        <b:else/>
          <data:comment.author/> <data:commentPostedByMsg/>
        </b:if>
        <span class='comment-timestamp'>
          <a expr:href='data:comment.url' title='comment permalink'>
            <data:comment.timestamp/>
          </a><b:include data='comment' name='commentDeleteIcon'/>        </span>
      </dt>
      <dd class='comment-body'>
        <b:if cond='data:comment.isDeleted'>
          <span class='deleted-comment'>
            <data:comment.body/>
          </span>
          <b:else/>
            <p><data:comment.body/></p>
        </b:if>
      </dd>
      <dd class='comment-footer'></dd>
    </b:loop>
  </dl>
</div>
Data Keterangan Tampilan/Contoh Tampilan
data:post.postAuthorClass ??? ...
data:widget.instanceId [?] -
data:post.avatarIndentClass Elemen ini akan menghasilkan nama kelas indentasi avatar avatar-comment-indent
data:comment.authorClass Elemen ini akan menghasilkan nama kelas administrator blog-author
data:comment.anchorName Elemen ini akan menghasilkan deret karakter yang nantinya akan berguna sebagai pendukung permalink komentar (diawali dengan hruf c, dan diikuti oleh ID komentar) c3630901959249728956
data:comment.favicon Elemen ini akan menghasilkan favicon komentator yang tidak memiliki foto profil ??? [biasanya berupa logo Blogger]
data:blog.enabledCommentProfileImages [?] -
data:comment.authorAvatarImage Elemen ini akan menghasilkan foto profil Sampel
data:comment.authorUrl Elemen ini akan menghasilkan tautan profil penulis komentar http://www.blogger.com/profile/0513752219663605XXXX
data:comment.author Elemen ini akan menghasilkan nama penulis komentar Taufik Nurrohman
data:commentPostedByMsg Elemen ini akan menghasilkan label kata kerja setelah nama komentar mengatakan..., said...
data:comment.url Elemen ini akan menghasilkan URL permalink komentar (URL halaman dengan akhiran berupa hash data:comment.anchorName Sampel
data:comment.timestamp Elemen ini akan menghasilkan timestamp penerbitan komentar Jumat, 02 November 2012 10:20:00 WIB
data:comment.isDeleted Boolean untuk menyatakan bahwa komentar sudah dihapus true, false
data:comment.body Elemen ini akan menghasilkan badan komentar Lorem ipsum dolor sit amet!
data:comment.id Elemen ini akan menghasilkan ID komentar (seperti data:comment.anchorName tapi tanpa awalan c. Hanya angka) 3630901959249728956
data:comment.inReplyTo Elemen ini akan menghasilkan ID komentar induk. Elemen ini hanya akan menampilkan ID komentar induk jika komentar tersebut merupakan komentar balasan dari salah satu komentar yang ada 3630901959249728956
data:comment.authorAvatarSrc Elemen ini akan menampilkan URL avatar komentar http://lh5.googleusercontent.com/-NDqfg6Z-elM/AAAAAAAAAAI/AAAAAAAADUQ/RODYRoOPPqM/s1600/profile-photo.jpg

Footer/Kaki Komentar

Elemen ini umumnya berisi pesan komentar dan formulir komentar:

<div class='comment-footer'>
  <b:if cond='data:post.embedCommentForm'>
    <b:if cond='data:post.allowNewComments'><b:include data='post' name='comment-form'/>    <b:else/>
      <data:post.noNewCommentsText/>
    </b:if>
  <b:else/>
    <b:if cond='data:post.allowComments'>
      <a expr:href='data:post.addCommentUrl' expr:onclick='data:post.addCommentOnclick'><data:postCommentMsg/></a>
    </b:if>
  </b:if>
</div>
Data Keterangan Tampilan/Contoh Tampilan
data:post.embedCommentForm Boolean untuk menyatakan opsi peletakkan formulir komentar tersemat di bawah posting true, false
data:post.allowNewComments Boolean untuk menyatakan bahwa administrator memperbolehkan masuknya komentar baru true, false
data:post.allowComments Boolean untuk menyatakan bahwa administrator memperbolehkan pengunjung untuk berkomentar true, false
data:post.noNewCommentsText Elemen ini akan menghasilkan pesan bahwa komentar baru tidak diperbolehkan Komentar baru tidak diperbolehkan untuk posting ini.
data:post.addCommentUrl Elemen ini akan menghasilkan URL komentar jendela munculan (???) ...
data:post.addCommentOnclick ??? ...
data:postCommentMsg Elemen ini akan menghasilkan label tautan pengeposan komentar Poskan Komentar

Backlink Container

???

<div id='backlinks-container'>
  <div expr:id='data:widget.instanceId + &quot;_backlinks-container&quot;'>
    <b:if cond='data:post.showBacklinks'><b:include data='post' name='backlinks'/>    </b:if>
  </div>
</div>
Data Keterangan Tampilan/Contoh Tampilan
data:post.showBacklinks ??? ...

TemplatesV2

Terdapat lima bagian utama dalam kerangka komentar pada template versi ke dua yaitu header, daftar komentar, footer komentar, popup komentar dan backlink:

<div class='comments' id='comments'>

  <!-- header -->
  <h4>0 Komentar:</h4>

  <!-- daftar komentar -->
  <div class='comments-content'>
    <script> ... </script>
    <div id='comment-holder'> ... </div>
  </div>

  <!-- footer komentar -->
  <p class='comment-footer'> ... </p>

  <!-- popup komentar -->
  <div class='comment-popup'> ... </div>

  <!-- kontainer backlink -->
  <div id='backlinks-container'> ... </div>

</div>

Header Komentar

Adalah sebuah elemen heading. Biasanya berupa elemen <h4> dengan tulisan yang menunjukkan jumlah komentar:

<h4>
  <b:if cond='data:post.numComments == 1'>1
    <data:commentLabel/>:
  <b:else/>
    <data:post.numComments/>
    <data:commentLabelPlural/>:
  </b:if>
</h4>

Lihat pada bagian TemplatesV1 - Header Komentar

Daftar Komentar

Berupa daftar komentar yang telah masuk, dibangun oleh elemen-elemen ordered list di dalam elemen <div class='comments-content'>:

<div class='comments-content'>
  <b:if cond='data:post.embedCommentForm'><b:include data='post' name='threaded_comment_js'/>  </b:if>
  <div id='comment-holder'>
     <data:post.commentHtml/>
  </div>
</div>
Data Keterangan Tampilan/Contoh Tampilan
data:post.commentHtml Elemen ini akan menghasikan seluruh markup daftar komentar Sampel

Footer/Kaki Komentar

Elemen ini berisi pesan komentar dan formulir komentar:

<p class='comment-footer'>
  <b:if cond='data:post.allowNewComments'><b:include data='post' name='threaded-comment-form'/>  <b:else/>
    <data:post.noNewCommentsText/>
  </b:if>
</p>

Lihat pada bagian TemplatesV1 - Footer/Kaki Komentar

Popup Komentar

???

<b:if cond='data:showCmtPopup'>
  <div id='comment-popup'>
    <iframe allowtransparency='true' id='comment-actions' name='comment-actions' scrolling='no'>
    </iframe>
  </div>
</b:if>
Data Keterangan Tampilan/Contoh Tampilan
data:showCmtPopup ??? ...

Backlink Container

Lihat pada bagian TemplatesV1 - Backlink Container

Labels: ,