PluginProbe
Cool Flipbox – Shortcode & Gutenberg Block / 1.4
Cool Flipbox – Shortcode & Gutenberg Block v1.4
trunk 1.4 1.5 1.6 1.6.2 1.7.1 1.8.3 1.9.0 1.9.1 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1
flip-boxes / CMB2 / js / cmb2.js

cmb2.js in Cool Flipbox – Shortcode & Gutenberg Block 1.4, at CMB2/js/cmb2.js

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