github.com/vugu/vugu@v0.3.6-0.20240430171613-3f6f402e014b/events-dom.go (about)

     1  package vugu
     2  
     3  import (
     4  	"sync"
     5  
     6  	"github.com/vugu/vugu/js"
     7  )
     8  
     9  // NewDOMEvent returns a new initialized DOMEvent.
    10  func NewDOMEvent(eventEnv EventEnv, eventSummary map[string]interface{}) DOMEvent {
    11  	return &domEvent{
    12  		eventSummary: eventSummary,
    13  		eventEnv:     eventEnv,
    14  		window:       js.Global().Get("window"),
    15  	}
    16  }
    17  
    18  // DOMEvent is an event originated in the browser.  It wraps the JS event that comes in.
    19  // It is meant to be used in WebAssembly but some methods exist here so code can compile
    20  // server-side as well (although DOMEvents should not ever be generated server-side).
    21  type DOMEvent interface {
    22  
    23  	// Prop returns a value from the EventSummary using the keys you specify.
    24  	// The keys is a list of map keys to be looked up. For example:
    25  	// e.Prop("target", "name") will return the same value as e.EventSummary()["target"]["name"],
    26  	// except that Prop helps with some edge cases and if a value is missing
    27  	// of the wrong type, nil will be returned, instead of panicing.
    28  	Prop(keys ...string) interface{}
    29  
    30  	// PropString is like Prop but returns it's value as a string.
    31  	// No type conversion is done, if the requested value is not
    32  	// already a string then an empty string will be returned.
    33  	PropString(keys ...string) string
    34  
    35  	// PropFloat64 is like Prop but returns it's value as a float64.
    36  	// No type conversion is done, if the requested value is not
    37  	// already a float64 then float64(0) will be returned.
    38  	PropFloat64(keys ...string) float64
    39  
    40  	// PropBool is like Prop but returns it's value as a bool.
    41  	// No type conversion is done, if the requested value is not
    42  	// already a bool then false will be returned.
    43  	PropBool(keys ...string) bool
    44  
    45  	// EventSummary returns a map with simple properties (primitive types) from the event.
    46  	// Accessing values returns by EventSummary incurs no additional performance or memory
    47  	// penalty, whereas calls to JSEvent, JSEventTarget, etc. require a call into the browser
    48  	// JS engine and the attendant resource usage.  So if you can get the information you
    49  	// need from the EventSummary, that's better.
    50  	EventSummary() map[string]interface{}
    51  
    52  	// JSEvent returns a js.Value in wasm that corresponds to the event object.
    53  	// Non-wasm implementation returns nil.
    54  	JSEvent() js.Value
    55  
    56  	// JSEventTarget returns the value of the "target" property of the event, the element
    57  	// that the event was originally fired/registered on.
    58  	JSEventTarget() js.Value
    59  
    60  	// JSEventCurrentTarget returns the value of the "currentTarget" property of the event, the element
    61  	// that is currently processing the event.
    62  	JSEventCurrentTarget() js.Value
    63  
    64  	// EventEnv returns the EventEnv for the current environment and allows locking and unlocking around modifications.
    65  	// See EventEnv struct.
    66  	EventEnv() EventEnv
    67  
    68  	// PreventDefault calls preventDefault() on the underlying DOM event.
    69  	// May only be used within event handler in same goroutine.
    70  	PreventDefault()
    71  
    72  	// StopPropagation calls stopPropagation() on the underlying DOM event.
    73  	// May only be used within event handler in same goroutine.
    74  	StopPropagation()
    75  }
    76  
    77  // domEvent implements the DOMEvent interface.
    78  // This way DOMEvent can be passed around without a pointer this helps make
    79  // DOM events and component events similar and consistent.
    80  // It might actually makes sense to move this into the domrender package at some point.
    81  type domEvent struct {
    82  	eventSummary map[string]interface{}
    83  
    84  	eventEnv EventEnv // from the renderer
    85  
    86  	// TODO: yeah but why? this does not need to be here
    87  	window js.Value // sure, why not
    88  }
    89  
    90  var _ DOMEvent = &domEvent{} // assert domEvent implements DOMEvent
    91  
    92  // Prop returns a value from the EventSummary using the keys you specify.
    93  // The keys is a list of map keys to be looked up. For example:
    94  // e.Prop("target", "name") will return the same value as e.EventSummary()["target"]["name"],
    95  // except that Prop helps with some edge cases and if a value is missing
    96  // of the wrong type, nil will be returned, instead of panicing.
    97  func (e *domEvent) Prop(keys ...string) interface{} {
    98  
    99  	var ret interface{}
   100  	ret = e.eventSummary
   101  
   102  	for _, key := range keys {
   103  
   104  		// see if ret is a map
   105  		m, _ := ret.(map[string]interface{})
   106  		if m == nil {
   107  			return nil
   108  		}
   109  
   110  		// and index into the map if so, replacing ret
   111  		ret = m[key]
   112  
   113  	}
   114  
   115  	return ret
   116  }
   117  
   118  // PropString is like Prop but returns it's value as a string.
   119  // No type conversion is done, if the requested value is not
   120  // already a string then an empty string will be returned.
   121  func (e *domEvent) PropString(keys ...string) string {
   122  	ret, _ := e.Prop(keys...).(string)
   123  	return ret
   124  }
   125  
   126  // PropFloat64 is like Prop but returns it's value as a float64.
   127  // No type conversion is done, if the requested value is not
   128  // already a float64 then float64(0) will be returned.
   129  func (e *domEvent) PropFloat64(keys ...string) float64 {
   130  	ret, _ := e.Prop(keys...).(float64)
   131  	return ret
   132  }
   133  
   134  // PropBool is like Prop but returns it's value as a bool.
   135  // No type conversion is done, if the requested value is not
   136  // already a bool then false will be returned.
   137  func (e *domEvent) PropBool(keys ...string) bool {
   138  	ret, _ := e.Prop(keys...).(bool)
   139  	return ret
   140  }
   141  
   142  // EventSummary returns a map with simple properties (primitive types) from the event.
   143  // Accessing values returns by EventSummary incurs no additional performance or memory
   144  // penalty, whereas calls to JSEvent, JSEventTarget, etc. require a call into the browser
   145  // JS engine and the attendant resource usage.  So if you can get the information you
   146  // need from the EventSummary, that's better.
   147  func (e *domEvent) EventSummary() map[string]interface{} {
   148  	return e.eventSummary
   149  }
   150  
   151  // JSEvent this returns a js.Value in wasm that corresponds to the event object.
   152  // Non-wasm implementation returns nil.
   153  func (e *domEvent) JSEvent() js.Value {
   154  	return e.window.Call("vuguGetActiveEvent")
   155  }
   156  
   157  // JSEventTarget returns the value of the "target" property of the event, the element
   158  // that the event was originally fired/registered on.
   159  func (e *domEvent) JSEventTarget() js.Value {
   160  	return e.window.Call("vuguGetActiveEventTarget")
   161  }
   162  
   163  // JSEventCurrentTarget returns the value of the "currentTarget" property of the event, the element
   164  // that is currently processing the event.
   165  func (e *domEvent) JSEventCurrentTarget() js.Value {
   166  	return e.window.Call("vuguGetActiveEventCurrentTarget")
   167  }
   168  
   169  // EventEnv returns the EventEnv for the current environment and allows locking and unlocking around modifications.
   170  // See EventEnv struct.
   171  func (e *domEvent) EventEnv() EventEnv {
   172  	return e.eventEnv
   173  }
   174  
   175  // PreventDefault calls preventDefault() on the underlying DOM event.
   176  // May only be used within event handler in same goroutine.
   177  func (e *domEvent) PreventDefault() {
   178  	e.window.Call("vuguActiveEventPreventDefault")
   179  }
   180  
   181  // StopPropagation calls stopPropagation() on the underlying DOM event.
   182  // May only be used within event handler in same goroutine.
   183  func (e *domEvent) StopPropagation() {
   184  	e.window.Call("vuguActiveEventStopPropagation")
   185  }
   186  
   187  // DOMEventHandlerSpec describes an event that gets registered with addEventListener.
   188  type DOMEventHandlerSpec struct {
   189  	EventType string // "click", "mouseover", etc.
   190  	Func      func(DOMEvent)
   191  	Capture   bool
   192  	Passive   bool
   193  }
   194  
   195  // // DOMEventHandler is created in BuildVDOM to represent a method call that is performed to handle an event.
   196  // type DOMEventHandler struct {
   197  // 	ReceiverAndMethodHash uint64        // hash value corresponding to the method and receiver, so we get a unique value for each combination of method and receiver
   198  // 	Method                reflect.Value // method to be called, with receiver baked into it if needed (see reflect/Value.MethodByName)
   199  // 	Args                  []interface{} // arguments to be passed in when calling (special case for eventStub)
   200  // }
   201  
   202  // func (d DOMEventHandler) hashString() string {
   203  // 	return fmt.Sprintf("%x", d.hash())
   204  // }
   205  
   206  // func (d DOMEventHandler) hash() (ret uint64) {
   207  
   208  // 	// defer func() {
   209  // 	// 	log.Printf("DOMEventHandler.hash for (receiver_and_method_hash=%v, method=%#v, args=%#v) is returning %v", d.ReceiverAndMethodHash, d.Method, d.Args, ret)
   210  // 	// }()
   211  
   212  // 	if !d.Method.IsValid() && len(d.Args) == 0 {
   213  // 		return 0
   214  // 	}
   215  
   216  // 	b8 := make([]byte, 8)
   217  // 	h := xxhash.New()
   218  
   219  // 	// this may end up being an issue but I need to move through this for now -
   220  // 	// this is only unique for the method itself, not the instance it's tied to, although
   221  // 	// it's probably rare to have to distinguish between multiple component types in use in an application
   222  // 	// fmt.Fprintf(h, "%#v", d.Method)
   223  
   224  // 	// NOTE: added ReceiverAndMethodHash to deal with the note above, let's see if it solves the problem
   225  // 	binary.BigEndian.PutUint64(b8, d.ReceiverAndMethodHash)
   226  // 	h.Write(b8)
   227  
   228  // 	// add the args
   229  // 	for _, a := range d.Args {
   230  // 		binary.BigEndian.PutUint64(b8, ComputeHash(a))
   231  // 		h.Write(b8)
   232  // 	}
   233  // 	return h.Sum64()
   234  // }
   235  
   236  // EventEnv provides locking mechanism to for rendering environment to events so
   237  // data access and rendering can be synchronized and avoid race conditions.
   238  type EventEnv interface {
   239  	Lock()         // acquire write lock
   240  	UnlockOnly()   // release write lock
   241  	UnlockRender() // release write lock and request re-render
   242  
   243  	RLock()   // acquire read lock
   244  	RUnlock() // release read lock
   245  }
   246  
   247  // NewEventEnvImpl creates and returns a new EventEnvImpl.
   248  func NewEventEnvImpl(rwmu *sync.RWMutex, requestRenderCH chan bool) *EventEnvImpl {
   249  	return &EventEnvImpl{
   250  		rwmu:            rwmu,
   251  		requestRenderCH: requestRenderCH,
   252  	}
   253  }
   254  
   255  // EventEnvImpl implements EventEnv
   256  type EventEnvImpl struct {
   257  	rwmu            *sync.RWMutex
   258  	requestRenderCH chan bool
   259  }
   260  
   261  // Lock will acquire write lock
   262  func (ee *EventEnvImpl) Lock() {
   263  	// if ee.rwmu == nil {
   264  	// 	return
   265  	// }
   266  	ee.rwmu.Lock()
   267  }
   268  
   269  // UnlockOnly will release the write lock
   270  func (ee *EventEnvImpl) UnlockOnly() {
   271  	// if ee.rwmu == nil {
   272  	// 	return
   273  	// }
   274  	ee.rwmu.Unlock()
   275  }
   276  
   277  // UnlockRender will release write lock and request re-render
   278  func (ee *EventEnvImpl) UnlockRender() {
   279  	// if ee.rwmu != nil {
   280  	ee.rwmu.Unlock()
   281  	// }
   282  	if ee.requestRenderCH != nil {
   283  		// send non-blocking
   284  		select {
   285  		case ee.requestRenderCH <- true:
   286  		default:
   287  		}
   288  	}
   289  }
   290  
   291  // RLock will acquire a read lock
   292  func (ee *EventEnvImpl) RLock() {
   293  	// if ee.rwmu == nil {
   294  	// 	return
   295  	// }
   296  	ee.rwmu.RLock()
   297  }
   298  
   299  // RUnlock will release the read lock
   300  func (ee *EventEnvImpl) RUnlock() {
   301  	// if ee.rwmu == nil {
   302  	// 	return
   303  	// }
   304  	ee.rwmu.RUnlock()
   305  }