Completed
Push — master ( dce43d...f66823 )
by J.D.
04:19
created

Base.extend.cancel   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 25
rs 8.8571
c 1
b 0
f 0
1
/**
2
 * wp.wordpoints.hooks.view.Base
3
 *
4
 * @class
5
 * @augments Backbone.View
6
 * @augments wp.wordpoints.hooks.view.Base
7
 */
8
var Base = wp.wordpoints.hooks.view.Base,
0 ignored issues
show
Bug introduced by
The variable wp seems to be never declared. If this is a global, consider adding a /** global: wp */ 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...
9
	Fields = wp.wordpoints.hooks.Fields,
10
	Reactors = wp.wordpoints.hooks.Reactors,
11
	Args = wp.wordpoints.hooks.Args,
12
	$ = Backbone.$,
0 ignored issues
show
Bug introduced by
The variable Backbone seems to be never declared. If this is a global, consider adding a /** global: Backbone */ 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...
13
	l10n = wp.wordpoints.hooks.view.l10n,
14
	data = wp.wordpoints.hooks.view.data,
15
	Reaction;
16
17
// The DOM element for a reaction...
18
Reaction = Base.extend({
19
20
	namespace: 'reaction',
21
22
	className: 'wordpoints-hook-reaction',
23
24
	template: wp.wordpoints.hooks.template( 'hook-reaction' ),
25
26
	// The DOM events specific to an item.
27
	events: function () {
28
29
		var events = {
30
			'click .actions .delete': 'confirmDelete',
31
			'click .save':            'save',
32
			'click .cancel':          'cancel',
33
			'click .close':           'close',
34
			'click .edit':            'edit',
35
			'change .fields *':       'lockOpen'
36
		};
37
38
		/*
39
		 * Use feature detection to determine whether we should use the `input`
40
		 * event. Input is preferred but lacks support in legacy browsers.
41
		 */
42
		if ( 'oninput' in document.createElement( 'input' ) ) {
43
			events['input input'] = 'lockOpen';
44
		} else {
45
			events['keyup input'] = 'maybeLockOpen';
46
		}
47
48
		return events;
49
	},
50
51
	initialize: function () {
52
53
		this.listenTo( this.model, 'change:description', this.renderDescription );
54
		this.listenTo( this.model, 'change:reactor', this.setReactor );
55
		this.listenTo( this.model, 'change:reactor', this.renderTarget );
56
		this.listenTo( this.model, 'destroy', this.remove );
57
		this.listenTo( this.model, 'sync', this.showSuccess );
58
		this.listenTo( this.model, 'error', this.showAjaxErrors );
59
		this.listenTo( this.model, 'invalid', this.showValidationErrors );
60
61
		this.on( 'render:settings', this.renderTarget );
62
63
		this.setReactor();
64
	},
65
66
	render: function () {
67
68
		this.$el.html( this.template() );
69
70
		this.$title    = this.$( '.title' );
71
		this.$fields   = this.$( '.fields' );
72
		this.$settings = this.$fields.find( '.settings' );
73
		this.$target   = this.$fields.find( '.target' );
74
75
		this.renderDescription();
76
77
		this.trigger( 'render', this );
78
79
		return this;
80
	},
81
82
	// Re-render the title of the hook.
83
	renderDescription: function () {
84
85
		this.$title.text( this.model.get( 'description' ) );
86
87
		this.trigger( 'render:title', this );
88
	},
89
90
	renderFields: function () {
91
92
		var currentActionType = this.getCurrentActionType();
93
94
		this.trigger( 'render:settings', this.$settings, currentActionType, this );
95
		this.trigger( 'render:fields', this.$fields, currentActionType, this );
96
97
		this.renderedFields = true;
98
	},
99
100
	renderTarget: function () {
101
102
		var argTypes = this.Reactor.get( 'arg_types' ),
103
			end;
104
105
		// If there is just one arg type, we can use the `_.where()`-like syntax.
106
		if ( argTypes.length === 1 ) {
107
108
			end = { _canonical: argTypes[0], _type: 'entity' };
109
110
		} else {
111
112
			// Otherwise, we'll be need our own function, for `_.filter()`.
113
			end = function ( arg ) {
114
				return (
115
					arg.get( '_type' ) === 'entity'
116
					&& _.contains( argTypes, arg.get( '_canonical' ) )
0 ignored issues
show
Bug introduced by
The variable _ seems to be never declared. If this is a global, consider adding a /** global: _ */ 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...
117
				);
118
			};
119
		}
120
121
		var hierarchies = Args.getHierarchiesMatching( {
122
			event: this.model.get( 'event' ),
123
			end: end
124
		} );
125
126
		var options = [];
127
128
		_.each( hierarchies, function ( hierarchy ) {
129
			options.push( {
130
				label: Args.buildHierarchyHumanId( hierarchy ),
131
				value: _.pluck( _.pluck( hierarchy, 'attributes' ), 'slug' ).join( ',' )
0 ignored issues
show
Bug introduced by
The variable _ seems to be never declared. If this is a global, consider adding a /** global: _ */ 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...
132
			} );
133
		});
134
135
		var value = this.model.get( 'target' );
136
137
		if ( _.isArray( value ) ) {
138
			value = value.join( ',' );
139
		}
140
141
		var label = this.Reactor.get( 'target_label' );
142
143
		if ( ! label ) {
144
			label = l10n.target_label;
145
		}
146
147
		if ( ! this.model.isNew() ) {
148
			label += ' ' + l10n.cannotBeChanged;
149
		}
150
151
		var field = Fields.create(
152
			'target'
153
			, value
154
			, {
155
				type: 'select',
156
				options: options,
157
				label: label
158
			}
159
		);
160
161
		this.$target.html( field );
162
163
		if ( ! this.model.isNew() ) {
164
			this.$target.find( 'select' ).prop( 'disabled', true );
165
		}
166
	},
167
168
	setReactor: function () {
169
		this.Reactor = Reactors.get( this.model.get( 'reactor' ) );
170
	},
171
172
	// Get the current action type that settings are being displayed for.
173
	// Right now we just default this to the first action type that the reactor
174
	// supports which is registered for this event.
175
	getCurrentActionType: function () {
176
177
		var eventActionTypes = data.event_action_types[ this.model.get( 'event' ) ];
178
179
		if ( ! eventActionTypes ) {
180
			return;
181
		}
182
183
		var reactorActionTypes = this.Reactor.get( 'action_types' );
184
185
		// We loop through the reactor action types as the primary list, because it
186
		// is in order, while the event action types isn't in any particular order.
187
		// Otherwise we'd end up selecting the action types inconsistently.
188
		for ( var i = 0; i < reactorActionTypes.length; i++ ) {
189
			if ( eventActionTypes[ reactorActionTypes[ i ] ] ) {
190
				return reactorActionTypes[ i ];
191
			}
192
		}
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...
193
	},
194
195
	// Toggle the visibility of the form.
196
	edit: function () {
197
198
		if ( ! this.renderedFields ) {
199
			this.renderFields();
200
		}
201
202
		// Then display the form.
203
		this.$fields.slideDown( 'fast' );
204
		this.$el.addClass( 'editing' );
205
	},
206
207
	// Close the form.
208
	close: function () {
209
210
		this.$fields.slideUp( 'fast' );
211
		this.$el.removeClass( 'editing' );
212
		this.$( '.success' ).hide();
213
	},
214
215
	// Maybe lock the form open when an input is altered.
216
	maybeLockOpen: function ( event ) {
217
218
		var $target = $( event.target );
219
220
		var attrSlug = Fields.getAttrSlug( this.model, $target.attr( 'name' ) );
221
222
		if ( $target.val() !== this.model.get( attrSlug ) + '' ) {
223
			this.lockOpen();
224
		}
225
	},
226
227
	// Lock the form open when the form values have been changed.
228
	lockOpen: function () {
229
230
		if ( this.cancelling ) {
231
			return;
232
		}
233
234
		this.$el.addClass( 'changed' );
235
		this.$( '.save' ).prop( 'disabled', false );
236
		this.$( '.success' ).fadeOut();
237
	},
238
239
	// Cancel editing or adding a new reaction.
240
	cancel: function () {
241
242
		if ( this.$el.hasClass( 'new' ) ) {
243
244
			this.model.collection.trigger( 'cancel-add-new' );
245
			this.remove();
246
247
			wp.a11y.speak( l10n.discardedReaction );
0 ignored issues
show
Bug introduced by
The variable wp seems to be never declared. If this is a global, consider adding a /** global: wp */ 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...
248
249
			return;
250
		}
251
252
		this.$el.removeClass( 'changed' );
253
		this.$( '.save' ).prop( 'disabled', true );
254
255
		this.cancelling = true;
256
257
		this.renderFields();
258
259
		this.trigger( 'cancel' );
260
261
		wp.a11y.speak( l10n.discardedChanges );
262
263
		this.cancelling = false;
264
	},
265
266
	// Save changes to the reaction.
267
	save: function () {
268
269
		this.wait();
270
		this.$( '.save' ).prop( 'disabled', true );
271
272
		wp.a11y.speak( l10n.saving );
0 ignored issues
show
Bug introduced by
The variable wp seems to be never declared. If this is a global, consider adding a /** global: wp */ 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...
273
274
		var formData = Fields.getFormData( this.model, this.$fields );
275
276
		if ( formData.target ) {
277
			formData.target = formData.target.split( ',' );
278
		}
279
280
		this.model.save( formData, { wait: true, rawAtts: formData } );
281
	},
282
283
	// Display a spinner while changes are being saved.
284
	wait: function () {
285
286
		this.$( '.spinner-overlay' ).show();
287
		this.$( '.err' ).slideUp();
288
	},
289
290
	// Confirm that a reaction is intended to be deleted before deleting it.
291
	confirmDelete: function () {
292
293
		var $dialog = $( '<div><p></p></div>' ),
294
			view = this;
295
296
		this.$( '.messages div' ).slideUp();
297
298
		$dialog
299
			.attr( 'title', l10n.confirmTitle )
300
			.find( 'p' )
301
				.text( l10n.confirmDelete )
302
			.end()
303
			.dialog({
304
				dialogClass: 'wp-dialog wordpoints-delete-hook-reaction-dialog',
305
				resizable: false,
306
				draggable: false,
307
				height: 250,
308
				modal: true,
309
				buttons: [
310
					{
311
						text: l10n.cancelText,
312
						'class': 'button-secondary',
313
						click: function() {
314
							$( this ).dialog( 'destroy' );
315
						}
316
					},
317
					{
318
						text: l10n.deleteText,
319
						'class': 'button-primary',
320
						click: function() {
321
							$( this ).dialog( 'destroy' );
322
							view.destroy();
323
						}
324
					}
325
				]
326
			});
327
	},
328
329
	// Remove the item, destroy the model.
330
	destroy: function () {
331
332
		wp.a11y.speak( l10n.deleting );
0 ignored issues
show
Bug introduced by
The variable wp seems to be never declared. If this is a global, consider adding a /** global: wp */ 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...
333
334
		this.wait();
335
336
		this.model.destroy(
337
			{
338
				wait: true,
339
				success: function () {
340
					wp.a11y.speak( l10n.reactionDeleted );
0 ignored issues
show
Bug introduced by
The variable wp seems to be never declared. If this is a global, consider adding a /** global: wp */ 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...
341
				}
342
			}
343
		);
344
	},
345
346
	// Display errors when the model has invalid fields.
347
	showValidationErrors: function ( model, errors ) {
348
		this.showError( errors );
349
	},
350
351
	// Display an error when there is an Ajax failure.
352
	showAjaxErrors: function ( event, response ) {
353
354
		var errors;
355
356
		if ( ! _.isEmpty( response.errors ) ) {
0 ignored issues
show
Bug introduced by
The variable _ seems to be never declared. If this is a global, consider adding a /** global: _ */ 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...
357
			errors = response.errors;
358
		} else if ( response.message ) {
359
			errors = response.message;
360
		} else {
361
			errors = l10n.unexpectedError;
362
		}
363
364
		this.showError( errors );
365
	},
366
367
	showError: function ( errors ) {
368
369
		var generalErrors = [];
370
		var a11yErrors = [];
371
		var $errors = this.$( '.messages .err' );
372
373
		this.$( '.spinner-overlay' ).hide();
374
375
		// Sometimes we get a list of errors.
376
		if ( _.isArray( errors ) ) {
0 ignored issues
show
Bug introduced by
The variable _ seems to be never declared. If this is a global, consider adding a /** global: _ */ 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...
377
378
			// When that happens, we loop over them and try to display each of
379
			// them next to their associated field.
380
			_.each( errors, function ( error ) {
381
382
				var $field, escapedFieldName;
383
384
				// Sometimes some of the errors aren't for any particular field
385
				// though, so we collect them in an array an display them all
386
				// together a bit later.
387
				if ( ! error.field ) {
388
					generalErrors.push( error.message );
389
					return;
390
				}
391
392
				escapedFieldName = Fields.getFieldName( error.field )
393
						.replace( /[^a-z0-9-_\[\]\{}\\]/gi, '' )
394
						.replace( /\\/g, '\\\\' );
395
396
				// When a field is specified, we try to locate it.
397
				$field = this.$( '[name="' + escapedFieldName + '"]' );
398
399
				if ( 0 === $field.length ) {
400
401
					// However, there are times when the error is for a field set
402
					// and not a single field. In that case, we try to find the
403
					// fields in that set.
404
					$field = this.$( '[name^="' + escapedFieldName + '"]' );
405
406
					// If that fails, we just add this to the general errors.
407
					if ( 0 === $field.length ) {
408
						generalErrors.push( error.message );
409
						return;
410
					}
411
412
					$field = $field.first();
413
				}
414
415
				$field.before(
416
					$( '<div class="message err"></div>' ).text( error.message )
417
				);
418
419
				a11yErrors.push( error.message );
420
421
			}, this );
422
423
			$errors.html( '' );
424
425
			// There may be some general errors that we need to display to the user.
426
			// We also add an explanation that there are some fields that need to be
427
			// corrected, if there were some per-field errors, to make sure that they
428
			// see those errors as well (since they may not be in view).
429
			if ( generalErrors.length < errors.length ) {
430
				generalErrors.unshift( l10n.fieldsInvalid );
431
			}
432
433
			_.each( generalErrors, function ( error ) {
0 ignored issues
show
Bug introduced by
The variable _ seems to be never declared. If this is a global, consider adding a /** global: _ */ 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...
434
				$errors.append( $( '<p></p>' ).text( error ) );
435
			});
436
437
			// Notify unsighted users as well.
438
			a11yErrors.unshift( l10n.fieldsInvalid );
439
440
			wp.a11y.speak( a11yErrors.join( ' ' ) );
0 ignored issues
show
Bug introduced by
The variable wp seems to be never declared. If this is a global, consider adding a /** global: wp */ 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...
441
442
		} else {
443
444
			$errors.text( errors );
445
			wp.a11y.speak( errors );
446
		}
447
448
		$errors.fadeIn();
449
	},
450
451
	// Display a success message.
452
	showSuccess: function () {
453
454
		this.$( '.spinner-overlay' ).hide();
455
456
		this.$( '.success' )
457
			.text( l10n.changesSaved )
458
			.slideDown();
459
460
		wp.a11y.speak( l10n.reactionSaved );
0 ignored issues
show
Bug introduced by
The variable wp seems to be never declared. If this is a global, consider adding a /** global: wp */ 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...
461
462
		this.$target.find( 'select' ).prop( 'disabled', true );
463
464
		this.$el.removeClass( 'new changed' );
465
	}
466
});
467
468
module.exports = Reaction;
469