Wednesday, May 16, 2018

Mengecek Adanya Komentar Balasan pada Komentar Induk di Blogger

Mengingat kembali tentang bagaimana kita membedakan antara komentar induk dengan komentar balasan pada Blogger dapat kita lakukan dengan cara mengecek adanya properti var.inReplyTo pada item komentar yang dimaksud. Properti ini bertugas untuk menyimpan ID komentar induk:

<b:loop values='data:post.comments' var='comment'>
  <b:if cond='data:comment.inReplyTo'>
    <!-- `data:comment` adalah komentar balasan -->
  <b:else/>
    <!-- `data:comment` adalah komentar induk -->
  </b:if>
</b:loop>

Dari sini kita dapat melakukan iterasi ulang di dalam iterasi komentar induk, dan kemudian menyaring anak-anak komentar yang memiliki nilai properti var.inReplyTo (dalam hal ini adalah reply.inReplyTo) yang sama dengan parent.id (dalam hal ini adalah comment.id):

<b:loop values='data:post.comments' var='comment'>
  <b:if cond='!data:comment.inReplyTo'>
    <b:loop values='data:post.comments' var='reply'>
      <b:if cond='data:reply.inReplyTo == data:comment.id'>
        <!-- komentar balasan untuk `data:comment` akan tersedia di sini sebagai `data:reply` -->
      </b:if>
    </b:loop>
  </b:if>
</b:loop>

Ekspresi lambda (fungsi anonim) pada Blogger memungkinkan kita untuk melakukan penyaringan komentar-komentar balasan terhadap ID komentar induk dengan cara yang lebih cepat seperti ini, karena penyaringan data komentar dapat dilakukan sebelum proses iterasi dilakukan:

<b:loop values='data:post.comments' var='comment'>
  <b:if cond='!data:comment.inReplyTo'>
    <b:loop values='data:post.comments filter (i => i.inReplyTo == data:comment.id)' var='reply'>
      <!-- komentar balasan untuk `data:comment` akan tersedia di sini sebagai `data:reply` -->
    </b:loop>
  </b:if>
</b:loop>

Ada satu metode yang menarik dalam fungsi anonim Blogger yaitu metode count. Metode ini memungkinkan kita untuk menghitung jumlah item yang ada setelah proses bersyarat selesai diterapkan pada koleksi data. Sebagai contoh, kode di bawah ini akan menampilkan jumlah total komentar dengan nama penulis Taufik Nurrohman:

Jumlah komentar dari Taufik Nurrohman: <b:eval expr='data:post.comments count (i => i.author == "Taufik Nurrohman")'/>

Dalam bahasa pemrograman, kita dapat melakukan sesuatu seperti ini untuk menghitung jumlah komentar dari Taufik Nurrohman, yang mana ini tidak akan dapat kita lakukan pada Blogger sebelum adanya fitur fungsi anonim:

let i = 0;
post.comments.forEach(comment => {
    if (comment.author == 'Taufik Nurrohman') {
        ++i;
    }
});

console.log('Jumlah komentar dari Taufik Nurrohman: ' + i);

Metode count pada fungsi anonim akan lebih sesuai jika disamakan dengan metode filter dan properti length pada JavaScript seperti ini:

let i = post.comments.filter(i => i.author == 'Taufik Nurrohman').length;

console.log('Jumlah komentar dari Taufik Nurrohman: ' + i);

Cara Mengecek Apakah Komentar Induk Memiliki Komentar Balasan atau Tidak

Sebuah komentar dari seorang pembaca bernama Satank Mkr pada artikel Membuat Fitur Komentar Berbalas (Threaded Comments) pada Blogger dengan Fungsional yang Asli kurang lebih menanyakan tentang bagaimana caranya menambahkan elemen pembungkus khusus yang akan melingkupi seluruh komentar balasan, sehingga jika terdapat setidaknya satu buah komentar balasan di bawah komentar induk, maka komentar-komentar balasan tersebut akan dibungkus dengan elemen HTML tertentu. Berikut adalah ilustrasi yang beliau maksudkan:

<b:if cond='data:post.numberOfComments > 0'>
  <ul class='comments'>
    <b:loop values='data:post.comments' var='comment'>
      <li class='comment'> … </li>
    </b:loop>
  </ul>
</b:if>
<ul class="comments">
  <li class="comment"> … </li>
  <li class="comment"> … </li>
  <li class="comment"> … </li>
</ul>

Melihat pada contoh di atas, akan sangat mudah untuk menambahkan elemen pembungkus <ul class="comments"> karena Blogger memiliki properti numberOfComments pada data:post yang bertugas untuk menyimpan jumlah keseluruhan komentar yang ada. Akan tetapi, kita tidak memiliki properti khusus untuk menghitung jumlah komentar balasan melalui data:comment, sehingga untuk menentukan apakah sebuah komentar induk memiliki komentar balasan atau tidak akan mustahil tanpa adanya properti khusus; katakanlah var.numReplies seperti ini:

<b:if cond='data:post.numberOfComments > 0'>
  <ul class='comments'>
    <b:loop values='data:post.comments' var='comment'>
      <li class='comment'> … </li>
        <b:if cond='data:comment.numReplies > 0'>
          <ul class='comment-replies'>
            <b:loop values='data:post.comments filter (i => i.inReplyTo == data:comment.id)' var='reply'>
              <li class='reply'> … </li>
            </b:loop>
          </ul>
      </b:if>
    </b:loop>
  </ul>
</b:if>
<ul class="comments">
  <li class="comment"> … </li>
  <li class="comment"> … </li>
  <li class="comment">
    …
    <ul class='comment-replies'>
      <li class='reply'> … </li>
      <li class='reply'> … </li>
    </ul>
  </li>
  <li class="comment"> … </li>
</ul>

Metode count datang menyelamatkan! Karena metode ini mampu menghitung jumlah komentar yang ada setelah proses bersyarat diterapkan pada data komentar, maka kita dapat menghitung jumlah komentar balasan terkait dengan komentar induk dengan cara seperti ini:

<b:with var='numReplies' value='data:post.comments count (i => i.inReplyTo == data:comment.id)'>
  Jumlah komentar balasan: <data:numReplies/>
</b:with>

Untuk menerapkannya sebagai ekspresi kondisional di dalam iterasi komentar, kita bisa menuliskannya seperti ini:

<b:if cond='data:post.numberOfComments > 0'>
  <ul class='comments'>
    <b:loop values='data:post.comments' var='comment'>
      <li class='comment'> … </li>
        <b:with var='numReplies' value='data:post.comments count (i => i.inReplyTo == data:comment.id)'>
          <b:if cond='data:numReplies > 0'>
            <ul class='comment-replies'>
              <b:loop values='data:post.comments filter (i => i.inReplyTo == data:comment.id)' var='reply'>
                <li class='reply'> … </li>
              </b:loop>
            </ul>
          </b:if>
        </b:with>
      </b:if>
    </b:loop>
  </ul>
</b:if>

Atau seperti ini juga bisa:

<b:if cond='data:post.numberOfComments > 0'>
  <ul class='comments'>
    <b:loop values='data:post.comments' var='comment'>
      <li class='comment'> … </li>
        <b:with var='replies' value='data:post.comments filter (i => i.inReplyTo == data:comment.id)'>
          <b:if cond='data:replies.size > 0'>
            <ul class='comment-replies'>
              <b:loop values='data:replies' var='reply'>
                <li class='reply'> … </li>
              </b:loop>
            </ul>
          </b:if>
        </b:with>
      </b:if>
    </b:loop>
  </ul>
</b:if>

Labels: , ,

Wednesday, May 9, 2018

Kelas HTML Otomatis untuk Blogger

Berikut ini adalah berbagai metode untuk menambahkan kelas otomatis berdasarkan kondisi halaman pada tema Blogger. Kelas ini nantinya dapat Anda gunakan untuk memodifikasi tema tanpa harus membungkus setiap kode CSS di dalam tag kondisional halaman yang spesifik.

Metode 1: Versi Lama

<html expr:class='data:blog.pageType'>
  …
</html>
.post { /* gaya posting di semua halaman */ }

.archive .post { /* gaya posting di halaman arsip */ }
.error_page .post { /* gaya posting di halaman kesalahan */ }
.index .post { /* gaya posting di halaman indeks */ }
.item .post { /* gaya pisting di halaman item */ }
.static_page .post { /* gaya posting di halaman statis */ }

Metode 2: Versi Baru

<html expr:class='data:view.type'>
  …
</html>
.post { /* gaya posting di semua halaman */ }

.error .post { /* gaya posting di halaman kesalahan */ }
.feed .post { /* gaya posting di halaman arsip, indeks, label, pencarian */ }
.item .post { /* gaya posting di halaman item dan statis */ }

Metode 3: Versi Manual

<html>
  <b:class cond='data:view.isArchive' name='is-archive'/>
  <b:class cond='data:view.isError' name='is-error'/>
  <b:class cond='data:view.isHomepage' name='is-home'/>
  <b:class cond='data:view.isLabelSearch' name='is-tags'/>
  <b:class cond='data:view.isMultipleItems' name='is-items'/>
  <b:class cond='data:view.isPage' name='is-page'/>
  <b:class cond='data:view.isPost' name='is-post'/>
  <b:class cond='data:view.isPreview' name='is-preview'/>
  <b:class cond='data:view.isSearch and !data:view.isLabelSearch' name='is-search'/>
  <b:class cond='data:view.isSingleItem' name='is-item'/>
  …
</html>
.post { /* gaya posting di semua halaman */ }

.is-items .post { /* gaya posting di halaman indeks */ }
.is-item .post { /* gaya posting di halaman item */ }

.is-archive .post { /* gaya posting di halaman arsip */ }
.is-error .post { /* gaya posting di halaman kesalahan */ }
.is-home .post { /* gaya posting di halaman muka */ }
.is-tags .post { /* gaya posting di halaman label */ }
.is-page .post { /* gaya posting di halaman statis */ }
.is-post .post { /* gaya posting di halaman posting */ }
.is-preview .post { /* gaya posting di halaman pratinjau */ }
.is-search .post { /* gaya posting di halaman pencarian */ }

Terkait: Tag Kondisional Halaman Blogger 2017

Labels: ,

Friday, May 4, 2018

Menyimpan Data Konfigurasi pada Skrip Pemanggil

Belakangan ini Saya mulai mengimplementasikan teknik penyimpanan data konfigurasi JavaScript yang menarik pada ekstensi-ekstensi Mecha yang berhubungan dengan JavaScript. Saya pikir, mungkin akan bermanfaat jika Saya menjabarkannya juga di sini karena beberapa pengembang skrip Blogger tentu akan sangat terbantu dengan teknik semacam ini. Saya sendiri tidak tahu siapa yang pertama kali memberikan gagasan ini, jadi untuk saat ini Saya masih tidak dapat menyebutkan orang-orang yang terkait dengan praktik semacam ini satu per satu. Metode klasik yang sering kita jumpai tentang bagaimana cara menyimpan variabel konfigurasi JavaScript adalah sebagai berikut:

Metode 1: Pada Tag Skrip Terpisah

Di sini pengguna diminta untuk mendefinisikan data konfigurasi pada variabel JavaScript yang diletakkan terpisah dari skrip utama.

<script>
var config = {
    var_1: 'foo',
    var_2: 'bar',
    var_3: 4
};
</script>
<script src="main.js"></script>

Kemudian di dalam berkas main.js kita bisa memanggil data konfigurasi yang ada satu per satu. Beberapa nilai default mungkin perlu ditentukan juga untuk memastikan skrip tetap bisa bekerja meski pengguna tidak mendefinisikan data konfigurasi yang diperlukan pada variabel konfigurasi pengguna:

var config = config || {};

var var_1 = config.var_1 || 'foo',
    var_2 = config.var_2 || 'bar',
    var_3 = config.var_3 || 4;

// Lakukan sesuatu dengan `var_1`, `var_2` dan `var_3` di sini…

Metode 2: Pada Argumen Fungsi

Di sini pengguna diminta untuk mendefinisikan data konfigurasi pada variabel JavaScript sebagai parameter fungsi. Tampilannya lebih ringkas jika dibandingkan dengan metode pertama meski kita masih tetap memerlukan tag skrip terpisah. Parameter bisa berupa satu variabel sebagai objek yang menyimpan beberapa data atau beberapa variabel yang masing-masing variabel menyimpan data tunggal:

Sebagai Objek

<script src="main.js"></script>
<script>
var main = new Main({
    var_1: 'foo',
    var_2: 'bar',
    var_3: 4
});
</script>

Kemudian di dalam berkas main.js:

function Main(config) {
    config = config || {};
    this.var_1 = config.var_1 || 'foo';
    this.var_2 = config.var_2 || 'bar';
    this.var_3 = config.var_3 || 4;

    // Lakukan sesuatu dengan `this.var_1`, `this.var_2` dan `this.var_3` di sini…

}

Sebagai Deret Variabel

<script src="main.js"></script>
<script>
var main = new Main('foo', 'bar', 4);
</script>

Kemudian di dalam berkas main.js:

function Main(var_1, var_2, var_3) {
    this.var_1 = var_1 || 'foo';
    this.var_2 = var_2 || 'bar';
    this.var_3 = var_3 || 4;

    // Lakukan sesuatu dengan `this.var_1`, `this.var_2` dan `this.var_3` di sini…

}

Metode 3: Pada Elemen HTML

Beberapa ada juga yang menggunakan teknik penyimpanan data konfigurasi JavaScript pada elemen HTML yang terkait dengan eksekusi skrip tertentu. Hal ini biasanya dipraktikkan untuk meringkas beberapa eksekusi JavaScript sekaligus yang memerlukan data konfigurasi yang berbeda-beda pada masing-masing elemen HTML yang dikenai.

Ketika kita menggunakan metode ke dua, maka ini yang akan terjadi:

<div class="item" id="item-1"></div>
<div class="item" id="item-2"></div>
<div class="item" id="item-3"></div>

<script src="main.js"></script>
<script>

var itam_1 = new Item(document.querySelector('#item-1'), {
    var_1: 'foo untuk item 1',
    var_2: 'bar untuk item 1',
    var_3: 4
});

var itam_2 = new Item(document.querySelector('#item-2'), {
    var_1: 'foo untuk item 2',
    var_2: 'bar untuk item 2',
    var_3: 7
});

var itam_3 = new Item(document.querySelector('#item-3'), {
    var_1: 'foo untuk item 3',
    var_2: 'bar untuk item 3',
    var_3: 2
});

</script>

Kemudian di dalam berkas main.js:

function Item(target, config) {
    config = config || {};
    target.innerHTML = config.var_1 || "";
    target.title = config.var_2 || null;

    // dan seterusnya…

}

Solusi paling mudah untuk mengatasi masalah ini adalah dengan menyimpan data konfigurasi pada elemen target itu sendiri. Mengenai format data yang tersimpan bisa bermacam-macam, tapi yang paling umum adalah tersimpan sebagai JSON:

<div class="item" id="item-1" data-config='{
  "var_1": "foo untuk item 1",
  "var_2": "bar untuk item 1",
  "var_3": 4
}'></div>

<div class="item" id="item-2" data-config='{
  "var_1": "foo untuk item 2",
  "var_2": "bar untuk item 2",
  "var_3": 7
}'></div>

<div class="item" id="item-3" data-config='{
  "var_1": "foo untuk item 3",
  "var_2": "bar untuk item 3",
  "var_3": 2
}'></div>

<script src="main.js"></script>
<script>

document.querySelectorAll('.item').forEach(function(target) {
    new Item(target, JSON.parse(target.getAttribute('data-config')));
});

</script>

Metode 4: Pada Tag Skrip Itu Sendiri

Ini adalah metode penyimpanan data konfigurasi yang belakangan ini Saya sukai karena dapat meringkas penulisan data konfigurasi global yang biasanya harus dituliskan pada variabel terpisah (untuk menyederhanakan penerapan data konfigurasi yang spesifik pada target yang berbeda, Saya lebih memilih metode ke tiga).

<script src="main.js" data-config='{
  "var_1": "foo",
  "var_2": "bar",
  "var_3": 4
}'></script>

Kemudian di dalam berkas main.js, kita menggunakan document.currentScript untuk mendapatkan elemen <script> yang digunakan untuk memanggil berkas main.js:

var script = document.currentScript;

var config = JSON.parse(script.getAttribute('data-config'));

Kita juga bisa menggunakan teks kueri di URL berkas main.js untuk menyimpan data konfigurasi, tapi kita perlu fungsi khusus seperti ini atau ini atau ini untuk mengubah teks kueri menjadi objek.

<script src="main.js?var_1=foo&amp;var_2=var&amp;var_3=4"></script>
var script = document.currentScript;

var config = get_vars(script.src);

function get_vars(url) {  }

Labels: ,

Query String Parser

JavaScript dengan ukuran kurang dari 1 KB untuk mengubah format teks kueri menjadi objek. Berkas ini akan menambahkan fungsi global bernama q2o yang kemudian dapat Anda gunakan seperti ini:

console.log(q2o('?foo=1&bar=2')); // {"foo":1,"bar":2}

Labels: , ,

Menggunakan `b:loop`

Tag <b:loop> berfungsi untuk melakukan iterasi data berupa koleksi. Tag ini memerlukan setidaknya dua buah atribut yaitu var untuk menamai variabel dan values untuk menampung keseluruhan data.

<ul>
  <b:loop values='["foo", "bar", "baz", "qux"]' var='v'>
    <li><data:v/></li>
  </b:loop>
</ul>

Hasil keluaran nantinya akan menjadi seperti ini:

<ul>
  <li>foo</li>
  <li>bar</li>
  <li>baz</li>
  <li>qux</li>
</ul>

Beberapa atribut opsional seperti index dan reverse juga dapat digunakan. Atribut index berfungsi untuk menamai variabel yang nantinya dapat kita gunakan untuk menampilkan posisi data dimulai dari indeks 0.

<ul>
  <b:loop values='["foo", "bar", "baz", "qux"]' var='v' index='k'>
    <li><b:eval expr='data:k + 1'/>. <data:v/></li>
  </b:loop>
</ul>

Hasil keluaran nantinya akan menjadi seperti ini:

<ul>
  <li>1. foo</li>
  <li>2. bar</li>
  <li>3. baz</li>
  <li>4. qux</li>
</ul>

Atribut reverse berfungsi untuk membalik koleksi data tanpa mengubah urutan indeks iterasi.

<ul>
  <b:loop values='["foo", "bar", "baz", "qux"]' var='v' index='k' reverse='true'>
    <li><b:eval expr='data:k + 1'/>. <data:v/></li>
  </b:loop>
</ul>

Hasil keluaran nantinya akan menjadi seperti ini:

<ul>
  <li>1. qux</li>
  <li>2. baz</li>
  <li>3. bar</li>
  <li>4. foo</li>
</ul>

Navigasi Otomatis

Kalau kamu cukup percaya diri, kamu bisa menggunakan fitur ini untuk membuat deret navigasi atau tautan berbagi otomatis:

<b:with var='navigation' value='[{
    title: "Home",
    url: data:blog.homepageUrl
}, {
    title: "About",
    url: path(data:blog.url, "p/about.html")
}, {
    title: "Contact",
    url: path(data:blog.url, "p/cotact.html")
}, {
    title: "Search",
    url: path(data:blog.url, "search")
}, {
    title: "Example",
    url: "//example.com"
}]'>

  <nav>
    <ul>
      <b:loop values='data:navigation' var='v'>
        <li>
          <b:class cond='data:blog.url == data:v.url' name='active'/>
          <a expr:href='data:v.url'><data:v.title/></a>
        </li>
      </b:loop>
    </ul>
  </nav>

</b:with>

Hasil keluaran nantinya akan menjadi seperti ini:

<nav>
  <ul>
    <li class='active'>
      <a href='http://www.dte.web.id/'>Home</a>
    </li>
    <li>
      <a href='http://www.dte.web.id/p/about.html'>About</a>
    </li>
    <li>
      <a href='http://www.dte.web.id/p/cotact.html'>Contact</a>
    </li>
    <li>
      <a href='http://www.dte.web.id/search'>Search</a>
    </li>
    <li>
      <a href='http://example.com'>Example</a>
    </li>
  </ul>
</nav>

Kelas active akan ditambahkan secara otomatis pada item navigasi ketika URL pada address bar sama dengan URL pada tautan di dalam item navigasi terkait.

Labels: ,

Operator URL pada Blogger

Berikut ini adalah beberapa operator pengubah URL yang dapat Anda gunakan untuk mengubah struktur URL.

path(url, path)

Berfungsi untuk mengubah komponen jalur.

<b:eval expr='path(data.blog.url, "foo.html")'/>
<b:eval expr='path(data.blog.url, "/foo.html")'/>

Kode di atas akan menghasilkan ini, bagaimanapun bentuk URL yang sedang aktif saat itu:

http://nama_blog.blogspot.com/foo.html
http://nama_blog.blogspot.com/foo.html

Operator ini juga tidak akan menghilangkan teks kueri yang sudah ada. Anda bisa mengetesnya dengan cara seperti ini:

<b:eval expr='data:blog.url'/>
<b:eval expr='path(data:blog.url, "foo.html")'/>
<b:eval expr='data:blog.url + "/foo.html"'/>
<b:eval expr='data:blog.homepageUrl + "/foo.html"'/>

Kode di atas akan menghasilkan ini ketika Anda mengunjungi blog Anda dengan menambahkan beberapa teks kueri seperti http://nama_blog.blogspot.com/search?q=foo:

http://nama_blog.blogspot.com/search?q=foo
http://nama_blog.blogspot.com/foo.html?q=foo
http://nama_blog.blogspot.com/search?q=foo/foo.html
http://nama_blog.blogspot.com//foo.html

params(url, params)

Berfungsi untuk mengganti teks kueri pada URL atau menambahkannya jika belum ada.

<b:eval expr='params(data:blog.url, {
    foo: "bar",
    baz: "qux"
})'/>

Kode di atas akan menghasilkan ini:

http://nama_blog.blogspot.com?baz=qux&foo=bar

Operator ini sepertinya tidak mendukung teks kueri multi-level:

<!-- ?test=1&test=2&test=3 -->
<b:eval expr='params(data:blog.url, {
    test: [1, 2, 3]
})'/>
<!-- ?test=1&test=2&test=3 -->
<b:eval expr='params(data:blog.url, {
    test: {1, 2, 3}
})'/>
<!-- ?test=null -->
<b:eval expr='params(data:blog.url, {
    test: {
        0: 1,
        1: 2,
        2: 3
    }
})'/>
<!-- Invalid expression! -->
<b:eval expr='params(data:blog.url, {
    test[0]: 1,
    test[1]: 2,
    test[2]: 3
})'/>
<!-- ?"test[0]"=1&"test[1]"=2&"test[2]"=3 -->
<b:eval expr='params(data:blog.url, {
    "test[0]": 1,
    "test[1]": 2,
    "test[2]": 3
})'/>

appendParams(url, params)

Berfungsi untuk menambahkan teks kueri atau mengubah nilai teks kueri yang sudah ada pada URL.

<b:eval expr='params(data:blog.url, {
    foo: "bar"
})'/>
<b:eval expr='appendParams(data:blog.url, {
    foo: "bar"
})'/>

Hasilnya akan menjadi seperti ini ketika kita berada di halaman pencarian:

http://nama_blog.blogspot.com/search?foo=bar
http://nama_blog.blogspot.com/search?foo=bar&q=baz

Seharusnya akan menghasilkan seperti contoh di atas, tapi setelah Saya coba sendiri ternyata hasilnya tidak ada bedanya dengan ketika Saya menggunakan params. Mungkin ini bug.

fragment(url, fragment)

Berfungsi untuk menyisipkan fragmen URL.

<b:eval expr='fragment(data:blog.url, "foo")'/>
<b:eval expr='fragment(data:blog.url, "#foo")'/>
<b:eval expr='data:blog.url + "#foo"'/>

Hasilnya akan menjadi seperti ini:

http://nama_blog.blogspot.com#foo
http://nama_blog.blogspot.com#foo
http://nama_blog.blogspot.com#foo

Labels: ,