@subiz/widget-v4 4.1.218 → 4.1.222
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/img/bar_ho_tro.svg +3 -0
- package/{dist/assets/img/bubble_default.6f6eec8.svg → assets/img/bubble_11.svg} +0 -1
- package/assets/less/form-page.less +58 -0
- package/assets/less/form.less +279 -3
- package/assets/less/widget.less +6 -0
- package/compiled_src/chatbox/ChatButton.js +22 -3
- package/compiled_src/chatbox/Widget.js +13 -18
- package/compiled_src/form/Form.js +270 -71
- package/compiled_src/form/FormPage.js +17 -4
- package/compiled_src/form/form-fields.js +244 -0
- package/compiled_src/form/index.js +2 -0
- package/compiled_src/form/rating-stars.js +48 -88
- package/compiled_src/plugin.js +10 -0
- package/compiled_src/store/api.js +5 -7
- package/compiled_src/store/api_fake.js +3 -1
- package/compiled_src/store/localize.js +33 -0
- package/compiled_src/store/store.js +57 -1
- package/compiled_src/store/widget-settings.js +67 -0
- package/compiled_src/util/common.js +1 -1
- package/package.json +3 -3
- package/src/chatbox/ChatButton.js +25 -4
- package/src/chatbox/Widget.js +21 -21
- package/src/form/Form.js +335 -97
- package/src/form/FormPage.js +21 -8
- package/src/form/form-fields.js +224 -0
- package/src/form/index.js +6 -1
- package/src/form/rating-stars.js +48 -64
- package/src/plugin.js +10 -0
- package/src/store/api.js +9 -7
- package/src/store/api_fake.js +3 -1
- package/src/store/localize.js +30 -0
- package/src/store/store.js +64 -1
- package/src/store/widget-settings.js +46 -0
- package/src/util/common.js +1 -1
- package/test.cjs +3 -0
- package/webpack.dev.js +2 -0
- package/dist/127.js +0 -273
- package/dist/132.js +0 -2817
- package/dist/185.js +0 -1917
- package/dist/1a12ed33347.127.js +0 -1
- package/dist/1a12ed33347.132.js +0 -1
- package/dist/1a12ed33347.185.js +0 -1
- package/dist/1a12ed33347.27.js +0 -1
- package/dist/1a12ed33347.278.js +0 -1
- package/dist/1a12ed33347.339.js +0 -1
- package/dist/1a12ed33347.459.js +0 -1
- package/dist/1a12ed33347.490.js +0 -1
- package/dist/1a12ed33347.app.js +0 -1
- package/dist/1a12ed33347.chat.html +0 -1
- package/dist/1a12ed33347.chat.js +0 -1
- package/dist/1a12ed33347.form.html +0 -1
- package/dist/1a12ed33347.form.js +0 -1
- package/dist/1a12ed33347.index.html +0 -72
- package/dist/1a12ed33347.library.html +0 -72
- package/dist/1a12ed33347.library.js +0 -1
- package/dist/1a12ed33347.preview.html +0 -759
- package/dist/1a12ed33347.preview.js +0 -1
- package/dist/1a12ed33347.webview.js +0 -1
- package/dist/27.js +0 -37
- package/dist/459.js +0 -37
- package/dist/490.js +0 -305
- package/dist/HASH +0 -1
- package/dist/assets/img/Facebook.64607ec.png +0 -0
- package/dist/assets/img/Zalo.6845707.png +0 -0
- package/dist/assets/img/award1a.1a58e10.png +0 -0
- package/dist/assets/img/award3a.3b74423.png +0 -0
- package/dist/assets/img/award4a.ed6ccd4.png +0 -0
- package/dist/assets/img/award5.39eb7ae.png +0 -0
- package/dist/assets/img/caret.998eb8f.svg +0 -1
- package/dist/assets/img/convos_empty.2353007.svg +0 -72
- package/dist/assets/img/default.06e6ee0.jpg +0 -0
- package/dist/assets/img/default_avatar.e449b04.png +0 -0
- package/dist/assets/img/default_logo.dd13a2b.svg +0 -5
- package/dist/assets/img/done_gray.d7e8203.svg +0 -4
- package/dist/assets/img/done_green.b817fd4.svg +0 -4
- package/dist/assets/img/dot_chat.335fd8a.svg +0 -3
- package/dist/assets/img/emoji/messenger/sub_23.529_58.824.d5faf14.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_23.529_60.784.1420241.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_25.490_0.000.1e6b2c0.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_25.490_45.098.3dfbc08.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_25.490_5.882.4b49e94.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_27.451_72.549.7114f58.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_27.451_84.314.231e2b7.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_27.451_96.078.ddbb008.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_29.412_5.882.61f3359.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_58.824_100.000.6df72bc.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_58.824_45.098.3d7a7ad.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_58.824_47.059.0f4d6e2.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_58.824_54.902.65724c1.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_58.824_64.706.9da3cba.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_58.824_72.549.edca5cf.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_58.824_78.431.f910f2a.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_58.824_88.235.def4e71.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_60.784_11.765.49c03d0.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_60.784_27.451.46b54e5.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_60.784_29.412.e36d284.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_60.784_3.922.bb191b0.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_60.784_35.294.af7b927.png +0 -0
- package/dist/assets/img/emoji/messenger/sub_60.784_7.843.98f2a99.png +0 -0
- package/dist/assets/img/empty.29bed2d.svg +0 -23
- package/dist/assets/img/empty_1.0b98af0.svg +0 -11
- package/dist/assets/img/examplte_logo.f86535f.svg +0 -1
- package/dist/assets/img/external-link.90577cc.svg +0 -1
- package/dist/assets/img/fb_inverse_circle.36c2cfc.png +0 -0
- package/dist/assets/img/ghn.070f8e3.svg +0 -17
- package/dist/assets/img/ghtk.326eac1.svg +0 -18
- package/dist/assets/img/image_default.be0698b.png +0 -0
- package/dist/assets/img/info.ebce7ea.svg +0 -1
- package/dist/assets/img/loader.37000a7.svg +0 -1
- package/dist/assets/img/loading_static.a27f0ac.svg +0 -10
- package/dist/assets/img/main-service1.d536cc2.png +0 -0
- package/dist/assets/img/main-service2.9b99549.png +0 -0
- package/dist/assets/img/not_found_search.2ddb8d1.png +0 -0
- package/dist/assets/img/package.8d37d0f.svg +0 -5
- package/dist/assets/img/package_fade.763c001.svg +0 -1
- package/dist/assets/img/rating--average.1ea492c.svg +0 -12
- package/dist/assets/img/rating--great.e3d712e.svg +0 -16
- package/dist/assets/img/rating--poor.2938a25.svg +0 -12
- package/dist/assets/img/search.f9705ff.svg +0 -1
- package/dist/assets/img/send.6db52a9.svg +0 -3
- package/dist/assets/img/shop.f7dc7d8.svg +0 -1
- package/dist/assets/img/shop_faded.df7845a.svg +0 -1
- package/dist/assets/img/stars_v2.79cf728.png +0 -0
- package/dist/assets/img/subiz-mono.8fbc067.svg +0 -3
- package/dist/assets/img/x.26fdfa3.svg +0 -5
- package/dist/malicious-app.js +0 -17
- package/dist/screenshoot.html +0 -7
- package/dist/screenshoot.js +0 -14454
package/src/form/Form.js
CHANGED
|
@@ -3,22 +3,47 @@ import {h, createRef, Fragment} from 'preact'
|
|
|
3
3
|
import sb from '../util/index.js'
|
|
4
4
|
import Block from '../chatbox/block.js'
|
|
5
5
|
import RatingStars from './rating-stars.js'
|
|
6
|
+
import {
|
|
7
|
+
OTHER_OPTION_VALUE,
|
|
8
|
+
translate,
|
|
9
|
+
isChips,
|
|
10
|
+
getPlaceholder,
|
|
11
|
+
getDescription,
|
|
12
|
+
toStoredValue,
|
|
13
|
+
sameValue,
|
|
14
|
+
getRatingConfig,
|
|
15
|
+
getScaleRange,
|
|
16
|
+
optionLabel,
|
|
17
|
+
isEmptyValue,
|
|
18
|
+
RatingFaces,
|
|
19
|
+
LinearScale,
|
|
20
|
+
} from './form-fields.js'
|
|
6
21
|
|
|
7
22
|
export default class Form2 extends Com {
|
|
8
23
|
state = {
|
|
9
24
|
errorFields: {},
|
|
10
25
|
submiting: false,
|
|
26
|
+
// "Other…" option (allow_other_option) of radio/checkbox/dropdown, by field key
|
|
27
|
+
otherSelected: {},
|
|
28
|
+
otherText: {},
|
|
11
29
|
}
|
|
12
30
|
|
|
13
31
|
constructor(props) {
|
|
14
32
|
super(props)
|
|
33
|
+
}
|
|
15
34
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
35
|
+
// refs are created lazily: formSetting may arrive after the first render (chat attachment)
|
|
36
|
+
getRef = (name) => {
|
|
37
|
+
if (!this[name]) this[name] = createRef()
|
|
38
|
+
return this[name]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
tr = (key) => translate(this.props.t, key)
|
|
42
|
+
|
|
43
|
+
getValue = (key, def) => {
|
|
44
|
+
let val = sb.find(sb.get(this.props.submission, 'values'), (f) => f.key === key) || {}
|
|
45
|
+
val = val.value || def || '""'
|
|
46
|
+
return sb.parseJSON(val)
|
|
22
47
|
}
|
|
23
48
|
|
|
24
49
|
renderFormComponent = (com, idx) => {
|
|
@@ -46,33 +71,79 @@ export default class Form2 extends Com {
|
|
|
46
71
|
)
|
|
47
72
|
}
|
|
48
73
|
|
|
74
|
+
// readable answer for submitted forms: labels instead of option codes, icons for rating
|
|
75
|
+
renderSubmittedValue = (com) => {
|
|
76
|
+
let empty = <span class='text-muted text-italic'>{this.props.t('no_info_input')}</span>
|
|
77
|
+
let val = this.getValue(com.key)
|
|
78
|
+
if (com.input_type === 'rating') {
|
|
79
|
+
let cfg = getRatingConfig(com)
|
|
80
|
+
let idx = sb.size(cfg.levels) ? cfg.levels.findIndex((l) => sameValue(val, l.value)) : -1
|
|
81
|
+
if (idx < 0) return empty
|
|
82
|
+
if (cfg.kind === 'face') return <RatingFaces readonly levels={[cfg.levels[idx]]} value={val} />
|
|
83
|
+
return (
|
|
84
|
+
<div class='form-submitted-rating'>
|
|
85
|
+
<RatingStars readonly count={cfg.levels.length} value={idx + 1} />
|
|
86
|
+
<span class='text-muted'>
|
|
87
|
+
{idx + 1}/{cfg.levels.length}
|
|
88
|
+
</span>
|
|
89
|
+
</div>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
if (com.input_type === 'linear_scale') {
|
|
93
|
+
if (isEmptyValue(val)) return empty
|
|
94
|
+
let {min, max} = getScaleRange(com)
|
|
95
|
+
let text = `${val}/${max}`
|
|
96
|
+
if (sameValue(val, min) && com.scale_min_label) text += ` · ${com.scale_min_label}`
|
|
97
|
+
if (sameValue(val, max) && com.scale_max_label) text += ` · ${com.scale_max_label}`
|
|
98
|
+
return <div>{text}</div>
|
|
99
|
+
}
|
|
100
|
+
if (['radio', 'checkbox', 'dropdown', 'select'].includes(com.input_type)) {
|
|
101
|
+
let vals = Array.isArray(val) ? val : isEmptyValue(val) ? [] : [val]
|
|
102
|
+
let labels = sb.map(vals, (v) => optionLabel(com, v))
|
|
103
|
+
if (labels.length === 0) return empty
|
|
104
|
+
if (com.input_type === 'checkbox' && isChips(com)) {
|
|
105
|
+
return (
|
|
106
|
+
<div class='form-chips readonly'>
|
|
107
|
+
{sb.map(labels, (label) => (
|
|
108
|
+
<span class='form-chip selected'>{label}</span>
|
|
109
|
+
))}
|
|
110
|
+
</div>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
return <div>{labels.join(', ')}</div>
|
|
114
|
+
}
|
|
115
|
+
if (Array.isArray(val)) val = val.join(', ')
|
|
116
|
+
return <div>{val || empty}</div>
|
|
117
|
+
}
|
|
118
|
+
|
|
49
119
|
renderField = (com, idx) => {
|
|
50
120
|
let errorMsg = this.state.errorFields[com.key]
|
|
51
121
|
if (this.props.isSubmited) {
|
|
52
|
-
let val = sb.find(this.props.submission.values, (f) => f.key === com.key) || {}
|
|
53
|
-
val = val.value || '""'
|
|
54
|
-
val = sb.parseJSON(val)
|
|
55
|
-
if (Array.isArray(val)) val = val.join(', ')
|
|
56
|
-
|
|
57
122
|
return (
|
|
58
123
|
<div class='form-component submitted' key={com.key}>
|
|
59
124
|
<label class='label'>{com.label}</label>
|
|
60
|
-
|
|
125
|
+
{this.renderSubmittedValue(com)}
|
|
61
126
|
</div>
|
|
62
127
|
)
|
|
63
128
|
}
|
|
129
|
+
let desc = getDescription(com)
|
|
130
|
+
let cls = 'form-component'
|
|
131
|
+
if (desc) cls += ' has-description'
|
|
64
132
|
return (
|
|
65
|
-
<div class=
|
|
133
|
+
<div class={cls} key={com.key}>
|
|
66
134
|
<label class='label'>
|
|
67
135
|
{com.label} {com.is_required && <span class='red'>*</span>}
|
|
68
136
|
</label>
|
|
137
|
+
{desc && <div class='form-field-description'>{desc}</div>}
|
|
69
138
|
{com.input_type === 'text' && this.renderFieldTextInput(com, idx)}
|
|
70
139
|
{com.input_type === 'textarea' && this.renderFieldTextareaInput(com, idx)}
|
|
71
140
|
{com.input_type === 'date' && this.renderFieldDateInput(com, idx)}
|
|
72
141
|
{com.input_type === 'radio' && this.renderFieldRadioInput(com, idx)}
|
|
73
|
-
{com.input_type === 'checkbox' &&
|
|
142
|
+
{com.input_type === 'checkbox' &&
|
|
143
|
+
(isChips(com) ? this.renderFieldChipsInput(com, idx) : this.renderFieldCheckboxInput(com, idx))}
|
|
74
144
|
{(com.input_type === 'dropdown' || com.input_type === 'select') && this.renderFieldDropdownInput(com, idx)}
|
|
75
145
|
{com.input_type === 'rating' && this.renderFieldRatingInput(com, idx)}
|
|
146
|
+
{com.input_type === 'linear_scale' && this.renderFieldLinearScaleInput(com, idx)}
|
|
76
147
|
{errorMsg && <div class='form-control--valid-input--error'>{errorMsg}</div>}
|
|
77
148
|
</div>
|
|
78
149
|
)
|
|
@@ -81,15 +152,13 @@ export default class Form2 extends Com {
|
|
|
81
152
|
renderFieldTextInput = (com, idx) => {
|
|
82
153
|
let cls = 'form-control'
|
|
83
154
|
if (this.state.errorFields[com.key]) cls = 'form-control input--error'
|
|
84
|
-
let val =
|
|
85
|
-
val = val.value || '""'
|
|
86
|
-
val = sb.parseJSON(val)
|
|
155
|
+
let val = this.getValue(com.key)
|
|
87
156
|
return (
|
|
88
157
|
<input
|
|
89
|
-
ref={this
|
|
158
|
+
ref={this.getRef(`${com.key}_input`)}
|
|
90
159
|
type='text'
|
|
91
160
|
class={cls}
|
|
92
|
-
placeholder={com
|
|
161
|
+
placeholder={getPlaceholder(com)}
|
|
93
162
|
value={val}
|
|
94
163
|
onInput={(e) => {
|
|
95
164
|
this.onChangeValue(com.key, e.target.value)
|
|
@@ -100,14 +169,12 @@ export default class Form2 extends Com {
|
|
|
100
169
|
}
|
|
101
170
|
|
|
102
171
|
renderFieldDateInput = (com, idx) => {
|
|
103
|
-
let val =
|
|
104
|
-
val = val.value || '""'
|
|
105
|
-
val = sb.parseJSON(val)
|
|
172
|
+
let val = this.getValue(com.key)
|
|
106
173
|
let cls = 'form-control'
|
|
107
174
|
if (this.state.errorFields[com.key]) cls = 'form-control input--error'
|
|
108
175
|
return (
|
|
109
176
|
<input
|
|
110
|
-
ref={this
|
|
177
|
+
ref={this.getRef(`${com.key}_input`)}
|
|
111
178
|
class={cls}
|
|
112
179
|
type='date'
|
|
113
180
|
value={val}
|
|
@@ -119,110 +186,236 @@ export default class Form2 extends Com {
|
|
|
119
186
|
)
|
|
120
187
|
}
|
|
121
188
|
|
|
189
|
+
// free text box shown when the "Other…" option is selected
|
|
190
|
+
renderOtherTextInput = (com) => {
|
|
191
|
+
if (!com.allow_other_option || !this.state.otherSelected[com.key]) return null
|
|
192
|
+
let cls = 'form-control form-other-input'
|
|
193
|
+
if (this.state.errorFields[com.key]) cls += ' input--error'
|
|
194
|
+
return (
|
|
195
|
+
<input
|
|
196
|
+
ref={this.getRef(`${com.key}_other_input`)}
|
|
197
|
+
type='text'
|
|
198
|
+
class={cls}
|
|
199
|
+
placeholder={this.tr('form_other_option_placeholder')}
|
|
200
|
+
value={this.state.otherText[com.key] || ''}
|
|
201
|
+
onInput={(e) => this.setOtherText(com, e.target.value)}
|
|
202
|
+
/>
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
122
206
|
renderFieldRadioInput = (com, idx) => {
|
|
123
|
-
let val =
|
|
124
|
-
|
|
125
|
-
val = sb.parseJSON(val)
|
|
207
|
+
let val = this.getValue(com.key)
|
|
208
|
+
let otherOn = !!this.state.otherSelected[com.key]
|
|
126
209
|
let wrappercls = 'form-radios-wrapper'
|
|
127
|
-
|
|
210
|
+
let count = sb.size(com.input_options) + (com.allow_other_option ? 1 : 0)
|
|
211
|
+
if (count >= 3 && this.props.mode !== 'attachment') wrappercls += ' horizontal'
|
|
128
212
|
return (
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
213
|
+
<Fragment>
|
|
214
|
+
<div class={wrappercls}>
|
|
215
|
+
{sb.map(com.input_options, (option, oidx) => {
|
|
216
|
+
let id = com.key + '_' + oidx
|
|
217
|
+
return (
|
|
218
|
+
<div class='radio'>
|
|
219
|
+
<input
|
|
220
|
+
type='radio'
|
|
221
|
+
name={com.key}
|
|
222
|
+
id={id}
|
|
223
|
+
checked={!otherOn && option.value === val}
|
|
224
|
+
onChange={() => this.toggleRadioField(com.key, option.value)}
|
|
225
|
+
/>
|
|
226
|
+
<label class='radio--label' for={id}>
|
|
227
|
+
{option.label}
|
|
228
|
+
</label>
|
|
229
|
+
<div class='radio--check' />
|
|
230
|
+
</div>
|
|
231
|
+
)
|
|
232
|
+
})}
|
|
233
|
+
{com.allow_other_option && (
|
|
133
234
|
<div class='radio'>
|
|
134
235
|
<input
|
|
135
236
|
type='radio'
|
|
136
237
|
name={com.key}
|
|
137
|
-
id={
|
|
138
|
-
checked={
|
|
139
|
-
onChange={() => this.
|
|
238
|
+
id={com.key + '_other'}
|
|
239
|
+
checked={otherOn}
|
|
240
|
+
onChange={() => this.setOtherSelected(com, true)}
|
|
140
241
|
/>
|
|
141
|
-
<label class='radio--label' for={
|
|
142
|
-
{
|
|
242
|
+
<label class='radio--label' for={com.key + '_other'}>
|
|
243
|
+
{this.tr('form_other_option')}
|
|
143
244
|
</label>
|
|
144
245
|
<div class='radio--check' />
|
|
145
246
|
</div>
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
|
|
247
|
+
)}
|
|
248
|
+
</div>
|
|
249
|
+
{this.renderOtherTextInput(com)}
|
|
250
|
+
</Fragment>
|
|
149
251
|
)
|
|
150
252
|
}
|
|
151
253
|
|
|
152
254
|
renderFieldCheckboxInput = (com, idx) => {
|
|
153
|
-
let val =
|
|
154
|
-
val
|
|
155
|
-
|
|
255
|
+
let val = this.getValue(com.key, '[]')
|
|
256
|
+
if (!Array.isArray(val)) val = []
|
|
257
|
+
let otherOn = !!this.state.otherSelected[com.key]
|
|
156
258
|
let wrappercls = 'form-checkboxes-wrapper'
|
|
157
|
-
|
|
259
|
+
let count = sb.size(com.input_options) + (com.allow_other_option ? 1 : 0)
|
|
260
|
+
if (count >= 3 && this.props.mode !== 'attachment') wrappercls += ' horizontal'
|
|
158
261
|
return (
|
|
159
|
-
<
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
262
|
+
<Fragment>
|
|
263
|
+
<div class={wrappercls}>
|
|
264
|
+
{sb.map(com.input_options, (option, oidx) => {
|
|
265
|
+
let id = com.key + '_' + oidx
|
|
266
|
+
return (
|
|
267
|
+
<div class='checkbox'>
|
|
268
|
+
<input
|
|
269
|
+
type='checkbox'
|
|
270
|
+
name={com.key}
|
|
271
|
+
id={id}
|
|
272
|
+
checked={val.includes(option.value)}
|
|
273
|
+
onChange={() => this.toggleCheckboxField(com.key, option.value)}
|
|
274
|
+
/>
|
|
275
|
+
<label class='checkbox--label' for={id}>
|
|
276
|
+
{option.label}
|
|
277
|
+
</label>
|
|
278
|
+
<div class='checkbox--check' />
|
|
279
|
+
</div>
|
|
280
|
+
)
|
|
281
|
+
})}
|
|
282
|
+
{com.allow_other_option && (
|
|
163
283
|
<div class='checkbox'>
|
|
164
284
|
<input
|
|
165
285
|
type='checkbox'
|
|
166
286
|
name={com.key}
|
|
167
|
-
id={
|
|
168
|
-
checked={
|
|
169
|
-
onChange={() => this.
|
|
287
|
+
id={com.key + '_other'}
|
|
288
|
+
checked={otherOn}
|
|
289
|
+
onChange={() => this.setOtherSelected(com, !otherOn)}
|
|
170
290
|
/>
|
|
171
|
-
<label class='checkbox--label' for={
|
|
172
|
-
{
|
|
291
|
+
<label class='checkbox--label' for={com.key + '_other'}>
|
|
292
|
+
{this.tr('form_other_option')}
|
|
173
293
|
</label>
|
|
174
294
|
<div class='checkbox--check' />
|
|
175
295
|
</div>
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
|
|
296
|
+
)}
|
|
297
|
+
</div>
|
|
298
|
+
{this.renderOtherTextInput(com)}
|
|
299
|
+
</Fragment>
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
renderFieldChipsInput = (com, idx) => {
|
|
304
|
+
let val = this.getValue(com.key, '[]')
|
|
305
|
+
if (!Array.isArray(val)) val = []
|
|
306
|
+
let otherOn = !!this.state.otherSelected[com.key]
|
|
307
|
+
return (
|
|
308
|
+
<Fragment>
|
|
309
|
+
<div class='form-chips' role='group'>
|
|
310
|
+
{sb.map(com.input_options, (option) => {
|
|
311
|
+
let selected = val.includes(option.value)
|
|
312
|
+
return (
|
|
313
|
+
<button
|
|
314
|
+
type='button'
|
|
315
|
+
class={'form-chip' + (selected ? ' selected' : '')}
|
|
316
|
+
aria-pressed={selected ? 'true' : 'false'}
|
|
317
|
+
onClick={() => this.toggleCheckboxField(com.key, option.value)}
|
|
318
|
+
>
|
|
319
|
+
{option.label || option.value}
|
|
320
|
+
</button>
|
|
321
|
+
)
|
|
322
|
+
})}
|
|
323
|
+
{com.allow_other_option && (
|
|
324
|
+
<button
|
|
325
|
+
type='button'
|
|
326
|
+
class={'form-chip form-chip-other' + (otherOn ? ' selected' : '')}
|
|
327
|
+
aria-pressed={otherOn ? 'true' : 'false'}
|
|
328
|
+
onClick={() => this.setOtherSelected(com, !otherOn)}
|
|
329
|
+
>
|
|
330
|
+
{this.tr('form_other_option')}
|
|
331
|
+
</button>
|
|
332
|
+
)}
|
|
333
|
+
</div>
|
|
334
|
+
{this.renderOtherTextInput(com)}
|
|
335
|
+
</Fragment>
|
|
179
336
|
)
|
|
180
337
|
}
|
|
181
338
|
|
|
182
339
|
renderFieldDropdownInput = (com, idx) => {
|
|
183
|
-
let val =
|
|
184
|
-
|
|
185
|
-
val = sb.parseJSON(val)
|
|
340
|
+
let val = this.getValue(com.key)
|
|
341
|
+
let otherOn = !!this.state.otherSelected[com.key]
|
|
186
342
|
let cls = 'form-control'
|
|
187
|
-
if (this.state.errorFields[com.key]) cls += ' input--error'
|
|
188
|
-
if (!val) cls += ' form-control--empty'
|
|
343
|
+
if (this.state.errorFields[com.key] && !otherOn) cls += ' input--error'
|
|
344
|
+
if (!val && !otherOn) cls += ' form-control--empty'
|
|
189
345
|
return (
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
346
|
+
<Fragment>
|
|
347
|
+
<select
|
|
348
|
+
class={cls}
|
|
349
|
+
ref={this.getRef(`${com.key}_input`)}
|
|
350
|
+
value={otherOn ? OTHER_OPTION_VALUE : val || ''}
|
|
351
|
+
onChange={(e) => {
|
|
352
|
+
if (e.target.value === OTHER_OPTION_VALUE) {
|
|
353
|
+
this.setOtherSelected(com, true)
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
if (otherOn) this.setOtherSelected(com, false, e.target.value)
|
|
357
|
+
else this.onChangeValue(com.key, e.target.value)
|
|
358
|
+
this.clearError(com.key)
|
|
359
|
+
}}
|
|
360
|
+
>
|
|
361
|
+
<option value=''>-- {this.props.t('no_select')} --</option>
|
|
362
|
+
{sb.map(com.input_options, (option) => {
|
|
363
|
+
return <option value={option.value}>{option.label}</option>
|
|
364
|
+
})}
|
|
365
|
+
{com.allow_other_option && <option value={OTHER_OPTION_VALUE}>{this.tr('form_other_option')}</option>}
|
|
366
|
+
</select>
|
|
367
|
+
{this.renderOtherTextInput(com)}
|
|
368
|
+
</Fragment>
|
|
203
369
|
)
|
|
204
370
|
}
|
|
205
371
|
|
|
206
372
|
renderFieldTextareaInput = (com, idx) => {
|
|
207
|
-
let
|
|
208
|
-
|
|
209
|
-
val = sb.parseJSON(val)
|
|
373
|
+
let cls = 'form-control'
|
|
374
|
+
if (this.state.errorFields[com.key]) cls += ' input--error'
|
|
210
375
|
return (
|
|
211
376
|
<textarea
|
|
212
377
|
onInput={(e) => {
|
|
213
378
|
this.onChangeValue(com.key, e.target.value)
|
|
214
379
|
this.clearError(com.key)
|
|
215
380
|
}}
|
|
216
|
-
ref={this
|
|
381
|
+
ref={this.getRef(`${com.key}_input`)}
|
|
217
382
|
rows='3'
|
|
218
|
-
class=
|
|
219
|
-
placeholder={com
|
|
383
|
+
class={cls}
|
|
384
|
+
placeholder={getPlaceholder(com)}
|
|
220
385
|
/>
|
|
221
386
|
)
|
|
222
387
|
}
|
|
223
388
|
|
|
224
389
|
renderFieldRatingInput = (com, idx) => {
|
|
225
|
-
|
|
390
|
+
let cfg = getRatingConfig(com)
|
|
391
|
+
let val = this.getValue(com.key)
|
|
392
|
+
let onChange = (v) => {
|
|
393
|
+
this.onChangeValue(com.key, v)
|
|
394
|
+
this.clearError(com.key)
|
|
395
|
+
}
|
|
396
|
+
if (cfg.kind === 'face') return <RatingFaces levels={cfg.levels} value={val} onChange={onChange} />
|
|
397
|
+
let selectedIdx = cfg.levels.findIndex((l) => sameValue(val, l.value))
|
|
398
|
+
return (
|
|
399
|
+
<RatingStars
|
|
400
|
+
count={cfg.levels.length}
|
|
401
|
+
value={selectedIdx + 1}
|
|
402
|
+
onChange={(n) => onChange(n > 0 ? toStoredValue(cfg.levels[n - 1].value) : '')}
|
|
403
|
+
/>
|
|
404
|
+
)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
renderFieldLinearScaleInput = (com, idx) => {
|
|
408
|
+
return (
|
|
409
|
+
<LinearScale
|
|
410
|
+
com={com}
|
|
411
|
+
numberedLabels={this.props.mode === 'attachment'}
|
|
412
|
+
value={this.getValue(com.key)}
|
|
413
|
+
onChange={(v) => {
|
|
414
|
+
this.onChangeValue(com.key, v)
|
|
415
|
+
this.clearError(com.key)
|
|
416
|
+
}}
|
|
417
|
+
/>
|
|
418
|
+
)
|
|
226
419
|
}
|
|
227
420
|
|
|
228
421
|
onSubmit = (e) => {
|
|
@@ -238,20 +431,19 @@ export default class Form2 extends Com {
|
|
|
238
431
|
let components = sb.get(this.props.formSetting, 'components') || []
|
|
239
432
|
sb.each(components, (com) => {
|
|
240
433
|
if (com.type === 'input') {
|
|
241
|
-
let val =
|
|
242
|
-
val = val.value || '""'
|
|
243
|
-
val = sb.parseJSON(val)
|
|
434
|
+
let val = this.getValue(com.key)
|
|
244
435
|
if (com.is_required) {
|
|
245
|
-
let isEmpty =
|
|
246
|
-
|
|
247
|
-
if (
|
|
436
|
+
let isEmpty = isEmptyValue(val)
|
|
437
|
+
// legacy rating stored 0 when the selected star was clicked again
|
|
438
|
+
if (com.input_type === 'rating' && val === 0) isEmpty = true
|
|
248
439
|
if (isEmpty) {
|
|
249
440
|
result = false
|
|
250
441
|
errors[com.key] = this.props.t('cannot_be_empty')
|
|
251
|
-
ref = `${com.key}_input`
|
|
442
|
+
if (!ref) ref = this.state.otherSelected[com.key] ? `${com.key}_other_input` : `${com.key}_input`
|
|
252
443
|
}
|
|
253
444
|
}
|
|
254
|
-
|
|
445
|
+
let isChoice = ['rating', 'linear_scale', 'radio', 'checkbox', 'dropdown', 'select'].includes(com.input_type)
|
|
446
|
+
if (com.validation && !Array.isArray(val) && !isChoice) {
|
|
255
447
|
let isInvalid = false
|
|
256
448
|
let validationText = ''
|
|
257
449
|
if (com.validation === 'email' && !sb.isEmail(val)) {
|
|
@@ -273,7 +465,7 @@ export default class Form2 extends Com {
|
|
|
273
465
|
if (isInvalid) {
|
|
274
466
|
result = false
|
|
275
467
|
errors[com.key] = this.props.t('format_is_invalid') + ' ' + validationText
|
|
276
|
-
ref = `${com.key}_input`
|
|
468
|
+
if (!ref) ref = `${com.key}_input`
|
|
277
469
|
}
|
|
278
470
|
}
|
|
279
471
|
}
|
|
@@ -287,13 +479,25 @@ export default class Form2 extends Com {
|
|
|
287
479
|
|
|
288
480
|
toggleRadioField(key, v) {
|
|
289
481
|
this.clearError(key)
|
|
482
|
+
if (this.state.otherSelected[key]) {
|
|
483
|
+
this.setState({otherSelected: {...this.state.otherSelected, [key]: false}})
|
|
484
|
+
}
|
|
290
485
|
this.onChangeValue(key, v)
|
|
291
486
|
}
|
|
292
487
|
|
|
488
|
+
getComponent = (key) => sb.find(sb.get(this.props.formSetting, 'components') || [], (c) => c.key === key)
|
|
489
|
+
|
|
490
|
+
// checkbox value = selected option values + the "Other" free text (if any), as a JSON array
|
|
491
|
+
commitCheckboxValue = (com, optionValues, otherOn, otherText) => {
|
|
492
|
+
let values = sb.filter(optionValues, (v) => sb.find(com.input_options, (o) => o.value === v))
|
|
493
|
+
let text = (otherText || '').trim()
|
|
494
|
+
if (otherOn && text) values.push(text)
|
|
495
|
+
this.onChangeValue(com.key, values)
|
|
496
|
+
}
|
|
497
|
+
|
|
293
498
|
toggleCheckboxField(key, v) {
|
|
294
|
-
let currentValues =
|
|
295
|
-
currentValues
|
|
296
|
-
currentValues = sb.parseJSON(currentValues)
|
|
499
|
+
let currentValues = this.getValue(key, '[]')
|
|
500
|
+
if (!Array.isArray(currentValues)) currentValues = []
|
|
297
501
|
|
|
298
502
|
if (currentValues.includes(v)) {
|
|
299
503
|
currentValues = currentValues.filter((s) => s !== v)
|
|
@@ -301,9 +505,45 @@ export default class Form2 extends Com {
|
|
|
301
505
|
currentValues.push(v)
|
|
302
506
|
}
|
|
303
507
|
this.clearError(key)
|
|
508
|
+
let com = this.getComponent(key)
|
|
509
|
+
if (com && com.allow_other_option) {
|
|
510
|
+
this.commitCheckboxValue(com, currentValues, this.state.otherSelected[key], this.state.otherText[key])
|
|
511
|
+
return
|
|
512
|
+
}
|
|
304
513
|
this.onChangeValue(key, currentValues)
|
|
305
514
|
}
|
|
306
515
|
|
|
516
|
+
// toggles the "Other…" option. For radio/dropdown, `nextValue` is the value
|
|
517
|
+
// to store when switching back to a regular option
|
|
518
|
+
setOtherSelected = (com, on, nextValue) => {
|
|
519
|
+
let key = com.key
|
|
520
|
+
let otherSelected = {...this.state.otherSelected, [key]: on}
|
|
521
|
+
let text = this.state.otherText[key] || ''
|
|
522
|
+
this.clearError(key)
|
|
523
|
+
this.setState({otherSelected}, () => {
|
|
524
|
+
let $input = this.getRef(`${key}_other_input`).current
|
|
525
|
+
if (on && $input) {
|
|
526
|
+
$input.focus()
|
|
527
|
+
}
|
|
528
|
+
})
|
|
529
|
+
if (com.input_type === 'checkbox') {
|
|
530
|
+
this.commitCheckboxValue(com, this.getValue(key, '[]') || [], on, text)
|
|
531
|
+
return
|
|
532
|
+
}
|
|
533
|
+
this.onChangeValue(key, on ? text.trim() : nextValue || '')
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
setOtherText = (com, text) => {
|
|
537
|
+
let key = com.key
|
|
538
|
+
this.setState({otherText: {...this.state.otherText, [key]: text}})
|
|
539
|
+
this.clearError(key)
|
|
540
|
+
if (com.input_type === 'checkbox') {
|
|
541
|
+
this.commitCheckboxValue(com, this.getValue(key, '[]') || [], true, text)
|
|
542
|
+
return
|
|
543
|
+
}
|
|
544
|
+
this.onChangeValue(key, text.trim())
|
|
545
|
+
}
|
|
546
|
+
|
|
307
547
|
onChangeValue = (key, v) => {
|
|
308
548
|
let formValues = sb.get(this.props.submission, 'values') || []
|
|
309
549
|
let f = sb.find(formValues, (field) => field.key === key)
|
|
@@ -328,9 +568,9 @@ export default class Form2 extends Com {
|
|
|
328
568
|
if (this.props.mode === 'attachment') {
|
|
329
569
|
if (title.trim() || desc.trim()) {
|
|
330
570
|
return (
|
|
331
|
-
<div class='form-component form-component-block'>
|
|
332
|
-
{title && <div>{title}</div>}
|
|
333
|
-
{desc && <div class='text-sm text-muted'>{desc}</div>}
|
|
571
|
+
<div class='form-component form-component-block form-attachment-header'>
|
|
572
|
+
{title && <div class='form-attachment-title'>{title}</div>}
|
|
573
|
+
{desc && <div class='form-attachment-desc text-sm text-muted'>{desc}</div>}
|
|
334
574
|
</div>
|
|
335
575
|
)
|
|
336
576
|
}
|
|
@@ -368,8 +608,6 @@ export default class Form2 extends Com {
|
|
|
368
608
|
|
|
369
609
|
render() {
|
|
370
610
|
let components = sb.get(this.props.formSetting, 'components') || []
|
|
371
|
-
let title = sb.get(this.props.formSetting, 'title') || ''
|
|
372
|
-
let desc = sb.get(this.props.formSetting, 'description') || ''
|
|
373
611
|
let cls = 'form-wrapper form'
|
|
374
612
|
if (this.props.mode === 'attachment') cls += ' form-wrapper-attachment'
|
|
375
613
|
return (
|
package/src/form/FormPage.js
CHANGED
|
@@ -68,11 +68,11 @@ export default class FormPage extends Com {
|
|
|
68
68
|
let descFont = sb.get(formSetting, 'text_style.font_family')
|
|
69
69
|
let inputLabelFont = sb.get(formSetting, 'input_style.font_family')
|
|
70
70
|
setting.form_background_color = formSetting.background_color
|
|
71
|
-
setting.form_header_font_size = sb.get(formSetting, 'header_style.font_size') || '
|
|
71
|
+
setting.form_header_font_size = sb.get(formSetting, 'header_style.font_size') || '28'
|
|
72
72
|
setting.form_header_font_family = headerFont
|
|
73
73
|
setting.form_desc_font_size = sb.get(formSetting, 'text_style.font_size') || '16'
|
|
74
74
|
setting.form_desc_font_family = descFont
|
|
75
|
-
setting.form_input_label_font_size = sb.get(formSetting, 'input_style.font_size') || '
|
|
75
|
+
setting.form_input_label_font_size = sb.get(formSetting, 'input_style.font_size') || '16'
|
|
76
76
|
setting.form_input_label_font_family = inputLabelFont
|
|
77
77
|
let outcss = sb.replaceCssVariable(css, setting)
|
|
78
78
|
|
|
@@ -184,7 +184,12 @@ export default class FormPage extends Com {
|
|
|
184
184
|
}
|
|
185
185
|
if (this.state.loading) return
|
|
186
186
|
this.setState({loading: true})
|
|
187
|
-
let res
|
|
187
|
+
let res
|
|
188
|
+
if (this.props.token) {
|
|
189
|
+
res = await this.props.store.checkFormToken(this.props.store.matchAccount().id, this.props.token)
|
|
190
|
+
} else {
|
|
191
|
+
res = await this.props.store.getPublicForm(this.props.formId)
|
|
192
|
+
}
|
|
188
193
|
//await sb.sleep(5000)
|
|
189
194
|
//res.error = 'true'
|
|
190
195
|
if (res.error) {
|
|
@@ -212,11 +217,19 @@ export default class FormPage extends Com {
|
|
|
212
217
|
}
|
|
213
218
|
if (this.state.submiting) return
|
|
214
219
|
this.setState({submiting: true})
|
|
215
|
-
let res
|
|
216
|
-
|
|
217
|
-
this.props.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
+
let res
|
|
221
|
+
if (this.props.token) {
|
|
222
|
+
res = await this.props.store.submitForm2(
|
|
223
|
+
this.props.store.matchAccount().id,
|
|
224
|
+
this.props.token,
|
|
225
|
+
this.state.submission,
|
|
226
|
+
)
|
|
227
|
+
} else {
|
|
228
|
+
res = await this.props.store.submitPublicForm(this.props.formId, {
|
|
229
|
+
values: sb.get(this.state.submission, 'values') || [],
|
|
230
|
+
page_url: document.referrer || location.href,
|
|
231
|
+
})
|
|
232
|
+
}
|
|
220
233
|
if (res.error) {
|
|
221
234
|
this.setState({submiting: false, errorMessage: this.props.t('form_submission_error')})
|
|
222
235
|
return
|