T-SOFT/Tema Geliştirme/Section

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/
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:

section/product-list/default.twig
{% 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:

GlobalTipiİçerik
PRODUCTSarrayBu section'da gösterilecek ürünler
TOTALintToplam sonuç sayısı
SEARCH_WORDstringArama kelimesi (varsa)
ALLOWED_SORTSobjectİzin verilen sıralama seçenekleri
IS_HOME_PAGEint (0/1)Section ana sayfada mı render ediliyor
IS_AUTO_LOAD_ACTIVEbool"Daha fazla yükle" otomatik mi
BLOCK.IDstringBu section instance'ının benzersiz kimliği
SETTINGobjectTema-genelinde nested ayarlar (SETTING.PERVIEW.XL gibi)
Tam liste her section için farklıdır. Hangi global değerlerin geldiğini sayfayı render ederken Devtools üzerinden doğrulayın.

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">
JS tarafında bloğa erişim: section'ın kök elemanına basılan 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 }) %}
PatternAnlamı
SETTING.PERVIEW.XLTema-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:

section/product-detail/default.twig
<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

YerKonvansiyon
Global değişkenlerPRODUCTS, 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 adı dinamik olduğunda her olası dosya mevcut olmalıdır. Panelden seçilen bir varyanta karşılık gelen dosya yoksa şablon hata verir. Snippet klasörlerinizi tüm seçenekleri kapsayacak şekilde düzenleyin.

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.

SectionVaryantlar (section/<isim>/<varyant>.twig)Ne için
sliderdefault, hero-slider, announcement, brand, featured-category, marquee, storyHero slider, marka listesi, duyuru bandı, kategori slider'ı
bannerdefault, card, grid-banner, image-text, info-icon, rich-text, videoTekli / ikili / üçlü banner, kart, ikon-bilgi blokları
showcasedefaultÜ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.URLTıklanınca gidilecek bağlantı
IMAGE.TITLE, IMAGE.SUBTITLE, IMAGE.BTNTEXT, IMAGE.DATA1Görsel üstüne basılacak metinler
IMAGE.COLBanner'da grid sütun span'ı (12'lik grid)
section/banner/default.twig
{% 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 %}
Header'da 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ı:

AyarEtkisi
SETTING.PAGINATION_TYPESlider altında nokta/pagination göster
SETTING.DISPLAY_NAVIGATIONSol/sağ ok butonları göster
SETTING.DISPLAY_THUMBNAILAlt 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:

section/showcase/default.twig
{% 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.

Yeni bir anasayfa kurarken mağaza sahibi panelden bu üç section tipini (slider/banner/showcase) sayfaya ekler, görselleri yükler ve varyantı seçer. Geliştirici varyant .twig dosyalarını yazar; veri panelden gelir.

Yardımcı helper'lar

Section yazarken sıkça kullandığınız fonksiyon ve filtreler:

HelperGöreviÖrnek
cookie('ad')Cookie değeri okurcookie('selected_layout')
query('parametre')URL query parametresi okurquery('sort')
snippet('isim', {...})Snippet çağırırsnippet('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 üretirurl(21, 'page')
vat(fiyat, oran)KDV dahil fiyatvat(P.PRICE_SELL, P.VAT)
format(sayı)Sayı formatlamaformat(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 %}
|mergeObject birleştirirOBJ|merge({key: val})
|replacePlaceholder değiştirirt.search_result|replace({'%COUNT%': TOTAL})
|rawHTML escape'i atlart.no_result|raw

En iyi uygulamalar

Devtools ile doğrula

UPPERCASE global değerleri eklentide canlı kontrol edin.

`BLOCK.ID` ile çakışmaları önle

Aynı sayfada aynı section iki kez varsa sabit id="catalog" kullanmayın.

Section kendine yetsin

Başka section'ın global değerlerine bağımlı kalmayın.

Tekrarlayan parçayı snippet'a al

Ürün kartı, fiyat etiketi gibi parçaları snippet'a çıkarın.

Sıradaki adım