From 4b002998784cd97dd22cd86605c4e34aff82e833 Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko <2277098+anoshenko@users.noreply.github.com> Date: Wed, 18 Jun 2025 13:24:53 +0300 Subject: [PATCH] Improved binding for 2 args listeners --- colorPicker.go | 21 +- datePicker.go | 21 +- dropDownList.go | 21 +- editView.go | 17 +- events.go | 591 ++-------------------------------------------- events2arg.go | 339 ++++++++++++++++++++++++++ numberPicker.go | 21 +- tableView.go | 19 +- tableViewUtils.go | 26 +- tabsLayout.go | 21 +- timePicker.go | 21 +- 11 files changed, 502 insertions(+), 616 deletions(-) create mode 100644 events2arg.go diff --git a/colorPicker.go b/colorPicker.go index f205094..66ecf34 100644 --- a/colorPicker.go +++ b/colorPicker.go @@ -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) } diff --git a/datePicker.go b/datePicker.go index 54da1c4..3cfe639 100644 --- a/datePicker.go +++ b/datePicker.go @@ -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) } diff --git a/dropDownList.go b/dropDownList.go index 830a655..1dd6802 100644 --- a/dropDownList.go +++ b/dropDownList.go @@ -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. diff --git a/editView.go b/editView.go index ece94fe..5040e29 100644 --- a/editView.go +++ b/editView.go @@ -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. diff --git a/events.go b/events.go index f663c47..f8319b2 100644 --- a/events.go +++ b/events.go @@ -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 -} - -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)} +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 args != nil { - method.Call(args) - } else { - ErrorLogF(`Unsupported prototype of "%s" method`, data.name) +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)") + } } } -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)") - } - } -} diff --git a/events2arg.go b/events2arg.go new file mode 100644 index 0000000..2ad256e --- /dev/null +++ b/events2arg.go @@ -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 +} diff --git a/numberPicker.go b/numberPicker.go index 17963b0..a2ec402 100644 --- a/numberPicker.go +++ b/numberPicker.go @@ -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. diff --git a/tableView.go b/tableView.go index 0bd784b..20c147e 100644 --- a/tableView.go +++ b/tableView.go @@ -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) } } } diff --git a/tableViewUtils.go b/tableViewUtils.go index 69b77e9..3d1da55 100644 --- a/tableViewUtils.go +++ b/tableViewUtils.go @@ -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. diff --git a/tabsLayout.go b/tabsLayout.go index 5fb6b40..07bc95c 100644 --- a/tabsLayout.go +++ b/tabsLayout.go @@ -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) +} diff --git a/timePicker.go b/timePicker.go index 9d8dea2..2b35e8a 100644 --- a/timePicker.go +++ b/timePicker.go @@ -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) }