PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / 6.4.1-beta5
Secure Custom Fields v6.4.1-beta5
6.9.5 6.9.4 6.9.3 6.9.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 / pro / _acf-field-flexible-content.js
secure-custom-fields / assets / src / js / pro Last commit date
_acf-blocks.js 1 year ago _acf-field-flexible-content.js 1 year ago _acf-field-gallery.js 1 year ago _acf-field-repeater.js 1 year ago _acf-jsx-names.js 1 year ago _acf-setting-clone.js 1 year ago _acf-setting-flexible-content.js 1 year ago _acf-setting-repeater.js 1 year ago _acf-ui-options-page.js 1 year ago acf-pro-blocks.js 1 year ago acf-pro-field-group.js 1 year ago acf-pro-input.js 1 year ago acf-pro-ui-options-page.js 1 year ago
_acf-field-flexible-content.js
694 lines
1 ( function ( $ ) {
2 var Field = acf.Field.extend( {
3 type: 'flexible_content',
4 wait: '',
5
6 events: {
7 'click [data-name="add-layout"]': 'onClickAdd',
8 'click [data-name="duplicate-layout"]': 'onClickDuplicate',
9 'click [data-name="remove-layout"]': 'onClickRemove',
10 'click [data-name="collapse-layout"]': 'onClickCollapse',
11 showField: 'onShow',
12 unloadField: 'onUnload',
13 mouseover: 'onHover',
14 },
15
16 $control: function () {
17 return this.$( '.acf-flexible-content:first' );
18 },
19
20 $layoutsWrap: function () {
21 return this.$( '.acf-flexible-content:first > .values' );
22 },
23
24 $layouts: function () {
25 return this.$( '.acf-flexible-content:first > .values > .layout' );
26 },
27
28 $layout: function ( index ) {
29 return this.$(
30 '.acf-flexible-content:first > .values > .layout:eq(' +
31 index +
32 ')'
33 );
34 },
35
36 $clonesWrap: function () {
37 return this.$( '.acf-flexible-content:first > .clones' );
38 },
39
40 $clones: function () {
41 return this.$( '.acf-flexible-content:first > .clones > .layout' );
42 },
43
44 $clone: function ( name ) {
45 return this.$(
46 '.acf-flexible-content:first > .clones > .layout[data-layout="' +
47 name +
48 '"]'
49 );
50 },
51
52 $actions: function () {
53 return this.$( '.acf-actions:last' );
54 },
55
56 $button: function () {
57 return this.$( '.acf-actions:last .button' );
58 },
59
60 $popup: function () {
61 return this.$( '.tmpl-popup:last' );
62 },
63
64 getPopupHTML: function () {
65 var html = this.$popup().html();
66 var $html = $( html );
67 var self = this;
68
69 // modify popup
70 $html.find( '[data-layout]' ).each( function () {
71 var $a = $( this );
72 var min = $a.data( 'min' ) || 0;
73 var max = $a.data( 'max' ) || 0;
74 var name = $a.data( 'layout' ) || '';
75 var count = self.countLayouts( name );
76
77 // max
78 if ( max && count >= max ) {
79 $a.addClass( 'disabled' );
80 return;
81 }
82
83 // min
84 if ( min && count < min ) {
85 var required = min - count;
86 var title = acf.__(
87 '{required} {label} {identifier} required (min {min})'
88 );
89 var identifier = acf._n( 'layout', 'layouts', required );
90
91 // translate
92 title = title.replace( '{required}', required );
93 title = title.replace( '{label}', name ); // 5.5.0
94 title = title.replace( '{identifier}', identifier );
95 title = title.replace( '{min}', min );
96
97 // badge
98 $a.append(
99 '<span class="badge" title="' +
100 title +
101 '">' +
102 required +
103 '</span>'
104 );
105 }
106 } );
107
108 // update
109 html = $html.outerHTML();
110
111 return html;
112 },
113
114 getValue: function () {
115 return this.$layouts().length;
116 },
117
118 allowRemove: function () {
119 var min = parseInt( this.get( 'min' ) );
120 return ! min || min < this.val();
121 },
122
123 allowAdd: function () {
124 var max = parseInt( this.get( 'max' ) );
125 return ! max || max > this.val();
126 },
127
128 isFull: function () {
129 var max = parseInt( this.get( 'max' ) );
130 return max && this.val() >= max;
131 },
132
133 addSortable: function ( self ) {
134 // bail early if max 1 row
135 if ( this.get( 'max' ) == 1 ) {
136 return;
137 }
138
139 // add sortable
140 this.$layoutsWrap().sortable( {
141 items: '> .layout',
142 handle: '> .acf-fc-layout-handle',
143 forceHelperSize: true,
144 forcePlaceholderSize: true,
145 scroll: true,
146 stop: function ( event, ui ) {
147 self.render();
148 },
149 update: function ( event, ui ) {
150 self.$input().trigger( 'change' );
151 },
152 } );
153 },
154
155 addCollapsed: function () {
156 var indexes = preference.load( this.get( 'key' ) );
157
158 // bail early if no collapsed
159 if ( ! indexes ) {
160 return false;
161 }
162
163 // loop
164 this.$layouts().each( function ( i ) {
165 if ( indexes.indexOf( i ) > -1 ) {
166 $( this ).addClass( '-collapsed' );
167 }
168 } );
169 },
170
171 addUnscopedEvents: function ( self ) {
172 // invalidField
173 this.on( 'invalidField', '.layout', function ( e ) {
174 self.onInvalidField( e, $( this ) );
175 } );
176 },
177
178 initialize: function () {
179 // add unscoped events
180 this.addUnscopedEvents( this );
181
182 // add collapsed
183 this.addCollapsed();
184
185 // disable clone
186 acf.disable( this.$clonesWrap(), this.cid );
187
188 // render
189 this.render();
190 },
191
192 render: function () {
193 // update order number
194 this.$layouts().each( function ( i ) {
195 $( this )
196 .find( '.acf-fc-layout-order:first' )
197 .html( i + 1 );
198 } );
199
200 // empty
201 if ( this.val() == 0 ) {
202 this.$control().addClass( '-empty' );
203 } else {
204 this.$control().removeClass( '-empty' );
205 }
206
207 // max
208 if ( this.isFull() ) {
209 this.$button().addClass( 'disabled' );
210 } else {
211 this.$button().removeClass( 'disabled' );
212 }
213 },
214
215 onShow: function ( e, $el, context ) {
216 // get sub fields
217 var fields = acf.getFields( {
218 is: ':visible',
219 parent: this.$el,
220 } );
221
222 // trigger action
223 // - ignore context, no need to pass through 'conditional_logic'
224 // - this is just for fields like google_map to render itself
225 acf.doAction( 'show_fields', fields );
226 },
227
228 countLayouts: function ( name ) {
229 return this.$layouts().filter( function () {
230 return $( this ).data( 'layout' ) === name;
231 } ).length;
232 },
233
234 countLayoutsByName: function ( currentLayout ) {
235 const layoutMax = currentLayout.data( 'max' );
236 if ( ! layoutMax ) {
237 return true;
238 }
239 const name = currentLayout.data( 'layout' ) || '';
240 const count = this.countLayouts( name );
241
242 if( count >= layoutMax ) {
243 let text = acf.__(
244 'This field has a limit of {max} {label} {identifier}'
245 );
246 const identifier = acf._n( 'layout', 'layouts', layoutMax );
247 const layoutLabel = '"' + currentLayout.data( 'label' ) + '"';
248 text = text.replace( '{max}', layoutMax );
249 text = text.replace( '{label}', layoutLabel );
250 text = text.replace( '{identifier}', identifier );
251
252 this.showNotice( {
253 text: text,
254 type: 'warning',
255 } );
256
257 return false;
258 }
259
260 return true;
261 },
262
263 validateAdd: function () {
264 // return true if allowed
265 if ( this.allowAdd() ) {
266 return true;
267 }
268
269 var max = this.get( 'max' );
270 var text = acf.__(
271 'This field has a limit of {max} {label} {identifier}'
272 );
273 var identifier = acf._n( 'layout', 'layouts', max );
274
275 text = text.replace( '{max}', max );
276 text = text.replace( '{label}', '' );
277 text = text.replace( '{identifier}', identifier );
278
279 this.showNotice( {
280 text: text,
281 type: 'warning',
282 } );
283
284 return false;
285 },
286
287 onClickAdd: function ( e, $el ) {
288 // validate
289 if ( ! this.validateAdd() ) {
290 return false;
291 }
292
293 // within layout
294 var $layout = null;
295 if ( $el.hasClass( 'acf-icon' ) ) {
296 $layout = $el.closest( '.layout' );
297 $layout.addClass( '-hover' );
298 }
299
300 // new popup
301 var popup = new Popup( {
302 target: $el,
303 targetConfirm: false,
304 text: this.getPopupHTML(),
305 context: this,
306 confirm: function ( e, $el ) {
307 // check disabled
308 if ( $el.hasClass( 'disabled' ) ) {
309 return;
310 }
311
312 // add
313 this.add( {
314 layout: $el.data( 'layout' ),
315 before: $layout,
316 } );
317 },
318 cancel: function () {
319 if ( $layout ) {
320 $layout.removeClass( '-hover' );
321 }
322 },
323 } );
324
325 // add extra event
326 popup.on( 'click', '[data-layout]', 'onConfirm' );
327 },
328
329 add: function ( args ) {
330 // defaults
331 args = acf.parseArgs( args, {
332 layout: '',
333 before: false,
334 } );
335
336 // validate
337 if ( ! this.allowAdd() ) {
338 return false;
339 }
340
341 // add row
342 var $el = acf.duplicate( {
343 target: this.$clone( args.layout ),
344 append: this.proxy( function ( $el, $el2 ) {
345 // append
346 if ( args.before ) {
347 args.before.before( $el2 );
348 } else {
349 this.$layoutsWrap().append( $el2 );
350 }
351
352 // enable
353 acf.enable( $el2, this.cid );
354
355 // render
356 this.render();
357 } ),
358 } );
359
360 // trigger change for validation errors
361 this.$input().trigger( 'change' );
362
363 return $el;
364 },
365
366 onClickDuplicate: function ( e, $el ) {
367 var $layout = $el.closest( '.layout' );
368 // Validate each layout's max count.
369 if ( ! this.countLayoutsByName( $layout.first() ) ) {
370 return false;
371 }
372
373 // Validate with warning.
374 if ( ! this.validateAdd() ) {
375 return false;
376 }
377
378 // get layout and duplicate it.
379 this.duplicateLayout( $layout );
380 },
381
382 duplicateLayout: function ( $layout ) {
383 // Validate without warning.
384 if ( ! this.allowAdd() ) {
385 return false;
386 }
387
388 var fieldKey = this.get( 'key' );
389
390 // Duplicate layout.
391 var $el = acf.duplicate( {
392 target: $layout,
393
394 // Provide a custom renaming callback to avoid renaming parent row attributes.
395 rename: function ( name, value, search, replace ) {
396 // Rename id attributes from "field_1-search" to "field_1-replace".
397 if ( name === 'id' || name === 'for' ) {
398 return value.replace(
399 fieldKey + '-' + search,
400 fieldKey + '-' + replace
401 );
402
403 // Rename name and for attributes from "[field_1][search]" to "[field_1][replace]".
404 } else {
405 return value.replace(
406 fieldKey + '][' + search,
407 fieldKey + '][' + replace
408 );
409 }
410 },
411 before: function ( $el ) {
412 acf.doAction( 'unmount', $el );
413 },
414 after: function ( $el, $el2 ) {
415 acf.doAction( 'remount', $el );
416 },
417 } );
418
419 // trigger change for validation errors
420 this.$input().trigger( 'change' );
421
422 // Update order numbers.
423 this.render();
424
425 // Draw focus to layout.
426 acf.focusAttention( $el );
427
428 // Return new layout.
429 return $el;
430 },
431
432 validateRemove: function () {
433 // return true if allowed
434 if ( this.allowRemove() ) {
435 return true;
436 }
437
438 var min = this.get( 'min' );
439 var text = acf.__(
440 'This field requires at least {min} {label} {identifier}'
441 );
442 var identifier = acf._n( 'layout', 'layouts', min );
443
444 // replace
445 text = text.replace( '{min}', min );
446 text = text.replace( '{label}', '' );
447 text = text.replace( '{identifier}', identifier );
448
449 // add notice
450 this.showNotice( {
451 text: text,
452 type: 'warning',
453 } );
454
455 return false;
456 },
457
458 onClickRemove: function ( e, $el ) {
459 var $layout = $el.closest( '.layout' );
460
461 // Bypass confirmation when holding down "shift" key.
462 if ( e.shiftKey ) {
463 return this.removeLayout( $layout );
464 }
465
466 // add class
467 $layout.addClass( '-hover' );
468
469 // add tooltip
470 var tooltip = acf.newTooltip( {
471 confirmRemove: true,
472 target: $el,
473 context: this,
474 confirm: function () {
475 this.removeLayout( $layout );
476 },
477 cancel: function () {
478 $layout.removeClass( '-hover' );
479 },
480 } );
481 },
482
483 removeLayout: function ( $layout ) {
484 // reference
485 var self = this;
486
487 var endHeight = this.getValue() == 1 ? 60 : 0;
488
489 // remove
490 acf.remove( {
491 target: $layout,
492 endHeight: endHeight,
493 complete: function () {
494 // trigger change to allow attachment save
495 self.$input().trigger( 'change' );
496
497 // render
498 self.render();
499 },
500 } );
501 },
502
503 onClickCollapse: function ( e, $el ) {
504 var $layout = $el.closest( '.layout' );
505
506 // toggle
507 if ( this.isLayoutClosed( $layout ) ) {
508 this.openLayout( $layout );
509 } else {
510 this.closeLayout( $layout );
511 }
512 },
513
514 isLayoutClosed: function ( $layout ) {
515 return $layout.hasClass( '-collapsed' );
516 },
517
518 openLayout: function ( $layout ) {
519 $layout.removeClass( '-collapsed' );
520 acf.doAction( 'show', $layout, 'collapse' );
521 },
522
523 closeLayout: function ( $layout ) {
524 $layout.addClass( '-collapsed' );
525 acf.doAction( 'hide', $layout, 'collapse' );
526
527 // render
528 // - no change could happen if layout was already closed. Only render when closing
529 this.renderLayout( $layout );
530 },
531
532 renderLayout: function ( $layout ) {
533 var $input = $layout.children( 'input' );
534 var prefix = $input.attr( 'name' ).replace( '[acf_fc_layout]', '' );
535
536 // ajax data
537 var ajaxData = {
538 action: 'acf/fields/flexible_content/layout_title',
539 field_key: this.get( 'key' ),
540 i: $layout.index(),
541 layout: $layout.data( 'layout' ),
542 value: acf.serialize( $layout, prefix ),
543 };
544
545 // ajax
546 $.ajax( {
547 url: acf.get( 'ajaxurl' ),
548 data: acf.prepareForAjax( ajaxData ),
549 dataType: 'html',
550 type: 'post',
551 success: function ( html ) {
552 if ( html ) {
553 $layout
554 .children( '.acf-fc-layout-handle' )
555 .html( html );
556 }
557 },
558 } );
559 },
560
561 onUnload: function () {
562 var indexes = [];
563
564 // loop
565 this.$layouts().each( function ( i ) {
566 if ( $( this ).hasClass( '-collapsed' ) ) {
567 indexes.push( i );
568 }
569 } );
570
571 // allow null
572 indexes = indexes.length ? indexes : null;
573
574 // set
575 preference.save( this.get( 'key' ), indexes );
576 },
577
578 onInvalidField: function ( e, $layout ) {
579 // open if is collapsed
580 if ( this.isLayoutClosed( $layout ) ) {
581 this.openLayout( $layout );
582 }
583 },
584
585 onHover: function () {
586 // add sortable
587 this.addSortable( this );
588
589 // remove event
590 this.off( 'mouseover' );
591 },
592 } );
593
594 acf.registerFieldType( Field );
595
596 /**
597 * Popup
598 *
599 * description
600 *
601 * @date 7/4/18
602 * @since ACF 5.6.9
603 *
604 * @param type $var Description. Default.
605 * @return type Description.
606 */
607
608 var Popup = acf.models.TooltipConfirm.extend( {
609 events: {
610 'click [data-layout]': 'onConfirm',
611 'click [data-event="cancel"]': 'onCancel',
612 },
613
614 render: function () {
615 // set HTML
616 this.html( this.get( 'text' ) );
617
618 // add class
619 this.$el.addClass( 'acf-fc-popup' );
620 },
621 } );
622
623 /**
624 * conditions
625 *
626 * description
627 *
628 * @date 9/4/18
629 * @since ACF 5.6.9
630 *
631 * @param type $var Description. Default.
632 * @return type Description.
633 */
634
635 // register existing conditions
636 acf.registerConditionForFieldType( 'hasValue', 'flexible_content' );
637 acf.registerConditionForFieldType( 'hasNoValue', 'flexible_content' );
638 acf.registerConditionForFieldType( 'lessThan', 'flexible_content' );
639 acf.registerConditionForFieldType( 'greaterThan', 'flexible_content' );
640
641 // state
642 var preference = new acf.Model( {
643 name: 'this.collapsedLayouts',
644
645 key: function ( key, context ) {
646 var count = this.get( key + context ) || 0;
647
648 // update
649 count++;
650 this.set( key + context, count, true );
651
652 // modify fieldKey
653 if ( count > 1 ) {
654 key += '-' + count;
655 }
656
657 return key;
658 },
659
660 load: function ( key ) {
661 var key = this.key( key, 'load' );
662 var data = acf.getPreference( this.name );
663
664 if ( data && data[ key ] ) {
665 return data[ key ];
666 } else {
667 return false;
668 }
669 },
670
671 save: function ( key, value ) {
672 var key = this.key( key, 'save' );
673 var data = acf.getPreference( this.name ) || {};
674
675 // delete
676 if ( value === null ) {
677 delete data[ key ];
678
679 // append
680 } else {
681 data[ key ] = value;
682 }
683
684 // allow null
685 if ( $.isEmptyObject( data ) ) {
686 data = null;
687 }
688
689 // save
690 acf.setPreference( this.name, data );
691 },
692 } );
693 } )( jQuery );
694