Skip to content

Commit c777cc4

Browse files
committed
refactor: Remove need for interfaces for events
This removes the Event interface in favour of an Event struct. This simplifies the code. This is the result of new insights that the interface model wasn't necessary to solve the problem of presenting a class hierarchy to client-scripts The change and background is described in [this discussion thread](https://github.com/orgs/gost-dom/discussions/50)
2 parents 0cf4a61 + 92f089c commit c777cc4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+772
-681
lines changed

browser_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"testing"
77

88
. "github.com/gost-dom/browser"
9-
"github.com/gost-dom/browser/dom"
9+
"github.com/gost-dom/browser/dom/event"
1010
"github.com/gost-dom/browser/html"
1111
. "github.com/gost-dom/browser/internal/testing/gomega-matchers"
1212
. "github.com/gost-dom/browser/testing/gomega-matchers"
@@ -108,7 +108,7 @@ func (s *BrowserNavigationTestSuite) TestNavigationAbortedByEventHandler() {
108108
anchor, _ := window.Document().QuerySelector("a")
109109
anchor.AddEventListener(
110110
"click",
111-
dom.NewEventHandlerFunc(dom.NoError(dom.Event.PreventDefault)),
111+
event.NewEventHandlerFunc(event.NoError((*event.Event).PreventDefault)),
112112
)
113113
anchor.Click()
114114
heading, _ := window.Document().QuerySelector("h1")

dom/document.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package dom
33
import (
44
"io"
55

6+
"github.com/gost-dom/browser/dom/event"
67
"golang.org/x/net/html"
78
)
89

@@ -19,7 +20,7 @@ const (
1920
// an oversight that it wasn't placed in an internal package. This will be
2021
// removed from the public API in a future version
2122
type DocumentParentWindow interface {
22-
EventTarget
23+
event.EventTarget
2324
ParseFragment(ownerDocument Document, reader io.Reader) (DocumentFragment, error)
2425
}
2526

@@ -50,7 +51,7 @@ func NewDocument(window DocumentParentWindow) Document {
5051
// Hmmm, can document be replaced; and now the old doc's event goes to a
5152
// window they shouldn't?
5253
// What about disconnected documents, e.g. `new Document()` in the browser?
53-
result.parentTarget = window
54+
result.SetParentTarget(window)
5455
result.SetSelf(result)
5556
return result
5657
}

dom/element_events_generated.go

+15-4
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
package dom
44

5+
import event "github.com/gost-dom/browser/dom/event"
6+
57
type elementEvents struct {
6-
target EventTarget
8+
target event.EventTarget
79
}
810

911
type ElementEvents interface {
@@ -13,19 +15,28 @@ type ElementEvents interface {
1315
}
1416

1517
func (e *elementEvents) Auxclick() bool {
18+
init := PointerEventInit{}
19+
init.Bubbles = true
20+
init.Cancelable = true
1621
return e.target.DispatchEvent(
17-
NewPointerEvent("auxclick", EventBubbles(true), EventCancelable(true)),
22+
NewPointerEvent("auxclick", init),
1823
)
1924
}
2025

2126
func (e *elementEvents) Click() bool {
27+
init := PointerEventInit{}
28+
init.Bubbles = true
29+
init.Cancelable = true
2230
return e.target.DispatchEvent(
23-
NewPointerEvent("click", EventBubbles(true), EventCancelable(true)),
31+
NewPointerEvent("click", init),
2432
)
2533
}
2634

2735
func (e *elementEvents) Contextmenu() bool {
36+
init := PointerEventInit{}
37+
init.Cancelable = true
38+
init.Bubbles = true
2839
return e.target.DispatchEvent(
29-
NewPointerEvent("contextmenu", EventBubbles(true), EventCancelable(true)),
40+
NewPointerEvent("contextmenu", init),
3041
)
3142
}

dom/element_test.go

+11-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55

66
"github.com/gost-dom/browser/dom"
77
. "github.com/gost-dom/browser/dom"
8+
"github.com/gost-dom/browser/dom/event"
9+
. "github.com/gost-dom/browser/internal/testing"
810
. "github.com/gost-dom/browser/internal/testing/gomega-matchers"
911
. "github.com/gost-dom/browser/testing/gomega-matchers"
1012
"github.com/stretchr/testify/suite"
@@ -295,15 +297,18 @@ var _ = Describe("Element", func() {
295297

296298
Describe("Click", func() {
297299
It("Is cancelable and bubbles", func() {
298-
var event Event
300+
var ev *event.Event
299301
doc := ParseHtmlString(`<body><div id="target"></div></body>`)
300302
element := doc.GetElementById("target")
301-
element.AddEventListener("click", NewEventHandlerFuncWithoutError(func(e Event) {
302-
event = e
303-
}))
303+
element.AddEventListener(
304+
"click",
305+
event.NewEventHandlerFuncWithoutError(func(e *event.Event) {
306+
ev = e
307+
}),
308+
)
304309
element.Click()
305-
gomega.Expect(event.Cancelable()).To(BeTrue())
306-
gomega.Expect(event.Bubbles()).To(BeTrue())
310+
gomega.Expect(ev.Cancelable()).To(BeTrue())
311+
gomega.Expect(ev.Bubbles()).To(BeTrue())
307312
})
308313
})
309314

dom/event/custom_events.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package event
2+
3+
/* -------- customEvent -------- */
4+
5+
type CustomEventInit struct {
6+
EventInit
7+
Details interface{}
8+
}
9+
10+
func NewCustomEvent(eventType string, init CustomEventInit) *Event {
11+
e := New(eventType, init)
12+
return e
13+
}
14+
15+
/* -------- errorEvent -------- */
16+
17+
type ErrorEventInit struct {
18+
EventInit
19+
Err error
20+
}
21+
22+
func NewErrorEvent(err error) *Event {
23+
e := New(
24+
"error",
25+
ErrorEventInit{Err: err, EventInit: EventInit{Bubbles: true}},
26+
)
27+
return e
28+
}

dom/event/event.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package event
2+
3+
import "github.com/gost-dom/browser/internal/entity"
4+
5+
/* -------- event -------- */
6+
7+
type Init interface {
8+
bubbles() bool
9+
cancelable() bool
10+
}
11+
12+
type EventInit struct {
13+
Bubbles bool
14+
Cancelable bool
15+
}
16+
17+
func (d EventInit) bubbles() bool {
18+
return d.Bubbles
19+
}
20+
21+
func (d EventInit) cancelable() bool {
22+
return d.Cancelable
23+
}
24+
25+
type Event struct {
26+
entity.Entity
27+
Init
28+
phase EventPhase
29+
cancelled bool
30+
eventType string
31+
stopped bool
32+
target EventTarget
33+
currentTarget EventTarget
34+
}
35+
36+
func New(eventType string, eventInit Init) *Event {
37+
return &Event{
38+
Entity: entity.New(),
39+
Init: eventInit,
40+
eventType: eventType,
41+
}
42+
}
43+
44+
func (e *Event) Bubbles() bool { return e.Init.bubbles() }
45+
func (e *Event) Cancelable() bool { return e.Init.cancelable() }
46+
func (e *Event) Type() string { return e.eventType }
47+
func (e *Event) StopPropagation() { e.stopped = true }
48+
func (e *Event) PreventDefault() { e.cancelled = true }
49+
func (e *Event) EventPhase() EventPhase { return e.phase }
50+
func (e *Event) Target() EventTarget { return e.target }
51+
func (e *Event) CurrentTarget() EventTarget { return e.currentTarget }
52+
53+
func (e *Event) reset(t EventTarget) {
54+
e.target = t
55+
e.stopped = false
56+
e.cancelled = false
57+
}

dom/event/event_handler.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package event
2+
3+
import "github.com/gost-dom/browser/internal/entity"
4+
5+
/* -------- EventHandler -------- */
6+
7+
// EventHandler is the interface for an event handler. In JavaScript; an event
8+
// handler can be a function; or an object with a `handleEvent` function. In Go
9+
// code, you can provide your own implementation, or use [NewEventHandlerFunc]
10+
// to create a valid handler from a function.
11+
//
12+
// Multiple EventHandler instances can represent the same underlying event
13+
// handler. E.g., when JavaScript code calls RemoveEventListener, a new Go
14+
// struct is created wrapping the same underlying handler.
15+
//
16+
// The Equals function must return true when the other event handler is the same
17+
// as the current value, so event handlers can properly be removed, and avoiding
18+
// duplicates are added by AddEventListener.
19+
type EventHandler interface {
20+
// HandleEvent is called when the the event occurrs.
21+
//
22+
// An non-nil error return value will dispatch an error event on the global
23+
// object in a normally configured environment.
24+
HandleEvent(event *Event) error
25+
// Equals must return true, if the underlying event handler of the other
26+
// handler is the same as this handler.
27+
Equals(other EventHandler) bool
28+
}
29+
30+
type HandlerFuncWithoutError = func(*Event)
31+
type HandlerFunc = func(*Event) error
32+
33+
type eventHandlerFunc struct {
34+
handlerFunc func(*Event) error
35+
id entity.ObjectId
36+
}
37+
38+
// NewEventHandlerFunc creates an [EventHandler] implementation from a compatible
39+
// function.
40+
//
41+
// Note: Calling this twice for the same Go-function will be treated as
42+
// different event handlers. Be sure to use the same instance returned from this
43+
// function when removing.
44+
func NewEventHandlerFunc(handler HandlerFunc) EventHandler {
45+
return eventHandlerFunc{handler, entity.NewObjectId()}
46+
}
47+
48+
// NoError takes a function accepting a single argument and has no return value,
49+
// and transforms it into a function that can be used where an error return
50+
// value is expected.
51+
func NoError[T func(U), U any](f T) func(U) error {
52+
return func(u U) error {
53+
f(u)
54+
return nil
55+
}
56+
}
57+
58+
func NewEventHandlerFuncWithoutError(handler HandlerFuncWithoutError) EventHandler {
59+
return eventHandlerFunc{func(event *Event) error {
60+
handler(event)
61+
return nil
62+
}, entity.NewObjectId()}
63+
}
64+
65+
func (e eventHandlerFunc) HandleEvent(event *Event) error {
66+
return e.handlerFunc(event)
67+
}
68+
69+
func (e eventHandlerFunc) Equals(handler EventHandler) bool {
70+
x, ok := handler.(eventHandlerFunc)
71+
return ok && x.id == e.id
72+
}

0 commit comments

Comments
 (0)