GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — develop ( 699b70...879176 )
by Chris
13:23
created

vendor/cmb2/js/cmb2.js   F

Complexity

Total Complexity 173
Complexity/F 2.44

Size

Lines of Code 959
Function Count 71

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
nc 0
dl 0
loc 959
rs 2.1818
c 0
b 0
f 0
wmc 173
mnd 4
bc 149
fnc 71
bpm 2.0985
cpm 2.4366
noi 4

35 Functions

Rating   Name   Duplication   Size   Complexity  
B cmb.initColorPickers 0 25 4
B cmb.doAjax 0 53 4
A cmb.updateNameAttr 0 18 2
A cmb.metabox 0 7 2
A cmb.log 0 5 4
B cmb._handleMedia 0 135 5
B cmb.removeGroupRow 0 29 3
B cmb.addGroupRow 0 36 3
B cmb.datePickerSetupOpts 0 40 2
A cmb.handleFileClick 0 12 3
A cmb.initDateTimePickers 0 10 2
A cmb.newRowHousekeeping 0 20 3
B cmb.cleanRow 0 39 4
A cmb.toggleHandle 0 4 1
B cmb.maybeOembed 0 28 1
A cmb.makeListSortable 0 6 2
A cmb.init 0 60 2
A cmb.emptyValue 0 3 1
A cmb.resizeoEmbeds 0 51 1
A cmb2.js ➔ $id 0 3 1
A cmb.toggleCheckBoxes 0 17 2
A cmb.handleMedia 0 10 3
B cmb.removeAjaxRow 0 29 5
B 0 958 1
A cmb.handleRemoveMedia 0 16 2
A cmb.addTimePickerClasses 0 16 1
B cmb.shiftRows 0 102 3
A cmb.triggerElement 0 5 1
A cmb.addAjaxRow 0 22 1
C cmb.elReplacements 0 45 13
A cmb.trigger 0 5 1
A cmb.spinner 0 8 2
A cmb.afterRowInsert 0 4 1
A cmb.initPickers 0 8 1
A cmb.resetTitlesAndIterator 0 18 2

How to fix   Complexity   

Complexity

Complex classes like vendor/cmb2/js/cmb2.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/**
2
 * Controls the behaviours of custom metabox fields.
3
 *
4
 * @author WebDevStudios
5
 * @see    https://github.com/WebDevStudios/CMB2
6
 */
7
8
 // TODO: fix this.
9
 // JQMIGRATE: jQuery.fn.attr('value') no longer gets properties
10
11
/**
12
 * Custom jQuery for Custom Metaboxes and Fields
13
 */
14
window.CMB2 = window.CMB2 || {};
15
(function(window, document, $, cmb, undefined){
16
	'use strict';
17
18
	// localization strings
19
	var l10n = window.cmb2_l10;
20
	var setTimeout = window.setTimeout;
21
	var $document;
22
	var $id = function( selector ) {
23
		return $( document.getElementById( selector ) );
24
	};
25
	var defaults = {
26
		idNumber        : false,
27
		repeatEls       : 'input:not([type="button"],[id^=filelist]),select,textarea,.cmb2-media-status',
28
		noEmpty         : 'input:not([type="button"]):not([type="radio"]):not([type="checkbox"]),textarea',
29
		repeatUpdate    : 'input:not([type="button"]),select,textarea,label',
30
		styleBreakPoint : 450,
31
		mediaHandlers   : {},
32
		defaults : {
33
			time_picker  : l10n.defaults.time_picker,
34
			date_picker  : l10n.defaults.date_picker,
35
			color_picker : l10n.defaults.color_picker || {},
36
		},
37
		media : {
38
			frames : {},
39
		},
40
	};
41
42
	cmb.metabox = function() {
43
		if ( cmb.$metabox ) {
44
			return cmb.$metabox;
45
		}
46
		cmb.$metabox = $('.cmb2-wrap > .cmb2-metabox');
47
		return cmb.$metabox;
48
	};
49
50
	cmb.init = function() {
51
		$document = $( document );
52
53
		// Setup the CMB2 object defaults.
54
		$.extend( cmb, defaults );
55
56
		cmb.trigger( 'cmb_pre_init' );
57
58
		var $metabox     = cmb.metabox();
59
		var $repeatGroup = $metabox.find('.cmb-repeatable-group');
60
61
		/**
62
		 * Initialize time/date/color pickers
63
		 */
64
		cmb.initPickers( $metabox.find('input[type="text"].cmb2-timepicker'), $metabox.find('input[type="text"].cmb2-datepicker'), $metabox.find('input[type="text"].cmb2-colorpicker') );
65
66
		// Insert toggle button into DOM wherever there is multicheck. credit: Genesis Framework
67
		$( '<p><span class="button cmb-multicheck-toggle">' + l10n.strings.check_toggle + '</span></p>' ).insertBefore( '.cmb2-checkbox-list:not(.no-select-all)' );
68
69
		// Make File List drag/drop sortable:
70
		cmb.makeListSortable();
71
72
		$metabox
73
			.on( 'change', '.cmb2_upload_file', function() {
74
				cmb.media.field = $( this ).attr( 'id' );
75
				$id( cmb.media.field + '_id' ).val('');
76
			})
77
			// Media/file management
78
			.on( 'click', '.cmb-multicheck-toggle', cmb.toggleCheckBoxes )
79
			.on( 'click', '.cmb2-upload-button', cmb.handleMedia )
80
			.on( 'click', '.cmb-attach-list li, .cmb2-media-status .img-status img, .cmb2-media-status .file-status > span', cmb.handleFileClick )
81
			.on( 'click', '.cmb2-remove-file-button', cmb.handleRemoveMedia )
82
			// Repeatable content
83
			.on( 'click', '.cmb-add-group-row', cmb.addGroupRow )
84
			.on( 'click', '.cmb-add-row-button', cmb.addAjaxRow )
85
			.on( 'click', '.cmb-remove-group-row', cmb.removeGroupRow )
86
			.on( 'click', '.cmb-remove-row-button', cmb.removeAjaxRow )
87
			// Ajax oEmbed display
88
			.on( 'keyup paste focusout', '.cmb2-oembed', cmb.maybeOembed )
89
			// Reset titles when removing a row
90
			.on( 'cmb2_remove_row', '.cmb-repeatable-group', cmb.resetTitlesAndIterator )
91
			.on( 'click', '.cmbhandle, .cmbhandle + .cmbhandle-title', cmb.toggleHandle );
92
93
		if ( $repeatGroup.length ) {
94
			$repeatGroup
95
				.filter('.sortable').each( function() {
96
					// Add sorting arrows
97
					$( this ).find( '.button.cmb-remove-group-row' ).before( '<a class="button cmb-shift-rows move-up alignleft" href="#"><span class="'+ l10n.up_arrow_class +'"></span></a> <a class="button cmb-shift-rows move-down alignleft" href="#"><span class="'+ l10n.down_arrow_class +'"></span></a>' );
98
				})
99
				.on( 'click', '.cmb-shift-rows', cmb.shiftRows )
100
				.on( 'cmb2_add_row', cmb.emptyValue );
101
		}
102
103
		// on pageload
104
		setTimeout( cmb.resizeoEmbeds, 500);
105
		// and on window resize
106
		$( window ).on( 'resize', cmb.resizeoEmbeds );
107
108
		cmb.trigger( 'cmb_init' );
109
	};
110
111
	cmb.resetTitlesAndIterator = function( evt ) {
112
		if ( ! evt.group ) {
113
			return;
114
		}
115
116
		// Loop repeatable group tables
117
		$( '.cmb-repeatable-group' ).each( function() {
118
			var $table = $( this );
119
			// Loop repeatable group table rows
120
			$table.find( '.cmb-repeatable-grouping' ).each( function( rowindex ) {
121
				var $row = $( this );
122
				// Reset rows iterator
123
				$row.data( 'iterator', rowindex );
124
				// Reset rows title
125
				$row.find( '.cmb-group-title h4' ).text( $table.find( '.cmb-add-group-row' ).data( 'grouptitle' ).replace( '{#}', ( rowindex + 1 ) ) );
126
			});
127
		});
128
	};
129
130
	cmb.toggleHandle = function( evt ) {
131
		evt.preventDefault();
132
		cmb.trigger( 'postbox-toggled', $( this ).parent('.postbox').toggleClass('closed') );
133
	};
134
135
	cmb.toggleCheckBoxes = function( evt ) {
136
		evt.preventDefault();
137
		var $this = $( this );
138
		var $multicheck = $this.closest( '.cmb-td' ).find( 'input[type=checkbox]:not([disabled])' );
139
140
		// If the button has already been clicked once...
141
		if ( $this.data( 'checked' ) ) {
142
			// clear the checkboxes and remove the flag
143
			$multicheck.prop( 'checked', false );
144
			$this.data( 'checked', false );
145
		}
146
		// Otherwise mark the checkboxes and add a flag
147
		else {
148
			$multicheck.prop( 'checked', true );
149
			$this.data( 'checked', true );
150
		}
151
	};
152
153
	cmb.handleMedia = function( evt ) {
154
		evt.preventDefault();
155
156
		var $el = $( this );
157
		cmb.attach_id = ! $el.hasClass( 'cmb2-upload-list' ) ? $el.closest( '.cmb-td' ).find( '.cmb2-upload-file-id' ).val() : false;
158
		// Clean up default 0 value
159
		cmb.attach_id = '0' !== cmb.attach_id ? cmb.attach_id : false;
160
161
		cmb._handleMedia( $el.prev('input.cmb2-upload-file').attr('id'), $el.hasClass( 'cmb2-upload-list' ) );
162
	};
163
164
	cmb.handleFileClick = function( evt ) {
165
		evt.preventDefault();
166
167
		var $el    = $( this );
168
		var $td    = $el.closest( '.cmb-td' );
169
		var isList = $td.find( '.cmb2-upload-button' ).hasClass( 'cmb2-upload-list' );
170
		cmb.attach_id = isList ? $el.find( 'input[type="hidden"]' ).data( 'id' ) : $td.find( '.cmb2-upload-file-id' ).val();
171
172
		if ( cmb.attach_id ) {
173
			cmb._handleMedia( $td.find( 'input.cmb2-upload-file' ).attr('id'), isList, cmb.attach_id );
174
		}
175
	};
176
177
	cmb._handleMedia = function( formfield, isList ) {
178
		if ( ! wp ) {
0 ignored issues
show
Best Practice introduced by
If you intend to check if the variable wp is declared in the current environment, consider using typeof wp === "undefined" instead. This is safe if the variable is not actually declared.
Loading history...
179
			return;
180
		}
181
182
		var media         = cmb.media;
183
		media.field       = formfield;
184
		media.$field      = $id( media.field );
185
		media.fieldData   = media.$field.data();
186
		media.previewSize = media.fieldData.previewsize;
187
		media.fieldName   = media.$field.attr('name');
188
		media.isList      = isList;
189
190
		var uploadStatus, attachment;
191
192
		// If this field's media frame already exists, reopen it.
193
		if ( media.field in media.frames ) {
194
			media.frames[ media.field ].open();
195
			return;
196
		}
197
198
		// Create the media frame.
199
		media.frames[ media.field ] = wp.media( {
200
			title: cmb.metabox().find('label[for="' + media.field + '"]').text(),
201
			library : media.fieldData.queryargs || {},
202
			button: {
203
				text: l10n.strings[ isList ? 'upload_files' : 'upload_file' ]
204
			},
205
			multiple: isList ? 'add' : false
206
		} );
207
208
		cmb.trigger( 'cmb_media_modal_init', media );
209
210
		cmb.mediaHandlers.list = function( selection, returnIt ) {
211
			// Get all of our selected files
212
			attachment = selection.toJSON();
213
214
			media.$field.val(attachment.url);
215
			$id( media.field +'_id' ).val(attachment.id);
216
217
			// Setup our fileGroup array
218
			var fileGroup = [];
219
220
			// Loop through each attachment
221
			$( attachment ).each( function() {
222
				if ( this.type && this.type === 'image' ) {
223
					var width = media.previewSize[0] ? media.previewSize[0] : 50;
224
					var height = media.previewSize[1] ? media.previewSize[1] : 50;
225
226
					// image preview
227
					uploadStatus = '<li class="img-status">'+
228
						'<img width="'+ width +'" height="'+ height +'" src="' + this.url + '" class="attachment-'+ width +'px'+ height +'px" alt="'+ this.filename +'">'+
229
						'<p><a href="#" class="cmb2-remove-file-button" rel="'+ media.field +'['+ this.id +']">'+ l10n.strings.remove_image +'</a></p>'+
230
						'<input type="hidden" id="filelist-'+ this.id +'" data-id="'+ this.id +'" name="'+ media.fieldName +'['+ this.id +']" value="' + this.url + '">'+
231
					'</li>';
232
233
				} else {
234
					// Standard generic output if it's not an image.
235
					uploadStatus = '<li class="file-status"><span>'+ l10n.strings.file +' <strong>'+ this.filename +'</strong></span>&nbsp;&nbsp; (<a href="' + this.url + '" target="_blank" rel="external">'+ l10n.strings.download +'</a> / <a href="#" class="cmb2-remove-file-button" rel="'+ media.field +'['+ this.id +']">'+ l10n.strings.remove_file +'</a>)'+
236
						'<input type="hidden" id="filelist-'+ this.id +'" data-id="'+ this.id +'" name="'+ media.fieldName +'['+ this.id +']" value="' + this.url + '">'+
237
					'</li>';
238
239
				}
240
241
				// Add our file to our fileGroup array
242
				fileGroup.push( uploadStatus );
243
			});
244
245
			if ( ! returnIt ) {
246
				// Append each item from our fileGroup array to .cmb2-media-status
247
				$( fileGroup ).each( function() {
248
					media.$field.siblings('.cmb2-media-status').slideDown().append(this);
249
				});
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...
250
			} else {
251
				return fileGroup;
252
			}
253
254
		};
255
256
		cmb.mediaHandlers.single = function( selection ) {
257
			// Only get one file from the uploader
258
			attachment = selection.first().toJSON();
259
260
			media.$field.val(attachment.url);
261
			$id( media.field +'_id' ).val(attachment.id);
262
263
			if ( attachment.type && attachment.type === 'image' ) {
264
				// image preview
265
				var width = media.previewSize[0] ? media.previewSize[0] : 350;
266
				uploadStatus = '<div class="img-status"><img width="'+ width +'px" style="max-width: '+ width +'px; width: 100%; height: auto;" src="' + attachment.url + '" alt="'+ attachment.filename +'" title="'+ attachment.filename +'" /><p><a href="#" class="cmb2-remove-file-button" rel="' + media.field + '">'+ l10n.strings.remove_image +'</a></p></div>';
267
			} else {
268
				// Standard generic output if it's not an image.
269
				uploadStatus = '<div class="file-status"><span>'+ l10n.strings.file +' <strong>'+ attachment.filename +'</strong></span>&nbsp;&nbsp; (<a href="'+ attachment.url +'" target="_blank" rel="external">'+ l10n.strings.download +'</a> / <a href="#" class="cmb2-remove-file-button" rel="'+ media.field +'">'+ l10n.strings.remove_file +'</a>)</div>';
270
			}
271
272
			// add/display our output
273
			media.$field.siblings('.cmb2-media-status').slideDown().html(uploadStatus);
274
		};
275
276
		cmb.mediaHandlers.selectFile = function() {
277
			var selection = media.frames[ media.field ].state().get('selection');
278
			var type = isList ? 'list' : 'single';
279
280
			if ( cmb.attach_id && isList ) {
281
				$( '[data-id="'+ cmb.attach_id +'"]' ).parents( 'li' ).replaceWith( cmb.mediaHandlers.list( selection, true ) );
282
			} else {
283
				cmb.mediaHandlers[type]( selection );
284
			}
285
286
			cmb.trigger( 'cmb_media_modal_select', selection, media );
287
		};
288
289
		cmb.mediaHandlers.openModal = function() {
290
			var selection = media.frames[ media.field ].state().get('selection');
291
			var attach;
292
293
			if ( ! cmb.attach_id ) {
294
				selection.reset();
295
			} else {
296
				attach = wp.media.attachment( cmb.attach_id );
297
				attach.fetch();
298
				selection.set( attach ? [ attach ] : [] );
299
			}
300
301
			cmb.trigger( 'cmb_media_modal_open', selection, media );
302
		};
303
304
		// When a file is selected, run a callback.
305
		media.frames[ media.field ]
306
			.on( 'select', cmb.mediaHandlers.selectFile )
307
			.on( 'open', cmb.mediaHandlers.openModal );
308
309
		// Finally, open the modal
310
		media.frames[ media.field ].open();
311
	};
312
313
	cmb.handleRemoveMedia = function( evt ) {
314
		evt.preventDefault();
315
		var $this = $( this );
316
		if ( $this.is( '.cmb-attach-list .cmb2-remove-file-button' ) ){
317
			$this.parents('li').remove();
318
			return false;
319
		}
320
321
		cmb.media.field = $this.attr('rel');
322
323
		cmb.metabox().find( 'input#' + cmb.media.field ).val('');
324
		cmb.metabox().find( 'input#' + cmb.media.field + '_id' ).val('');
325
		$this.parents('.cmb2-media-status').html('');
326
327
		return false;
328
	};
329
330
	cmb.cleanRow = function( $row, prevNum, group ) {
331
		var $elements = $row.find( cmb.repeatUpdate );
332
		if ( group ) {
333
334
			var $other  = $row.find( '[id]' ).not( cmb.repeatUpdate );
335
336
			// Remove extra ajaxed rows
337
			$row.find('.cmb-repeat-table .cmb-repeat-row:not(:first-child)').remove();
338
339
			// Update all elements w/ an ID
340
			if ( $other.length ) {
341
				$other.each( function() {
342
					var $_this = $( this );
343
					var oldID = $_this.attr( 'id' );
344
					var newID = oldID.replace( '_'+ prevNum, '_'+ cmb.idNumber );
345
					var $buttons = $row.find('[data-selector="'+ oldID +'"]');
346
					$_this.attr( 'id', newID );
347
348
					// Replace data-selector vars
349
					if ( $buttons.length ) {
350
						$buttons.attr( 'data-selector', newID ).data( 'selector', newID );
351
					}
352
				});
353
			}
354
		}
355
356
		$elements.filter(':checked').prop( 'checked', false );
357
		$elements.filter(':selected').prop( 'selected', false );
358
359
		if ( $row.find('h3.cmb-group-title').length ) {
360
			$row.find( 'h3.cmb-group-title' ).text( $row.data( 'title' ).replace( '{#}', ( cmb.idNumber + 1 ) ) );
361
		}
362
363
		$elements.each( function() {
364
			cmb.elReplacements( $( this ), prevNum );
365
		} );
366
367
		return cmb;
368
	};
369
370
	cmb.elReplacements = function( $newInput, prevNum ) {
371
		var oldFor    = $newInput.attr( 'for' );
372
		var oldVal    = $newInput.val();
373
		var type      = $newInput.prop( 'type' );
374
		var checkable = 'radio' === type || 'checkbox' === type ? oldVal : false;
375
		// var $next  = $newInput.next();
376
		var attrs     = {};
0 ignored issues
show
Unused Code introduced by
The assignment to variable attrs seems to be never used. Consider removing it.
Loading history...
377
		var newID, oldID;
378
		if ( oldFor ) {
379
			attrs = { 'for' : oldFor.replace( '_'+ prevNum, '_'+ cmb.idNumber ) };
380
		} else {
381
			var oldName = $newInput.attr( 'name' );
382
			// Replace 'name' attribute key
383
			var newName = oldName ? oldName.replace( '['+ prevNum +']', '['+ cmb.idNumber +']' ) : '';
384
			oldID       = $newInput.attr( 'id' );
385
			newID       = oldID ? oldID.replace( '_'+ prevNum, '_'+ cmb.idNumber ) : '';
386
			attrs       = {
387
				id: newID,
388
				name: newName,
389
				// value: '',
390
				'data-iterator': cmb.idNumber,
391
			};
392
393
		}
394
395
		// Clear out old values
396
		if ( undefined !== typeof( oldVal ) && oldVal || checkable ) {
397
			attrs.value = checkable ? checkable : '';
398
		}
399
400
		// Clear out textarea values
401
		if ( 'TEXTAREA' === $newInput.prop('tagName') ) {
402
			$newInput.html( '' );
403
		}
404
405
		if ( checkable ) {
406
			$newInput.removeAttr( 'checked' );
407
		}
408
409
		$newInput
410
			.removeClass( 'hasDatepicker' )
411
			.attr( attrs ).val( checkable ? checkable : '' );
412
413
		return $newInput;
414
	};
415
416
	cmb.newRowHousekeeping = function( $row ) {
417
418
		var $colorPicker = $row.find( '.wp-picker-container' );
419
		var $list        = $row.find( '.cmb2-media-status' );
420
421
		if ( $colorPicker.length ) {
422
			// Need to clean-up colorpicker before appending
423
			$colorPicker.each( function() {
424
				var $td = $( this ).parent();
425
				$td.html( $td.find( 'input[type="text"].cmb2-colorpicker' ).attr('style', '') );
426
			});
427
		}
428
429
		// Need to clean-up colorpicker before appending
430
		if ( $list.length ) {
431
			$list.empty();
432
		}
433
434
		return cmb;
435
	};
436
437
	cmb.afterRowInsert = function( $row ) {
438
		// Init pickers from new row
439
		cmb.initPickers( $row.find('input[type="text"].cmb2-timepicker'), $row.find('input[type="text"].cmb2-datepicker'), $row.find('input[type="text"].cmb2-colorpicker') );
440
	};
441
442
	cmb.updateNameAttr = function () {
443
444
		var $this = $( this );
445
		var name  = $this.attr( 'name' ); // get current name
446
447
		// If name is defined
448
		if ( typeof name !== 'undefined' ) {
449
			var prevNum = parseInt( $this.parents( '.cmb-repeatable-grouping' ).data( 'iterator' ), 10 );
450
			var newNum  = prevNum - 1; // Subtract 1 to get new iterator number
451
452
			// Update field name attributes so data is not orphaned when a row is removed and post is saved
453
			var $newName = name.replace( '[' + prevNum + ']', '[' + newNum + ']' );
454
455
			// New name with replaced iterator
456
			$this.attr( 'name', $newName );
457
		}
458
459
	};
460
461
	cmb.emptyValue = function( evt, row ) {
462
		$( cmb.noEmpty, row ).val( '' );
463
	};
464
465
	cmb.addGroupRow = function( evt ) {
466
		evt.preventDefault();
467
468
		var $this = $( this );
469
470
		// before anything significant happens
471
		cmb.triggerElement( $this, 'cmb2_add_group_row_start', $this );
472
473
		var $table   = $id( $this.data('selector') );
474
		var $oldRow  = $table.find('.cmb-repeatable-grouping').last();
475
		var prevNum  = parseInt( $oldRow.data('iterator'), 10 );
476
		cmb.idNumber = parseInt( prevNum, 10 ) + 1;
477
		var $row     = $oldRow.clone();
478
479
		// Make sure the next number doesn't exist.
480
		while ( $table.find( '.cmb-repeatable-grouping[data-iterator="'+ cmb.idNumber +'"]' ).length > 0 ) {
481
			cmb.idNumber++;
482
		}
483
484
		cmb.newRowHousekeeping( $row.data( 'title', $this.data( 'grouptitle' ) ) ).cleanRow( $row, prevNum, true );
485
		$row.find( '.cmb-add-row-button' ).prop( 'disabled', false );
486
487
		var $newRow = $( '<div class="postbox cmb-row cmb-repeatable-grouping" data-iterator="'+ cmb.idNumber +'">'+ $row.html() +'</div>' );
488
		$oldRow.after( $newRow );
489
490
		cmb.afterRowInsert( $newRow );
491
492
		if ( $table.find('.cmb-repeatable-grouping').length <= 1 ) {
493
			$table.find('.cmb-remove-group-row').prop( 'disabled', true );
494
		} else {
495
			$table.find('.cmb-remove-group-row').prop( 'disabled', false );
496
		}
497
498
		cmb.triggerElement( $table, { type: 'cmb2_add_row', group: true }, $newRow );
499
500
	};
501
502
	cmb.addAjaxRow = function( evt ) {
503
		evt.preventDefault();
504
505
		var $this         = $( this );
506
		var $table        = $id( $this.data('selector') );
507
		var $emptyrow     = $table.find('.empty-row');
508
		var prevNum       = parseInt( $emptyrow.find('[data-iterator]').data('iterator'), 10 );
509
		cmb.idNumber      = parseInt( prevNum, 10 ) + 1;
510
		var $row          = $emptyrow.clone();
511
512
		cmb.newRowHousekeeping( $row ).cleanRow( $row, prevNum );
513
514
		$emptyrow.removeClass('empty-row hidden').addClass('cmb-repeat-row');
515
		$emptyrow.after( $row );
516
517
		cmb.afterRowInsert( $row );
518
519
		cmb.triggerElement( $table, { type: 'cmb2_add_row', group: false }, $row );
520
521
		$table.find( '.cmb-remove-row-button' ).removeClass( 'button-disabled' );
522
523
	};
524
525
	cmb.removeGroupRow = function( evt ) {
526
		evt.preventDefault();
527
528
		var $this   = $( this );
529
		var $table  = $id( $this.data('selector') );
530
		var $parent = $this.parents('.cmb-repeatable-grouping');
531
		var number  = $table.find('.cmb-repeatable-grouping').length;
532
533
		// Needs to always be at least one group.
534
		if ( number < 2 ) {
535
			return;
536
		}
537
538
		cmb.triggerElement( $table, 'cmb2_remove_group_row_start', $this );
539
540
		// when a group is removed loop through all next groups and update fields names
541
		$parent.nextAll( '.cmb-repeatable-grouping' ).find( cmb.repeatEls ).each( cmb.updateNameAttr );
542
543
		$parent.remove();
544
545
		if ( number <= 2 ) {
546
			$table.find('.cmb-remove-group-row').prop( 'disabled', true );
547
		} else {
548
			$table.find('.cmb-remove-group-row').prop( 'disabled', false );
549
		}
550
551
		cmb.triggerElement( $table, { type: 'cmb2_remove_row', group: true } );
552
553
	};
554
555
	cmb.removeAjaxRow = function( evt ) {
556
		evt.preventDefault();
557
558
		var $this = $( this );
559
560
		// Check if disabled
561
		if ( $this.hasClass( 'button-disabled' ) ) {
562
			return;
563
		}
564
565
		var $parent = $this.parents('.cmb-row');
566
		var $table  = $this.parents('.cmb-repeat-table');
567
		var number  = $table.find('.cmb-row').length;
568
569
		if ( number > 2 ) {
570
			if ( $parent.hasClass('empty-row') ) {
571
				$parent.prev().addClass( 'empty-row' ).removeClass('cmb-repeat-row');
572
			}
573
			$this.parents('.cmb-repeat-table .cmb-row').remove();
574
			if ( number === 3 ) {
575
				$table.find( '.cmb-remove-row-button' ).addClass( 'button-disabled' );
576
			}
577
578
			cmb.triggerElement( $table, { type: 'cmb2_remove_row', group: false } );
579
580
		} else {
581
			$this.addClass( 'button-disabled' );
582
		}
583
	};
584
585
	cmb.shiftRows = function( evt ) {
586
587
		evt.preventDefault();
588
589
		var $this = $( this );
590
		var $from = $this.parents( '.cmb-repeatable-grouping' );
591
		var $goto = $this.hasClass( 'move-up' ) ? $from.prev( '.cmb-repeatable-grouping' ) : $from.next( '.cmb-repeatable-grouping' );
592
593
		// Before shift occurs.
594
		cmb.triggerElement( $this, 'cmb2_shift_rows_enter', $this, $from, $goto );
595
596
		if ( ! $goto.length ) {
597
			return;
598
		}
599
600
		// About to shift
601
		cmb.triggerElement( $this, 'cmb2_shift_rows_start', $this, $from, $goto );
602
603
		var inputVals = [];
604
		// Loop this item's fields
605
		$from.find( cmb.repeatEls ).each( function() {
606
			var $element = $( this );
607
			var elType = $element.attr( 'type' );
608
			var val;
609
610
			if ( $element.hasClass('cmb2-media-status') ) {
611
				// special case for image previews
612
				val = $element.html();
613
			} else if ( 'checkbox' === elType || 'radio' === elType ) {
614
				val = $element.is(':checked');
615
			} else if ( 'select' === $element.prop('tagName') ) {
616
				val = $element.is(':selected');
617
			} else {
618
				val = $element.val();
619
			}
620
621
			// Get all the current values per element
622
			inputVals.push( { val: val, $: $element } );
623
		});
624
		// And swap them all
625
		$goto.find( cmb.repeatEls ).each( function( index ) {
626
			var $element = $( this );
627
			var elType = $element.attr( 'type' );
628
			var val;
629
630
			if ( $element.hasClass('cmb2-media-status') ) {
631
				var toRowId = $element.closest('.cmb-repeatable-grouping').attr('data-iterator');
632
				var fromRowId = inputVals[ index ].$.closest('.cmb-repeatable-grouping').attr('data-iterator');
633
634
				// special case for image previews
635
				val = $element.html();
636
				$element.html( inputVals[ index ].val );
637
				inputVals[ index ].$.html( val );
638
639
				inputVals[ index ].$.find( 'input' ).each(function() {
640
					var name = $( this ).attr( 'name' );
641
					name = name.replace( '['+toRowId+']', '['+fromRowId+']' );
642
					$( this ).attr( 'name', name );
643
				});
644
				$element.find('input').each(function() {
645
					var name = $( this ).attr('name');
646
					name = name.replace('['+fromRowId+']', '['+toRowId+']');
647
					$( this ).attr('name', name);
648
				});
649
650
			}
651
			// handle checkbox swapping
652
			else if ( 'checkbox' === elType  ) {
653
				inputVals[ index ].$.prop( 'checked', $element.is(':checked') );
654
				$element.prop( 'checked', inputVals[ index ].val );
655
			}
656
			// handle radio swapping
657
			else if ( 'radio' === elType  ) {
658
				if ( $element.is( ':checked' ) ) {
659
					inputVals[ index ].$.attr( 'data-checked', 'true' );
660
				}
661
				if ( inputVals[ index ].$.is( ':checked' ) ) {
662
					$element.attr( 'data-checked', 'true' );
663
				}
664
			}
665
			// handle select swapping
666
			else if ( 'select' === $element.prop('tagName') ) {
667
				inputVals[ index ].$.prop( 'selected', $element.is(':selected') );
668
				$element.prop( 'selected', inputVals[ index ].val );
669
			}
670
			// handle normal input swapping
671
			else {
672
				inputVals[ index ].$.val( $element.val() );
673
				$element.val( inputVals[ index ].val );
674
			}
675
		});
676
677
		$from.find( 'input[data-checked=true]' ).prop( 'checked', true ).removeAttr( 'data-checked' );
678
		$goto.find( 'input[data-checked=true]' ).prop( 'checked', true ).removeAttr( 'data-checked' );
679
680
		// trigger color picker change event
681
		$from.find( 'input[type="text"].cmb2-colorpicker' ).trigger( 'change' );
682
		$goto.find( 'input[type="text"].cmb2-colorpicker' ).trigger( 'change' );
683
684
		// shift done
685
		cmb.triggerElement( $this, 'cmb2_shift_rows_complete', $this, $from, $goto );
686
	};
687
688
	cmb.initPickers = function( $timePickers, $datePickers, $colorPickers ) {
689
		// Initialize jQuery UI timepickers
690
		cmb.initDateTimePickers( $timePickers, 'timepicker', 'time_picker' );
691
		// Initialize jQuery UI datepickers
692
		cmb.initDateTimePickers( $datePickers, 'datepicker', 'date_picker' );
693
		// Initialize color picker
694
		cmb.initColorPickers( $colorPickers );
695
	};
696
697
	cmb.initDateTimePickers = function( $selector, method, defaultKey ) {
698
		if ( $selector.length ) {
699
			$selector[ method ]( 'destroy' ).each( function() {
700
				var $this     = $( this );
701
				var fieldOpts = $this.data( method ) || {};
702
				var options   = $.extend( {}, cmb.defaults[ defaultKey ], fieldOpts );
703
				$this[ method ]( cmb.datePickerSetupOpts( fieldOpts, options, method ) );
704
			} );
705
		}
706
	};
707
708
	cmb.datePickerSetupOpts = function( fieldOpts, options, method ) {
709
		var existing = $.extend( {}, options );
710
711
		options.beforeShow = function( input, inst ) {
712
			if ( 'timepicker' === method ) {
713
				cmb.addTimePickerClasses( inst.dpDiv );
714
			}
715
716
			// Wrap datepicker w/ class to narrow the scope of jQuery UI CSS and prevent conflicts
717
			$id( 'ui-datepicker-div' ).addClass( 'cmb2-element' );
718
719
			// Let's be sure to call beforeShow if it was added
720
			if ( 'function' === typeof existing.beforeShow ) {
721
				existing.beforeShow( input, inst );
722
			}
723
		};
724
725
		if ( 'timepicker' === method ) {
726
			options.onChangeMonthYear = function( year, month, inst, picker ) {
727
				cmb.addTimePickerClasses( inst.dpDiv );
728
729
				// Let's be sure to call onChangeMonthYear if it was added
730
				if ( 'function' === typeof existing.onChangeMonthYear ) {
731
					existing.onChangeMonthYear( year, month, inst, picker );
732
				}
733
			};
734
		}
735
736
		options.onClose = function( dateText, inst ) {
737
			// Remove the class when we're done with it (and hide to remove FOUC).
738
			$id( 'ui-datepicker-div' ).removeClass( 'cmb2-element' ).hide();
739
740
			// Let's be sure to call onClose if it was added
741
			if ( 'function' === typeof existing.onClose ) {
742
				existing.onClose( dateText, inst );
743
			}
744
		};
745
746
		return options;
747
	};
748
749
	// Adds classes to timepicker buttons.
750
	cmb.addTimePickerClasses = function( $picker ) {
751
		var func = cmb.addTimePickerClasses;
752
		func.count = func.count || 0;
753
754
		// Wait a bit to let the timepicker render, since these are pre-render events.
755
		setTimeout( function() {
756
			if ( $picker.find( '.ui-priority-secondary' ).length ) {
757
				$picker.find( '.ui-priority-secondary' ).addClass( 'button-secondary' );
758
				$picker.find( '.ui-priority-primary' ).addClass( 'button-primary' );
759
				func.count = 0;
760
			} else if ( func.count < 5 ) {
761
				func.count++;
762
				func( $picker );
763
			}
764
		}, 10 );
765
	};
766
767
	cmb.initColorPickers = function( $selector ) {
768
		if ( ! $selector.length ) {
769
			return;
770
		}
771
		if ( typeof jQuery.wp === 'object' && typeof jQuery.wp.wpColorPicker === 'function' ) {
772
773
			$selector.each( function() {
774
				var $this = $( this );
775
				var fieldOpts = $this.data( 'colorpicker' ) || {};
776
				$this.wpColorPicker( $.extend( {}, cmb.defaults.color_picker, fieldOpts ) );
777
			} );
778
779
		} else {
780
			$selector.each( function( i ) {
781
				$( this ).after( '<div id="picker-' + i + '" style="z-index: 1000; background: #EEE; border: 1px solid #CCC; position: absolute; display: block;"></div>' );
782
				$id( 'picker-' + i ).hide().farbtastic( $( this ) );
783
			} )
784
			.focus( function() {
785
				$( this ).next().show();
786
			} )
787
			.blur( function() {
788
				$( this ).next().hide();
789
			} );
790
		}
791
	};
792
793
	cmb.makeListSortable = function() {
794
		var $filelist = cmb.metabox().find( '.cmb2-media-status.cmb-attach-list' );
795
		if ( $filelist.length ) {
796
			$filelist.sortable({ cursor: 'move' }).disableSelection();
797
		}
798
	};
799
800
	cmb.maybeOembed = function( evt ) {
801
		var $this = $( this );
802
803
		var m = {
804
			focusout : function() {
805
				setTimeout( function() {
806
					// if it's been 2 seconds, hide our spinner
807
					cmb.spinner( '.postbox .cmb2-metabox', true );
808
				}, 2000);
809
			},
810
			keyup : function() {
811
				var betw = function( min, max ) {
812
					return ( evt.which <= max && evt.which >= min );
813
				};
814
				// Only Ajax on normal keystrokes
815
				if ( betw( 48, 90 ) || betw( 96, 111 ) || betw( 8, 9 ) || evt.which === 187 || evt.which === 190 ) {
816
					// fire our ajax function
817
					cmb.doAjax( $this, evt );
818
				}
819
			},
820
			paste : function() {
821
				// paste event is fired before the value is filled, so wait a bit
822
				setTimeout( function() { cmb.doAjax( $this ); }, 100);
823
			}
824
		};
825
826
		m[ evt.type ]();
827
	};
828
829
	/**
830
	 * Resize oEmbed videos to fit in their respective metaboxes
831
	 */
832
	cmb.resizeoEmbeds = function() {
833
		cmb.metabox().each( function() {
834
			var $this      = $( this );
835
			var $tableWrap = $this.parents('.inside');
836
			var isSide     = $this.parents('.inner-sidebar').length || $this.parents( '#side-sortables' ).length;
837
			var isSmall    = isSide;
838
			var isSmallest = false;
839
			if ( ! $tableWrap.length )  {
840
				return true; // continue
841
			}
842
843
			// Calculate new width
844
			var tableW = $tableWrap.width();
845
846
			if ( cmb.styleBreakPoint > tableW ) {
847
				isSmall    = true;
848
				isSmallest = ( cmb.styleBreakPoint - 62 ) > tableW;
849
			}
850
851
			tableW = isSmall ? tableW : Math.round(($tableWrap.width() * 0.82)*0.97);
852
			var newWidth = tableW - 30;
853
			if ( isSmall && ! isSide && ! isSmallest ) {
854
				newWidth = newWidth - 75;
855
			}
856
			if ( newWidth > 639 ) {
857
				return true; // continue
858
			}
859
860
			var $embeds   = $this.find('.cmb-type-oembed .embed-status');
861
			var $children = $embeds.children().not('.cmb2-remove-wrapper');
862
			if ( ! $children.length ) {
863
				return true; // continue
864
			}
865
866
			$children.each( function() {
867
				var $this     = $( this );
868
				var iwidth    = $this.width();
869
				var iheight   = $this.height();
870
				var _newWidth = newWidth;
871
				if ( $this.parents( '.cmb-repeat-row' ).length && ! isSmall ) {
872
					// Make room for our repeatable "remove" button column
873
					_newWidth = newWidth - 91;
874
					_newWidth = 785 > tableW ? _newWidth - 15 : _newWidth;
875
				}
876
				// Calc new height
877
				var newHeight = Math.round((_newWidth * iheight)/iwidth);
878
				$this.width(_newWidth).height(newHeight);
879
			});
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...
880
881
		});
882
	};
883
884
	/**
885
	 * Safely log things if query var is set
886
	 * @since  1.0.0
887
	 */
888
	cmb.log = function() {
889
		if ( l10n.script_debug && console && typeof console.log === 'function' ) {
890
			console.log.apply(console, arguments);
891
		}
892
	};
893
894
	cmb.spinner = function( $context, hide ) {
895
		if ( hide ) {
896
			$('.cmb-spinner', $context ).hide();
897
		}
898
		else {
899
			$('.cmb-spinner', $context ).show();
900
		}
901
	};
902
903
	// function for running our ajax
904
	cmb.doAjax = function( $obj ) {
905
		// get typed value
906
		var oembed_url = $obj.val();
907
		// only proceed if the field contains more than 6 characters
908
		if ( oembed_url.length < 6 ) {
909
			return;
910
		}
911
912
		// get field id
913
		var field_id         = $obj.attr('id');
914
		var $context         = $obj.closest( '.cmb-td' );
915
		var $embed_container = $context.find( '.embed-status' );
916
		var $embed_wrap      = $context.find( '.embed_wrap' );
917
		var $child_el        = $embed_container.find( ':first-child' );
918
		var oembed_width     = $embed_container.length && $child_el.length ? $child_el.width() : $obj.width();
919
920
		cmb.log( 'oembed_url', oembed_url, field_id );
921
922
		// show our spinner
923
		cmb.spinner( $context );
924
		// clear out previous results
925
		$embed_wrap.html('');
926
		// and run our ajax function
927
		setTimeout( function() {
928
			// if they haven't typed in 500 ms
929
			if ( $( '.cmb2-oembed:focus' ).val() !== oembed_url ) {
930
				return;
931
			}
932
			$.ajax({
933
				type : 'post',
934
				dataType : 'json',
935
				url : l10n.ajaxurl,
936
				data : {
937
					'action'          : 'cmb2_oembed_handler',
938
					'oembed_url'      : oembed_url,
939
					'oembed_width'    : oembed_width > 300 ? oembed_width : 300,
940
					'field_id'        : field_id,
941
					'object_id'       : $obj.data( 'objectid' ),
942
					'object_type'     : $obj.data( 'objecttype' ),
943
					'cmb2_ajax_nonce' : l10n.ajax_nonce
944
				},
945
				success: function(response) {
946
					cmb.log( response );
947
					// hide our spinner
948
					cmb.spinner( $context, true );
949
					// and populate our results from ajax response
950
					$embed_wrap.html( response.data );
951
				}
952
			});
953
954
		}, 500);
955
956
	};
957
958
	cmb.trigger = function( evtName ) {
959
		var args = Array.prototype.slice.call( arguments, 1 );
960
		args.push( cmb );
961
		$document.trigger( evtName, args );
962
	};
963
964
	cmb.triggerElement = function( $el, evtName ) {
965
		var args = Array.prototype.slice.call( arguments, 2 );
966
		args.push( cmb );
967
		$el.trigger( evtName, args );
968
	};
969
970
	$( cmb.init );
971
972
})(window, document, jQuery, window.CMB2);
973