github.com/vugu/vugu@v0.3.5/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 }