Temel kavramlar

Olaylar

DOM olayları ve değiştiriciler, motif.on/off/trigger bileşen olayları, yaşam döngüsü olayları, uygulama olay veriyolu ve Emitter ile servis olayları; JSX ile ve JSX'siz.

MotifJS'te üç olay katmanı vardır:

  1. DOM olayları — bir elemana onclick, oninput gibi prop'larla, JSX'siz yazımda motif.on ile bağlanır.
  2. Bileşen olayları — component.motif.on/trigger/off ile bileşenler arası iletişim.
  3. Uygulama olayları — context.on/fire ile uygulama genelinde yayın.

Örnekler başlangıç şablonundaki not uygulamasının kesitleridir: not listesi (NotesLayout) ve düzenleyici (NotePage). Note ve NotesService Bileşenler sayfasındaki src/services/notes.ts dosyasındakilerdir.

this.state bileşenin kendi durumudur; this.remove ve this.close bileşenin metotlarıdır.

state bileşenin reactive ile kurduğu yerel durumdur; remove ve close bileşen fonksiyonunun içindeki yerel fonksiyonlardır.

DOM olayları

JSX'te on... prop'ları DOM olaylarına bağlanır; olay adları küçük harfle yazılır. JSX'siz yazımda aynı bağ motif.on(ad, işleyici) çağrısıdır:

<button type="button" onclick={() => notes.togglePin(note)}>Sabitle</button>
<input type="search" oninput={(e) => (this.state.query = (e.target as HTMLInputElement).value)} />
<form onsubmit={(sender, e) => { e.preventDefault(); notes.create(); }}>…</form>
<a href="/archive" onclick:prevent={(sender, _e) => sender.context.navigate("/archive")}>Arşiv</a>
<button type="button" onclick={() => notes.togglePin(note)}>Sabitle</button>
<input type="search" oninput={(e) => (state.query = (e.target as HTMLInputElement).value)} />
<form onsubmit={(sender, e) => { e.preventDefault(); notes.create(); }}>…</form>
<a href="/archive" onclick:prevent={(sender, _e) => sender.context.navigate("/archive")}>Arşiv</a>
const pin = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", () => notes.togglePin(note));
  button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Sabitle")));
});
const search = motifComponent("input", (field: ComponentBase) => {
  field.attr.add({ type: "search" });
  field.motif.on("input", (_sender: ComponentBase, e: Event) => (this.state.query = (e.target as HTMLInputElement).value));
});
const form = motifComponent("form", (form: ComponentBase) => {
  form.motif.on("submit", (_sender: ComponentBase, e: Event) => { e.preventDefault(); notes.create(); });
});
const archive = motifComponent("a", (link: ComponentBase) => {
  link.attr.add({ href: "/archive" });
  link.motif.on("click:prevent", (sender: ComponentBase, _e: Event) => sender.context.navigate("/archive"));
  link.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Arşiv")));
});
const pin = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", () => notes.togglePin(note));
  button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Sabitle")));
});
const search = motifComponent("input", (field: ComponentBase) => {
  field.attr.add({ type: "search" });
  field.motif.on("input", (_sender: ComponentBase, e: Event) => (state.query = (e.target as HTMLInputElement).value));
});
const form = motifComponent("form", (form: ComponentBase) => {
  form.motif.on("submit", (_sender: ComponentBase, e: Event) => { e.preventDefault(); notes.create(); });
});
const archive = motifComponent("a", (link: ComponentBase) => {
  link.attr.add({ href: "/archive" });
  link.motif.on("click:prevent", (sender: ComponentBase, _e: Event) => sender.context.navigate("/archive"));
  link.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Arşiv")));
});
<button type="button" onclick={() => notes.togglePin(note)}>Sabitle</button>
<input type="search" oninput={(e) => (this.state.query = e.target.value)} />
<form onsubmit={(sender, e) => { e.preventDefault(); notes.create(); }}>…</form>
<a href="/archive" onclick:prevent={(sender, _e) => sender.context.navigate("/archive")}>Arşiv</a>
<button type="button" onclick={() => notes.togglePin(note)}>Sabitle</button>
<input type="search" oninput={(e) => (state.query = e.target.value)} />
<form onsubmit={(sender, e) => { e.preventDefault(); notes.create(); }}>…</form>
<a href="/archive" onclick:prevent={(sender, _e) => sender.context.navigate("/archive")}>Arşiv</a>
const pin = motifComponent("button", (button) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", () => notes.togglePin(note));
  button.controls.add(motifComponent("text", (node) => node.setText("Sabitle")));
});
const search = motifComponent("input", (field) => {
  field.attr.add({ type: "search" });
  field.motif.on("input", (_sender, e) => (this.state.query = e.target.value));
});
const form = motifComponent("form", (form) => {
  form.motif.on("submit", (_sender, e) => { e.preventDefault(); notes.create(); });
});
const archive = motifComponent("a", (link) => {
  link.attr.add({ href: "/archive" });
  link.motif.on("click:prevent", (sender, _e) => sender.context.navigate("/archive"));
  link.controls.add(motifComponent("text", (node) => node.setText("Arşiv")));
});
const pin = motifComponent("button", (button) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", () => notes.togglePin(note));
  button.controls.add(motifComponent("text", (node) => node.setText("Sabitle")));
});
const search = motifComponent("input", (field) => {
  field.attr.add({ type: "search" });
  field.motif.on("input", (_sender, e) => (state.query = e.target.value));
});
const form = motifComponent("form", (form) => {
  form.motif.on("submit", (_sender, e) => { e.preventDefault(); notes.create(); });
});
const archive = motifComponent("a", (link) => {
  link.attr.add({ href: "/archive" });
  link.motif.on("click:prevent", (sender, _e) => sender.context.navigate("/archive"));
  link.controls.add(motifComponent("text", (node) => node.setText("Arşiv")));
});

Düz etikette her DOM olayı bu yazımla bağlanır (onpointerdown, onmouseenter, onanimationend dahil); onClick gibi camelCase yazım da aynı olayı bağlar.

Bir bileşen etiketinde DOM olay adı taşıyan prop (onchange/onChange, onCopy, onResize, onSelect, onReset, onContextMenu …) bileşenin elemanına DOM dinleyicisi olarak bağlanır ve bileşenin props'una ulaşmaz; camelCase yazımda derleyici MJX002 uyarısı verir. Geri çağrı prop'u için DOM olayıyla çakışmayan bir ad seçin (onValueChange, onConfirm). Elemanı olmayan (fragment) bileşende böyle bir dinleyicinin bağlanacağı eleman yoktur (bkz. Kök eleman).

İşleyici imzaları

İşleyici iki biçimden biriyle yazılır; MotifJS parametre sayısına göre doğru olanı çağırır. Kural motif.on için de aynıdır: tek parametreli işleyici olayı alır, göndereni değil:

<input type="text" onkeydown={(e) => e.key === "Enter" && notes.create()} />          {/* yalnız olay */}
<li onclick={(sender, e) => sender.class.add("selected")}>…</li>                       {/* gönderen + olay */}
const field = motifComponent("input", (input: ComponentBase) => {
  input.attr.add({ type: "text" });
  input.motif.on("keydown", (_sender: ComponentBase, e: KeyboardEvent) => e.key === "Enter" && notes.create());
});
const row = motifComponent("li", (li: ComponentBase) => {
  li.motif.on("click", (sender: ComponentBase, _e: Event) => sender.class.add("selected"));
});
<input type="text" onkeydown={(e) => e.key === "Enter" && notes.create()} />          {/* yalnız olay */}
<li onclick={(sender, e) => sender.class.add("selected")}>…</li>                       {/* gönderen + olay */}
const field = motifComponent("input", (input) => {
  input.attr.add({ type: "text" });
  input.motif.on("keydown", (_sender, e) => e.key === "Enter" && notes.create());
});
const row = motifComponent("li", (li) => {
  li.motif.on("click", (sender, _e) => sender.class.add("selected"));
});

İşleyici hataları

İşleyici hata fırlatırsa ya da async işleyicinin döndürdüğü promise reddedilirse hata MJX123 (The '<olay>' event handler threw.) koduyla bir MotifError olarak errorHandler'a raporlanır; asıl hata cause alanındadır. Rapor üretim kipinde de konsola yazılır (app.useLogging(false) kapatır) ve errorHandler.addListener(fn) dinleyicilerine ulaşır. Aynı olayın diğer dinleyicileri çalışmaya devam eder.

on:ad / on-ad / on_ad yazımı

Bu yazım işleyiciyi olduğu gibi etiketin bileşenine bağlar: <X on:saved={fn}/>, sender.motif.on("saved", fn) demektir. Olay adı, önek (on:, on-, on_) atıldıktan sonra kalan kısmın tamamıdır: on-my-event → my-event, on_my_event → my_event. Hem DOM olayları hem de motif.trigger ile tetiklenen özel olaylar için kullanılır ve yukarıdaki parametre sayısı kuralı aynen geçerlidir:

<DeleteConfirm note={note} on:confirm={(target: Note) => this.remove(target)} />   {/* tek parametre: trigger'a verilen veri */}
<button type="button" on-click={(sender, _e) => sender.motif.hide()}>Kapat</button>   {/* iki parametre: gönderen + olay */}
<button type="button" on_click={this.close}>Kapat</button>   {/* metot başvurusu olduğu gibi geçer */}
<DeleteConfirm note={note} on:confirm={(target: Note) => remove(target)} />   {/* tek parametre: trigger'a verilen veri */}
<button type="button" on-click={(sender, _e) => sender.motif.hide()}>Kapat</button>   {/* iki parametre: gönderen + olay */}
<button type="button" on_click={close}>Kapat</button>   {/* fonksiyon başvurusu olduğu gibi geçer */}
const confirm = motifComponent(DeleteConfirm, { note, initializeComponent: (confirm: ComponentBase) => confirm.motif.on("confirm", (_confirm: ComponentBase, target: Note) => this.remove(target)) });
const close = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", (sender: ComponentBase, _e: Event) => sender.motif.hide());
  button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Kapat")));
});
const closeByMethod = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", this.close);
  button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Kapat")));
});
const confirm = motifComponent(DeleteConfirm, { note, initializeComponent: (confirm: ComponentBase) => confirm.motif.on("confirm", (_confirm: ComponentBase, target: Note) => remove(target)) });
const close = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", (sender: ComponentBase, _e: Event) => sender.motif.hide());
  button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Kapat")));
});
const closeByMethod = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", close);
  button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Kapat")));
});
<DeleteConfirm note={note} on:confirm={(target) => this.remove(target)} />   {/* tek parametre: trigger'a verilen veri */}
<button type="button" on-click={(sender, _e) => sender.motif.hide()}>Kapat</button>   {/* iki parametre: gönderen + olay */}
<button type="button" on_click={this.close}>Kapat</button>   {/* metot başvurusu olduğu gibi geçer */}
<DeleteConfirm note={note} on:confirm={(target) => remove(target)} />   {/* tek parametre: trigger'a verilen veri */}
<button type="button" on-click={(sender, _e) => sender.motif.hide()}>Kapat</button>   {/* iki parametre: gönderen + olay */}
<button type="button" on_click={close}>Kapat</button>   {/* fonksiyon başvurusu olduğu gibi geçer */}
const confirm = motifComponent(DeleteConfirm, { note, initializeComponent: (confirm) => confirm.motif.on("confirm", (_confirm, target) => this.remove(target)) });
const close = motifComponent("button", (button) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", (sender, _e) => sender.motif.hide());
  button.controls.add(motifComponent("text", (node) => node.setText("Kapat")));
});
const closeByMethod = motifComponent("button", (button) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", this.close);
  button.controls.add(motifComponent("text", (node) => node.setText("Kapat")));
});
const confirm = motifComponent(DeleteConfirm, { note, initializeComponent: (confirm) => confirm.motif.on("confirm", (_confirm, target) => remove(target)) });
const close = motifComponent("button", (button) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", (sender, _e) => sender.motif.hide());
  button.controls.add(motifComponent("text", (node) => node.setText("Kapat")));
});
const closeByMethod = motifComponent("button", (button) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", close);
  button.controls.add(motifComponent("text", (node) => node.setText("Kapat")));
});

Silme onayı DeleteConfirm, "Sil" düğmesine basılınca kendi bileşeninde motif.trigger("confirm", note) çağırır (sınıfta ve Options'ta this.motif.trigger, fonksiyon bileşeninde döndürdüğü kökün motif.trigger'ı) ve ilk işleyici notu alır. JSX'siz yazımda dinleyici initializeComponent anahtarında kurulur; sınıf, fonksiyon ve Options bileşeninde aynıdır. Olay adı serbest olduğu için on:/on-/on_ prop'larının parametreleri any tiplidir; veriye kendi tipini parametrede verirsiniz ((target: Note) => …). motif.on'un tipi (sender, e) olduğundan JSX'siz yazımda veri ikinci parametredir.

Olay değiştiricileri (modifiers)

Olay adına iki nokta ile eklenen değiştiriciler davranışı ayarlar. Bunlar component.motif.on API'sinde ve tip sisteminde tanımlıdır:

Değiştirici Etki
:once Bir kez çalışır, sonra kendini kaldırır.
:passive addEventListener için passive: true.
:capture Yakalama (capture) fazında dinler.
:prevent İşleyici sonrası preventDefault().
:stop İşleyici sonrası stopPropagation().
:self Yalnızca olay hedefi elemanın kendisiyse çalışır.
:trusted Yalnızca isTrusted olaylarda çalışır.

JSX'te öznitelik adı tek bir : alabildiği için etikette öznitelik başına bir değiştirici yazılır; motif.on ile zincir kurulur. Değiştiricili prop da değiştiricisiz hâliyle aynı tipi alır; onkeydown:once={(e) => e.key} içinde e KeyboardEvent olur:

<form onsubmit:prevent={() => notes.create()}>…</form>
<button type="button" onclick:once={() => notes.create()}>İlk notu oluştur</button>
<div class="list" onscroll:passive={() => rememberScroll()}>…</div>
const form = motifComponent("form", (form: ComponentBase) => {
  form.motif.on("submit:prevent", () => notes.create());
});
const first = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button" });
  button.motif.on("click:once", () => notes.create());
  button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("İlk notu oluştur")));
});
const list = motifComponent("div", (list: ComponentBase) => {
  list.class.add("list");
  list.motif.on("scroll:passive", () => rememberScroll());
});
<form onsubmit:prevent={() => notes.create()}>…</form>
<button type="button" onclick:once={() => notes.create()}>İlk notu oluştur</button>
<div class="list" onscroll:passive={() => rememberScroll()}>…</div>
const form = motifComponent("form", (form) => {
  form.motif.on("submit:prevent", () => notes.create());
});
const first = motifComponent("button", (button) => {
  button.attr.add({ type: "button" });
  button.motif.on("click:once", () => notes.create());
  button.controls.add(motifComponent("text", (node) => node.setText("İlk notu oluştur")));
});
const list = motifComponent("div", (list) => {
  list.class.add("list");
  list.motif.on("scroll:passive", () => rememberScroll());
});

Bileşen olay API'si: motif.on / motif.off / motif.trigger

Her bileşende programatik olay yönetimi motif ad alanındadır. Örnekte satır tıklanınca bileşen kendi saved olayını tetikler; o olayın işleyicisi yalnız dahili olarak kaydedilmiştir:

const onSaved = (sender: ComponentBase, saved: Note) => notes.touch(saved);

this.motif.on("click", () => this.motif.trigger("saved", note));
this.motif.on("saved", onSaved, false);
this.motif.off("saved", onSaved);
const onSaved = (sender: ComponentBase, saved: Note) => notes.touch(saved);

row.motif.on("click", () => row.motif.trigger("saved", note));
row.motif.on("saved", onSaved, false);
row.motif.off("saved", onSaved);
const onSaved = (sender, saved) => notes.touch(saved);

this.motif.on("click", () => this.motif.trigger("saved", note));
this.motif.on("saved", onSaved, false);
this.motif.off("saved", onSaved);
const onSaved = (sender, saved) => notes.touch(saved);

row.motif.on("click", () => row.motif.trigger("saved", note));
row.motif.on("saved", onSaved, false);
row.motif.off("saved", onSaved);

Fonksiyon bileşeninde bu çağrılar bileşenin kendisi üzerindedir (örnekte row): JSX'te kökün onConfig kancasına ya da ref'ine gelen bileşen, JSX'siz yazımda kökü kuran motifComponent fonksiyonunun aldığı bileşen.

x:mounted — DOM'a bağlanma anı

Eleman gerçekten DOM'a eklendiğinde çalışacak bir kanca; başlık alanına odak vermek gibi işler için:

this.motif.on("x:mounted", () => this.element.focus());
title.motif.on("x:mounted", () => title.element.focus());
this.motif.on("x:mounted", () => this.element.focus());
title.motif.on("x:mounted", () => title.element.focus());

Fonksiyon bileşeninde title başlık alanının bileşenidir: JSX'te ref ile alınır, JSX'siz yazımda motifComponent ile kurulan değişkendir.

Her abonelik bir kez, eleman document'a bağlandıktan sonraki görevde çalışır; eleman zaten document içindeyse aboneliği izleyen görevde. Bileşen sökülüp yeniden yerleştiğinde (taşıma, motif.hide/motif.show, keepAlive) x:mounted tekrar fırlamaz; o an için x:activated kullanın (Yaşam Döngüsü). Henüz çalışmamış bir aboneliği motif.off("x:mounted", işleyici) iptal eder. İşleyicinin fırlattığı hata MJX122 olarak raporlanır.

Yaşam döngüsü olayları

Bileşen yaşam döngüsü olaylarını da motif.on ile dinleyebilirsiniz. x: öneki bu olayları DOM'a değil bileşenin kendi yayıcısına bağlar; işleyici (sender, e) imzasıyla çağrılır (e: { cancel }), tek parametreli işleyici yalnızca e alır:

this.motif.on("x:built", (sender) => sender.class.add("ready"));
this.motif.on("x:disposed", () => clearInterval(timer));
row.motif.on("x:built", (sender) => sender.class.add("ready"));
row.motif.on("x:disposed", () => clearInterval(timer));
this.motif.on("x:built", (sender) => sender.class.add("ready"));
this.motif.on("x:disposed", () => clearInterval(timer));
row.motif.on("x:built", (sender) => sender.class.add("ready"));
row.motif.on("x:disposed", () => clearInterval(timer));

Tanınan adlar: x:initializing, x:initialized, x:config, x:configured, x:building, x:built, x:mounted, x:visibilityChanged, x:activated, x:deactivated, x:disposing, x:disposed. Abonelik bileşenle birlikte temizlenir; motif.off ile elle de kaldırılır. Aynı işleyici birden çok kez abone olduysa motif.off hepsini kaldırır. x:initializing ve x:initialized bileşenin yapıcısında biter; kurulmuş bir bileşene sonradan abone olunursa işleyici abone olunduğu anda bir kez çalışır.

Bu dinleyicilerden biri hata fırlatırsa hata, kanca sınıf metodu ya da prop olarak yazılmış gibi MJX122 (ör. The component x:built hook threw.) koduyla errorHandler'a raporlanır; bileşen kurulmaya devam eder.

Zamanlama notu: düz new Component("div", …) örneklerinde x:config yapıcıda fırlar; yapıcı döndükten sonra abone olmak için geç kalınmış olur. Alt sınıflarda (class X extends Component) onConfig build() başında çalıştığı için yapıcıdan sonra abone olmak yeterlidir. Ayrıntı: Yaşam Döngüsü.

Uygulama olayları — context.on / context.fire

Bileşenler arasında (ebeveyn-çocuk ilişkisi olmadan) haberleşmek için uygulama olay veriyolu kullanılır. Bileşenin context'i çalışan Application'dır (sınıfta ve Options'ta this.context, fonksiyon bileşeninde kök bileşenin context'i); dinleyici bileşen serbest bırakılınca abonelik kendiliğinden kalkar. ArchiveCounter arşivlenen notları sayar: sınıfta onConfig içinde this.context.on, fonksiyonda köke yazılan onConfig prop'u, Options'ta onConfigured; JSX'siz yazımda hepsi aynı context.on çağrısıdır:

import { Component, reactive } from "@motifx/core";

export class ArchiveCounter extends Component {
  state = reactive({ count: 0 });

  override onConfig() {
    this.context.on("noteArchived", () => this.state.count++);
  }

  override view() {
    return <span class="archived">{() => `${this.state.count} arşivlendi`}</span>;
  }
}
import { reactive } from "@motifx/core";

export function ArchiveCounter() {
  const state = reactive({ count: 0 });
  return (
    <span class="archived" onConfig={(counter) => counter.context.on("noteArchived", () => state.count++)}>
      {() => `${state.count} arşivlendi`}
    </span>
  );
}
import { motifOptions, reactive } from "@motifx/core";

export const ArchiveCounter = () => motifOptions({
  el: "span",
  data: reactive({ count: 0 }),

  onConfigured() {
    this.class.add("archived");
    this.context.on("noteArchived", () => this.data.count++);
  },

  view() {
    return <>{() => `${this.data.count} arşivlendi`}</>;
  },
});
import { Component, ComponentBase, motifComponent, reactive } from "@motifx/core";

export class ArchiveCounter extends Component {
  state = reactive({ count: 0 });

  override onConfig() {
    this.context.on("noteArchived", () => this.state.count++);
  }

  override view() {
    return motifComponent("span", (span: ComponentBase) => {
      span.class.add("archived");
      span.bindings.text(() => `${this.state.count} arşivlendi`);
    });
  }
}
import { ComponentBase, motifComponent, reactive } from "@motifx/core";

export function ArchiveCounter() {
  const state = reactive({ count: 0 });
  return motifComponent("span", {
    onConfig: (counter: ComponentBase) => counter.context.on("noteArchived", () => state.count++),
    initializeComponent: (span: ComponentBase) => {
      span.class.add("archived");
      span.bindings.text(() => `${state.count} arşivlendi`);
    },
  });
}
import { ComponentBase, motifComponent, motifOptions, reactive } from "@motifx/core";

export const ArchiveCounter = () => motifOptions({
  el: "span",
  data: reactive({ count: 0 }),

  onConfigured() {
    this.class.add("archived");
    this.context.on("noteArchived", () => this.data.count++);
  },

  view() {
    return motifComponent("text", (node: ComponentBase) => node.bindings.text(() => `${this.data.count} arşivlendi`));
  },
});
import { Component, reactive } from "@motifx/core";

export class ArchiveCounter extends Component {
  state = reactive({ count: 0 });

  onConfig() {
    this.context.on("noteArchived", () => this.state.count++);
  }

  view() {
    return <span class="archived">{() => `${this.state.count} arşivlendi`}</span>;
  }
}
import { reactive } from "@motifx/core";

export function ArchiveCounter() {
  const state = reactive({ count: 0 });
  return (
    <span class="archived" onConfig={(counter) => counter.context.on("noteArchived", () => state.count++)}>
      {() => `${state.count} arşivlendi`}
    </span>
  );
}
import { motifOptions, reactive } from "@motifx/core";

export const ArchiveCounter = () => motifOptions({
  el: "span",
  data: reactive({ count: 0 }),

  onConfigured() {
    this.class.add("archived");
    this.context.on("noteArchived", () => this.data.count++);
  },

  view() {
    return <>{() => `${this.data.count} arşivlendi`}</>;
  },
});
import { Component, motifComponent, reactive } from "@motifx/core";

export class ArchiveCounter extends Component {
  state = reactive({ count: 0 });

  onConfig() {
    this.context.on("noteArchived", () => this.state.count++);
  }

  view() {
    return motifComponent("span", (span) => {
      span.class.add("archived");
      span.bindings.text(() => `${this.state.count} arşivlendi`);
    });
  }
}
import { motifComponent, reactive } from "@motifx/core";

export function ArchiveCounter() {
  const state = reactive({ count: 0 });
  return motifComponent("span", {
    onConfig: (counter) => counter.context.on("noteArchived", () => state.count++),
    initializeComponent: (span) => {
      span.class.add("archived");
      span.bindings.text(() => `${state.count} arşivlendi`);
    },
  });
}
import { motifComponent, motifOptions, reactive } from "@motifx/core";

export const ArchiveCounter = () => motifOptions({
  el: "span",
  data: reactive({ count: 0 }),

  onConfigured() {
    this.class.add("archived");
    this.context.on("noteArchived", () => this.data.count++);
  },

  view() {
    return motifComponent("text", (node) => node.bindings.text(() => `${this.data.count} arşivlendi`));
  },
});

Yayıncı, olayı gönderen bileşenin context'i üzerinden yayınlar; fire'ın ikinci argümanı dinleyicilere veri olarak gider:

<button type="button" onclick={(sender, e) => { notes.archive(note); sender.context.fire("noteArchived", note); }}>Arşivle</button>
const archive = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", (sender: ComponentBase, _e: Event) => { notes.archive(note); sender.context.fire("noteArchived", note); });
  button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Arşivle")));
});
<button type="button" onclick={(sender, e) => { notes.archive(note); sender.context.fire("noteArchived", note); }}>Arşivle</button>
const archive = motifComponent("button", (button) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", (sender, _e) => { notes.archive(note); sender.context.fire("noteArchived", note); });
  button.controls.add(motifComponent("text", (node) => node.setText("Arşivle")));
});

Application olay API'si:

Metot Açıklama
app.on(event, handler) Dinler; abonelikten çıkmak için bir fonksiyon döndürür. İşleyicinin parametresi fire'a verilen veridir ((note?: Note) => …).
app.fire(event, args?) Olayı tetikler.
app.off(event, handler) Dinleyiciyi kaldırır.
app.onRouterChanged(handler) Her navigasyonda (açılış ve 404 dâhil) çalışacak kanca; abonelikten çıkmak için bir fonksiyon döndürür.
app.onLifecycle(handler) Sekme/uygulama görünürlüğü ve bağlantı değişimleri; bkz. Uygulama yaşam döngüsü.

Olay adı dizge ya da Symbol olabilir; fire dinleyicileri ekleniş sırasıyla, senkron çağırır.

Uygulama olayı dinleyicisinin fırlattığı hata MJX123 (The '<olay>' event handler threw.) koduyla raporlanır ve olayın diğer dinleyicileri çalışır. onRouterChanged işleyicileri de böyledir (The 'motifjs-router-navigated' event handler threw.); gezinme yine tamamlanır.

Bir bileşenin context'i uygulamanın bileşene bağlı bir görünümüdür: onun üzerinden açılan on ve onRouterChanged abonelikleri bileşen serbest bırakılınca kendiliğinden kaldırılır (onLifecycle bu kapsama girmez). Application.main, elde tutulan app örneği ya da useApplication().application üzerinden açılan abonelikler hiçbir bileşene bağlı değildir; döndürülen fonksiyonu kendiniz çağırın ya da bir bileşene motif.setDisposable ile kaydedin (Bellek Yönetimi ve Serbest Bırakma):

const unsubscribe = app.on<Note>("noteArchived", (note) => {
  if (note) document.title = `${note.title} arşivlendi`;
});

unsubscribe();
const unsubscribe = app.on("noteArchived", (note) => {
  if (note) document.title = `${note.title} arşivlendi`;
});

unsubscribe();

Servis olayları — Emitter<T>

Uygulama olayları adla çalışır ve uygulamanın tamamına açıktır. Bir servisin kendi olayını tipiyle birlikte yayması için Emitter<T> kullanılır; bileşenden bağımsız, tek tip taşıyan küçük bir yayıcıdır. Örnekteki servis silinen notu bildirir:

import { Emitter, Injectable, reactive } from "@motifx/core";

export interface Note {
  id: number;
  title: string;
  body: string;
  pinned: boolean;
  archived: boolean;
  createdAt: number;
  updatedAt: number;
}

@Injectable({ lifetime: "singleton" })
export class NotesService {
  readonly state = reactive({ items: [] as Note[] });
  private readonly removed = new Emitter<Note>();
  readonly onRemoved = this.removed.event;

  remove(id: number): boolean {
    const index = this.state.items.findIndex((note) => note.id === id);
    if (index < 0) return false;
    const [note] = this.state.items.splice(index, 1);
    this.removed.fire(note);
    return true;
  }

  dispose() {
    this.removed.dispose();
  }
}
import { Emitter, Injectable, reactive } from "@motifx/core";

@Injectable({ lifetime: "singleton" })
export class NotesService {
  state = reactive({ items: [] });
  removed = new Emitter();
  onRemoved = this.removed.event;

  remove(id) {
    const index = this.state.items.findIndex((note) => note.id === id);
    if (index < 0) return false;
    const [note] = this.state.items.splice(index, 1);
    this.removed.fire(note);
    return true;
  }

  dispose() {
    this.removed.dispose();
  }
}

Bir bileşen aboneliği kendi ömrüne motif.register ile bağlar; bileşen serbest bırakılınca abonelik kalkar:

this.motif.register(this.notes.onRemoved((note) => (this.state.removed = note.title)));
list.motif.register(notes.onRemoved((note) => (state.removed = note.title)));
this.motif.register(this.notes.onRemoved((note) => (this.state.removed = note.title)));
list.motif.register(notes.onRemoved((note) => (state.removed = note.title)));

Fonksiyon bileşeninde list, aboneliği taşıyan bileşendir: JSX'te kökün onConfig kancasına ya da ref'ine gelen bileşen, JSX'siz yazımda kökü kuran motifComponent fonksiyonunun aldığı bileşen.

Sonraki adım

Form girdilerini ve iki yönlü bağlamayı öğrenmek için Formlar ve İki Yönlü Bağlama bölümüne geçin.