-
Notifications
You must be signed in to change notification settings - Fork 6
/
proxy.go
222 lines (193 loc) · 7.43 KB
/
proxy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
package control
import (
"bytes"
"context"
"fmt"
"github.com/goradd/goradd/pkg/crypt"
"github.com/goradd/goradd/pkg/html"
"github.com/goradd/goradd/pkg/javascript"
"github.com/goradd/goradd/pkg/page"
"github.com/goradd/goradd/pkg/page/action"
"github.com/goradd/goradd/pkg/page/event"
html2 "html"
"strings"
)
type ProxyI interface {
page.ControlI
LinkHtml(label string,
actionValue string,
attributes html.Attributes,
) string
TagHtml(label string,
actionValue string,
attributes html.Attributes,
tag string,
rawHtml bool,
) string
ButtonHtml(label string,
eventActionValue string,
attributes html.Attributes,
rawHtml bool,
) string
OnSubmit(action action.ActionI) *page.Event
}
// Proxy is a control that attaches events to controls. It is useful for attaching
// similar events to a series of controls, like all the links in a table, or all the buttons in button bar.
// You can also use it to draw a series of links or buttons. The proxy differentiates between the different objects
// that are sending it events by the ActionValue that you given the proxy when it draws.
//
// To use a Proxy, create it in the control that wraps the controls the proxy will manage.
// Attach an event to the proxy control, and in the action handler, look for the ControlValue in the Action Value
// to know which of the controls sent the event. Draw the proxy with one of the following:
// LinkHtml() - Output the proxy as a link
// ButtonHtml() - Output the proxy as a button
// TagHtml() - Output the proxy in any tag
// ActionAttributes() - Returns attributes you can use in any custom control to attach a proxy
//
// The ProxyColumn of the Table object will use a proxy to draw items in a table column.
type Proxy struct {
page.ControlBase
}
// NewProxy creates a new proxy. The parent must be the wrapping control of the objects that the proxy will manage.
func NewProxy(parent page.ControlI, id string) *Proxy {
p := &Proxy{}
p.Self = p
p.Init(parent, id)
return p
}
func (p *Proxy) Init(parent page.ControlI, id string) {
p.ControlBase.Init(parent, id)
p.SetShouldAutoRender(true)
p.SetActionValue(javascript.JsCode(`goradd.proxyVal(event)`))
}
func (p *Proxy) this() ProxyI {
return p.Self.(ProxyI)
}
// OnSubmit is a shortcut for adding a click event handler that is particular to buttons. It debounces the click, to
// prevent potential accidental multiple form submissions. All events fired after this event fires will be lost. It is
// intended to be used when the action will result in navigating to a new page.
func (p *Proxy) OnSubmit(action action.ActionI) page.ControlI {
return p.On(event.Click().Terminating().Delay(250), action)
}
// Draw is used by the form engine to draw the control. As a proxy, there is no html to draw, but this is where the scripts attached to the
// proxy get sent to the response. This should get drawn by the auto-drawing routine, since proxies are not rendered in templates.
func (p *Proxy) Draw(ctx context.Context, buf *bytes.Buffer) (err error) {
response := p.ParentForm().Response()
// p.this().PutCustomScript(ctx, response) // Proxies should not have custom scripts?
p.GetActionScripts(response)
err = p.PostRender(ctx, buf)
return
}
// LinkHtml renders the proxy as a link. To conform to the html standard and accessibility guidelines,
// links should only be used to navigate away from the page, so the action of your proxy should lead to
// that kind of behavior. Otherwise, use ButtonHtml.
func (p *Proxy) LinkHtml(ctx context.Context,
label string,
actionValue string,
attributes html.Attributes,
) string {
if attributes == nil {
attributes = html.NewAttributes()
}
attributes.Set("onclick", "return false;") // make sure we do not follow the link if javascript is on.
var href string
if attributes.Has("href") {
href = attributes.Get("href")
} else {
href = page.GetContext(ctx).HttpContext.URL.RequestURI() // for non-javascript compatibility
if offset := strings.Index(href, page.HtmlVarAction); offset >= 0 {
href = href[:offset-1] // remove the variables we placed here ourselves
}
}
// These next two lines allow the proxy to work even when javascript is off.
av := page.HtmlVarAction + "=" + p.ID() + "_" + actionValue
av += "&" + page.HtmlVarPagestate + "=" + crypt.SessionEncryptUrlValue(ctx, p.Page().StateID())
if !strings.ContainsRune(href, '?') {
href += "?" + av
} else {
href += "&" + av
}
attributes.Set("href", href)
return p.TagHtml(label, actionValue, attributes, "a", false)
}
// TagHtml lets you customize the tag that will be used to embed the proxy.
func (p *Proxy) TagHtml(label string,
actionValue string,
attributes html.Attributes,
tag string,
labelIsHtml bool,
) string {
a := html.NewAttributes()
a.SetDataAttribute("grProxy", p.ID())
if actionValue != "" {
a.SetDataAttribute("grAv", actionValue)
}
if attributes != nil {
a.Merge(attributes) // will only apply defaults that are not in attributes
}
if !labelIsHtml {
label = html2.EscapeString(label)
}
return html.RenderTagNoSpace(tag, a, label)
}
// ButtonHtml outputs the proxy as a button tag.
// actionValue becomes the event's ControlValue parameter
func (p *Proxy) ButtonHtml(label string,
actionValue string,
attributes html.Attributes,
labelIsHtml bool,
) string {
a := html.NewAttributes()
a.Set("onclick", "return false") // To prevent a return from activating the button
a.Set("type", "submit") // To support non-javascript situations
a.Set("name", page.HtmlVarAction) // needed for non-javascript posts
buttonValue := p.ID() + "_" + actionValue
a.Set("value", buttonValue) // needed for non-javascript posts
if attributes != nil {
a.Merge(attributes)
}
// TODO: We can possibly do actionValue differently now since its already in the value above
return p.TagHtml(label, actionValue, a, "button", labelIsHtml)
}
// ActionAttributes returns attributes that can be included in any tag to attach a proxy to the tag.
func (p *Proxy) ActionAttributes(actionValue string) html.Attributes {
a := html.NewAttributes()
a.SetDataAttribute("grProxy", p.ID())
if actionValue != "" {
a.SetDataAttribute("grAv", actionValue)
}
return a
}
// WrapEvent is an internal function to allow the control to customize its treatment of event processing.
func (p *Proxy) WrapEvent(eventName string, selector string, eventJs string, options map[string]interface{}) string {
// This attaches the event to the parent control.
return fmt.Sprintf(`g$('%s').on('%s', '[data-gr-proxy="%s"]', function(event, eventData){%s}, %s);`, p.Parent().ID(), eventName, p.ID(), eventJs, javascript.ToJavaScript(options))
}
type On struct {
Event *page.Event
Action action.ActionI
}
type ProxyCreator struct {
// ID is the id of the proxy. Proxies do not draw, so this id will not show up in the html, but you can
// use it to get the proxy from the page.
ID string
// On is a shortcut to assign a single action to an event. If you want a proxy that responds to more than
// one event or action, use On in the ControlOptions struct
On On
page.ControlOptions
}
func (c ProxyCreator) Create(ctx context.Context, parent page.ControlI) page.ControlI {
ctrl := NewProxy(parent, c.ID)
if c.On.Event != nil {
ctrl.On(c.On.Event, c.On.Action)
}
ctrl.ApplyOptions(ctx, c.ControlOptions)
return ctrl
}
// GetProxy is a convenience method to return the button with the given id from the page.
func GetProxy(c page.ControlI, id string) *Proxy {
return c.Page().GetControl(id).(*Proxy)
}
func init() {
page.RegisterControl(&Proxy{})
}