Section
Section'lar, bir sayfaya panelden eklenebilen bağımsız modüllerdir. Mağaza sahibi, tema editöründen istediği section'ı sayfaya ekler ve ayarlarını düzenler. Geliştirici, section'ın işaretlemesini ve stilini Twig + Tailwind ile tanımlar.
Dizin yapısı
Her section, kendi adıyla bir klasör altında yaşar; giriş dosyası default.twig'dir. Varyantlar (örn. liste / grid görünümleri) aynı klasör altında ayrı .twig dosyaları olabilir.
section/
├── product-list/
│ └── default.twig
├── category-menu/
│ └── default.twig
├── header/
│ └── default.twig
└── ...
Mağaza sahibi sayfaya "product-list" section'ını eklediğinde, T-SOFT section/product-list/default.twig'i render eder.
_ ile başlayan section'lar sistem dosyalarıdır (_current, _order gibi) — bunlar sistem tarafından kullanılır ve özelleştirilmemelidir. Diğer section dosyalarını düzenlerken de yapıyı olabildiğince standart tutun; tema güncellemelerinin sorunsuz uygulanabilmesi buna bağlıdır.Section'a veri nasıl ulaşır?
Section ayarları panel tarafında yönetilir; tema kodunda JSON şeması veya {% raw %}{% schema %}{% endraw %} bloğu yoktur. Section render edildiğinde T-SOFT, o block'a ait veriyi ve panelden gelen ayarları Twig'e UPPERCASE global değerler olarak ekler.
section.settings.xxx kullanmayın. T-SOFT'ta section verisi SECTION veya BLOCK.SETTINGS gibi nesneler altında değil; doğrudan TOTAL, PRODUCTS, SEARCH_WORD gibi UPPERCASE global'ler olarak gelir. Hangi değişkenlerin geldiğini canlı görmek için T-Soft Theme Devtools → Blok Değişkenleri sekmesini açın.Örnek: product-list section'ı
Aşağıda gerçek bir section dosyasının kısaltılmış örneği yer alır. Örnek, UPPERCASE global değerler ve T-SOFT helper'ları (snippet(), cookie(), query(), t) ile çalışır:
{% from 'layout/components/macros.twig' import grid_cols %}
{% set SELECTED_LAYOUT = cookie('selected_layout')|default(SETTING.PERVIEW.XL ?: 4) %}
{% set PERVIEW = SETTING.PERVIEW|merge({ XL: SELECTED_LAYOUT, XXL: SELECTED_LAYOUT }) %}
<div class="w-full mb-4 lg:mb-6">
{% if IS_HOME_PAGE == 0 and TOTAL != 0 %}
<div class="flex items-center gap-4 mb-4">
{% if ALLOWED_SORTS|length > 0 %}
<select name="sort" data-toggle="sort">
{% for key, translation_key in ALLOWED_SORTS %}
<option value="{{ key }}"
{% if query('sort')|default('0') == key %} selected {% endif %}>
{{ t[translation_key] }}
</option>
{% endfor %}
</select>
{% endif %}
</div>
{% endif %}
{% if PRODUCTS|length > 0 %}
<div id="catalog-{{ BLOCK.ID }}"
class="grid gap-4 {{ grid_cols(PERVIEW, {xs: 2, md: 3}) }}">
{% for key, P in PRODUCTS %}
{{ snippet('product-list.product-card.default') }}
{% endfor %}
</div>
{% else %}
{% set COUNT = "<span class='text-primary font-bold'>" ~ TOTAL ~ "</span>" %}
{% if SEARCH_WORD %}
<div>{{ t.search_result_empty|replace({'%SEARCH_WORD%': SEARCH_WORD})|raw }}</div>
{% else %}
<div>{{ t.no_result|raw }}</div>
{% endif %}
{% endif %}
</div>
Bir section'a tipik olarak gelen global'ler
Bu değişkenler section bazında farklılaşır; her section için kendi veri kümesi aktarılır. Aşağıdaki tablo product-list örneğinden alınmıştır:
| Global | Tipi | İçerik |
|---|---|---|
PRODUCTS | array | Bu section'da gösterilecek ürünler |
TOTAL | int | Toplam sonuç sayısı |
SEARCH_WORD | string | Arama kelimesi (varsa) |
ALLOWED_SORTS | object | İzin verilen sıralama seçenekleri |
IS_HOME_PAGE | int (0/1) | Section ana sayfada mı render ediliyor |
IS_AUTO_LOAD_ACTIVE | bool | "Daha fazla yükle" otomatik mi |
BLOCK.ID | string | Bu section instance'ının benzersiz kimliği |
SETTING | object | Tema-genelinde nested ayarlar (SETTING.PERVIEW.XL gibi) |
BLOCK.ID neden önemli?
Aynı sayfada birden fazla aynı tip section bulunabilir — ör. iki tane product-list (öne çıkanlar + indirimli). BLOCK.ID her instance için benzersizdir; DOM ID'leri, JS event'leri ve "auto-load" gibi mekanizmaları çakışmadan kurmanızı sağlar:
<div id="catalog-{{ BLOCK.ID }}">...</div>
<input type="hidden"
data-id="{{ BLOCK.ID }}"
data-target="#catalog-{{ BLOCK.ID }}"
data-toggle="product-loader">
data-block-id attribute'u üzerinden global BLOCKS dizisinden o bloğun SETTING verisine ulaşabilirsiniz — kalıp için bkz. JavaScript'e Genel Bakış.SETTING global'i
SETTING, tema-genelinde geçerli olan nested ayar nesnesidir — bir section'a değil, tüm temaya enjekte edilir. Ör. ürün gridinin viewport başına kaç sütun göstereceği:
{% set PERVIEW = SETTING.PERVIEW|merge({ XL: SELECTED_LAYOUT, XXL: SELECTED_LAYOUT }) %}
| Pattern | Anlamı |
|---|---|
SETTING.PERVIEW.XL | Tema-geneli ayar: XL viewport'ta sütun sayısı |
|merge({ KEY: value }) | Object'i birleştirip yeni nesne döndür |
?: | Twig elvis — sol taraf falsy ise sağdakini kullan |
|default(value) | Twig fallback filtresi |
Ürün detay sayfasında P
Ürün detay sayfasında aktif ürün, P global'i olarak gelir. Liste section'larındaki for döngüsünde de loop iterator'ı UPPERCASE — genellikle P — kullanılır:
<h1 class="font-semibold text-base md:text-xl">{{ P.TITLE }}</h1>
{% if P.PRODUCT_CODE %}
<span>{{ t.product_code }}: {{ P.PRODUCT_CODE }}</span>
{% endif %}
{% if P.DISPLAY_VAT == 1 or P.VAT == 0 %}
<span>{{ vat(P.PRICE_SELL, P.VAT) }}</span> {{ P.TARGET_CURRENCY }}
{% else %}
<span>{{ format(P.PRICE_SELL) }}</span> {{ P.TARGET_CURRENCY }} + {{ t.vat }}
{% endif %}
{% for M_D in P.MULTIPLE_DISCOUNT %}
<tr data-min="{{ M_D.MIN }}" data-max="{{ M_D.MAX }}" data-percent="{{ M_D.PERCENT }}">
<td>{{ t.amount_range|replace({'%MIN%': M_D.MIN, '%MAX%': M_D.MAX})|raw }}</td>
<td>%{{ M_D.PERCENT }}</td>
</tr>
{% endfor %}
Yaygın P alanları: P.ID, P.TITLE, P.PRODUCT_CODE, P.PRICE_SELL, P.PRICE_NOT_DISCOUNTED, P.VAT, P.TARGET_CURRENCY, P.IN_STOCK, P.HAS_VARIANT, P.VARIANT_FEATURE1_LIST, P.MULTIPLE_DISCOUNT, P.SYMBOLS, P.SHORT_DESCRIPTION. Tam liste her zaman Devtools'tan teyit edilmeli.
UPPERCASE konvansiyonu
| Yer | Konvansiyon |
|---|---|
| Global değişkenler | PRODUCTS, TOTAL, BLOCK, SETTING, P |
for döngüsü iterator'ı | for P in PRODUCTS, for M_D in P.MULTIPLE_DISCOUNT, for LIST in CAMPAIGN_LIST |
snippet() parametre anahtarları | { PRODUCT_ID: P.ID, FORM_ID: P.PERSONALIZATION_ID } |
Yerel {% raw %}{% set %}{% endraw %} değişkenleri | {% raw %}{% set STOCK_STATUS = ... %}{% endraw %}, {% raw %}{% set CASH_POINT_AMOUNT = ... %}{% endraw %} |
Snippet'a veri geçirme
Snippet'lar çağıran section'ın değişkenlerini görebilir — özellikle for döngüsündeki loop değişkenini. Aşağıdaki örnekte P snippet'a açıkça geçirilmez, ancak snippet P ismine erişebilir:
{% for key, P in PRODUCTS %}
{{ snippet('product-list.product-card.default') }}
{% endfor %}
Daha bağımsız bir snippet istiyorsanız parametreyi açıkça (UPPERCASE anahtarlarla) geçirin:
{{ snippet('product-personalization', { PRODUCT_ID: P.ID, FORM_ID: P.PERSONALIZATION_ID }) }}
Dinamik snippet adı
Snippet adı bir SETTING'ten gelebilir — böylece mağaza paneli tarafında "hangi varyant snippet'ı kullanılsın" seçilebilir:
{# SETTING'den gelirse onu, yoksa default snippet'i kullan #}
{{ snippet(SETTING.SUBFOLDER_PRODUCT_DETAIL_IMAGES|default('product-detail.images.vertical')) }}
{{ snippet(SETTING.SUBFOLDER_PRODUCT_VARIANT_1|default('product-detail.variant-1.default')) }}
snippet_uri() ve lazy yüklenen parçalar
Bir snippet'ı sayfa yüklendiğinde değil etkileşim anında AJAX ile yüklemek isterseniz, snippet_uri() ile o snippet'ın endpoint URI'sini üretirsiniz. JS tarafında bu URI fetch edilir, dönen HTML enjekte edilir:
<template
data-url="{{ snippet_uri('product-out-stock', {'include':'product-detail', 'class':'btn btn-dark w-full h-10'}) }}"
data-stock-status="{{ STOCK_STATUS }}"
data-toggle="show-out-stock">
</template>
Tipik kullanım: stok bildirim formu, mağaza listesi popup'ı, "hızlı mesaj" gibi modal içerikleri.
Anasayfa yapı taşları — slider, banner, showcase
Bir anasayfa genellikle üç section tipiyle inşa edilir. Her birinin birden fazla varyantı vardır; mağaza sahibi paneldeki "varyant" seçimiyle hangi dosyanın render edileceğine karar verir.
| Section | Varyantlar (section/<isim>/<varyant>.twig) | Ne için |
|---|---|---|
slider | default, hero-slider, announcement, brand, featured-category, marquee, story | Hero slider, marka listesi, duyuru bandı, kategori slider'ı |
banner | default, card, grid-banner, image-text, info-icon, rich-text, video | Tekli / ikili / üçlü banner, kart, ikon-bilgi blokları |
showcase | default | Ürün vitrini (sekmeli, AJAX'li çoklu ürün listesi) |
Ortak desen: SETTING.IMAGES ile görsel listesi
Slider ve banner section'larının çoğu, panelden yüklenen SETTING.IMAGES dizisini döngüyle render eder. Her görsel öğesinde aşağıdaki alanlar bulunur:
| Alan | İçerik |
|---|---|
IMAGE.IMG1.{PATH, WIDTH, HEIGHT} | Birincil (desktop) görsel |
IMAGE.IMG2.{PATH, WIDTH, HEIGHT} | Mobil görsel (<source media="(max-width:640px)">) |
IMAGE.IMG3.{PATH, WIDTH, HEIGHT} | Thumbnail (slider'larda) |
IMAGE.URL.URL | Tıklanınca gidilecek bağlantı |
IMAGE.TITLE, IMAGE.SUBTITLE, IMAGE.BTNTEXT, IMAGE.DATA1 | Görsel üstüne basılacak metinler |
IMAGE.COL | Banner'da grid sütun span'ı (12'lik grid) |
{% from 'layout/components/macros.twig' import link_attributes, col_span %}
{% if SETTING.IMAGES|length > 0 %}
<div class="grid grid-cols-12 gap-4">
{% for key, IMAGE in SETTING.IMAGES %}
<div class="{{ col_span(IMAGE.COL, {xs: 12}) }} relative group">
<picture>
{% if IMAGE.IMG2.PATH %}
<source data-srcset="{{ IMAGE.IMG2.PATH }}"
width="{{ IMAGE.IMG2.WIDTH }}" height="{{ IMAGE.IMG2.HEIGHT }}"
media="(max-width:640px)">
{% endif %}
<img data-src="{{ IMAGE.IMG1.PATH }}"
width="{{ IMAGE.IMG1.WIDTH }}" height="{{ IMAGE.IMG1.HEIGHT }}"
alt="{{ IMAGE.TITLE|striptags }}"
class="w-full h-full object-cover lazyload"
loading="lazy">
</picture>
{% if IMAGE.URL.URL %}
<a href="{{ IMAGE.URL.URL }}" {{ link_attributes(IMAGE) }}
class="absolute inset-0" aria-label="{{ IMAGE.TITLE|striptags }}"></a>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
SETTING.TITLE / SETTING.SUBTITLE her üç section tipinde de mevcut — bölümün üstüne başlık + alt yazı eklemek için kullanılır.Slider'a özel ayarlar
Slider varyantlarında ek SETTING alanları:
| Ayar | Etkisi |
|---|---|
SETTING.PAGINATION_TYPE | Slider altında nokta/pagination göster |
SETTING.DISPLAY_NAVIGATION | Sol/sağ ok butonları göster |
SETTING.DISPLAY_THUMBNAIL | Alt thumbnail şeridi göster |
Slider DOM'una data-toggle="slider" + data-swiper data-perview="xs:1" data-margin="xs:16,lg:24" eklendiğinde Swiper otomatik init olur — JS yazmanıza gerek yoktur.
Showcase — sekmeli ürün vitrini
Showcase, bir veya birden fazla tab içerir (SETTING.TABS). İlk tab inline render edilir, diğerleri tıklandığında AJAX ile yüklenir:
{% for key, TAB in SETTING.TABS %}
<div id="showcase-{{ BLOCK.ID }}{{ TAB.INDEX }}"
class="{% if key == 0 %}active tab-loaded{% endif %}">
{% if key == 0 %}
{{ snippet(SETTING.SUBFOLDER_SHOWCASE|default('showcase.slider')) }}
{% else %}
<template data-showcase-products></template>
{% endif %}
</div>
{% endfor %}
Tab başlığı için TAB.TITLE, AJAX endpoint için TAB.SHOWCASE.API, opsiyonel görsel için TAB.IMG1.PATH.
.twig dosyalarını yazar; veri panelden gelir.Yardımcı helper'lar
Section yazarken sıkça kullandığınız fonksiyon ve filtreler:
| Helper | Görevi | Örnek |
|---|---|---|
cookie('ad') | Cookie değeri okur | cookie('selected_layout') |
query('parametre') | URL query parametresi okur | query('sort') |
snippet('isim', {...}) | Snippet çağırır | snippet('product-card', {PRODUCT_ID: P.ID}) |
snippet_uri('isim', {...}) | Snippet URI üretir (AJAX lazy) | snippet_uri('product-out-stock', {...}) |
url(id, 'tip') | İçerik sayfası URL'i üretir | url(21, 'page') |
vat(fiyat, oran) | KDV dahil fiyat | vat(P.PRICE_SELL, P.VAT) |
format(sayı) | Sayı formatlama | format(P.PRICE_SELL) |
{% raw %}{% from 'yol' import macro %}{% endraw %} | Macro import eder | {% raw %}{% from 'layout/components/macros.twig' import grid_cols %}{% endraw %} |
{% raw %}{% include 'yol' %}{% endraw %} | Bir Twig dosyasını gömer | {% raw %}{% include 'layout/pagination.twig' %}{% endraw %} |
|merge | Object birleştirir | OBJ|merge({key: val}) |
|replace | Placeholder değiştirir | t.search_result|replace({'%COUNT%': TOTAL}) |
|raw | HTML escape'i atlar | t.no_result|raw |
En iyi uygulamalar
Devtools ile doğrula
`BLOCK.ID` ile çakışmaları önle
id="catalog" kullanmayın.