İçindekiler
Tier 2 kontrolleri, native HTML girdilerinin ötesine geçer: sunucu tarafı
arama, ağaç/graf seçimi, zengin metin/kod/markdown düzenleme, önizlemeli
dosya yüklemeleri, çizim pedleri ve daha fazlası. Select ailesi, telefon
girdisi, veri güdümlü picker'lar, editörler, upload ve görsel kontroller
dahil tamamı github.com/zatrano/goui/forms paketinde yaşar.
import "github.com/zatrano/goui/forms"
Aşağıdaki her alan core.BaseComponent + forms.CommonAttrs +
forms.FieldValidation'ı gömer (belirtilmediği yerler hariç) ve
05-forms-tier1.md ile
06-validation.md'de açıklanan paylaşılan
Name()/RawValue()/SetRawValue(string)/Validate() bool sözleşmesini
uygular. Hepsi sunucu tarafında render edilir: Go struct'ı tek doğruluk
kaynağıdır, ve tarayıcı sadece aşağıda açıkça "UI-only (sadece UI)" olarak
not edilen küçük UI durumu dilimine sahiptir (örn. bir takvimin şu anda
hangi ayı gösterdiği).
Select ailesi (forms)
Bunların hepsi forms.BaseSelectField'i gömer:
type SelectItem struct {
Value string
Label string
Disabled bool
}
type BaseSelectField struct {
core.BaseComponent
forms.CommonAttrs
forms.FieldValidation
Items []SelectItem
Filtered []SelectItem // son sunucu tarafı filtre geçişi
Query string
Open bool
Value string
Values []string
FilterMode FilterMode // FilterServer (varsayılan) | FilterClient
MaxResults int // varsayılan 50
Placeholder string
OnChange func(value string)
OnQuery func(query string)
}
Sunucu vs. UI-only: filtreleme varsayılan olarak sunucu
taraflıdır (FilterServer) — her tuş vuruşu bir query olayı gönderir,
sunucu forms.FilterItems aracılığıyla Filtered'ı yeniden hesaplar
(label/value üzerinde büyük/küçük harfe duyarsız alt dize eşleşmesi,
MaxResults ile sınırlandırılmış), ve yeni <li> listesini bir yama
olarak geri gönderir. FilterMode: FilterClient, küçük, sabit listeler
için vardır ama o durumda bile sunucu seçim durumunun sahibidir;
forms'de hiçbir şey seçenek DOM'unu saf olarak JavaScript'te
filtrelemez. İsteğe bağlı selectable.js istemci modülü bunu
güçlendirir: sadece sunucunun zaten render ettiği listenin üzerine
klavye vurgusu/Enter-ile-seçim ekler — seçenekleri istemci tarafında
filtrelemez.
Searchable Select
type SearchableSelect struct {
BaseSelectField
EventName string // olaylar için önek, örn. "city" → city.query / city.select
}
city := forms.SearchableSelect{
BaseSelectField: forms.BaseSelectField{
CommonAttrs: forms.CommonAttrs{Name: "city", ID: "city"},
Placeholder: "Select a city",
Items: []forms.SelectItem{
{Value: "ist", Label: "İstanbul"},
{Value: "ank", Label: "Ankara"},
},
},
EventName: "city",
}
HandleEvent eylemleri (<eventName>.<action> üzerinden dispatch
edilir): toggle, open, close, query, select. Kendine ait bir
istemci modülü dosyası yoktur — açma/kapama ve klavye davranışı genel
olarak selectable.js tarafından kapsanır.
Multi Select
type MultiSelect struct {
BaseSelectField
EventName string
}
SearchableSelect ile aynı şekle sahiptir ama Values []string'i takip
eder; seçili öğeleri kaldırılabilir <span class="goui-chip"> etiketleri
olarak render eder. Eylemler: toggle, open, close, query,
select (üyeliği açar/kapatır), remove.
cities := forms.MultiSelect{
BaseSelectField: forms.BaseSelectField{
CommonAttrs: forms.CommonAttrs{Name: "cities", ID: "cities"},
Items: cityItems,
},
EventName: "cities",
}
Combobox
type Combobox struct {
BaseSelectField
EventName string
RestrictToList bool // true olduğunda, serbest metni reddeder — Value yalnızca Items'tan
}
Filtrelenmiş bir öneri panelini de açan bir metin girdisi.
RestrictToList ayarlanmadıkça, her tuş vuruşu ham yazılan metni
Value'ya da ayarlar (serbest metin izinlidir); bir öneri seçmek
Value'yu öğeye ve Query'yi onun etiketine ayarlar. Eylemler:
toggle/open, close, query, select, commit.
role := forms.Combobox{
BaseSelectField: forms.BaseSelectField{
CommonAttrs: forms.CommonAttrs{Name: "role", ID: "role"},
Items: []forms.SelectItem{{Value: "admin", Label: "Admin"}, {Value: "editor", Label: "Editor"}},
},
EventName: "role",
}
Autocomplete
type Autocomplete struct {
BaseSelectField
EventName string
}
Combobox gibidir ama yazarken Value'yu ayarlamaz — sadece bir
öneri seçimi (veya seçim olmadan commit, ki bu yazılan metne geri
döner) Value'yu ayarlar. Eylemler: query, select, commit,
close.
suggest := forms.Autocomplete{
BaseSelectField: forms.BaseSelectField{CommonAttrs: forms.CommonAttrs{Name: "suggest", ID: "suggest"}, Items: cityItems},
EventName: "suggest",
}
Tag Input / Chips Input
type TagInput struct {
core.BaseComponent
forms.CommonAttrs
forms.FieldValidation
Values []string
Draft string
Placeholder string
EventName string
OnChange func(tags []string)
}
// ChipsInput, TagInput değerlerinin çip sunumunu vurgulayan bir takma addır.
type ChipsInput = TagInput
BaseSelectField üzerine inşa edilmemiştir — sabit bir Items listesi
üzerinde bir seçici değil, serbest metin etiket koleksiyonudur.
Büyük/küçük harfe duyarsız olarak yinelenenleri temizler. Eylemler:
draft (yazarken tampon), add/commit (virgülle ayrılmış girdi
desteklenir — "go, rust" her ikisini de ekler), remove.
skills := forms.TagInput{
CommonAttrs: forms.CommonAttrs{Name: "skills", ID: "skills"},
Placeholder: "Add a tag (Enter/blur)",
EventName: "skills",
}
Tree Select
type TreeNode struct {
Value string
Label string
Disabled bool
Children []TreeNode
}
type TreeSelect struct {
BaseSelectField
Nodes []TreeNode
Expanded map[string]bool
EventName string
}
Mount, Expanded'i tembel (lazily) tahsis eder. Her dal düğümü için
genişlet/daralt geçişleriyle iç içe bir <ul> render eder. Eylemler:
toggle (panel açma/kapama), close, expand (bir düğümün genişletme
durumunu değiştirir), select.
dept := forms.TreeSelect{
BaseSelectField: forms.BaseSelectField{CommonAttrs: forms.CommonAttrs{Name: "dept", ID: "dept"}},
EventName: "dept",
Nodes: []forms.TreeNode{
{Value: "eng", Label: "Engineering", Children: []forms.TreeNode{
{Value: "be", Label: "Backend"}, {Value: "fe", Label: "Frontend"},
}},
},
}
Cascader
type CascaderLevel struct {
Items []SelectItem
Selected string
}
type Cascader struct {
BaseSelectField
EventName string
Levels []CascaderLevel
LoadChildren func(level int, parentValue string) []SelectItem
}
Her seçimin sonraki sütunu sunucu tarafında sizin LoadChildren
callback'inizle yüklediği çok sütunlu bir "içine inme" (drill-down)
kontrolüdür. Mount, Levels boşsa Levels[0]'ı Items'tan besler.
RawValue(), her seviyenin seçimini / ile birleştirir. Eylem: pick
(payload hem value hem level taşır); seçim yapmak herhangi bir daha
derin seviyeyi temizler ve ya yeni bir sütun ekler (LoadChildren'ın
döndürdüğü öğeler) ya da hiç çocuk yoksa commit eder (OnChange).
loc := forms.Cascader{
BaseSelectField: forms.BaseSelectField{
CommonAttrs: forms.CommonAttrs{Name: "loc", ID: "loc"},
Items: []forms.SelectItem{{Value: "tr", Label: "Türkiye"}, {Value: "de", Label: "Almanya"}},
},
EventName: "loc",
LoadChildren: func(level int, parent string) []forms.SelectItem {
if level == 0 && parent == "tr" {
return []forms.SelectItem{{Value: "ist", Label: "İstanbul"}, {Value: "ank", Label: "Ankara"}}
}
return nil
},
}
Dual Listbox
type DualListbox struct {
BaseSelectField
EventName string
SelectedQuery string
SelectedFilter []SelectItem
}
Taşıma eylemleriyle birlikte iki bağımsız olarak aranabilir sütun
("mevcut" / "seçili"). Her iki taraf da sunucu tarafında filtrelenir
(ApplyAvailableQuery, ApplySelectedQuery). Eylemler:
query_left/query (mevcut taraf), query_right (seçili taraf), add,
remove, add_all, remove_all.
perms := forms.DualListbox{
BaseSelectField: forms.BaseSelectField{CommonAttrs: forms.CommonAttrs{Name: "perms", ID: "perms"}, Items: permItems},
EventName: "perms",
}
Phone Input (forms)
type PhoneInput struct {
forms.CommonAttrs
forms.FieldValidation
Dial SearchableSelect // çevirme kodu
Number forms.TextInput // ulusal numara
}
func NewPhoneInput(name string) *PhoneInput
Yeni bir kontrol ailesi değildir — bir SearchableSelect'i (çevirme
kodu, forms.DialCodeItems()'tan önceden yüklenmiş, varsayılan
+90) bir forms.TextInput (type=tel) yanına bağlayan bir kompozisyon
yardımcısıdır. RawValue(), bir E.164 benzeri "<dial> <number>"
string'i döndürür. HandleEvent, olay önekini ait olduğu alt alanla
eşleştirerek dispatch eder.
phone := forms.NewPhoneInput("phone") // *PhoneInput
Country / Language / Timezone / Currency Picker (forms)
Bunlar ayrı struct türleri değildir — curated (derlenmiş)
[]SelectItem verisiyle (forms.CountryItems(), LanguageItems(),
TimezoneItems(), CurrencyItems()) önceden yüklenmiş
SearchableSelect factory fonksiyonlarıdır:
func NewCountryPicker(name, event string) SearchableSelect
func NewLanguagePicker(name, event string) SearchableSelect
func NewTimezonePicker(name, event string) SearchableSelect
func NewCurrencyPicker(name, event string) SearchableSelect
country := forms.NewCountryPicker("country", "country") // SearchableSelect
language := forms.NewLanguagePicker("lang", "lang")
tz := forms.NewTimezonePicker("tz", "tz")
currency := forms.NewCurrencyPicker("cur", "cur")
SearchableSelect için yukarıda belgelenen her şey (sunucu tarafı
filtre, sadece selectable.js klavye navigasyonu, istemci tarafında
filtreleme yok) değişmeden geçerlidir.
Emoji / Icon / Font Picker (forms)
Yukarıdaki seçicilerle aynı desen — curated öğe kümeleri
(EmojiItems(), IconItems(), FontItems()) üzerinde SearchableSelect
factory'leri:
func NewEmojiPicker(name, event string) SearchableSelect
func NewIconPicker(name, event string) SearchableSelect
func NewFontPicker(name, event string) SearchableSelect
emoji := forms.NewEmojiPicker("emoji", "emoji")
icon := forms.NewIconPicker("icon", "icon")
font := forms.NewFontPicker("font", "font")
FontItems(), Value olarak tam CSS font-family yığınlarını
döndürür (örn. "Georgia, serif"), böylece Value'yu doğrudan satır içi
style="font-family:..." olarak canlı bir önizleme için
uygulayabilirsiniz, examples/misc-controls'un yaptığı gibi.
forms.MentionUsers() — örnek kullanıcıların küçük, curated bir
[]SelectItem dizini — aynı dosyada yaşar ve kendisi bir seçici olarak
render edilmek için değil, aşağıdaki forms.MentionTextarea için
MentionUser listelerini beslemek içindir.
Currency Input (forms)
type CurrencyInput struct {
core.BaseComponent
CommonAttrs
FieldValidation
Value float64
Currency string // ISO kodu, varsayılan TRY
Locale string // varsayılan "tr"
Decimals int // varsayılan 2
Draft string // yazarken ham metin
EventName string
OnChange func(value float64)
}
Bir float64 saklar; tüm görüntüleme biçimlendirmesi sunucu
taraflıdır (forms.NumberFormat/forms.ParseLocalizedNumber — tr
1.234,56 tarzı gruplama kullanır, en 1,234.56 kullanır). Yazarken,
ham metin Draft'ta tutulur ve sadece ayrıştırılabiliyorsa
blur/change'de Value'ya commit edilir; aksi halde ayrıştırılamayan
Draft görünür kalır, böylece kullanıcı bir yazım hatasını düzeltebilir.
price := forms.CurrencyInput{
CommonAttrs: forms.CommonAttrs{Name: "price", ID: "price"},
Currency: "TRY",
Locale: "tr",
Value: 1250.5,
EventName: "price",
}
Percentage Input (forms)
type PercentageInput struct {
core.BaseComponent
CommonAttrs
FieldValidation
Value float64 // yüzde puanı, örn. 45.5, 45,5% anlamına gelir
Locale string
Decimals int // varsayılan 1
Min, Max *float64
Draft string
EventName string
OnChange func(value float64)
}
CurrencyInput ile aynı draft/commit/locale-biçimlendirme deseni,
isteğe bağlı Min/Max sınırlamasıyla.
max, min := 100.0, 0.0
vat := forms.PercentageInput{
CommonAttrs: forms.CommonAttrs{Name: "vat", ID: "vat"},
Value: 20, Min: &min, Max: &max, EventName: "vat",
}
Rating (forms)
type Rating struct {
core.BaseComponent
CommonAttrs
FieldValidation
Value int // 0..Max
Max int // varsayılan 5
Icon string // varsayılan ★
EmptyIcon string // varsayılan ☆
EventName string
OnChange func(value int)
}
Max <button> simgesi render eder; şu anda seçili yıldıza tıklamak
onu 0'a geri döndürür ("un-rating" — puanı geri almaya olanak tanır).
İstemci modülü yok — her yıldız için data-goui-value ile saf
g-click.
score := forms.Rating{CommonAttrs: forms.CommonAttrs{Name: "score", ID: "score"}, Value: 3, Max: 5, EventName: "score"}
Date Range / Time Range Picker (forms)
type DateRangePicker struct {
core.BaseComponent
CommonAttrs
FieldValidation
Start, End, Min, Max string
EventName string
OnChange func(start, end string)
}
type TimeRangePicker struct {
core.BaseComponent
CommonAttrs
FieldValidation
Start, End, Min, Max, Step string
EventName string
OnChange func(start, end string)
}
Yan yana iki native <input type="date">/<input type="time"> elemanı;
End < Start olduğunda Validate() ekstra bir hata ekler
(forms.date_range.invalid / forms.time_range.invalid). İstemci
modülü yok — her ikisi de g-change ile düz native girdilerdir.
leave := forms.DateRangePicker{
CommonAttrs: forms.CommonAttrs{Name: "leave", ID: "leave"},
Start: "2026-07-10", End: "2026-07-15", EventName: "leave",
}
shift := forms.TimeRangePicker{
CommonAttrs: forms.CommonAttrs{Name: "shift", ID: "shift"},
Start: "09:00", End: "17:30", EventName: "shift",
}
Calendar Date Picker (forms)
type CalendarDatePicker struct {
core.BaseComponent
CommonAttrs
FieldValidation
Value, Min, Max string
Open bool
Placeholder string
EventName string
OnChange func(value string)
}
Sunucu vs. UI-only: seçili Value (YYYY-MM-DD), Min/Max
sınırları, ve açık/kapalı durum sunucu sahiplidir. Ay/yıl navigasyonu
yalnızca istemci taraflıdır — client/modules/calendar.js'deki
(enhanceCalendar) ‹/› başlık düğmeleri yerel bir view değişkenini
hareket ettirir ve ızgarayı tamamen tarayıcıda yeniden render eder, ay
değişikliği başına hiçbir ağ gidiş-dönüşü olmadan. Sadece nihai
gün tıklaması, panelde data-select-event içindeki olay adı
aracılığıyla sunucuya bir g-click (data-goui-value="<ymd>") geri
gönderir. Bu yüzden sunucu tarafında render edilen panel, calendar.js
mount olup devralana kadar sadece bir yer tutucudur
(<div class="goui-calendar-placeholder">Loading…</div>) — bu, o alt
ağacı sunucudan kasıtlı olarak asla yeniden render etmez.
day := forms.CalendarDatePicker{
CommonAttrs: forms.CommonAttrs{Name: "day", ID: "day"},
Value: "2026-07-16", Placeholder: "Pick a date", EventName: "day",
}
İstemci modülü: client/modules/calendar.js (enhanceCalendar(root)).
OTP / PIN Input (forms)
type OTPInput struct {
core.BaseComponent
CommonAttrs
FieldValidation
Length int // varsayılan 6
Value string
Masked bool // parola tarzı hücreler (PIN)
EventName string
OnChange func(value string)
}
// PINInput bir takma addır; PIN UX'i için Masked: true ayarlayın.
type PINInput = OTPInput
Length tek karakterli <input> hücresi render eder (Masked
olduğunda type=password). Tam kod sunucu tarafında Value'da yaşar;
hücre başına düzenlemeler digit eylemini kullanır (payload index +
value taşır), tam değiştirme commit/paste/change/input
kullanır. Toplanan uzunluk Length ile eşleşmediğinde Validate(),
forms.otp.incomplete'i ekler.
otp := forms.OTPInput{CommonAttrs: forms.CommonAttrs{Name: "otp", ID: "otp"}, Length: 6, EventName: "otp"}
pin := forms.PINInput{CommonAttrs: forms.CommonAttrs{Name: "pin", ID: "pin"}, Length: 4, Masked: true, EventName: "pin"}
İstemci modülü: client/modules/otp.js (enhanceOTP) — sadece
UI'ye ait otomatik-sonraki-hücreye-geçme, backspace-ile-öncekine-dönme,
ok tuşu navigasyonu, ve yapıştırma-hücreler-arasında-bölünür. Her hücre
için native input olayları ateşler, böylece mevcut g-input
delegasyonu her rakamı sunucuya göndermeye devam eder; kendisi
WebSocket ile doğrudan konuşmaz.
Rich Text Editor (forms)
type RichTextEditor struct {
core.BaseComponent
CommonAttrs
FieldValidation
Value string // HTML
EventName string
DebounceMS int // varsayılan 350
OnChange func(value string)
}
Sunucu vs. UI-only: Value (HTML içeriği) sunucuda yetkilidir, ama
düzenleme yüzeyinin kendisi tamamen istemciye aittir — bir CDN'den
yüklenen bir Quill örneği
(client/modules/richtext.js, enhanceRichText/mountQuill). Render
edilen markup, sarmalayıcı üzerinde data-goui-ignore taşır, böylece
diff-yama istemcisi onun içine asla uzlaştırma (reconcile) yapmaz
(bkz. client/goui.js'deki applyPatch'in isGoUIIgnored kontrolü) —
Quill'in canlı DOM'unu yamalamak, imleç konumunu, geri alma geçmişini ve
seçimi bozardı.
İçerik senkronizasyonu, Quill'in her text-change'de yazdığı ve üzerinde
g-debounce ile debounce edilmiş sentetik bir input olayı ateşlediği
gizli bir <textarea class="goui-editor-sync"> üzerinden çalışır. Sunucu
tarafında, HandleEvent'in sync eylemi, Value'yu MarkDirty()'yi
çağırmadan güncelleyerek — ve karşılık gelen demo ek olarak bu kontrol
için üst bileşenin HandleEvent'inden core.ErrSkipRender döndürerek —
bu şekilde rich-text senkronizasyon olayları için hiçbir render
frame'i asla geri gönderilmez:
case strings.HasPrefix(event, "rt."):
_ = d.Rich.HandleEvent(ctx, event, payload)
// Quill DOM'un sahibidir — yamalamak yeniden mount eder ve HTML'i iki kez escape eder.
return core.ErrSkipRender
rich := forms.RichTextEditor{CommonAttrs: forms.CommonAttrs{Name: "rt", ID: "rt"}, Value: "<p>Hello</p>", EventName: "rt"}
İstemci modülü: client/modules/richtext.js.
Markdown Editor (forms)
type MarkdownEditor struct {
core.BaseComponent
CommonAttrs
FieldValidation
Value string
PreviewHTML string
Rows int // varsayılan 10
Placeholder string
EventName string
DebounceMS int // varsayılan 250
OnChange func(value string)
}
Sunucu vs. UI-only: kaynak <textarea>, normal bir sunucu tarafında
render edilen Tier-1-tarzı kontroldür (istemci modülü yok,
data-goui-ignore yok) — her tuş vuruşu normal bir Textarea gibi
g-input/sync üzerinden gidiş-dönüş yapar. Canlı önizleme paneli, dışa
açık yardımcı aracılığıyla goldmark
kullanılarak tamamen sunucuda render edilir:
func RenderMarkdown(source string) string
Mount ve her sync HandleEvent çağrısı, PreviewHTML = RenderMarkdown(Value)'yu
ayarlayan refreshPreview()'i çağırır; Render() bu HTML'i doğrudan bir
<div class="goui-markdown-preview"> içine yayar. Bu normal (yok
sayılmamış) bir alt ağaç olduğundan, diff motoru onu diğer herhangi bir
sunucu tarafında render edilmiş HTML gibi seve seve yamalar.
md := forms.MarkdownEditor{
CommonAttrs: forms.CommonAttrs{Name: "md", ID: "md"},
Value: "# Hello\n\n**Markdown** rendered server-side.",
Rows: 12,
EventName: "md",
}
Code Editor (forms)
type CodeEditor struct {
core.BaseComponent
CommonAttrs
FieldValidation
Value string
Language string // örn. javascript, go, htmlmixed — varsayılan javascript
EventName string
DebounceMS int // varsayılan 350
OnChange func(value string)
}
Sunucu vs. UI-only: RichTextEditor ile aynı desen — bir CDN'den
CodeMirror 5 örneği
(client/modules/codeeditor.js, enhanceCodeEditor/mountCM)
düzenleme yüzeyinin sahibidir, yamaların onu asla dokunmaması için
data-goui-ignore işaretlidir, ve g-debounce ile debounce edilmiş
gizli bir <textarea class="goui-editor-sync"> aracılığıyla senkronize
olur. Tam olarak rich text gibi, üst bileşenin HandleEvent'i
code.* senkronizasyon olayları için core.ErrSkipRender döndürmelidir:
case strings.HasPrefix(event, "code."):
_ = d.Code.HandleEvent(ctx, event, payload)
return core.ErrSkipRender
code := forms.CodeEditor{
CommonAttrs: forms.CommonAttrs{Name: "code", ID: "code"},
Value: "function hello() {\n return 'GoUI';\n}\n",
Language: "javascript",
EventName: "code",
}
İstemci modülü: client/modules/codeeditor.js.
Drag & Drop Upload / Image Upload (forms)
type UploadedRef struct {
ID string
Name string
URL string
ContentType string
Size int64
}
type DragDropUpload struct {
core.BaseComponent
CommonAttrs
FieldValidation
Files []UploadedRef
Accept string
Multiple bool
ShowThumbs bool
UploadURL string // varsayılan /goui/upload
EventName string
OnChange func(files []UploadedRef)
}
// ImageUpload preset'i: Accept "image/*", ShowThumbs true.
func NewImageUpload(name, event string) DragDropUpload
Sunucu vs. UI-only: ikili baytlar WebSocket üzerinden asla
yolculuk etmez. client/modules/upload.js (enhanceUpload),
sürükle/bırak ve dosya girdisi change'ini yakalar, ham dosyayı
data-upload-url'e (varsayılan olarak) POST eder
(adapter'ınızın Store seçeneği veya upload.Mount aracılığıyla, ki bu bir
upload.Storage'a —
örn. upload.LocalStore — yazar ve JSON Meta'yı döndürür), ardından
metadata'yı data-goui-* özniteliklerinde taşıyan gizli bir
<button class="goui-upload-carrier" g-click="<event>.uploaded">
üzerinde sentetik bir tıklama üretir, böylece mevcut
g-click/collectPayload delegasyonu id, name, url, size,
contentType içeren bir event frame'i gönderir — soket üzerinden
sadece küçük JSON referansı yolculuk eder. Sunucu tarafında,
uploaded eylemi bir forms.UploadedRef'i ekler/değiştirir; remove
eylemi ID ile bir tanesini düşürür.
docs := forms.DragDropUpload{
CommonAttrs: forms.CommonAttrs{Name: "docs", ID: "docs"},
Multiple: true,
Accept: ".pdf,.txt,.png,.jpg",
ShowThumbs: true,
EventName: "docs",
}
images := forms.NewImageUpload("images", "images") // DragDropUpload preset'i
HTTP tarafını uygulama başına bir kez kaydedin:
store, err := upload.NewLocalStore("./.goui-uploads", "/goui/files", 8<<20)
gouifiber.Register(app, gouifiber.Options{Server: server, Store: store})
// POST /goui/upload, GET /goui/files/:id
İstemci modülü: client/modules/upload.js (kendi postFile/notifyUploaded
çağırıları için aşağıdaki avatar.js ve signature.js tarafından da
import edilir).
Avatar Upload + Image Cropper (forms)
type AvatarUpload struct {
core.BaseComponent
CommonAttrs
FieldValidation
Avatar UploadedRef
UploadURL string
EventName string
OnChange func(ref UploadedRef)
}
Sunucu vs. UI-only: nihai saklanan Avatar referansı sunucu
durumudur; kırpma etkileşiminin kendisi tamamen istemci taraflıdır.
client/modules/avatar.js (enhanceAvatar), dosya seçiminde bir
<canvas> katmanı açar, kullanıcının 1:1 bir kareyi
kaydırmasına (pointerdown/pointermove) izin verir, ve "Kırp &
Yükle" (Crop & Upload) üzerinde canvas.toBlob(...)'u çağırarak
kırpmayı istemci tarafında bir PNG Blob'una rasterize eder, bunu
upload.js'in sunduğu aynı postFile/notifyUploaded yardımcılarıyla
yükler, ardından katmanı gizler. Sunucu asla kırpılmamış pikselleri veya
kırpma koordinatlarını görmez — sadece nihai kırpılmış PNG dosya
referansını (action: "uploaded") veya onu kaldırmak için bir
"clear" eylemini görür.
avatar := forms.AvatarUpload{CommonAttrs: forms.CommonAttrs{Name: "avatar", ID: "avatar"}, EventName: "avatar"}
İstemci modülü: client/modules/avatar.js.
Signature Pad (forms)
type SignaturePad struct {
core.BaseComponent
CommonAttrs
FieldValidation
File UploadedRef
UploadURL string
EventName string
OnChange func(ref UploadedRef)
}
Sunucu vs. UI-only: çizmenin kendisi (bir <canvas> üzerinde
pointerdown/pointermove darbeleri) %100 istemci taraflıdır
(client/modules/signature.js, enhanceSignature/mountPad). "Kaydet"e
tıklamak, canvas'ı bir PNG blob'una rasterize eder ve onu tam olarak
AvatarUpload'ın yaptığı gibi yükler, başarıda action: "uploaded"'ı
ateşler; "Temizle" (yerel temizleme) sadece sunucu gidiş-dönüşü olmadan
canvas piksellerini temizler; ayrı bir sunucuya bağlı "Kaydı sil" düğmesi
(sadece File.ID ayarlandığında render edilir) saklanan referansı
düşürmek için action: "clear" gönderir.
sig := forms.SignaturePad{CommonAttrs: forms.CommonAttrs{Name: "sig", ID: "sig"}, EventName: "sig"}
İstemci modülü: client/modules/signature.js.
Mention (forms)
type MentionUser struct {
ID string
Label string
}
type MentionTextarea struct {
core.BaseComponent
CommonAttrs
FieldValidation
Value string
Placeholder string
Rows int // varsayılan 4
Users []MentionUser // tam dizin
Filtered []MentionUser
Query string // @'dan sonraki metin
Open bool
EventName string
OnChange func(value string)
}
İmleç konumunda tamamlanmamış bir @fragment algılayan (mentionQuery
aracılığıyla, string tabanlı — gerçek imleç konumuna değil tüm
Value'ya bakar, dolayısıyla basitleştirilmiş bir "son @" sezgiseli
(heuristic)) ve sunucu tarafında filtrelenmiş bir öneri listesi
(filterUsers, ID/etiket üzerinde alt dize eşleşmesi, 8 ile
sınırlandırılmış) açan bir <textarea>. Bir öneri seçmek (pick
eylemi), @fragment'i @<id> ile değiştirir. İstemci modülü yok — bir
düz Textarea-tarzı kontrol, ardından koşullu olarak render edilmiş bir
<ul>.
mention := forms.MentionTextarea{
CommonAttrs: forms.CommonAttrs{Name: "mention", ID: "mention"},
Placeholder: "Tag someone with @...",
Users: []forms.MentionUser{{ID: "ayse", Label: "Ayşe Yılmaz"}},
EventName: "mention",
}
Color (Swatch) Picker (forms)
type SwatchColorPicker struct {
core.BaseComponent
CommonAttrs
FieldValidation
Value string // #rrggbb
Swatches []string
EventName string
OnChange func(value string)
}
Tier 1'deki native forms.ColorInput'a gelişmiş bir alternatif: bir sıra
önceden ayarlı swatch düğmesi artı serbest metin bir hex alanı.
Swatches boşsa 10 önceden ayarlı swatch'a varsayılan olur. Eylemler:
pick/select (bir swatch'tan), hex/change/input (metin alanından,
normalizeHex aracılığıyla normalize edilmiş — küçük harfli,
#-önekli).
color := forms.SwatchColorPicker{
CommonAttrs: forms.CommonAttrs{Name: "color", ID: "color"},
Value: "#2563eb",
EventName: "color",
}
Gradient Picker (forms)
type GradientPicker struct {
core.BaseComponent
CommonAttrs
FieldValidation
From, To, Angle string // örn. Angle "135deg"
EventName string
OnChange func(css string)
}
func (g *GradientPicker) CSS() string // "linear-gradient(<angle>, <from>, <to>)"
İki native <input type="color"> swatch'ı artı serbest metin bir açı
alanı; Render(), canlı bir önizleme <div>'i ve üretilen CSS'i
<code> olarak gösterir. Eylemler: from, to, angle.
grad := forms.GradientPicker{
CommonAttrs: forms.CommonAttrs{Name: "grad", ID: "grad"},
From: "#2563eb", To: "#db2777", Angle: "135deg", EventName: "grad",
}
Character Counter (ShowCharCount)
Ayrı bir struct değildir — mevcut Tier 1 kontrolleri üzerinde bir
alan: forms.TextInput.ShowCharCount ve forms.Textarea.ShowCharCount.
true olduğunda, Render(), MaxLength'i aştığında hata olarak
renklendirilmiş, len(value) / MaxLength'i (rune sayılmış) gösteren bir
<p class="goui-char-count"> ekler. Her iki bayrağı ayarlamak da, kendiniz
DebounceMS'i ayarlamadıysanız g-debounce'u 100'e varsayılan yapar,
böylece sayaç her tuş vuruşunda olayları spamlamadan duyarlı bir şekilde
güncellenir.
bio := forms.Textarea{
CommonAttrs: forms.CommonAttrs{Name: "bio", ID: "bio"},
Rows: 4,
MaxLength: 120,
ShowCharCount: true,
HelperText: "Up to 120 characters",
}
Password Strength (ShowStrength)
Ayrıca forms.TextInput üzerinde bir alandır: ShowStrength bool, ki bu
sadece Type == "password" olduğunda render edilir. Puanlama, küçük bir
sunucu tarafı sezgiseldir (forms.PasswordStrength, 0–4: uzunluk
≥8/≥12, karakter sınıfı çeşitliliği) ve şu şekilde açığa çıkarılmıştır:
type PasswordStrengthLevel int
const (
StrengthEmpty PasswordStrengthLevel = iota
StrengthWeak
StrengthFair
StrengthGood
StrengthStrong
)
func PasswordStrength(password string) PasswordStrengthLevel
Render(), bir çevrilmiş etiketle (forms.password_strength.* i18n
key'leri — bkz. 03-i18n.md) birlikte bir
<div class="goui-password-strength <level>"> çubuğu ekler (genişlik =
level*25%).
pw := forms.TextInput{
CommonAttrs: forms.CommonAttrs{Name: "pw", ID: "pw"},
Type: "password",
ShowStrength: true,
}
Özet tablo
| Kontrol | Paket | Struct | İstemci modülü | Notlar |
|---|---|---|---|---|
| Searchable Select | forms |
SearchableSelect |
— (selectable.js kullanır) |
sunucu tarafı filtre |
| Multi Select | forms |
MultiSelect |
— (selectable.js kullanır) |
Values'ın çipleri |
| Combobox | forms |
Combobox |
— (selectable.js kullanır) |
RestrictToList olmadıkça serbest metin |
| Autocomplete | forms |
Autocomplete |
— (selectable.js kullanır) |
Value yalnızca seçim/commit'te ayarlanır |
| Tag Input / Chips Input | forms |
TagInput / ChipsInput (takma ad) |
— | yinelenen temizleme, virgülle bölme |
| Tree Select | forms |
TreeSelect |
— | sunucu sahipli Expanded map'i |
| Cascader | forms |
Cascader |
— | LoadChildren callback'i |
| Dual Listbox | forms |
DualListbox |
— | iki bağımsız filtrelenmiş taraf |
| Phone | forms |
PhoneInput |
— | SearchableSelect + TextInput'i kompoze eder |
| Country/Language/Timezone/Currency Picker | forms |
SearchableSelect (NewXPicker üzerinden) |
— | curated SelectItem verisi |
| Emoji/Icon/Font Picker | forms |
SearchableSelect (NewXPicker üzerinden) |
— | curated SelectItem verisi |
| Currency Input | forms |
CurrencyInput |
— | sunucu locale biçimlendirmesi |
| Percentage Input | forms |
PercentageInput |
— | sunucu locale biçimlendirmesi |
| Rating | forms |
Rating |
— | saf g-click |
| Date Range | forms |
DateRangePicker |
— | iki native <input type=date> |
| Time Range | forms |
TimeRangePicker |
— | iki native <input type=time> |
| Calendar | forms |
CalendarDatePicker |
calendar.js |
ay navigasyonu yalnızca istemci taraflıdır |
| OTP / PIN | forms |
OTPInput / PINInput (takma ad) |
otp.js |
sadece UI otomatik-ilerleme/yapıştırma |
| Rich Text | forms |
RichTextEditor |
richtext.js |
Quill; ErrSkipRender + data-goui-ignore |
| Markdown | forms |
MarkdownEditor |
— | goldmark aracılığıyla sunucu tarafında render edilir |
| Code Editor | forms |
CodeEditor |
codeeditor.js |
CodeMirror; ErrSkipRender + data-goui-ignore |
| DragDrop Upload | forms |
DragDropUpload |
upload.js |
ikili HTTP üzerinden, referans WS üzerinden |
| Image Upload | forms |
DragDropUpload (NewImageUpload üzerinden) |
upload.js |
preset: image/* + küçük resimler |
| Avatar Upload | forms |
AvatarUpload |
avatar.js |
kırpma katmanını içerir |
| Image Cropper | forms |
(AvatarUpload'ın bir parçası) |
avatar.js |
istemci tarafı canvas kırpma |
| Color (Swatch) | forms |
SwatchColorPicker |
— | swatch'lar + hex alanı |
| Gradient | forms |
GradientPicker |
— | iki renk + açı |
| Signature | forms |
SignaturePad |
signature.js |
canvas çizim → PNG yükleme |
| Mention | forms |
MentionTextarea |
— | sunucu tarafında filtrelenmiş @ önerileri |
| Character Counter | forms |
TextInput.ShowCharCount / Textarea.ShowCharCount |
— | alan bayrağı, struct değil |
| Password Strength | forms |
TextInput.ShowStrength |
— | alan bayrağı, Type: "password" gerektirir |
Yukarıdaki her kontrolün tam olarak bağlanmış, çalıştırılabilir
versiyonları için examples/ dizinine bakın (portlar ve eşleme
01-getting-started.md'de belgelenmiştir) —
özellikle searchable-select (3002), numeric-controls (3003),
field-meta (3004), date-controls (3005), identity-inputs (3006),
editors (3007), media-upload (3008), ve misc-controls (3009).