/* global fusionBuilderText, fusionGlobalManager, FusionApp, FusionPageBuilderViewManager, fusionAllElements, FusionPageBuilderApp, FusionEvents, fusionAppConfig */
/* eslint no-empty-function: 0 */
/* eslint no-shadow: 0 */
var FusionPageBuilder = FusionPageBuilder || {};

( function() {

	jQuery( document ).ready( function() {

		// Builder Element View
		FusionPageBuilder.BaseView = window.wp.Backbone.View.extend( {

			modalDialogMoreView: null,

			events: {
			},

			/**
			 * Init.
			 *
			 * @since 2.0.0
			 * @return {void}
			 */
			initialize: function() {
			},

			/**
			 * Before initial render.
			 *
			 * @since 2.0.0
			 * @return {void}
			 */
			beforeRender: function() {
			},

			/**
			 * Filters render markup.
			 *
			 * @since 2.0.0
			 * @return null
			 */
			filterRender: function( $markup ) {
				return $markup;
			},

			/**
			 * Runs during render() call.
			 *
			 * @since 2.0.0
			 * @return null
			 */
			onRender: function() {
			},

			/**
				* Runs during initialize() call.
			 *
			 * @since 2.0.0
			 * @return null
			 */
			onInit: function() {
			},

			/**
			 * Runs just before view is removed.
			 *
			 * @since 2.0.0
			 * @return null
			 */
			beforeRemove: function() {
			},

			/**
			 * Runs just after render on cancel.
			 *
			 * @since 2.0.0
			 * @return null
			 */
			onCancel: function() {
			},

			/**
			 * Runs just after render or cancel.
			 *
			 * @since 3.0.2
			 * @return null
			 */
			beforeGenerateShortcode: function() {
				var elementType = this.model.get( 'element_type' ),
					options     = fusionAllElements[ elementType ].params,
					values      = jQuery.extend( true, {}, fusionAllElements[ elementType ].defaults, _.fusionCleanParameters( this.model.get( 'params' ) ) ),
					self        = this,
					iconWithoutFusionPrefix;

				if ( 'object' !== typeof options ) {
					return;
				}

				// If images needs replaced lets check element to see if we have media being used to add to object.
				if ( 'undefined' !== typeof FusionApp.data.replaceAssets && FusionApp.data.replaceAssets && ( 'undefined' !== typeof FusionApp.data.fusion_element_type || 'fusion_template' === FusionApp.getPost( 'post_type' ) ) ) {

					this.mapStudioImages( options, values );

					if ( 'undefined' !== typeof this.model.get( 'multi' ) && 'multi_element_parent' === this.model.get( 'multi' ) ) {
						this.model.children.each( function( child ) {
							var elementType = child.attributes.element_type,
								childOptions = fusionAllElements[ elementType ].params,
								childValues  = jQuery.extend( true, {}, fusionAllElements[ elementType ].defaults, _.fusionCleanParameters( child.attributes.params ) );

							self.mapStudioImages( childOptions, childValues );
						} );
					}

					if ( 'fusion_form' === elementType && '' !== values.form_post_id ) {
						// If its not within object already, add it.
						if ( 'undefined' === typeof FusionPageBuilderApp.mediaMap.forms[ values.form_post_id ] ) {
							FusionPageBuilderApp.mediaMap.forms[ values.form_post_id ] = true;
						}
					}

					// Add custom icons that used in forms to media map.
					if ( this.isString( elementType ) && elementType.startsWith( 'fusion_form_' ) && this.isString( values.input_field_icon ) && 'fusion-prefix-' === values.input_field_icon.substr( 0, 14 ) ) {
						if ( 'undefined' !== typeof fusionAppConfig.customIcons ) {
							iconWithoutFusionPrefix = values.input_field_icon.substr( 14 );

							// TODO: try to optimize this check.
							jQuery.each( fusionAppConfig.customIcons, function( iconPostName, iconSet ) {

							if ( 0 === iconWithoutFusionPrefix.indexOf( iconSet.css_prefix ) ) {
									FusionPageBuilderApp.mediaMap.icons[ iconSet.post_id ] = iconSet.css_prefix;
									return false;
								}
							} );
						}
					}

				}
			},

			/**
			 * Add studio images to media map.
			 * @param {Object} options
			 * @param {Object} values
			 * @returns void
			 */
			mapStudioImages: function( options, values ) {

				if ( 'object' !== typeof options ) {
					return;
				}

				// If images needs replaced lets check element to see if we have media being used to add to object.
				_.each( options, function( option ) {
					var value;
					if ( 'upload' === option.type && 'undefined' !== typeof values[ option.param_name ] && '' !== values[ option.param_name ] ) {
						value = values[ option.param_name ];

						if ( 'undefined' === typeof value || 'undefined' === value ) {
							return;
						}

						// If its not within object already, add it.
						if ( 'undefined' === typeof FusionPageBuilderApp.mediaMap.images[ value ] ) {
							FusionPageBuilderApp.mediaMap.images[ value ] = true;
						}

						// Check if we have an image ID for this param.
						if ( 'undefined' !== typeof values[ option.param_name + '_id' ] && '' !== values[ option.param_name + '_id' ] )	{
							if ( 'object' !== typeof FusionPageBuilderApp.mediaMap.images[ value ] ) {
								FusionPageBuilderApp.mediaMap.images[ value ] = {};
							}
							FusionPageBuilderApp.mediaMap.images[ value ][ option.param_name + '_id' ] = values[ option.param_name + '_id' ];
						}

					} else if ( 'upload_images' === option.type && 'undefined' !== typeof values[ option.param_name ] && '' !== values[ option.param_name ] ) {
						if ( 'object' !== typeof FusionPageBuilderApp.mediaMap.multiple_images ) {
							FusionPageBuilderApp.mediaMap.multiple_images = {};
						}

						const key = option.param_name + '-' + values[ option.param_name ];

						if ( 'object' !== typeof FusionPageBuilderApp.mediaMap.multiple_images[ key ] ) {
							FusionPageBuilderApp.mediaMap.multiple_images[ key ] = {};
						}

						// Add images URLs
						const images = values[ option.param_name ].split( ',' );
						images.forEach( ( id ) => {
								const image = wp.media.attachment( id );
								if ( _.isUndefined( image.get( 'url' ) ) ) {
									image.fetch().then( function() {
										FusionPageBuilderApp.mediaMap.multiple_images[ key ][ id ] = image.get( 'url' );
									} );
								} else {
									FusionPageBuilderApp.mediaMap.multiple_images[ key ][ id ] = image.get( 'url' );
								}
						} );
					}
				} );

			},

			/**
			 * Triggers a refresh.
			 *
			 * @since 2.0.0
			 * @return void
			 */
			refreshJs: function() {
				jQuery( '#fb-preview' )[ 0 ].contentWindow.jQuery( 'body' ).trigger( 'fusion-element-render-' + this.model.attributes.element_type, this.model.attributes.cid );
			},

			/**
			 * Triggers responsive typography to recalculate.
			 *
			 * @since 2.0.0
			 * @return void
			 */
			updateResponsiveTypography: function() {
				document.querySelector( '#fb-preview' ).contentWindow.document.body.dispatchEvent( new Event( 'fusion-force-typography-update', { 'bubbles': true, 'cancelable': true } ) );
			},

			/**
			 * Re-Renders the view.
			 *
			 * @since 2.0.0
			 * @param {Object} event - The event triggering the rerender.
			 * @return {void}
			 */
			reRender: function( event ) {
				if ( event && 'object' === typeof event ) {
					event.preventDefault();
				}

				this.patchView( event );

				if ( this.model.get( 'inline_editor' ) && ! this.activeInlineEditing ) {
					FusionPageBuilderApp.inlineEditorHelpers.liveEditorEvent( this );
					this.activeInlineEditing = false;
				}
			},

			patchView: function() {
				var self            = this,
					$oldContent     = '',
					$newContent     = '',
					MultiGlobalArgs = {},
					diff,
					heightBeforePatch;

				if ( 'generated_element' === this.model.get( 'type' ) || 'fusion_builder_form_step' === this.model.get( 'type' ) ) {
					return;
				}

				heightBeforePatch = this.$el.outerHeight();
				this.beforePatch();
				FusionPageBuilderApp.disableDocumentWrite();

				$oldContent = this.getElementContent();
				$newContent = $oldContent.clone();

				$newContent.html( self.getTemplate() );

				// Find the difference
				diff = FusionPageBuilderApp._diffdom.diff( $oldContent[ 0 ], $newContent[ 0 ] );

				// Columns. Skip resizable patching.
				if ( 'function' === typeof this.patcherFilter ) {
					diff = this.patcherFilter( diff );
				}

				// Apply the difference.
				FusionPageBuilderApp._diffdom.apply( $oldContent[ 0 ], diff );

				if ( 'fusion_builder_column' !== this.model.get( 'element_type' ) ) {

					// Handle multiple global elements.
					MultiGlobalArgs = {
						currentModel: this.model,
						handleType: 'changeView',
						difference: diff
					};
					fusionGlobalManager.handleMultiGlobal( MultiGlobalArgs );
				}

				$oldContent.removeClass( 'fusion-loader' );

				FusionPageBuilderApp.enableDocumentWrite();
				this.afterPatch();

				// So equalHeights columns are updated.
				if ( heightBeforePatch !== this.$el.outerHeight() && 'function' === typeof this._triggerColumn ) {
					this._triggerColumn();
				}
			},

			/**
			 * Filter out DOM before patching.
			 *
			 * @since 2.0.0
			 * @return {void}
			 */
			patcherFilter: function( diffs ) {
				var filteredDiffs = [],
					ignoreList    = [
						'aria-multiline',
						'contenteditable',
						'data-inline-fontsize',
						'data-medium-editor-index',
						'data-medium-editor-element',
						'data-medium-focused',
						'data-placeholder',
						'medium-editor-index',
						'role',
						'spellcheck'
					],
					skipReInit = false;

				if ( this.activeInlineEditing ) {
					_.each( diffs, function( diff ) {
						if ( 'removeAttribute' === diff.action && -1 !== jQuery.inArray( diff.name, ignoreList ) ) {
							skipReInit = true;
							return;
						} else if ( 'modifyAttribute' === diff.action && -1 !== diff.oldValue.indexOf( 'medium-editor-element' ) && -1 === diff.oldValue.indexOf( 'medium-editor-element' ) ) {
							diff.newValue = diff.newValue + ' medium-editor-element';
							filteredDiffs.push( diff );
							skipReInit = true;
							return;
						}

						filteredDiffs.push( diff );
					} );
					diffs = filteredDiffs;

					// If we are not just removing/modifying attributes then inline needs recreated.
					this.activeInlineEditing = skipReInit;
					this.autoSelectEditor    = ! skipReInit;
				}
				return diffs;
			},

			/**
			 * Runs before view DOM is patched.
			 *
			 * @since 2.0.0
			 * @return null
			 */
			beforePatch: function() {
			},

			/**
			 * Runs after view DOM is patched.
			 *
			 * @since 2.0.0
			 * @return null
			 */
			afterPatch: function() {

				// This will trigger a JS event on the preview frame.
				this._refreshJs();
			},

			/**
			 * Runs after render to open any newly added inline element settings.
			 *
			 * @since 2.0.0
			 * @return null
			 */
			renderInlineSettings: function() {
				var newlyAdded;

				if ( 'undefined' === typeof FusionPageBuilderApp.inlineEditors || ! FusionPageBuilderApp.inlineEditors.shortcodeAdded ) {
					return;
				}

				newlyAdded = this.model.inlineCollection.find( function( model ) {
					return 'true' == model.get( 'params' ).open_settings; // jshint ignore: line
				} );

				if ( 'undefined' !== typeof newlyAdded ) {
					newlyAdded.parentView = this;
					newlyAdded.$target    = this.$el.find( '.fusion-disable-editing[data-id="' + newlyAdded.get( 'cid' ) + '"]' );

					delete newlyAdded.attributes.params.open_settings;
					const shortcode = newlyAdded.get( 'inlineElement' ); //newlyAdded.attributes.inlineElement;
					const newShortcode = shortcode.replace( ' open_settings="true"', '' );
					newlyAdded.set( 'inlineElement', newShortcode );

					newlyAdded.parentView.model.attributes.params.element_content = newlyAdded.parentView.model.attributes.params.element_content.replace( shortcode, newShortcode );

					if ( 'undefined' !== typeof FusionApp && 'off' !== FusionApp.preferencesData.open_settings ) {
						FusionPageBuilderApp.inlineEditorHelpers.getInlineElementSettings( newlyAdded );
					}
				}
			},

			/**
			 * Get the template.
			 *
			 * @since 2.0.0
			 * @return {void}
			 */
			getTemplate: function() {
				var atts = this.getTemplateAtts();

				if ( 'undefined' !== typeof this.elementTemplate ) {
					return this.elementTemplate( atts );
				}
			},

			/**
			 * Modify template attributes.
			 *
			 * @since 2.0.0
			 * @return {Object}
			 */
			filterTemplateAtts: function( atts ) {
				return atts;
			},

			baseInit: function() {
				var elementType = this.model.get( 'element_type' );

				this.initialValue = {};
				this.logHistory   = {};
				if ( 'string' === typeof elementType && -1 === jQuery.inArray( elementType, FusionPageBuilderApp.inlineElements ) ) {
					this.listenTo( FusionEvents, 'fusion-global-update-' + elementType, this.updateDefault );
					this.listenTo( FusionEvents, 'fusion-extra-update-' + elementType, this.updateExtra );
				}

				this.initDynamicParams();
			},

			initDynamicParams: function() {
				var self              = this,
					params            = this.model.get( 'params' ),
					dynamicData       = params.dynamic_params,
					parsedDynamicData = {};

				this.dynamicParams = new FusionPageBuilder.DynamicParams( { elementView: this } );

				// Handle "block" dynamic data.
				if ( 'string' === typeof params.dynamic_params && '' !== params.dynamic_params ) {
					try {
						if ( FusionPageBuilderApp.base64Encode( FusionPageBuilderApp.base64Decode( dynamicData ) ) === dynamicData ) {
							dynamicData       = FusionPageBuilderApp.base64Decode( dynamicData );
							dynamicData       = _.unescape( dynamicData );
							parsedDynamicData = JSON.parse( dynamicData );
						}
					} catch ( error ) {
						console.log( error ); // jshint ignore:line
					}
				} else {

					// Handle the inline dynamic data. Add to the main object as array, for differentiation from "block" dynamic data.
					const element          = fusionAllElements[ this.model.get( 'element_type' ) ],
						allowedOptionTypes = [ 'textfield', 'textarea', 'tinymce' ],
						ignoredOptions     = [ 'animation_', 'columnn_direction', 'margin_', 'padding_', 'max_', 'min_'];

					if ( ! _.isEmpty( params ) ) {
						if ( 'awb_dd' === element.shortcode ) {
							parsedDynamicData['awb_dd_output'] = [ this.buildParamsObject( params, false ) ];
						} else {
							_.each( params, function( value, name ) {
								if ( 'undefined' !== typeof value && '' !== value && 'undefined' !== typeof element.params[ name ] && allowedOptionTypes.includes( element.params[ name ].type ) && ! ignoredOptions.some( sub => value.toString().includes( sub ) ) ) {
									const inlineDynamicData = value.toString().match( /\{.*?\}/g );

									if ( ! _.isEmpty( inlineDynamicData ) ) {
										parsedDynamicData[ name ] = inlineDynamicData;
									}
								}
							} );
						}
					}
				}

				// Stroe dynamic data.
				if ( !  _.isEmpty( parsedDynamicData ) ) {
					self.dynamicParams.setData( parsedDynamicData );
				}

				this.listenTo( FusionEvents, 'awb-options-change', this.updateDynamicAtts );
			},

			updateDynamicAtts: function( paramName, paramValue, cid ) {
				if ( cid !== this.model.get( 'cid' ) ) {
					return
				}

				const self             = this,
					element            = fusionAllElements[ self.model.get( 'element_type' ) ],
					allowedOptionTypes = [ 'textfield', 'textarea', 'tinymce' ],
					ignoredOptions     = [ 'animation_', 'columnn_direction', 'margin_', 'padding_', 'max_', 'min_'];

				let dynamicParams     = self.dynamicParams.getAll()

				if ( 'undefined' !== typeof paramValue && '' !== paramValue && 'undefined' !== typeof element.params[ paramName ] && allowedOptionTypes.includes( element.params[ paramName ].type ) && ! ignoredOptions.some( sub => paramValue.includes( sub ) ) ) {
					const inlineDynamicData = paramValue.match( /\{.*?\}/g );

					if ( ! _.isEmpty( inlineDynamicData ) ) {
						dynamicParams[ paramName ] = inlineDynamicData;
					} else {
						delete dynamicParams[ paramName ];
					}

					self.dynamicParams.setData( dynamicParams );
				}
			},

			/**
			 * Get dynamic values.
			 *
			 * @since 2.0.0
			 * @return {Object}
			 */
			getDynamicAtts: function( atts ) {
				var self = this;

				if ( 'undefined' !== typeof self.dynamicParams && self.dynamicParams && ! _.isEmpty( self.dynamicParams.getAll() ) ) {
					_.each( self.dynamicParams.getAll(), function( data, id ) {

						// Handle "block" dynamic data.
						if ( ! Array.isArray( data ) ) {
							const value = self.dynamicParams.getParamValue( data );

							if ( 'undefined' !== typeof value && false !== value ) {
								atts.values[ id ] = value;
							}
						} else {

							// Handle inline dynamic data.
							let paramValue = '';

							// Dynamic data element.
							if ( 'awb_dd_output' === id ) {
								paramValue = FusionPageBuilderApp.dynamicValues.getValue( { 'data': data[0] } );

								if ( 'object' === typeof paramValue && 'function' === typeof paramValue.then ) {
									paramValue.then(
										function() {
											let params = self.model.get( 'params' );
											params[ id ] = paramValue.responseJSON.content;
											self.model.set( 'params', params );
											FusionPageBuilderApp.inlineEditorHelpers.processInlineElement( self.model, id, false );
										},
									);
								} else {
									atts.values[ id ] = paramValue;
								}
							} else if ( 'undefined' === typeof atts.values.disable_idd || 'no' === atts.values.disable_idd ) {
								paramValue = atts.values[ id ];

								_.each( data, function( endpoint, index ) {

									// The object with "data" as index is needed because of the "block" dynamic data structure.
									const value = FusionPageBuilderApp.dynamicValues.getValue( { 'data': endpoint } );

									if ( 'object' === typeof value && 'function' === typeof value.then ) {
										value.then(
											function() {
												if ( null !== value.responseJSON.content ) {
													const markup = '<span class="awb-dynamic-data-outline awb-dynamic-data"><span class="awb-dd-params">' + endpoint + '</span><span class="awb-dd-output">' + value.responseJSON.content + '</span></span>';
													paramValue = paramValue.replace( endpoint, markup );
													self.reRender();
												}
											},
										);
									} else {
										const markup  = '<span class="awb-dynamic-data-outline awb-dynamic-data"><span class="awb-dd-params">' + endpoint + '</span><span class="awb-dd-output">' + value + '</span></span>';
										paramValue = paramValue.replace( endpoint, markup );
									}
								} );

								atts.values[ id ] = paramValue;
							}
						}
					} );
				}


				return atts;
			},

			/**
			 * Gets element DOM for patching.
			 *
			 * @since 2.1
			 * @return {Object}
			 */
			getValues: function() {
				var elementType = this.model.get( 'element_type' ),
					element     = fusionAllElements[ elementType ];

				return this.getDynamicAtts( jQuery.extend( true, {}, element.defaults, _.fusionCleanParameters( this.model.get( 'params' ) ) ) );
			},

			/**
			 * Gets element DOM for patching.
			 *
			 * @since 2.0.0
			 * @return {Object}
			 */
			getElementContent: function() {
				var self = this;

				switch ( this.model.get( 'type' ) ) {

				case 'fusion_builder_column':
				case 'fusion_builder_container':
				case 'fusion_builder_column_inner':
					return self.$el;
				case 'element':
					if ( 'multi_element_child' !== self.model.get( 'multi' ) ) {
						return self.$el.find( '.fusion-builder-element-content' );
					}
					return self.$el.find( '.fusion-builder-child-element-content' );
				}
			},

			/**
			 * Settings handler.
			 *
			 * @since 2.0.0
			 * @param {Object} event - The event.
			 * @return {void}
			 */
			settings: function( event ) {

				var self = this,
					viewSettings = {
						model: this.model,
						collection: this.collection
					},
					customSettingsViewName,
					modalView,
					parentView,
					generated         = 'generated_element' === this.model.get( 'type' ),
					childElementClass = '',
					dialogTitle       = '',
					resizePopupClass  = localStorage.getItem( 'resizePopupClass' );

				if ( event ) {
					event.preventDefault();
				}

				this.onSettingsOpen();

				customSettingsViewName = fusionAllElements[ this.model.get( 'element_type' ) ].custom_settings_view_name;

				// Check for generated element child.
				if ( 'multi_element_child' === this.model.get( 'multi' ) ) {
					parentView = FusionPageBuilderViewManager.getView( this.model.get( 'parent' ) );
					if ( parentView && 'generated_element' === parentView.model.get( 'type' ) ) {
						generated = true;
						viewSettings.model.set( 'type', 'generated_element' );
						viewSettings.model.set( 'display', 'dialog' );
					}
				}

				if ( 'undefined' !== typeof customSettingsViewName && '' !== customSettingsViewName ) {
					modalView = new FusionPageBuilder[ customSettingsViewName ]( viewSettings );
				} else {
					modalView = new FusionPageBuilder.ElementSettingsView( viewSettings );
				}

				// Activate column spacing.
				if ( 'fusion_builder_column' === this.model.get( 'element_type' ) || 'fusion_builder_column_inner' === this.model.get( 'element_type' ) ) {
					this.columnSpacing();
					this.paddingDrag();
					this.marginDrag();

					// Hides column size popup.
					this.$el.removeClass( 'active' );
					this.$el.closest( '.fusion-builder-container' ).removeClass( 'fusion-column-sizer-active' );
				}

				// Activate resize handles.
				if ( 'fusion_builder_container' === this.model.get( 'element_type' ) ) {
					this.paddingDrag();
					this.marginDrag();
				}

				if ( 'fusion_builder_container' === this.model.get( 'element_type' ) || 'fusion_builder_column' === this.model.get( 'element_type' ) || 'fusion_builder_column_inner' === this.model.get( 'element_type' ) ) {
					this.$el.addClass( 'fusion-builder-element-edited' );
				}

				childElementClass = 'undefined' !== this.model.get( 'multi' ) && 'multi_element_child' === this.model.get( 'multi' ) ? ' fusion-builder-child-element' : '';
				dialogTitle       = this.getDialogTitle();

				// No need to render if it already is.
				if ( ! FusionPageBuilderApp.SettingsHelpers.shouldRenderSettings( modalView ) ) {
					return;
				}

				// If we want dialog.
				if ( 'dialog' === FusionApp.preferencesData.editing_mode || generated ) {
					jQuery( modalView.render().el ).dialog( {
						title: dialogTitle,
						width: FusionApp.dialog.dialogData.width,
						height: FusionApp.dialog.dialogData.height,
						position: FusionApp.dialog.dialogData.position,
						dialogClass: 'fusion-builder-dialog fusion-builder-settings-dialog' + childElementClass,
						minWidth: 327,
						type: this.model.get( 'type' ),

						dragStop: function( event, ui ) {
							FusionApp.dialog.saveDialogPosition( ui.offset );
						},

						resizeStart: function() {
							FusionApp.dialog.addResizingClasses();
						},

						resizeStop: function( event, ui ) {
							var $dialog = jQuery( event.target ).closest( '.ui-dialog' ),
								width   = $dialog.find( '.fusion-tabs-menu > li' ).length;

							if ( width ) {
								width = 100 * width;
							}
							if ( width && ui.size.width > width ) {
								$dialog.find( '.fusion-tabs-menu' ).addClass( 'show-text' );
							} else {
								$dialog.find( '.fusion-tabs-menu' ).removeClass( 'show-text' );
							}

							FusionApp.dialog.saveDialogSize( ui.size );

							if ( 450 > ui.size.width && ! $dialog.hasClass( 'fusion-builder-dialog-narrow' ) ) {
								$dialog.addClass( 'fusion-builder-dialog-narrow' );
							} else if ( 450 <= ui.size.width && $dialog.hasClass( 'fusion-builder-dialog-narrow' ) ) {
								$dialog.removeClass( 'fusion-builder-dialog-narrow' );
							}

							FusionApp.dialog.removeResizingClasses();
						},

						open: function( event ) {
							var $dialogContent = jQuery( event.target ),
								$dialog        = $dialogContent.closest( '.ui-dialog' ),
								width;

							// On start can sometimes be laggy/late.
							FusionApp.dialog.addResizingHoverEvent();

							if ( modalView.$el.find( '.has-group-options' ).length ) {
								$dialog.addClass( 'fusion-builder-group-options' );
							}

							$dialogContent.find( '.fusion-builder-section-name' ).blur();

							jQuery( '.ui-dialog' ).not( $dialog ).hide();

							jQuery( '.fusion-back-menu-item' ).on( 'click', function() {
								modalView.openParent();

								self.onSettingsClose();
							} );

							self.modalDialogMoreView = new FusionPageBuilder.modalDialogMore( { model: self.model } );

							// We need to render context submenu on open.
							FusionPageBuilderApp.SettingsHelpers.renderDialogMoreOptions( modalView );

							if ( null !== resizePopupClass ) {
								jQuery( 'body' ).addClass( resizePopupClass );
								self.modalDialogMoreView.resizePopup( resizePopupClass );
							}

							jQuery( '#fb-preview' )[ 0 ].contentWindow.jQuery( 'body' ).addClass( 'fusion-dialog-ui-active' );

							if ( 450 > $dialog.width() && ! $dialog.hasClass( 'fusion-builder-dialog-narrow' ) ) {
								$dialog.addClass( 'fusion-builder-dialog-narrow' );
							} else if ( 450 <= $dialog.width() && $dialog.hasClass( 'fusion-builder-dialog-narrow' ) ) {
								$dialog.removeClass( 'fusion-builder-dialog-narrow' );
							}

							// Check if dialog is positioned outside of viewport and reposition it if needed.
							if ( FusionApp.dialog.maybeRepositionDialog( $dialog ) ) {
								FusionApp.dialog.saveDialogPosition( $dialog.offset() );
							}

							width = $dialog.find( '.fusion-tabs-menu > li' ).length;
							if ( width ) {
								width = 100 * width;
							}
							if ( width && $dialog.width() > width ) {
								$dialog.find( '.fusion-tabs-menu' ).addClass( 'show-text' );
							}

						},

						dragStart: function( event ) {

							// Used to close any open drop-downs in TinyMce.
							jQuery( event.target ).trigger( 'click' );
						},

						beforeClose: function( event ) {

							FusionApp.dialogCloseResets( modalView );
							self.modalDialogMoreView = null;
							modalView.saveSettings( event );

							FusionEvents.trigger( 'fusion-content-changed' );
						}

					} );
				} else {
					jQuery( '#fb-preview' )[ 0 ].contentWindow.jQuery( 'body' ).addClass( 'fusion-sidebar-ui-active' );

					// Adding into sidebar view instead.
					modalView.model.set( 'title', dialogTitle );
					modalView.model.set( 'display', 'sidebar' );
					FusionApp.sidebarView.renderElementSettings( modalView );
				}
			},

			getDialogTitle: function() {
				var dialogTitle = fusionAllElements[ this.model.get( 'element_type' ) ].name,
					params;

				if ( 'multi_element_child' === this.model.get( 'multi' ) ) {
					params = jQuery.extend( true, {}, this.model.get( 'params' ) );
					dialogTitle = 'Item';
					if ( 'undefined' !== typeof params.title && params.title.length ) {
						dialogTitle = params.title;
					} else if ( 'undefined' !== typeof params.title_front && params.title_front.length ) {
						dialogTitle = params.title_front;
					} else if ( 'undefined' !== typeof params.name && params.name.length ) {
						dialogTitle = params.name;
					} else if ( 'undefined' !== typeof params.image && params.image.length ) {
						dialogTitle = params.image;

						// If contains backslash, retrieve only last part.
						if ( -1 !== dialogTitle.indexOf( '/' ) && -1 === dialogTitle.indexOf( '[' ) ) {
							dialogTitle = dialogTitle.split( '/' );
							dialogTitle = dialogTitle.slice( -1 )[ 0 ];
						}
					} else if ( 'image' === this.model.attributes.element_name && 'undefined' !== typeof params.element_content && params.element_content.length ) {
						dialogTitle = params.element_content;

						// If contains backslash, retrieve only last part.
						if ( -1 !== dialogTitle.indexOf( '/' ) && -1 === dialogTitle.indexOf( '[' ) ) {
							dialogTitle = dialogTitle.split( '/' );
							dialogTitle = dialogTitle.slice( -1 )[ 0 ];
						}
					} else if ( 'undefined' !== typeof params.video && params.video.length ) {
						dialogTitle = params.video;
					} else if ( 'undefined' !== typeof params.element_content && params.element_content.length ) {
						dialogTitle = params.element_content;
					}

					// Remove HTML tags but keep quotation marks etc.
					dialogTitle = dialogTitle.replace( /(<([^>]+)>)/ig, '' );
					dialogTitle = ( dialogTitle && 15 < dialogTitle.length ) ? dialogTitle.substring( 0, 15 ) + '...' : dialogTitle;

					dialogTitle = _.fusionUcFirst( dialogTitle );
				}
				return dialogTitle;
			},

			/**
			 * Extendable function for when settings is opened.
			 *
			 * @since 2.0.0
			 * @return {void}
			 */
			onSettingsOpen: function() {
				jQuery( this.el ).addClass( 'awb-element-settings-open' );
			},

			/**
			 * Extendable function for when settings is closed.
			 *
			 * @since 2.0.0
			 * @return {void}
			 */
			onSettingsClose: function() {
				var $dialog = jQuery( '.ui-dialog:not( .fusion-video-dialog ):not( .fusion-builder-preferences-dialog )' ).first();

				// If there are opened dialogs which are resizable.
				if ( 0 < $dialog.length && ! jQuery( 'body' ).hasClass( 'fusion-settings-dialog-large' ) ) {

					// Change it's size.
					jQuery( $dialog ).css( 'width', FusionApp.dialog.dialogData.width + 'px' );
					jQuery( $dialog ).css( 'height', FusionApp.dialog.dialogData.height + 'px' );

					// Reposition it.
					jQuery( $dialog ).position( {
						my: FusionApp.dialog.dialogData.position.my,
						at: FusionApp.dialog.dialogData.position.at,
						of: window
					} );

					jQuery( '#fb-preview' )[ 0 ].contentWindow.jQuery( 'body' ).removeClass( 'fusion-dialog-ui-active' )
				} else {
					jQuery( '#fb-preview' )[ 0 ].contentWindow.jQuery( 'body' ).removeClass( 'fusion-sidebar-ui-active' );
				}

				jQuery( this.el ).removeClass( 'awb-element-settings-open' );
			},

			/**
			 * Renders the content.
			 *
			 * @since 2.0.0
			 * @return {void}
			 */
			renderContent: function() {
			},

			/**
			 * Adds loading overlay while ajax is performing.
			 *
			 * @since 2.0.0
			 * @return {void}
			 */
			addLoadingOverlay: function() {
				var contentType = 'element',
					$elementContent;

				if ( _.isObject( this.model.attributes ) ) {
					if ( 'fusion_builder_container' === this.model.attributes.element_type ) {
						contentType = 'container';
					} else if ( 'fusion_builder_column' === this.model.attributes.element_type ) {
						contentType = 'columns';
					}
				}

				$elementContent = this.$el.find( '.fusion-builder-' + contentType + '-content' );

				if ( ! $elementContent.hasClass( 'fusion-loader' ) ) {
					$elementContent.addClass( 'fusion-loader' );
					$elementContent.append( '<span class="fusion-builder-loader"></span>' );
				}
			},

			/**
			 * Removes loading overlay after ajax is done.
			 *
			 * @since 3.5
			 * @return {void}
			 */
			removeLoadingOverlay: function() {
				var contentType = 'element',
					$elementContent;

				if ( _.isObject( this.model.attributes ) ) {
					if ( 'fusion_builder_container' === this.model.attributes.element_type ) {
						contentType = 'container';
					} else if ( 'fusion_builder_column' === this.model.attributes.element_type ) {
						contentType = 'columns';
					}
				}

				$elementContent = this.$el.find( '.fusion-builder-' + contentType + '-content' );

				if ( $elementContent.hasClass( 'fusion-loader' ) ) {
					$elementContent.removeClass( 'fusion-loader' );
					$elementContent.find( '.fusion-builder-loader' ).remove();
				}
			},

			/**
			 * Removes an element.
			 *
			 * @since 2.0.0
			 * @param {Object} event - The event triggering the element removal.
			 * @return {void}
			 */
			removeElement: function( event ) {
				var parentCid = this.model.get( 'parent' );

				if ( event ) {
					event.preventDefault();
					FusionEvents.trigger( 'fusion-content-changed' );
				}

				// Remove element view
				FusionPageBuilderViewManager.removeView( this.model.get( 'cid' ) );

				// Destroy element model
				this.model.destroy();

				FusionEvents.trigger( 'fusion-element-removed', this.model.get( 'cid' ) );

				// Update column trigger.
				this.triggerColumn( parentCid );

				// Destroy dynamic param model.
				if ( this.dynamicParam ) {
					this.dynamicParam.destroy();
				}

				this.remove();
			},

			/**
			 * Opens the library. Builds the settings for this view
			 * and then calls FusionPageBuilder.LibraryView and renders it.
			 *
			 * @since 2.0.0
			 * @param {Object} event - The js event.
			 * @return {void}
			 */
			openLibrary: function( event ) {
				var view,
					libraryModel = {
						target: jQuery( event.currentTarget ).data( 'target' ),
						focus: jQuery( event.currentTarget ).data( 'focus' ),
						element_cid: this.model.get( 'cid' ),
						element_name: 'undefined' !== typeof this.model.get( 'admin_label' ) && '' !== this.model.get( 'admin_label' ) ? this.model.get( 'admin_label' ) : ''
					},
					viewSettings = {
						model: libraryModel
					};

				if ( event ) {
					event.preventDefault();
					event.stopPropagation();
					FusionPageBuilderApp.sizesHide( event );
				}

				view = new FusionPageBuilder.LibraryView( viewSettings );
				view.render();

				// Make sure to close any context menus which may be open.
				FusionPageBuilderApp.removeContextMenu();
			},

			/**
			 * Disable external links.
			 *
			 * @since 2.0.0
			 * @param {Object} event - The event.
			 * @return {void}
			 */
			disableLink: function( event ) {
				if ( ! jQuery( event.target ).closest( '.fusion-builder-module-controls-container' ).length && 'lightbox' !== jQuery( event.currentTarget ).attr( 'target' ) ) {
					event.preventDefault();

					if ( FusionApp.modifierActive && ! jQuery( event.target ).parent().hasClass( 'fusion-lightbox' ) ) {
						FusionApp.checkLink( event );
					}
				}
			},

			/**
			 * Creates droppable zone and makes element draggable.
			 *
			 * @since 2.0.0
			 * @return {void}
			 */
			droppableElement: function() {
				var self  = this,
					$el   = this.$el,
					cid   = this.model.get( 'cid' ),
					$body = jQuery( '#fb-preview' )[ 0 ].contentWindow.jQuery( 'body' );

				if ( ! $el ) {
					return;
				}
				if ( 'undefined' === typeof this.elementTarget || ! this.elementTarget.length ) {
					this.elementTarget = this.$el.find( '.fusion-element-target' );
				}

				$el.draggable( {
					appendTo: FusionPageBuilderApp.$el,
					zIndex: 999999,
					delay: 100,
					cursorAt: { top: 15, left: 15 },
					iframeScroll: true,
					containment: $body,
					cancel: '.fusion-live-editable, .fusion-builder-live-child-element:not( [data-fusion-no-dragging] ), .variations select, .awb-openstreet-map',
					helper: function() {
						var $classes = FusionPageBuilderApp.DraggableHelpers.draggableClasses( cid );
						return jQuery( '<div class="fusion-element-helper ' + $classes + '" data-cid="' + cid + '"><span class="' + fusionAllElements[ self.model.get( 'element_type' ) ].icon + '"></span></div>' );
					},
					start: function() {
						$body.addClass( 'fusion-element-dragging fusion-active-dragging' );
						$el.addClass( 'fusion-being-dragged' );
						$el.prev( '.fusion-builder-live-element' ).find( '.target-after' ).addClass( 'target-disabled' );
					},
					stop: function() {
						setTimeout( function() {
							$body.removeClass( 'fusion-element-dragging fusion-active-dragging' );
						}, 10 );
						$el.removeClass( 'fusion-being-dragged' );
						FusionPageBuilderApp.$el.find( '.target-disabled' ).removeClass( 'target-disabled' );
					}
				} );

				this.elementTarget.droppable( {
					tolerance: 'touch',
					hoverClass: 'ui-droppable-active',
					accept: '.fusion-builder-live-element, .fusion_builder_row_inner',
					drop: function( event, ui ) {
						var handleDropElement = self.handleDropElement.bind( self );
						handleDropElement( ui.draggable, $el, jQuery( event.target ) );
					}
				} );

			},

			handleDropElement: function( $element, $targetEl, $dropTarget ) {
				var parentCid      = $dropTarget.closest( '.fusion-builder-column' ).data( 'cid' ),
					columnView     = FusionPageBuilderViewManager.getView( parentCid ),
					elementCid     = $element.data( 'cid' ),
					elementView    = FusionPageBuilderViewManager.getView( elementCid ),
					MultiGlobalArgs,
					newIndex;

				// Move the actual html.
				if ( $dropTarget.hasClass( 'target-after' ) ) {
					$targetEl.after( $element );
				} else {
					$targetEl.before( $element );
				}

				newIndex = $element.parent().children( '.fusion-builder-live-element, .fusion_builder_row_inner' ).index( $element );

				FusionPageBuilderApp.onDropCollectionUpdate( elementView.model, newIndex, parentCid );

				// Save history state
				FusionEvents.trigger( 'fusion-history-save-step', fusionBuilderText.moved + ' ' + fusionAllElements[ elementView.model.get( 'element_type' ) ].name + ' ' + fusionBuilderText.element );

				// Handle multiple global elements.
				MultiGlobalArgs = {
					currentModel: elementView.model,
					handleType: 'save',
					attributes: elementView.model.attributes
				};
				fusionGlobalManager.handleMultiGlobal( MultiGlobalArgs );

				FusionEvents.trigger( 'fusion-content-changed' );

				columnView._equalHeights();
			},

			/**
			 * Destroy or disable the droppable and draggable.
			 *
			 * @since 2.0.0
			 * @return {void}
			 */
			disableDroppableElement: function() {
				var $el = this.$el;

				// If its been init, just disable.
				if ( 'undefined' !== typeof $el.draggable( 'instance' ) ) {
					$el.draggable( 'disable' );
				}

				// If its been init, just disable.
				if ( 'undefined' !== typeof this.elementTarget && this.elementTarget.length && 'undefined' !== typeof this.elementTarget.droppable( 'instance' ) ) {
					this.elementTarget.droppable( 'disable' );
				}
			},

			/**
			 * Enable the droppable and draggable.
			 *
			 * @since 2.0.0
			 * @return {void}
			 */
			enableDroppableElement: function() {
				var $el = this.$el;

				// If they have been init, then just disable.
				if ( 'undefined' !== typeof $el.draggable( 'instance' ) && 'undefined' !== typeof this.elementTarget && this.elementTarget.length && 'undefined' !== typeof this.elementTarget.droppable( 'instance' ) ) {
					$el.draggable( 'enable' );
					this.elementTarget.droppable( 'enable' );
				} else {

					// No sign of init, then need to call it.
					this.droppableElement();
				}
			},

			/**
			 * Gets edit label.
			 *
			 * @since 2.0.0
			 * @return {string}
			 */
			getEditLabel: function() {
				var editLabel   = fusionBuilderText.element_settings,
					elementType = this.model.get( 'element_type' );
				if ( 'undefined' !== typeof fusionAllElements[ elementType ] ) {
					editLabel = fusionBuilderText.custom_element_settings;
					editLabel = editLabel.replace( '%s', fusionAllElements[ elementType ].name );
				}
				return editLabel;
			},

			/**
			 * Simple prevent default function.
			 *
			 * @since 2.0.0
			 * @param {Object} event - Click event object.
			 * @return {void}
			 */
			preventDefault: function( event ) {
				event.preventDefault();
			},

			/**
			 * Update element settings on drag (columns and containers).
			 *
			 * @since 2.0.0
			 * @param {string} selector - Selector of option.
			 * @param {string} value - Value to update to.
			 * @return {void}
			 */
			updateDragSettings: function( selector, value ) {
				var $option = jQuery( '[data-element-cid="' + this.model.get( 'cid' ) + '"] ' + selector ),
					$elementSettings,
					$section;

				if ( $option.length ) {
					$elementSettings = $option.closest( '.fusion_builder_module_settings' );
					if ( ! $elementSettings.find( '.fusion-tabs-menu a[href="#design"]' ).parent().hasClass( 'current' ) ) {
						$elementSettings.find( '.fusion-tabs-menu a[href="#design"]' ).parent().trigger( 'click' );
					}
					$section = $elementSettings.find( '.fusion-tabs-menu a[href="#design"]' ).closest( '.fusion-sidebar-section, .ui-dialog-content' );
					$section.scrollTop(  $option.position().top + $section.scrollTop() );
					$option.val( value ).trigger( 'change' );
				}
			},

			/**
			 * Check for element ajax callbacks and run them.
			 *
			 * @since 2.0.0
			 * @return {void}
			 */
			triggerAjaxCallbacks: function( skip ) {
				var self          = this,
					AjaxCallbacks = {},
					args          = {
						skip: 'undefined' === typeof skip ? false : skip
					};

				if ( 'undefined' !== typeof fusionAllElements[ this.model.get( 'element_type' ) ].has_ajax ) {

					// Collect callbacks. Do not fire the same action twice.
					_.each( fusionAllElements[ this.model.get( 'element_type' ) ].has_ajax, function( callback ) {
						AjaxCallbacks = {};
						AjaxCallbacks[ callback.action ] = {};
						AjaxCallbacks[ callback.action ][ 'function' ]   = callback[ 'function' ];
						AjaxCallbacks[ callback.action ].param_name = callback.param_name;
						AjaxCallbacks[ callback.action ].action     = callback.action;
					} );

					// Trigger ajax callbacks to populate query_data attribute
					_.each( AjaxCallbacks, function( callback ) {
						FusionApp.callback[ callback[ 'function' ] ]( callback.param_name, self.model.attributes.params[ callback.param_name ], self.model.attributes, args, self.model.get( 'cid' ), callback.action, self.model, self );
					} );
				}
			},

			updateExtra: function() {
				this.reRender();
			},

			updateDefault: function( param, value ) {
				var modelData        = jQuery.extend( this.model.attributes, {} ),
					reRender         = true,
					callbackFunction = false,
					params           = this.model.get( 'params' );

				// Only re-render if actually using default.
				if ( ( 'undefined' === typeof params[ param ] || '' === params[ param ] || 'default' === params[ param ] ) && ! this.dynamicParams.hasDynamicParam( param ) ) {

					callbackFunction = FusionPageBuilderApp.getCallbackFunction( modelData, param, value, this, true );
					callbackFunction.args = 'undefined' === typeof callbackFunction.args ? {} : callbackFunction.args;
					callbackFunction.args.skipRerender = false;
					if ( false !== callbackFunction && 'function' === typeof FusionApp.callback[ callbackFunction[ 'function' ] ] ) {
						reRender = this.doCallbackFunction( callbackFunction, false, param, value, modelData, true );
					}

					if ( reRender ) {
						this.reRender();
					}
				}
			},

			historyUpdateParam: function( param, value ) {
				var modelData        = jQuery.extend( this.model.attributes, {} ),
					reRender         = true,
					callbackFunction = false;

				this.changeParam( param, value, false, true );

				callbackFunction = FusionPageBuilderApp.getCallbackFunction( modelData, param, value, this, true );
				if ( false !== callbackFunction && 'function' === typeof FusionApp.callback[ callbackFunction[ 'function' ] ] ) {
					reRender = this.doCallbackFunction( callbackFunction, false, param, value, modelData, true );
				}

				if ( reRender ) {
					this.reRender();
				}
			},

			updateParam: function( param, value, event ) {
				var modelData        = jQuery.extend( this.model.attributes, {} ),
					reRender         = true,
					callbackFunction = FusionPageBuilderApp.getCallbackFunction( modelData, param, value, this );

				if ( false !== callbackFunction && 'function' === typeof FusionApp.callback[ callbackFunction[ 'function' ] ] ) {
					reRender = this.doCallbackFunction( callbackFunction, event, param, value, modelData );
				} else {
					this.changeParam( param, value );
				}

				return reRender;
			},

			setInitialValue: function( param ) {
				if ( 'undefined' !== typeof this.initialValue && 'undefined' === typeof this.initialValue[ param ] && 'undefined' !== typeof param ) {
					this.initialValue[ param ] = 'undefined' !== typeof this.model.get( 'params' )[ param ] ? this.model.get( 'params' )[ param ] : '';
				}
			},

			logChangeEvent: function( param, value, label ) {
				this.logHistory._param = this.logHistory._param || {};
				if ( ! ( param in this.logHistory._param ) ) {
					this.logHistory._param[ param ] = _.debounce( _.bind( function( param, value, label ) {
						var state = {
								type: 'param',
								param: param,
								newValue: value,
								cid: this.model.get( 'cid' )
							},
							elementMap  = fusionAllElements[ this.model.get( 'element_type' ) ],
							paramObject = elementMap.params[ param ],
							paramTitle  = 'object' === typeof paramObject ? paramObject.heading : param;

						if ( 'undefined' !== typeof label ) {
							paramTitle = label;
						} else if ( 'object' !== typeof paramObject && jQuery( '.multi-builder-dimension #' + param ).length ) {
							paramObject = elementMap.params[ jQuery( '.multi-builder-dimension #' + param ).closest( '.multi-builder-dimension' ).attr( 'id' ) ];
							if ( 'object' === typeof paramObject && 'string' === typeof paramObject.heading ) {
								paramTitle = paramObject.heading;
							}
						} else if ( 'object' !== typeof paramObject && jQuery( '.font_family #' + param ).length ) {
							paramObject = elementMap.params[ jQuery( '.font_family #' + param ).closest( '.fusion-builder-option' ).attr( 'data-option-id' ) ];
							if ( 'object' === typeof paramObject && 'string' === typeof paramObject.heading ) {
								paramTitle = paramObject.heading;
							}
						} else if (  'object' !== typeof paramObject && jQuery( '.typography [name="' + param + '"]' ).length ) {
							paramObject = elementMap.params[ jQuery( '.typography [name="' + param + '"]' ).closest( '.fusion-builder-option' ).attr( 'data-option-id' ) ];
							if ( 'object' === typeof paramObject && 'string' === typeof paramObject.heading ) {
								paramTitle = paramObject.heading;
							}
						}

						state.oldValue = this.initialValue[ param ];
						delete this.initialValue[ param ];

						this.model.trigger( 'change' );
						FusionEvents.trigger( 'fusion-history-save-step', fusionBuilderText.edited + ' ' + elementMap.name + ' - ' + paramTitle, state );
					}, this ), 500 );
				}
				this.logHistory._param[ param ]( param, value, label );
			},

			changeParam: function( param, value, label, silent ) {
				var parentView;
				if ( ! silent && ! this.model.get( 'inlineElement' ) ) {
					this.setInitialValue( param );
					this.model.attributes.params[ param ] = value;

					// Update parent after param has been changed.
					if ( 'multi_element_child' === this.model.get( 'multi' ) ) {
						parentView = FusionPageBuilderViewManager.getView( this.model.get( 'parent' ) );
						if ( parentView && 'function' === typeof parentView.updateElementContent ) {
							parentView.updateElementContent();
						}
					}
					this.logChangeEvent( param, value, label );
				} else {
					this.model.attributes.params[ param ] = value;
				}
				if ( 'function' === typeof this.updateInlineParams && 'fusion_builder_form_step' === this.model.get( 'element_type' ) ) {
					this.updateInlineParams( param, value );
				}
			},

			/**
			 * Gets callback function for option change.
			 *
			 * @since 2.0.0
			 * @return {void}
			 */
			doCallbackFunction: function( callbackFunction, event, paramName, paramValue, modelData, skipChange ) {
				var reRender = true,
					returnData;

				callbackFunction.args   = 'undefined' === typeof callbackFunction.args ? {} : callbackFunction.args;
				callbackFunction.ajax   = 'undefined' === typeof callbackFunction.ajax ? false : callbackFunction.ajax;
				callbackFunction.action = 'undefined' === typeof callbackFunction.action ? false : callbackFunction.action;
				skipChange              = 'undefined' === typeof skipChange ? false : skipChange;

				// If skip is set then param will not be changed.
				callbackFunction.args.skip = skipChange;

				// If ajax trigger via debounce, else do it here and retun data.
				if ( callbackFunction.ajax ) {
					reRender = false;
					this.addLoadingOverlay();
					this._triggerCallback( event, callbackFunction, paramName, paramValue, modelData.cid, modelData );
				} else {
					returnData = FusionApp.callback[ callbackFunction[ 'function' ] ]( paramName, paramValue, callbackFunction.args, this );
				}
				if ( 'undefined' !== typeof returnData && 'undefined' !== typeof returnData.render ) {
					reRender = returnData.render;
				}

				return reRender;
			},

			/**
			 * Triggers a callback function.
			 *
			 * @since 2.0.0
			 * @param {Object}        event - The event.
			 * @param {string|Object} callbackFunction - The callback function.
			 * @return {void}
			 */
			triggerCallback: function( event, callbackFunction, paramName, paramValue, cid, modelData ) {

				if ( 'undefined' === typeof cid && 'undefined' !== typeof callbackFunction.cid ) {
					cid = callbackFunction.cid;
				}

				if ( 'undefined' === typeof modelData ) {
					modelData = jQuery.extend( this.model.attributes, {} );
				}

				// This is added due to the new elements causing max call stack.  Not sure why but it shouldn't be necessary in any case.
				if ( 'undefined' !== typeof modelData ) {
					delete modelData.view;
				}
				if ( 'fusion_do_shortcode' !== callbackFunction[ 'function' ] ) {
					FusionApp.callback[ callbackFunction[ 'function' ] ]( paramName, paramValue, modelData, callbackFunction.args, cid, callbackFunction.action, this.model, this );
				} else {
					FusionApp.callback[ callbackFunction[ 'function' ] ]( cid, callbackFunction.content, callbackFunction.parent );
				}
			},

			addCssProperty: function ( selectors, property, value, important ) {

				if ( 'object' === typeof selectors ) {
					selectors = Object.values( selectors );
				}

				if ( 'object' === typeof selectors ) {
					selectors = selectors.join( ',' );
				}

				if ( 'object' !== typeof this.dynamic_css[ selectors ] ) {
					this.dynamic_css[ selectors ] = {};
				}

				if ( 'undefined' !== typeof important && important ) {
					value += ' !important';
				}
				if ( 'undefined' === typeof this.dynamic_css[ selectors ][ property ] || ( 'undefined' !== typeof important && important ) || ! this.dynamic_css[ selectors ][ property ].includes( 'important' ) ) {
					this.dynamic_css[ selectors ][ property ] = value;
				}
			},

			/**
			 * Get a string with each of the option as a CSS variable, if the option is not default.
			 *
			 * @since 3.9
			 * @param {array} options The array with the options ids.
			 * @return {string}
			 */
			getCssVarsForOptions( options ) {
				var css = '',
					varName,
					value,
					callback_args,
					key;

				for ( key in options ) {
					if ( ! options.hasOwnProperty( key ) || 'length' === key ) {
						continue; // eslint-disable-line no-continue
					}

					value = options[ key ];

					if ( 'object' === typeof value ) { // If the value is an array, then the CSS var name is the key.
						if ( ! this.isDefault( key ) ) {
							varName       = '--awb-' + key.replaceAll( '_', '-' );
							callback_args = ( 'object' === typeof value.args ? value.args : [ this.values[ key ] ] );
							css          += varName + ':' + value.callback.apply( null, callback_args ) + ';';
						}
					} else {
						if ( ! this.isDefault( value ) ) { // eslint-disable-line no-lonely-if
							varName = '--awb-' + value.replaceAll( '_', '-' );
							css    += varName + ':' + this.values[ value ] + ';';
						}
					}
				}

				return css;
			},

			/**
			 * Get a string with custom CSS variables, created from array key => value pairs.
			 *
			 * @since 3.9
			 * @param {Object} $options The object with the custom css vars. The property
			 * represents the option name, while the value represents the custom value.
			 * @return {string}
			 */
			getCustomCssVars( options, prefix ) {
				var css = '',
					varName,
					property;

				if ( 'undefined' === typeof prefix ) {
					prefix = true;
				}
				for ( property in options ) {
					if ( ! options.hasOwnProperty( property ) ) {
						continue; // eslint-disable-line no-continue
					}

					if ( prefix ) {
						varName = '--awb-' + property.replaceAll( '_', '-' );
					} else {
						varName = '--' + property;
					}
					css    += varName + ':' + options[ property ] + ';';
				}

				return css;
			},

			/**
			 * Get declaration for typography vars with the given values.
			 *
			 * @since 3.9
			 * @param {string} titleTag An HTML tag, Ex: 'h2', 'h3', 'div'.. etc.
			 * @param {Object} nameValueMap The key is a css property, the array value is the CSS value.
			 * @return string
			 */
			getHeadingFontVars( titleTag, nameValueMap ) {
				var varPrefix = '',
					cssProp,
					style     = '';

					if ( [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ].includes( titleTag ) ) {
						varPrefix = '--' + titleTag + '_typography-';
					} else if ( 'div' === titleTag || 'p' === titleTag ) {
						varPrefix = '--body_typography-';
					} else {
						return style;
					}

				for ( cssProp in nameValueMap ) {
					if ( nameValueMap[ cssProp ] && '' !== nameValueMap[ cssProp ] ) {
						style += varPrefix + cssProp + ':' + nameValueMap[ cssProp ] + ';';
					}
				}

				return style;
			},

			/**
			 * Get a string with all the border radius CSS variables.
			 * @since 3.9
			 * @param {array} options The array with the options ids.
			 * @return {string}
			 */
			getBorderRadiusVars( options ) {
				var style = '',
					varName,
					edges = {
						'border_radius_top_left': 'border-top-left-radius',
						'border_radius_top_right': 'border-top-right-radius',
						'border_radius_bottom_left': 'border-bottom-left-radius',
						'border_radius_bottom_right': 'border-bottom-right-radius'
					};

				for ( varName in edges ) {
					if ( options.hasOwnProperty( varName )  && '' !== options[ varName ] ) {
						style += '--awb-' + edges[ varName ] + ':' + options[ varName ] + ';';
					}
				}

				return style;
			},

			isDefault: function( param ) {
				return this.values[ param ] === fusionAllElements[ this.model.get( 'element_type' ) ].defaults[ param ];
			},

			/**
			 * Get font styling vars, created from _.fusionGetFontStyle helper.
			 *
			 * @since 3.9
			 * @param string key typography options key.
			 * @param object values  the values object.
			 * @return string
			 */
			getFontStylingVars( key, values ) {
				let css = '';

				const font_styles = _.fusionGetFontStyle( key, values, 'object' );
				Object.keys( font_styles ).forEach( ( rule ) => {
					const value = font_styles[ rule ];
					key = key.replace( '_font', '' );
					let name = key + '-' + rule;
					name = name.replaceAll( '_', '-' );

					css += `--awb-${name}: ${value};`;
				} );

				return css;
			},

			/**
			 * Get aspect ratio vars.
			 *
			 * @since 3.9
			 * @param object values  the values object.
			 * @return string
			 */
			getAspectRatioVars( values ) {
				if ( '' ===  values.aspect_ratio ) {
					return '';
				}

				let css = '';

				// Calc Ratio
				if ( 'custom' ===  values.aspect_ratio && '' !==  values.custom_aspect_ratio ) {
					css += '--awb-aspect-ratio: 100 / ' + values.custom_aspect_ratio + ';';
				} else {
					const 	aspectRatio = values.aspect_ratio.split( '-' ),
							width 		= aspectRatio[ 0 ] || '',
							height 		= aspectRatio[ 1 ] || '';
					css += `--awb-aspect-ratio: ${width / height};`;
				}

				//Ratio Position
				if ( '' !== values.aspect_ratio_position ) {
					css += '--awb-object-position:' + values.aspect_ratio_position + ';';
				}

				return css;
			},

			/**
			 * Check if parent using dynamic content.
			 *
			 * @since 3.11
			 * @param object values the values object.
			 * @return string
			 */
			isParentHasDynamicContent( values ) {
				if ( values.dynamic_params ) {
					let dynamicData = FusionPageBuilderApp.base64Decode( values.dynamic_params );
					dynamicData = _.unescape( dynamicData );
					dynamicData = JSON.parse( dynamicData );

					return dynamicData.parent_dynamic_content ? true : false;
				}

				return false;
			},

			parseCSS: function () {
				var css = '';

				if ( 'object' !== typeof this.dynamic_css ) {
					return '';
				}

				_.each( this.dynamic_css, function ( properties, selector ) {
					if ( 'object' === typeof properties ) {
						css += selector + '{';
						_.each( properties, function ( value, property ) {
							css += property + ':' + value + ';';
						} );
						css += '}';
					}
				} );

				return css;
			},

			// Scroll to element and highlight it.
			scrollHighlight: function( scroll = true, highlight = true ) {
				var $trigger       = jQuery( '#fb-preview' )[ 0 ].contentWindow.jQuery( '.fusion-one-page-text-link' ),
					$el            = this.$el,
					elementIdAdded = false,
					$highlightedEl,
					elId           = $el.attr( 'id' );

				scroll = 'undefined' === typeof scroll ? true : scroll;

				if ( ! elId ) {
					$el.attr( 'id', 'fusion-temporary-id-' + this.cid );
					elId = 'fusion-temporary-id-' + this.cid;
					elementIdAdded = true;
				}

				setTimeout( function() {
					if ( scroll && $trigger.length && 'function' === typeof $trigger.fusion_scroll_to_anchor_target ) {
						$trigger.attr( 'href', '#' + elId ).fusion_scroll_to_anchor_target( 15 );
					}

					if ( elementIdAdded ) {
						setTimeout( function() {
							$el.removeAttr( 'id' );
						}, 10 );
					}

					if ( highlight ) {
						$highlightedEl = $el;
						// This is intended to be only for columns.
						if ( $el.find( '> .fusion-column-wrapper' ).length ) {
							$highlightedEl = $el.find( '> .fusion-column-wrapper' );
						}

						$highlightedEl.addClass( 'fusion-active-highlight' );
						setTimeout( function() {
							$highlightedEl.removeClass( 'fusion-active-highlight' );
						}, 6000 );
					}
				}, 10 );
			},
			isString( s ) {
				if ( 'string' === typeof s || s instanceof String ) {
					return true;
				}
				return false;
			}

		} );
	} );
}( jQuery ) );
