1
|
|
|
/** global: GLSR, grecaptcha, HTMLFormElement, site_reviews */ |
2
|
|
|
;(function( window, document, GLSR, undefined ) { |
3
|
|
|
|
4
|
|
|
"use strict"; |
5
|
|
|
|
6
|
|
|
var Forms = function() { // object |
7
|
|
|
this.nodeList = document.querySelectorAll( 'form.glsr-form' ); |
8
|
|
|
this.forms = []; |
9
|
|
|
for( var i = 0; i < this.nodeList.length; i++ ) { |
10
|
|
|
var submitButton = this.nodeList[i].querySelector( '[type=submit]' ); |
11
|
|
|
if( !submitButton )continue; |
12
|
|
|
this.forms.push( new Form( this.nodeList[i], submitButton )); |
13
|
|
|
} |
14
|
|
|
}; |
15
|
|
|
|
16
|
|
|
var Form = function( formEl, buttonEl ) { // HTMLElement, HTMLElement |
17
|
|
|
this.button = buttonEl; |
18
|
|
|
this.form = formEl; |
19
|
|
|
this.init(); |
20
|
|
|
}; |
21
|
|
|
|
22
|
|
|
Form.prototype = { |
23
|
|
|
|
24
|
|
|
config: { |
25
|
|
|
fieldErrorsClass: 'glsr-field-errors', |
26
|
|
|
fieldSelector: '.glsr-field', |
27
|
|
|
formMessagesClass: 'glsr-form-messages', |
28
|
|
|
hasErrorClass: 'glsr-has-error', |
29
|
|
|
}, |
30
|
|
|
|
31
|
|
|
/** @return void */ |
32
|
|
|
addRemoveClass: function( el, classValue, bool ) { // HTMLElement, string, bool |
33
|
|
|
el.classList[bool ? 'add' : 'remove']( classValue ); |
34
|
|
|
}, |
35
|
|
|
|
36
|
|
|
/** @return void */ |
37
|
|
|
clearFieldError: function( el ) { // HTMLElement |
38
|
|
|
var fieldEl = el.closest( this.config.fieldSelector ); |
39
|
|
|
if( fieldEl === null )return; |
40
|
|
|
fieldEl.classList.remove( this.config.hasErrorClass ); |
41
|
|
|
var errorEl = fieldEl.querySelector( this.config.fieldErrorsSelector ); |
42
|
|
|
if( errorEl !== null ) { |
43
|
|
|
errorEl.parentNode.removeChild( errorEl ); |
44
|
|
|
} |
45
|
|
|
}, |
46
|
|
|
|
47
|
|
|
/** @return void */ |
48
|
|
|
clearFormErrors: function() { |
49
|
|
|
this.getResultsEl().innerHTML = ''; |
50
|
|
|
for( var i = 0; i < this.form.length; i++ ) { |
51
|
|
|
this.clearFieldError( this.form[i] ); |
52
|
|
|
} |
53
|
|
|
}, |
54
|
|
|
|
55
|
|
|
/** @return void */ |
56
|
|
|
disableButton: function() { |
57
|
|
|
this.button.setAttribute( 'disabled', '' ); |
58
|
|
|
}, |
59
|
|
|
|
60
|
|
|
/** @return void */ |
61
|
|
|
enableButton: function() { |
62
|
|
|
this.button.removeAttribute( 'disabled' ); |
63
|
|
|
}, |
64
|
|
|
|
65
|
|
|
/** @return void */ |
66
|
|
|
fallbackSubmit: function() { |
67
|
|
|
if( this.isAjaxUploadSupported() && this.isFileAPISupported() && this.isFormDataSupported() )return; |
68
|
|
|
this.form.submit(); |
69
|
|
|
}, |
70
|
|
|
|
71
|
|
|
/** @return void */ |
72
|
|
|
handleResponse: function( response ) { // object |
73
|
|
|
console.log( response ); |
74
|
|
|
if( response.recaptcha === true ) { |
75
|
|
|
console.log( 'executing recaptcha' ); |
76
|
|
|
return this.recaptchaExecute(); |
77
|
|
|
} |
78
|
|
|
if( response.recaptcha === 'reset' ) { |
79
|
|
|
console.log( 'reseting failed recaptcha' ); |
80
|
|
|
this.recaptchaReset(); |
81
|
|
|
} |
82
|
|
|
if( response.errors === false ) { |
83
|
|
|
console.log( 'reseting recaptcha' ); |
84
|
|
|
GLSR.recaptchaReset(); |
85
|
|
|
this.form.reset(); |
86
|
|
|
} |
87
|
|
|
console.log( 'submission finished' ); |
88
|
|
|
this.showFieldErrors( response.errors ); |
89
|
|
|
this.showResults( response ); |
90
|
|
|
this.enableButton(); |
91
|
|
|
response.form = this.form; |
92
|
|
|
document.dispatchEvent( new CustomEvent( 'site-reviews/after/submission', { detail: response })); |
93
|
|
|
}, |
94
|
|
|
|
95
|
|
|
/** @return bool */ |
96
|
|
|
isAjaxUploadSupported: function() { |
97
|
|
|
var xhr = new XMLHttpRequest(); |
98
|
|
|
return !!( xhr && ( 'upload' in xhr ) && ( 'onprogress' in xhr.upload )); |
99
|
|
|
}, |
100
|
|
|
|
101
|
|
|
/** @return bool */ |
102
|
|
|
isFileAPISupported: function() { |
103
|
|
|
var fi = document.createElement('INPUT'); |
104
|
|
|
fi.type = 'file'; |
105
|
|
|
return 'files' in fi; |
106
|
|
|
}, |
107
|
|
|
|
108
|
|
|
/** @return bool */ |
109
|
|
|
isFormDataSupported: function() { |
110
|
|
|
return !!window.FormData; |
111
|
|
|
}, |
112
|
|
|
|
113
|
|
|
/** @return HTMLDivElement */ |
114
|
|
|
getFieldErrorsEl: function( fieldEl ) { // HTMLElement |
115
|
|
|
var errorsEl = fieldEl.querySelector( '.' + this.config.fieldErrorsClass ); |
116
|
|
|
if( errorsEl === null ) { |
117
|
|
|
errorsEl = document.createElement( 'div' ); |
118
|
|
|
errorsEl.setAttribute( 'class', this.config.fieldErrorsClass ); |
119
|
|
|
el.appendChild( newEl ); |
120
|
|
|
} |
121
|
|
|
return errorsEl; |
122
|
|
|
}, |
123
|
|
|
|
124
|
|
|
/** @return object */ |
125
|
|
|
getFormData: function( recaptchaToken ) { // string|null |
126
|
|
|
if( recaptchaToken === undefined ) { |
127
|
|
|
recaptchaToken = ''; |
128
|
|
|
} |
129
|
|
|
return { |
130
|
|
|
action: site_reviews.action, |
131
|
|
|
request: new FormData( this.form ), |
132
|
|
|
'g-recaptcha-response': recaptchaToken, |
133
|
|
|
}; |
134
|
|
|
}, |
135
|
|
|
|
136
|
|
|
/** @return HTMLDivElement */ |
137
|
|
|
getResultsEl: function() { |
138
|
|
|
var resultsEl = this.form.querySelector( '.' + this.config.formMessagesClass ); |
139
|
|
|
if( resultsEl === null ) { |
140
|
|
|
resultsEl = document.createElement( 'div' ); |
141
|
|
|
resultsEl.setAttribute( 'class', this.config.formMessagesClass ); |
142
|
|
|
this.button.parentNode.insertBefore( resultsEl, this.button.nextSibling ); |
143
|
|
|
} |
144
|
|
|
return resultsEl; |
145
|
|
|
}, |
146
|
|
|
|
147
|
|
|
/** @return void */ |
148
|
|
|
init: function() { |
149
|
|
|
this.button.addEventListener( 'click', this.onClick.bind( this )); |
150
|
|
|
this.form.addEventListener( 'change', this.onChange.bind( this )); |
151
|
|
|
this.form.addEventListener( 'submit', this.onSubmit.bind( this )); |
152
|
|
|
this.initStarRatings(); |
153
|
|
|
}, |
154
|
|
|
|
155
|
|
|
/** @return void */ |
156
|
|
|
initStarRatings: function() { |
157
|
|
|
new StarRating( 'select.glsr-star-rating', { |
158
|
|
|
clearable: false, |
159
|
|
|
showText: false, |
160
|
|
|
onClick: this.clearFieldError(), |
161
|
|
|
}); |
162
|
|
|
}, |
163
|
|
|
|
164
|
|
|
/** @return void */ |
165
|
|
|
onChange: function( ev ) { // Event |
166
|
|
|
this.clearFieldError( ev.target ); |
167
|
|
|
}, |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* This event method handles the mayhem caused by the invisible-recaptcha plugin |
171
|
|
|
* and is triggered on the invisible-recaptcha callback |
172
|
|
|
* @return void */ |
173
|
|
|
onClick: function( ev ) { // MouseEvent |
174
|
|
|
var form = this; |
175
|
|
|
this.form.onsubmit = null; |
176
|
|
|
HTMLFormElement.prototype._submit = HTMLFormElement.prototype.submit; |
177
|
|
|
HTMLFormElement.prototype.submit = function() { |
178
|
|
|
var token = this.querySelector( '#g-recaptcha-response' ); |
179
|
|
|
if( null !== token && this.querySelector( form.config.fieldSelector )) { |
180
|
|
|
form.submitForm( token.value ); |
181
|
|
|
return; |
182
|
|
|
} |
183
|
|
|
this._submit(); |
184
|
|
|
}); |
185
|
|
|
}, |
186
|
|
|
|
187
|
|
|
/** @return void */ |
188
|
|
|
onSubmit: function( ev ) { // HTMLEvent |
189
|
|
|
if( this.form.classList.contains( 'no-ajax' ))return; |
190
|
|
|
ev.preventDefault(); |
191
|
|
|
this.recaptchaAddListeners(); |
192
|
|
|
this.clearFormErrors(); |
193
|
|
|
this.submitForm(); |
194
|
|
|
}, |
195
|
|
|
|
196
|
|
|
/** @return void */ |
197
|
|
|
recaptchaAddListeners: function() { |
198
|
|
|
var overlayEl = this.recaptchaGetOverlay(); |
199
|
|
|
if( overlayEl === -1 )return; |
200
|
|
|
overlayEl.addEventListener( 'click', this.enableButton.bind( this )); |
201
|
|
|
window.addEventListener( 'keyup', this.recaptchaOnKeyup.bind( this, overlayEl )); |
202
|
|
|
}, |
203
|
|
|
|
204
|
|
|
/** @return void */ |
205
|
|
|
recaptchaExecute: function() { |
206
|
|
|
var recaptchaId = this.recaptchaGetId(); |
207
|
|
|
if( recaptchaId !== -1 ) { |
208
|
|
|
grecaptcha.execute( recaptchaId ); |
209
|
|
|
return; |
210
|
|
|
} |
211
|
|
|
// recaptcha ID not found so pass through an error |
212
|
|
|
this.submitForm( false ); |
213
|
|
|
}, |
214
|
|
|
|
215
|
|
|
/** @return string|int (-1) */ |
216
|
|
|
recaptchaGetId: function() { |
217
|
|
|
return this.recaptchaSearch( function( value, id ) { |
218
|
|
|
if( Object.prototype.toString.call( value ) !== '[object HTMLDivElement]' )return; |
219
|
|
|
if( value.closest( 'form' ) === this.form ) { |
220
|
|
|
return id; |
221
|
|
|
} |
222
|
|
|
}); |
223
|
|
|
}, |
224
|
|
|
|
225
|
|
|
/** @return HTMLDivElement|int (-1) */ |
226
|
|
|
recaptchaGetOverlay: function() { |
227
|
|
|
return this.recaptchaSearch( function( value ) { |
228
|
|
|
if( Object.prototype.toString.call( value ) !== '[object Object]' )return; |
229
|
|
|
for( var obj in value) { |
230
|
|
|
if( !value.hasOwnProperty( obj ) || Object.prototype.toString.call( value[obj] ) !== '[object HTMLDivElement]' )continue; |
231
|
|
|
if( value[obj].className === '' ) { |
232
|
|
|
return value[obj].firstChild; |
233
|
|
|
} |
234
|
|
|
} |
235
|
|
|
return false; |
236
|
|
|
}); |
237
|
|
|
}, |
238
|
|
|
|
239
|
|
|
/** @return void */ |
240
|
|
|
recaptchaOnKeyup: function( ev ) { // KeyboardEvent |
241
|
|
|
if( ev.keyCode !== 27 )return; |
242
|
|
|
this.enableButton(); |
243
|
|
|
this.recaptchaRemoveListeners( ev.target ); |
244
|
|
|
}, |
245
|
|
|
|
246
|
|
|
/** @return void */ |
247
|
|
|
recaptchaRemoveListeners: function( overlayEl ) { // HTMLDivElement |
248
|
|
|
overlayEl.removeEventListener( 'click', this.enableButton ); |
249
|
|
|
window.removeEventListener( 'keyup', this.recaptchaOnKeyup ); |
250
|
|
|
}, |
251
|
|
|
|
252
|
|
|
/** @return void */ |
253
|
|
|
recaptchaReset: function() { |
254
|
|
|
var recaptchaId = this.recaptchaGetId(); |
255
|
|
|
if( recaptchaId !== -1 ) { |
256
|
|
|
grecaptcha.reset( recaptchaId ); |
257
|
|
|
} |
258
|
|
|
}, |
259
|
|
|
|
260
|
|
|
/** @return mixed|int (-1) */ |
261
|
|
|
recaptchaSearch: function() { |
262
|
|
|
var result = -1; |
263
|
|
|
if( window.hasOwnProperty( '___grecaptcha_cfg' )) { |
264
|
|
|
var clients = window.___grecaptcha_cfg.clients; |
265
|
|
|
var i, key; |
266
|
|
|
for( i in clients ) { |
267
|
|
|
for( key in clients[i] ) { |
268
|
|
|
if( !( result = callback( clients[i][key], i ).bind( this )))continue; |
269
|
|
|
return result; |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
} |
273
|
|
|
return result; |
274
|
|
|
}, |
275
|
|
|
|
276
|
|
|
/** @return void */ |
277
|
|
|
postAjax: function( formData, success ) { |
278
|
|
|
var xhr = new XMLHttpRequest(); |
279
|
|
|
xhr.open( 'POST', site_reviews.ajaxurl ); |
280
|
|
|
xhr.onreadystatechange = function() { |
281
|
|
|
if( xhr.readyState !== 4 )return; |
282
|
|
|
success( JSON.parse( xhr.responseText )).bind( this ); |
283
|
|
|
}.bind( this ); |
284
|
|
|
xhr.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' ); |
285
|
|
|
xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8' ); |
286
|
|
|
xhr.send( formData ); |
287
|
|
|
}, |
288
|
|
|
|
289
|
|
|
/** @return void */ |
290
|
|
|
showFieldErrors: function( errors ) { // object |
291
|
|
|
if( !errors )return; |
292
|
|
|
var fieldEl, errorsEl; |
293
|
|
|
for( var error in errors ) { |
294
|
|
|
if( !errors.hasOwnProperty( error ))continue; |
295
|
|
|
fieldEl = this.form.querySelector( '[name=' + error + ']' ).closest( this.config.fieldSelector ); |
296
|
|
|
fieldEl.classList.add( this.config.hasErrorClass ); |
297
|
|
|
errorsEl = this.getFieldErrorsEl( $fieldEl ); |
298
|
|
|
for( var i = 0; i < errors[error].errors.length; i++ ) { |
299
|
|
|
errorsEl.innerHTML += errors[error].errors[i]; |
300
|
|
|
} |
301
|
|
|
} |
302
|
|
|
}, |
303
|
|
|
|
304
|
|
|
/** @return void */ |
305
|
|
|
showResults: function( response ) { // object |
306
|
|
|
var resultsEl = this.getResultsEl(); |
307
|
|
|
this.addRemoveClass( resultsEl, 'gslr-has-errors', !!response.errors ); |
308
|
|
|
resultsEl.innerHTML = response.message; |
309
|
|
|
}, |
310
|
|
|
|
311
|
|
|
/** @return void */ |
312
|
|
|
submitForm: function( recaptchaToken ) { // string|null |
313
|
|
|
this.disableButton(); |
314
|
|
|
this.fallbackSubmit(); |
315
|
|
|
this.postAjax( this.getFormData( recaptchaToken ), this.handleResponse ); |
316
|
|
|
}, |
317
|
|
|
}; |
318
|
|
|
|
319
|
|
|
GLSR.Forms = Forms; |
320
|
|
|
|
321
|
|
|
})( window, document, GLSR ); |
322
|
|
|
|