PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / 6.4.0-beta2
Secure Custom Fields v6.4.0-beta2
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-repeater.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-repeater.js
966 lines
1 ( function ( $ ) {
2 var Field = acf.Field.extend( {
3 type: 'repeater',
4 wait: '',
5 page: 1,
6 nextRowNum: 0,
7
8 events: {
9 'click a[data-event="add-row"]': 'onClickAdd',
10 'click a[data-event="duplicate-row"]': 'onClickDuplicate',
11 'click a[data-event="remove-row"]': 'onClickRemove',
12 'click a[data-event="collapse-row"]': 'onClickCollapse',
13 'click a[data-event="first-page"]:not(.disabled)' : 'onClickFirstPage',
14 'click a[data-event="last-page"]:not(.disabled)' : 'onClickLastPage',
15 'click a[data-event="prev-page"]:not(.disabled)': 'onClickPrevPage',
16 'click a[data-event="next-page"]:not(.disabled)': 'onClickNextPage',
17 'change .current-page': 'onChangeCurrentPage',
18 'click .acf-order-input-wrap': 'onClickRowOrder',
19 'blur .acf-order-input': 'onBlurRowOrder',
20 'change .acf-order-input': 'onChangeRowOrder',
21 'changed:total_rows': 'onChangeTotalRows',
22 showField: 'onShow',
23 unloadField: 'onUnload',
24 mouseover: 'onHover',
25 change: 'onChangeField',
26 },
27
28 $control: function () {
29 return this.$( '.acf-repeater:first' );
30 },
31
32 $table: function () {
33 return this.$( 'table:first' );
34 },
35
36 $tbody: function () {
37 return this.$( 'tbody:first' );
38 },
39
40 $rows: function () {
41 return this.$( 'tbody:first > tr' ).not( '.acf-clone, .acf-deleted' );
42 },
43
44 $row: function ( index ) {
45 return this.$( 'tbody:first > tr:eq(' + index + ')' );
46 },
47
48 $clone: function () {
49 return this.$( 'tbody:first > tr.acf-clone' );
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 $firstPageButton: function() {
61 return this.$( '.acf-tablenav:last .first-page' );
62 },
63
64 $prevPageButton: function() {
65 return this.$( '.acf-tablenav:last .prev-page' );
66 },
67
68 $nextPageButton: function() {
69 return this.$( '.acf-tablenav:last .next-page' );
70 },
71
72 $lastPageButton: function() {
73 return this.$( '.acf-tablenav:last .last-page' );
74 },
75
76 $pageInput: function() {
77 return this.$( '.current-page:last' );
78 },
79
80 totalPages: function() {
81 const totalPages = this.$( '.acf-total-pages:last' ).text();
82 return parseInt( totalPages );
83 },
84
85 getValue: function () {
86 return this.$rows().length;
87 },
88
89 allowRemove: function () {
90 let numRows = this.val();
91 let minRows = parseInt( this.get( 'min' ) );
92
93 if ( this.get( 'pagination' ) ) {
94 numRows = this.get( 'total_rows' );
95 }
96
97 return ! minRows || minRows < numRows;
98 },
99
100 allowAdd: function () {
101 let numRows = this.val();
102 let maxRows = parseInt( this.get( 'max' ) );
103
104 if ( this.get( 'pagination' ) ) {
105 numRows = this.get( 'total_rows' );
106 }
107
108 return ! maxRows || maxRows > numRows;
109 },
110
111 addSortable: function ( self ) {
112 // bail early if max 1 row
113 if ( this.get( 'max' ) == 1 ) {
114 return;
115 }
116
117 // Bail early if using pagination.
118 if ( this.get( 'pagination') ) {
119 return;
120 }
121
122 // add sortable
123 this.$tbody().sortable( {
124 items: '> tr',
125 handle: '> td.order',
126 forceHelperSize: true,
127 forcePlaceholderSize: true,
128 scroll: true,
129 stop: function ( event, ui ) {
130 self.render();
131 },
132 update: function ( event, ui ) {
133 self.$input().trigger( 'change' );
134 },
135 } );
136 },
137
138 addCollapsed: function () {
139 // vars
140 var indexes = preference.load( this.get( 'key' ) );
141
142 // bail early if no collapsed
143 if ( ! indexes ) {
144 return false;
145 }
146
147 // loop
148 this.$rows().each( function ( i ) {
149 if ( indexes.indexOf( i ) > -1 ) {
150 if ( $( this ).find( '.-collapsed-target' ).length ) {
151 $( this ).addClass( '-collapsed' );
152 }
153 }
154 } );
155 },
156
157 addUnscopedEvents: function ( self ) {
158 // invalidField
159 this.on( 'invalidField', '.acf-row', function ( e ) {
160 var $row = $( this );
161 if ( self.isCollapsed( $row ) ) {
162 self.expand( $row );
163 }
164 } );
165
166 // Listen for changes to fields, so we can persist them in the DOM.
167 if ( this.get( 'pagination' ) ) {
168 this.on( 'change', 'input, select, textarea', function ( e ) {
169 const $changed = $( e.currentTarget );
170 if ( ! $changed.hasClass( 'acf-order-input' ) && ! $changed.hasClass( 'acf-row-status' ) ) {
171 self.onChangeField( e, $( this ) );
172 }
173 } );
174 }
175
176 this.listenForSavedMetaBoxes();
177 },
178
179 initialize: function () {
180 // add unscoped events
181 this.addUnscopedEvents( this );
182
183 // add collapsed
184 this.addCollapsed();
185
186 // disable clone
187 acf.disable( this.$clone(), this.cid );
188
189 // Set up the next row number.
190 if ( this.get( 'pagination' ) ) {
191 this.nextRowNum = this.get( 'total_rows' );
192 }
193
194 // render
195 this.render();
196 },
197
198 render: function ( update_order_numbers = true ) {
199 // Update order number.
200 if ( update_order_numbers ) {
201 this.$rows().each( function ( i ) {
202 $( this )
203 .find( '> .order > span' )
204 .html( i + 1 );
205 } );
206 }
207
208 // Extract vars.
209 var $control = this.$control();
210 var $button = this.$button();
211
212 // empty
213 if ( this.val() == 0 ) {
214 $control.addClass( '-empty' );
215 } else {
216 $control.removeClass( '-empty' );
217 }
218
219 // Reached max rows.
220 if ( ! this.allowAdd() ) {
221 $control.addClass( '-max' );
222 $button.addClass( 'disabled' );
223 } else {
224 $control.removeClass( '-max' );
225 $button.removeClass( 'disabled' );
226 }
227
228 if ( this.get( 'pagination' ) ) {
229 this.maybeDisablePagination();
230 }
231
232 // Reached min rows (not used).
233 //if( !this.allowRemove() ) {
234 // $control.addClass('-min');
235 //} else {
236 // $control.removeClass('-min');
237 //}
238 },
239
240 listenForSavedMetaBoxes: function() {
241 if ( ! acf.isGutenbergPostEditor() || ! this.get( 'pagination' ) ) {
242 return;
243 }
244
245 let checkedMetaBoxes = true;
246 wp.data.subscribe( () => {
247 if ( wp.data.select( 'core/edit-post' ).isSavingMetaBoxes() ) {
248 checkedMetaBoxes = false;
249 } else {
250 if ( ! checkedMetaBoxes ) {
251 checkedMetaBoxes = true;
252 this.set( 'total_rows', 0, true );
253 this.ajaxLoadPage( true );
254 }
255 }
256 } );
257 },
258
259 incrementTotalRows: function() {
260 let totalRows = this.get( 'total_rows' );
261 this.set( 'total_rows', ++totalRows, true );
262 },
263
264 decrementTotalRows: function() {
265 let totalRows = this.get( 'total_rows' );
266 this.set( 'total_rows', --totalRows, true );
267 },
268
269 validateAdd: function () {
270 // return true if allowed
271 if ( this.allowAdd() ) {
272 return true;
273 }
274
275 // vars
276 var max = this.get( 'max' );
277 var text = acf.__( 'Maximum rows reached ({max} rows)' );
278
279 // replace
280 text = text.replace( '{max}', max );
281
282 // add notice
283 this.showNotice( {
284 text: text,
285 type: 'warning',
286 } );
287
288 // return
289 return false;
290 },
291
292 onClickAdd: function ( e, $el ) {
293 // validate
294 if ( ! this.validateAdd() ) {
295 return false;
296 }
297
298 // add above row
299 if ( $el.hasClass( 'acf-icon' ) ) {
300 this.add( {
301 before: $el.closest( '.acf-row' ),
302 } );
303
304 // default
305 } else {
306 this.add();
307 }
308 },
309
310 add: function ( args ) {
311 // validate
312 if ( ! this.allowAdd() ) {
313 return false;
314 }
315
316 // defaults
317 args = acf.parseArgs( args, {
318 before: false,
319 } );
320
321 // add row
322 var $el = acf.duplicate( {
323 target: this.$clone(),
324 append: this.proxy( function ( $el, $el2 ) {
325 // append
326 if ( args.before ) {
327 args.before.before( $el2 );
328 } else {
329 $el.before( $el2 );
330 }
331
332 // remove clone class
333 $el2.removeClass( 'acf-clone' );
334
335 // enable
336 acf.enable( $el2, this.cid );
337 } ),
338 } );
339
340 if ( this.get( 'pagination' ) ) {
341 this.incrementTotalRows();
342
343 if ( false !== args.before ) {
344 // If the row was inserted above an existing row, try to keep that order.
345 const prevRowNum = parseInt( args.before.find( '.acf-row-number' ).first().text() ) || 0;
346 let newRowNum = prevRowNum;
347
348 if ( newRowNum && ! args.before.hasClass( 'acf-inserted' ) && ! args.before.hasClass( 'acf-added' ) ) {
349 --newRowNum;
350 }
351
352 if ( args.before.hasClass( 'acf-divider' ) ) {
353 args.before.removeClass( 'acf-divider' );
354 $el.addClass( 'acf-divider' );
355 }
356
357 this.updateRowStatus( $el, 'inserted' );
358 this.updateRowStatus( $el, 'reordered', newRowNum );
359
360 // Hide the row numbers to avoid confusion with existing rows.
361 $el.find( '.acf-row-number' ).first().hide().text( newRowNum );
362 if ( ! $el.find( '.acf-order-input-wrap' ).hasClass( 'disabled' ) ) {
363 let message = acf.__( 'Order will be assigned upon save' );
364 $el.find( '.acf-order-input-wrap' ).addClass( 'disabled' );
365 $el.find( '.acf-row-number' ).first().after( '<span title="' + message + '">-</span>' );
366 }
367 $el.find( '.acf-order-input' ).first().hide();
368 $el.attr( 'data-inserted', newRowNum );
369 } else {
370 this.nextRowNum++;
371
372 $el.find( '.acf-order-input' ).first().val( this.nextRowNum );
373 $el.find( '.acf-row-number' ).first().text( this.nextRowNum );
374 this.updateRowStatus( $el, 'added' );
375
376 if ( ! this.$tbody().find( '.acf-divider' ).length ) {
377 $el.addClass( 'acf-divider' );
378 }
379 }
380
381 $el.find( '.acf-input:first' )
382 .find( 'input:not([type=hidden]), select, textarea' )
383 .first()
384 .trigger( 'focus' );
385 }
386
387 // Render and trigger change for validation errors.
388 this.render();
389 this.$input().trigger( 'change' );
390
391 return $el;
392 },
393
394 onClickDuplicate: function ( e, $el ) {
395 // Validate with warning.
396 if ( ! this.validateAdd() ) {
397 return false;
398 }
399
400 // get layout and duplicate it.
401 var $row = $el.closest( '.acf-row' );
402 this.duplicateRow( $row );
403 },
404
405 duplicateRow: function ( $row ) {
406 // Validate without warning.
407 if ( ! this.allowAdd() ) {
408 return false;
409 }
410
411 // Vars.
412 var fieldKey = this.get( 'key' );
413
414 // Duplicate row.
415 var $el = acf.duplicate( {
416 target: $row,
417
418 // Provide a custom renaming callback to avoid renaming parent row attributes.
419 rename: function ( name, value, search, replace ) {
420 // Rename id attributes from "field_1-search" to "field_1-replace".
421 if ( name === 'id' || name === 'for' ) {
422 return value.replace(
423 fieldKey + '-' + search,
424 fieldKey + '-' + replace
425 );
426
427 // Rename name and for attributes from "[field_1][search]" to "[field_1][replace]".
428 } else {
429 return value.replace(
430 fieldKey + '][' + search,
431 fieldKey + '][' + replace
432 );
433 }
434 },
435 before: function ( $el ) {
436 acf.doAction( 'unmount', $el );
437 },
438 after: function ( $el, $el2 ) {
439 acf.doAction( 'remount', $el );
440 },
441 } );
442
443 if ( this.get( 'pagination' ) ) {
444 this.incrementTotalRows();
445
446 // If the row was inserted above an existing row, try to keep that order.
447 const prevRowNum = parseInt( $row.find( '.acf-row-number' ).first().text() ) || 0;
448
449 this.updateRowStatus( $el, 'inserted' );
450 this.updateRowStatus( $el, 'reordered', prevRowNum );
451
452 // Hide the row numbers to avoid confusion with existing rows.
453 $el.find( '.acf-row-number' ).first().hide();
454 if ( ! $el.find( '.acf-order-input-wrap' ).hasClass( 'disabled' ) ) {
455 let message = acf.__( 'Order will be assigned upon save' );
456 $el.find( '.acf-order-input-wrap' ).addClass( 'disabled' );
457 $el.find( '.acf-row-number' ).first().after( '<span title="' + message + '">-</span>' );
458 }
459 $el.find( '.acf-order-input' ).first().hide();
460 $el.attr( 'data-inserted', prevRowNum );
461 $el.removeClass( 'acf-divider' );
462 }
463
464 // trigger change for validation errors
465 this.$input().trigger( 'change' );
466
467 // Update order numbers.
468 this.render();
469
470 // Focus on new row.
471 acf.focusAttention( $el );
472
473 // Return new layout.
474 return $el;
475 },
476
477 validateRemove: function () {
478 // return true if allowed
479 if ( this.allowRemove() ) {
480 return true;
481 }
482
483 // vars
484 var min = this.get( 'min' );
485 var text = acf.__( 'Minimum rows not reached ({min} rows)' );
486
487 // replace
488 text = text.replace( '{min}', min );
489
490 // add notice
491 this.showNotice( {
492 text: text,
493 type: 'warning',
494 } );
495
496 // return
497 return false;
498 },
499
500 onClickRemove: function ( e, $el ) {
501 var $row = $el.closest( '.acf-row' );
502
503 // Bypass confirmation when holding down "shift" key.
504 if ( e.shiftKey ) {
505 return this.remove( $row );
506 }
507
508 // add class
509 $row.addClass( '-hover' );
510
511 // add tooltip
512 var tooltip = acf.newTooltip( {
513 confirmRemove: true,
514 target: $el,
515 context: this,
516 confirm: function () {
517 this.remove( $row );
518 },
519 cancel: function () {
520 $row.removeClass( '-hover' );
521 },
522 } );
523 },
524
525 onClickRowOrder: function( e, $el ) {
526 if ( ! this.get( 'pagination' ) ) {
527 return;
528 }
529
530 if ( $el.hasClass( 'disabled' ) ) {
531 return;
532 }
533
534 $el.find( '.acf-row-number' ).hide();
535 $el.find( '.acf-order-input' ).show().trigger( 'select' );
536 },
537
538 onBlurRowOrder: function( e, $el ) {
539 this.onChangeRowOrder( e, $el, false );
540 },
541
542 onChangeRowOrder: function( e, $el, update = true ) {
543 if ( ! this.get( 'pagination' ) ) {
544 return;
545 }
546
547 const $row = $el.closest( '.acf-row' );
548 const $orderSpan = $row.find( '.acf-row-number' ).first();
549 let hrOrder = $el.val();
550
551 $row.find( '.acf-order-input' ).first().hide();
552
553 if ( ! acf.isNumeric( hrOrder ) || parseFloat( hrOrder ) < 0 ) {
554 $orderSpan.show();
555 return;
556 }
557
558 hrOrder = Math.round( hrOrder );
559
560 const newOrder = hrOrder - 1;
561
562 $el.val( hrOrder );
563 $orderSpan.text( hrOrder ).show();
564
565 if ( update ) {
566 this.updateRowStatus( $row, 'reordered', newOrder );
567 }
568 },
569
570 onChangeTotalRows: function() {
571 const perPage = parseInt( this.get( 'per_page' ) ) || 20;
572 const totalRows = parseInt( this.get( 'total_rows' ) ) || 0;
573 const totalPages = Math.ceil( totalRows / perPage );
574
575 // Update the total pages in pagination.
576 this.$( '.acf-total-pages:last' ).text( totalPages );
577 this.nextRowNum = totalRows;
578
579 // If the current page no longer exists, load the last page.
580 if ( this.page > totalPages ) {
581 this.page = totalPages;
582 this.ajaxLoadPage();
583 }
584 },
585
586 remove: function ( $row ) {
587 const self = this;
588
589 if ( this.get( 'pagination' ) ) {
590 this.decrementTotalRows();
591
592 // If using pagination and the row had already been saved, just hide the row instead of deleting it.
593 if ( $row.data( 'id' ).includes( 'row-' ) ){
594 this.updateRowStatus( $row, 'deleted' );
595
596 $row.hide();
597 self.$input().trigger( 'change' );
598 self.render( false );
599 return;
600 } else if ( $row.hasClass( 'acf-divider' ) ) {
601 $row.next( '.acf-added' ).addClass( 'acf-divider' );
602 }
603 }
604
605 // If not using pagination, delete the actual row.
606 acf.remove( {
607 target: $row,
608 endHeight: 0,
609 complete: function () {
610 // trigger change to allow attachment save
611 self.$input().trigger( 'change' );
612
613 // render
614 self.render();
615
616 // sync collapsed order
617 //self.sync();
618 },
619 } );
620 },
621
622 isCollapsed: function ( $row ) {
623 return $row.hasClass( '-collapsed' );
624 },
625
626 collapse: function ( $row ) {
627 $row.addClass( '-collapsed' );
628 acf.doAction( 'hide', $row, 'collapse' );
629 },
630
631 expand: function ( $row ) {
632 $row.removeClass( '-collapsed' );
633 acf.doAction( 'show', $row, 'collapse' );
634 },
635
636 onClickCollapse: function ( e, $el ) {
637 // vars
638 var $row = $el.closest( '.acf-row' );
639 var isCollpased = this.isCollapsed( $row );
640
641 // shift
642 if ( e.shiftKey ) {
643 $row = this.$rows();
644 }
645
646 // toggle
647 if ( isCollpased ) {
648 this.expand( $row );
649 } else {
650 this.collapse( $row );
651 }
652 },
653
654 onShow: function ( e, $el, context ) {
655 // get sub fields
656 var fields = acf.getFields( {
657 is: ':visible',
658 parent: this.$el,
659 } );
660
661 // trigger action
662 // - ignore context, no need to pass through 'conditional_logic'
663 // - this is just for fields like google_map to render itself
664 acf.doAction( 'show_fields', fields );
665 },
666
667 onUnload: function () {
668 // vars
669 var indexes = [];
670
671 // loop
672 this.$rows().each( function ( i ) {
673 if ( $( this ).hasClass( '-collapsed' ) ) {
674 indexes.push( i );
675 }
676 } );
677
678 // allow null
679 indexes = indexes.length ? indexes : null;
680
681 // set
682 preference.save( this.get( 'key' ), indexes );
683 },
684
685 onHover: function () {
686 // add sortable
687 this.addSortable( this );
688
689 // remove event
690 this.off( 'mouseover' );
691 },
692
693 onChangeField: function( e, $el ) {
694 const $target = $( e.delegateTarget );
695 let $row = $el.closest( '.acf-row' );
696
697 if ( $row.closest( '.acf-field-repeater' ).data( 'key' ) !== $target.data( 'key' ) ) {
698 $row = $row.parent().closest( '.acf-row' );
699 }
700
701 this.updateRowStatus( $row, 'changed' );
702 },
703
704 updateRowStatus: function( $row, status, data = true ) {
705 if ( ! this.get( 'pagination' ) ) {
706 return;
707 }
708
709 const parent_key = $row.parents( '.acf-field-repeater' ).data( 'key' );
710
711 if ( this.parent() && parent_key !== this.get( 'key' ) ) {
712 return;
713 }
714
715 const row_id = $row.data( 'id' );
716 const input_name = this.$el.find( '.acf-repeater-hidden-input:first' ).attr( 'name' );
717 const status_name = `${input_name}[${row_id}][acf_${status}]`;
718 const status_input = `<input type="hidden" class="acf-row-status" name="${status_name}" value="${data}" />`;
719
720 if ( ! $row.hasClass( 'acf-' + status ) ) {
721 $row.addClass( 'acf-' + status );
722 }
723
724 // TODO: Update so that this doesn't get messed up with repeater subfields.
725 const $existing_status = $row.find( `input[name='${status_name}']` );
726 if ( ! $existing_status.length ) {
727 $row.find( 'td' ).first().append( status_input );
728 } else {
729 $existing_status.val( data );
730 }
731 },
732
733 onClickFirstPage: function() {
734 this.validatePage( 1 );
735 },
736
737 onClickPrevPage: function() {
738 this.validatePage( this.page - 1 );
739 },
740
741 onClickNextPage: function( e ) {
742 this.validatePage( this.page + 1 );
743 },
744
745 onClickLastPage: function() {
746 this.validatePage( this.totalPages() );
747 },
748
749 onChangeCurrentPage: function() {
750 this.validatePage( this.$pageInput().val() );
751 },
752
753 maybeDisablePagination: function() {
754 this.$actions().find( '.acf-nav' ).removeClass( 'disabled' );
755
756 if ( this.page <= 1 ) {
757 this.$firstPageButton().addClass( 'disabled' );
758 this.$prevPageButton().addClass( 'disabled' );
759 }
760
761 if ( this.page >= this.totalPages() ) {
762 this.$nextPageButton().addClass( 'disabled' );
763 this.$lastPageButton().addClass( 'disabled' );
764 }
765 },
766
767 validatePage: function( nextPage ) {
768 const self = this;
769
770 // Validate the current page.
771 acf.validateForm( {
772 form: this.$control(),
773 event: '',
774 reset: true,
775 success: function( $form ) {
776 self.page = nextPage;
777
778 // Set up some sane defaults.
779 if ( self.page <= 1 ) {
780 self.page = 1;
781 }
782 if ( self.page >= self.totalPages() ) {
783 self.page = self.totalPages();
784 }
785
786 self.ajaxLoadPage();
787 },
788 failure: function( $form ) {
789 self.$pageInput().val( self.page );
790 return false;
791 },
792 } );
793 },
794
795 ajaxLoadPage: function( clearChanged = false ) {
796 const ajaxData = acf.prepareForAjax( {
797 action: 'acf/ajax/query_repeater',
798 paged: this.page,
799 field_key: this.get( 'key' ),
800 field_name: this.get( 'orig_name' ),
801 rows_per_page: parseInt( this.get( 'per_page' ) ),
802 refresh: clearChanged,
803 nonce: this.get( 'nonce' )
804 } );
805
806 $.ajax(
807 {
808 url: ajaxurl,
809 method: 'POST',
810 dataType: 'json',
811 data: ajaxData,
812 context: this,
813 }
814 ).done(
815 function( response ) {
816 const { rows } = response.data;
817 const $existingRows = this.$tbody().find( '> tr' );
818
819 $existingRows.not( '.acf-clone' ).hide();
820
821 if ( clearChanged ) {
822 // Remove any existing rows since we are refreshing from the server.
823 $existingRows.not( '.acf-clone' ).remove();
824
825 // Trigger a change in total rows, so we can update pagination.
826 this.set( 'total_rows', response.data.total_rows, false );
827 } else {
828 $existingRows.not( '.acf-changed, .acf-deleted, .acf-reordered, .acf-added, .acf-inserted, .acf-clone' ).remove();
829 }
830
831 Object.keys( rows ).forEach( index => {
832 let $row = false;
833 let $unsavedRow = this.$tbody().find( '> *[data-id=row-' + index + ']' );
834 let $insertedRow = this.$tbody().find( '> *[data-inserted=' + index + ']' );
835
836 // Unsaved new rows that are inserted into this specific position.
837 if ( $insertedRow.length ) {
838 $insertedRow.appendTo( this.$tbody() ).show();
839 acf.doAction( 'remount', $insertedRow );
840 }
841
842 // Skip unsaved deleted rows; we don't want to show them again.
843 if ( $unsavedRow.hasClass( 'acf-deleted' ) ) {
844 return;
845 }
846
847 // Unsaved edited rows should be moved to correct position.
848 if ( $unsavedRow.length ) {
849 acf.doAction( 'unmount', $unsavedRow );
850 $unsavedRow.appendTo( this.$tbody() ).show();
851 acf.doAction( 'remount', $unsavedRow );
852 return;
853 }
854
855 // Rows from the server (that haven't been changed or deleted) should be appended and shown.
856 $row = $( rows[ index ] );
857 this.$tbody().append( $row ).show();
858 acf.doAction( 'remount', $row );
859
860 // Move clone field back to the right spot.
861 this.$clone().appendTo( this.$tbody() );
862 } );
863
864 const $addedRows = this.$tbody().find( '.acf-added:hidden' );
865
866 // If there are any new rows that are still hidden, append them to the bottom.
867 if ( $addedRows.length ) {
868 const self = this;
869
870 $addedRows.each( function() {
871 const $addedRow = $( this );
872 $addedRow.insertBefore( self.$clone() ).show();
873 acf.doAction( 'remount', $addedRow );
874 } );
875 }
876
877 // Update the page input.
878 this.$pageInput().val( this.page );
879 this.maybeDisablePagination();
880 }
881 ).fail(
882 function( jqXHR, textStatus, errorThrown ) {
883 const error = acf.getXhrError( jqXHR );
884 let message = acf.__( 'Error loading page' );
885
886 if ( '' !== error ) {
887 message = `${message}: ${error}`;
888 }
889
890 this.showNotice( {
891 text: message,
892 type: 'warning',
893 } );
894 }
895 );
896 },
897
898 } );
899
900 acf.registerFieldType( Field );
901
902 // register existing conditions
903 acf.registerConditionForFieldType( 'hasValue', 'repeater' );
904 acf.registerConditionForFieldType( 'hasNoValue', 'repeater' );
905 acf.registerConditionForFieldType( 'lessThan', 'repeater' );
906 acf.registerConditionForFieldType( 'greaterThan', 'repeater' );
907
908 // state
909 var preference = new acf.Model( {
910 name: 'this.collapsedRows',
911
912 key: function ( key, context ) {
913 // vars
914 var count = this.get( key + context ) || 0;
915
916 // update
917 count++;
918 this.set( key + context, count, true );
919
920 // modify fieldKey
921 if ( count > 1 ) {
922 key += '-' + count;
923 }
924
925 // return
926 return key;
927 },
928
929 load: function ( key ) {
930 // vars
931 var key = this.key( key, 'load' );
932 var data = acf.getPreference( this.name );
933
934 // return
935 if ( data && data[ key ] ) {
936 return data[ key ];
937 } else {
938 return false;
939 }
940 },
941
942 save: function ( key, value ) {
943 // vars
944 var key = this.key( key, 'save' );
945 var data = acf.getPreference( this.name ) || {};
946
947 // delete
948 if ( value === null ) {
949 delete data[ key ];
950
951 // append
952 } else {
953 data[ key ] = value;
954 }
955
956 // allow null
957 if ( $.isEmptyObject( data ) ) {
958 data = null;
959 }
960
961 // save
962 acf.setPreference( this.name, data );
963 },
964 } );
965 } )( jQuery );
966