Passed
Push — master ( 695827...5cb715 )
by Paul
04:39
created

Form.postAjax   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 4
rs 10
1
/** global: GLSR, grecaptcha, HTMLFormElement, site_reviews */
2
;(function( window, document, GLSR, undefined ) {
3
4
	'use strict';
5
6
	var Form = function( formEl, buttonEl ) { // HTMLElement, HTMLElement
7
		this.button = buttonEl;
8
		this.form = formEl;
9
		this.init();
10
	};
11
12
	Form.prototype = {
13
14
		config: {
15
			fieldErrorsClass: 'glsr-field-errors',
16
			fieldSelector: '.glsr-field',
17
			formMessagesClass: 'glsr-form-messages',
18
			hasErrorClass: 'glsr-has-error',
19
		},
20
21
		/** @return void */
22
		addRemoveClass: function( el, classValue, bool ) { // HTMLElement, string, bool
23
			el.classList[bool ? 'add' : 'remove']( classValue );
24
		},
25
26
		/** @return void */
27
		clearFieldError: function( el ) { // HTMLElement
28
			var fieldEl = el.closest( this.config.fieldSelector );
29
			if( fieldEl === null )return;
30
			fieldEl.classList.remove( this.config.hasErrorClass );
31
			var errorEl = fieldEl.querySelector( this.config.fieldErrorsSelector );
32
			if( errorEl !== null ) {
33
				errorEl.parentNode.removeChild( errorEl );
34
			}
35
		},
36
37
		/** @return void */
38
		clearFormErrors: function() {
39
			this.getResultsEl().innerHTML = '';
40
			for( var i = 0; i < this.form.length; i++ ) {
41
				this.clearFieldError( this.form[i] );
42
			}
43
		},
44
45
		/** @return void */
46
		disableButton: function() {
47
			this.button.setAttribute( 'disabled', '' );
48
		},
49
50
		/** @return void */
51
		enableButton: function() {
52
			this.button.removeAttribute( 'disabled' );
53
		},
54
55
		/** @return void */
56
		fallbackSubmit: function() {
57
			if( this.isAjaxUploadSupported() && this.isFileAPISupported() && this.isFormDataSupported() )return;
58
			this.form.submit();
59
		},
60
61
		/** @return void */
62
		handleResponse: function( response ) { // object
63
			console.log( response );
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
64
			if( response.recaptcha === true ) {
65
				console.log( 'executing recaptcha' );
66
				return this.recaptchaExecute();
67
			}
68
			if( response.recaptcha === 'reset' ) {
69
				console.log( 'reseting failed recaptcha' );
70
				this.recaptchaReset();
71
			}
72
			if( response.errors === false ) {
73
				console.log( 'reseting recaptcha' );
74
				GLSR.recaptchaReset();
75
				this.form.reset();
76
			}
77
			console.log( 'submission finished' );
78
			this.showFieldErrors( response.errors );
79
			this.showResults( response );
80
			this.enableButton();
81
			response.form = this.form;
82
			document.dispatchEvent( new CustomEvent( 'site-reviews/after/submission', { detail: response }));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
Bug introduced by
The variable CustomEvent seems to be never declared. If this is a global, consider adding a /** global: CustomEvent */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
83
		},
84
85
		/** @return bool */
86
		isAjaxUploadSupported: function() {
87
			var xhr = new XMLHttpRequest();
0 ignored issues
show
Bug introduced by
The variable XMLHttpRequest seems to be never declared. If this is a global, consider adding a /** global: XMLHttpRequest */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
88
			return !!( xhr && ( 'upload' in xhr ) && ( 'onprogress' in xhr.upload ));
89
		},
90
91
		/** @return bool */
92
		isFileAPISupported: function() {
93
			var fi = document.createElement('INPUT');
94
			fi.type = 'file';
95
			return 'files' in fi;
96
		},
97
98
		/** @return bool */
99
		isFormDataSupported: function() {
100
			return !!window.FormData;
101
		},
102
103
		/** @return HTMLDivElement */
104
		getFieldErrorsEl: function( fieldEl ) { // HTMLElement
105
			var errorsEl = fieldEl.querySelector( '.' + this.config.fieldErrorsClass );
106
			if( errorsEl === null ) {
107
				errorsEl = document.createElement( 'div' );
108
				errorsEl.setAttribute( 'class', this.config.fieldErrorsClass );
109
				fieldEl.appendChild( errorsEl );
110
			}
111
			return errorsEl;
112
		},
113
114
		/** @return object */
115
		getFormData: function( recaptchaToken ) { // string|null
116
			if( recaptchaToken === undefined ) {
117
				recaptchaToken = '';
118
			}
119
			return {
120
				action: site_reviews.action,
121
				request: new FormData( this.form ),
122
				'g-recaptcha-response': recaptchaToken,
123
			};
124
		},
125
126
		/** @return HTMLDivElement */
127
		getResultsEl: function() {
128
			var resultsEl = this.form.querySelector( '.' + this.config.formMessagesClass );
129
			if( resultsEl === null ) {
130
				resultsEl = document.createElement( 'div' );
131
				resultsEl.setAttribute( 'class', this.config.formMessagesClass );
132
				this.button.parentNode.insertBefore( resultsEl, this.button.nextSibling );
133
			}
134
			return resultsEl;
135
		},
136
137
		/** @return void */
138
		init: function() {
139
			this.button.addEventListener( 'click', this.onClick.bind( this ));
140
			this.form.addEventListener( 'change', this.onChange.bind( this ));
141
			this.form.addEventListener( 'submit', this.onSubmit.bind( this ));
142
			this.initStarRatings();
143
		},
144
145
		/** @return void */
146
		initStarRatings: function() {
147
			new StarRating( 'select.glsr-star-rating', {
0 ignored issues
show
Bug introduced by
The variable StarRating seems to be never declared. If this is a global, consider adding a /** global: StarRating */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
148
				clearable: false,
149
				showText: false,
150
				onClick: this.clearFieldError(),
151
			});
152
		},
153
154
		/** @return void */
155
		onChange: function( ev ) { // Event
156
			this.clearFieldError( ev.target );
157
		},
158
159
		/**
160
		 * This event method handles the mayhem caused by the invisible-recaptcha plugin
161
		 * and is triggered on the invisible-recaptcha callback
162
		 * @return void */
163
		onClick: function() {
164
			var form = this;
165
			this.form.onsubmit = null;
166
			HTMLFormElement.prototype._submit = HTMLFormElement.prototype.submit;
167
			HTMLFormElement.prototype.submit = function() {
168
				var token = this.querySelector( '#g-recaptcha-response' );
169
				if( null !== token && this.querySelector( form.config.fieldSelector )) {
170
					form.submitForm( token.value );
171
					return;
172
				}
173
				this._submit();
174
			};
175
		},
176
177
		/** @return void */
178
		onSubmit: function( ev ) { // HTMLEvent
179
			if( this.form.classList.contains( 'no-ajax' ))return;
180
			ev.preventDefault();
181
			this.recaptchaAddListeners();
182
			this.clearFormErrors();
183
			this.submitForm();
184
		},
185
186
		/** @return void */
187
		recaptchaAddListeners: function() {
188
			var overlayEl = this.recaptchaGetOverlay();
189
			if( overlayEl === -1 )return;
190
			overlayEl.addEventListener( 'click', this.enableButton.bind( this ));
191
			window.addEventListener( 'keyup', this.recaptchaOnKeyup.bind( this, overlayEl ));
192
		},
193
194
		/** @return void */
195
		recaptchaExecute: function() {
196
			var recaptchaId = this.recaptchaGetId();
197
			if( recaptchaId !== -1 ) {
198
				grecaptcha.execute( recaptchaId );
199
				return;
200
			}
201
			// recaptcha ID not found so pass through an error
202
			this.submitForm( false );
203
		},
204
205
		/** @return string|int (-1) */
206
		recaptchaGetId: function() {
207
			return this.recaptchaSearch( function( value, id ) {
208
				if( Object.prototype.toString.call( value ) !== '[object HTMLDivElement]' )return;
209
				if( value.closest( 'form' ) === this.form ) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if value.closest("form") === this.form is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
210
					return id;
211
				}
212
			});
213
		},
214
215
		/** @return HTMLDivElement|int (-1) */
216
		recaptchaGetOverlay: function() {
217
			return this.recaptchaSearch( function( value ) {
218
				if( Object.prototype.toString.call( value ) !== '[object Object]' )return;
219
				for( var obj in value) {
220
					if( !value.hasOwnProperty( obj ) || Object.prototype.toString.call( value[obj] ) !== '[object HTMLDivElement]' )continue;
221
					if( value[obj].className === '' ) {
222
						return value[obj].firstChild;
223
					}
224
				}
225
				return false;
226
			});
227
		},
228
229
		/** @return void */
230
		recaptchaOnKeyup: function( ev ) { // KeyboardEvent
231
			if( ev.keyCode !== 27 )return;
232
			this.enableButton();
233
			this.recaptchaRemoveListeners( ev.target );
234
		},
235
236
		/** @return void */
237
		recaptchaRemoveListeners: function( overlayEl ) { // HTMLDivElement
238
			overlayEl.removeEventListener( 'click', this.enableButton );
239
			window.removeEventListener( 'keyup', this.recaptchaOnKeyup );
240
		},
241
242
		/** @return void */
243
		recaptchaReset: function() {
244
			var recaptchaId = this.recaptchaGetId();
245
			if( recaptchaId !== -1 ) {
246
				grecaptcha.reset( recaptchaId );
247
			}
248
		},
249
250
		/** @return mixed|int (-1) */
251
		recaptchaSearch: function( callback ) { // function
252
			var result = -1;
253
			if( window.hasOwnProperty( '___grecaptcha_cfg' )) {
254
				var clients = window.___grecaptcha_cfg.clients;
255
				var i, key;
256
				for( i in clients ) {
257
					for( key in clients[i] ) {
258
						if( !( result = callback( clients[i][key], i ).bind( this )))continue;
259
						return result;
260
					}
261
				}
262
			}
263
			return result;
264
		},
265
266
		/** @return void */
267
		postAjax: function( formData, success ) {
268
			var xhr = new XMLHttpRequest();
0 ignored issues
show
Bug introduced by
The variable XMLHttpRequest seems to be never declared. If this is a global, consider adding a /** global: XMLHttpRequest */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
269
			xhr.open( 'POST', site_reviews.ajaxurl );
270
			xhr.onreadystatechange = function() {
271
				if( xhr.readyState !== 4 )return;
272
				success( JSON.parse( xhr.responseText )).bind( this );
273
			}.bind( this );
274
			xhr.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' );
275
			xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8' );
276
			xhr.send( formData );
277
		},
278
279
		/** @return void */
280
		showFieldErrors: function( errors ) { // object
281
			if( !errors )return;
282
			var fieldEl, errorsEl;
283
			for( var error in errors ) {
284
				if( !errors.hasOwnProperty( error ))continue;
285
				fieldEl = this.form.querySelector( '[name=' + error + ']' ).closest( this.config.fieldSelector );
286
				fieldEl.classList.add( this.config.hasErrorClass );
287
				errorsEl = this.getFieldErrorsEl( fieldEl );
288
				for( var i = 0; i < errors[error].errors.length; i++ ) {
289
					errorsEl.innerHTML += errors[error].errors[i];
290
				}
291
			}
292
		},
293
294
		/** @return void */
295
		showResults: function( response ) { // object
296
			var resultsEl = this.getResultsEl();
297
			this.addRemoveClass( resultsEl, 'gslr-has-errors', !!response.errors );
298
			resultsEl.innerHTML = response.message;
299
		},
300
301
		/** @return void */
302
		submitForm: function( recaptchaToken ) { // string|null
303
			this.disableButton();
304
			this.fallbackSubmit();
305
			this.postAjax( this.getFormData( recaptchaToken ), this.handleResponse );
306
		},
307
	};
308
309
	GLSR.Forms = function() { // object
310
		this.nodeList = document.querySelectorAll( 'form.glsr-form' );
311
		this.forms = [];
312
		for( var i = 0; i < this.nodeList.length; i++ ) {
313
			var submitButton = this.nodeList[i].querySelector( '[type=submit]' );
314
			if( !submitButton )continue;
315
			this.forms.push( new Form( this.nodeList[i], submitButton ));
316
		}
317
	};
318
319
})( window, document, GLSR );
320