Sissejuhatus
Eelmistes postitustes ehitasime vundamendi – CPT UI + ACF, Service ja Team CPT-d, meta-väljad. Aga me pole neid veel tegelikult kasutanud. Andmed istuvad andmebaasis, frontend ei näe neid.
Selles postituses anname vundamendile mõtte: ehitame Teenused-arhiivilehe (/services/), mis kuvab Service CPT postituste kaarte koos pisipildi, pealkirja, ikooni ja hinnaga. Block Bindings API sillab visuaalsed Block Editor patternid ACF meta-väljadega.
Postitus on kirjutatud nii, et õpilane saab seda samm-sammult järgi teha. Iga sammu juures on Miks, Kuidas ja Mida kontrollida.
Mida me täpsemalt ehitame
patterns/card-service.php– üksiku teenuse kaart (kasutame Query Loop’i sees)templates/archive-service.html– arhiivileht, mis renderdub/services/URL-ilfunctions.phptäiendus –register_post_meta()ACF väljade jaoks
Eeldused
- WordPress 6.5+ (Block Bindings API)
- CPT UI + ACF pluginad aktiivsed (#3)
- Service CPT registreeritud (URL slug “services”, has_archive true)
- ACF Field Group “Service Fields” – väljad
price_from(Number),icon(Text),featured(True/False) - Vähemalt 1 Service postitus loodud koos täidetud ACF väljadega
- DDEV keskkond
Samm 1: register_post_meta() functions.php-sse
Miks?
WP Block Bindings API saab lugeda core/post-meta source’iga ainult registreeritud meta-välju. ACF salvestab oma väljad wp_postmeta tabelisse, aga ei registreeri neid REST-API jaoks. Tulemus: Block Bindings ei näe ACF välju, frontend kuvab tühja.
Lahendus: registreeri meta-väljad ka PHP-koodi kaudu, atribuudiga show_in_rest: true. Duubeldus on talutav – ACF UI on andmesisestuseks, register_post_meta() on Block Editor’i jaoks tuvastatavus.
Kuidas?
Ava wp-content/themes/fse-theme/functions.php. Lisa pärast olemasolevat fse_theme_register_block_styles() funktsiooni järgmine:
/**
* Register ACF meta fields for REST API + Block Bindings.
*/
function fse_theme_register_meta_fields() {
// Service CPT
register_post_meta( 'service', 'price_from', [
'type' => 'number',
'single' => true,
'show_in_rest' => true,
] );
register_post_meta( 'service', 'icon', [
'type' => 'string',
'single' => true,
'show_in_rest' => true,
] );
register_post_meta( 'service', 'featured', [
'type' => 'boolean',
'single' => true,
'show_in_rest' => true,
] );
// Team CPT
register_post_meta( 'team', 'job_title', [
'type' => 'string',
'single' => true,
'show_in_rest' => true,
] );
register_post_meta( 'team', 'email', [
'type' => 'string',
'single' => true,
'show_in_rest' => true,
] );
register_post_meta( 'team', 'linkedin', [
'type' => 'string',
'single' => true,
'show_in_rest' => true,
] );
}
add_action( 'init', 'fse_theme_register_meta_fields' );
Mida kontrollida?
Salvesta fail. Ava brauser, navigeeri /wp-json/wp/v2/service?_fields=id,title,meta. Peaksid vastusena nägema JSON-i, mis sisaldab meta objekti koos price_from, icon, featured väljadega:
{
"id": 144,
"title": { "rendered": "SEO Optimization" },
"meta": {
"price_from": 199,
"icon": "🎯",
"featured": true
}
}
Kui meta objekt on tühi → register_post_meta ei töötanud. Kontrolli typo’sid, faili salvestust, ja DDEV ümberkäivitust (ddev restart).
Terve functions.php pärast seda sammu (versioon 3)
Pärast register_post_meta lisamist näeb functions.php välja nii. Muudatused võrreldes versiooniga 2 (#6 lõpus): uus funktsioon fse_theme_register_meta_fields() + selle hook’ile haakimine.
<?php
/**
* fse-theme functions.php
*
* Theme support + block styles + pattern categories + page excerpt UI + meta-väljade registreerimine.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Theme support deklaratsioonid.
*/
function fse_theme_setup() {
add_theme_support( 'post-thumbnails' );
add_theme_support( 'html5', [
'search-form',
'comment-form',
'comment-list',
'gallery',
'caption',
'style',
'script',
] );
add_theme_support( 'wp-block-styles' );
add_post_type_support( 'page', 'excerpt' );
}
add_action( 'after_setup_theme', 'fse_theme_setup' );
/**
* Block style: Secondary Button.
*/
function fse_theme_register_block_styles() {
register_block_style( 'core/button', [
'name' => 'secondary',
'label' => __( 'Secondary', 'fse-theme' ),
] );
}
add_action( 'init', 'fse_theme_register_block_styles' );
/**
* UUS: Register ACF meta fields for REST API + Block Bindings.
*/
function fse_theme_register_meta_fields() {
// Service CPT
register_post_meta( 'service', 'price_from', [
'type' => 'number',
'single' => true,
'show_in_rest' => true,
] );
register_post_meta( 'service', 'icon', [
'type' => 'string',
'single' => true,
'show_in_rest' => true,
] );
register_post_meta( 'service', 'featured', [
'type' => 'boolean',
'single' => true,
'show_in_rest' => true,
] );
// Team CPT
register_post_meta( 'team', 'job_title', [
'type' => 'string',
'single' => true,
'show_in_rest' => true,
] );
register_post_meta( 'team', 'email', [
'type' => 'string',
'single' => true,
'show_in_rest' => true,
] );
register_post_meta( 'team', 'linkedin', [
'type' => 'string',
'single' => true,
'show_in_rest' => true,
] );
}
add_action( 'init', 'fse_theme_register_meta_fields' );
/**
* Pattern kategooriad.
*/
function fse_theme_register_pattern_categories() {
register_block_pattern_category( 'fse-theme-hero', [
'label' => __( 'FSE Theme – Hero', 'fse-theme' ),
] );
register_block_pattern_category( 'fse-theme-card', [
'label' => __( 'FSE Theme – Cards', 'fse-theme' ),
] );
register_block_pattern_category( 'fse-theme-section', [
'label' => __( 'FSE Theme – Sections', 'fse-theme' ),
] );
}
add_action( 'init', 'fse_theme_register_pattern_categories' );
Samm 2: card-service.php pattern
Miks pattern?
Service kaart kasutatakse kahes kohas: arhiivilehel (kogu nimekiri) ja tulevikus avalehel (Featured Services Query Loop). Otsustusmaatriksi (vt #4) järgi 2+ kohta sama struktuuriga, varieeruv andmestik → pattern Query Loop’i sees.
Kuidas? — kaks teed (vt Case 14)
Tee A (Site Editor): Patterns → Create new → “Card Service” → ehita visuaalselt → CBT export. Töötab, aga Block Bindings markup tuleb lisada Code Editor’is.
Tee B (otsekoodi): Loo fail VS Code’is. Kiirem, kui struktuur on selge. Meie valisime Tee B, sest:
- Eelmise patterni (page-hero.php) tegime Site Editor’s – kogemus käes
- Block Bindings JSON-süntaks on raskem UI-st klõpsata kui otse kirjutada
- Otsekoodi puhul ei ole CBT-ekspordi lõksu (JSON-attributes-strip, page-hetro typo)
Kuidas? — sammude koodina
Loo fail wp-content/themes/fse-theme/patterns/card-service.php järgmise sisuga:
<?php
/**
* Title: Card Service
* Slug: fse-theme/card-service
* Categories: featured
* Description: Teenuse kaart. Kasutatakse Query Loop'i sees archive-service.html-is ja avalehel Featured Services sektsioonis.
*/
?>
<!-- wp:group {"tagName":"article","style":{"spacing":{"padding":{"top":"var:preset|spacing|l","right":"var:preset|spacing|l","bottom":"var:preset|spacing|l","left":"var:preset|spacing|l"},"blockGap":"var:preset|spacing|s"},"border":{"radius":"8px","width":"1px","color":"#e5e5e5"}},"layout":{"type":"constrained"}} -->
<article class="wp-block-group" style="border-color:#e5e5e5;border-width:1px;border-radius:8px;padding:var(--wp--preset--spacing--l)">
<!-- wp:post-featured-image {"isLink":true,"aspectRatio":"16/9"} /-->
<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"icon"}}}},"fontSize":"x-large"} -->
<p class="has-x-large-font-size">🎯</p>
<!-- /wp:paragraph -->
<!-- wp:post-title {"level":2,"isLink":true,"fontSize":"large"} /-->
<!-- wp:post-excerpt {"moreText":""} /-->
<!-- wp:group {"layout":{"type":"flex","flexWrap":"nowrap"}} -->
<div class="wp-block-group">
<!-- wp:paragraph -->
<p>Alates</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"price_from"}}}},"style":{"typography":{"fontWeight":"700"}}} -->
<p style="font-weight:700">0</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>€</p>
<!-- /wp:paragraph -->
</div>
<!-- /wp:group -->
</article>
<!-- /wp:group -->
Block Bindings markup’i selgitus
Vaata icon paragraph blokki:
<!-- wp:paragraph {
"metadata": {
"bindings": {
"content": { ← seo `content` atribuut ...
"source": "core/post-meta", ← ... meta-väljaga ...
"args": {"key": "icon"} ← ... mille võtmega "icon"
}
}
}
} -->
<p>🎯</p> ← fallback tekst, kuvatakse kui meta tühi
<!-- /wp:paragraph -->
Frontend’s WP loeb meta-välja icon, asendab paragraph’i sisu sellega. Kui meta-väli on tühi, jääb fallback tekst.
Mida kontrollida?
Pärast faili loomist, käivita terminalis:
ddev exec wp eval-file wp-content/themes/fse-theme/flush-pattern-cache.php
ddev exec wp cache flush
Seejärel ava WP admin → Appearance → Editor → Patterns. Tee hard refresh (Cmd+Shift+R). Peaksid nägema “Card Service” patternit “Featured” kategoorias.
Samm 3: archive-service.html template
Miks?
WP template hierarchy järgi: URL /services/ otsib kõigepealt archive-service.html, siis archive.html, siis index.html. Loome esimese, et meil oleks täielik kontroll arhiivilehe üle.
Pedagoogiline lõks: miks ei kasuta PageHero pattern reference’it?
PageHero pattern kasutab wp:post-title ja wp:post-excerpt blokke. Need loevad praeguse postituse andmeid. Aga CPT-arhiivilehel POLE praegust postitust – on hoopis nimekiri postitustest.
PageHero töötab:
- Page-template’is (
page.html) – Meist, Kontakt, Privaatsus home.html-is (page_for_posts) – Blogi listing, sest Posts page on tegelikult Page
PageHero EI tööta:
- CPT-arhiivilehel (
archive-service.html) – pole alus-Page’i
Lahendus: inline hero arhiivi-template’is, kasutame wp:query-title blokki (loeb CPT label’i).
Kuidas?
Loo fail wp-content/themes/fse-theme/templates/archive-service.html:
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
<!-- Inline hero (CPT-arhiivi jaoks, mitte PageHero pattern) -->
<!-- wp:group {"tagName":"section","align":"full","backgroundColor":"bg-gray","style":{"spacing":{"padding":{"top":"var:preset|spacing|xxxxxl","bottom":"var:preset|spacing|xxxxxl"}}},"layout":{"type":"default"}} -->
<section class="wp-block-group alignfull has-bg-gray-background-color has-background">
<!-- wp:query-title {"type":"archive","textAlign":"center","level":1,"showPrefix":false} /-->
<!-- wp:paragraph {"align":"center"} -->
<p class="has-text-align-center">Kõik teenused, mida pakume.</p>
<!-- /wp:paragraph -->
</section>
<!-- /wp:group -->
<!-- Query Loop, kasutab meie card-service patternit -->
<!-- wp:query {"query":{"perPage":9,"postType":"service","order":"asc","orderBy":"date","inherit":false},"align":"wide","layout":{"type":"constrained"}} -->
<div class="wp-block-query alignwide">
<!-- wp:post-template {"layout":{"type":"grid","columnCount":3}} -->
<!-- wp:pattern {"slug":"fse-theme/card-service"} /-->
<!-- /wp:post-template -->
<!-- wp:query-pagination -->
<!-- wp:query-pagination-previous /-->
<!-- wp:query-pagination-numbers /-->
<!-- wp:query-pagination-next /-->
<!-- /wp:query-pagination -->
<!-- wp:query-no-results -->
<!-- wp:paragraph -->
<p>Teenuseid ei leitud.</p>
<!-- /wp:paragraph -->
<!-- /wp:query-no-results -->
</div>
<!-- /wp:query -->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->
Query Loop’i seadete selgitus
"postType": "service"– päring tagastab AINULT Service CPT postitusi"perPage": 9– kuni 9 kaarti lehekülje kohta (3×3 grid)"order": "asc"– vanemad enne (newest first oleks “desc”)"inherit": false– ei päri template’ist (sest custom päring)align: "wide"– 1312px lai (theme.json wideSize)
Post Template sees on AINULT <!-- wp:pattern slug="fse-theme/card-service" --> – pattern reference, mis tähendab et iga muudatus card-service.php-s kajastub kõikjal.
Mida kontrollida?
Salvesta fail. Käivita terminalis:
ddev exec wp eval-file wp-content/themes/fse-theme/flush-pattern-cache.php
ddev exec wp cache flush
Ava brauser: https://[sinu-sait].ddev.site/services/. Peaksid nägema:
- Header (template-part)
- Hall sektsioon “Services” (CPT label) + “Kõik teenused, mida pakume.”
- 3-veeruline grid kaartidest, igaüks: pisipilt + 🎯 ikoon + H2 pealkiri + excerpt + “Alates X €”
- Footer
Lõks, mida me kohtasime: ACF + Block Bindings
Esimese katsega kasutasime Block Bindings source’iks acf/field (ACF 6.2+ peaks toetama). Frontend’s tagastas binding TÜHJA – isegi kui ACF UI andmebaasis on väärtus.
Põhjus pole 100% kindel. Võimalik, et:
- ACF Block Bindings adapter vajab ACF Pro plugin’it (free versioonis ei tööta)
- Field Group seades on eraldi lüliti
- Args.key formaat erineb (field_key vs field_name)
Lahendus: register_post_meta() + core/post-meta source. WP-natiivne, töötab ACF versioonist sõltumata, lihtsam debugida.
Õpilasele: kui sa ehitad seda projekti uuesti, järgi otse Samm 1-st (register_post_meta). Ära raisaka aega acf/field source’iga.
Veaotsing
Kaart kuvab “Alates 0 €” (fallback)
- Kontrolli
functions.php– kasregister_post_meta()on sees? Salvestatud? - Kontrolli REST-API:
/wp-json/wp/v2/service/144?_fields=meta– kas meta on seal nähtav? - Kontrolli ACF: kas tegelikult on Service postitusele väärtus täidetud?
ddev exec wp post meta get 144 price_from
Pattern ei ilmu admin’is
- Pattern cache flush:
ddev exec wp eval-file wp-content/themes/fse-theme/flush-pattern-cache.php - Object cache flush:
ddev exec wp cache flush - Hard refresh brauseris: Cmd+Shift+R (Mac) või Ctrl+Shift+R
- Kontrolli PHP-header’i süntaksit (Title, Slug, Categories ühel real)
CPT label kuvub inglise keeles (“Services” mitte “Teenused”)
CPT UI → Edit Service → Labels → muuda “Services” → “Teenused”, “Service” → “Teenus”. URL on eraldi (rewrite slug = “teenused” annab /teenused/).
Õppetundide kokkuvõte
- Vundament saab kasutuselevõetud: CPT + ACF + Pattern + Template + Block Bindings tervikuna
- Block Bindings API sillab patternid ja meta-väljad – WP-natiivne, deklaratiivne
- core/post-meta vajab register_post_meta() – ACF ei tee seda automaatselt
- CPT-arhiivilehel ei tööta PageHero pattern (post-title/excerpt blokid) – kasuta query-title + inline hero
- Pattern reference Query Loop’i sees tagab muudatuse propagatsiooni (mitte inline-imine)
- Site Editor vs otsekoodi: mõlemad on legitiimsed, vali rütmi järgi
Mis tuleb järgmiseks
Avaleht “Featured Services” – sama card-service pattern, aga Query Loop filtriga featured = true. See on meta_query kasutus – kuidas filtreerida postitusi ACF-välja järgi. Tuleb järgmises postituses.
Tagantjärele saame ehitada ka single-service.html (üksiku teenuse leht ServiceHero + Body + FAQ), archive-team.html (Meeskond, sama muster), page.html (Meist, Kontakt, Privaatsus – PageHero pattern reference’iga).
Kõik need õppetundid on detailselt talletatud project/casestudy.md Case 14-16-s. Õpilase soovituslik järjekord:
- #0 (Mida me teeme)
- #1, #2, #3 (theme.json, plaan, vundament)
- #4 (FSE filosoofia)
- #5 (PageHero)
- #6 (Blog Home)
- #7 (see postitus, CPT + ACF kasutuselevõtt)
Hea õppimist.