PluginProbe
GamiPress – Gamification plugin to reward points, badges & ranks in WordPress, now with AI / 8.0.3
GamiPress – Gamification plugin to reward points, badges & ranks in WordPress, now with AI v8.0.3
8.0.4 8.0.3 8.0.1 8.0.2 8.0.0 7.9.9.7 7.9.9.6 7.9.9.5 7.9.9.4 7.9.9.3 7.9.9.2 7.9.9.1 7.9.9 7.9.8 7.9.7 7.9.6 7.9.5 7.9.4 7.9.3 7.9.2 7.9.1 7.9.0 7.8.9 7.8.8 7.8.7 All 46 releases
gamipress / libraries / cmb2 / js / cmb2.js

cmb2.js in GamiPress – Gamification plugin to reward points, badges & ranks in WordPress, now with AI 8.0.3, at libraries/cmb2/js/cmb2.js

1,325 lines 40.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Controls the behaviours of custom metabox fields.
3 *
4 * @author CMB2 team
5 * @see https://github.com/CMB2/CMB2
6 */
7
8 /**
9 * Custom jQuery for Custom Metaboxes and Fields
10 */
11 window.CMB2 = window.CMB2 || {};
12 (function(window, document, $, cmb, undefined){
13 'use strict';
14
15 // localization strings
16 var l10n = window.cmb2_l10;
17 var setTimeout = window.setTimeout;
18 var $document;
19 var $id = function( selector ) {
20 return $( document.getElementById( selector ) );
21 };
22 var getRowId = function( id, newIterator ) {
23 id = id.split('-');
24 id.splice(id.length - 1, 1);
25 id.push( newIterator );
26 return id.join('-');
27 };
28 cmb.$id = $id;
29 var defaults = {
30 idNumber : false,
31 repeatEls : 'input:not([type="button"]),select,textarea,.cmb2-media-status',
32 noEmpty : 'input:not([type="button"]):not([type="radio"]):not([type="checkbox"]),textarea',
33 repeatUpdate : 'input:not([type="button"]),select,textarea,label',
34 styleBreakPoint : 450,
35 mediaHandlers : {},
36 defaults : {
37 time_picker : l10n.defaults.time_picker,
38 date_picker : l10n.defaults.date_picker,
39 color_picker : l10n.defaults.color_picker || {},
40 code_editor : l10n.defaults.code_editor,
41 },
42 media : {
43 frames : {},
44 },
45 };
46
47 cmb.init = function() {
48 $document = $( document );
49
50 // Setup the CMB2 object defaults.
51 $.extend( cmb, defaults );
52
53 cmb.trigger( 'cmb_pre_init' );
54
55 var $metabox = cmb.metabox();
56 var $repeatGroup = $metabox.find('.cmb-repeatable-group');
57
58 // Init time/date/color pickers
59 cmb.initPickers( $metabox.find('input[type="text"].cmb2-timepicker'), $metabox.find('input[type="text"].cmb2-datepicker'), $metabox.find('input[type="text"].cmb2-colorpicker') );
60
61 // Init code editors.
62 cmb.initCodeEditors( $metabox.find( '.cmb2-textarea-code:not(.disable-codemirror)' ) );
63
64 // Insert toggle button into DOM wherever there is multicheck. credit: Genesis Framework
65 $( '<p><span class="button-secondary cmb-multicheck-toggle">' + l10n.strings.check_toggle + '</span></p>' ).insertBefore( '.cmb2-checkbox-list:not(.no-select-all)' );
66
67 // Make File List drag/drop sortable:
68 cmb.makeListSortable();
69 // Make Repeatable fields drag/drop sortable:
70 cmb.makeRepeatableSortable();
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 .on( 'cmb2_add_row', cmb.emptyValue )
96 .on( 'cmb2_add_row', cmb.setDefaults )
97 .filter('.sortable').each( function() {
98 // Add sorting arrows
99 $( this ).find( '.cmb-remove-group-row-button' ).before( '<a class="button-secondary cmb-shift-rows move-up alignleft" href="#"><span class="'+ l10n.up_arrow_class +'"></span></a> <a class="button-secondary cmb-shift-rows move-down alignleft" href="#"><span class="'+ l10n.down_arrow_class +'"></span></a>' );
100 })
101 .on( 'click', '.cmb-shift-rows', cmb.shiftRows );
102 }
103
104 // on pageload
105 setTimeout( cmb.resizeoEmbeds, 500);
106 // and on window resize
107 $( window ).on( 'resize', cmb.resizeoEmbeds );
108
109 if ( $id( 'addtag' ).length ) {
110 cmb.listenTagAdd();
111 }
112
113 $( document ).on( 'cmb_init', cmb.mceEnsureSave );
114
115 cmb.trigger( 'cmb_init' );
116 };
117
118 // Handles updating tiny mce instances when saving a gutenberg post.
119 // https://github.com/CMB2/CMB2/issues/1156
120 cmb.mceEnsureSave = function() {
121 // If no wp.data, do not proceed (no gutenberg)
122 if ( ! wp.data || ! wp.data.hasOwnProperty('subscribe') ) {
123 return;
124 }
125
126 // If the current user cannot richedit, or MCE is not available, bail.
127 if ( ! cmb.canTinyMCE() ) {
128 return;
129 }
130
131 wp.data.subscribe( function() {
132 var editor = wp.data.hasOwnProperty('select') ? wp.data.select( 'core/editor' ) : null;
133
134 // the post is currently being saved && we have tinymce editors
135 if ( editor && editor.isSavingPost && editor.isSavingPost() && window.tinyMCE.editors.length ) {
136 for ( var i = 0; i < window.tinyMCE.editors.length; i++ ) {
137 if ( window.tinyMCE.activeEditor !== window.tinyMCE.editors[i] ) {
138 window.tinyMCE.editors[i].save();
139 }
140 }
141 }
142 });
143 };
144
145 cmb.canTinyMCE = function() {
146 return l10n.user_can_richedit && window.tinyMCE;
147 };
148
149 cmb.listenTagAdd = function() {
150 $document.ajaxSuccess( function( evt, xhr, settings ) {
151 if ( settings.data && settings.data.length && -1 !== settings.data.indexOf( 'action=add-tag' ) ) {
152 cmb.resetBoxes( $id( 'addtag' ).find( '.cmb2-wrap > .cmb2-metabox' ) );
153 }
154 });
155 };
156
157 cmb.resetBoxes = function( $boxes ) {
158 $.each( $boxes, function() {
159 cmb.resetBox( $( this ) );
160 });
161 };
162
163 cmb.resetBox = function( $box ) {
164 $box.find( '.wp-picker-clear' ).trigger( 'click' );
165 $box.find( '.cmb2-remove-file-button' ).trigger( 'click' );
166 $box.find( '.cmb-row.cmb-repeatable-grouping:not(:first-of-type) .cmb-remove-group-row' ).click();
167 $box.find( '.cmb-repeat-row:not(:first-child)' ).remove();
168
169 $box.find( 'input:not([type="button"]),select,textarea' ).each( function() {
170 var $element = $( this );
171 var tagName = $element.prop('tagName');
172
173 if ( 'INPUT' === tagName ) {
174 var elType = $element.attr( 'type' );
175 if ( 'checkbox' === elType || 'radio' === elType ) {
176 $element.prop( 'checked', false );
177 } else {
178 $element.val( '' );
179 }
180 }
181 if ( 'SELECT' === tagName ) {
182 $( 'option:selected', this ).prop( 'selected', false );
183 }
184 if ( 'TEXTAREA' === tagName ) {
185 $element.html( '' );
186 }
187 });
188 };
189
190 cmb.resetTitlesAndIterator = function( evt ) {
191 if ( ! evt.group ) {
192 return;
193 }
194
195 var $table = $( evt.target );
196 var groupTitle = $table.find( '.cmb-add-group-row' ).data( 'grouptitle' );
197
198 // Loop repeatable group table rows
199 $table.find( '.cmb-repeatable-grouping' ).each( function( rowindex ) {
200 var $row = $( this );
201 var prevIterator = parseInt( $row.data( 'iterator' ), 10 );
202 if ( prevIterator === rowindex ) {
203 return;
204 }
205
206 // Reset rows iterator
207 $row
208 .attr( 'data-iterator', rowindex )
209 .data( 'iterator', rowindex )
210 .attr('id', getRowId( $row.attr('id'), rowindex ) )
211 .find( cmb.repeatEls ).each( function() {
212 cmb.updateNameAttr( $( this ), prevIterator, rowindex );
213 });
214
215 cmb.resetGroupTitles( $row, rowindex, groupTitle );
216 });
217 };
218
219 cmb.resetGroupTitles = function( $row, newIterator, groupTitle ) {
220 if ( groupTitle ) {
221 var $rowTitle = $row.find( 'h3.cmb-group-title' );
222
223 // Reset rows title
224 if ( $rowTitle.length ) {
225 $rowTitle.text( groupTitle.replace( '{#}', parseInt( newIterator, 10 ) + 1 ) );
226 }
227 }
228 };
229
230 cmb.toggleHandle = function( evt ) {
231 evt.preventDefault();
232 cmb.trigger( 'postbox-toggled', $( this ).parent('.postbox').toggleClass('closed') );
233 };
234
235 cmb.toggleCheckBoxes = function( evt ) {
236 evt.preventDefault();
237 var $this = $( this );
238 var $multicheck = $this.closest( '.cmb-td' ).find( 'input[type=checkbox]:not([disabled])' );
239
240 var $toggled = ! $this.data( 'checked' );
241 $multicheck.prop( 'checked', $toggled ).trigger( 'change' );
242 $this.data( 'checked', $toggled );
243 };
244
245 cmb.handleMedia = function( evt ) {
246 evt.preventDefault();
247
248 var $el = $( this );
249 cmb.attach_id = ! $el.hasClass( 'cmb2-upload-list' ) ? $el.closest( '.cmb-td' ).find( '.cmb2-upload-file-id' ).val() : false;
250 // Clean up default 0 value
251 cmb.attach_id = '0' !== cmb.attach_id ? cmb.attach_id : false;
252
253 cmb.handleFieldMedia( $el.prev('input.cmb2-upload-file'), $el.hasClass( 'cmb2-upload-list' ) );
254 };
255
256 cmb.handleFileClick = function( evt ) {
257 if ( $( evt.target ).is( 'a' ) ) {
258 return;
259 }
260
261 evt.preventDefault();
262
263 var $el = $( this );
264 var $td = $el.closest( '.cmb-td' );
265 var isList = $td.find( '.cmb2-upload-button' ).hasClass( 'cmb2-upload-list' );
266 cmb.attach_id = isList ? $el.find( 'input[type="hidden"]' ).data( 'id' ) : $td.find( '.cmb2-upload-file-id' ).val();
267
268 if ( cmb.attach_id ) {
269 cmb.handleFieldMedia( $td.find( 'input.cmb2-upload-file' ), isList );
270 }
271 };
272
273 // Leaving this in for back-compat...
274 cmb._handleMedia = function( id, isList ) {
275 return cmb.handleFieldMedia( $id( id ), isList );
276 };
277
278 cmb.handleFieldMedia = function( $field, isList ) {
279 if ( ! wp ) {
280 return;
281 }
282
283 var id = $field.attr('id');
284 var fieldData = $field.data();
285 var media, handlers;
286
287 // Get/set unique id since actual id cold _not_ be unique due to bad replacements, etc...
288 var uid = fieldData.mediaid;
289 if ( ! uid ) {
290 uid = _.uniqueId();
291 $field.attr('data-mediaid', uid).data('mediaid', uid);
292 fieldData.mediaid = uid;
293 }
294
295 handlers = cmb.mediaHandlers;
296 media = cmb.media;
297 media.mediaid = uid;
298 media.field = id;
299 media.$field = $field;
300 media.fieldData = fieldData;
301 media.previewSize = media.fieldData.previewsize;
302 media.sizeName = media.fieldData.sizename;
303 media.fieldName = $field.attr('name');
304 media.isList = isList;
305
306 // If this field's media frame already exists, reopen it.
307 if ( uid in media.frames ) {
308 return media.frames[ uid ].open();
309 }
310
311 // Create the media frame.
312 media.frames[ uid ] = wp.media( {
313 title: cmb.metabox().find('label[for="' + id + '"]').text(),
314 library : media.fieldData.queryargs || {},
315 button: {
316 text: l10n.strings[ isList ? 'upload_files' : 'upload_file' ]
317 },
318 multiple: isList ? 'add' : false
319 } );
320
321 // Enable the additional media filters: https://github.com/CMB2/CMB2/issues/873
322 media.frames[ uid ].states.first().set( 'filterable', 'all' );
323
324 cmb.trigger( 'cmb_media_modal_init', media );
325
326 handlers.list = function( selection, returnIt ) {
327
328 // Setup our fileGroup array
329 var fileGroup = [];
330 var attachmentHtml;
331
332 if ( ! handlers.list.templates ) {
333 handlers.list.templates = {
334 image : wp.template( 'cmb2-list-image' ),
335 file : wp.template( 'cmb2-list-file' ),
336 };
337 }
338
339 // Loop through each attachment
340 selection.each( function( attachment ) {
341
342 // Image preview or standard generic output if it's not an image.
343 attachmentHtml = handlers.getAttachmentHtml( attachment, 'list' );
344
345 // Add our file to our fileGroup array
346 fileGroup.push( attachmentHtml );
347 });
348
349 if ( ! returnIt ) {
350 // Append each item from our fileGroup array to .cmb2-media-status
351 media.$field.siblings( '.cmb2-media-status' ).append( fileGroup );
352 } else {
353 return fileGroup;
354 }
355
356 };
357
358 handlers.single = function( selection ) {
359 if ( ! handlers.single.templates ) {
360 handlers.single.templates = {
361 image : wp.template( 'cmb2-single-image' ),
362 file : wp.template( 'cmb2-single-file' ),
363 };
364 }
365
366 // Only get one file from the uploader
367 var attachment = selection.first();
368
369 media.$field.val( attachment.get( 'url' ) );
370 media.$field.closest( '.cmb-td' ).find('.cmb2-upload-file-id')
371 .val( attachment.get( 'id' ) );
372
373 // Image preview or standard generic output if it's not an image.
374 var attachmentHtml = handlers.getAttachmentHtml( attachment, 'single' );
375
376 // add/display our output
377 media.$field.siblings( '.cmb2-media-status' ).slideDown().html( attachmentHtml );
378 };
379
380 handlers.getAttachmentHtml = function( attachment, templatesId ) {
381 var isImage = 'image' === attachment.get( 'type' );
382 var data = handlers.prepareData( attachment, isImage );
383
384 // Image preview or standard generic output if it's not an image.
385 return handlers[ templatesId ].templates[ isImage ? 'image' : 'file' ]( data );
386 };
387
388 handlers.prepareData = function( data, image ) {
389 if ( image ) {
390 // Set the correct image size data
391 handlers.getImageData.call( data, 50 );
392 }
393
394 data = data.toJSON();
395 data.mediaField = media.field;
396 data.mediaFieldName = media.fieldName;
397 data.stringRemoveImage = l10n.strings.remove_image;
398 data.stringFile = l10n.strings.file;
399 data.stringDownload = l10n.strings.download;
400 data.stringRemoveFile = l10n.strings.remove_file;
401
402 return data;
403 };
404
405 handlers.getImageData = function( fallbackSize ) {
406
407 // Preview size dimensions
408 var previewW = media.previewSize[0] || fallbackSize;
409 var previewH = media.previewSize[1] || fallbackSize;
410
411 // Image dimensions and url
412 var url = this.get( 'url' );
413 var width = this.get( 'width' );
414 var height = this.get( 'height' );
415 var sizes = this.get( 'sizes' );
416
417 // Get the correct dimensions and url if a named size is set and exists
418 // fallback to the 'large' size
419 if ( sizes ) {
420 if ( sizes[ media.sizeName ] ) {
421 url = sizes[ media.sizeName ].url;
422 width = sizes[ media.sizeName ].width;
423 height = sizes[ media.sizeName ].height;
424 } else if ( sizes.large ) {
425 url = sizes.large.url;
426 width = sizes.large.width;
427 height = sizes.large.height;
428 }
429 }
430
431 // Fit the image in to the preview size, keeping the correct aspect ratio
432 if ( width > previewW ) {
433 height = Math.floor( previewW * height / width );
434 width = previewW;
435 }
436
437 if ( height > previewH ) {
438 width = Math.floor( previewH * width / height );
439 height = previewH;
440 }
441
442 if ( ! width ) {
443 width = previewW;
444 }
445
446 if ( ! height ) {
447 height = 'svg' === this.get( 'filename' ).split( '.' ).pop() ? '100%' : previewH;
448 }
449
450 this.set( 'sizeUrl', url );
451 this.set( 'sizeWidth', width );
452 this.set( 'sizeHeight', height );
453
454 return this;
455 };
456
457 handlers.selectFile = function() {
458 var selection = media.frames[ uid ].state().get( 'selection' );
459 var type = isList ? 'list' : 'single';
460
461 if ( cmb.attach_id && isList ) {
462 $( '[data-id="'+ cmb.attach_id +'"]' ).parents( 'li' ).replaceWith( handlers.list( selection, true ) );
463 } else {
464 handlers[type]( selection );
465 }
466
467 cmb.trigger( 'cmb_media_modal_select', selection, media );
468 };
469
470 handlers.openModal = function() {
471 var selection = media.frames[ uid ].state().get( 'selection' );
472 var attach;
473
474 if ( ! cmb.attach_id ) {
475 selection.reset();
476 } else {
477 attach = wp.media.attachment( cmb.attach_id );
478 attach.fetch();
479 selection.set( attach ? [ attach ] : [] );
480 }
481
482 cmb.trigger( 'cmb_media_modal_open', selection, media );
483 };
484
485 // When a file is selected, run a callback.
486 media.frames[ uid ]
487 .on( 'select', handlers.selectFile )
488 .on( 'open', handlers.openModal );
489
490 // Finally, open the modal
491 media.frames[ uid ].open();
492 };
493
494 cmb.handleRemoveMedia = function( evt ) {
495 evt.preventDefault();
496 var $this = $( this );
497 if ( $this.is( '.cmb-attach-list .cmb2-remove-file-button' ) ) {
498 $this.parents( '.cmb2-media-item' ).remove();
499 return false;
500 }
501
502 var $cell = $this.closest( '.cmb-td' );
503 cmb.media.$field = $cell.find('.cmb2-upload-file');
504 cmb.media.field = cmb.media.$field.attr('id');
505
506 cmb.media.$field.val('');
507 $cell.find('.cmb2-upload-file-id').val('');
508 $this.parents('.cmb2-media-status').html('');
509
510 return false;
511 };
512
513 cmb.cleanRow = function( $row, prevNum, group ) {
514 var $elements = $row.find( cmb.repeatUpdate );
515 if ( group ) {
516
517 var $other = $row.find( '[id]' ).not( cmb.repeatUpdate );
518
519 // Remove extra ajaxed rows
520 $row.find('.cmb-repeat-table .cmb-repeat-row:not(:first-child)').remove();
521
522 // Update all elements w/ an ID
523 if ( $other.length ) {
524 $other.each( function() {
525 var $_this = $( this );
526 var oldID = $_this.attr( 'id' );
527 var newID = oldID.replace( '_'+ prevNum, '_'+ cmb.idNumber );
528 var $buttons = $row.find('[data-selector="'+ oldID +'"]');
529 $_this.attr( 'id', newID );
530
531 // Replace data-selector vars
532 if ( $buttons.length ) {
533 $buttons.attr( 'data-selector', newID ).data( 'selector', newID );
534 }
535 });
536 }
537 }
538
539 $elements.filter( ':checked' ).removeAttr( 'checked' );
540 $elements.find( ':checked' ).removeAttr( 'checked' );
541 $elements.filter( ':selected' ).removeAttr( 'selected' );
542 $elements.find( ':selected' ).removeAttr( 'selected', false );
543
544 cmb.resetGroupTitles( $row, cmb.idNumber, $row.data( 'title' ) );
545
546 $elements.each( function() {
547 cmb.elReplacements( $( this ), prevNum, group );
548 } );
549
550 return cmb;
551 };
552
553 cmb.elReplacements = function( $newInput, prevNum, group ) {
554 var oldFor = $newInput.attr( 'for' );
555 var oldVal = $newInput.val();
556 var type = $newInput.prop( 'type' );
557 var defVal = cmb.getFieldArg( $newInput, 'default' );
558 var newVal = 'undefined' !== typeof defVal && false !== defVal ? defVal : '';
559 var tagName = $newInput.prop('tagName');
560 var checkable = 'radio' === type || 'checkbox' === type ? oldVal : false;
561 var attrs = {};
562 var newID, oldID;
563 if ( oldFor ) {
564 attrs = { 'for' : oldFor.replace( '_'+ prevNum, '_'+ cmb.idNumber ) };
565 } else {
566 var oldName = $newInput.attr( 'name' );
567 var newName;
568 oldID = $newInput.attr( 'id' );
569
570 // Handle adding groups vs rows.
571 if ( group ) {
572 // Expect another bracket after group's index closing bracket.
573 newName = oldName ? oldName.replace( '['+ prevNum +'][', '['+ cmb.idNumber +'][' ) : '';
574 // Expect another underscore after group's index trailing underscore.
575 newID = oldID ? oldID.replace( '_' + prevNum + '_', '_' + cmb.idNumber + '_' ) : '';
576 }
577 else {
578 // Row indexes are at the very end of the string.
579 newName = oldName ? cmb.replaceLast( oldName, '[' + prevNum + ']', '[' + cmb.idNumber + ']' ) : '';
580 newID = oldID ? cmb.replaceLast( oldID, '_' + prevNum, '_' + cmb.idNumber ) : '';
581 }
582
583 attrs = {
584 id: newID,
585 name: newName
586 };
587
588 }
589
590 // Clear out textarea values
591 if ( 'TEXTAREA' === tagName ) {
592 $newInput.html( newVal );
593 }
594
595 if ( 'SELECT' === tagName && 'undefined' !== typeof defVal ) {
596 var $toSelect = $newInput.find( '[value="'+ defVal + '"]' );
597 if ( $toSelect.length ) {
598 $toSelect.attr( 'selected', 'selected' ).prop( 'selected', 'selected' );
599 }
600 }
601
602 if ( checkable ) {
603 $newInput.removeAttr( 'checked' );
604 if ( 'undefined' !== typeof defVal && oldVal === defVal ) {
605 $newInput.attr( 'checked', 'checked' ).prop( 'checked', 'checked' );
606 }
607 }
608
609 if ( ! group && $newInput[0].hasAttribute( 'data-iterator' ) ) {
610 attrs['data-iterator'] = cmb.idNumber;
611 }
612
613 $newInput
614 .removeClass( 'hasDatepicker' )
615 .val( checkable ? checkable : newVal ).attr( attrs );
616
617 return $newInput;
618 };
619
620 cmb.newRowHousekeeping = function( $row ) {
621 var $colorPicker = $row.find( '.wp-picker-container' );
622 var $list = $row.find( '.cmb2-media-status' );
623
624 if ( $colorPicker.length ) {
625 // Need to clean-up colorpicker before appending
626 $colorPicker.each( function() {
627 var $td = $( this ).parent();
628 $td.html( $td.find( 'input[type="text"].cmb2-colorpicker' ).attr('style', '') );
629 });
630 }
631
632 // Need to clean-up colorpicker before appending
633 if ( $list.length ) {
634 $list.empty();
635 }
636
637 return cmb;
638 };
639
640 cmb.afterRowInsert = function( $row ) {
641 // Init pickers from new row
642 cmb.initPickers( $row.find('input[type="text"].cmb2-timepicker'), $row.find('input[type="text"].cmb2-datepicker'), $row.find('input[type="text"].cmb2-colorpicker') );
643 };
644
645 cmb.updateNameAttr = function ( $el, prevIterator, newIterator ) {
646 var name = $el.attr( 'name' ); // get current name
647
648 // If name is defined
649 if ( 'undefined' !== typeof name ) {
650 var isFileList = $el.attr( 'id' ).indexOf('filelist');
651
652 // Update field name attributes so data is not orphaned when a row is removed and post is saved
653 var from = isFileList ? '[' + prevIterator + '][' : '[' + prevIterator + ']';
654 var to = isFileList ? '[' + newIterator + '][' : '[' + newIterator + ']';
655 var newName = name.replace( from, to );
656
657 // New name with replaced iterator
658 $el.attr( 'name', newName );
659 }
660 };
661
662 cmb.emptyValue = function( evt, row ) {
663 $( cmb.noEmpty, row ).val( '' );
664 };
665
666 cmb.setDefaults = function( evt, row ) {
667 $( cmb.noEmpty, row ).each( function() {
668 var $el = $(this);
669 var defVal = cmb.getFieldArg( $el, 'default' );
670 if ( 'undefined' !== typeof defVal && false !== defVal ) {
671 $el.val( defVal );
672 }
673 });
674 };
675
676 cmb.addGroupRow = function( evt ) {
677 evt.preventDefault();
678
679 var $this = $( this );
680
681 // before anything significant happens
682 cmb.triggerElement( $this, 'cmb2_add_group_row_start', $this );
683
684 var $table = $id( $this.data('selector') );
685 var $oldRow = $table.find('.cmb-repeatable-grouping').last();
686 var prevNum = parseInt( $oldRow.data('iterator'), 10 );
687 cmb.idNumber = parseInt( prevNum, 10 ) + 1;
688 var $row = $oldRow.clone();
689 var nodeName = $row.prop('nodeName') || 'div';
690
691 // Make sure the next number doesn't exist.
692 while ( $table.find( '.cmb-repeatable-grouping[data-iterator="'+ cmb.idNumber +'"]' ).length > 0 ) {
693 cmb.idNumber++;
694 }
695
696 cmb.newRowHousekeeping( $row.data( 'title', $this.data( 'grouptitle' ) ) ).cleanRow( $row, prevNum, true );
697 $row.find( '.cmb-add-row-button' ).prop( 'disabled', false );
698
699 var $newRow = $( '<' + nodeName + ' id="'+ getRowId( $oldRow.attr('id'), cmb.idNumber ) +'" class="postbox cmb-row cmb-repeatable-grouping" data-iterator="'+ cmb.idNumber +'">'+ $row.html() +'</' + nodeName + '>' );
700 $oldRow.after( $newRow );
701
702 cmb.afterRowInsert( $newRow );
703 cmb.makeRepeatableSortable( $newRow );
704
705 cmb.triggerElement( $table, { type: 'cmb2_add_row', group: true }, $newRow );
706 };
707
708 cmb.addAjaxRow = function( evt ) {
709 evt.preventDefault();
710
711 var $this = $( this );
712 var $table = $id( $this.data('selector') );
713 var $row = $table.find('.empty-row');
714 var prevNum = parseInt( $row.find('[data-iterator]').data('iterator'), 10 );
715 cmb.idNumber = parseInt( prevNum, 10 ) + 1;
716 var $emptyrow = $row.clone();
717
718 cmb.newRowHousekeeping( $emptyrow ).cleanRow( $emptyrow, prevNum );
719
720 $row.removeClass('empty-row hidden').addClass('cmb-repeat-row');
721 $row.after( $emptyrow );
722
723 cmb.afterRowInsert( $emptyrow );
724
725 cmb.triggerElement( $table, { type: 'cmb2_add_row', group: false }, $emptyrow, $row );
726 };
727
728 cmb.removeGroupRow = function( evt ) {
729 evt.preventDefault();
730
731 var $this = $( this );
732 var confirmation = $this.data('confirm');
733
734 // Process further only if deletion confirmation enabled and user agreed.
735 if ( ! cmb.resetRow.resetting && confirmation && ! window.confirm( confirmation ) ) {
736 return;
737 }
738
739 var $table = $id( $this.data('selector') );
740 var $parent = $this.parents('.cmb-repeatable-grouping');
741 var number = $table.find('.cmb-repeatable-grouping').length;
742
743 if ( number < 2 ) {
744 return cmb.resetRow( $parent.parents('.cmb-repeatable-group').find( '.cmb-add-group-row' ), $this );
745 }
746
747 cmb.triggerElement( $table, 'cmb2_remove_group_row_start', $this );
748
749 $parent.remove();
750
751 cmb.triggerElement( $table, { type: 'cmb2_remove_row', group: true } );
752 };
753
754 cmb.removeAjaxRow = function( evt ) {
755 evt.preventDefault();
756
757 var $this = $( this );
758
759 // Check if disabled
760 if ( $this.hasClass( 'button-disabled' ) ) {
761 return;
762 }
763
764 var $parent = $this.parents('.cmb-row');
765 var $table = $this.parents('.cmb-repeat-table');
766 var number = $table.find('.cmb-row').length;
767
768 if ( number <= 2 ) {
769 return cmb.resetRow( $parent.find( '.cmb-add-row-button' ), $this );
770 }
771
772 if ( $parent.hasClass('empty-row') ) {
773 $parent.prev().addClass( 'empty-row' ).removeClass('cmb-repeat-row');
774 }
775
776 $this.parents('.cmb-repeat-table .cmb-row').remove();
777
778
779 cmb.triggerElement( $table, { type: 'cmb2_remove_row', group: false } );
780 };
781
782 cmb.resetRow = function( $addNewBtn, $removeBtn ) {
783 cmb.resetRow.resetting = true;
784 // Click the "add new" button followed by the "remove this" button
785 // in order to reset the repeat row to empty values.
786 $addNewBtn.trigger( 'click' );
787 $removeBtn.trigger( 'click' );
788 cmb.resetRow.resetting = false;
789 };
790
791 cmb.shiftRows = function( evt ) {
792
793 evt.preventDefault();
794
795 var $this = $( this );
796 var moveUp = $this.hasClass( 'move-up' ) ? true : false;
797 var $from = $this.parents( '.cmb-repeatable-grouping' );
798 var $goto = $from[ moveUp ? 'prev' : 'next' ]( '.cmb-repeatable-grouping' );
799
800 // Before shift occurs.
801 cmb.triggerElement( $this, 'cmb2_shift_rows_enter', $this, $from, $goto );
802
803 if ( ! $goto.length ) {
804 return;
805 }
806
807 // About to shift
808 cmb.triggerElement( $this, 'cmb2_shift_rows_start', $this, $from, $goto );
809
810 var fromIterator = $from.attr('data-iterator');
811 var toIterator = $goto.attr('data-iterator');
812
813 // Replace name attributes in both groups.
814 $from.find( cmb.repeatEls ).each( function() {
815 cmb.updateNameAttr( $( this ), fromIterator, toIterator );
816 });
817 $goto.find( cmb.repeatEls ).each( function() {
818 cmb.updateNameAttr( $( this ), toIterator, fromIterator );
819 });
820
821 // Replace titles in both groups.
822 var groupTitle = $this.parents( '.cmb-repeatable-group' ).find('[data-grouptitle]').data( 'grouptitle' );
823 if ( groupTitle ) {
824 cmb.resetGroupTitles( $from, toIterator, groupTitle );
825 cmb.resetGroupTitles( $goto, fromIterator, groupTitle );
826 }
827
828 // Update the iterators
829 $from.data( 'iterator', toIterator );
830 $from.attr( 'data-iterator', toIterator );
831 $goto.data( 'iterator', fromIterator );
832 $goto.attr( 'data-iterator', fromIterator );
833
834 // Now move the group to it's destination.
835 $goto[moveUp ? 'before' : 'after']( $from );
836
837 // Scroll to the top of the shifted group.
838 $([document.documentElement, document.body]).animate({
839 scrollTop: $from.offset().top - 50
840 }, 300);
841
842 // shift done
843 cmb.triggerElement( $this, 'cmb2_shift_rows_complete', $this, $from, $goto );
844 };
845
846 cmb.initPickers = function( $timePickers, $datePickers, $colorPickers ) {
847 cmb.trigger( 'cmb_init_pickers', {
848 time: $timePickers,
849 date: $datePickers,
850 color: $colorPickers
851 } );
852
853 // Initialize jQuery UI timepickers
854 cmb.initDateTimePickers( $timePickers, 'timepicker', 'time_picker' );
855 // Initialize jQuery UI datepickers
856 cmb.initDateTimePickers( $datePickers, 'datepicker', 'date_picker' );
857 // Initialize color picker
858 cmb.initColorPickers( $colorPickers );
859 };
860
861 cmb.initDateTimePickers = function( $selector, method, defaultKey ) {
862 if ( $selector.length ) {
863 $selector[ method ]( 'destroy' ).each( function() {
864 var $this = $( this );
865 var fieldOpts = $this.data( method ) || {};
866 var options = $.extend( {}, cmb.defaults[ defaultKey ], fieldOpts );
867 $this[ method ]( cmb.datePickerSetupOpts( fieldOpts, options, method ) );
868 } );
869 }
870 };
871
872 cmb.datePickerSetupOpts = function( fieldOpts, options, method ) {
873 var existing = $.extend( {}, options );
874
875 options.beforeShow = function( input, inst ) {
876 if ( 'timepicker' === method ) {
877 cmb.addTimePickerClasses( inst.dpDiv );
878 }
879
880 // Wrap datepicker w/ class to narrow the scope of jQuery UI CSS and prevent conflicts
881 $id( 'ui-datepicker-div' ).addClass( 'cmb2-element' );
882
883 // Let's be sure to call beforeShow if it was added
884 if ( 'function' === typeof existing.beforeShow ) {
885 existing.beforeShow( input, inst );
886 }
887 };
888
889 if ( 'timepicker' === method ) {
890 options.onChangeMonthYear = function( year, month, inst, picker ) {
891 cmb.addTimePickerClasses( inst.dpDiv );
892
893 // Let's be sure to call onChangeMonthYear if it was added
894 if ( 'function' === typeof existing.onChangeMonthYear ) {
895 existing.onChangeMonthYear( year, month, inst, picker );
896 }
897 };
898 }
899
900 options.onClose = function( dateText, inst ) {
901 // Remove the class when we're done with it (and hide to remove FOUC).
902 var $picker = $id( 'ui-datepicker-div' ).removeClass( 'cmb2-element' ).hide();
903 if ( 'timepicker' === method && ! $( inst.input ).val() ) {
904 // Set the timepicker field value if it's empty.
905 inst.input.val( $picker.find( '.ui_tpicker_time' ).text() );
906 }
907
908 // Let's be sure to call onClose if it was added
909 if ( 'function' === typeof existing.onClose ) {
910 existing.onClose( dateText, inst );
911 }
912 };
913
914 return options;
915 };
916
917 // Adds classes to timepicker buttons.
918 cmb.addTimePickerClasses = function( $picker ) {
919 var func = cmb.addTimePickerClasses;
920 func.count = func.count || 0;
921
922 // Wait a bit to let the timepicker render, since these are pre-render events.
923 setTimeout( function() {
924 if ( $picker.find( '.ui-priority-secondary' ).length ) {
925 $picker.find( '.ui-priority-secondary' ).addClass( 'button-secondary' );
926 $picker.find( '.ui-priority-primary' ).addClass( 'button-primary' );
927 func.count = 0;
928 } else if ( func.count < 5 ) {
929 func.count++;
930 func( $picker );
931 }
932 }, 10 );
933 };
934
935 cmb.initColorPickers = function( $selector ) {
936 if ( ! $selector.length ) {
937 return;
938 }
939 if ( 'object' === typeof jQuery.wp && 'function' === typeof jQuery.wp.wpColorPicker ) {
940
941 $selector.each( function() {
942 var $this = $( this );
943 var fieldOpts = $this.data( 'colorpicker' ) || {};
944 $this.wpColorPicker( $.extend( {}, cmb.defaults.color_picker, fieldOpts ) );
945 } );
946
947 } else {
948 $selector.each( function( i ) {
949 $( this ).after( '<div id="picker-' + i + '" style="z-index: 1000; background: #EEE; border: 1px solid #CCC; position: absolute; display: block;"></div>' );
950 $id( 'picker-' + i ).hide().farbtastic( $( this ) );
951 } )
952 .focus( function() {
953 $( this ).next().show();
954 } )
955 .blur( function() {
956 $( this ).next().hide();
957 } );
958 }
959 };
960
961 cmb.initCodeEditors = function( $selector ) {
962 cmb.trigger( 'cmb_init_code_editors', $selector );
963
964 if ( ! cmb.defaults.code_editor || ! wp || ! wp.codeEditor || ! $selector.length ) {
965 return;
966 }
967
968 $selector.each( function() {
969 wp.codeEditor.initialize(
970 this.id,
971 cmb.codeEditorArgs( $( this ).data( 'codeeditor' ) )
972 );
973 } );
974 };
975
976 cmb.codeEditorArgs = function( overrides ) {
977 var props = [ 'codemirror', 'csslint', 'jshint', 'htmlhint' ];
978 var args = $.extend( {}, cmb.defaults.code_editor );
979 overrides = overrides || {};
980
981 for ( var i = props.length - 1; i >= 0; i-- ) {
982 if ( overrides.hasOwnProperty( props[i] ) ) {
983 args[ props[i] ] = $.extend( {}, args[ props[i] ] || {}, overrides[ props[i] ] );
984 }
985 }
986
987 return args;
988 };
989
990 cmb.makeListSortable = function() {
991 var $filelist = cmb.metabox().find( '.cmb2-media-status.cmb-attach-list' );
992 if ( $filelist.length ) {
993 $filelist.sortable({ cursor: 'move' }).disableSelection();
994 }
995 };
996
997 cmb.makeRepeatableSortable = function( $row ) {
998 var $repeatables = ($row || cmb.metabox()).find( '.cmb-repeat-table .cmb-field-list' );
999
1000 if ( $repeatables.length ) {
1001 $repeatables.sortable({
1002 items : '.cmb-repeat-row',
1003 cursor: 'move',
1004 // The default "cancel" attributes are: "input,textarea,button,select,option".
1005 // We are appending .CodeMirror.
1006 // See https://api.jqueryui.com/sortable/#option-cancel
1007 cancel: 'input,textarea,button,select,option,.CodeMirror'
1008 });
1009 }
1010 };
1011
1012 cmb.maybeOembed = function( evt ) {
1013 var $this = $( this );
1014
1015 var m = {
1016 focusout : function() {
1017 setTimeout( function() {
1018 // if it's been 2 seconds, hide our spinner
1019 cmb.spinner( '.cmb2-metabox', true );
1020 }, 2000);
1021 },
1022 keyup : function() {
1023 var betw = function( min, max ) {
1024 return ( evt.which <= max && evt.which >= min );
1025 };
1026 // Only Ajax on normal keystrokes
1027 if ( betw( 48, 90 ) || betw( 96, 111 ) || betw( 8, 9 ) || evt.which === 187 || evt.which === 190 ) {
1028 // fire our ajax function
1029 cmb.doAjax( $this, evt );
1030 }
1031 },
1032 paste : function() {
1033 // paste event is fired before the value is filled, so wait a bit
1034 setTimeout( function() { cmb.doAjax( $this ); }, 100);
1035 }
1036 };
1037
1038 m[ evt.type ]();
1039 };
1040
1041 /**
1042 * Resize oEmbed videos to fit in their respective metaboxes
1043 *
1044 * @since 0.9.4
1045 *
1046 * @return {return}
1047 */
1048 cmb.resizeoEmbeds = function() {
1049 cmb.metabox().each( function() {
1050 var $this = $( this );
1051 var $tableWrap = $this.parents('.inside');
1052 var isSide = $this.parents('.inner-sidebar').length || $this.parents( '#side-sortables' ).length;
1053 var isSmall = isSide;
1054 var isSmallest = false;
1055 if ( ! $tableWrap.length ) {
1056 return true; // continue
1057 }
1058
1059 // Calculate new width
1060 var tableW = $tableWrap.width();
1061
1062 if ( cmb.styleBreakPoint > tableW ) {
1063 isSmall = true;
1064 isSmallest = ( cmb.styleBreakPoint - 62 ) > tableW;
1065 }
1066
1067 tableW = isSmall ? tableW : Math.round(($tableWrap.width() * 0.82)*0.97);
1068 var newWidth = tableW - 30;
1069 if ( isSmall && ! isSide && ! isSmallest ) {
1070 newWidth = newWidth - 75;
1071 }
1072 if ( newWidth > 639 ) {
1073 return true; // continue
1074 }
1075
1076 var $embeds = $this.find('.cmb-type-oembed .embed-status');
1077 var $children = $embeds.children().not('.cmb2-remove-wrapper');
1078 if ( ! $children.length ) {
1079 return true; // continue
1080 }
1081
1082 $children.each( function() {
1083 var $this = $( this );
1084 var iwidth = $this.width();
1085 var iheight = $this.height();
1086 var _newWidth = newWidth;
1087 if ( $this.parents( '.cmb-repeat-row' ).length && ! isSmall ) {
1088 // Make room for our repeatable "remove" button column
1089 _newWidth = newWidth - 91;
1090 _newWidth = 785 > tableW ? _newWidth - 15 : _newWidth;
1091 }
1092 // Calc new height
1093 var newHeight = Math.round((_newWidth * iheight)/iwidth);
1094 $this.width(_newWidth).height(newHeight);
1095 });
1096 });
1097 };
1098
1099 // function for running our ajax
1100 cmb.doAjax = function( $obj ) {
1101 // get typed value
1102 var oembed_url = $obj.val();
1103 // only proceed if the field contains more than 6 characters
1104 if ( oembed_url.length < 6 ) {
1105 return;
1106 }
1107
1108 // get field id
1109 var field_id = $obj.attr('id');
1110 var $context = $obj.closest( '.cmb-td' );
1111 var $embed_container = $context.find( '.embed-status' );
1112 var $embed_wrap = $context.find( '.embed_wrap' );
1113 var $child_el = $embed_container.find( ':first-child' );
1114 var oembed_width = $embed_container.length && $child_el.length ? $child_el.width() : $obj.width();
1115
1116 cmb.log( 'oembed_url', oembed_url, field_id );
1117
1118 // show our spinner
1119 cmb.spinner( $context );
1120 // clear out previous results
1121 $embed_wrap.html('');
1122 // and run our ajax function
1123 setTimeout( function() {
1124 // if they haven't typed in 500 ms
1125 if ( $( '.cmb2-oembed:focus' ).val() !== oembed_url ) {
1126 return;
1127 }
1128 $.ajax({
1129 type : 'post',
1130 dataType : 'json',
1131 url : l10n.ajaxurl,
1132 data : {
1133 'action' : 'cmb2_oembed_handler',
1134 'oembed_url' : oembed_url,
1135 'oembed_width' : oembed_width > 300 ? oembed_width : 300,
1136 'field_id' : field_id,
1137 'object_id' : $obj.data( 'objectid' ),
1138 'object_type' : $obj.data( 'objecttype' ),
1139 'cmb2_ajax_nonce' : l10n.ajax_nonce
1140 },
1141 success: function(response) {
1142 cmb.log( response );
1143 // hide our spinner
1144 cmb.spinner( $context, true );
1145 // and populate our results from ajax response
1146 $embed_wrap.html( response.data );
1147 }
1148 });
1149
1150 }, 500);
1151
1152 };
1153
1154 /**
1155 * Gets jQuery object containing all CMB metaboxes. Caches the result.
1156 *
1157 * @since 1.0.2
1158 *
1159 * @return {Object} jQuery object containing all CMB metaboxes.
1160 */
1161 cmb.metabox = function() {
1162 if ( cmb.$metabox ) {
1163 return cmb.$metabox;
1164 }
1165 cmb.$metabox = $('.cmb2-wrap > .cmb2-metabox');
1166 return cmb.$metabox;
1167 };
1168
1169 /**
1170 * Starts/stops contextual spinner.
1171 *
1172 * @since 1.0.1
1173 *
1174 * @param {object} $context The jQuery parent/context object.
1175 * @param {bool} hide Whether to hide the spinner (will show by default).
1176 *
1177 * @return {void}
1178 */
1179 cmb.spinner = function( $context, hide ) {
1180 var m = hide ? 'removeClass' : 'addClass';
1181 $('.cmb-spinner', $context )[ m ]( 'is-active' );
1182 };
1183
1184 /**
1185 * Triggers a jQuery event on the document object.
1186 *
1187 * @since 2.2.3
1188 *
1189 * @param {string} evtName The name of the event to trigger.
1190 *
1191 * @return {void}
1192 */
1193 cmb.trigger = function( evtName ) {
1194 var args = Array.prototype.slice.call( arguments, 1 );
1195 args.push( cmb );
1196 $document.trigger( evtName, args );
1197 };
1198
1199 /**
1200 * Triggers a jQuery event on the given jQuery object.
1201 *
1202 * @since 2.2.3
1203 *
1204 * @param {object} $el The jQuery element object.
1205 * @param {string} evtName The name of the event to trigger.
1206 *
1207 * @return {void}
1208 */
1209 cmb.triggerElement = function( $el, evtName ) {
1210 var args = Array.prototype.slice.call( arguments, 2 );
1211 args.push( cmb );
1212 $el.trigger( evtName, args );
1213 };
1214
1215 /**
1216 * Get an argument for a given field.
1217 *
1218 * @since 2.5.0
1219 *
1220 * @param {string|object} hash The field hash, id, or a jQuery object for a field.
1221 * @param {string} arg The argument to get on the field.
1222 *
1223 * @return {mixed} The argument value.
1224 */
1225 cmb.getFieldArg = function( hash, arg ) {
1226 return cmb.getField( hash )[ arg ];
1227 };
1228
1229 /**
1230 * Get a field object instances. Can be filtered by passing in a filter callback function.
1231 * e.g. `const fileFields = CMB2.getFields(f => 'file' === f.type);`
1232 *
1233 * @since 2.5.0
1234 *
1235 * @param {mixed} filterCb An optional filter callback function.
1236 *
1237 * @return array An array of field object instances.
1238 */
1239 cmb.getFields = function( filterCb ) {
1240 if ( 'function' === typeof filterCb ) {
1241 var fields = [];
1242 $.each( l10n.fields, function( hash, field ) {
1243 if ( filterCb( field, hash ) ) {
1244 fields.push( field );
1245 }
1246 });
1247 return fields;
1248 }
1249
1250 return l10n.fields;
1251 };
1252
1253 /**
1254 * Get a field object instance by hash or id.
1255 *
1256 * @since 2.5.0
1257 *
1258 * @param {string|object} hash The field hash, id, or a jQuery object for a field.
1259 *
1260 * @return {object} The field object or an empty object.
1261 */
1262 cmb.getField = function( hash ) {
1263 var field = {};
1264 hash = hash instanceof jQuery ? hash.data( 'hash' ) : hash;
1265 if ( hash ) {
1266 try {
1267 if ( l10n.fields[ hash ] ) {
1268 throw new Error( hash );
1269 }
1270
1271 cmb.getFields( function( field ) {
1272 if ( 'function' === typeof hash ) {
1273 if ( hash( field ) ) {
1274 throw new Error( field.hash );
1275 }
1276 } else if ( field.id && field.id === hash ) {
1277 throw new Error( field.hash );
1278 }
1279 });
1280 } catch( e ) {
1281 field = l10n.fields[ e.message ];
1282 }
1283 }
1284
1285 return field;
1286 };
1287
1288 /**
1289 * Safely log things if query var is set. Accepts same parameters as console.log.
1290 *
1291 * @since 1.0.0
1292 *
1293 * @return {void}
1294 */
1295 cmb.log = function() {
1296 if ( l10n.script_debug && console && 'function' === typeof console.log ) {
1297 console.log.apply(console, arguments);
1298 }
1299 };
1300
1301 /**
1302 * Replace the last occurrence of a string.
1303 *
1304 * @since 2.2.6
1305 *
1306 * @param {string} string String to search/replace.
1307 * @param {string} search String to search.
1308 * @param {string} replace String to replace search with.
1309 *
1310 * @return {string} Possibly modified string.
1311 */
1312 cmb.replaceLast = function( string, search, replace ) {
1313 // find the index of last time word was used
1314 var n = string.lastIndexOf( search );
1315
1316 // slice the string in 2, one from the start to the lastIndexOf
1317 // and then replace the word in the rest
1318 return string.slice( 0, n ) + string.slice( n ).replace( search, replace );
1319 };
1320
1321 // Kick it off!
1322 $( cmb.init );
1323
1324 })(window, document, jQuery, window.CMB2);
1325