Improved binding for 2 args listeners

This commit is contained in:
Alexei Anoshenko 2025-06-18 13:24:53 +03:00
parent 3c3c09b043
commit 4b00299878
11 changed files with 502 additions and 616 deletions

View File

@ -106,13 +106,13 @@ func (picker *colorPickerData) propertyChanged(tag PropertyName) {
color := GetColorPickerValue(picker)
picker.Session().callFunc("setInputValue", picker.htmlID(), color.rgbString())
if listeners := GetColorChangedListeners(picker); len(listeners) > 0 {
if listeners := getTwoArgEventListeners[ColorPicker, Color](picker, nil, ColorChangedEvent); len(listeners) > 0 {
oldColor := Color(0)
if value := picker.getRaw("old-color"); value != nil {
oldColor = value.(Color)
}
for _, listener := range listeners {
listener(picker, color, oldColor)
listener.Run(picker, color, oldColor)
}
}
@ -156,8 +156,8 @@ func (picker *colorPickerData) handleCommand(self View, command PropertyName, da
oldColor := GetColorPickerValue(picker)
picker.properties[ColorPickerValue] = color
if color != oldColor {
for _, listener := range GetColorChangedListeners(picker) {
listener(picker, color, oldColor)
for _, listener := range getTwoArgEventListeners[ColorPicker, Color](picker, nil, ColorChangedEvent) {
listener.Run(picker, color, oldColor)
}
if listener, ok := picker.changeListener[ColorPickerValue]; ok {
listener(picker, ColorPickerValue)
@ -194,8 +194,17 @@ func GetColorPickerValue(view View, subviewID ...string) Color {
// GetColorChangedListeners returns the ColorChangedListener list of an ColorPicker subview.
// If there are no listeners then the empty list is returned
//
// Result elements can be of the following types:
// - func(rui.ColorPicker, rui.Color, rui.Color),
// - func(rui.ColorPicker, rui.Color),
// - func(rui.ColorPicker),
// - func(rui.Color, rui.Color),
// - func(rui.Color),
// - func(),
// - string.
//
// The second argument (subviewID) specifies the path to the child element whose value needs to be returned.
// If it is not specified then a value from the first argument (view) is returned.
func GetColorChangedListeners(view View, subviewID ...string) []func(ColorPicker, Color, Color) {
return getTwoArgEventListeners[ColorPicker, Color](view, subviewID, ColorChangedEvent)
func GetColorChangedListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[ColorPicker, Color](view, subviewID, ColorChangedEvent)
}

View File

@ -274,7 +274,7 @@ func (picker *datePickerData) propertyChanged(tag PropertyName) {
date := GetDatePickerValue(picker)
session.callFunc("setInputValue", picker.htmlID(), date.Format(dateFormat))
if listeners := GetDateChangedListeners(picker); len(listeners) > 0 {
if listeners := getTwoArgEventListeners[DatePicker, time.Time](picker, nil, DateChangedEvent); len(listeners) > 0 {
oldDate := time.Now()
if value := picker.getRaw("old-date"); value != nil {
if date, ok := value.(time.Time); ok {
@ -282,7 +282,7 @@ func (picker *datePickerData) propertyChanged(tag PropertyName) {
}
}
for _, listener := range listeners {
listener(picker, date, oldDate)
listener.Run(picker, date, oldDate)
}
}
@ -348,8 +348,8 @@ func (picker *datePickerData) handleCommand(self View, command PropertyName, dat
oldValue := GetDatePickerValue(picker)
picker.properties[DatePickerValue] = value
if value != oldValue {
for _, listener := range GetDateChangedListeners(picker) {
listener(picker, value, oldValue)
for _, listener := range getTwoArgEventListeners[DatePicker, time.Time](picker, nil, DateChangedEvent) {
listener.Run(picker, value, oldValue)
}
if listener, ok := picker.changeListener[DatePickerValue]; ok {
listener(picker, DatePickerValue)
@ -445,8 +445,17 @@ func GetDatePickerValue(view View, subviewID ...string) time.Time {
// GetDateChangedListeners returns the DateChangedListener list of an DatePicker subview.
// If there are no listeners then the empty list is returned
//
// Result elements can be of the following types:
// - func(rui.DatePicker, time.Time, time.Time),
// - func(rui.DatePicker, time.Time),
// - func(rui.DatePicker),
// - func(time.Time, time.Time),
// - func(time.Time),
// - func(),
// - string.
//
// The second argument (subviewID) specifies the path to the child element whose value needs to be returned.
// If it is not specified then a value from the first argument (view) is returned.
func GetDateChangedListeners(view View, subviewID ...string) []func(DatePicker, time.Time, time.Time) {
return getTwoArgEventListeners[DatePicker, time.Time](view, subviewID, DateChangedEvent)
func GetDateChangedListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[DatePicker, time.Time](view, subviewID, DateChangedEvent)
}

View File

@ -104,8 +104,8 @@ func (list *dropDownListData) propertyChanged(tag PropertyName) {
list.Session().callFunc("selectDropDownListItem", list.htmlID(), current)
oldCurrent, _ := intProperty(list, "old-current", list.Session(), -1)
for _, listener := range GetDropDownListeners(list) {
listener(list, current, oldCurrent)
for _, listener := range getTwoArgEventListeners[DropDownList, int](list, nil, DropDownEvent) {
listener.Run(list, current, oldCurrent)
}
default:
@ -245,8 +245,8 @@ func (list *dropDownListData) handleCommand(self View, command PropertyName, dat
if GetCurrent(list) != number && number >= 0 && number < len(items) {
old := GetCurrent(list)
list.properties[Current] = number
for _, listener := range GetDropDownListeners(list) {
listener(list, number, old)
for _, listener := range getTwoArgEventListeners[DropDownList, int](list, nil, DropDownEvent) {
listener.Run(list, number, old)
}
if listener, ok := list.changeListener[Current]; ok {
listener(list, Current)
@ -265,10 +265,19 @@ func (list *dropDownListData) handleCommand(self View, command PropertyName, dat
// GetDropDownListeners returns the "drop-down-event" listener list. If there are no listeners then the empty list is returned.
//
// Result elements can be of the following types:
// - func(rui.DropDownList, int, int),
// - func(rui.DropDownList, int),
// - func(rui.DropDownList),
// - func(int, int),
// - func(int),
// - func(),
// - string.
//
// The second argument (subviewID) specifies the path to the child element whose value needs to be returned.
// If it is not specified then a value from the first argument (view) is returned.
func GetDropDownListeners(view View, subviewID ...string) []func(DropDownList, int, int) {
return getTwoArgEventListeners[DropDownList, int](view, subviewID, DropDownEvent)
func GetDropDownListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[DropDownList, int](view, subviewID, DropDownEvent)
}
// GetDropDownItems return the DropDownList items list.

View File

@ -268,8 +268,8 @@ func (edit *editViewData) AppendText(text string) {
}
func (edit *editViewData) textChanged(newText, oldText string) {
for _, listener := range GetTextChangedListeners(edit) {
listener(edit, newText, oldText)
for _, listener := range getTwoArgEventListeners[EditView, string](edit, nil, EditTextChangedEvent) {
listener.Run(edit, newText, oldText)
}
if listener, ok := edit.changeListener[Text]; ok {
listener(edit, Text)
@ -461,10 +461,19 @@ func IsSpellcheck(view View, subviewID ...string) bool {
// GetTextChangedListeners returns the TextChangedListener list of an EditView or MultiLineEditView subview.
// If there are no listeners then the empty list is returned
//
// Result elements can be of the following types:
// - func(rui.EditView, string, string),
// - func(rui.EditView, string),
// - func(rui.EditView),
// - func(string, string),
// - func(string),
// - func(),
// - string.
//
// The second argument (subviewID) specifies the path to the child element whose value needs to be returned.
// If it is not specified then a value from the first argument (view) is returned.
func GetTextChangedListeners(view View, subviewID ...string) []func(EditView, string, string) {
return getTwoArgEventListeners[EditView, string](view, subviewID, EditTextChangedEvent)
func GetTextChangedListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[EditView, string](view, subviewID, EditTextChangedEvent)
}
// GetEditViewType returns a value of the Type property of EditView.

583
events.go
View File

@ -41,161 +41,32 @@ var eventJsFunc = map[PropertyName]struct{ jsEvent, jsFunc string }{
DragLeaveEvent: {jsEvent: "ondragleave", jsFunc: "dragLeaveEvent"},
}
/*
type oneArgListener[V View, E any] interface {
call(V, E)
listener() func(V, E)
rawListener() any
func viewEventsHtml[T any](view View, events []PropertyName, buffer *strings.Builder) {
for _, tag := range events {
if js, ok := eventJsFunc[tag]; ok {
if value := getOneArgEventListeners[View, T](view, nil, tag); len(value) > 0 {
buffer.WriteString(js.jsEvent)
buffer.WriteString(`="`)
buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
}
type oneArgListener0[V View, E any] struct {
fn func()
}
type oneArgListenerV[V View, E any] struct {
fn func(V)
}
type oneArgListenerE[V View, E any] struct {
fn func(E)
}
type oneArgListenerVE[V View, E any] struct {
fn func(V, E)
}
type oneArgListenerBinding[V View, E any] struct {
name string
}
func newOneArgListener0[V View, E any](fn func()) oneArgListener[V, E] {
obj := new(oneArgListener0[V, E])
obj.fn = fn
return obj
}
func (data *oneArgListener0[V, E]) call(_ V, _ E) {
data.fn()
}
func (data *oneArgListener0[V, E]) listener() func(V, E) {
return data.call
}
func (data *oneArgListener0[V, E]) rawListener() any {
return data.fn
}
func newOneArgListenerV[V View, E any](fn func(V)) oneArgListener[V, E] {
obj := new(oneArgListenerV[V, E])
obj.fn = fn
return obj
}
func (data *oneArgListenerV[V, E]) call(view V, _ E) {
data.fn(view)
}
func (data *oneArgListenerV[V, E]) listener() func(V, E) {
return data.call
}
func (data *oneArgListenerV[V, E]) rawListener() any {
return data.fn
}
func newOneArgListenerE[V View, E any](fn func(E)) oneArgListener[V, E] {
obj := new(oneArgListenerE[V, E])
obj.fn = fn
return obj
}
func (data *oneArgListenerE[V, E]) call(_ V, event E) {
data.fn(event)
}
func (data *oneArgListenerE[V, E]) listener() func(V, E) {
return data.call
}
func (data *oneArgListenerE[V, E]) rawListener() any {
return data.fn
}
func newOneArgListenerVE[V View, E any](fn func(V, E)) oneArgListener[V, E] {
obj := new(oneArgListenerVE[V, E])
obj.fn = fn
return obj
}
func (data *oneArgListenerVE[V, E]) call(view V, arg E) {
data.fn(view, arg)
}
func (data *oneArgListenerVE[V, E]) listener() func(V, E) {
return data.fn
}
func (data *oneArgListenerVE[V, E]) rawListener() any {
return data.fn
}
func newOneArgListenerBinding[V View, E any](name string) oneArgListener[V, E] {
obj := new(oneArgListenerBinding[V, E])
obj.name = name
return obj
}
func (data *oneArgListenerBinding[V, E]) call(view V, event E) {
bind := view.binding()
if bind == nil {
ErrorLogF(`There is no a binding object for call "%s"`, data.name)
return
}
val := reflect.ValueOf(bind)
method := val.MethodByName(data.name)
if !method.IsValid() {
ErrorLogF(`The "%s" method is not valid`, data.name)
return
}
methodType := method.Type()
var args []reflect.Value = nil
switch methodType.NumIn() {
case 0:
args = []reflect.Value{}
case 1:
inType := methodType.In(0)
if inType == reflect.TypeOf(view) {
args = []reflect.Value{reflect.ValueOf(view)}
} else if inType == reflect.TypeOf(event) {
args = []reflect.Value{reflect.ValueOf(event)}
}
case 2:
if methodType.In(0) == reflect.TypeOf(view) && methodType.In(1) == reflect.TypeOf(event) {
args = []reflect.Value{reflect.ValueOf(view), reflect.ValueOf(event)}
}
}
if args != nil {
method.Call(args)
func updateEventListenerHtml(view View, tag PropertyName) {
if js, ok := eventJsFunc[tag]; ok {
value := view.getRaw(tag)
session := view.Session()
htmlID := view.htmlID()
if value == nil {
session.removeProperty(view.htmlID(), js.jsEvent)
} else {
ErrorLogF(`Unsupported prototype of "%s" method`, data.name)
session.updateProperty(htmlID, js.jsEvent, js.jsFunc+"(this, event)")
}
}
func (data *oneArgListenerBinding[V, E]) listener() func(V, E) {
return data.call
}
func (data *oneArgListenerBinding[V, E]) rawListener() any {
return data.name
}
*/
func valueToNoArgEventListeners[V any](view View, value any) ([]func(V), bool) {
if value == nil {
return nil, true
@ -303,326 +174,6 @@ func valueToNoArgEventListeners[V any](view View, value any) ([]func(V), bool) {
return nil, false
}
/*
func valueToOneArgEventListeners[V View, E any](view View, value any) ([]oneArgListener[V, E], bool) {
if value == nil {
return nil, true
}
switch value := value.(type) {
case string:
return []oneArgListener[V, E]{newOneArgListenerBinding[V, E](value)}, true
case func(V, E):
return []oneArgListener[V, E]{newOneArgListenerVE[V, E](value)}, true
case func(V):
return []oneArgListener[V, E]{newOneArgListenerV[V, E](value)}, true
case func(E):
return []oneArgListener[V, E]{newOneArgListenerE[V, E](value)}, true
case func():
return []oneArgListener[V, E]{newOneArgListener0[V, E](value)}, true
case []func(V, E):
result := make([]oneArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newOneArgListenerVE[V, E](fn))
}
}
return result, len(result) > 0
case []func(E):
result := make([]oneArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newOneArgListenerE[V, E](fn))
}
}
return result, len(result) > 0
case []func(V):
result := make([]oneArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newOneArgListenerV[V, E](fn))
}
}
return result, len(result) > 0
case []func():
result := make([]oneArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newOneArgListener0[V, E](fn))
}
}
return result, len(result) > 0
case []any:
result := make([]oneArgListener[V, E], 0, len(value))
for _, v := range value {
if v != nil {
switch v := v.(type) {
case func(V, E):
result = append(result, newOneArgListenerVE[V, E](v))
case func(E):
result = append(result, newOneArgListenerE[V, E](v))
case func(V):
result = append(result, newOneArgListenerV[V, E](v))
case func():
result = append(result, newOneArgListener0[V, E](v))
case string:
result = append(result, newOneArgListenerBinding[V, E](v))
default:
return nil, false
}
}
}
return result, len(result) > 0
}
return nil, false
}
*/
func valueToTwoArgEventListeners[V View, E any](view View, value any) ([]func(V, E, E), bool) {
if value == nil {
return nil, true
}
switch value := value.(type) {
case string:
fn := func(view V, val1 E, val2 E) {
bind := view.binding()
if bind == nil {
ErrorLogF(`There is no a binding object for call "%s"`, value)
return
}
val := reflect.ValueOf(bind)
method := val.MethodByName(value)
if !method.IsValid() {
ErrorLogF(`The "%s" method is not valid`, value)
return
}
methodType := method.Type()
var args []reflect.Value = nil
switch methodType.NumIn() {
case 0:
args = []reflect.Value{}
case 1:
inType := methodType.In(0)
if inType == reflect.TypeOf(view) {
args = []reflect.Value{reflect.ValueOf(view)}
} else if inType == reflect.TypeOf(val1) {
args = []reflect.Value{reflect.ValueOf(val1)}
}
case 2:
valType := reflect.TypeOf(val1)
if methodType.In(0) == reflect.TypeOf(view) && methodType.In(1) == valType {
args = []reflect.Value{reflect.ValueOf(view), reflect.ValueOf(val1)}
} else if methodType.In(0) == valType && methodType.In(1) == valType {
args = []reflect.Value{reflect.ValueOf(val1), reflect.ValueOf(val2)}
}
case 3:
valType := reflect.TypeOf(val1)
if methodType.In(0) == reflect.TypeOf(view) && methodType.In(1) == valType && methodType.In(2) == valType {
args = []reflect.Value{reflect.ValueOf(view), reflect.ValueOf(val1), reflect.ValueOf(val2)}
}
}
if args != nil {
method.Call(args)
} else {
ErrorLogF(`Unsupported prototype of "%s" method`, value)
}
}
return []func(V, E, E){fn}, true
case func(V, E, E):
return []func(V, E, E){value}, true
case func(V, E):
fn := func(v V, val, _ E) {
value(v, val)
}
return []func(V, E, E){fn}, true
case func(E, E):
fn := func(_ V, val, old E) {
value(val, old)
}
return []func(V, E, E){fn}, true
case func(E):
fn := func(_ V, val, _ E) {
value(val)
}
return []func(V, E, E){fn}, true
case func(V):
fn := func(v V, _, _ E) {
value(v)
}
return []func(V, E, E){fn}, true
case func():
fn := func(V, E, E) {
value()
}
return []func(V, E, E){fn}, true
case []func(V, E, E):
if len(value) == 0 {
return nil, true
}
for _, fn := range value {
if fn == nil {
return nil, false
}
}
return value, true
case []func(V, E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(view V, val, _ E) {
fn(view, val)
}
}
return listeners, true
case []func(E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(_ V, val, _ E) {
fn(val)
}
}
return listeners, true
case []func(E, E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(_ V, val, old E) {
fn(val, old)
}
}
return listeners, true
case []func(V):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(view V, _, _ E) {
fn(view)
}
}
return listeners, true
case []func():
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(V, E, E) {
fn()
}
}
return listeners, true
case []any:
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, v := range value {
if v == nil {
return nil, false
}
switch fn := v.(type) {
case func(V, E, E):
listeners[i] = fn
case func(V, E):
listeners[i] = func(view V, val, _ E) {
fn(view, val)
}
case func(E, E):
listeners[i] = func(_ V, val, old E) {
fn(val, old)
}
case func(E):
listeners[i] = func(_ V, val, _ E) {
fn(val)
}
case func(V):
listeners[i] = func(view V, _, _ E) {
fn(view)
}
case func():
listeners[i] = func(V, E, E) {
fn()
}
default:
return nil, false
}
}
return listeners, true
}
return nil, false
}
func getNoArgEventListeners[V View](view View, subviewID []string, tag PropertyName) []func(V) {
if view = getSubview(view, subviewID); view != nil {
if value := view.Get(tag); value != nil {
@ -634,38 +185,6 @@ func getNoArgEventListeners[V View](view View, subviewID []string, tag PropertyN
return []func(V){}
}
/*
func getOneArgEventListeners[V View, E any](view View, subviewID []string, tag PropertyName) []oneArgListener[V, E] {
if view = getSubview(view, subviewID); view != nil {
if value := view.Get(tag); value != nil {
if result, ok := value.([]oneArgListener[V, E]); ok {
return result
}
}
}
return []oneArgListener[V, E]{}
}
func getOneArgEventRawListeners[V View, E any](view View, subviewID []string, tag PropertyName) []any {
listeners := getOneArgEventListeners[V, E](view, subviewID, tag)
result := make([]any, len(listeners))
for i, l := range listeners {
result[i] = l.rawListener()
}
return result
}
*/
func getTwoArgEventListeners[V View, E any](view View, subviewID []string, tag PropertyName) []func(V, E, E) {
if view = getSubview(view, subviewID); view != nil {
if value := view.Get(tag); value != nil {
if result, ok := value.([]func(V, E, E)); ok {
return result
}
}
}
return []func(V, E, E){}
}
func setNoArgEventListener[V View](view View, tag PropertyName, value any) []PropertyName {
if listeners, ok := valueToNoArgEventListeners[V](view, value); ok {
if len(listeners) > 0 {
@ -680,73 +199,3 @@ func setNoArgEventListener[V View](view View, tag PropertyName, value any) []Pro
notCompatibleType(tag, value)
return nil
}
/*
func setOneArgEventListener[V View, T any](view View, tag PropertyName, value any) []PropertyName {
if listeners, ok := valueToOneArgEventListeners[V, T](view, value); ok {
if len(listeners) > 0 {
view.setRaw(tag, listeners)
} else if view.getRaw(tag) != nil {
view.setRaw(tag, nil)
} else {
return []PropertyName{}
}
return []PropertyName{tag}
}
notCompatibleType(tag, value)
return nil
}
*/
func setTwoArgEventListener[V View, T any](view View, tag PropertyName, value any) []PropertyName {
listeners, ok := valueToTwoArgEventListeners[V, T](view, value)
if !ok {
notCompatibleType(tag, value)
return nil
}
if len(listeners) > 0 {
view.setRaw(tag, listeners)
} else if view.getRaw(tag) != nil {
view.setRaw(tag, nil)
} else {
return []PropertyName{}
}
return []PropertyName{tag}
}
func viewEventsHtml[T any](view View, events []PropertyName, buffer *strings.Builder) {
for _, tag := range events {
if js, ok := eventJsFunc[tag]; ok {
if value := getOneArgEventListeners[View, T](view, nil, tag); len(value) > 0 {
buffer.WriteString(js.jsEvent)
buffer.WriteString(`="`)
buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
}
}
/*if value := view.getRaw(tag); value != nil {
if js, ok := eventJsFunc[tag]; ok {
if listeners, ok := value.([] func(View, T)); ok && len(listeners) > 0 {
buffer.WriteString(js.jsEvent)
buffer.WriteString(`="`)
buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
}
}
}
*/
}
}
func updateEventListenerHtml(view View, tag PropertyName) {
if js, ok := eventJsFunc[tag]; ok {
value := view.getRaw(tag)
session := view.Session()
htmlID := view.htmlID()
if value == nil {
session.removeProperty(view.htmlID(), js.jsEvent)
} else {
session.updateProperty(htmlID, js.jsEvent, js.jsFunc+"(this, event)")
}
}
}

339
events2arg.go Normal file
View File

@ -0,0 +1,339 @@
package rui
import "reflect"
type twoArgListener[V View, E any] interface {
Run(V, E, E)
rawListener() any
}
type twoArgListener0[V View, E any] struct {
fn func()
}
type twoArgListenerV[V View, E any] struct {
fn func(V)
}
type twoArgListenerE[V View, E any] struct {
fn func(E)
}
type twoArgListenerVE[V View, E any] struct {
fn func(V, E)
}
type twoArgListenerEE[V View, E any] struct {
fn func(E, E)
}
type twoArgListenerVEE[V View, E any] struct {
fn func(V, E, E)
}
type twoArgListenerBinding[V View, E any] struct {
name string
}
func newTwoArgListener0[V View, E any](fn func()) twoArgListener[V, E] {
obj := new(twoArgListener0[V, E])
obj.fn = fn
return obj
}
func (data *twoArgListener0[V, E]) Run(_ V, _ E, _ E) {
data.fn()
}
func (data *twoArgListener0[V, E]) rawListener() any {
return data.fn
}
func newTwoArgListenerV[V View, E any](fn func(V)) twoArgListener[V, E] {
obj := new(twoArgListenerV[V, E])
obj.fn = fn
return obj
}
func (data *twoArgListenerV[V, E]) Run(view V, _ E, _ E) {
data.fn(view)
}
func (data *twoArgListenerV[V, E]) rawListener() any {
return data.fn
}
func newTwoArgListenerE[V View, E any](fn func(E)) twoArgListener[V, E] {
obj := new(twoArgListenerE[V, E])
obj.fn = fn
return obj
}
func (data *twoArgListenerE[V, E]) Run(_ V, arg E, _ E) {
data.fn(arg)
}
func (data *twoArgListenerE[V, E]) rawListener() any {
return data.fn
}
func newTwoArgListenerVE[V View, E any](fn func(V, E)) twoArgListener[V, E] {
obj := new(twoArgListenerVE[V, E])
obj.fn = fn
return obj
}
func (data *twoArgListenerVE[V, E]) Run(view V, arg E, _ E) {
data.fn(view, arg)
}
func (data *twoArgListenerVE[V, E]) rawListener() any {
return data.fn
}
func newTwoArgListenerEE[V View, E any](fn func(E, E)) twoArgListener[V, E] {
obj := new(twoArgListenerEE[V, E])
obj.fn = fn
return obj
}
func (data *twoArgListenerEE[V, E]) Run(_ V, arg1 E, arg2 E) {
data.fn(arg1, arg2)
}
func (data *twoArgListenerEE[V, E]) rawListener() any {
return data.fn
}
func newTwoArgListenerVEE[V View, E any](fn func(V, E, E)) twoArgListener[V, E] {
obj := new(twoArgListenerVEE[V, E])
obj.fn = fn
return obj
}
func (data *twoArgListenerVEE[V, E]) Run(view V, arg1 E, arg2 E) {
data.fn(view, arg1, arg2)
}
func (data *twoArgListenerVEE[V, E]) rawListener() any {
return data.fn
}
func newTwoArgListenerBinding[V View, E any](name string) twoArgListener[V, E] {
obj := new(twoArgListenerBinding[V, E])
obj.name = name
return obj
}
func (data *twoArgListenerBinding[V, E]) Run(view V, arg1 E, arg2 E) {
bind := view.binding()
if bind == nil {
ErrorLogF(`There is no a binding object for call "%s"`, data.name)
return
}
val := reflect.ValueOf(bind)
method := val.MethodByName(data.name)
if !method.IsValid() {
ErrorLogF(`The "%s" method is not valid`, data.name)
return
}
methodType := method.Type()
var args []reflect.Value = nil
switch methodType.NumIn() {
case 0:
args = []reflect.Value{}
case 1:
inType := methodType.In(0)
if inType == reflect.TypeOf(view) {
args = []reflect.Value{reflect.ValueOf(view)}
} else if inType == reflect.TypeOf(arg1) {
args = []reflect.Value{reflect.ValueOf(arg1)}
}
case 2:
valType := reflect.TypeOf(arg1)
if methodType.In(0) == reflect.TypeOf(view) && methodType.In(1) == valType {
args = []reflect.Value{reflect.ValueOf(view), reflect.ValueOf(arg1)}
} else if methodType.In(0) == valType && methodType.In(1) == valType {
args = []reflect.Value{reflect.ValueOf(arg1), reflect.ValueOf(arg2)}
}
case 3:
valType := reflect.TypeOf(arg1)
if methodType.In(0) == reflect.TypeOf(view) && methodType.In(1) == valType && methodType.In(2) == valType {
args = []reflect.Value{reflect.ValueOf(view), reflect.ValueOf(arg1), reflect.ValueOf(arg2)}
}
}
if args != nil {
method.Call(args)
} else {
ErrorLogF(`Unsupported prototype of "%s" method`, data.name)
}
}
func (data *twoArgListenerBinding[V, E]) rawListener() any {
return data.name
}
func valueToTwoArgEventListeners[V View, E any](value any) ([]twoArgListener[V, E], bool) {
if value == nil {
return nil, true
}
switch value := value.(type) {
case []twoArgListener[V, E]:
return value, true
case twoArgListener[V, E]:
return []twoArgListener[V, E]{value}, true
case string:
return []twoArgListener[V, E]{newTwoArgListenerBinding[V, E](value)}, true
case func(V, E):
return []twoArgListener[V, E]{newTwoArgListenerVE(value)}, true
case func(V):
return []twoArgListener[V, E]{newTwoArgListenerV[V, E](value)}, true
case func(E):
return []twoArgListener[V, E]{newTwoArgListenerE[V](value)}, true
case func():
return []twoArgListener[V, E]{newTwoArgListener0[V, E](value)}, true
case func(E, E):
return []twoArgListener[V, E]{newTwoArgListenerEE[V](value)}, true
case func(V, E, E):
return []twoArgListener[V, E]{newTwoArgListenerVEE(value)}, true
case []func(V, E):
result := make([]twoArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newTwoArgListenerVE(fn))
}
}
return result, len(result) > 0
case []func(E):
result := make([]twoArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newTwoArgListenerE[V](fn))
}
}
return result, len(result) > 0
case []func(V):
result := make([]twoArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newTwoArgListenerV[V, E](fn))
}
}
return result, len(result) > 0
case []func():
result := make([]twoArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newTwoArgListener0[V, E](fn))
}
}
return result, len(result) > 0
case []func(E, E):
result := make([]twoArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newTwoArgListenerEE[V](fn))
}
}
return result, len(result) > 0
case []func(V, E, E):
result := make([]twoArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newTwoArgListenerVEE(fn))
}
}
return result, len(result) > 0
case []any:
result := make([]twoArgListener[V, E], 0, len(value))
for _, v := range value {
if v != nil {
switch v := v.(type) {
case func(V, E):
result = append(result, newTwoArgListenerVE(v))
case func(E):
result = append(result, newTwoArgListenerE[V](v))
case func(V):
result = append(result, newTwoArgListenerV[V, E](v))
case func():
result = append(result, newTwoArgListener0[V, E](v))
case func(E, E):
result = append(result, newTwoArgListenerEE[V](v))
case func(V, E, E):
result = append(result, newTwoArgListenerVEE(v))
case string:
result = append(result, newTwoArgListenerBinding[V, E](v))
default:
return nil, false
}
}
}
return result, len(result) > 0
}
return nil, false
}
func setTwoArgEventListener[V View, T any](view View, tag PropertyName, value any) []PropertyName {
if listeners, ok := valueToTwoArgEventListeners[V, T](value); ok {
if len(listeners) > 0 {
view.setRaw(tag, listeners)
} else if view.getRaw(tag) != nil {
view.setRaw(tag, nil)
} else {
return []PropertyName{}
}
return []PropertyName{tag}
}
notCompatibleType(tag, value)
return nil
}
func getTwoArgEventListeners[V View, E any](view View, subviewID []string, tag PropertyName) []twoArgListener[V, E] {
if view = getSubview(view, subviewID); view != nil {
if value := view.Get(tag); value != nil {
if result, ok := value.([]twoArgListener[V, E]); ok {
return result
}
}
}
return []twoArgListener[V, E]{}
}
func getTwoArgEventRawListeners[V View, E any](view View, subviewID []string, tag PropertyName) []any {
listeners := getTwoArgEventListeners[V, E](view, subviewID, tag)
result := make([]any, len(listeners))
for i, l := range listeners {
result[i] = l.rawListener()
}
return result
}

View File

@ -201,7 +201,7 @@ func (picker *numberPickerData) propertyChanged(tag PropertyName) {
format := picker.numberFormat()
picker.Session().callFunc("setInputValue", picker.htmlID(), fmt.Sprintf(format, value))
if listeners := GetNumberChangedListeners(picker); len(listeners) > 0 {
if listeners := getTwoArgEventListeners[NumberPicker, float64](picker, nil, NumberChangedEvent); len(listeners) > 0 {
old := 0.0
if val := picker.getRaw("old-number"); val != nil {
if n, ok := val.(float64); ok {
@ -210,7 +210,7 @@ func (picker *numberPickerData) propertyChanged(tag PropertyName) {
}
if old != value {
for _, listener := range listeners {
listener(picker, value, old)
listener.Run(picker, value, old)
}
}
}
@ -280,8 +280,8 @@ func (picker *numberPickerData) handleCommand(self View, command PropertyName, d
oldValue := GetNumberPickerValue(picker)
picker.properties[NumberPickerValue] = text
if value != oldValue {
for _, listener := range GetNumberChangedListeners(picker) {
listener(picker, value, oldValue)
for _, listener := range getTwoArgEventListeners[NumberPicker, float64](picker, nil, NumberChangedEvent) {
listener.Run(picker, value, oldValue)
}
if listener, ok := picker.changeListener[NumberPickerValue]; ok {
listener(picker, NumberPickerValue)
@ -359,10 +359,19 @@ func GetNumberPickerValue(view View, subviewID ...string) float64 {
// GetNumberChangedListeners returns the NumberChangedListener list of an NumberPicker subview.
// If there are no listeners then the empty list is returned
//
// Result elements can be of the following types:
// - func(rui.NumberPicker, float64, float64),
// - func(rui.NumberPicker, float64),
// - func(rui.NumberPicker),
// - func(float64, float64),
// - func(float64),
// - func(),
// - string.
//
// The second argument (subviewID) specifies the path to the child element whose value needs to be returned.
// If it is not specified then a value from the first argument (view) is returned.
func GetNumberChangedListeners(view View, subviewID ...string) []func(NumberPicker, float64, float64) {
return getTwoArgEventListeners[NumberPicker, float64](view, subviewID, NumberChangedEvent)
func GetNumberChangedListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[NumberPicker, float64](view, subviewID, NumberChangedEvent)
}
// GetNumberPickerPrecision returns the precision of displaying fractional part in editor of NumberPicker subview.

View File

@ -845,8 +845,17 @@ func (table *tableViewData) propertyChanged(tag PropertyName) {
current := tableViewCurrent(table)
session.callFunc("setTableCellCursorByID", htmlID, current.Row, current.Column)
for _, listener := range getTwoArgEventListeners[TableView, int](table, nil, TableCellSelectedEvent) {
listener.Run(table, current.Row, current.Column)
}
case RowSelection:
session.callFunc("setTableRowCursorByID", htmlID, tableViewCurrent(table).Row)
current := tableViewCurrent(table)
session.callFunc("setTableRowCursorByID", htmlID, current.Row)
for _, listener := range getOneArgEventListeners[TableView, int](table, nil, TableRowSelectedEvent) {
listener.Run(table, current.Row)
}
}
case Gap:
@ -1687,8 +1696,8 @@ func (table *tableViewData) handleCommand(self View, command PropertyName, data
listener(table, Current)
}
for _, listener := range GetTableCellSelectedListeners(table) {
listener(table, row, column)
for _, listener := range getTwoArgEventListeners[TableView, int](table, nil, TableCellSelectedEvent) {
listener.Run(table, row, column)
}
}
}
@ -1704,8 +1713,8 @@ func (table *tableViewData) handleCommand(self View, command PropertyName, data
case "cellClick":
if row, ok := dataIntProperty(data, "row"); ok {
if column, ok := dataIntProperty(data, "column"); ok {
for _, listener := range GetTableCellClickedListeners(table) {
listener(table, row, column)
for _, listener := range getTwoArgEventListeners[TableView, int](table, nil, TableCellClickedEvent) {
listener.Run(table, row, column)
}
}
}

View File

@ -149,19 +149,37 @@ func GetTableCurrent(view View, subviewID ...string) CellIndex {
// GetTableCellClickedListeners returns listeners of event which occurs when the user clicks on a table cell.
// If there are no listeners then the empty list is returned.
//
// Result elements can be of the following types:
// - func(rui.TableView, int, int),
// - func(rui.TableView, int),
// - func(rui.TableView),
// - func(int, int),
// - func(int),
// - func(),
// - string.
//
// The second argument (subviewID) specifies the path to the child element whose value needs to be returned.
// If it is not specified then a value from the first argument (view) is returned.
func GetTableCellClickedListeners(view View, subviewID ...string) []func(TableView, int, int) {
return getTwoArgEventListeners[TableView, int](view, subviewID, TableCellClickedEvent)
func GetTableCellClickedListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[TableView, int](view, subviewID, TableCellClickedEvent)
}
// GetTableCellSelectedListeners returns listeners of event which occurs when a table cell becomes selected.
// If there are no listeners then the empty list is returned.
//
// Result elements can be of the following types:
// - func(rui.TableView, int, int),
// - func(rui.TableView, int),
// - func(rui.TableView),
// - func(int, int),
// - func(int),
// - func(),
// - string.
//
// The second argument (subviewID) specifies the path to the child element whose value needs to be returned.
// If it is not specified then a value from the first argument (view) is returned.
func GetTableCellSelectedListeners(view View, subviewID ...string) []func(TableView, int, int) {
return getTwoArgEventListeners[TableView, int](view, subviewID, TableCellSelectedEvent)
func GetTableCellSelectedListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[TableView, int](view, subviewID, TableCellSelectedEvent)
}
// GetTableRowClickedListeners returns listeners of event which occurs when the user clicks on a table row.

View File

@ -211,7 +211,7 @@ func (tabsLayout *tabsLayoutData) propertyChanged(tag PropertyName) {
if listeners := getTwoArgEventListeners[TabsLayout, int](tabsLayout, nil, CurrentTabChangedEvent); len(listeners) > 0 {
oldCurrent, _ := intProperty(tabsLayout, "old-current", session, -1)
for _, listener := range listeners {
listener(tabsLayout, current, oldCurrent)
listener.Run(tabsLayout, current, oldCurrent)
}
}
@ -427,7 +427,7 @@ func (tabsLayout *tabsLayoutData) Insert(view View, index int) {
func (tabsLayout *tabsLayoutData) currentChanged(newCurrent, oldCurrent int) {
for _, listener := range getTwoArgEventListeners[TabsLayout, int](tabsLayout, nil, CurrentTabChangedEvent) {
listener(tabsLayout, newCurrent, oldCurrent)
listener.Run(tabsLayout, newCurrent, oldCurrent)
}
if listener, ok := tabsLayout.changeListener[Current]; ok {
listener(tabsLayout, Current)
@ -766,3 +766,20 @@ func (tabsLayout *tabsLayoutData) handleCommand(self View, command PropertyName,
func GetTabCloseEventListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[TabsLayout, int](view, subviewID, TabCloseEvent)
}
// GetCurrentTabChangedEventListeners returns the "current-tab-changed" listener list. If there are no listeners then the empty list is returned.
//
// Result elements can be of the following types:
// - func(rui.TabsLayout, int, int),
// - func(rui.TabsLayout, int),
// - func(rui.TabsLayout),
// - func(int, int),
// - func(int),
// - func(),
// - string.
//
// The second argument (subviewID) specifies the path to the child element whose value needs to be returned.
// If it is not specified then a value from the first argument (view) is returned.
func GetCurrentTabChangedEventListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[TabsLayout, int](view, subviewID, CurrentTabChangedEvent)
}

View File

@ -246,7 +246,7 @@ func (picker *timePickerData) propertyChanged(tag PropertyName) {
value := GetTimePickerValue(picker)
session.callFunc("setInputValue", picker.htmlID(), value.Format(timeFormat))
if listeners := GetTimeChangedListeners(picker); len(listeners) > 0 {
if listeners := getTwoArgEventListeners[TimePicker, time.Time](picker, nil, TimeChangedEvent); len(listeners) > 0 {
oldTime := time.Now()
if val := picker.getRaw("old-time"); val != nil {
if time, ok := val.(time.Time); ok {
@ -254,7 +254,7 @@ func (picker *timePickerData) propertyChanged(tag PropertyName) {
}
}
for _, listener := range listeners {
listener(picker, value, oldTime)
listener.Run(picker, value, oldTime)
}
}
@ -320,8 +320,8 @@ func (picker *timePickerData) handleCommand(self View, command PropertyName, dat
oldValue := GetTimePickerValue(picker)
picker.properties[TimePickerValue] = value
if value != oldValue {
for _, listener := range GetTimeChangedListeners(picker) {
listener(picker, value, oldValue)
for _, listener := range getTwoArgEventListeners[TimePicker, time.Time](picker, nil, TimeChangedEvent) {
listener.Run(picker, value, oldValue)
}
if listener, ok := picker.changeListener[TimePickerValue]; ok {
listener(picker, TimePickerValue)
@ -418,8 +418,17 @@ func GetTimePickerValue(view View, subviewID ...string) time.Time {
// GetTimeChangedListeners returns the TimeChangedListener list of an TimePicker subview.
// If there are no listeners then the empty list is returned
//
// Result elements can be of the following types:
// - func(rui.TimePicker, time.Time, time.Time),
// - func(rui.TimePicker, time.Time),
// - func(rui.TimePicker),
// - func(time.Time, time.Time),
// - func(time.Time),
// - func(),
// - string.
//
// The second argument (subviewID) specifies the path to the child element whose value needs to be returned.
// If it is not specified then a value from the first argument (view) is returned.
func GetTimeChangedListeners(view View, subviewID ...string) []func(TimePicker, time.Time, time.Time) {
return getTwoArgEventListeners[TimePicker, time.Time](view, subviewID, TimeChangedEvent)
func GetTimeChangedListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[TimePicker, time.Time](view, subviewID, TimeChangedEvent)
}