Di 10 Tags Variabel dan Percabangan kita udah bisa nyimpen nilai dan bikin keputusan. Sekarang kita belajar ngulang: nampilin daftar produk, koleksi, artikel, dan apapun yang jumlahnya banyak.

Hampir semua halaman toko itu daftar: daftar produk di koleksi, daftar varian di halaman produk, daftar item di keranjang, daftar artikel di blog. Jadi tag perulangan ini bakal kepakai di mana-mana.

for

Ngerender ekspresi buat tiap item di dalam array:

{% for variable in array %}
  expression
{% endfor %}

Contoh:

{% for product in collection.products -%}
  {{ product.title }}
{%- endfor %}

Kalau koleksinya punya 4 produk, hasilnya:

Draught of Immortality
Glacier ice
Health potion
Invisibility potion

Dan ini aturan paling penting di bab ini: for maksimal 50 iterasi. Kalau array-nya punya 200 item, for cuma ngerender 50 pertama, sisanya diam-diam dilewatin tanpa error. Buat lebih dari 50, wajib pakai paginate yang dibahas di bawah.

Angka 50 ini bukan kesewenangan Shopify, tapi pelindung performa: ngerender 200 kartu produk dalam satu halaman itu berat buat server dan browser.

1. limit

Batasin jumlah iterasi:

{% for product in collection.products limit: 2 -%}
  {{ product.title }}
{%- endfor %}

Hasilnya:

Draught of Immortality
Glacier ice

Tapi ada catatan performa penting: limit cuma ngatur berapa kali loop jalan, bukan berapa banyak data yang diambil dari server. collection.products tetap ngambil 50 produk walaupun limit: 2. Buat beneran ngurangin data yang diambil, pakai paginate dengan page_size yang cocok. Dibahas di bagian paginate.

2. offset

Mulai iterasi dari indeks tertentu. Indeksnya dihitung dari 1, bukan 0:

{% for product in collection.products offset: 2 -%}
  {{ product.title }}
{%- endfor %}

Hasilnya, dua produk pertama dilewatin:

Health potion
Invisibility potion

Bingung soal “dari 1”? Anggap aja offset: 2 artinya “lewati 2 item pertama”. Jadi mulai dari item ke-3.

3. range

Daripada ngulang item di array, kamu bisa ngulang rentang angka. Bisa pakai angka literal maupun variabel:

{% for i in (1..3) -%}
  {{ i }}
{%- endfor %}
{%- assign lower_limit = 2 -%}
{%- assign upper_limit = 4 -%}
{% for i in (lower_limit..upper_limit) -%}
  {{ i }}
{%- endfor %}

Hasilnya:

1
2
3
2
3
4

Perhatiin: rentangnya inklusif di kedua ujung. (1..3) itu 1, 2, dan 3. Ini berguna buat nampilin bintang rating, nomor halaman, atau apapun yang butuh angka berurutan.

4. reversed

Ngulang dari belakang:

{% for product in collection.products reversed -%}
  {{ product.title }}
{%- endfor %}

Hasilnya:

Invisibility potion
Health potion
Glacier ice
Draught of Immortality

Ejaannya reversed dengan d, bukan reverse. Dan gak ada tanda titik dua setelahnya karena ini bukan parameter dengan nilai.

❌ salah

{% for product in collection.products reverse: true %}

✅ benar

{% for product in collection.products reversed %}

Parameter-parameter ini bisa digabung: {% for product in collection.products limit: 3 offset: 2 reversed %}.

Object forloop

Tiap for punya object pendamping bernama forloop yang nyimpen informasi soal loop-nya. Ini yang bikin kamu bisa tau “ini item pertama?”, “ini item terakhir?”, “ini item ke berapa?”:

  1. first, true kalau ini iterasi pertama.
  2. last, true kalau ini iterasi terakhir.
  3. index, nomor iterasi sekarang, mulai dari 1.
  4. index0, nomor iterasi sekarang, mulai dari 0.
  5. rindex, nomor iterasi dihitung dari belakang, mulai dari 1.
  6. rindex0, nomor iterasi dari belakang, mulai dari 0.
  7. length, total jumlah iterasi.
  8. parentloop, object forloop milik loop induk kalau loop ini bersarang. Kalau gak bersarang, nil.

Contoh paling klasik, nambahin koma di antara item kecuali yang terakhir:

{% for page in pages -%}
  {%- if forloop.length > 0 -%}
    {{ page.title }}{% unless forloop.last %}, {% endunless -%}
  {%- endif -%}
{% endfor %}

Hasilnya:

About us, Contact, Potion dosages

Contoh parentloop di loop bersarang:

{% for i in (1..3) -%}
  {% for j in (1..3) -%}
    {{ forloop.parentloop.index }} - {{ forloop.index }}
  {%- endfor %}
{%- endfor %}

Hasilnya:

1 - 1
1 - 2
1 - 3
2 - 1
2 - 2
2 - 3
3 - 1
3 - 2
3 - 3

Di dalam loop dalam, forloop itu milik loop dalam. Buat nyentuh loop luar, harus lewat forloop.parentloop.

Kegunaan lain forloop yang sering: ngasih class first atau last ke elemen, bikin nomor urut, atau nampilin elemen pemisah cuma di antara item. Tapi hati-hati sama loop bersarang yang dalam, bahasannya di 58 Performance Liquid.

else di Dalam for

for bisa punya else yang jalan kalau array-nya kosong (panjangnya nol):

{% for product in collection.products %}
  {{ product.title }}<br>
{% else %}
  There are no products in this collection.
{% endfor %}

Kalau koleksinya kosong, hasilnya:

There are no products in this collection.

Ini jauh lebih rapi daripada ngecek size dulu pakai if:

❌ kerja dua kali, ngecek ukuran terus loop

{% if collection.products.size > 0 %}
  {% for product in collection.products %}
    {{ product.title }}
  {% endfor %}
{% else %}
  There are no products in this collection.
{% endif %}

✅ cukup satu tag

{% for product in collection.products %}
  {{ product.title }}
{% else %}
  There are no products in this collection.
{% endfor %}

break dan continue

1. break

Berhentiin loop sepenuhnya:

{% for i in (1..5) -%}
  {%- if i == 4 -%}
    {% break %}
  {%- else -%}
    {{ i }}
  {%- endif -%}
{%- endfor %}

Hasilnya, berhenti waktu ketemu 4:

1
2
3

2. continue

Lewati iterasi ini, lanjut ke berikutnya:

{% for i in (1..5) -%}
  {%- if i == 4 -%}
    {% continue %}
  {%- else -%}
    {{ i }}
  {%- endif -%}
{%- endfor %}

Hasilnya, 4 dilewatin tapi loop lanjut:

1
2
3
5

Bedanya: break itu “udah, cukup, keluar”, continue itu “yang ini skip, lanjut yang lain”. Contoh nyata: break buat berhenti setelah nemu produk pertama yang tersedia, continue buat ngelewatin produk yang stoknya habis.

cycle

Ngulang sekelompok string, ngeluarin satu per satu tiap iterasi. Kalau udah habis, balik ke awal. Wajib di dalam for:

{% for i in (1..4) -%}
  {% cycle 'one', 'two', 'three' %}
{%- endfor %}

Hasilnya:

one
two
three
one

Kegunaan klasiknya: class ganjil/genap buat baris tabel, atau warna latar bergantian di daftar.

Grup cycle

Ada jebakan halus di sini: kalau ada beberapa tag cycle dengan parameter yang sama di template yang sama, mereka dianggap satu grup dan berbagi hitungan. Jadi cycle kedua gak mulai dari awal, tapi lanjut dari posisi terakhir cycle pertama.

Buat ngatasinnya, kasih nama grup:

{% cycle 'group_1': 'one', 'two', 'three' %}

Lihat perbedaannya:

<!-- Iteration 1 -->
{% for i in (1..4) -%}
  {% cycle 'one', 'two', 'three' %}
{%- endfor %}
<!-- Iteration 2 -->
{% for i in (1..4) -%}
  {% cycle 'one', 'two', 'three' %}
{%- endfor %}
<!-- Iteration 3 -->
{% for i in (1..4) -%}
  {% cycle 'group_1': 'one', 'two', 'three' %}
{%- endfor %}
<!-- Iteration 4 -->
{% for i in (1..4) -%}
  {% cycle 'group_2': 'one', 'two', 'three' %}
{%- endfor %}

Hasilnya:

<!-- Iteration 1 -->
one
two
three
one
<!-- Iteration 2 -->
two
three
one
two
<!-- Iteration 3 -->
one
two
three
one
<!-- Iteration 4 -->
one
two
three
one

Iterasi 2 mulai dari two karena nyambung ke iterasi 1. Iterasi 3 dan 4 mulai dari one karena grupnya beda.

❌ berisiko, dua loop berbagi hitungan cycle

{% for item in list_a %}{% cycle 'odd', 'even' %}{% endfor %}
{% for item in list_b %}{% cycle 'odd', 'even' %}{% endfor %}

✅ aman, tiap loop punya grupnya sendiri

{% for item in list_a %}{% cycle 'a': 'odd', 'even' %}{% endfor %}
{% for item in list_b %}{% cycle 'b': 'odd', 'even' %}{% endfor %}

tablerow

Bikin baris tabel HTML buat tiap item di array. Wajib dibungkus <table> dan </table>:

<table>
  {% tablerow product in collection.products %}
    {{ product.title }}
  {% endtablerow %}
</table>

Hasilnya, satu baris dengan semua item sebagai kolom:

<table>
  <tr class="row1">
<td class="col1">
    Draught of Immortality
  </td><td class="col2">
    Glacier ice
  </td><td class="col3">
    Health potion
  </td><td class="col4">
    Invisibility potion
  </td></tr>
</table>

Perhatiin <tr> dan <td>-nya dibikinin otomatis lengkap dengan class row1, col1, dan seterusnya. Kamu cuma nulis isi sel-nya.

1. cols

Nentuin jumlah kolom. Setelah kolom penuh, otomatis pindah baris:

<table>
  {% tablerow product in collection.products cols: 2 %}
    {{ product.title }}
  {% endtablerow %}
</table>

Hasilnya dua baris dua kolom:

<table>
  <tr class="row1">
<td class="col1">
    Draught of Immortality
  </td><td class="col2">
    Glacier ice
  </td></tr>
<tr class="row2"><td class="col1">
    Health potion
  </td><td class="col2">
    Invisibility potion
  </td></tr>
</table>

2. limit, offset, range

Sama persis kaya di for: limit: 2 batasin jumlah, offset: 2 lewati dua pertama, dan (1..3) buat rentang angka.

3. Object tablerowloop

Padanan forloop buat tablerow. Punya semua properti forloop (first, last, index, index0, rindex, rindex0, length) ditambah yang khusus kolom:

  1. col, nomor kolom sekarang, mulai dari 1.
  2. col0, nomor kolom sekarang, mulai dari 0.
  3. col_first, true kalau kolom pertama di barisnya.
  4. col_last, true kalau kolom terakhir di barisnya.

Jujur aja, tablerow ini jarang dipakai di theme modern karena layout tabel udah ditinggalin buat grid CSS. Tapi tetap ada dan kamu bakal nemuin di theme lama.

paginate

Ini pasangan wajib for. Karena for mentok di 50 iterasi, paginate mecah array jadi beberapa halaman biar bisa nampilin ratusan item.

Gak semua array bisa di-paginate. Cuma ini:

  1. article.comments
  2. blog.articles
  3. collections
  4. collection.products
  5. customer.addresses
  6. customer.orders
  7. metaobject_definition.values
  8. pages
  9. product.variants
  10. search.results
  11. Setting bertipe article_list, collection_list, dan product_list.

Batas jauhnya: paginate cuma bisa sampai item ke-25.000. Kalau butuh lebih jauh, filter dulu array-nya sebelum di-paginate.

Sintaksnya:

{% paginate array by page_size %}
  {% for item in array %}
    forloop_content
  {% endfor %}
{% endpaginate %}

page_size itu jumlah item per halaman, antara 1 sampai 250. Di dalam blok paginate, kamu dapat akses ke object paginate buat bikin navigasi halaman, atau pakai filter default_pagination yang udah jadi:

{% paginate collection.products by 5 %}
  {% for product in collection.products -%}
    {{ product.title }}
  {%- endfor %}
  {{- paginate | default_pagination }}
{% endpaginate %}

Kalau koleksinya punya 19 produk, hasilnya 5 produk pertama plus navigasi:

Blue Mountain Flower
Charcoal
Crocodile tears
Dandelion milk
Draught of Immortality
<span class="page current">1</span> <span class="page"><a href="/services/liquid_rendering/resource?page=2" title="">2</a></span> <span class="page"><a href="/services/liquid_rendering/resource?page=3" title="">3</a></span> <span class="page"><a href="/services/liquid_rendering/resource?page=4" title="">4</a></span> <span class="next"><a href="/services/liquid_rendering/resource?page=2" title="">Next &raquo;</a></span>

Perhatiin: for di dalam paginate tetap for biasa, tapi collection.products-nya udah “dipotong” jadi cuma 5 item halaman ini. Object paginate-nya dibahas di 17 Objects Lainnya.

1. window_size

Ngatur berapa nomor halaman yang kelihatan di navigasi:

{% paginate collection.products by 3, window_size: 1 %}
  {% for product in collection.products -%}
    {{ product.title }}
  {%- endfor %}
  {{- paginate | default_pagination }}
{% endpaginate %}

Hasilnya navigasi yang ringkas dengan … di tengah:

Blue Mountain Flower
Charcoal
Crocodile tears
<span class="page current">1</span> <span class="deco">&hellip;</span> <span class="page"><a href="/services/liquid_rendering/resource?page=7" title="">7</a></span> <span class="next"><a href="/services/liquid_rendering/resource?page=2" title="">Next &raquo;</a></span>

2. Paginate Setting Array

Setting bertipe article_list, collection_list, dan product_list bisa di-paginate secara independen dari daftar lain di halaman yang sama, karena mereka pakai parameter query dengan kunci unik yang otomatis dikasih sama tag paginate. Kamu gak perlu nyentuh kuncinya, tapi bisa diakses lewat paginate.page_param.

Kalau mau paginate dua array tanpa refresh halaman, pakai Section Rendering API yang dibahas di 51 Ajax API dan Section Rendering API.

3. Trik Performa: paginate Buat Batasin Data

Ini trik yang jarang diketahui tapi penting. Ingat tadi: limit di for gak ngurangin data yang diambil. collection.products tetap ngambil 50 produk dari server walaupun kamu cuma nampilin 4.

Solusinya: bungkus pakai paginate dengan page_size yang cocok, walaupun kamu gak nampilin navigasi halamannya sama sekali. Dengan begitu server cuma ngambil 4 produk, bukan 50:

{% paginate collection.products by 4 %}
  {% for product in collection.products limit: 4 -%}
    {{ product.title }}
  {%- endfor %}
{% endpaginate -%}
<!-- Less performant method -->
{% for product in collection.products limit: 4 -%}
  {{ product.title }}
{%- endfor -%}

Dua-duanya nampilin 4 produk yang sama, tapi yang pertama jauh lebih ringan di server.

Kenapa limit: 4 masih ditulis padahal udah by 4? Karena kadang data yang balik bisa lebih banyak dari yang diminta, jadi paginate dan limit dipakai bareng buat mastiin.

❌ berat, server ngambil 50 produk padahal cuma 4 yang ditampilin

{% for product in collection.products limit: 4 %}
  {{ product.title }}
{% endfor %}

✅ ringan, server cuma ngambil 4

{% paginate collection.products by 4 %}
  {% for product in collection.products limit: 4 %}
    {{ product.title }}
  {% endfor %}
{% endpaginate %}

Ini bakal dibahas lagi di 58 Performance Liquid.

Ringkasan

  1. for maksimal 50 iterasi, sisanya dilewatin diam-diam. Lebih dari itu wajib paginate.
  2. Parameter for: limit (batas jumlah), offset (lewati N pertama, dihitung dari 1), reversed (dari belakang), dan range (1..3) yang inklusif.
  3. limit cuma ngatur iterasi, bukan data yang diambil. Buat ngurangin data, pakai paginate dengan page_size.
  4. Object forloop nyimpen first, last, index, index0, rindex, length, dan parentloop buat loop bersarang.
  5. for bisa punya else yang jalan kalau array-nya kosong, lebih rapi daripada ngecek size.
  6. break keluar dari loop, continue lewati iterasi ini.
  7. cycle ngulang sekelompok string, dan beberapa cycle dengan parameter sama berbagi hitungan kecuali dikasih nama grup.
  8. tablerow bikin <tr> dan <td> otomatis, wajib di dalam <table>, punya cols, dan object tablerowloop dengan properti kolom. Jarang dipakai di theme modern.
  9. paginate cuma buat array tertentu, page_size 1–250, mentok di item ke-25.000, dan window_size ngatur jumlah nomor halaman yang kelihatan.
  10. Trik: bungkus for pakai paginate walaupun gak butuh navigasi, biar server cuma ngambil data secukupnya.

Bab selanjutnya kita masuk ke tag-tag yang cuma ada di Shopify: render buat manggil snippet, section dan sections, content_for, form buat keranjang dan login, layout, sampai javascript dan stylesheet.