Skip to content

Commit 49a9f52

Browse files
authored
Merge pull request #2498 from HadyShaltout/master
Tweak: Enable [active_callback] For Repeater's Children
2 parents 1466c12 + 5bdbeec commit 49a9f52

File tree

3 files changed

+347
-0
lines changed

3 files changed

+347
-0
lines changed

kirki-packages/control-repeater/src/control.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,8 @@ wp.customize.controlConstructor.repeater = wp.customize.Control.extend({
670670
this.setting.set(encodeURI(JSON.stringify(filteredValue)));
671671

672672
if (refresh) {
673+
// Check acive_callback in every change
674+
KirkiRepeaterDependencies.init();
673675
// Trigger the change event on the hidden field so
674676
// previewer refresh the website on Customizer
675677
this.settingField.trigger("change");

kirki-packages/module-field-dependencies/src/Field_Dependencies.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ class Field_Dependencies {
2727
*/
2828
private $dependencies = [];
2929

30+
/**
31+
* An array of all repeater controls available.
32+
* Regardless if it has [active_callback] or not.
33+
*
34+
* @access private
35+
* @since 4.1.1
36+
* @var array
37+
*/
38+
private $repeater_controls = [];
39+
3040
/**
3141
* Constructor.
3242
*
@@ -40,6 +50,28 @@ public function __construct() {
4050

4151
}
4252

53+
/**
54+
* Collect all Repeater Controls in a new dependencies array
55+
* other than [kirkiControlDependencies]
56+
* to fix the issue that we can't find the repeater control
57+
* in the array [kirkiControlDependencies] because
58+
* it doesn't have [active_callback] array
59+
*
60+
* Now, We can use [active_callback] array in the repeater's childern
61+
*
62+
* @access private
63+
* @since 4.1.1
64+
* @param array $args The field arguments.
65+
* @return void
66+
*/
67+
private function field_add_repeater_controls( $args ) {
68+
69+
if ('repeater' === $args['type'] || 'kirki-repeater' === $args['type'] ) {
70+
$this->repeater_controls[ $args['settings'] ] = '__return_true';
71+
}
72+
73+
}
74+
4375
/**
4476
* Filter control arguments.
4577
*
@@ -50,6 +82,9 @@ public function __construct() {
5082
*/
5183
public function field_add_control_args( $args ) {
5284

85+
// Collect a list of all Repeater Controls available
86+
$this->field_add_repeater_controls( $args );
87+
5388
if ( isset( $args['active_callback'] ) ) {
5489
if ( is_array( $args['active_callback'] ) ) {
5590
if ( ! is_callable( $args['active_callback'] ) ) {
@@ -104,6 +139,7 @@ public function field_dependencies() {
104139

105140
wp_enqueue_script( 'kirki_field_dependencies', URL::get_from_path( dirname( __DIR__ ) . '/dist/control.js' ), [ 'jquery', 'customize-base', 'customize-controls' ], '4.0', true );
106141
wp_localize_script( 'kirki_field_dependencies', 'kirkiControlDependencies', $this->dependencies );
142+
wp_localize_script( 'kirki_field_dependencies', 'kirkiRepeaterControlsAvailable', $this->repeater_controls );
107143

108144
}
109145
}

kirki-packages/module-field-dependencies/src/control.js

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,315 @@ var kirkiDependencies = {
318318
},
319319
};
320320

321+
322+
/**
323+
* Enable [active_callback] for repeater's controls
324+
* This function MUST be here to use [kirkiControlDependencies] passed from [Field_Dependencies.php]
325+
*
326+
* @author: Kirki
327+
* @since 4.1.1
328+
*/
329+
var KirkiRepeaterDependencies = {
330+
331+
repeatersControls: {},
332+
333+
repeatersActiveCallbackFields: {},
334+
listenTo: {},
335+
336+
init: function() {
337+
var self = this;
338+
339+
/* 1. Collect All Repeaters */
340+
_.each( window.kirkiRepeaterControlsAvailable, function (repDetails, repeaterID) {
341+
342+
var control = wp.customize.control(repeaterID);
343+
344+
if( control && control.params && control.params.type && control.params.type === 'repeater' ) {
345+
346+
self.repeatersControls[ control.id ] = self.repeatersControls[ control.id ] || [];
347+
348+
self.repeatersControls[ control.id ] = {
349+
'user_entries': JSON.parse( decodeURI( control.setting.get() ) ) /* @see function [getValue] in [wp.customize.controlConstructor.repeater] located in [controls/js/script.js] */
350+
};
351+
352+
}
353+
354+
} );
355+
356+
/* 2. Collect Active Callbacks Arrays for each Available Repeater */
357+
_.each( self.repeatersControls, function( repDetails, repID ) {
358+
359+
var repControl = wp.customize.control( repID ),
360+
repUserEntries = (! _.isUndefined( repDetails ) && !_.isUndefined( repDetails['user_entries'] ) && ! _.isEmpty( repDetails['user_entries'] ) ) ? repDetails['user_entries'] : null;
361+
362+
if( ! _.isUndefined( repControl ) && ! _.isNull( repUserEntries ) ) {
363+
364+
_.each(repUserEntries, function(rowValue, rowIndex) {
365+
366+
_.each(rowValue, function(eleValue, eleID) {
367+
368+
if( !_.isUndefined( repControl.params.fields[ eleID ] ) ) {
369+
370+
var eleDetails = repControl.params.fields[ eleID ];
371+
372+
self.showRepeaterControl( repControl.id, eleDetails, rowValue );
373+
374+
}
375+
376+
});
377+
378+
});
379+
380+
self.repeatersActiveCallbackFields[ repControl.id ] = self.repeatersActiveCallbackFields[ repControl.id ] || [];
381+
self.repeatersActiveCallbackFields[ repControl.id ] = self.listenTo;
382+
/* Destroy it */
383+
self.listenTo = {};
384+
385+
}
386+
387+
} );
388+
389+
390+
/* 3. Iterate inside every user entry and Apply [showRepeaterControl] on each slave element */
391+
_.each( self.repeatersActiveCallbackFields, function( required_fields, repID ) {
392+
393+
objRepeaterControl = wp.customize.control( repID );
394+
395+
if( ! _.isUndefined( objRepeaterControl ) ) {
396+
397+
var repUserEntries = JSON.parse( decodeURI( objRepeaterControl.setting.get() ) ), /* @see function [getValue] in [wp.customize.controlConstructor.repeater] located in [controls/js/script.js] */
398+
repFields = objRepeaterControl.params.fields;
399+
400+
_.each(repUserEntries, function(rowValue, rowIndex) {
401+
402+
_.each( required_fields, function( slaves, master ) {
403+
404+
_.each( slaves, function( slave ) {
405+
406+
var objSlave = repFields[ slave ],
407+
setActiveState,
408+
isDisplayed;
409+
410+
isDisplayed = function() {
411+
return self.showRepeaterControl( repID, objSlave, rowValue );
412+
};
413+
414+
setActiveState = function() {
415+
if( isDisplayed() ) {
416+
jQuery(objRepeaterControl.selector).find( '[data-row="' + rowIndex + '"] .repeater-field-' + slave ).removeClass('inactive').addClass('active').slideDown('fast');
417+
}
418+
else {
419+
jQuery(objRepeaterControl.selector).find( '[data-row="' + rowIndex + '"] .repeater-field-' + slave ).removeClass('active').addClass('inactive').slideUp('fast');
420+
}
421+
};
422+
423+
setActiveState();
424+
425+
426+
} );
427+
428+
});
429+
430+
});
431+
432+
}
433+
434+
} );
435+
436+
},
437+
438+
439+
/**
440+
* Should we show the control?
441+
*
442+
* @since 4.0.22
443+
*
444+
* @param {string} repeaterID - The repeater ID
445+
* @param {string|object} control - The control-id or the control object.
446+
* @param {object} rowEntries - The user entry for a repeater block
447+
* @returns {bool}
448+
*/
449+
showRepeaterControl: function( repeaterID, control, rowEntries ) {
450+
451+
var self = this,
452+
show = true,
453+
454+
isOption = (
455+
! _.isUndefined( control ) && /* Fix: Multiple Repeaters with no active_callback */
456+
! _.isUndefined( control.id ) && /* Fix: Multiple Repeaters with no active_callback */
457+
control.id && // Check if id exists.
458+
control.type && // Check if tpe exists.
459+
! _.isEmpty( control.type ) // Check if control's type is not empty.
460+
),
461+
i;
462+
463+
// Exit early if control not found or if "required" argument is not defined.
464+
if ( 'undefined' === typeof control || 'undefined' === typeof control.active_callback || ( control.active_callback && _.isEmpty( control.active_callback ) ) ) {
465+
return true;
466+
}
467+
468+
// Loop control requirements.
469+
for ( i = 0; i < control.active_callback.length; i++ ) {
470+
if ( ! self.checkCondition( repeaterID, control.active_callback[ i ], control, rowEntries, isOption, 'AND' ) ) {
471+
show = false;
472+
}
473+
}
474+
return show;
475+
},
476+
477+
/**
478+
* Check a condition.
479+
*
480+
* @param {string} repeaterID - The repeater ID
481+
* @param {Object} requirement - The requirement, inherited from showRepeaterControl - Represents the Active Callack Array.
482+
* @param {Object} control - The repeater's control object.
483+
* @param {object} rowEntries - The user entry for a repeater block
484+
* @param {bool} isOption - Whether it's an option or not.
485+
* @param {string} relation - Can be one of 'AND' or 'OR'.
486+
*/
487+
checkCondition: function( repeaterID, requirement, control, rowEntries, isOption, relation ) {
488+
var self = this,
489+
childRelation = ( 'AND' === relation ) ? 'OR' : 'AND',
490+
nestedItems,
491+
requirementSettingValue,
492+
i;
493+
494+
495+
496+
// If an array of other requirements nested, we need to process them separately.
497+
if ( 'undefined' !== typeof requirement[0] && 'undefined' === typeof requirement.setting ) {
498+
499+
nestedItems = [];
500+
501+
// Loop sub-requirements.
502+
for ( i = 0; i < requirement.length; i++ ) {
503+
nestedItems.push( self.checkCondition( repeaterID, requirement[ i ], control, rowEntries, isOption, childRelation ) );
504+
}
505+
506+
507+
// OR relation. Check that true is part of the array.
508+
if ( 'OR' === childRelation ) {
509+
return ( -1 !== nestedItems.indexOf( true ) );
510+
}
511+
512+
// AND relation. Check that false is not part of the array.
513+
return ( -1 === nestedItems.indexOf( false ) );
514+
}
515+
516+
517+
// Early exit if setting is not defined.
518+
if ( ! requirement.setting in rowEntries ) {
519+
return true;
520+
}
521+
522+
/* Requirement Setting User Value */
523+
requirementSettingValue = rowEntries[ requirement.setting ];
524+
525+
// console.log( requirementSettingValue );
526+
527+
/**
528+
* Output: listenTo
529+
*
530+
* Master_#1 => array(
531+
* 0: Slave #1,
532+
* 1: Slave #2
533+
* )
534+
*
535+
*/
536+
self.listenTo[ requirement.setting ] = self.listenTo[ requirement.setting ] || [];
537+
538+
if ( -1 === self.listenTo[ requirement.setting ].indexOf( control.id ) ) {
539+
540+
self.listenTo[ requirement.setting ].push( control.id );
541+
542+
}
543+
544+
return self.evaluate(
545+
requirement.value,
546+
requirementSettingValue,
547+
requirement.operator
548+
);
549+
550+
},
551+
552+
/**
553+
* Figure out if the 2 values have the relation we want.
554+
*
555+
* @since 4.0.22
556+
* @param {mixed} value1 - The 1st value.
557+
* @param {mixed} value2 - The 2nd value.
558+
* @param {string} operator - The comparison to use.
559+
* @returns {bool}
560+
*/
561+
evaluate: function( value1, value2, operator ) {
562+
var found = false;
563+
564+
if ( '===' === operator ) {
565+
return value1 === value2;
566+
}
567+
if ( '==' === operator || '=' === operator || 'equals' === operator || 'equal' === operator ) {
568+
return value1 == value2;
569+
}
570+
if ( '!==' === operator ) {
571+
return value1 !== value2;
572+
}
573+
if ( '!=' === operator || 'not equal' === operator ) {
574+
return value1 != value2;
575+
}
576+
if ( '>=' === operator || 'greater or equal' === operator || 'equal or greater' === operator ) {
577+
return value2 >= value1;
578+
}
579+
if ( '<=' === operator || 'smaller or equal' === operator || 'equal or smaller' === operator ) {
580+
return value2 <= value1;
581+
}
582+
if ( '>' === operator || 'greater' === operator ) {
583+
return value2 > value1;
584+
}
585+
if ( '<' === operator || 'smaller' === operator ) {
586+
return value2 < value1;
587+
}
588+
if ( 'contains' === operator || 'in' === operator ) {
589+
if ( _.isArray( value1 ) && _.isArray( value2 ) ) {
590+
_.each( value2, function( value ) {
591+
if ( value1.includes( value ) ) {
592+
found = true;
593+
return false;
594+
}
595+
} );
596+
return found;
597+
}
598+
if ( _.isArray( value2 ) ) {
599+
_.each( value2, function( value ) {
600+
if ( value == value1 ) { // jshint ignore:line
601+
found = true;
602+
}
603+
} );
604+
return found;
605+
}
606+
if ( _.isObject( value2 ) ) {
607+
if ( ! _.isUndefined( value2[ value1 ] ) ) {
608+
found = true;
609+
}
610+
_.each( value2, function( subValue ) {
611+
if ( value1 === subValue ) {
612+
found = true;
613+
}
614+
} );
615+
return found;
616+
}
617+
if ( _.isString( value2 ) ) {
618+
if ( _.isString( value1 ) ) {
619+
return ( -1 < value1.indexOf( value2 ) && -1 < value2.indexOf( value1 ) );
620+
}
621+
return -1 < value1.indexOf( value2 );
622+
}
623+
}
624+
return value1 == value2;
625+
}
626+
627+
};
628+
321629
jQuery(document).ready(function () {
322630
kirkiDependencies.init();
631+
KirkiRepeaterDependencies.init();
323632
});

0 commit comments

Comments
 (0)