Başlangıç
MotifJS Dokümantasyonu
MotifJS'in ne yaptığı, bölümlerin dizini ve bir not listesi kesitiyle hızlı bakış.
MotifJS; hızı, reaktiviteyi ve mimari yapıyı tek çatı altında toplayan, modern web uygulamaları için tasarlanmış, JavaScript ve TypeScript için bir UI framework'üdür. Görünüm JSX ile yazılır; derleyici bunu derleme zamanında bileşen çağrılarına çevirir. Aynı çağrılar JSX olmadan, TypeScript ya da JavaScript'te doğrudan da yazılır. JSX'teki her etiket kendi DOM elemanını taşıyan bir bileşen nesnesine dönüşür ve bu nesneler controls ağacında yaşar. Bir getter'a bağlanan değer yalnızca bağlandığı elemanın o özelliğini günceller; bileşen yeniden kurulmaz.
MotifJS neyi farklı yapar?
MotifJS'te arayüz, uygulama açık kaldıkça yaşayan bileşen nesnelerinden kurulu bir ağaçtır. Görünüm bir kez kurulur; sonrasını bu nesneler taşır: bağlar değerleri günceller, ağaç elle ya da bağlarla değişir, ömür açıkça yönetilir.
- Her etiket bir bileşen nesnesidir.
<div>,<button>, hatta düz bir metin derleyicide birmotifComponentçağrısına döner. Ortaya çıkan nesnenin kendielement'i, çocuklarını tutancontrols'ü,bindings,class,attrve olaylarla görünürlüğü yönetenmotifüyeleri vardır. Kurulan eleman bir kopya olarak bırakılmaz; uygulama boyunca elde tutulur,parent,siblingsve$()ile ağaçta bulunur ve değiştirilir. view()bir kez çalışır. Bileşen kurulurken çağrılır, sonraki değişiklikleri bağlar taşır. Kurulmuş bir bileşendecontrols.add(<div/>)çocuğu o anda kurup elemanını yerine koyar; henüz kurulmamış bir bileşende çocuk, sahibiyle birlikte kurulur.- Ağaç çalışma anında düzenlenir.
controls.add,insert,move,moveToIndexveremovebileşen ağacını doğrudan değiştirir. Başka bir bileşenincontrols.add'ine verilen bileşen eski yerinden ayrılıp yeni yerine taşınır; durumu, bağları ve olayları onunla gider.removebileşeni serbest bırakır.detachise varsa çıkış geçişini oynatıp bileşeni yalnızca ağaçtan ve DOM'dan ayırır; ayrılan bileşen sonra yeniden eklenebilir. - Gerçek eleman erişimi. Her bileşenin
element'i yapıcıda oluşur; bileşen kurulmadan ve sayfaya eklenmeden önce de erişilebilir. Kendi elemanı olmayan (fragment) bileşendeelementbir yorum düğümüdür. - Noktasal reaktivite. Bir state alanı değiştiğinde tüm bileşen değil, yalnızca o alana bağlı DOM düğümü/özniteliği güncellenir.
- Bekleyen ve gizlenen bileşen yerini korur.
x-waitkoşulu sağlanana kadar bileşeni hiç kurmaz.x-displayaynı bekletmenin tersidir: baştafalseise bileşen kurulmaz, kurulduktan sonrafalseolursa gizlenir. İkisinde de bileşen,hideStrategy: "detach"verilmedikçe DOM'da bir yorum izi bırakır ve gösterildiğinde aynı yere döner. - Ömür açıkça yönetilir. Serbest bırakılan bileşen çocuklarını, bağlarını ve olaylarını kapatır;
motif.registerya damotif.setDisposableile ona bağlanan kaynaklar da onunla kapanır. Bileşenincontext.on'u ile açılan uygulama aboneliği, bileşen serbest bırakılınca kendiliğinden kalkar. - Servis ömrü rota geçişine bağlanır. Router kullanıldığında her rota geçişi kendi servis kapsamını açar ve o geçişte kurulan bileşenler bu kapsama bağlanır.
scopedkaydedilen servis kapsam başına bir kez oluşur. Yeni geçiş tamamlanınca önceki geçişin kapsamı, saklanan bir sayfa onu tutmuyorsa, servisleriyle birlikte kapanır. - Sayfa her dönüşte yeniden kurulmaz.
keepAlive: trueverilen rotanın bileşeni ilk kurulumdan sonra saklanır ve geri dönüldüğünde aynı örnek gösterilir. Yığın gezinmesi açıkken (app.useRouter({ …, stack: true })) geride kalan sayfalar varsayılan olarak beş adıma kadar tutulur; geri dönüldüğünde aynı örnekler kaydırma konumlarıyla gelir. Kayma geçişi (animation: "slide") ve kenardan kaydırarak geri dönme (swipeBack) seçenektir. - Bütünleşik altyapı. Yönlendirme, bağımlılık enjeksiyonu, yaşam döngüsü, animasyon ve bellek yönetimi çekirdekle birlikte gelir.
Frame,Lazy,Transport/TransportTo,ContentBody/ContentBlock,Virtualization,Resilience,QueryileList,DictionaryveLinkedListkoleksiyonları da çekirdeğin parçasıdır. - JavaScript ya da TypeScript, JSX'li ya da JSX'siz. Bileşenler sınıf, fonksiyon ya da Options API nesnesidir; görünüm JSX ile ya da aynı çağrıların elle yazılmış (imperatif) hâliyle kurulur. Üç biçimin sonucu da aynı türden bir bileşendir ve aynı ağaçta yan yana kullanılır.
MotifJS uzun ömürlü ve durum-yoğun arayüzler için tasarlanmıştır. Sunucu tarafı render (SSR) için henüz bir planlama yapılmadı; ileride değerlendirilebilir.
İçindekiler
| Kategori | Bölüm | Konu |
|---|---|---|
| Başlangıç | Başlarken | Kurulum, build yapılandırması, ilk bileşen ve uygulama |
| Temel kavramlar | Bileşenler | Sınıf / fonksiyon / Options API bileşenleri, kök eleman, view() ve controls, sınıf üyelerinin adları |
| Temel kavramlar | Bileşen Oluşturma | Bir bileşeni tanımlamak, elemanını ve içeriğini vermek, kullanmak; yaratılma ve kurulma |
| Temel kavramlar | Props ve Çocuklar | props, bileşenler arası veri akışı, childs |
| Temel kavramlar | controls ve Bileşen Ağacı | controls koleksiyonu, bileşen ağacında gezinme, içeriği başka bir yere taşıma |
| Temel kavramlar | Eleman ve Görünüş | element, ref, onRefCreated; class, attr, style, setText |
| Temel kavramlar | motif Ad Alanı | show / hide, on / off / trigger, setDisposable, clear, motif.options |
| Temel kavramlar | bindings | JSX'teki canlı ifadelerin karşılığı, bindings üyeleri, watch, bağın ömrü |
| Temel kavramlar | Durum, Bağlam ve Servisler | Durum bayrakları, setState / reState, context, servislere erişim, build / dispose |
| Temel kavramlar | using ile Asenkron Sonuç | Serbest bırakılan bileşene gelen sonucu atmak, işleyiciler, MJX129, denetimin zamanı |
| Temel kavramlar | doWork ile Asenkron Sonuç | Bileşenin ömrüne bağlı söz, MJX108, catch ve finally, using ile farkı |
| Temel kavramlar | JSX ve Şablonlar | JSX kuralları, class/style/öznitelik, spread, x- direktifleri, fragment, derleme tanıları, explain |
| Temel kavramlar | JSX'siz Görünümler | motifComponent ve new Component çağrı biçimleri, props anahtarları, metin düğümleri, preconfig, dal ve liste için fragment, JSX'siz dosyada sınıf bileşeni, derlenmiş çıktıyı okuma |
| Temel kavramlar | Reaktivite | reactive, createSignal, createComputed, createLazyComputed, effect, untracked |
| Temel kavramlar | Koşullu Gösterim ve Listeler | x-wait/x-display, &&, üçlü operatör, .map, key ve öğe kimliği, switch, bağ API'si |
| Temel kavramlar | Olaylar | DOM olayları, değiştiriciler, bileşen, yaşam döngüsü ve uygulama olayları, servis olayları (Emitter) |
| Temel kavramlar | Formlar ve İki Yönlü Bağlama | Tek yönlü value + oninput, bindings.model ve x-model ile iki yönlü bağ, checked, value, <select>, form gönderimi, doğrulama deseni |
| Temel kavramlar | Yaşam Döngüsü | Yaşam döngüsü kancaları ve sıraları, initializeComponent, görünürlük, bekletme, serbest bırakma, uygulama yaşam döngüsü |
| Uygulama yapısı | Stil ve Animasyon | class/style/attr yardımcıları, satır içi <style>, Tailwind, Bootstrap ve harici CSS, geçiş (transition) sistemi: CSS sınıfı ve WAAPI keyframe geçişleri, giriş/çıkış kipi (mode) |
| Uygulama yapısı | Bellek Yönetimi ve Serbest Bırakma | IDisposable, otomatik temizlik, motif.register/setDisposable, serbest bırakma metotları, detach/silentDetach/silentUnlink, DisposableStore, using/doWork, sızıntı izleme, canlı bileşen sayımı |
| Uygulama yapısı | Hata Yönetimi | Fırlatma/rapor/uyarı yolları, MotifError, errorHandler, setUnexpectedErrorHandler, safeCall*, kullanıcı kodunda hatanın yakalandığı yer |
| Uygulama yapısı | Dayanıklılık (Resilience) | Resilience.create zinciri ve sıralama, retry, timeout, circuitBreaker, bulkhead, rateLimiter, fallback, decorate, hata kodları |
| Uygulama yapısı | Pencereler | app.windows: bir bileşeni yeni pencerede ya da hep üstte Picture-in-Picture penceresinde açmak, geri almak, stil ve tema aynalama, Electron |
| Yönlendirme | Yönlendirmeye Giriş | useRouter, gezinme modları, bulunamayan rota ve hata sayfası |
| Yönlendirme | Rota Tablosu | RouteItem alanları, iç içe rotalar, redirect/alias, yol parametreleri, resolve/href/routes, tembel rotalar |
| Yönlendirme | RouterView | Rota çıktısının yeri, iç içe ve adlandırılmış RouterView |
| Yönlendirme | Bağlantılar | rel="router", RouterLink, enableRouterClassing |
| Yönlendirme | Gezinme ve Rota Durumu | navigate/navigateByName, canlı rota durumu, direction/state, scrollMemory |
| Yönlendirme | Rota Kancaları ve Guard'lar | Rota kancaları, useGuard, onRouterChanged, app.use, yeniden başlatma ve kapatma |
| Yönlendirme | Yığın Gezinmesi | stack: tutulan sayfalar, geçiş animasyonu, kaydırarak geri |
| Bağımlılık Enjeksiyonu | Bağımlılık Enjeksiyonuna Giriş | Servisi kaydetmek, bileşenden kullanmak, tam örnek |
| Bağımlılık Enjeksiyonu | Servis Kaydı | ServiceCollection kayıt biçimleri, startup singleton'lar, @Injectable ve seçenekleri |
| Bağımlılık Enjeksiyonu | Yaşam Süreleri ve Kapsamlar | singleton / scoped / transient, gezinme kapsamı, scoped handle |
| Bağımlılık Enjeksiyonu | Servis Çözümleme | provider.get / getAsync, getService, FromService, inject, deps, useApplication |
| Bağımlılık Enjeksiyonu | Servislerin Serbest Bırakılması | app.dispose / provider.dispose, kapsamın kapanması, transient izleme |
| Rehberler | Motif Üstüne Paket Yazmak | Derleyici çıktısının açık API olması; tema, katman, yerleşim, eklenti, veri, sarmalayıcı, ayar ve geçiş paketlerinin dayandığı mekanizmalar |
| Rehberler | Üçüncü Parti Kütüphane Sarmalama | Kendi DOM'unu yöneten kütüphaneleri bileşene dönüştürme |
| Rehberler | MotifJS için npm Kütüphanesi Yayımlama | Bileşen/servis paketi hazırlama: derleme, tipler, CSS, prop sözleşmesi, yayın denetimi |
| Rehberler | Bileşen Kütüphanesi Yazmak | Uygulamaların kullandığı bir kitin kuralları: tek bağla izlenen prop'lar, olduğu gibi kullanılan veri, içeride izlenmeyen durum, elle çizim, değişince yazma, ömür tuzakları, ölçme |
| Rehberler | DevTools sözleşmesi | useDevelopment(true) ile açılan __MOTIF_DEVTOOLS__ nesnesi: ağaç, olaylar, reaktivite izi, sayaçlar, router ve hata kayıtları; paketler için registerDevtools |
| Referans | API Referansı | Dışa aktarılan tüm sembollerin özeti |
| Referans | Hata ve Uyarı Kodları | Bütün MJX kodları, türleri, konsoldaki mesajları ve anlamları |
| Referans | Koleksiyon Sorguları (Query) | Query.from(...) ile zincirlenebilir filtreleme, sıralama, gruplama |
| Referans | Koleksiyonlar | List, Dictionary, NameValuePair, LinkedList |
| Gelişmiş | Frame | Tek değişen içeriği tutan kapsayıcı; zamanlama ve geçişler |
| Gelişmiş | Lazy | Dinamik import() ya da herhangi bir Promise ile içerik; yükleme akışı, seçenekler, yeniden deneme, iptal, hata raporu |
| Gelişmiş | Transport ve TransportTo | İçeriği başka bir yuvada gösterme; replace/merge kipleri, Transporter |
| Gelişmiş | ContentBody ve ContentBlock | Birden çok bloğun birlikte doldurduğu gövde |
| Gelişmiş | Sanal Listeler (Virtualization) | Büyük veri için sanal kaydırma |
Hızlı bakış
Bir not listesi: NotesService notları tek bir yerde tutar; NotesPane arama kutusunu, satırları ve seçili notun düzenleyicisini gösterir. SettingsService tema ve yoğunluk ayarını tutar ve uygulama açılırken kurulur. Aynı bileşen altı biçimde yazılmıştır: sınıf, fonksiyon ve Options API, her biri JSX ile ve JSX'siz.
import { reactive } from "@motifx/core";
export interface Note {
id: number;
title: string;
body: string;
pinned: boolean;
archived: boolean;
createdAt: number;
updatedAt: number;
}
export type Folder = "notes" | "pinned" | "archive";
export const FOLDERS: Record<Folder, { title: string; empty: string }> = {
notes: { title: "Tüm notlar", empty: "Henüz not yok. İlkini siz yazın." },
pinned: { title: "Sabitlenenler", empty: "Sabitlenmiş not yok. Üstte kalmasını istediğiniz notu sabitleyin." },
archive: { title: "Arşiv", empty: "Arşiv boş." },
};
const inFolder: Record<Folder, (note: Note) => boolean> = {
notes: (note) => !note.archived,
pinned: (note) => note.pinned && !note.archived,
archive: (note) => note.archived,
};
export class NotesService {
readonly state = reactive({ items: [] as Note[] });
private nextId = this.state.items.length + 1;
list(folder: Folder, query = ""): Note[] {
const q = query.trim().toLowerCase();
return this.state.items
.filter(inFolder[folder])
.filter((note) => !q || `${note.title}\n${note.body}`.toLowerCase().includes(q))
.sort((a, b) => Number(b.pinned) - Number(a.pinned) || b.updatedAt - a.updatedAt);
}
count(folder: Folder): number {
return this.state.items.filter(inFolder[folder]).length;
}
find(id: number | string): Note | undefined {
const wanted = Number(id);
return this.state.items.find((note) => note.id === wanted);
}
create(): Note {
const now = Date.now();
this.state.items.unshift({ id: this.nextId++, title: "", body: "", pinned: false, archived: false, createdAt: now, updatedAt: now });
return this.state.items[0];
}
touch(note: Note): void {
note.updatedAt = Date.now();
}
togglePin(note: Note): void {
note.pinned = !note.pinned;
this.touch(note);
}
archive(note: Note): void {
note.archived = true;
note.pinned = false;
this.touch(note);
}
restore(note: Note): void {
note.archived = false;
this.touch(note);
}
remove(id: number): boolean {
const index = this.state.items.findIndex((note) => note.id === id);
if (index < 0) return false;
this.state.items.splice(index, 1);
return true;
}
emptyArchive(): number {
let removed = 0;
for (let i = this.state.items.length - 1; i >= 0; i--) {
if (!this.state.items[i].archived) continue;
this.state.items.splice(i, 1);
removed++;
}
return removed;
}
}
import { reactive } from "@motifx/core";
export const FOLDERS = {
notes: { title: "Tüm notlar", empty: "Henüz not yok. İlkini siz yazın." },
pinned: { title: "Sabitlenenler", empty: "Sabitlenmiş not yok. Üstte kalmasını istediğiniz notu sabitleyin." },
archive: { title: "Arşiv", empty: "Arşiv boş." },
};
const inFolder = {
notes: (note) => !note.archived,
pinned: (note) => note.pinned && !note.archived,
archive: (note) => note.archived,
};
export class NotesService {
state = reactive({ items: [] });
nextId = this.state.items.length + 1;
list(folder, query = "") {
const q = query.trim().toLowerCase();
return this.state.items
.filter(inFolder[folder])
.filter((note) => !q || `${note.title}\n${note.body}`.toLowerCase().includes(q))
.sort((a, b) => Number(b.pinned) - Number(a.pinned) || b.updatedAt - a.updatedAt);
}
count(folder) {
return this.state.items.filter(inFolder[folder]).length;
}
find(id) {
const wanted = Number(id);
return this.state.items.find((note) => note.id === wanted);
}
create() {
const now = Date.now();
this.state.items.unshift({ id: this.nextId++, title: "", body: "", pinned: false, archived: false, createdAt: now, updatedAt: now });
return this.state.items[0];
}
touch(note) {
note.updatedAt = Date.now();
}
togglePin(note) {
note.pinned = !note.pinned;
this.touch(note);
}
archive(note) {
note.archived = true;
note.pinned = false;
this.touch(note);
}
restore(note) {
note.archived = false;
this.touch(note);
}
remove(id) {
const index = this.state.items.findIndex((note) => note.id === id);
if (index < 0) return false;
this.state.items.splice(index, 1);
return true;
}
emptyArchive() {
let removed = 0;
for (let i = this.state.items.length - 1; i >= 0; i--) {
if (!this.state.items[i].archived) continue;
this.state.items.splice(i, 1);
removed++;
}
return removed;
}
}
import { reactive } from "@motifx/core";
export type Theme = "system" | "light" | "dark";
export type Density = "comfortable" | "compact";
export const THEMES: readonly Theme[] = ["system", "light", "dark"];
export const DENSITIES: readonly Density[] = ["comfortable", "compact"];
const STORAGE_KEY = "settings";
export class SettingsService {
readonly state = reactive({ theme: "system" as Theme, density: "comfortable" as Density });
constructor() {
try {
const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? "{}");
if (THEMES.includes(saved.theme)) this.state.theme = saved.theme;
if (DENSITIES.includes(saved.density)) this.state.density = saved.density;
} catch {}
this.apply();
}
setTheme(theme: Theme) {
this.state.theme = theme;
this.save();
}
setDensity(density: Density) {
this.state.density = density;
this.save();
}
private save() {
this.apply();
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({ theme: this.state.theme, density: this.state.density }));
} catch {}
}
private apply() {
const root = document.documentElement;
if (this.state.theme === "system") delete root.dataset.theme;
else root.dataset.theme = this.state.theme;
if (this.state.density === "comfortable") delete root.dataset.density;
else root.dataset.density = this.state.density;
}
}
import { reactive } from "@motifx/core";
export const THEMES = ["system", "light", "dark"];
export const DENSITIES = ["comfortable", "compact"];
const STORAGE_KEY = "settings";
export class SettingsService {
state = reactive({ theme: "system", density: "comfortable" });
constructor() {
try {
const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) ?? "{}");
if (THEMES.includes(saved.theme)) this.state.theme = saved.theme;
if (DENSITIES.includes(saved.density)) this.state.density = saved.density;
} catch {}
this.apply();
}
setTheme(theme) {
this.state.theme = theme;
this.save();
}
setDensity(density) {
this.state.density = density;
this.save();
}
save() {
this.apply();
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({ theme: this.state.theme, density: this.state.density }));
} catch {}
}
apply() {
const root = document.documentElement;
if (this.state.theme === "system") delete root.dataset.theme;
else root.dataset.theme = this.state.theme;
if (this.state.density === "comfortable") delete root.dataset.density;
else root.dataset.density = this.state.density;
}
}
import { Component, reactive } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export class NotesPane extends Component {
notes = this.getService(NotesService)!;
state = reactive({ query: "", current: null as Note | null });
get visible() {
return this.notes.list("notes", this.state.query);
}
create() {
this.state.current = this.notes.create();
}
override view() {
return (
<div class="notes">
<header class="list__head">
<h2>Notlar</h2>
<button type="button" onclick={() => this.create()}>Yeni not</button>
</header>
<input type="search" placeholder="Notlarda ara" x-model={() => this.state.query} />
<div class="rows">
{this.visible.map((note) => (
<button type="button" key={note.id} class={() => (note === this.state.current ? "row active" : "row")} onclick={() => (this.state.current = note)}>
<i class="pin" x-display={() => note.pinned} />
<span>{() => note.title.trim() || "Adsız"}</span>
</button>
))}
<p class="list__empty" x-display={() => this.visible.length === 0}>
{() => (this.state.query.trim() ? "Arama sonucu yok." : "Henüz not yok.")}
</p>
</div>
<article class="detail" x-display={() => this.state.current !== null}>
<input placeholder="Başlık" x-model={() => this.state.current!.title} />
<textarea placeholder="Not" x-model={() => this.state.current!.body} />
<button type="button" onclick={() => this.notes.togglePin(this.state.current!)}>
{() => (this.state.current?.pinned ? "Sabitlemeyi kaldır" : "Sabitle")}
</button>
</article>
</div>
);
}
}
import { FromService, reactive } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export function NotesPane() {
const notes = FromService(NotesService)!;
const state = reactive({ query: "", current: null as Note | null });
const visible = () => notes.list("notes", state.query);
const create = () => (state.current = notes.create());
return (
<div class="notes">
<header class="list__head">
<h2>Notlar</h2>
<button type="button" onclick={create}>Yeni not</button>
</header>
<input type="search" placeholder="Notlarda ara" x-model={() => state.query} />
<div class="rows">
{visible().map((note) => (
<button type="button" key={note.id} class={() => (note === state.current ? "row active" : "row")} onclick={() => (state.current = note)}>
<i class="pin" x-display={() => note.pinned} />
<span>{() => note.title.trim() || "Adsız"}</span>
</button>
))}
<p class="list__empty" x-display={() => visible().length === 0}>
{() => (state.query.trim() ? "Arama sonucu yok." : "Henüz not yok.")}
</p>
</div>
<article class="detail" x-display={() => state.current !== null}>
<input placeholder="Başlık" x-model={() => state.current!.title} />
<textarea placeholder="Not" x-model={() => state.current!.body} />
<button type="button" onclick={() => notes.togglePin(state.current!)}>
{() => (state.current?.pinned ? "Sabitlemeyi kaldır" : "Sabitle")}
</button>
</article>
</div>
);
}
import { FromService, motifOptions, reactive } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export const NotesPane = () => {
const notes = FromService(NotesService)!;
return motifOptions({
el: "div",
data: reactive({ query: "", current: null as Note | null }),
visible() {
return notes.list("notes", this.data.query);
},
create() {
this.data.current = notes.create();
},
view() {
this.class.add("notes");
return (
<>
<header class="list__head">
<h2>Notlar</h2>
<button type="button" onclick={() => this.create()}>Yeni not</button>
</header>
<input type="search" placeholder="Notlarda ara" x-model={() => this.data.query} />
<div class="rows">
{this.visible().map((note) => (
<button type="button" key={note.id} class={() => (note === this.data.current ? "row active" : "row")} onclick={() => (this.data.current = note)}>
<i class="pin" x-display={() => note.pinned} />
<span>{() => note.title.trim() || "Adsız"}</span>
</button>
))}
<p class="list__empty" x-display={() => this.visible().length === 0}>
{() => (this.data.query.trim() ? "Arama sonucu yok." : "Henüz not yok.")}
</p>
</div>
<article class="detail" x-display={() => this.data.current !== null}>
<input placeholder="Başlık" x-model={() => this.data.current!.title} />
<textarea placeholder="Not" x-model={() => this.data.current!.body} />
<button type="button" onclick={() => notes.togglePin(this.data.current!)}>
{() => (this.data.current?.pinned ? "Sabitlemeyi kaldır" : "Sabitle")}
</button>
</article>
</>
);
},
});
};
import { Component, ComponentBase, motifComponent, reactive } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export class NotesPane extends Component {
notes = this.getService(NotesService)!;
state = reactive({ query: "", current: null as Note | null });
get visible() {
return this.notes.list("notes", this.state.query);
}
create() {
this.state.current = this.notes.create();
}
override view() {
return motifComponent("div", (root: ComponentBase) => {
root.class.add("notes");
root.controls.add(this.head(), this.search(), this.rows(), this.detail());
});
}
private head() {
return motifComponent("header", (head: ComponentBase) => {
head.class.add("list__head");
const title = motifComponent("h2", (h2: ComponentBase) => {
h2.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Notlar")));
});
const newNote = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.motif.on("click", () => this.create());
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Yeni not")));
});
head.controls.add(title, newNote);
});
}
private search() {
return motifComponent("input", (field: ComponentBase) => {
field.attr.add({ type: "search", placeholder: "Notlarda ara" });
field.bindings.model(() => this.state.query, (value: string) => (this.state.query = value));
});
}
private rows() {
return motifComponent("div", (rows: ComponentBase) => {
rows.class.add("rows");
const list = motifComponent({
initializeComponent: (holder: ComponentBase) => {
holder.bindings.list(() => this.visible, (note: Note) => this.row(note));
},
});
const empty = motifComponent("p", {
preconfig: (p: ComponentBase) => p.bindings.display(() => this.visible.length === 0),
initializeComponent: (p: ComponentBase) => {
p.class.add("list__empty");
p.bindings.text(() => (this.state.query.trim() ? "Arama sonucu yok." : "Henüz not yok."));
},
});
rows.controls.add(list, empty);
});
}
private row(note: Note) {
return motifComponent("button", {
indexkey: () => note.id,
initializeComponent: (row: ComponentBase) => {
row.attr.add({ type: "button" });
row.class.add(() => (note === this.state.current ? "row active" : "row"));
row.motif.on("click", () => (this.state.current = note));
const pin = motifComponent("i", {
preconfig: (i: ComponentBase) => i.bindings.display(() => note.pinned),
initializeComponent: (i: ComponentBase) => i.class.add("pin"),
});
const title = motifComponent("span", (span: ComponentBase) => span.bindings.text(() => note.title.trim() || "Adsız"));
row.controls.add(pin, title);
},
});
}
private detail() {
return motifComponent("article", {
preconfig: (article: ComponentBase) => article.bindings.display(() => this.state.current !== null),
initializeComponent: (article: ComponentBase) => {
article.class.add("detail");
const title = motifComponent("input", (field: ComponentBase) => {
field.attr.add({ placeholder: "Başlık" });
field.bindings.model(() => this.state.current!.title, (value: string) => (this.state.current!.title = value));
});
const body = motifComponent("textarea", (field: ComponentBase) => {
field.attr.add({ placeholder: "Not" });
field.bindings.model(() => this.state.current!.body, (value: string) => (this.state.current!.body = value));
});
const pin = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.motif.on("click", () => this.notes.togglePin(this.state.current!));
button.bindings.text(() => (this.state.current?.pinned ? "Sabitlemeyi kaldır" : "Sabitle"));
});
article.controls.add(title, body, pin);
},
});
}
}
import { ComponentBase, FromService, motifComponent, reactive } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export function NotesPane() {
const notes = FromService(NotesService)!;
const state = reactive({ query: "", current: null as Note | null });
const visible = () => notes.list("notes", state.query);
const create = () => (state.current = notes.create());
const head = motifComponent("header", (bar: ComponentBase) => {
bar.class.add("list__head");
const title = motifComponent("h2", (h2: ComponentBase) => {
h2.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Notlar")));
});
const newNote = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.motif.on("click", create);
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Yeni not")));
});
bar.controls.add(title, newNote);
});
const search = motifComponent("input", (field: ComponentBase) => {
field.attr.add({ type: "search", placeholder: "Notlarda ara" });
field.bindings.model(() => state.query, (value: string) => (state.query = value));
});
const row = (note: Note) =>
motifComponent("button", {
indexkey: () => note.id,
initializeComponent: (item: ComponentBase) => {
item.attr.add({ type: "button" });
item.class.add(() => (note === state.current ? "row active" : "row"));
item.motif.on("click", () => (state.current = note));
const pin = motifComponent("i", {
preconfig: (i: ComponentBase) => i.bindings.display(() => note.pinned),
initializeComponent: (i: ComponentBase) => i.class.add("pin"),
});
const title = motifComponent("span", (span: ComponentBase) => span.bindings.text(() => note.title.trim() || "Adsız"));
item.controls.add(pin, title);
},
});
const rows = motifComponent("div", (box: ComponentBase) => {
box.class.add("rows");
const list = motifComponent({
initializeComponent: (holder: ComponentBase) => holder.bindings.list(visible, row),
});
const empty = motifComponent("p", {
preconfig: (p: ComponentBase) => p.bindings.display(() => visible().length === 0),
initializeComponent: (p: ComponentBase) => {
p.class.add("list__empty");
p.bindings.text(() => (state.query.trim() ? "Arama sonucu yok." : "Henüz not yok."));
},
});
box.controls.add(list, empty);
});
const detail = motifComponent("article", {
preconfig: (article: ComponentBase) => article.bindings.display(() => state.current !== null),
initializeComponent: (article: ComponentBase) => {
article.class.add("detail");
const title = motifComponent("input", (field: ComponentBase) => {
field.attr.add({ placeholder: "Başlık" });
field.bindings.model(() => state.current!.title, (value: string) => (state.current!.title = value));
});
const body = motifComponent("textarea", (field: ComponentBase) => {
field.attr.add({ placeholder: "Not" });
field.bindings.model(() => state.current!.body, (value: string) => (state.current!.body = value));
});
const pin = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.motif.on("click", () => notes.togglePin(state.current!));
button.bindings.text(() => (state.current?.pinned ? "Sabitlemeyi kaldır" : "Sabitle"));
});
article.controls.add(title, body, pin);
},
});
return motifComponent("div", (root: ComponentBase) => {
root.class.add("notes");
root.controls.add(head, search, rows, detail);
});
}
import { ComponentBase, FromService, motifComponent, motifOptions, reactive } from "@motifx/core";
import { NotesService, type Note } from "../services/notes";
export const NotesPane = () => {
const notes = FromService(NotesService)!;
return motifOptions({
el: "div",
data: reactive({ query: "", current: null as Note | null }),
visible() {
return notes.list("notes", this.data.query);
},
create() {
this.data.current = notes.create();
},
view() {
this.class.add("notes");
return [this.head(), this.search(), this.rows(), this.detail()];
},
head() {
return motifComponent("header", (head: ComponentBase) => {
head.class.add("list__head");
const title = motifComponent("h2", (h2: ComponentBase) => {
h2.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Notlar")));
});
const newNote = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.motif.on("click", () => this.create());
button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Yeni not")));
});
head.controls.add(title, newNote);
});
},
search() {
return motifComponent("input", (field: ComponentBase) => {
field.attr.add({ type: "search", placeholder: "Notlarda ara" });
field.bindings.model(() => this.data.query, (value: string) => (this.data.query = value));
});
},
rows() {
return motifComponent("div", (rows: ComponentBase) => {
rows.class.add("rows");
const list = motifComponent({
initializeComponent: (holder: ComponentBase) => {
holder.bindings.list(() => this.visible(), (note: Note) => this.row(note));
},
});
const empty = motifComponent("p", {
preconfig: (p: ComponentBase) => p.bindings.display(() => this.visible().length === 0),
initializeComponent: (p: ComponentBase) => {
p.class.add("list__empty");
p.bindings.text(() => (this.data.query.trim() ? "Arama sonucu yok." : "Henüz not yok."));
},
});
rows.controls.add(list, empty);
});
},
row(note: Note) {
return motifComponent("button", {
indexkey: () => note.id,
initializeComponent: (row: ComponentBase) => {
row.attr.add({ type: "button" });
row.class.add(() => (note === this.data.current ? "row active" : "row"));
row.motif.on("click", () => (this.data.current = note));
const pin = motifComponent("i", {
preconfig: (i: ComponentBase) => i.bindings.display(() => note.pinned),
initializeComponent: (i: ComponentBase) => i.class.add("pin"),
});
const title = motifComponent("span", (span: ComponentBase) => span.bindings.text(() => note.title.trim() || "Adsız"));
row.controls.add(pin, title);
},
});
},
detail() {
return motifComponent("article", {
preconfig: (article: ComponentBase) => article.bindings.display(() => this.data.current !== null),
initializeComponent: (article: ComponentBase) => {
article.class.add("detail");
const title = motifComponent("input", (field: ComponentBase) => {
field.attr.add({ placeholder: "Başlık" });
field.bindings.model(() => this.data.current!.title, (value: string) => (this.data.current!.title = value));
});
const body = motifComponent("textarea", (field: ComponentBase) => {
field.attr.add({ placeholder: "Not" });
field.bindings.model(() => this.data.current!.body, (value: string) => (this.data.current!.body = value));
});
const pin = motifComponent("button", (button: ComponentBase) => {
button.attr.add({ type: "button" });
button.motif.on("click", () => notes.togglePin(this.data.current!));
button.bindings.text(() => (this.data.current?.pinned ? "Sabitlemeyi kaldır" : "Sabitle"));
});
article.controls.add(title, body, pin);
},
});
},
});
};
import { Component, reactive } from "@motifx/core";
import { NotesService } from "../services/notes";
export class NotesPane extends Component {
notes = this.getService(NotesService);
state = reactive({ query: "", current: null });
get visible() {
return this.notes.list("notes", this.state.query);
}
create() {
this.state.current = this.notes.create();
}
view() {
return (
<div class="notes">
<header class="list__head">
<h2>Notlar</h2>
<button type="button" onclick={() => this.create()}>Yeni not</button>
</header>
<input type="search" placeholder="Notlarda ara" x-model={() => this.state.query} />
<div class="rows">
{this.visible.map((note) => (
<button type="button" key={note.id} class={() => (note === this.state.current ? "row active" : "row")} onclick={() => (this.state.current = note)}>
<i class="pin" x-display={() => note.pinned} />
<span>{() => note.title.trim() || "Adsız"}</span>
</button>
))}
<p class="list__empty" x-display={() => this.visible.length === 0}>
{() => (this.state.query.trim() ? "Arama sonucu yok." : "Henüz not yok.")}
</p>
</div>
<article class="detail" x-display={() => this.state.current !== null}>
<input placeholder="Başlık" x-model={() => this.state.current.title} />
<textarea placeholder="Not" x-model={() => this.state.current.body} />
<button type="button" onclick={() => this.notes.togglePin(this.state.current)}>
{() => (this.state.current?.pinned ? "Sabitlemeyi kaldır" : "Sabitle")}
</button>
</article>
</div>
);
}
}
import { FromService, reactive } from "@motifx/core";
import { NotesService } from "../services/notes";
export function NotesPane() {
const notes = FromService(NotesService);
const state = reactive({ query: "", current: null });
const visible = () => notes.list("notes", state.query);
const create = () => (state.current = notes.create());
return (
<div class="notes">
<header class="list__head">
<h2>Notlar</h2>
<button type="button" onclick={create}>Yeni not</button>
</header>
<input type="search" placeholder="Notlarda ara" x-model={() => state.query} />
<div class="rows">
{visible().map((note) => (
<button type="button" key={note.id} class={() => (note === state.current ? "row active" : "row")} onclick={() => (state.current = note)}>
<i class="pin" x-display={() => note.pinned} />
<span>{() => note.title.trim() || "Adsız"}</span>
</button>
))}
<p class="list__empty" x-display={() => visible().length === 0}>
{() => (state.query.trim() ? "Arama sonucu yok." : "Henüz not yok.")}
</p>
</div>
<article class="detail" x-display={() => state.current !== null}>
<input placeholder="Başlık" x-model={() => state.current.title} />
<textarea placeholder="Not" x-model={() => state.current.body} />
<button type="button" onclick={() => notes.togglePin(state.current)}>
{() => (state.current?.pinned ? "Sabitlemeyi kaldır" : "Sabitle")}
</button>
</article>
</div>
);
}
import { FromService, motifOptions, reactive } from "@motifx/core";
import { NotesService } from "../services/notes";
export const NotesPane = () => {
const notes = FromService(NotesService);
return motifOptions({
el: "div",
data: reactive({ query: "", current: null }),
visible() {
return notes.list("notes", this.data.query);
},
create() {
this.data.current = notes.create();
},
view() {
this.class.add("notes");
return (
<>
<header class="list__head">
<h2>Notlar</h2>
<button type="button" onclick={() => this.create()}>Yeni not</button>
</header>
<input type="search" placeholder="Notlarda ara" x-model={() => this.data.query} />
<div class="rows">
{this.visible().map((note) => (
<button type="button" key={note.id} class={() => (note === this.data.current ? "row active" : "row")} onclick={() => (this.data.current = note)}>
<i class="pin" x-display={() => note.pinned} />
<span>{() => note.title.trim() || "Adsız"}</span>
</button>
))}
<p class="list__empty" x-display={() => this.visible().length === 0}>
{() => (this.data.query.trim() ? "Arama sonucu yok." : "Henüz not yok.")}
</p>
</div>
<article class="detail" x-display={() => this.data.current !== null}>
<input placeholder="Başlık" x-model={() => this.data.current.title} />
<textarea placeholder="Not" x-model={() => this.data.current.body} />
<button type="button" onclick={() => notes.togglePin(this.data.current)}>
{() => (this.data.current?.pinned ? "Sabitlemeyi kaldır" : "Sabitle")}
</button>
</article>
</>
);
},
});
};
import { Component, motifComponent, reactive } from "@motifx/core";
import { NotesService } from "../services/notes";
export class NotesPane extends Component {
notes = this.getService(NotesService);
state = reactive({ query: "", current: null });
get visible() {
return this.notes.list("notes", this.state.query);
}
create() {
this.state.current = this.notes.create();
}
view() {
return motifComponent("div", (root) => {
root.class.add("notes");
root.controls.add(this.head(), this.search(), this.rows(), this.detail());
});
}
head() {
return motifComponent("header", (head) => {
head.class.add("list__head");
const title = motifComponent("h2", (h2) => {
h2.controls.add(motifComponent("text", (node) => node.setText("Notlar")));
});
const newNote = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.motif.on("click", () => this.create());
button.controls.add(motifComponent("text", (node) => node.setText("Yeni not")));
});
head.controls.add(title, newNote);
});
}
search() {
return motifComponent("input", (field) => {
field.attr.add({ type: "search", placeholder: "Notlarda ara" });
field.bindings.model(() => this.state.query, (value) => (this.state.query = value));
});
}
rows() {
return motifComponent("div", (rows) => {
rows.class.add("rows");
const list = motifComponent({
initializeComponent: (holder) => {
holder.bindings.list(() => this.visible, (note) => this.row(note));
},
});
const empty = motifComponent("p", {
preconfig: (p) => p.bindings.display(() => this.visible.length === 0),
initializeComponent: (p) => {
p.class.add("list__empty");
p.bindings.text(() => (this.state.query.trim() ? "Arama sonucu yok." : "Henüz not yok."));
},
});
rows.controls.add(list, empty);
});
}
row(note) {
return motifComponent("button", {
indexkey: () => note.id,
initializeComponent: (row) => {
row.attr.add({ type: "button" });
row.class.add(() => (note === this.state.current ? "row active" : "row"));
row.motif.on("click", () => (this.state.current = note));
const pin = motifComponent("i", {
preconfig: (i) => i.bindings.display(() => note.pinned),
initializeComponent: (i) => i.class.add("pin"),
});
const title = motifComponent("span", (span) => span.bindings.text(() => note.title.trim() || "Adsız"));
row.controls.add(pin, title);
},
});
}
detail() {
return motifComponent("article", {
preconfig: (article) => article.bindings.display(() => this.state.current !== null),
initializeComponent: (article) => {
article.class.add("detail");
const title = motifComponent("input", (field) => {
field.attr.add({ placeholder: "Başlık" });
field.bindings.model(() => this.state.current.title, (value) => (this.state.current.title = value));
});
const body = motifComponent("textarea", (field) => {
field.attr.add({ placeholder: "Not" });
field.bindings.model(() => this.state.current.body, (value) => (this.state.current.body = value));
});
const pin = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.motif.on("click", () => this.notes.togglePin(this.state.current));
button.bindings.text(() => (this.state.current?.pinned ? "Sabitlemeyi kaldır" : "Sabitle"));
});
article.controls.add(title, body, pin);
},
});
}
}
import { FromService, motifComponent, reactive } from "@motifx/core";
import { NotesService } from "../services/notes";
export function NotesPane() {
const notes = FromService(NotesService);
const state = reactive({ query: "", current: null });
const visible = () => notes.list("notes", state.query);
const create = () => (state.current = notes.create());
const head = motifComponent("header", (bar) => {
bar.class.add("list__head");
const title = motifComponent("h2", (h2) => {
h2.controls.add(motifComponent("text", (node) => node.setText("Notlar")));
});
const newNote = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.motif.on("click", create);
button.controls.add(motifComponent("text", (node) => node.setText("Yeni not")));
});
bar.controls.add(title, newNote);
});
const search = motifComponent("input", (field) => {
field.attr.add({ type: "search", placeholder: "Notlarda ara" });
field.bindings.model(() => state.query, (value) => (state.query = value));
});
const row = (note) =>
motifComponent("button", {
indexkey: () => note.id,
initializeComponent: (item) => {
item.attr.add({ type: "button" });
item.class.add(() => (note === state.current ? "row active" : "row"));
item.motif.on("click", () => (state.current = note));
const pin = motifComponent("i", {
preconfig: (i) => i.bindings.display(() => note.pinned),
initializeComponent: (i) => i.class.add("pin"),
});
const title = motifComponent("span", (span) => span.bindings.text(() => note.title.trim() || "Adsız"));
item.controls.add(pin, title);
},
});
const rows = motifComponent("div", (box) => {
box.class.add("rows");
const list = motifComponent({
initializeComponent: (holder) => holder.bindings.list(visible, row),
});
const empty = motifComponent("p", {
preconfig: (p) => p.bindings.display(() => visible().length === 0),
initializeComponent: (p) => {
p.class.add("list__empty");
p.bindings.text(() => (state.query.trim() ? "Arama sonucu yok." : "Henüz not yok."));
},
});
box.controls.add(list, empty);
});
const detail = motifComponent("article", {
preconfig: (article) => article.bindings.display(() => state.current !== null),
initializeComponent: (article) => {
article.class.add("detail");
const title = motifComponent("input", (field) => {
field.attr.add({ placeholder: "Başlık" });
field.bindings.model(() => state.current.title, (value) => (state.current.title = value));
});
const body = motifComponent("textarea", (field) => {
field.attr.add({ placeholder: "Not" });
field.bindings.model(() => state.current.body, (value) => (state.current.body = value));
});
const pin = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.motif.on("click", () => notes.togglePin(state.current));
button.bindings.text(() => (state.current?.pinned ? "Sabitlemeyi kaldır" : "Sabitle"));
});
article.controls.add(title, body, pin);
},
});
return motifComponent("div", (root) => {
root.class.add("notes");
root.controls.add(head, search, rows, detail);
});
}
import { FromService, motifComponent, motifOptions, reactive } from "@motifx/core";
import { NotesService } from "../services/notes";
export const NotesPane = () => {
const notes = FromService(NotesService);
return motifOptions({
el: "div",
data: reactive({ query: "", current: null }),
visible() {
return notes.list("notes", this.data.query);
},
create() {
this.data.current = notes.create();
},
view() {
this.class.add("notes");
return [this.head(), this.search(), this.rows(), this.detail()];
},
head() {
return motifComponent("header", (head) => {
head.class.add("list__head");
const title = motifComponent("h2", (h2) => {
h2.controls.add(motifComponent("text", (node) => node.setText("Notlar")));
});
const newNote = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.motif.on("click", () => this.create());
button.controls.add(motifComponent("text", (node) => node.setText("Yeni not")));
});
head.controls.add(title, newNote);
});
},
search() {
return motifComponent("input", (field) => {
field.attr.add({ type: "search", placeholder: "Notlarda ara" });
field.bindings.model(() => this.data.query, (value) => (this.data.query = value));
});
},
rows() {
return motifComponent("div", (rows) => {
rows.class.add("rows");
const list = motifComponent({
initializeComponent: (holder) => {
holder.bindings.list(() => this.visible(), (note) => this.row(note));
},
});
const empty = motifComponent("p", {
preconfig: (p) => p.bindings.display(() => this.visible().length === 0),
initializeComponent: (p) => {
p.class.add("list__empty");
p.bindings.text(() => (this.data.query.trim() ? "Arama sonucu yok." : "Henüz not yok."));
},
});
rows.controls.add(list, empty);
});
},
row(note) {
return motifComponent("button", {
indexkey: () => note.id,
initializeComponent: (row) => {
row.attr.add({ type: "button" });
row.class.add(() => (note === this.data.current ? "row active" : "row"));
row.motif.on("click", () => (this.data.current = note));
const pin = motifComponent("i", {
preconfig: (i) => i.bindings.display(() => note.pinned),
initializeComponent: (i) => i.class.add("pin"),
});
const title = motifComponent("span", (span) => span.bindings.text(() => note.title.trim() || "Adsız"));
row.controls.add(pin, title);
},
});
},
detail() {
return motifComponent("article", {
preconfig: (article) => article.bindings.display(() => this.data.current !== null),
initializeComponent: (article) => {
article.class.add("detail");
const title = motifComponent("input", (field) => {
field.attr.add({ placeholder: "Başlık" });
field.bindings.model(() => this.data.current.title, (value) => (this.data.current.title = value));
});
const body = motifComponent("textarea", (field) => {
field.attr.add({ placeholder: "Not" });
field.bindings.model(() => this.data.current.body, (value) => (this.data.current.body = value));
});
const pin = motifComponent("button", (button) => {
button.attr.add({ type: "button" });
button.motif.on("click", () => notes.togglePin(this.data.current));
button.bindings.text(() => (this.data.current?.pinned ? "Sabitlemeyi kaldır" : "Sabitle"));
});
article.controls.add(title, body, pin);
},
});
},
});
};
import { Application } from "@motifx/core";
import { NotesPane } from "./pages/NotesPane";
import { NotesService } from "./services/notes";
import { SettingsService } from "./services/settings";
const builder = Application.CreateBuilder();
builder.services.addSingleton(NotesService, { useClass: NotesService });
builder.services.addSingleton(SettingsService, { useClass: SettingsService, startup: true });
const app = builder.build();
app.run("#app", new NotesPane());
import { Application } from "@motifx/core";
import { NotesPane } from "./pages/NotesPane";
import { NotesService } from "./services/notes";
import { SettingsService } from "./services/settings";
const builder = Application.CreateBuilder();
builder.services.addSingleton(NotesService, { useClass: NotesService });
builder.services.addSingleton(SettingsService, { useClass: SettingsService, startup: true });
const app = builder.build();
app.run("#app", <NotesPane />);
import { Application } from "@motifx/core";
import { NotesPane } from "./pages/NotesPane";
import { NotesService } from "./services/notes";
import { SettingsService } from "./services/settings";
const builder = Application.CreateBuilder();
builder.services.addSingleton(NotesService, { useClass: NotesService });
builder.services.addSingleton(SettingsService, { useClass: SettingsService, startup: true });
const app = builder.build();
app.run("#app", new NotesPane());
import { Application } from "@motifx/core";
import { NotesPane } from "./pages/NotesPane";
import { NotesService } from "./services/notes";
import { SettingsService } from "./services/settings";
const builder = Application.CreateBuilder();
builder.services.addSingleton(NotesService, { useClass: NotesService });
builder.services.addSingleton(SettingsService, { useClass: SettingsService, startup: true });
const app = builder.build();
app.run("#app", NotesPane());
import { Application, motifComponent } from "@motifx/core";
import { NotesPane } from "./pages/NotesPane";
import { NotesService } from "./services/notes";
import { SettingsService } from "./services/settings";
const builder = Application.CreateBuilder();
builder.services.addSingleton(NotesService, { useClass: NotesService });
builder.services.addSingleton(SettingsService, { useClass: SettingsService, startup: true });
const app = builder.build();
app.run("#app", motifComponent(NotesPane));
import { Application } from "@motifx/core";
import { NotesPane } from "./pages/NotesPane";
import { NotesService } from "./services/notes";
import { SettingsService } from "./services/settings";
const builder = Application.CreateBuilder();
builder.services.addSingleton(NotesService, { useClass: NotesService });
builder.services.addSingleton(SettingsService, { useClass: SettingsService, startup: true });
const app = builder.build();
app.run("#app", new NotesPane());
import { Application } from "@motifx/core";
import { NotesPane } from "./pages/NotesPane";
import { NotesService } from "./services/notes";
import { SettingsService } from "./services/settings";
const builder = Application.CreateBuilder();
builder.services.addSingleton(NotesService, { useClass: NotesService });
builder.services.addSingleton(SettingsService, { useClass: SettingsService, startup: true });
const app = builder.build();
app.run("#app", <NotesPane />);
import { Application } from "@motifx/core";
import { NotesPane } from "./pages/NotesPane";
import { NotesService } from "./services/notes";
import { SettingsService } from "./services/settings";
const builder = Application.CreateBuilder();
builder.services.addSingleton(NotesService, { useClass: NotesService });
builder.services.addSingleton(SettingsService, { useClass: SettingsService, startup: true });
const app = builder.build();
app.run("#app", new NotesPane());
import { Application } from "@motifx/core";
import { NotesPane } from "./pages/NotesPane";
import { NotesService } from "./services/notes";
import { SettingsService } from "./services/settings";
const builder = Application.CreateBuilder();
builder.services.addSingleton(NotesService, { useClass: NotesService });
builder.services.addSingleton(SettingsService, { useClass: SettingsService, startup: true });
const app = builder.build();
app.run("#app", NotesPane());
import { Application, motifComponent } from "@motifx/core";
import { NotesPane } from "./pages/NotesPane";
import { NotesService } from "./services/notes";
import { SettingsService } from "./services/settings";
const builder = Application.CreateBuilder();
builder.services.addSingleton(NotesService, { useClass: NotesService });
builder.services.addSingleton(SettingsService, { useClass: SettingsService, startup: true });
const app = builder.build();
app.run("#app", motifComponent(NotesPane));
Örnekte dikkat çeken noktalar:
- Her not tek bir reaktif nesnedir. Düzenleyicide başlık değişince satırdaki metin, sabitleme değişince satırdaki simge güncellenir; liste yeniden kurulmaz.
NotesService.createlisteye başa ekler, satırlar bunu kendiliğinden izler. - Bağlar noktasaldır.
x-model/bindings.modeliki yönlü bağ,{() => …}/bindings.textcanlı metin,x-display/bindings.displaygörünürlük,.map+key/bindings.list+indexkeysatır listesidir. Satırlar öğe nesnesinin kendisiyle eşlenir;keyeşlemeyi değiştirmez, aynı anahtar iki kez kullanılırsa geliştirme kipinde MJX202 uyarısı verilir. JSX'te{note.title}gibi bir alan okumasını derleyici kendisi bağa çevirir; JSX'siz yazımda canlı kalması gereken değer getter ile verilir,setText("…")ve düz değerler bir kez yazılır. - Servisler uygulama kurulurken kaydedilir.
builder.services.addSingleton(NotesService, { useClass: NotesService })servisibuild()öncesinde kaydeder.startup: trueverilen tekil servis, bir bileşen istemesini beklemedenapp.run()başında, kök bileşen kurulmadan önce oluşturulur; tekil olmayan bir serviste bu seçenek yok sayılır ve MJX415 uyarısı verilir. Kaydın öteki yolu sınıfı@Injectable({ lifetime: "singleton" })ile işaretlemektir. Bu işaret sınıfın modülü yüklendiğinde kayda girer:build()yalnızca o ana kadar yüklenmiş işaretli sınıfları kaydeder; modülü henüz yüklenmemiş bir servis, modül yüklenip ilk kez istenene kadar kayıtta yoktur vestartup: trueverilmiş olsa bile açılışta oluşturulmaz. Builder'da kaydedilmiş bir sınıfta bu işaret yok sayılır. Örnekte sınıf bileşeni servisithis.getService, fonksiyon ve Options bileşeniFromServiceile alır; Options bileşenithis.getService'i de kullanabilir. - İmperatif biçim aynı çekirdek çağrılarının elle yazılmış hâlidir.
motifComponent(etiket, kurulum)bir bileşen oluşturur; kurulum fonksiyonuinitializeComponentolarak bir kez çalışır. Gizli başlayan eleman içindisplaybağıpreconfig'de kurulur ki eleman görünene kadar kurulmasın. Düz metin bir"text"bileşenidir:motifComponent("text", (node) => node.setText("…")). Derleyici her ifade için kendi seçtiği bağı üretir (canlı metindebindings.method, üçlü ifadedebindings.ternary); elle yazarken canlı metin içinbindings.textyeterlidir. Bir JSX ifadesinin hangi çağrıya indiğininpx motif-explain dosya.tsxyazdırır.
Sonraki adım
Başlamak için Başlarken bölümüne geçin.