Passed
Push — master ( 4f48ff...695827 )
by Paul
04:55
created

+/scripts/public/forms.js   A

Complexity

Total Complexity 0
Complexity/F 0

Size

Lines of Code 1
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
nc 1
dl 0
loc 1
rs 10
c 0
b 0
f 0
wmc 0
mnd 0
bc 0
fnc 0
bpm 0
cpm 0
noi 0
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