PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / 6.4.2
Secure Custom Fields v6.4.2
6.9.1 6.9.0 6.8.9 6.8.7 6.8.8 6.8.6 6.8.4 6.8.5 trunk 6.4.0-beta1 6.4.0-beta2 6.4.1 6.4.1-beta3 6.4.1-beta4 6.4.1-beta5 6.4.1-beta6 6.4.1-beta7 6.4.2 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 6.5.6 6.5.7 6.6.0 6.7.0 6.7.1 6.8.0 6.8.1 6.8.2 6.8.3
secure-custom-fields / assets / src / js / _field-group-field.js
secure-custom-fields / assets / src / js Last commit date
pro 1 year ago _acf-compatibility.js 1 year ago _acf-condition-types.js 1 year ago _acf-condition.js 1 year ago _acf-conditions.js 1 year ago _acf-field-accordion.js 1 year ago _acf-field-button-group.js 1 year ago _acf-field-checkbox.js 1 year ago _acf-field-color-picker.js 1 year ago _acf-field-date-picker.js 1 year ago _acf-field-date-time-picker.js 1 year ago _acf-field-file.js 1 year ago _acf-field-google-map.js 1 year ago _acf-field-icon-picker.js 1 year ago _acf-field-image.js 1 year ago _acf-field-link.js 1 year ago _acf-field-oembed.js 1 year ago _acf-field-page-link.js 1 year ago _acf-field-post-object.js 1 year ago _acf-field-radio.js 1 year ago _acf-field-range.js 1 year ago _acf-field-relationship.js 1 year ago _acf-field-select.js 1 year ago _acf-field-tab.js 1 year ago _acf-field-taxonomy.js 1 year ago _acf-field-time-picker.js 1 year ago _acf-field-true-false.js 1 year ago _acf-field-url.js 1 year ago _acf-field-user.js 1 year ago _acf-field-wysiwyg.js 1 year ago _acf-field.js 1 year ago _acf-fields.js 1 year ago _acf-helpers.js 1 year ago _acf-hooks.js 1 year ago _acf-internal-post-type.js 1 year ago _acf-media.js 1 year ago _acf-modal.js 1 year ago _acf-model.js 1 year ago _acf-notice.js 1 year ago _acf-panel.js 1 year ago _acf-popup.js 1 year ago _acf-postbox.js 1 year ago _acf-screen.js 1 year ago _acf-select2.js 1 year ago _acf-tinymce.js 1 year ago _acf-tooltip.js 1 year ago _acf-unload.js 1 year ago _acf-validation.js 1 year ago _acf.js 1 year ago _browse-fields-modal.js 1 year ago _field-group-compatibility.js 1 year ago _field-group-conditions.js 1 year ago _field-group-field.js 1 year ago _field-group-fields.js 1 year ago _field-group-locations.js 1 year ago _field-group-settings.js 1 year ago _field-group.js 1 year ago acf-escaped-html-notice.js 1 year ago acf-field-group.js 1 year ago acf-input.js 1 year ago acf-internal-post-type.js 1 year ago acf.js 1 year ago
_field-group-field.js
1066 lines
1 ( function ( $, undefined ) {
2 acf.FieldObject = acf.Model.extend( {
3 // class used to avoid nested event triggers
4 eventScope: '.acf-field-object',
5
6 // variable for field type select2
7 fieldTypeSelect2: false,
8
9 // events
10 events: {
11 'click .copyable': 'onClickCopy',
12 'click .handle': 'onClickEdit',
13 'click .close-field': 'onClickEdit',
14 'click a[data-key="acf_field_settings_tabs"]': 'onChangeSettingsTab',
15 'click .delete-field': 'onClickDelete',
16 'click .duplicate-field': 'duplicate',
17 'click .move-field': 'move',
18 'click .browse-fields': 'browseFields',
19
20 'focus .edit-field': 'onFocusEdit',
21 'blur .edit-field, .row-options a': 'onBlurEdit',
22
23 'change .field-type': 'onChangeType',
24 'change .field-required': 'onChangeRequired',
25 'blur .field-label': 'onChangeLabel',
26 'blur .field-name': 'onChangeName',
27
28 change: 'onChange',
29 changed: 'onChanged',
30 },
31
32 // data
33 data: {
34 // Similar to ID, but used for HTML puposes.
35 // It is possbile for a new field to have an ID of 0, but an id of 'field_123' */
36 id: 0,
37
38 // The field key ('field_123')
39 key: '',
40
41 // The field type (text, image, etc)
42 type: '',
43
44 // The $post->ID of this field
45 //ID: 0,
46
47 // The field's parent
48 //parent: 0,
49
50 // The menu order
51 //menu_order: 0
52 },
53
54 setup: function ( $field ) {
55 // set $el
56 this.$el = $field;
57
58 // inherit $field data (id, key, type)
59 this.inherit( $field );
60
61 // load additional props
62 // - this won't trigger 'changed'
63 this.prop( 'ID' );
64 this.prop( 'parent' );
65 this.prop( 'menu_order' );
66 },
67
68 $input: function ( name ) {
69 return $( '#' + this.getInputId() + '-' + name );
70 },
71
72 $meta: function () {
73 return this.$( '.meta:first' );
74 },
75
76 $handle: function () {
77 return this.$( '.handle:first' );
78 },
79
80 $settings: function () {
81 return this.$( '.settings:first' );
82 },
83
84 $setting: function ( name ) {
85 return this.$( '.acf-field-settings:first .acf-field-setting-' + name );
86 },
87
88 $fieldTypeSelect: function () {
89 return this.$( '.field-type' );
90 },
91
92 $fieldLabel: function () {
93 return this.$( '.field-label' );
94 },
95
96 getParent: function () {
97 return acf.getFieldObjects( { child: this.$el, limit: 1 } ).pop();
98 },
99
100 getParents: function () {
101 return acf.getFieldObjects( { child: this.$el } );
102 },
103
104 getFields: function () {
105 return acf.getFieldObjects( { parent: this.$el } );
106 },
107
108 getInputName: function () {
109 return 'acf_fields[' + this.get( 'id' ) + ']';
110 },
111
112 getInputId: function () {
113 return 'acf_fields-' + this.get( 'id' );
114 },
115
116 newInput: function ( name, value ) {
117 // vars
118 var inputId = this.getInputId();
119 var inputName = this.getInputName();
120
121 // append name
122 if ( name ) {
123 inputId += '-' + name;
124 inputName += '[' + name + ']';
125 }
126
127 // create input (avoid HTML + JSON value issues)
128 var $input = $( '<input />' ).attr( {
129 id: inputId,
130 name: inputName,
131 value: value,
132 } );
133 this.$( '> .meta' ).append( $input );
134
135 // return
136 return $input;
137 },
138
139 getProp: function ( name ) {
140 // check data
141 if ( this.has( name ) ) {
142 return this.get( name );
143 }
144
145 // get input value
146 var $input = this.$input( name );
147 var value = $input.length ? $input.val() : null;
148
149 // set data silently (cache)
150 this.set( name, value, true );
151
152 // return
153 return value;
154 },
155
156 setProp: function ( name, value ) {
157 // get input
158 var $input = this.$input( name );
159 var prevVal = $input.val();
160
161 // create if new
162 if ( ! $input.length ) {
163 $input = this.newInput( name, value );
164 }
165
166 // remove
167 if ( value === null ) {
168 $input.remove();
169
170 // update
171 } else {
172 $input.val( value );
173 }
174
175 //console.log('setProp', name, value, this);
176
177 // set data silently (cache)
178 if ( ! this.has( name ) ) {
179 //console.log('setting silently');
180 this.set( name, value, true );
181
182 // set data allowing 'change' event to fire
183 } else {
184 //console.log('setting loudly!');
185 this.set( name, value );
186 }
187
188 // return
189 return this;
190 },
191
192 prop: function ( name, value ) {
193 if ( value !== undefined ) {
194 return this.setProp( name, value );
195 } else {
196 return this.getProp( name );
197 }
198 },
199
200 props: function ( props ) {
201 Object.keys( props ).map( function ( key ) {
202 this.setProp( key, props[ key ] );
203 }, this );
204 },
205
206 getLabel: function () {
207 // get label with empty default
208 var label = this.prop( 'label' );
209 if ( label === '' ) {
210 label = acf.__( '(no label)' );
211 }
212
213 // return
214 return label;
215 },
216
217 getName: function () {
218 return this.prop( 'name' );
219 },
220
221 getType: function () {
222 return this.prop( 'type' );
223 },
224
225 getTypeLabel: function () {
226 var type = this.prop( 'type' );
227 var types = acf.get( 'fieldTypes' );
228 return types[ type ] ? types[ type ].label : type;
229 },
230
231 getKey: function () {
232 return this.prop( 'key' );
233 },
234
235 initialize: function () {
236 this.checkCopyable();
237 },
238
239 makeCopyable: function ( text ) {
240 if ( ! navigator.clipboard ) return '<span class="copyable copy-unsupported">' + text + '</span>';
241 return '<span class="copyable">' + text + '</span>';
242 },
243
244 checkCopyable: function () {
245 if ( ! navigator.clipboard ) {
246 this.$el.find( '.copyable' ).addClass( 'copy-unsupported' );
247 }
248 },
249
250 initializeFieldTypeSelect2: function () {
251 if ( this.fieldTypeSelect2 ) return;
252
253 // Support disabling via filter.
254 if ( this.$fieldTypeSelect().hasClass( 'disable-select2' ) ) return;
255
256 // Check for a full modern version of select2, bail loading if not found with a console warning.
257 try {
258 $.fn.select2.amd.require( 'select2/compat/dropdownCss' );
259 } catch ( err ) {
260 console.warn(
261 'ACF was not able to load the full version of select2 due to a conflicting version provided by another plugin or theme taking precedence. Select2 fields may not work as expected.'
262 );
263 return;
264 }
265
266 this.fieldTypeSelect2 = acf.newSelect2( this.$fieldTypeSelect(), {
267 field: false,
268 ajax: false,
269 multiple: false,
270 allowNull: false,
271 suppressFilters: true,
272 dropdownCssClass: 'field-type-select-results',
273 templateResult: function ( selection ) {
274 if ( selection.loading || ( selection.element && selection.element.nodeName === 'OPTGROUP' ) ) {
275 var $selection = $( '<span class="acf-selection"></span>' );
276 $selection.html( acf.strEscape( selection.text ) );
277 } else {
278 var $selection = $(
279 '<i class="field-type-icon field-type-icon-' +
280 selection.id.replaceAll( '_', '-' ) +
281 '"></i><span class="acf-selection has-icon">' +
282 acf.strEscape( selection.text ) +
283 '</span>'
284 );
285 }
286 $selection.data( 'element', selection.element );
287 return $selection;
288 },
289 templateSelection: function ( selection ) {
290 var $selection = $(
291 '<i class="field-type-icon field-type-icon-' +
292 selection.id.replaceAll( '_', '-' ) +
293 '"></i><span class="acf-selection has-icon">' +
294 acf.strEscape( selection.text ) +
295 '</span>'
296 );
297 $selection.data( 'element', selection.element );
298 return $selection;
299 },
300 } );
301
302 this.fieldTypeSelect2.on( 'select2:open', function () {
303 $( '.field-type-select-results input.select2-search__field' ).attr(
304 'placeholder',
305 acf.__( 'Type to search...' )
306 );
307 } );
308
309 this.fieldTypeSelect2.on( 'change', function ( e ) {
310 $( e.target ).parents( 'ul:first' ).find( 'button.browse-fields' ).prop( 'disabled', true );
311 } );
312
313 // When typing happens on the li element above the select2.
314 this.fieldTypeSelect2.$el
315 .parent()
316 .on( 'keydown', '.select2-selection.select2-selection--single', this.onKeyDownSelect );
317 },
318
319 addProFields: function () {
320 // Don't run if on pro.
321 if ( acf.get( 'is_pro' ) ) {
322 return;
323 }
324
325 // Make sure we haven't appended these fields before.
326 var $fieldTypeSelect = this.$fieldTypeSelect();
327 if ( $fieldTypeSelect.hasClass( 'acf-free-field-type' ) ) return;
328
329 // Loop over each pro field type and append it to the select.
330 const PROFieldTypes = acf.get( 'PROFieldTypes' );
331 if ( typeof PROFieldTypes !== 'object' ) return;
332
333 const $layoutGroup = $fieldTypeSelect.find( 'optgroup option[value="group"]' ).parent();
334
335 const $contentGroup = $fieldTypeSelect.find( 'optgroup option[value="image"]' ).parent();
336
337 for ( const [ name, field ] of Object.entries( PROFieldTypes ) ) {
338 const $useGroup = field.category === 'content' ? $contentGroup : $layoutGroup;
339 const $existing = $useGroup.children( '[value="' + name + '"]' );
340 const label = `${ acf.strEscape( field.label ) } (${ acf.strEscape( acf.__( 'PRO Only' ) ) })`;
341
342 if ( $existing.length ) {
343 // Already added by pro, update existing option.
344 $existing.text( label );
345
346 // Don't disable if already selected (prevents re-save from overriding field type).
347 if ( $fieldTypeSelect.val() !== name ) {
348 $existing.attr( 'disabled', 'disabled' );
349 }
350 } else {
351 // Append new disabled option.
352 $useGroup.append( `<option value="null" disabled="disabled">${ label }</option>` );
353 }
354 }
355
356 $fieldTypeSelect.addClass( 'acf-free-field-type' );
357 },
358
359 render: function () {
360 // vars
361 var $handle = this.$( '.handle:first' );
362 var menu_order = this.prop( 'menu_order' );
363 var label = this.getLabel();
364 var name = this.prop( 'name' );
365 var type = this.getTypeLabel();
366 var key = this.prop( 'key' );
367 var required = this.$input( 'required' ).prop( 'checked' );
368
369 // update menu order
370 $handle.find( '.acf-icon' ).html( parseInt( menu_order ) + 1 );
371
372 // update required
373 if ( required ) {
374 label += ' <span class="acf-required">*</span>';
375 }
376
377 // update label
378 $handle.find( '.li-field-label strong a' ).html( label );
379
380 // update name
381 $handle.find( '.li-field-name' ).html( this.makeCopyable( acf.strSanitize( name ) ) );
382
383 // update type
384 const iconName = acf.strSlugify( this.getType() );
385 $handle.find( '.field-type-label' ).text( ' ' + type );
386 $handle
387 .find( '.field-type-icon' )
388 .removeClass()
389 .addClass( 'field-type-icon field-type-icon-' + iconName );
390
391 // update key
392 $handle.find( '.li-field-key' ).html( this.makeCopyable( key ) );
393
394 // action for 3rd party customization
395 acf.doAction( 'render_field_object', this );
396 },
397
398 refresh: function () {
399 acf.doAction( 'refresh_field_object', this );
400 },
401
402 isOpen: function () {
403 return this.$el.hasClass( 'open' );
404 },
405
406 onClickCopy: function ( e ) {
407 e.stopPropagation();
408 if ( ! navigator.clipboard || $( e.target ).is( 'input' ) ) return;
409
410 // Find the value to copy depending on input or text elements.
411 let copyValue;
412 if ( $( e.target ).hasClass( 'acf-input-wrap' ) ) {
413 copyValue = $( e.target ).find( 'input' ).first().val();
414 } else {
415 copyValue = $( e.target ).text().trim();
416 }
417
418 navigator.clipboard.writeText( copyValue ).then( () => {
419 $( e.target ).closest( '.copyable' ).addClass( 'copied' );
420 setTimeout( function () {
421 $( e.target ).closest( '.copyable' ).removeClass( 'copied' );
422 }, 2000 );
423 } );
424 },
425
426 onClickEdit: function ( e ) {
427 const $target = $( e.target );
428
429 if ( $target.parent().hasClass( 'row-options' ) && ! $target.hasClass( 'edit-field' ) ) {
430 return;
431 }
432
433 this.isOpen() ? this.close() : this.open();
434 },
435
436 onChangeSettingsTab: function () {
437 const $settings = this.$el.children( '.settings' );
438 acf.doAction( 'show', $settings );
439 },
440
441 /**
442 * Adds 'active' class to row options nearest to the target.
443 */
444 onFocusEdit: function ( e ) {
445 var $rowOptions = $( e.target ).closest( 'li' ).find( '.row-options' );
446 $rowOptions.addClass( 'active' );
447 },
448
449 /**
450 * Removes 'active' class from row options if links in same row options area are no longer in focus.
451 */
452 onBlurEdit: function ( e ) {
453 var focusDelayMilliseconds = 50;
454 var $rowOptionsBlurElement = $( e.target ).closest( 'li' ).find( '.row-options' );
455
456 // Timeout so that `activeElement` gives the new element in focus instead of the body.
457 setTimeout( function () {
458 var $rowOptionsFocusElement = $( document.activeElement ).closest( 'li' ).find( '.row-options' );
459 if ( ! $rowOptionsBlurElement.is( $rowOptionsFocusElement ) ) {
460 $rowOptionsBlurElement.removeClass( 'active' );
461 }
462 }, focusDelayMilliseconds );
463 },
464
465 open: function () {
466 // vars
467 var $settings = this.$el.children( '.settings' );
468
469 // initialise field type select
470 this.addProFields();
471 this.initializeFieldTypeSelect2();
472
473 // action (open)
474 acf.doAction( 'open_field_object', this );
475 this.trigger( 'openFieldObject' );
476
477 // action (show)
478 acf.doAction( 'show', $settings );
479
480 this.hideEmptyTabs();
481
482 // open
483 $settings.slideDown();
484 this.$el.addClass( 'open' );
485 },
486
487 onKeyDownSelect: function ( e ) {
488 // Omit events from special keys.
489 if (
490 ! (
491 ( e.which >= 186 && e.which <= 222 ) || // punctuation and special characters
492 [
493 8, 9, 13, 16, 17, 18, 19, 20, 27, 32, 33, 34, 35, 36, 37, 38, 39, 40, 45, 46, 91, 92, 93, 144,
494 145,
495 ].includes( e.which ) || // Special keys
496 ( e.which >= 112 && e.which <= 123 )
497 )
498 ) {
499 // Function keys
500 $( this ).closest( '.select2-container' ).siblings( 'select:enabled' ).select2( 'open' );
501 return;
502 }
503 },
504
505 close: function () {
506 // vars
507 var $settings = this.$el.children( '.settings' );
508
509 // close
510 $settings.slideUp();
511 this.$el.removeClass( 'open' );
512
513 // action (close)
514 acf.doAction( 'close_field_object', this );
515 this.trigger( 'closeFieldObject' );
516
517 // action (hide)
518 acf.doAction( 'hide', $settings );
519 },
520
521 serialize: function () {
522 return acf.serialize( this.$el, this.getInputName() );
523 },
524
525 save: function ( type ) {
526 // defaults
527 type = type || 'settings'; // meta, settings
528
529 // vars
530 var save = this.getProp( 'save' );
531
532 // bail if already saving settings
533 if ( save === 'settings' ) {
534 return;
535 }
536
537 // prop
538 this.setProp( 'save', type );
539
540 // debug
541 this.$el.attr( 'data-save', type );
542
543 // action
544 acf.doAction( 'save_field_object', this, type );
545 },
546
547 submit: function () {
548 // vars
549 var inputName = this.getInputName();
550 var save = this.get( 'save' );
551
552 // close
553 if ( this.isOpen() ) {
554 this.close();
555 }
556
557 // allow all inputs to save
558 if ( save == 'settings' ) {
559 // do nothing
560 // allow only meta inputs to save
561 } else if ( save == 'meta' ) {
562 this.$( '> .settings [name^="' + inputName + '"]' ).remove();
563
564 // prevent all inputs from saving
565 } else {
566 this.$( '[name^="' + inputName + '"]' ).remove();
567 }
568
569 // action
570 acf.doAction( 'submit_field_object', this );
571 },
572
573 onChange: function ( e, $el ) {
574 // save settings
575 this.save();
576
577 // action for 3rd party customization
578 acf.doAction( 'change_field_object', this );
579 },
580
581 onChanged: function ( e, $el, name, value ) {
582 if ( this.getType() === $el.attr( 'data-type' ) ) {
583 $( 'button.acf-btn.browse-fields' ).prop( 'disabled', false );
584 }
585
586 // ignore 'save'
587 if ( name == 'save' ) {
588 return;
589 }
590
591 // save meta
592 if ( [ 'menu_order', 'parent' ].indexOf( name ) > -1 ) {
593 this.save( 'meta' );
594
595 // save field
596 } else {
597 this.save();
598 }
599
600 // render
601 if ( [ 'menu_order', 'label', 'required', 'name', 'type', 'key' ].indexOf( name ) > -1 ) {
602 this.render();
603 }
604
605 // action for 3rd party customization
606 acf.doAction( 'change_field_object_' + name, this, value );
607 },
608
609 onChangeLabel: function ( e, $el ) {
610 // set
611 const label = $el.val();
612 const safeLabel = acf.encode( label );
613 this.set( 'label', safeLabel );
614
615 // render name
616 if ( this.prop( 'name' ) == '' ) {
617 var name = acf.applyFilters( 'generate_field_object_name', acf.strSanitize( label ), this );
618 this.prop( 'name', name );
619 }
620 },
621
622 onChangeName: function ( e, $el ) {
623 const sanitizedName = acf.strSanitize( $el.val(), false );
624
625 $el.val( sanitizedName );
626 this.set( 'name', sanitizedName );
627
628 if ( sanitizedName.startsWith( 'field_' ) ) {
629 alert( acf.__( 'The string "field_" may not be used at the start of a field name' ) );
630 }
631 },
632
633 onChangeRequired: function ( e, $el ) {
634 // set
635 var required = $el.prop( 'checked' ) ? 1 : 0;
636 this.set( 'required', required );
637 },
638
639 delete: function ( args ) {
640 // defaults
641 args = acf.parseArgs( args, {
642 animate: true,
643 } );
644
645 // add to remove list
646 var id = this.prop( 'ID' );
647
648 if ( id ) {
649 var $input = $( '#_acf_delete_fields' );
650 var newVal = $input.val() + '|' + id;
651 $input.val( newVal );
652 }
653
654 // action
655 acf.doAction( 'delete_field_object', this );
656
657 // animate
658 if ( args.animate ) {
659 this.removeAnimate();
660 } else {
661 this.remove();
662 }
663 },
664
665 onClickDelete: function ( e, $el ) {
666 // Bypass confirmation when holding down "shift" key.
667 if ( e.shiftKey ) {
668 return this.delete();
669 }
670
671 // add class
672 this.$el.addClass( '-hover' );
673
674 // add tooltip
675 var tooltip = acf.newTooltip( {
676 confirmRemove: true,
677 target: $el,
678 context: this,
679 confirm: function () {
680 this.delete();
681 },
682 cancel: function () {
683 this.$el.removeClass( '-hover' );
684 },
685 } );
686 },
687
688 removeAnimate: function () {
689 // vars
690 var field = this;
691 var $list = this.$el.parent();
692 var $fields = acf.findFieldObjects( {
693 sibling: this.$el,
694 } );
695
696 // remove
697 acf.remove( {
698 target: this.$el,
699 endHeight: $fields.length ? 0 : 50,
700 complete: function () {
701 field.remove();
702 acf.doAction( 'removed_field_object', field, $list );
703 },
704 } );
705
706 // action
707 acf.doAction( 'remove_field_object', field, $list );
708 },
709
710 duplicate: function () {
711 // vars
712 var newKey = acf.uniqid( 'field_' );
713
714 // duplicate
715 var $newField = acf.duplicate( {
716 target: this.$el,
717 search: this.get( 'id' ),
718 replace: newKey,
719 } );
720
721 // set new key
722 $newField.attr( 'data-key', newKey );
723
724 // get instance
725 var newField = acf.getFieldObject( $newField );
726
727 // update newField label / name
728 var label = newField.prop( 'label' );
729 var name = newField.prop( 'name' );
730 var end = name.split( '_' ).pop();
731 var copy = acf.__( 'copy' );
732
733 // increase suffix "1"
734 if ( acf.isNumeric( end ) ) {
735 var i = end * 1 + 1;
736 label = label.replace( end, i );
737 name = name.replace( end, i );
738
739 // increase suffix "(copy1)"
740 } else if ( end.indexOf( copy ) === 0 ) {
741 var i = end.replace( copy, '' ) * 1;
742 i = i ? i + 1 : 2;
743
744 // replace
745 label = label.replace( end, copy + i );
746 name = name.replace( end, copy + i );
747
748 // add default "(copy)"
749 } else {
750 label += ' (' + copy + ')';
751 name += '_' + copy;
752 }
753
754 newField.prop( 'ID', 0 );
755 newField.prop( 'label', label );
756 newField.prop( 'name', name );
757 newField.prop( 'key', newKey );
758
759 // close the current field if it's open.
760 if ( this.isOpen() ) {
761 this.close();
762 }
763
764 // open the new field and initialise correctly.
765 newField.open();
766
767 // focus label
768 var $label = newField.$setting( 'label input' );
769 setTimeout( function () {
770 $label.trigger( 'focus' );
771 }, 251 );
772
773 // action
774 acf.doAction( 'duplicate_field_object', this, newField );
775 acf.doAction( 'append_field_object', newField );
776 },
777
778 wipe: function () {
779 // vars
780 var prevId = this.get( 'id' );
781 var prevKey = this.get( 'key' );
782 var newKey = acf.uniqid( 'field_' );
783
784 // rename
785 acf.rename( {
786 target: this.$el,
787 search: prevId,
788 replace: newKey,
789 } );
790
791 // data
792 this.set( 'id', newKey );
793 this.set( 'prevId', prevId );
794 this.set( 'prevKey', prevKey );
795
796 // props
797 this.prop( 'key', newKey );
798 this.prop( 'ID', 0 );
799
800 // attr
801 this.$el.attr( 'data-key', newKey );
802 this.$el.attr( 'data-id', newKey );
803
804 // action
805 acf.doAction( 'wipe_field_object', this );
806 },
807
808 move: function () {
809 // helper
810 var hasChanged = function ( field ) {
811 return field.get( 'save' ) == 'settings';
812 };
813
814 // vars
815 var changed = hasChanged( this );
816
817 // has sub fields changed
818 if ( ! changed ) {
819 acf.getFieldObjects( {
820 parent: this.$el,
821 } ).map( function ( field ) {
822 changed = hasChanged( field ) || field.changed;
823 } );
824 }
825
826 // bail early if changed
827 if ( changed ) {
828 alert( acf.__( 'This field cannot be moved until its changes have been saved' ) );
829 return;
830 }
831
832 // step 1.
833 var id = this.prop( 'ID' );
834 var field = this;
835 var popup = false;
836 var step1 = function () {
837 // popup
838 popup = acf.newPopup( {
839 title: acf.__( 'Move Custom Field' ),
840 loading: true,
841 width: '300px',
842 openedBy: field.$el.find( '.move-field' ),
843 } );
844
845 // ajax
846 var ajaxData = {
847 action: 'acf/field_group/move_field',
848 field_id: id,
849 };
850
851 // get HTML
852 $.ajax( {
853 url: acf.get( 'ajaxurl' ),
854 data: acf.prepareForAjax( ajaxData ),
855 type: 'post',
856 dataType: 'html',
857 success: step2,
858 } );
859 };
860
861 var step2 = function ( html ) {
862 // update popup
863 popup.loading( false );
864 popup.content( html );
865
866 // submit form
867 popup.on( 'submit', 'form', step3 );
868 };
869
870 var step3 = function ( e, $el ) {
871 // prevent
872 e.preventDefault();
873
874 // disable
875 acf.startButtonLoading( popup.$( '.button' ) );
876
877 // ajax
878 var ajaxData = {
879 action: 'acf/field_group/move_field',
880 field_id: id,
881 field_group_id: popup.$( 'select' ).val(),
882 };
883
884 // get HTML
885 $.ajax( {
886 url: acf.get( 'ajaxurl' ),
887 data: acf.prepareForAjax( ajaxData ),
888 type: 'post',
889 dataType: 'html',
890 success: step4,
891 } );
892 };
893
894 var step4 = function ( html ) {
895 popup.content( html );
896
897 if ( wp.a11y && wp.a11y.speak && acf.__ ) {
898 wp.a11y.speak( acf.__( 'Field moved to other group' ), 'polite' );
899 }
900
901 popup.$( '.acf-close-popup' ).focus();
902
903 field.removeAnimate();
904 };
905
906 // start
907 step1();
908 },
909
910 browseFields: function ( e, $el ) {
911 e.preventDefault();
912
913 const modal = acf.newBrowseFieldsModal( {
914 openedBy: this,
915 } );
916 },
917
918 onChangeType: function ( e, $el ) {
919 // clea previous timout
920 if ( this.changeTimeout ) {
921 clearTimeout( this.changeTimeout );
922 }
923
924 // set new timeout
925 // - prevents changing type multiple times whilst user types in newType
926 this.changeTimeout = this.setTimeout( function () {
927 this.changeType( $el.val() );
928 }, 300 );
929 },
930
931 changeType: function ( newType ) {
932 var prevType = this.prop( 'type' );
933 var prevClass = acf.strSlugify( 'acf-field-object-' + prevType );
934 var newClass = acf.strSlugify( 'acf-field-object-' + newType );
935
936 // Update props.
937 this.$el.removeClass( prevClass ).addClass( newClass );
938 this.$el.attr( 'data-type', newType );
939 this.$el.data( 'type', newType );
940
941 // Abort XHR if this field is already loading AJAX data.
942 if ( this.has( 'xhr' ) ) {
943 this.get( 'xhr' ).abort();
944 }
945
946 // Store old settings so they can be reused later.
947 const $oldSettings = {};
948
949 this.$el
950 .find( '.acf-field-settings:first > .acf-field-settings-main > .acf-field-type-settings' )
951 .each( function () {
952 let tab = $( this ).data( 'parent-tab' );
953 let $tabSettings = $( this ).children().removeData();
954
955 $oldSettings[ tab ] = $tabSettings;
956
957 $tabSettings.detach();
958 } );
959
960 this.set( 'settings-' + prevType, $oldSettings );
961
962 // Show the settings if we already have them cached.
963 if ( this.has( 'settings-' + newType ) ) {
964 let $newSettings = this.get( 'settings-' + newType );
965
966 this.showFieldTypeSettings( $newSettings );
967 this.set( 'type', newType );
968 return;
969 }
970
971 // Add loading spinner.
972 const $loading = $(
973 '<div class="acf-field"><div class="acf-input"><div class="acf-loading"></div></div></div>'
974 );
975 this.$el.find( '.acf-field-settings-main-general .acf-field-type-settings' ).before( $loading );
976
977 const ajaxData = {
978 action: 'acf/field_group/render_field_settings',
979 field: this.serialize(),
980 prefix: this.getInputName(),
981 };
982
983 // Get the settings for this field type over AJAX.
984 var xhr = $.ajax( {
985 url: acf.get( 'ajaxurl' ),
986 data: acf.prepareForAjax( ajaxData ),
987 type: 'post',
988 dataType: 'json',
989 context: this,
990 success: function ( response ) {
991 if ( ! acf.isAjaxSuccess( response ) ) {
992 return;
993 }
994
995 this.showFieldTypeSettings( response.data );
996 },
997 complete: function () {
998 // also triggered by xhr.abort();
999 $loading.remove();
1000 this.set( 'type', newType );
1001 //this.refresh();
1002 },
1003 } );
1004
1005 // set
1006 this.set( 'xhr', xhr );
1007 },
1008
1009 showFieldTypeSettings: function ( settings ) {
1010 if ( 'object' !== typeof settings ) {
1011 return;
1012 }
1013
1014 const self = this;
1015 const tabs = Object.keys( settings );
1016
1017 tabs.forEach( ( tab ) => {
1018 const $tab = self.$el.find(
1019 '.acf-field-settings-main-' + tab.replace( '_', '-' ) + ' .acf-field-type-settings'
1020 );
1021 let tabContent = '';
1022
1023 if ( [ 'object', 'string' ].includes( typeof settings[ tab ] ) ) {
1024 tabContent = settings[ tab ];
1025 }
1026
1027 $tab.prepend( tabContent );
1028 acf.doAction( 'append', $tab );
1029 } );
1030
1031 this.hideEmptyTabs();
1032 },
1033
1034 updateParent: function () {
1035 // vars
1036 var ID = acf.get( 'post_id' );
1037
1038 // check parent
1039 var parent = this.getParent();
1040 if ( parent ) {
1041 ID = parseInt( parent.prop( 'ID' ) ) || parent.prop( 'key' );
1042 }
1043
1044 // update
1045 this.prop( 'parent', ID );
1046 },
1047
1048 hideEmptyTabs: function () {
1049 const $settings = this.$settings();
1050 const $tabs = $settings.find( '.acf-field-settings:first > .acf-field-settings-main' );
1051
1052 $tabs.each( function () {
1053 const $tabContent = $( this );
1054 const tabName = $tabContent.find( '.acf-field-type-settings:first' ).data( 'parentTab' );
1055 const $tabLink = $settings.find( '.acf-settings-type-' + tabName ).first();
1056
1057 if ( $.trim( $tabContent.text() ) === '' ) {
1058 $tabLink.hide();
1059 } else if ( $tabLink.is( ':hidden' ) ) {
1060 $tabLink.show();
1061 }
1062 } );
1063 },
1064 } );
1065 } )( jQuery );
1066