-
Notifications
You must be signed in to change notification settings - Fork 1
/
namedboolarray.go
185 lines (167 loc) · 4.95 KB
/
namedboolarray.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
package jaws
import (
"strconv"
"strings"
"github.com/linkdata/deadlock"
)
// NamedBoolArray stores the data required to support HTML 'select' elements
// and sets of HTML radio buttons. It it safe to use from multiple goroutines
// concurrently.
type NamedBoolArray struct {
Jid string // (read-only) JaWS ID of the array
prefix string // Jid+"/"
mu deadlock.RWMutex // protects following
data []*NamedBool
}
// NewNamedBoolArray creates a new object to track a related set of named booleans.
//
// The JaWS ID string 'jid' is used as the ID for <select> elements and the
// value for the 'name' attribute for radio buttons. If left empty, MakeID() will
// be used to assign a unique ID.
func NewNamedBoolArray(jid string) *NamedBoolArray {
if jid == "" {
jid = MakeID()
}
return &NamedBoolArray{Jid: jid, prefix: jid + "/"}
}
// ReadLocked calls the given function with the NamedBoolArray locked for reading.
func (nba *NamedBoolArray) ReadLocked(fn func(nbl []*NamedBool)) {
nba.mu.RLock()
defer nba.mu.RUnlock()
fn(nba.data)
}
// WriteLocked calls the given function with the NamedBoolArray locked for writing and
// replaces the internal []*NamedBool slice with the return value.
func (nba *NamedBoolArray) WriteLocked(fn func(nbl []*NamedBool) []*NamedBool) {
nba.mu.Lock()
defer nba.mu.Unlock()
nba.data = fn(nba.data)
}
// Add adds a NamedBool with the given name and the given text.
// Returns itself.
//
// Note that while it's legal to have multiple NamedBool with the same name
// since it's allowed in HTML, it's probably not a good idea.
func (nba *NamedBoolArray) Add(name, text string) *NamedBoolArray {
nb := &NamedBool{Name: name, Html: text}
nba.mu.Lock()
nba.data = append(nba.data, nb)
nba.mu.Unlock()
return nba
}
// Set sets the Checked state for the NamedBool(s) with the given name or Jid.
func (nba *NamedBoolArray) Set(name string, state bool) {
name = strings.TrimPrefix(name, nba.prefix)
nba.mu.Lock()
for _, nb := range nba.data {
if nb.Name == name {
nb.Checked = state
}
}
nba.mu.Unlock()
}
// Get returns the name of first NamedBool in the group that
// has it's Checked value set to true. Returns an empty string
// if none are true.
//
// In case you can have more than one selected or you need to
// distinguish between a blank name and the fact that none are
// set to true, use ReadLocked() to inspect the data directly.
func (nba *NamedBoolArray) Get() (name string) {
nba.mu.RLock()
for _, nb := range nba.data {
if nb.Checked {
name = nb.Name
break
}
}
nba.mu.RUnlock()
return
}
func (nba *NamedBoolArray) JidOf(name string) string {
return nba.prefix + name
}
// SetOnly sets the Checked state for the NamedBool(s) with the
// given name to true and all others to false.
func (nba *NamedBoolArray) SetOnly(name string) {
name = strings.TrimPrefix(name, nba.prefix)
nba.mu.Lock()
for _, nb := range nba.data {
nb.Checked = (nb.Name == name)
}
nba.mu.Unlock()
}
func (nba *NamedBoolArray) isCheckedLocked(name string) bool {
name = strings.TrimPrefix(name, nba.prefix)
for _, nb := range nba.data {
if nb.Checked && nb.Name == name {
return true
}
}
return false
}
// IsChecked returns true if any of the NamedBool in the set that have the
// given name are Checked. Returns false if the name is not found.
func (nba *NamedBoolArray) IsChecked(name string) (state bool) {
nba.mu.RLock()
state = nba.isCheckedLocked(name)
nba.mu.RUnlock()
return
}
// String returns a string representation of the NamedBoolArray suitable for debugging.
func (nba *NamedBoolArray) String() string {
var sb strings.Builder
sb.WriteString("&NamedBoolArray{")
sb.WriteString(strconv.Quote(nba.Jid))
sb.WriteString(",[")
nba.mu.RLock()
for i, nb := range nba.data {
if i > 0 {
sb.WriteByte(',')
}
sb.WriteString(nb.String())
}
nba.mu.RUnlock()
sb.WriteString("]}")
return sb.String()
}
func (nba *NamedBoolArray) radioList(rq *Request, fn InputTextFn) (rl []Radio) {
nba.mu.RLock()
rl = make([]Radio, len(nba.data))
for i, nb := range nba.data {
rl[i] = Radio{
nba: nba,
rq: rq,
fn: fn,
NamedBool: *nb,
}
}
nba.mu.RUnlock()
return
}
func (nba *NamedBoolArray) radioEventFn(rq *Request, jid, evt, val string, fn InputTextFn) (err error) {
if evt == "input" && val != "" && strings.HasPrefix(jid, nba.prefix) {
var v bool
if v, err = strconv.ParseBool(val); err == nil {
name := strings.TrimPrefix(jid, nba.prefix)
nba.mu.Lock()
for _, nb := range nba.data {
nb.Checked = v && (nb.Name == name)
}
nba.mu.Unlock()
rq.SetBoolValue(jid, v)
if fn != nil {
err = fn(rq, name)
}
}
}
return
}
// JawsRadioGroupData implements part of RadioGrouper. It returns itself.
func (nba *NamedBoolArray) JawsRadioGroupData() *NamedBoolArray {
return nba
}
// JawsRadioGroupHandler implements part of RadioGrouper. It does nothing and returns nil.
func (nba *NamedBoolArray) JawsRadioGroupHandler(rq *Request, boolName string) error {
return nil
}