Passed
Push — master ( 5cb715...24da2d )
by Paul
04:25
created

Form.recaptchaSearch_   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 14
rs 8.8571
1
/** global: GLSR, grecaptcha, HTMLFormElement, StarRating */
2
;(function() {
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
		config: {
14
			fieldErrorsClass: 'glsr-field-errors',
15
			fieldSelector: '.glsr-field',
16
			formMessagesClass: 'glsr-form-messages',
17
			hasErrorClass: 'glsr-has-error',
18
		},
19
20
		/** @return void */
21
		addRemoveClass_: function( el, classValue, bool ) { // HTMLElement, string, bool
22
			el.classList[bool ? 'add' : 'remove']( classValue );
23
		},
24
25
		/** @return void */
26
		clearFieldError_: function( el ) { // HTMLElement
27
			var fieldEl = el.closest( this.config.fieldSelector );
28
			if( fieldEl === null )return;
29
			fieldEl.classList.remove( this.config.hasErrorClass );
30
			var errorEl = fieldEl.querySelector( this.config.fieldErrorsSelector );
31
			if( errorEl !== null ) {
32
				errorEl.parentNode.removeChild( errorEl );
33
			}
34
		},
35
36
		/** @return void */
37
		clearFormErrors_: function() {
38
			this.getResultsEl_().innerHTML = '';
39
			for( var i = 0; i < this.form.length; i++ ) {
40
				this.clearFieldError_( this.form[i] );
41
			}
42
		},
43
44
		/** @return void */
45
		disableButton_: function() {
46
			this.button.setAttribute( 'disabled', '' );
47
		},
48
49
		/** @return void */
50
		enableButton_: function() {
51
			this.button.removeAttribute( 'disabled' );
52
		},
53
54
		/** @return void */
55
		fallbackSubmit_: function() {
56
			if( GLSR.Ajax.isFileAPISupported() && GLSR.Ajax.isFormDataSupported() && GLSR.Ajax.isUploadSupported() )return;
57
			this.form.submit();
58
		},
59
60
		/** @return void */
61
		handleResponse_: function( response ) { // object
62
			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...
63
			if( response.recaptcha === true ) {
64
				console.log( 'executing recaptcha' );
65
				return this.recaptchaExecute_();
66
			}
67
			if( response.recaptcha === 'reset' ) {
68
				console.log( 'reseting failed recaptcha' );
69
				this.recaptchaReset_();
70
			}
71
			if( response.errors === false ) {
72
				console.log( 'reseting recaptcha' );
73
				this.recaptchaReset_();
74
				this.form.reset();
75
			}
76
			console.log( 'submission finished' );
77
			this.showFieldErrors_( response.errors );
78
			this.showResults_( response );
79
			this.enableButton_();
80
			response.form = this.form;
81
			document.dispatchEvent( new CustomEvent( 'site-reviews/after/submission', { detail: response }));
0 ignored issues
show
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...
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...
82
		},
83
84
		/** @return HTMLDivElement */
85
		getFieldErrorsEl_: function( fieldEl ) { // HTMLElement
86
			var errorsEl = fieldEl.querySelector( '.' + this.config.fieldErrorsClass );
87
			if( errorsEl === null ) {
88
				errorsEl = document.createElement( 'div' );
89
				errorsEl.setAttribute( 'class', this.config.fieldErrorsClass );
90
				fieldEl.appendChild( errorsEl );
91
			}
92
			return errorsEl;
93
		},
94
95
		/** @return object */
96
		getFormData_: function( recaptchaToken ) { // string|null
97
			recaptchaToken = recaptchaToken || '';
98
			var formData = new FormData( this.form );
99
			formData.append( 'g-recaptcha-response', recaptchaToken );
100
			return formData;
101
		},
102
103
		/** @return HTMLDivElement */
104
		getResultsEl_: function() {
105
			var resultsEl = this.form.querySelector( '.' + this.config.formMessagesClass );
106
			if( resultsEl === null ) {
107
				resultsEl = document.createElement( 'div' );
108
				resultsEl.setAttribute( 'class', this.config.formMessagesClass );
109
				this.button.parentNode.insertBefore( resultsEl, this.button.nextSibling );
110
			}
111
			return resultsEl;
112
		},
113
114
		/** @return void */
115
		init_: function() {
116
			this.button.addEventListener( 'click', this.onClick_.bind( this ));
117
			this.form.addEventListener( 'change', this.onChange_.bind( this ));
118
			this.form.addEventListener( 'submit', this.onSubmit_.bind( this ));
119
			this.initStarRatings_();
120
		},
121
122
		/** @return void */
123
		initStarRatings_: function() {
124
			new StarRating( 'select.glsr-star-rating', {
125
				clearable: false,
126
				showText: false,
127
				onClick: this.clearFieldError_.bind( this ),
128
			});
129
		},
130
131
		/** @return void */
132
		onChange_: function( ev ) { // Event
133
			this.clearFieldError_( ev.target );
134
		},
135
136
		/**
137
		 * This event method handles the mayhem caused by the invisible-recaptcha plugin
138
		 * and is triggered on the invisible-recaptcha callback
139
		 * @return void */
140
		onClick_: function() {
141
			var form = this;
142
			this.form.onsubmit = null;
143
			HTMLFormElement.prototype._submit = HTMLFormElement.prototype.submit;
144
			HTMLFormElement.prototype.submit = function() {
145
				var token = this.querySelector( '#g-recaptcha-response' );
146
				if( null !== token && this.querySelector( form.config.fieldSelector )) {
147
					form.submitForm_( token.value );
148
					return;
149
				}
150
				this._submit();
151
			};
152
		},
153
154
		/** @return void */
155
		onSubmit_: function( ev ) { // HTMLEvent
156
			if( this.form.classList.contains( 'no-ajax' ))return;
157
			ev.preventDefault();
158
			this.recaptchaAddListeners_();
159
			this.clearFormErrors_();
160
			this.submitForm_();
161
		},
162
163
		/** @return void */
164
		recaptchaAddListeners_: function() {
165
			var overlayEl = this.recaptchaGetOverlay_();
166
			if( overlayEl === -1 )return;
167
			overlayEl.addEventListener( 'click', this.enableButton_.bind( this ));
168
			window.addEventListener( 'keyup', this.recaptchaOnKeyup_.bind( this, overlayEl ));
169
		},
170
171
		/** @return void */
172
		recaptchaExecute_: function() {
173
			var recaptchaId = this.recaptchaGetId_();
174
			if( recaptchaId !== -1 ) {
175
				grecaptcha.execute( recaptchaId );
176
				return;
177
			}
178
			// recaptcha ID not found so pass through an error
179
			this.submitForm_( false );
180
		},
181
182
		/** @return string|int (-1) */
183
		recaptchaGetId_: function() {
184
			return this.recaptchaSearch_( function( value, id ) {
185
				if( Object.prototype.toString.call( value ) !== '[object HTMLDivElement]' )return;
186
				if( value.closest( 'form' ) === this.form ) {
1 ignored issue
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...
187
					return id;
188
				}
189
			});
190
		},
191
192
		/** @return HTMLDivElement|int (-1) */
193
		recaptchaGetOverlay_: function() {
194
			return this.recaptchaSearch_( function( value ) {
195
				if( Object.prototype.toString.call( value ) !== '[object Object]' )return;
196
				for( var obj in value) {
197
					if( !value.hasOwnProperty( obj ) || Object.prototype.toString.call( value[obj] ) !== '[object HTMLDivElement]' )continue;
198
					if( value[obj].className === '' ) {
199
						return value[obj].firstChild;
200
					}
201
				}
202
				return false;
203
			});
204
		},
205
206
		/** @return void */
207
		recaptchaOnKeyup_: function( ev ) { // KeyboardEvent
208
			if( ev.keyCode !== 27 )return;
209
			this.enableButton_();
210
			this.recaptchaRemoveListeners_( ev.target );
211
		},
212
213
		/** @return void */
214
		recaptchaRemoveListeners_: function( overlayEl ) { // HTMLDivElement
215
			overlayEl.removeEventListener( 'click', this.enableButton_ );
216
			window.removeEventListener( 'keyup', this.recaptchaOnKeyup_ );
217
		},
218
219
		/** @return void */
220
		recaptchaReset_: function() {
221
			var recaptchaId = this.recaptchaGetId_();
222
			if( recaptchaId !== -1 ) {
223
				grecaptcha.reset( recaptchaId );
224
			}
225
		},
226
227
		/** @return mixed|int (-1) */
228
		recaptchaSearch_: function( callback ) { // function
229
			var result = -1;
230
			if( window.hasOwnProperty( '___grecaptcha_cfg' )) {
231
				var clients = window.___grecaptcha_cfg.clients;
232
				var i, key;
233
				for( i in clients ) {
234
					for( key in clients[i] ) {
235
						if( !( result = callback( clients[i][key], i ).bind( this )))continue;
236
						return result;
237
					}
238
				}
239
			}
240
			return result;
241
		},
242
243
		/** @return void */
244
		showFieldErrors_: function( errors ) { // object
245
			if( !errors )return;
246
			var fieldEl, errorsEl;
247
			for( var error in errors ) {
248
				if( !errors.hasOwnProperty( error ))continue;
249
				fieldEl = this.form.querySelector( '[name=' + error + ']' ).closest( this.config.fieldSelector );
250
				fieldEl.classList.add( this.config.hasErrorClass );
251
				errorsEl = this.getFieldErrorsEl_( fieldEl );
252
				for( var i = 0; i < errors[error].errors.length; i++ ) {
253
					errorsEl.innerHTML += errors[error].errors[i];
254
				}
255
			}
256
		},
257
258
		/** @return void */
259
		showResults_: function( response ) { // object
260
			var resultsEl = this.getResultsEl_();
261
			this.addRemoveClass_( resultsEl, 'gslr-has-errors', !!response.errors );
262
			resultsEl.innerHTML = response.message;
263
		},
264
265
		/** @return void */
266
		submitForm_: function( recaptchaToken ) { // string|null
267
			this.disableButton_();
268
			this.fallbackSubmit_();
269
			GLSR.Ajax.post( this.getFormData_( recaptchaToken ), this.handleResponse_.bind( this ), {
270
				'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
271
			});
272
		},
273
	};
274
275
	GLSR.Forms = function() {
276
		this.nodeList = document.querySelectorAll( 'form.glsr-form' );
277
		this.forms = [];
278
		for( var i = 0; i < this.nodeList.length; i++ ) {
279
			var submitButton = this.nodeList[i].querySelector( '[type=submit]' );
280
			if( !submitButton )continue;
281
			this.forms.push( new Form( this.nodeList[i], submitButton ));
282
		}
283
	};
284
})();
285