Added change listener binding

This commit is contained in:
Alexei Anoshenko 2025-06-22 21:04:01 +03:00
parent d633c80155
commit 0433f460e4
23 changed files with 163 additions and 102 deletions

View File

@ -160,7 +160,7 @@ func (picker *colorPickerData) handleCommand(self View, command PropertyName, da
listener.Run(picker, color, oldColor) listener.Run(picker, color, oldColor)
} }
if listener, ok := picker.changeListener[ColorPickerValue]; ok { if listener, ok := picker.changeListener[ColorPickerValue]; ok {
listener(picker, ColorPickerValue) listener.Run(picker, ColorPickerValue)
} }
} }
} }

View File

@ -98,8 +98,8 @@ func (customView *CustomViewData) SetParams(params Params) bool {
} }
// SetChangeListener set the function to track the change of the View property // SetChangeListener set the function to track the change of the View property
func (customView *CustomViewData) SetChangeListener(tag PropertyName, listener func(View, PropertyName)) { func (customView *CustomViewData) SetChangeListener(tag PropertyName, listener any) bool {
customView.superView.SetChangeListener(tag, listener) return customView.superView.SetChangeListener(tag, listener)
} }
// Remove removes the property with name defined by the argument // Remove removes the property with name defined by the argument

13
data.go
View File

@ -49,14 +49,17 @@ type DataObject interface {
ToParams() Params ToParams() Params
} }
// DataNodeType defines the type of DataNode
type DataNodeType int
// Constants which are used to describe a node type, see [DataNode] // Constants which are used to describe a node type, see [DataNode]
const ( const (
// TextNode - node is the pair "tag - text value". Syntax: <tag> = <text> // TextNode - node is the pair "tag - text value". Syntax: <tag> = <text>
TextNode = 0 TextNode DataNodeType = 0
// ObjectNode - node is the pair "tag - object". Syntax: <tag> = <object name>{...} // ObjectNode - node is the pair "tag - object". Syntax: <tag> = <object name>{...}
ObjectNode = 1 ObjectNode DataNodeType = 1
// ArrayNode - node is the pair "tag - object". Syntax: <tag> = [...] // ArrayNode - node is the pair "tag - object". Syntax: <tag> = [...]
ArrayNode = 2 ArrayNode DataNodeType = 2
) )
// DataNode interface of a data node // DataNode interface of a data node
@ -65,7 +68,7 @@ type DataNode interface {
Tag() string Tag() string
// Type returns a node type. Possible values are TextNode, ObjectNode and ArrayNode // Type returns a node type. Possible values are TextNode, ObjectNode and ArrayNode
Type() int Type() DataNodeType
// Text returns node text // Text returns node text
Text() string Text() string
@ -253,7 +256,7 @@ func (node *dataNode) Tag() string {
return node.tag return node.tag
} }
func (node *dataNode) Type() int { func (node *dataNode) Type() DataNodeType {
if node.array != nil { if node.array != nil {
return ArrayNode return ArrayNode
} }

View File

@ -75,7 +75,7 @@ func TestParseDataText(t *testing.T) {
t.Errorf(`obj.PropertyValue("key5") result: ("%s",%v)`, val, ok) t.Errorf(`obj.PropertyValue("key5") result: ("%s",%v)`, val, ok)
} }
testKey := func(obj DataObject, index int, tag string, nodeType int) DataNode { testKey := func(obj DataObject, index int, tag string, nodeType DataNodeType) DataNode {
key := obj.Property(index) key := obj.Property(index)
if key == nil { if key == nil {
t.Errorf(`%s.Property(%d) == nil`, obj.Tag(), index) t.Errorf(`%s.Property(%d) == nil`, obj.Tag(), index)
@ -118,7 +118,7 @@ func TestParseDataText(t *testing.T) {
type testKeyData struct { type testKeyData struct {
tag string tag string
nodeType int nodeType DataNodeType
} }
data := []testKeyData{ data := []testKeyData{

View File

@ -352,7 +352,7 @@ func (picker *datePickerData) handleCommand(self View, command PropertyName, dat
listener.Run(picker, value, oldValue) listener.Run(picker, value, oldValue)
} }
if listener, ok := picker.changeListener[DatePickerValue]; ok { if listener, ok := picker.changeListener[DatePickerValue]; ok {
listener(picker, DatePickerValue) listener.Run(picker, DatePickerValue)
} }
} }
} }

View File

@ -193,7 +193,7 @@ func (detailsView *detailsViewData) handleCommand(self View, command PropertyNam
if n, ok := dataIntProperty(data, "open"); ok { if n, ok := dataIntProperty(data, "open"); ok {
detailsView.properties[Expanded] = (n != 0) detailsView.properties[Expanded] = (n != 0)
if listener, ok := detailsView.changeListener[Expanded]; ok { if listener, ok := detailsView.changeListener[Expanded]; ok {
listener(detailsView, Expanded) listener.Run(detailsView, Expanded)
} }
} }
return true return true

View File

@ -249,7 +249,7 @@ func (list *dropDownListData) handleCommand(self View, command PropertyName, dat
listener.Run(list, number, old) listener.Run(list, number, old)
} }
if listener, ok := list.changeListener[Current]; ok { if listener, ok := list.changeListener[Current]; ok {
listener(list, Current) listener.Run(list, Current)
} }
} }
} else { } else {

View File

@ -272,7 +272,7 @@ func (edit *editViewData) textChanged(newText, oldText string) {
listener.Run(edit, newText, oldText) listener.Run(edit, newText, oldText)
} }
if listener, ok := edit.changeListener[Text]; ok { if listener, ok := edit.changeListener[Text]; ok {
listener(edit, Text) listener.Run(edit, Text)
} }
} }

View File

@ -139,8 +139,7 @@ func (data *noArgListenerBinding[V]) Run(view V) {
args = []reflect.Value{} args = []reflect.Value{}
case 1: case 1:
inType := methodType.In(0) if equalType(methodType.In(0), reflect.TypeOf(view)) {
if inType == reflect.TypeOf(view) {
args = []reflect.Value{reflect.ValueOf(view)} args = []reflect.Value{reflect.ValueOf(view)}
} }
} }
@ -152,6 +151,10 @@ func (data *noArgListenerBinding[V]) Run(view V) {
} }
} }
func equalType(inType reflect.Type, argType reflect.Type) bool {
return inType == argType || (inType.Kind() == reflect.Interface && argType.Implements(inType))
}
func (data *noArgListenerBinding[V]) rawListener() any { func (data *noArgListenerBinding[V]) rawListener() any {
return data.name return data.name
} }

View File

@ -106,6 +106,7 @@ func (data *oneArgListenerBinding[V, E]) Run(view V, event E) {
} }
methodType := method.Type() methodType := method.Type()
var args []reflect.Value = nil var args []reflect.Value = nil
switch methodType.NumIn() { switch methodType.NumIn() {
case 0: case 0:
@ -113,14 +114,15 @@ func (data *oneArgListenerBinding[V, E]) Run(view V, event E) {
case 1: case 1:
inType := methodType.In(0) inType := methodType.In(0)
if inType == reflect.TypeOf(view) { if equalType(inType, reflect.TypeOf(event)) {
args = []reflect.Value{reflect.ValueOf(view)}
} else if inType == reflect.TypeOf(event) {
args = []reflect.Value{reflect.ValueOf(event)} args = []reflect.Value{reflect.ValueOf(event)}
} else if equalType(inType, reflect.TypeOf(view)) {
args = []reflect.Value{reflect.ValueOf(view)}
} }
case 2: case 2:
if methodType.In(0) == reflect.TypeOf(view) && methodType.In(1) == reflect.TypeOf(event) { if equalType(methodType.In(0), reflect.TypeOf(view)) &&
equalType(methodType.In(1), reflect.TypeOf(event)) {
args = []reflect.Value{reflect.ValueOf(view), reflect.ValueOf(event)} args = []reflect.Value{reflect.ValueOf(view), reflect.ValueOf(event)}
} }
} }

View File

@ -140,6 +140,7 @@ func (data *twoArgListenerBinding[V, E]) Run(view V, arg1 E, arg2 E) {
} }
methodType := method.Type() methodType := method.Type()
var args []reflect.Value = nil var args []reflect.Value = nil
switch methodType.NumIn() { switch methodType.NumIn() {
case 0: case 0:
@ -147,23 +148,25 @@ func (data *twoArgListenerBinding[V, E]) Run(view V, arg1 E, arg2 E) {
case 1: case 1:
inType := methodType.In(0) inType := methodType.In(0)
if inType == reflect.TypeOf(view) { if equalType(inType, reflect.TypeOf(arg1)) {
args = []reflect.Value{reflect.ValueOf(view)}
} else if inType == reflect.TypeOf(arg1) {
args = []reflect.Value{reflect.ValueOf(arg1)} args = []reflect.Value{reflect.ValueOf(arg1)}
} else if equalType(inType, reflect.TypeOf(view)) {
args = []reflect.Value{reflect.ValueOf(view)}
} }
case 2: case 2:
valType := reflect.TypeOf(arg1) inType0 := methodType.In(0)
if methodType.In(0) == reflect.TypeOf(view) && methodType.In(1) == valType { inType1 := methodType.In(1)
if equalType(inType0, reflect.TypeOf(view)) && equalType(inType1, reflect.TypeOf(arg1)) {
args = []reflect.Value{reflect.ValueOf(view), reflect.ValueOf(arg1)} args = []reflect.Value{reflect.ValueOf(view), reflect.ValueOf(arg1)}
} else if methodType.In(0) == valType && methodType.In(1) == valType { } else if equalType(inType0, reflect.TypeOf(arg1)) && equalType(inType1, reflect.TypeOf(arg2)) {
args = []reflect.Value{reflect.ValueOf(arg1), reflect.ValueOf(arg2)} args = []reflect.Value{reflect.ValueOf(arg1), reflect.ValueOf(arg2)}
} }
case 3: case 3:
valType := reflect.TypeOf(arg1) if equalType(methodType.In(0), reflect.TypeOf(view)) &&
if methodType.In(0) == reflect.TypeOf(view) && methodType.In(1) == valType && methodType.In(2) == valType { equalType(methodType.In(1), reflect.TypeOf(arg1)) &&
equalType(methodType.In(2), reflect.TypeOf(arg2)) {
args = []reflect.Value{reflect.ValueOf(view), reflect.ValueOf(arg1), reflect.ValueOf(arg2)} args = []reflect.Value{reflect.ValueOf(view), reflect.ValueOf(arg1), reflect.ValueOf(arg2)}
} }
} }

View File

@ -458,10 +458,7 @@ func (gridLayout *gridLayoutData) UpdateGridContent() {
if gridLayout.created { if gridLayout.created {
updateInnerHTML(gridLayout.htmlID(), gridLayout.session) updateInnerHTML(gridLayout.htmlID(), gridLayout.session)
} }
gridLayout.contentChanged()
if listener, ok := gridLayout.changeListener[Content]; ok {
listener(gridLayout, Content)
}
} }
} }

View File

@ -196,10 +196,7 @@ func (listLayout *listLayoutData) UpdateContent() {
if listLayout.created { if listLayout.created {
updateInnerHTML(listLayout.htmlID(), listLayout.session) updateInnerHTML(listLayout.htmlID(), listLayout.session)
} }
listLayout.contentChanged()
if listener, ok := listLayout.changeListener[Content]; ok {
listener(listLayout, Content)
}
} }
} }

View File

@ -963,7 +963,7 @@ func (listView *listViewData) handleCurrent(number int) {
listener.Run(listView, number) listener.Run(listView, number)
} }
if listener, ok := listView.changeListener[Current]; ok { if listener, ok := listView.changeListener[Current]; ok {
listener(listView, Current) listener.Run(listView, Current)
} }
} }
@ -1022,7 +1022,7 @@ func (listView *listViewData) onItemClick(number int) {
setArrayPropertyValue(listView, Checked, checkedItem) setArrayPropertyValue(listView, Checked, checkedItem)
if listener, ok := listView.changeListener[Checked]; ok { if listener, ok := listView.changeListener[Checked]; ok {
listener(listView, Checked) listener.Run(listView, Checked)
} }
for _, listener := range getOneArgEventListeners[ListView, []int](listView, nil, ListItemCheckedEvent) { for _, listener := range getOneArgEventListeners[ListView, []int](listView, nil, ListItemCheckedEvent) {

View File

@ -1430,32 +1430,32 @@ func (data *mediaPlayerErrorListenerBinding) Run(player MediaPlayer, code int, m
args = []reflect.Value{} args = []reflect.Value{}
case 1: case 1:
switch methodType.In(0) { inType := methodType.In(0)
case reflect.TypeOf(player): if equalType(inType, reflect.TypeOf(player)) {
args = []reflect.Value{reflect.ValueOf(player)} args = []reflect.Value{reflect.ValueOf(player)}
case reflect.TypeOf(code): } else if equalType(inType, reflect.TypeOf(code)) {
args = []reflect.Value{reflect.ValueOf(code)} args = []reflect.Value{reflect.ValueOf(code)}
case reflect.TypeOf(message): } else if equalType(inType, reflect.TypeOf(message)) {
args = []reflect.Value{reflect.ValueOf(message)} args = []reflect.Value{reflect.ValueOf(message)}
} }
case 2: case 2:
in0 := methodType.In(0) in0 := methodType.In(0)
in1 := methodType.In(1) in1 := methodType.In(1)
if in0 == reflect.TypeOf(player) { if equalType(in0, reflect.TypeOf(player)) {
if in1 == reflect.TypeOf(code) { if equalType(in1, reflect.TypeOf(code)) {
args = []reflect.Value{reflect.ValueOf(player), reflect.ValueOf(code)} args = []reflect.Value{reflect.ValueOf(player), reflect.ValueOf(code)}
} else if in1 == reflect.TypeOf(message) { } else if equalType(in1, reflect.TypeOf(message)) {
args = []reflect.Value{reflect.ValueOf(player), reflect.ValueOf(message)} args = []reflect.Value{reflect.ValueOf(player), reflect.ValueOf(message)}
} }
} else if in0 == reflect.TypeOf(code) && in1 == reflect.TypeOf(message) { } else if equalType(in0, reflect.TypeOf(code)) && equalType(in1, reflect.TypeOf(message)) {
args = []reflect.Value{reflect.ValueOf(code), reflect.ValueOf(message)} args = []reflect.Value{reflect.ValueOf(code), reflect.ValueOf(message)}
} }
case 3: case 3:
if methodType.In(0) == reflect.TypeOf(player) && if equalType(methodType.In(0), reflect.TypeOf(player)) &&
methodType.In(1) == reflect.TypeOf(code) && equalType(methodType.In(1), reflect.TypeOf(code)) &&
methodType.In(2) == reflect.TypeOf(message) { equalType(methodType.In(2), reflect.TypeOf(message)) {
args = []reflect.Value{ args = []reflect.Value{
reflect.ValueOf(player), reflect.ValueOf(player),
reflect.ValueOf(code), reflect.ValueOf(code),

View File

@ -284,7 +284,7 @@ func (picker *numberPickerData) handleCommand(self View, command PropertyName, d
listener.Run(picker, value, oldValue) listener.Run(picker, value, oldValue)
} }
if listener, ok := picker.changeListener[NumberPickerValue]; ok { if listener, ok := picker.changeListener[NumberPickerValue]; ok {
listener(picker, NumberPickerValue) listener.Run(picker, NumberPickerValue)
} }
} }
} }

View File

@ -610,12 +610,6 @@ func (layout *stackLayoutData) moveToFrontByIndex(index int, onShow []func(View)
session.updateCSSProperty(peekPageID, "transform", transformCSS) session.updateCSSProperty(peekPageID, "transform", transformCSS)
} }
func (layout *stackLayoutData) contentChanged() {
if listener, ok := layout.changeListener[Content]; ok {
listener(layout, Content)
}
}
func (layout *stackLayoutData) RemovePeek() View { func (layout *stackLayoutData) RemovePeek() View {
return layout.RemoveView(len(layout.views) - 1) return layout.RemoveView(len(layout.views) - 1)
} }
@ -683,9 +677,7 @@ func (layout *stackLayoutData) Append(view View) {
} }
session.appendToInnerHTML(stackID, buffer.String()) session.appendToInnerHTML(stackID, buffer.String())
if listener, ok := layout.changeListener[Content]; ok { layout.contentChanged()
listener(layout, Content)
}
} }
} }
@ -721,9 +713,7 @@ func (layout *stackLayoutData) Insert(view View, index int) {
session := layout.Session() session := layout.Session()
session.appendToInnerHTML(stackID, buffer.String()) session.appendToInnerHTML(stackID, buffer.String())
if listener, ok := layout.changeListener[Content]; ok { layout.contentChanged()
listener(layout, Content)
}
} }
// Remove removes view from list and return it // Remove removes view from list and return it
@ -754,10 +744,7 @@ func (layout *stackLayoutData) RemoveView(index int) View {
} }
layout.Session().callFunc("removeView", view.htmlID()+"page") layout.Session().callFunc("removeView", view.htmlID()+"page")
layout.contentChanged()
if listener, ok := layout.changeListener[Content]; ok {
listener(layout, Content)
}
return view return view
} }

View File

@ -1676,7 +1676,7 @@ func (table *tableViewData) handleCommand(self View, command PropertyName, data
current.Row = row current.Row = row
table.setRaw(Current, current.Row) table.setRaw(Current, current.Row)
if listener, ok := table.changeListener[Current]; ok { if listener, ok := table.changeListener[Current]; ok {
listener(table, Current) listener.Run(table, Current)
} }
for _, listener := range getOneArgEventListeners[TableView, int](table, nil, TableRowSelectedEvent) { for _, listener := range getOneArgEventListeners[TableView, int](table, nil, TableRowSelectedEvent) {
@ -1693,7 +1693,7 @@ func (table *tableViewData) handleCommand(self View, command PropertyName, data
current.Column = column current.Column = column
table.setRaw(Current, current.Row) table.setRaw(Current, current.Row)
if listener, ok := table.changeListener[Current]; ok { if listener, ok := table.changeListener[Current]; ok {
listener(table, Current) listener.Run(table, Current)
} }
for _, listener := range getTwoArgEventListeners[TableView, int](table, nil, TableCellSelectedEvent) { for _, listener := range getTwoArgEventListeners[TableView, int](table, nil, TableCellSelectedEvent) {

View File

@ -403,9 +403,7 @@ func (tabsLayout *tabsLayoutData) Append(view View) {
view.SetChangeListener(TabCloseButton, tabsLayout.updateTabCloseButton) view.SetChangeListener(TabCloseButton, tabsLayout.updateTabCloseButton)
if tabsLayout.created { if tabsLayout.created {
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.Session()) updateInnerHTML(tabsLayout.htmlID(), tabsLayout.Session())
if listener, ok := tabsLayout.changeListener[Content]; ok { tabsLayout.contentChanged()
listener(tabsLayout, Content)
}
tabsLayout.Set(Current, len(tabsLayout.views)-1) tabsLayout.Set(Current, len(tabsLayout.views)-1)
} }
} }
@ -429,9 +427,7 @@ func (tabsLayout *tabsLayoutData) currentChanged(newCurrent, oldCurrent int) {
for _, listener := range getTwoArgEventListeners[TabsLayout, int](tabsLayout, nil, CurrentTabChangedEvent) { for _, listener := range getTwoArgEventListeners[TabsLayout, int](tabsLayout, nil, CurrentTabChangedEvent) {
listener.Run(tabsLayout, newCurrent, oldCurrent) listener.Run(tabsLayout, newCurrent, oldCurrent)
} }
if listener, ok := tabsLayout.changeListener[Current]; ok { tabsLayout.contentChanged()
listener(tabsLayout, Current)
}
} }
// Remove removes view from list and return it // Remove removes view from list and return it
@ -457,9 +453,7 @@ func (tabsLayout *tabsLayoutData) RemoveView(index int) View {
tabsLayout.Set(Current, newCurrent) tabsLayout.Set(Current, newCurrent)
} }
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.Session()) updateInnerHTML(tabsLayout.htmlID(), tabsLayout.Session())
if listener, ok := tabsLayout.changeListener[Content]; ok { tabsLayout.contentChanged()
listener(tabsLayout, Content)
}
} else if newCurrent != oldCurrent { } else if newCurrent != oldCurrent {
tabsLayout.setRaw(Current, newCurrent) tabsLayout.setRaw(Current, newCurrent)
} }

View File

@ -324,7 +324,7 @@ func (picker *timePickerData) handleCommand(self View, command PropertyName, dat
listener.Run(picker, value, oldValue) listener.Run(picker, value, oldValue)
} }
if listener, ok := picker.changeListener[TimePickerValue]; ok { if listener, ok := picker.changeListener[TimePickerValue]; ok {
listener(picker, TimePickerValue) listener.Run(picker, TimePickerValue)
} }
} }

81
view.go
View File

@ -30,6 +30,8 @@ func (frame Frame) Bottom() float64 {
return frame.Top + frame.Height return frame.Top + frame.Height
} }
const changeListeners PropertyName = "change-listeners"
// View represents a base view interface // View represents a base view interface
type View interface { type View interface {
ViewStyle ViewStyle
@ -66,8 +68,18 @@ type View interface {
// a description of the error is written to the log // a description of the error is written to the log
SetAnimated(tag PropertyName, value any, animation AnimationProperty) bool SetAnimated(tag PropertyName, value any, animation AnimationProperty) bool
// SetChangeListener set the function to track the change of the View property // SetChangeListener set the function (the second argument) to track the change of the View property (the first argument).
SetChangeListener(tag PropertyName, listener func(View, PropertyName)) //
// Allowed listener function formats:
//
// func(view rui.View, property rui.PropertyName)
// func(view rui.View)
// func(property rui.PropertyName)
// func()
// string
//
// If the second argument is given as a string, it specifies the name of the binding function.
SetChangeListener(tag PropertyName, listener any) bool
// HasFocus returns 'true' if the view has focus // HasFocus returns 'true' if the view has focus
HasFocus() bool HasFocus() bool
@ -110,7 +122,7 @@ type viewData struct {
_htmlID string _htmlID string
parentID string parentID string
systemClass string systemClass string
changeListener map[PropertyName]func(View, PropertyName) changeListener map[PropertyName]oneArgListener[View, PropertyName]
singleTransition map[PropertyName]AnimationProperty singleTransition map[PropertyName]AnimationProperty
addCSS map[string]string addCSS map[string]string
frame Frame frame Frame
@ -162,7 +174,7 @@ func (view *viewData) init(session Session) {
view.changed = view.propertyChanged view.changed = view.propertyChanged
view.tag = "View" view.tag = "View"
view.session = session view.session = session
view.changeListener = map[PropertyName]func(View, PropertyName){} view.changeListener = map[PropertyName]oneArgListener[View, PropertyName]{}
view.addCSS = map[string]string{} view.addCSS = map[string]string{}
//view.animation = map[string]AnimationEndListener{} //view.animation = map[string]AnimationEndListener{}
view.singleTransition = map[PropertyName]AnimationProperty{} view.singleTransition = map[PropertyName]AnimationProperty{}
@ -242,11 +254,8 @@ func (view *viewData) Remove(tag PropertyName) {
if view.created && len(changedTags) > 0 { if view.created && len(changedTags) > 0 {
for _, tag := range changedTags { for _, tag := range changedTags {
view.changed(tag) view.changed(tag)
}
for _, tag := range changedTags {
if listener, ok := view.changeListener[tag]; ok { if listener, ok := view.changeListener[tag]; ok {
listener(view, tag) listener.Run(view, tag)
} }
} }
} }
@ -273,11 +282,8 @@ func (view *viewData) Set(tag PropertyName, value any) bool {
if view.created && len(changedTags) > 0 { if view.created && len(changedTags) > 0 {
for _, tag := range changedTags { for _, tag := range changedTags {
view.changed(tag) view.changed(tag)
}
for _, tag := range changedTags {
if listener, ok := view.changeListener[tag]; ok { if listener, ok := view.changeListener[tag]; ok {
listener(view, tag) listener.Run(view, tag)
} }
} }
} }
@ -295,7 +301,8 @@ func normalizeViewTag(tag PropertyName) PropertyName {
} }
func (view *viewData) getFunc(tag PropertyName) any { func (view *viewData) getFunc(tag PropertyName) any {
if tag == ID { switch tag {
case ID:
if id := view.ID(); id != "" { if id := view.ID(); id != "" {
return id return id
} else { } else {
@ -361,6 +368,29 @@ func (view *viewData) setFunc(tag PropertyName, value any) []PropertyName {
view.setRaw(Binding, value) view.setRaw(Binding, value)
return []PropertyName{Binding} return []PropertyName{Binding}
case changeListeners:
switch value := value.(type) {
case DataObject:
for i := range value.PropertyCount() {
node := value.Property(i)
if node.Type() == TextNode {
if text := node.Text(); text != "" {
view.changeListener[PropertyName(node.Tag())] = newOneArgListenerBinding[View, PropertyName](text)
}
}
}
if len(view.changeListener) > 0 {
view.setRaw(changeListeners, view.changeListener)
}
return []PropertyName{tag}
case DataNode:
if value.Type() == ObjectNode {
return view.setFunc(tag, value.Object())
}
}
return []PropertyName{}
case Animation: case Animation:
oldAnimations := []AnimationProperty{} oldAnimations := []AnimationProperty{}
if val := view.getRaw(Animation); val != nil { if val := view.getRaw(Animation); val != nil {
@ -1251,12 +1281,33 @@ func (view *viewData) handleCommand(self View, command PropertyName, data DataOb
} }
func (view *viewData) SetChangeListener(tag PropertyName, listener func(View, PropertyName)) { func (view *viewData) SetChangeListener(tag PropertyName, listener any) bool {
if listener == nil { if listener == nil {
delete(view.changeListener, tag) delete(view.changeListener, tag)
} else { } else {
view.changeListener[tag] = listener switch listener := listener.(type) {
case func():
view.changeListener[tag] = newOneArgListener0[View, PropertyName](listener)
case func(View):
view.changeListener[tag] = newOneArgListenerV[View, PropertyName](listener)
case func(PropertyName):
view.changeListener[tag] = newOneArgListenerE[View](listener)
case func(View, PropertyName):
view.changeListener[tag] = newOneArgListenerVE(listener)
case string:
view.changeListener[tag] = newOneArgListenerBinding[View, PropertyName](listener)
default:
return false
}
view.setRaw(changeListeners, view.changeListener)
} }
return true
} }
func (view *viewData) HasFocus() bool { func (view *viewData) HasFocus() bool {

View File

@ -661,6 +661,14 @@ func supportedPropertyValue(value any) bool {
case []mediaPlayerErrorListener: case []mediaPlayerErrorListener:
return getMediaPlayerErrorListenerBinding(value) != "" return getMediaPlayerErrorListenerBinding(value) != ""
case map[PropertyName]oneArgListener[View, PropertyName]:
for _, listener := range value {
if text, ok := listener.rawListener().(string); ok && text != "" {
return true
}
}
return false
default: default:
return false return false
} }
@ -978,6 +986,23 @@ func writePropertyValue(buffer *strings.Builder, tag PropertyName, value any, in
case []mediaPlayerErrorListener: case []mediaPlayerErrorListener:
buffer.WriteString(getMediaPlayerErrorListenerBinding(value)) buffer.WriteString(getMediaPlayerErrorListenerBinding(value))
case map[PropertyName]oneArgListener[View, PropertyName]:
buffer.WriteString("_{")
for key, listener := range value {
if text, ok := listener.rawListener().(string); ok && text != "" {
buffer.WriteRune('\n')
buffer.WriteString(indent)
buffer.WriteRune('\t')
writeString(string(key))
buffer.WriteString(" = ")
writeString(text)
buffer.WriteRune(',')
}
buffer.WriteRune('\n')
buffer.WriteString(indent)
buffer.WriteRune('}')
}
} }
} }

View File

@ -85,10 +85,13 @@ func (container *viewsContainerData) Append(view View) {
viewHTML(view, buffer, "") viewHTML(view, buffer, "")
container.Session().appendToInnerHTML(container.htmlID(), buffer.String()) container.Session().appendToInnerHTML(container.htmlID(), buffer.String())
container.contentChanged()
}
}
if listener, ok := container.changeListener[Content]; ok { func (container *viewsContainerData) contentChanged() {
listener(container, Content) if listener, ok := container.changeListener[Content]; ok {
} listener.Run(container, Content)
} }
} }
@ -113,9 +116,7 @@ func (container *viewsContainerData) insert(view View, index int) bool {
func (container *viewsContainerData) Insert(view View, index int) { func (container *viewsContainerData) Insert(view View, index int) {
if container.insert(view, index) && container.created { if container.insert(view, index) && container.created {
updateInnerHTML(container.htmlID(), container.Session()) updateInnerHTML(container.htmlID(), container.Session())
if listener, ok := container.changeListener[Content]; ok { container.contentChanged()
listener(container, Content)
}
} }
} }
@ -149,9 +150,7 @@ func (container *viewsContainerData) RemoveView(index int) View {
view := container.removeView(index) view := container.removeView(index)
if view != nil && container.created { if view != nil && container.created {
container.Session().callFunc("removeView", view.htmlID()) container.Session().callFunc("removeView", view.htmlID())
if listener, ok := container.changeListener[Content]; ok { container.contentChanged()
listener(container, Content)
}
} }
return view return view
} }