felixarntz/plugin-lib

View on GitHub
assets/src/js/fields.js

Summary

Maintainability
F
3 mos
Test Coverage
( function( exports, $, _, Backbone, wp, fieldsAPIData ) {
    'use strict';

    _.mixin({
        alias: function( alias, func ) {
            func( alias );
        },
        attrs: function( attrs ) {
            var attributeString = '';

            _.each( attrs, function( value, attr ) {
                if ( _.isBoolean( value ) && 0 !== attr.indexOf( 'data-' ) ) {
                    if ( value ) {
                        attributeString += ' ' + attr;
                    }
                } else {
                    if ( _.isArray( value ) || _.isObject( value ) ) {
                        value = window.JSON.stringify( value );
                    }

                    if ( _.isString( value ) && -1 !== value.search( '"' ) ) {
                        attributeString += " " + attr + "='" + value + "'";
                    } else {
                        attributeString += ' ' + attr + '="' + value + '"';
                    }
                }
            });

            return attributeString;
        },
        deepClone: function( obj ) {
            var clone = _.clone( obj );

            _.each( clone, function( value, key ) {
                if ( _.isObject( value ) ) {
                    clone[ key ] = _.deepClone( value );
                }
            });

            return clone;
        }
    });

    var cbHelpers = {
        'get_data_by_condition_bool_helper': function( prop, values, args, reverse ) {
            var operator = ( args.operator && args.operator.toUpperCase() === 'OR' ) ? 'OR' : 'AND';

            var resultFalse, resultTrue, value, identifier, i;
            if ( reverse ) {
                resultFalse = args.result_true || true;
                resultTrue  = args.result_false || false;
            } else {
                resultFalse = args.result_false || false;
                resultTrue  = args.result_true || true;
            }

            if ( 'OR' === operator ) {
                for ( i in Object.keys( values ) ) {
                    identifier = Object.keys( values )[ i ];
                    value      = values[ identifier ];

                    if ( value ) {
                        return resultTrue;
                    }
                }

                return resultFalse;
            }

            for ( i in Object.keys( values ) ) {
                identifier = Object.keys( values )[ i ];
                value      = values[ identifier ];

                if ( ! value ) {
                    return resultFalse;
                }
            }

            return resultTrue;
        },

        'get_data_by_condition_numeric_comparison_helper': function( prop, values, args, reverse ) {
            var operator = ( args.operator && args.operator.toUpperCase() === 'OR' ) ? 'OR' : 'AND';

            var resultFalse, resultTrue, breakpoint, sanitize, inclusive, value, identifier, i;
            if ( reverse ) {
                resultFalse = args.result_true || true;
                resultTrue  = args.result_false || false;
            } else {
                resultFalse = args.result_false || false;
                resultTrue  = args.result_true || true;
            }

            breakpoint = 0.0;
            sanitize = parseFloat;
            if ( ! _.isUndefined( args.breakpoint ) ) {
                if ( parseInt( args.breakpoint, 10 ) === args.breakpoint ) {
                    sanitize = _.bind( parseInt, undefined, undefined, 10 );
                } else if ( 'string' === typeof args.breakpoint && args.breakpoint.match( /^((\d{4}-\d{2}-\d{2})|(\d{2}:\d{2}:\d{2})|(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}))$/ ) ) {
                    sanitize = function( dt ) {
                        var datetime = new Date( dt.replace( ' ', 'T' ) );

                        return datetime.getTime() / 1000;
                    };
                }

                breakpoint = sanitize( args.breakpoint );
            }

            inclusive = !! args.inclusive;
            if ( reverse ) {
                inclusive = ! inclusive;
            }

            if ( 'OR' === operator ) {
                for ( i in Object.keys( values ) ) {
                    identifier = Object.keys( values )[ i ];
                    value      = sanitize( values[ identifier ] );

                    if ( value > breakpoint || value === breakpoint && inclusive ) {
                        return resultTrue;
                    }
                }

                return resultFalse;
            }

            for ( i in Object.keys( values ) ) {
                identifier = Object.keys( values )[ i ];
                value      = sanitize( values[ identifier ] );

                if ( value < breakpoint || value === breakpoint && ! inclusive ) {
                    return resultFalse;
                }
            }

            return resultTrue;
        },

        'merge_into_result': function( result, value, operator ) {
            if ( _.isArray( result ) && _.isArray( value ) ) {
                if ( 'OR' === operator ) {
                    result = _.union( result, value );
                } else {
                    result = _.intersection( result, value );
                }

                return result;
            }

            if ( _.isObject( result ) && _.isObject( value ) ) {
                if ( 'OR' === operator ) {
                    result = _.extend( result, value );
                } else {
                    result = _.extend( _.pick( result, _.keys( value ) ), _.pick( value, _.keys( result ) ) );
                }

                return result;
            }

            if ( _.isBoolean( result ) && _.isBoolean( value ) ) {
                if ( 'OR' === operator ) {
                    result = result || value;
                } else {
                    result = result && value;
                }

                return result;
            }

            return value;
        }
    };

    var defaultDependencyCallbacks = {
        'get_data_by_condition_true': function( prop, values, args, cb ) {
            var result = cbHelpers.get_data_by_condition_bool_helper( prop, values, args, false );

            cb( result );
        },

        'get_data_by_condition_false': function( prop, values, args, cb ) {
            var result = cbHelpers.get_data_by_condition_bool_helper( prop, values, args, true );

            cb( result );
        },

        'get_data_by_condition_greater_than': function( prop, values, args, cb ) {
            var result = cbHelpers.get_data_by_condition_numeric_comparison_helper( prop, values, args, false );

            cb( result );
        },

        'get_data_by_condition_lower_than': function( prop, values, args, cb ) {
            var result = cbHelpers.get_data_by_condition_numeric_comparison_helper( prop, values, args, true );

            cb( result );
        },

        'get_data_by_map': function( prop, values, args, cb ) {
            var defaultResult = args['default'] || null;

            if ( _.isUndefined( args.map ) || _.isEmpty( args.map ) ) {
                cb( defaultResult );
                return;
            }

            var map      = args.map;
            var merge    = !! args.merge;
            var operator = ( args.operator && args.operator.toUpperCase() === 'OR' ) ? 'OR' : 'AND';
            var identifier, value;

            var result = null;

            var usedValues = [];
            for ( var i in Object.keys( values ) ) {
                identifier = Object.keys( values )[ i ];
                value      = values[ identifier ];

                if ( _.isBoolean( value ) ) {
                    value = value ? 1 : 0;
                }

                if ( _.isUndefined( map[ value ] ) ) {
                    continue;
                }

                if ( merge && ! _.contains( usedValues, value ) ) {
                    usedValues.push( value );
                    result = cbHelpers.merge_into_result( result, map[ value ], operator );
                } else {
                    usedValues.push( value );
                    if ( _.isObject( map[ value ] ) ) {
                        result = _.clone( map[ value ] );
                    } else {
                        result = map[ value ];
                    }
                }
            }

            if ( null === result ) {
                cb( defaultResult );
            } else {
                cb( result );
            }
        },

        'get_data_by_named_map': function( prop, values, args, cb, instanceId ) {
            var defaultResult = args['default'] || null;

            if ( _.isUndefined( args.named_map ) || _.isEmpty( args.named_map ) ) {
                cb( defaultResult );
                return;
            }

            var namedMap = args.named_map;
            var merge    = !! args.merge;
            var operator = ( args.operator && args.operator.toUpperCase() === 'OR' ) ? 'OR' : 'AND';
            var identifier, rawIdentifier, value, map;

            var result = null;

            var usedValues = {};
            for ( var i in Object.keys( values ) ) {
                identifier = Object.keys( values )[ i ];
                value      = values[ identifier ];

                if ( _.isUndefined( namedMap[ identifier ] ) ) {
                    rawIdentifier = identifier.replace( '-', '_' );
                    if ( _.isUndefined( namedMap[ rawIdentifier ] ) ) {
                        rawIdentifier = rawIdentifier.replace( instanceId + '_', '' );
                        if ( _.isUndefined( namedMap[ rawIdentifier ] ) ) {
                            continue;
                        }
                        map = namedMap[ rawIdentifier ];
                    } else {
                        map = namedMap[ rawIdentifier ];
                    }
                } else {
                    map = namedMap[ identifier ];
                }

                if ( _.isBoolean( value ) ) {
                    value = value ? 1 : 0;
                }

                usedValues[ identifier ] = [];
                if ( _.isUndefined( map[ value ] ) ) {
                    continue;
                }

                if ( merge && ! _.contains( usedValues[ identifier ], value ) ) {
                    usedValues[ identifier ].push( value );
                    result = cbHelpers.merge_into_result( result, map[ value ], operator );
                } else {
                    usedValues[ identifier ].push( value );
                    if ( _.isObject( map[ value ] ) ) {
                        result = _.clone( map[ value ] );
                    } else {
                        result = map[ value ];
                    }
                }
            }

            if ( null === result ) {
                cb( defaultResult );
            } else {
                cb( result );
            }
        }
    };

    var fieldsAPI = {};

    var drPriv = {
        callbacks: {},
        queues: {},
        queueCount: 0,
        queueTotal: 0
    };

    fieldsAPI.DependencyResolver = {
        startQueue: function( instanceId ) {
            drPriv.queueCount++;
            drPriv.queueTotal++;

            var queueIdentifier = 'queue' + drPriv.queueTotal;
            var queue = new fieldsAPI.DependencyResolverQueue( queueIdentifier, instanceId );

            drPriv.queues[ queueIdentifier ] = queue;

            return queue;
        },

        finishQueue: function( queueIdentifier ) {
            if ( _.isUndefined( drPriv.queues[ queueIdentifier ] ) ) {
                return;
            }

            delete drPriv.queues[ queueIdentifier ];

            drPriv.queueCount--;
        },

        addCallback: function( callbackName, callback ) {
            if ( ! _.isFunction( callback ) ) {
                return;
            }

            drPriv.callbacks[ callbackName ] = callback;
        },

        getCallback: function( callbackName ) {
            return drPriv.callbacks[ callbackName ];
        },

        loadCallbacks: function() {
            var names = Object.keys( defaultDependencyCallbacks );

            for ( var i in names ) {
                fieldsAPI.DependencyResolver.addCallback( names[ i ], defaultDependencyCallbacks[ names[ i ] ] );
            }

            $( document ).trigger( 'pluginLibFieldsAPIDependencyCallbacks', fieldsAPI.DependencyResolver );
        }
    };

    fieldsAPI.DependencyResolverQueue = function( queueIdentifier, instanceId ) {
        this.queueIdentifier = queueIdentifier;
        this.instanceId      = instanceId;
        this.queuedItems     = [];
        this.resolvedProps   = {};
        this.busyCount       = 0;
        this.finalizeCallback;
    };

    _.extend( fieldsAPI.DependencyResolverQueue.prototype, {
        add: function( targetId, prop, callback, values, args ) {
            callback = fieldsAPI.DependencyResolver.getCallback( callback );
            if ( ! callback ) {
                return;
            }

            this.queuedItems.push({
                targetId: targetId,
                prop: prop,
                callback: callback,
                values: values,
                args: args
            });
        },

        resolve: function( finalizeCallback ) {
            var queuedItem;

            this.busyCount = this.queuedItems.length;
            this.finalizeCallback = finalizeCallback;

            for ( var i in this.queuedItems ) {
                queuedItem = this.queuedItems[ i ];

                queuedItem.callback( queuedItem.prop, queuedItem.values, queuedItem.args, _.bind( this.resolved, this, queuedItem.targetId, queuedItem.prop ), this.instanceId );
            }
        },

        resolved: function( targetId, prop, propValue ) {
            if ( null !== propValue ) {
                if ( _.isUndefined( this.resolvedProps[ targetId ] ) ) {
                    this.resolvedProps[ targetId ] = {};
                }

                this.resolvedProps[ targetId ][ prop ] = propValue;
            }

            this.busyCount--;

            if ( 0 === this.busyCount ) {
                this.finalResolved();
            }
        },

        finalResolved: function() {
            fieldsAPI.DependencyResolver.finishQueue( this.queueIdentifier );

            this.finalizeCallback( this.resolvedProps );
        }
    });

    /**
     * pluginLibFieldsAPI.Field
     *
     * A field.
     *
     * A field has no persistence with the server.
     *
     * @class
     * @augments Backbone.Model
     */
    fieldsAPI.Field = Backbone.Model.extend({
        initialize: function() {
            this.on( 'change:display', this.onDisplayChange, this );
        },

        onDisplayChange: function( field, display ) {
            var wrapAttrs  = field.get( 'wrapAttrs' );
            var inputAttrs = field.get( 'inputAttrs' );

            if ( wrapAttrs ) {
                wrapAttrs['aria-hidden'] = display ? 'false' : 'true';
            }

            if ( inputAttrs ) {
                inputAttrs.tabindex = display ? '0' : '-1';

                if ( inputAttrs['data-required'] ) {
                    inputAttrs.required = display;
                }
            }
        },

        sync: function() {
            return false;
        }
    });

    /**
     * pluginLibFieldsAPI.FieldManager
     *
     * A collection of fields.
     *
     * This collection has no persistence with the server.
     *
     * @class
     * @augments Backbone.Collection
     *
     * @param {array}  [models]             Models to initialize with the collection.
     * @param {object} [options]            Options hash for the collection.
     * @param {string} [options.instanceId] Instance ID for the collection.
     *
     */
    fieldsAPI.FieldManager = Backbone.Collection.extend({
        /**
         * @type {pluginLibFieldsAPI.Field}
         */
        model: fieldsAPI.Field,

        /**
         * @param {Array} [models=[]] Array of models used to populate the collection.
         * @param {Object} [options={}]
         */
        initialize: function( models, options ) {
            options = options || {};

            if ( options.instanceId ) {
                this.instanceId = options.instanceId;
            }

            this.dependencyTriggers = {};

            this.on( 'update', this.updateDependencyTriggers, this );
            this.on( 'change:currentValue', this.triggerDependantsUpdate, this );
        },

        sync: function() {
            return false;
        },

        setupDependencyTriggers: function() {
            var field;

            for ( var i in this.models ) {
                field = this.models[ i ];

                this.addFieldDependencies( field.get( 'id' ), field.get( 'dependencies' ) );
            }
        },

        updateDependencyTriggers: function( collection, options ) {
            var field;

            for ( var i in options.added ) {
                field = options.added[ i ];

                this.addFieldDependencies( field.get( 'id' ), field.get( 'dependencies' ) );
            }

            for ( var j in options.removed ) {
                field = options.removed[ j ];

                this.removeFieldDependencies( field.get( 'id' ) );
            }
        },

        triggerDependantsUpdate: function( field, currentValue ) {
            var fieldId = field.get( 'id' );

            if ( ! _.isArray( this.dependencyTriggers[ fieldId ] ) ) {
                return;
            }

            var dependencyQueue = new fieldsAPI.DependencyResolver.startQueue( this.instanceId );
            var dependency;
            var currentValues;

            for ( var i in this.dependencyTriggers[ fieldId ] ) {
                dependency = this.dependencyTriggers[ fieldId ][ i ];
                currentValues = {};

                for ( var j in dependency.fields ) {
                    if ( dependency.fields[ j ] === fieldId ) {
                        currentValues[ fieldId ] = currentValue;
                    } else {
                        currentValues[ dependency.fields[ j ] ] = this.get( dependency.fields[ j ] ).get( 'currentValue' );
                    }
                }

                dependencyQueue.add( dependency.targetId, dependency.prop, dependency.callback, currentValues, dependency.args );
            }

            dependencyQueue.resolve( _.bind( this.updateDependants, this ) );
        },

        updateDependants: function( dependencyProps ) {
            _.each( dependencyProps, _.bind( function( props, targetId ) {
                this.get( targetId ).set( props );
            }, this ) );
        },

        addFieldDependencies: function( id, dependencies ) {
            if ( ! _.isArray( dependencies ) ) {
                return;
            }

            _.each( dependencies, _.bind( function( dependency ) {
                var fieldId;

                for ( var i in dependency.fields ) {
                    fieldId = dependency.fields[ i ];

                    if ( _.isUndefined( this.dependencyTriggers[ fieldId ] ) ) {
                        this.dependencyTriggers[ fieldId ] = [];
                    }

                    this.dependencyTriggers[ fieldId ].push({
                        targetId: id,
                        prop: dependency.prop,
                        callback: dependency.callback,
                        fields: dependency.fields,
                        args: dependency.args
                    });
                }
            }, this ) );
        },

        removeFieldDependencies: function( id ) {
            _.each( this.dependencyTriggers, _.bind( function( dependencies, fieldId ) {
                var newDependencies = [];

                for ( var i in dependencies ) {
                    if ( dependencies[ i ].targetId === id ) {
                        continue;
                    }

                    newDependencies.push( dependencies[ i ] );
                }

                if ( newDependencies.length ) {
                    this.dependencyTriggers[ fieldId ] = newDependencies;
                } else {
                    delete this.dependencyTriggers[ fieldId ];
                }
            }, this ) );
        }
    });

    function _getObjectReplaceableFields( obj ) {
        var fields = {};

        _.each( obj, function( value, key ) {
            if ( _.isObject( value ) && ! _.isArray( value ) ) {
                value = _getObjectReplaceableFields( value );
                if ( ! _.isEmpty( value ) ) {
                    fields[ key ] = value;
                }
            } else if ( _.isString( value ) ) {
                if ( value.match( /%([A-Za-z0-9]+)%/g ) ) {
                    fields[ key ] = value;
                }
            }
        });

        return fields;
    }

    function _replaceObjectFields( obj, replacements, fields ) {
        if ( _.isUndefined( fields ) ) {
            fields = _getObjectReplaceableFields( obj );
        }

        function _doReplacements( match, name ) {
            if ( ! _.isUndefined( replacements[ name ] ) ) {
                return replacements[ name ];
            }

            return match;
        }

        _.each( fields, function( value, key ) {
            if ( _.isObject( value ) ) {
                if ( ! _.isObject( obj[ key ] ) ) {
                    obj[ key ] = {};
                }

                _replaceObjectFields( obj[ key ], replacements, value );
            } else {
                obj[ key ] = value.replace( /%([A-Za-z0-9]+)%/g, _doReplacements );
            }
        });
    }

    function _generateItem( itemInitial, index ) {
        var newItem = _.deepClone( itemInitial );

        _replaceObjectFields( newItem, {
            index: index,
            indexPlus1: index + 1
        });

        return newItem;
    }

    function _adjustRepeatableIndexes( itemInitial, items, startIndex ) {
        if ( ! startIndex ) {
            startIndex = 0;
        }

        var fields = _getObjectReplaceableFields( itemInitial );

        for ( var i = startIndex; i < items.length; i++ ) {
            _replaceObjectFields( items[ i ], {
                index: i,
                indexPlus1: i + 1
            }, fields );
        }

        return items;
    }

    /**
     * pluginLibFieldsAPI.FieldView
     *
     * A field view.
     *
     * @class
     * @augments Backbone.View
     *
     * @param {object} [options]       Options hash for the view.
     * @param {object} [options.model] Field model.
     *
     */
    fieldsAPI.FieldView = Backbone.View.extend({
        /**
         * @param {Object} [options={}]
         */
        constructor: function( options ) {
            options = options || {};

            var model;

            if ( options.model ) {
                model = options.model;

                if ( ! options.el ) {
                    options.el = '#' + model.get( 'id' ) + '-wrap';
                }

                if ( ! options.labelTemplate ) {
                    options.labelTemplate = 'plugin-lib-field-' + model.get( 'slug' ) + '-label';
                }

                if ( ! options.contentTemplate ) {
                    options.contentTemplate = 'plugin-lib-field-' + model.get( 'slug' ) + '-content';
                }

                if ( ! options.repeatableItemTemplate ) {
                    options.repeatableItemTemplate = 'plugin-lib-field-' + model.get( 'slug' ) + '-repeatable-item';
                }

                var dependencies = model.get( 'dependencies' );
                if ( _.isArray( dependencies ) ) {
                    var methodName;
                    for ( var i in dependencies ) {
                        methodName = 'apply' + ( '_' + dependencies[ i ].prop ).replace( /_([a-zA-Z0-9])/g, function( matches, part ) {
                            return part.toUpperCase();
                        });

                        if ( _.isFunction( this[ methodName ] ) ) {
                            model.on( 'change:' + dependencies[ i ].prop, this[ methodName ], this );
                        }
                    }
                }

                this.events = this.getEvents( model );
            }

            if ( options.labelTemplate ) {
                this.labelTemplate = wp.template( options.labelTemplate );
            }

            if ( options.contentTemplate ) {
                this.contentTemplate = wp.template( options.contentTemplate );
            }

            if ( options.repeatableItemTemplate ) {
                this.repeatableItemTemplate = wp.template( options.repeatableItemTemplate );
            }

            if ( this.preRender ) {
                this.on( 'preRender', this.preRender, this );
            }

            if ( this.postRender ) {
                this.on( 'postRender', this.postRender, this );
            }

            Backbone.View.apply( this, arguments );
        },

        initialize: function() {
            var view         = this;
            var $contentWrap = this.$( '.content-wrap' );
            var content      = $contentWrap.html();

            if ( _.isString( content ) && ! content.trim().length || ! content ) {
                return;
            }

            if ( this.model.get( 'repeatable' ) && $contentWrap.find( '.plugin-lib-repeatable-wrap' ).length ) {
                $contentWrap.find( '.plugin-lib-repeatable-item' ).each( function() {
                    view.trigger( 'postRender', $( this ) );
                });
            } else {
                this.trigger( 'postRender', $contentWrap );
            }
        },

        renderLabel: function() {
            var $labelWrap;

            if ( this.labelTemplate ) {
                $labelWrap = this.$( '#' + this.model.get( 'id' ) + '-label-wrap' );

                $labelWrap.replaceWith( this.labelTemplate( this.model.toJSON() ) );
            }
        },

        renderContent: function() {
            var view = this;
            var $contentWrap;

            if ( this.contentTemplate ) {
                $contentWrap = this.$( '#' + this.model.get( 'id' ) + '-content-wrap' );

                if ( this.model.get( 'repeatable' ) && $contentWrap.find( '.plugin-lib-repeatable-wrap' ).length ) {
                    $contentWrap.find( '.plugin-lib-repeatable-item' ).each( function() {
                        view.trigger( 'preRender', $( this ) );
                    });
                } else {
                    this.trigger( 'preRender', $contentWrap );
                }
                this.undelegateEvents();

                $contentWrap.replaceWith( this.contentTemplate( this.model.toJSON() ) );

                $contentWrap = this.$( '#' + this.model.get( 'id' ) + '-content-wrap' );

                this.delegateEvents();
                if ( this.model.get( 'repeatable' ) && $contentWrap.find( '.plugin-lib-repeatable-wrap' ).length ) {
                    $contentWrap.find( '.plugin-lib-repeatable-item' ).each( function() {
                        view.trigger( 'postRender', $( this ) );
                    });
                } else {
                    this.trigger( 'postRender', $contentWrap );
                }
            }
        },

        changeValue: function( e ) {
            var value = this.getInputValue( this.$( e.target ) );

            this.model.set( 'currentValue', value );

            this.model.trigger( 'changeValue', this.model, value );
        },

        changeItemValue: function( e ) {
            var $itemInput = this.$( e.target );
            var $item      = $itemInput.parents( '.plugin-lib-repeatable-item' );
            var itemIndex  = $item.parent().children().index( $item );

            var items = this.model.get( 'items' );
            if ( ! items[ itemIndex ] ) {
                return;
            }

            items[ itemIndex ].currentValue = this.getInputValue( $itemInput );

            this.model.set( 'items', items );

            this.model.trigger( 'changeItemValue', this.model, items[ itemIndex ], items[ itemIndex ].currentValue );
        },

        addItem: function( e ) {
            var limit = this.model.get( 'repeatableLimit' );
            var items   = this.model.get( 'items' );
            if ( limit > 0 && items.length >= limit ) {
                return;
            }

            var $button   = this.$( e.target );
            var $wrap     = this.$( $button.data( 'target' ) );
            var itemIndex = $wrap.children().length;

            $button.prop( 'disabled', true );

            var newItem = _generateItem( this.model.get( 'itemInitial' ), itemIndex );

            items.push( newItem );

            var $newItem = $( this.repeatableItemTemplate( newItem ) );

            $wrap.append( $newItem );

            this.trigger( 'postRender', $newItem );

            this.model.set( 'items', items );

            if ( limit > 0 && items.length >= limit ) {
                $button.hide();
            } else {
                $button.prop( 'disabled', false );
            }

            this.model.trigger( 'addItem', this.model, newItem );
        },

        addItemOnEnter: function( e ) {
            if ( e.which !== 13 ) {
                return;
            }

            var $item = this.$( e.target ).parents( '.plugin-lib-repeatable-item' );

            if ( $item.find( '.plugin-lib-control' ).length > 1 ) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            this.addItem({
                target: this.$( e.target ).parents( '.plugin-lib-repeatable-wrap' ).next( '.plugin-lib-repeatable-add-button' )[0]
            });

            if ( $item.next().length ) {
                $item.next().find( '.plugin-lib-control' ).focus();
            }
        },

        removeItem: function( e ) {
            var self = this;

            var limit = this.model.get( 'repeatableLimit' );
            var items = this.model.get( 'items' );

            var $button   = this.$( e.target );
            var $item     = this.$( $button.data( 'target' ) );
            var $wrap     = $item.parent();
            var itemIndex = $wrap.children().index( $item );
            var itemToRemove;

            $button.prop( 'disabled', true );

            if ( ! items[ itemIndex ] ) {
                return;
            }

            itemToRemove = items[ itemIndex ];

            items.splice( itemIndex, 1 );

            self.trigger( 'preRender', $item );

            $item.remove();

            if ( itemIndex < items.length ) {
                items = _adjustRepeatableIndexes( this.model.get( 'itemInitial' ), items, itemIndex );
                $wrap.children().each( function( index ) {
                    if ( index < itemIndex ) {
                        return;
                    }

                    var $itemToAdjust = $( this );
                    var $newItem      = $( self.repeatableItemTemplate( items[ index ] ) );

                    self.trigger( 'preRender', $itemToAdjust );
                    self.undelegateEvents();

                    $itemToAdjust.replaceWith( $newItem );

                    self.delegateEvents();
                    self.trigger( 'postRender', $newItem );
                });
            }

            this.model.set( 'items', items );

            if ( limit > 0 && items.length < limit ) {
                $( 'button[data-target="#' + $wrap.attr( 'id' ) + '"]' ).prop( 'disabled', false ).show();
            }

            this.model.trigger( 'removeItem', this.model, itemToRemove );
        },

        remove: function() {
            if ( this.preRender ) {
                this.off( 'preRender', this.preRender, this );
            }

            if ( this.postRender ) {
                this.off( 'postRender', this.postRender, this );
            }

            return Backbone.View.prototype.remove.apply( this, arguments );
        },

        getEvents: function( model ) {
            if ( model.get( 'repeatable' ) && _.isArray( model.get( 'items' ) ) ) {
                return {
                    'click .plugin-lib-repeatable-add-button': 'addItem',
                    'keydown .plugin-lib-repeatable-item .plugin-lib-control': 'addItemOnEnter',
                    'click .plugin-lib-repeatable-remove-button': 'removeItem',
                    'change :input': 'changeItemValue'
                };
            }

            return {
                'change :input': 'changeValue'
            };
        },

        getInputValue: function( $input ) {
            var currentValue = null;
            var dateParts;

            if ( ( $input.is( ':checkbox' ) && '[]' === $input.attr( 'name' ).substr( -2 ) ) ) {
                currentValue = [];

                $input.parent().each( ':checkbox:checked', _.bind( function( index, element ) {
                    currentValue.push( this.$( element ).val() );
                }, this ) );
            } else if ( $input.is( ':radio' ) ) {
                currentValue = $input.parent().find( ':radio:checked' ).val();
            } else if ( $input.is( ':checkbox' ) ) {
                if ( $input.prop( 'checked' ) ) {
                    currentValue = true;
                } else {
                    currentValue = false;
                }
            } else if ( $input.is( 'select' ) && $input.prop( 'multiple' ) ) {
                currentValue = [];

                $input.each( 'option:selected', _.bind( function( index, element ) {
                    currentValue.push( this.$( element ).val() );
                }, this ) );
            } else if ( $.fn.datetimepicker && $input.data( 'xdsoft_datetimepicker' ) ) {
                currentValue = $input.datetimepicker( 'getValue' );

                if ( currentValue ) {
                    dateParts    = {
                        year:   ( '' + currentValue.getFullYear() ).padStart( 4, '0' ),
                        month:  ( '' + ( currentValue.getMonth() + 1 ) ).padStart( 2, '0' ),
                        day:    ( '' + currentValue.getDate() ).padStart( 2, '0' ),
                        hour:   ( '' + currentValue.getHours() ).padStart( 2, '0' ),
                        minute: ( '' + currentValue.getMinutes() ).padStart( 2, '0' ),
                        second: ( '' + currentValue.getSeconds() ).padStart( 2, '0' )
                    };

                    switch ( $input.data( 'store' ) ) {
                        case 'date':
                            currentValue = dateParts.year + '-' + dateParts.month + '-' + dateParts.day;
                            break;
                        case 'time':
                            currentValue = dateParts.hour + ':' + dateParts.minute + ':' + dateParts.second;
                            break;
                        default:
                            currentValue = dateParts.year + '-' + dateParts.month + '-' + dateParts.day + ' ' + dateParts.hour + ':' + dateParts.minute + ':' + dateParts.second;
                    }
                } else {
                    currentValue = null;
                }
            } else {
                currentValue = $input.val();
            }

            return currentValue;
        },

        applyLabel: function( field, label ) {
            var labelAttrs = field.get( 'labelAttrs' );
            var $label = this.$( labelAttrs.id ? '#' + labelAttrs.id : '.plugin-lib-label:first' );

            if ( $label.length ) {
                $label.html( label );
            } else {
                this.renderLabel();
            }
        },

        applyDescription: function( field, description ) {
            var $description = this.$( '#' + field.get( 'id' ) + '-description' );

            if ( $description.length ) {
                if ( description.length ) {
                    $description.html( description );
                } else {
                    $description.remove();
                }
            } else {
                if ( description.length ) {
                    this.renderContent();
                }
            }
        },

        applyDisplay: function( field, display ) {
            var $wrap = this.$el;

            $wrap.find( '.plugin-lib-control' ).each( function() {
                var $this    = $( this );
                var $select2 = $this.data( 'select2' ) ? $this.data( 'select2' ).$selection : undefined;

                if ( $select2 ) {
                    $select2.attr( 'tabindex', display ? '0' : '-1' );
                } else {
                    $this.attr( 'tabindex', display ? '0' : '-1' );
                }

                if ( $this.data( 'required' ) ) {
                    $( this ).prop( 'required', display );
                }
            });

            if ( display ) {
                $wrap.addClass( 'plugin-lib-toggling' ).removeClass( 'plugin-lib-hidden' ).attr( 'aria-hidden', 'false' );
                setTimeout( function() {
                    $wrap.removeClass( 'plugin-lib-toggling' );
                }, 800 );
            } else {
                $wrap.addClass( 'plugin-lib-toggling' ).addClass( 'plugin-lib-hidden' ).attr( 'aria-hidden', 'true' );
                setTimeout( function() {
                    $wrap.removeClass( 'plugin-lib-toggling' );
                }, 800 );
            }
        },

        applyDefault: function( field, defaultVal ) {
            // Do nothing.
        },

        applyChoices: function( field, choices ) {
            this.renderContent();
        },

        applyOptgroups: function( field, optgroups ) {
            this.renderContent();
        },

        applyUnit: function( field, unit ) {
            var $unit = this.$( '.plugin-lib-unit' );

            if ( $unit.length ) {
                if ( unit.length ) {
                    $unit.html( unit );
                } else {
                    $unit.remove();
                }
            } else {
                if ( unit.length ) {
                    this.$( '#' + field.get( 'id' ) ).after( '<span class="plugin-lib-unit">' + unit + '</span>' );
                }
            }
        }
    });

    fieldsAPI.FieldView.WYSIWYGFieldView = fieldsAPI.FieldView.extend({
        preRender: function( $el ) {
            var editorId = $el.find( '.plugin-lib-control' ).attr( 'id' );
            var editor;

            if ( wp.editor && wp.editor.remove ) {
                wp.editor.remove( editorId );
                return;
            }

            editor = window.tinymce.get( editorId );
            if ( editor ) {
                editor.save();
                editor.remove();

                delete window.tinyMCEPreInit.mceInit[ editorId ];
                delete window.tinyMCEPreInit.qtInit[ editorId ];
            }
        },

        postRender: function( $el ) {
            var editorId = $el.find( '.plugin-lib-control' ).attr( 'id' );
            var editorData = $el.find( '.plugin-lib-control' ).data();
            var editorSettings;

            if ( wp.editor && wp.editor.initialize ) {
                editorSettings = _.extend( {}, wp.editor.getDefaultSettings() );
                editorSettings.tinymce.wpautop = !! editorData.wpautop;

                if ( 'simple' !== editorData.buttonMode ) {
                    editorSettings.tinymce.toolbar1 = 'formatselect,bold,italic,bullist,numlist,blockquote,alignleft,aligncenter,alignright,link,unlink,spellchecker,wp_adv';
                    editorSettings.tinymce.toolbar2 = 'strikethrough,hr,forecolor,pastetext,removeformat,charmap,outdent,indent,undo,redo';
                    editorSettings.quicktags.buttons = 'strong,em,link,block,del,ins,img,ul,ol,li,code,close';
                }

                if ( editorData.mediaButtons ) {
                    editorSettings.tinymce.plugins += ',image';
                    editorSettings.tinymce.setup = function() {
                        var $tools = $el.find( '.wp-editor-wrap .wp-editor-tools' );
                        var $mediaButtons = $( '<div>' ).attr({
                            id: 'wp-' + editorId + '-media-buttons',
                            class: 'wp-media-buttons'
                        }).append(
                            $( '<button>' ).attr({
                                type: 'button',
                                class: 'button insert-media add_media',
                                'data-editor': editorId
                            }).html( '<span class="wp-media-buttons-icon"></span> ' + fieldsAPIData.i18nWYSIWYGAddMediaButton )
                        );

                        $tools.prepend( $mediaButtons );

                        $el.find( '.plugin-lib-control' ).trigger( 'editorInitialized' );
                    };
                }

                wp.editor.initialize( editorId, editorSettings );
                return;
            }

            if ( _.isUndefined( window.tinyMCEPreInit.mceInit[ editorId ] ) || _.isUndefined( window.tinyMCEPreInit.qtInit[ editorId ] ) ) {
                window.tinyMCEPreInit.mceInit[ editorId ] = this.model.get( 'tinyMCESettings' );
                window.tinyMCEPreInit.qtInit[ editorId ]  = this.model.get( 'quickTagsSettings' );

                window.tinymce.init( window.tinyMCEPreInit.mceInit[ editorId ] );
                window.quicktags( window.tinyMCEPreInit.qtInit[ editorId ] );
            }
        }
    });

    fieldsAPI.FieldView.SelectFieldView = fieldsAPI.FieldView.extend({
        preRender: function( $el ) {
            $el.find( 'select.plugin-lib-control' ).select2( 'destroy' );
        },

        postRender: function( $el ) {
            $el.find( 'select.plugin-lib-control' ).select2({
                width: 'element',
                closeOnSelect: true,
                minimumResultsForSearch: 8
            });
        }
    });

    function getDataField( fieldPath, data ) {
        if ( _.isEmpty( fieldPath ) ) {
            return data;
        }

        if ( ! _.isObject( data ) ) {
            return undefined;
        }

        var currentField = fieldPath.shift();

        if ( ! data[ currentField ] ) {
            return undefined;
        }

        return getDataField( fieldPath, data[ currentField ] );
    }

    function replacePlaceholdersWithData( placeholderString, data ) {
        var replaced = placeholderString.replace( /\%([A-Za-z0-9_\.]+)\%/g, function( match, placeholder ) {
            var fieldPath = placeholder.split( '.' );

            var value = getDataField( fieldPath, data );
            if ( _.isObject( value ) ) {
                return match;
            }

            value = '' + value;

            if ( _.isEmpty( value ) ) {
                return match;
            }

            return value;
        });

        if ( -1 < replaced.search( '%' ) ) {
            return '';
        }

        return replaced;
    }

    fieldsAPI.FieldView.AutocompleteFieldView = fieldsAPI.FieldView.extend({
        preRender: function( $el ) {
            $el.find( '.plugin-lib-control' ).autocomplete( 'destroy' );

            this.model.set( 'currentLabel', '' );
        },

        postRender: function( $el ) {
            var autocompleteArgs = this.model.get( 'autocomplete' );
            if ( ! autocompleteArgs ) {
                autocompleteArgs = this.model.get( 'itemInitial' ).autocomplete;
            }

            $el.find( '.plugin-lib-control' ).autocomplete({
                source: function( request, response ) {
                    var restUrl = fieldsAPIData.restUrl + autocompleteArgs.restPlaceholderSearchRoute.replace( '%search%', request.term );
                    $.ajax( restUrl, {
                        method: 'GET',
                        dataType: 'json',
                        headers: {
                            'X-WP-Nonce': fieldsAPIData.restNonce
                        },
                        success: function( data ) {
                            var results = [];
                            for ( var i in data ) {
                                var value = replacePlaceholdersWithData( autocompleteArgs.valueGenerator, data[ i ] );
                                var label = replacePlaceholdersWithData( autocompleteArgs.labelGenerator, data[ i ] );

                                if ( ! _.isEmpty( value ) && ! _.isEmpty( label ) ) {
                                    results.push({
                                        value: value,
                                        label: label
                                    });
                                }
                            }

                            response( results );
                        },
                        error: function( xhr ) {
                            if ( _.isObject( xhr.responseJSON ) && null !== xhr.responseJSON && ! _.isEmpty( xhr.responseJSON.message ) ) {
                                console.error( xhr.responseJSON.message );
                            }

                            response([]);
                        },
                        complete: function( xhr ) {
                            var newNonce = xhr.getResponseHeader( 'X-WP-Nonce' );
                            if ( newNonce ) {
                                fieldsAPIData.restNonce = newNonce;
                            }
                        }
                    });
                },
                select: function( e, ui ) {
                    e.preventDefault();

                    $el.find( '.plugin-lib-control' ).val( ui.item.label );
                    $el.find( '.plugin-lib-control' ).prev().val( ui.item.value ).trigger( 'change' );
                },
                focus: function( e, ui ) {
                    e.preventDefault();

                    $el.find( '.plugin-lib-control' ).val( ui.item.label );
                }
            });
        }
    });

    var dtpLocaleSet = false;

    fieldsAPI.FieldView.DatetimeFieldView = fieldsAPI.FieldView.extend({
        constructor: function() {
            if ( ! dtpLocaleSet ) {
                $.datetimepicker.setLocale( fieldsAPIData.language );
                dtpLocaleSet = true;
            }

            fieldsAPI.FieldView.apply( this, arguments );
        },

        preRender: function( $el ) {
            $el.find( '.plugin-lib-control' ).datetimepicker( 'destroy' );
        },

        postRender: function( $el ) {
            var $hidden = $el.find( '.plugin-lib-control' ).prev();
            var store   = $el.find( '.plugin-lib-control' ).data( 'store' );
            var options = {
                closeOnDateSelect: true,
                formatDate: 'Y-m-d',
                formatTime: 'H:i',
                dayOfWeekStart: fieldsAPIData.startOfWeek,
                scrollMonth: false,
                scrollTime: false,
                scrollInput: false,
                validateOnBlur: false
            };

            switch ( store ) {
                case 'time':
                    options.format     = fieldsAPIData.timeFormat;
                    options.datepicker = false;
                    break;
                case 'date':
                    options.format     = fieldsAPIData.dateFormat;
                    options.timepicker = false;
                    break;
                default:
                    options.format = fieldsAPIData.datetimeFormat;
            }

            function parseDateTimeDefault( dateInstance ) {
                var currentValue = '';
                var dateParts;

                if ( dateInstance ) {
                    dateParts    = {
                        year:   ( '' + dateInstance.getFullYear() ).padStart( 4, '0' ),
                        month:  ( '' + ( dateInstance.getMonth() + 1 ) ).padStart( 2, '0' ),
                        day:    ( '' + dateInstance.getDate() ).padStart( 2, '0' ),
                        hour:   ( '' + dateInstance.getHours() ).padStart( 2, '0' ),
                        minute: ( '' + dateInstance.getMinutes() ).padStart( 2, '0' ),
                        second: ( '' + dateInstance.getSeconds() ).padStart( 2, '0' )
                    };

                    switch ( store ) {
                        case 'date':
                            currentValue = dateParts.year + '-' + dateParts.month + '-' + dateParts.day;
                            break;
                        case 'time':
                            currentValue = dateParts.hour + ':' + dateParts.minute + ':' + dateParts.second;
                            break;
                        default:
                            currentValue = dateParts.year + '-' + dateParts.month + '-' + dateParts.day + ' ' + dateParts.hour + ':' + dateParts.minute + ':' + dateParts.second;
                    }
                }

                return currentValue;
            }

            options.onShow = function( ct, $input ) {
                var newOptions = {};
                var value      = $hidden.val();
                var helper;

                if ( value ) {
                    newOptions.value = new Date( value );
                }

                if ( $input.attr( 'min' ) ) {
                    helper = $input.attr( 'min' ).split( ' ' );
                    if ( helper.length === 2 ) {
                        newOptions.minDate = helper[0];
                        newOptions.minTime = helper[1];
                    } else if( helper.length === 1 ) {
                        if ( 'time' === store ) {
                            newOptions.minTime = helper[0];
                        } else {
                            newOptions.minDate = helper[0];
                        }
                    }
                }

                if ( $input.attr( 'max' ) ) {
                    helper = $input.attr( 'max' ).split( ' ' );
                    if ( helper.length === 2 ) {
                        newOptions.maxDate = helper[0];
                        newOptions.maxTime = helper[1];
                    } else if( helper.length === 1 ) {
                        if ( 'time' === store ) {
                            newOptions.maxTime = helper[0];
                        } else {
                            newOptions.maxDate = helper[0];
                        }
                    }
                }

                if ( 'date' !== store && $input.attr( 'step' ) ) {
                    newOptions.step = parseInt( $input.attr( 'step' ), 10 );
                }

                this.setOptions( newOptions );
            };

            options.onChangeDateTime = function( currentValue ) {
                currentValue = parseDateTimeDefault( currentValue );
                $hidden.val( currentValue ).trigger( 'change' );
            };

            $el.find( '.plugin-lib-control' )
                .datetimepicker( options )
                .on( 'change', function( e ) {
                    var formattedValue = this.value;
                    var currentValue;

                    if ( ! formattedValue ) {
                        currentValue = '';
                    } else {
                        formattedValue = formattedValue.trim();

                        if ( ! formattedValue.length ) {
                            currentValue = '';
                        } else {
                            formattedValue = new Date( formattedValue );
                            if ( ! isNaN( formattedValue.valueOf() ) ) {
                                currentValue = parseDateTimeDefault( formattedValue );
                            }
                        }
                    }

                    if ( 'undefined' !== typeof currentValue ) {
                        $hidden.val( currentValue ).trigger( 'change' );
                    }
                });
        }
    });

    fieldsAPI.FieldView.ColorFieldView = fieldsAPI.FieldView.extend({
        preRender: function( $el ) {
            $el.find( '.plugin-lib-control' ).wpColorPicker( 'destroy' );
        },

        postRender: function( $el ) {
            $el.find( '.plugin-lib-control' ).wpColorPicker({
                mode: 'hsv'
            });
        }
    });

    fieldsAPI.FieldView.MediaFieldView = fieldsAPI.FieldView.extend({
        preRender: function( $el ) {
            $el.find( '.plugin-lib-control' ).wpMediaPicker( 'destroy' );
        },

        postRender: function( $el ) {
            $el.find( '.plugin-lib-control' ).wpMediaPicker({
                filterable: false,
                label_add: fieldsAPIData.i18nMediaAddButton,
                label_replace: fieldsAPIData.i18nMediaReplaceButton,
                label_remove: fieldsAPIData.i18nMediaRemoveButton,
                label_modal: fieldsAPIData.i18nMediaModalHeading,
                label_button: fieldsAPIData.i18nMediaModalButton
            });
        }
    });

    fieldsAPI.FieldView.MapFieldView = fieldsAPI.FieldView.extend({
        preRender: function( $el ) {
            $el.find( '.plugin-lib-control' ).wpMapPicker( 'destroy' );
        },

        postRender: function( $el ) {
            $el.find( '.plugin-lib-control' ).wpMapPicker();
        }
    });

    fieldsAPI.FieldView.GroupFieldView = fieldsAPI.FieldView.extend({
        changeValue: function( e ) {
            var $fieldInput = this.$( e.target );
            var fieldId = $fieldInput.attr( 'id' );
            var newValue = this.getInputValue( $fieldInput );

            var currentValue = this.model.get( 'currentValue' );
            var fields = this.model.get( 'fields' );

            var fieldKeys = Object.keys( fields );
            var data, id;
            for ( var i in fieldKeys ) {
                id = fieldKeys[ i ];
                data = fields[ id ];

                if ( data.id === fieldId ) {
                    fields[ id ].currentValue = newValue;
                    currentValue[ id ] = newValue;
                    break;
                }
            }

            this.model.set( 'fields', fields );
            this.model.set( 'currentValue', currentValue );
        },

        changeItemValue: function( e ) {
            var $itemFieldInput = this.$( e.target );
            var itemFieldId = $itemFieldInput.attr( 'id' );
            var newValue = this.getInputValue( $itemFieldInput );

            var $itemGroup = $itemFieldInput.parents( '.plugin-lib-group-control' );
            var $item      = $itemGroup.parents( '.plugin-lib-repeatable-item' );
            var itemIndex  = $item.parent().children().index( $item );

            var items = this.model.get( 'items' );
            if ( ! items[ itemIndex ] ) {
                return;
            }

            var fieldKeys = Object.keys( items[ itemIndex ].fields );
            var data, id;
            for ( var i in fieldKeys ) {
                id = fieldKeys[ i ];
                data = items[ itemIndex ].fields[ id ];

                if ( data.id === itemFieldId ) {
                    items[ itemIndex ].fields[ id ].currentValue = newValue;
                    items[ itemIndex ].currentValue[ id ] = newValue;
                    break;
                }

                id = undefined;
            }

            if ( ! id ) {
                return;
            }

            this.model.set( 'items', items );

            this.model.trigger( 'changeItemValue', this.model, items[ itemIndex ], items[ itemIndex ].currentValue );
            this.model.trigger( 'changeItemValue:' + id, this.model, items[ itemIndex ], items[ itemIndex ].currentValue );
        },

        preRender: function( $el ) {
            var fields;

            if ( this.model.get( 'repeatable' ) && _.isArray( this.model.get( 'items' ) ) ) {
                var itemIndex = $el.parent().children().index( $el );
                var items = this.model.get( 'items' );
                if ( items[ itemIndex ] ) {
                    fields = items[ itemIndex ].fields;
                }
            } else {
                fields = this.model.get( 'fields' );
            }

            if ( ! fields ) {
                return;
            }

            _.each( fields, function( data ) {
                if ( ! data.backboneView || 'FieldView' === data.backboneView ) {
                    return;
                }

                if ( ! fieldsAPI.FieldView[ data.backboneView ] ) {
                    return;
                }

                if ( ! fieldsAPI.FieldView[ data.backboneView ].prototype.preRender ) {
                    return;
                }

                var $subel = $el.find( '#' + data.wrapAttrs.id );

                fieldsAPI.FieldView[ data.backboneView ].prototype.preRender.apply( this, [ $subel ] );
            });
        },

        postRender: function( $el ) {
            var fields;

            if ( this.model.get( 'repeatable' ) && _.isArray( this.model.get( 'items' ) ) ) {
                var itemIndex = $el.parent().children().index( $el );
                var items = this.model.get( 'items' );
                if ( items[ itemIndex ] ) {
                    fields = items[ itemIndex ].fields;
                }
            } else {
                fields = this.model.get( 'fields' );
            }

            if ( ! fields ) {
                return;
            }

            _.each( fields, function( data ) {
                if ( ! data.backboneView || 'FieldView' === data.backboneView ) {
                    return;
                }

                if ( ! fieldsAPI.FieldView[ data.backboneView ] ) {
                    return;
                }

                if ( ! fieldsAPI.FieldView[ data.backboneView ].prototype.postRender ) {
                    return;
                }

                var $subel = $el.find( '#' + data.wrapAttrs.id );

                fieldsAPI.FieldView[ data.backboneView ].prototype.postRender.apply( this, [ $subel ] );
            });
        }
    });

    fieldsAPI.FieldManager.instances = {};

    $( document ).ready( function() {
        fieldsAPI.DependencyResolver.loadCallbacks();

        _.each( fieldsAPIData.field_managers, function( instance, instanceId ) {
            if ( instance.skip_initialization ) {
                return;
            }

            fieldsAPI.FieldManager.instances[ instanceId ] = new fieldsAPI.FieldManager( _.values( instance.fields ), {
                instanceId: instanceId
            });
            fieldsAPI.FieldManager.instances[ instanceId ].setupDependencyTriggers();

            _.each( fieldsAPI.FieldManager.instances[ instanceId ].models, function( field ) {
                var viewClassName = field.get( 'backboneView' );
                var FieldView     = fieldsAPI.FieldView;

                if ( viewClassName && 'FieldView' !== viewClassName && fieldsAPI.FieldView[ viewClassName ] ) {
                    FieldView = fieldsAPI.FieldView[ viewClassName ];
                }

                new FieldView({
                    model: field
                });
            });
        });
    });

    exports.pluginLibFieldsAPI = fieldsAPI;

}( window, jQuery, _, Backbone, wp, pluginLibFieldsAPIData ) );