aristath/kirki

View on GitHub
packages/kirki-framework/module-field-dependencies/src/script.js

Summary

Maintainability
D
2 days
Test Coverage
var kirkiDependencies = {

    listenTo: {},

    init: function() {
        var self = this;

        _.each( window.kirkiControlDependencies, function( requires, controlID ) {
            var control = wp.customize.control( controlID );
            if ( control ) {
                wp.customize.control( controlID ).params.required = requires;
                self.showKirkiControl( control );
            }
        } );

        _.each( self.listenTo, function( slaves, master ) {
            _.each( slaves, function( slave ) {
                wp.customize( master, function( setting ) {
                    var setupControl = function( control ) {
                        var setActiveState,
                            isDisplayed;

                        isDisplayed = function() {
                            return self.showKirkiControl( wp.customize.control( slave ) );
                        };
                        setActiveState = function() {
                            control.active.set( isDisplayed() );
                        };

                        setActiveState();
                        setting.bind( setActiveState );
                        control.active.validate = isDisplayed;
                    };
                    wp.customize.control( slave, setupControl );
                } );
            } );
        } );
    },

    /**
     * Should we show the control?
     *
     * @since 3.0.17
     * @param {string|object} control - The control-id or the control object.
     * @returns {bool} - Whether the control should be shown or not.
     */
    showKirkiControl: function( control ) {
        var self     = this,
            show     = true,
            isOption = (
                control.params && // Check if control.params exists.
                control.params.kirkiOptionType &&  // Check if option_type exists.
                'option' === control.params.kirkiOptionType &&  // We're using options.
                control.params.kirkiOptionName && // Check if option_name exists.
                ! _.isEmpty( control.params.kirkiOptionName ) // Check if option_name is not empty.
            ),
            i;


        if ( _.isString( control ) ) {
            control = wp.customize.control( control );
        }

        // Exit early if control not found or if "required" argument is not defined.
        if ( 'undefined' === typeof control || ( control.params && _.isEmpty( control.params.required ) ) ) {
            return true;
        }

        // Loop control requirements.
        for ( i = 0; i < control.params.required.length; i++ ) {
            if ( ! self.checkCondition( control.params.required[ i ], control, isOption, 'AND' ) ) {
                show = false;
            }
        }
        return show;
    },

    /**
     * Check a condition.
     *
     * @param {Object} requirement - The requirement, inherited from showKirkiControl.
     * @param {Object} control - The control object.
     * @param {bool}   isOption - Whether it's an option or not.
     * @param {string} relation - Can be one of 'AND' or 'OR'.
     * @returns {bool} - Returns the results of the condition checks.
     */
    checkCondition: function( requirement, control, isOption, relation ) {
        var self          = this,
            childRelation = ( 'AND' === relation ) ? 'OR' : 'AND',
            nestedItems,
            value,
            i;

        // Tweak for using active callbacks with serialized options instead of theme_mods.
        if ( isOption && requirement.setting ) {

            // Make sure we don't already have the option_name in there.
            if ( -1 === requirement.setting.indexOf( control.params.kirkiOptionName + '[' ) ) {
                requirement.setting = control.params.kirkiOptionName + '[' + requirement.setting + ']';
            }
        }

        // If an array of other requirements nested, we need to process them separately.
        if ( 'undefined' !== typeof requirement[0] && 'undefined' === typeof requirement.setting ) {
            nestedItems = [];

            // Loop sub-requirements.
            for ( i = 0; i < requirement.length; i++ ) {
                nestedItems.push( self.checkCondition( requirement[ i ], control, isOption, childRelation ) );
            }

            // OR relation. Check that true is part of the array.
            if ( 'OR' === childRelation ) {
                return ( -1 !== nestedItems.indexOf( true ) );
            }

            // AND relation. Check that false is not part of the array.
            return ( -1 === nestedItems.indexOf( false ) );
        }

        // Early exit if setting is not defined.
        if ( 'undefined' === typeof wp.customize.control( requirement.setting ) ) {
            return true;
        }

        self.listenTo[ requirement.setting ] = self.listenTo[ requirement.setting ] || [];
        if ( -1 === self.listenTo[ requirement.setting ].indexOf( control.id ) ) {
            self.listenTo[ requirement.setting ].push( control.id );
        }

        value = wp.customize( requirement.setting ).get();
        if ( wp.customize.control( requirement.setting ).setting ) {
            value = wp.customize.control( requirement.setting ).setting._value;
        }

        return self.evaluate( requirement.value, value, requirement.operator, requirement.choice );
    },

    /**
     * Figure out if the 2 values have the relation we want.
     *
     * @since 3.0.17
     * @param {mixed} value1 - The 1st value.
     * @param {mixed} value2 - The 2nd value.
     * @param {string} operator - The comparison to use.
     * @param {string} choice - If we want to check an item in an object value.
     * @returns {bool} - Returns the evaluation result.
     */
    evaluate: function( value1, value2, operator, choice ) {
        var found = false;

        if ( choice && 'object' === typeof value2 ) {
            value2 = value2[ choice ];
        }

        if ( '===' === operator ) {
            return value1 === value2;
        }
        if ( '==' === operator || '=' === operator || 'equals' === operator || 'equal' === operator ) {
            return value1 == value2;
        }
        if ( '!==' === operator ) {
            return value1 !== value2;
        }
        if ( '!=' === operator || 'not equal' === operator ) {
            return value1 != value2;
        }
        if ( '>=' === operator || 'greater or equal' === operator || 'equal or greater' === operator ) {
            return value2 >= value1;
        }
        if ( '<=' === operator || 'smaller or equal' === operator || 'equal or smaller' === operator ) {
            return value2 <= value1;
        }
        if ( '>' === operator || 'greater' === operator ) {
            return value2 > value1;
        }
        if ( '<' === operator || 'smaller' === operator ) {
            return value2 < value1;
        }
        if ( 'contains' === operator || 'in' === operator ) {
            if ( _.isArray( value1 ) && _.isArray( value2 ) ) {
                _.each( value2, function( value ) {
                    if ( value1.includes( value ) ) {
                        found = true;
                        return false;
                    }
                } );
                return found;
            }
            if ( _.isArray( value2 ) ) {
                _.each( value2, function( value ) {
                    if ( value == value1 ) { // jshint ignore:line
                        found = true;
                    }
                } );
                return found;
            }
            if ( _.isObject( value2 ) ) {
                if ( ! _.isUndefined( value2[ value1 ] ) ) {
                    found = true;
                }
                _.each( value2, function( subValue ) {
                    if ( value1 === subValue ) {
                        found = true;
                    }
                } );
                return found;
            }
            if ( _.isString( value2 ) ) {
                if ( _.isString( value1 ) ) {
                    return ( -1 < value1.indexOf( value2 ) && -1 < value2.indexOf( value1 ) );
                }
                return -1 < value1.indexOf( value2 );
            }
        }
        if ( 'does not contain' === operator || 'not in' === operator ) {
            return ( ! this.evaluate( value1, value2, 'contains', choice ) );
        }

        return value1 == value2;
    }
};

jQuery( document ).ready( function() {
    kirkiDependencies.init();
} );