(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 1 ) {
var lastEntry = this.at( this.models.length - 2 );
if (
(
entry.get( 'text' ) === lastEntry.get( 'text' ) && entry.get( 'time' ) - lastEntry.get( 'time' ) < 15
) ||
(
entry.get( 'data' ) === lastEntry.get( 'data' )
)
) {
// If both entries have the same text and are within 20 seconds of each other, or have the same data, then remove most recent
this.remove( entry );
lastEntry.set( 'count', lastEntry.get( 'count' ) + 1 );
}
}
// Make sure that there are not to many entries in this collection
while ( this.models.length > this.maxSize ) {
this.shift();
}
}
} );
},{}],3:[function(require,module,exports){
var panels = window.panels;
module.exports = Backbone.Collection.extend( {
model: panels.model.row,
/**
* Destroy all the rows in this collection
*/
empty: function () {
var model;
do {
model = this.collection.first();
if ( ! model ) {
break;
}
model.destroy();
} while ( true );
}
} );
},{}],4:[function(require,module,exports){
var panels = window.panels;
module.exports = Backbone.Collection.extend( {
model: panels.model.widget,
initialize: function () {
}
} );
},{}],5:[function(require,module,exports){
var panels = window.panels, $ = jQuery;
module.exports = panels.view.dialog.extend( {
dialogClass: 'so-panels-dialog-add-builder',
render: function () {
// Render the dialog and attach it to the builder interface
this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-builder' ).html(), {} ) );
this.$( '.so-content .siteorigin-panels-builder' ).append( this.builder.$el );
},
initializeDialog: function () {
var thisView = this;
this.once( 'open_dialog_complete', function () {
thisView.builder.initSortable();
} );
this.on( 'open_dialog_complete', function () {
thisView.builder.trigger( 'builder_resize' );
} );
}
} );
},{}],6:[function(require,module,exports){
var panels = window.panels, $ = jQuery;
module.exports = panels.view.dialog.extend( {
historyEntryTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-dialog-history-entry' ).html() ) ),
entries: {},
currentEntry: null,
revertEntry: null,
selectedEntry: null,
previewScrollTop: null,
dialogClass: 'so-panels-dialog-history',
dialogIcon: 'history',
events: {
'click .so-close': 'closeDialog',
'click .so-restore': 'restoreSelectedEntry'
},
initializeDialog: function () {
this.entries = new panels.collection.historyEntries();
this.on( 'open_dialog', this.setCurrentEntry, this );
this.on( 'open_dialog', this.renderHistoryEntries, this );
},
render: function () {
var thisView = this;
// Render the dialog and attach it to the builder interface
this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-history' ).html(), {} ) );
this.$( 'iframe.siteorigin-panels-history-iframe' ).on( 'load', function () {
var $$ = $( this );
$$.show();
$$.contents().scrollTop( thisView.previewScrollTop );
} );
},
/**
* Set the original entry. This should be set when creating the dialog.
*
* @param {panels.model.builder} builder
*/
setRevertEntry: function ( builder ) {
this.revertEntry = new panels.model.historyEntry( {
data: JSON.stringify( builder.getPanelsData() ),
time: parseInt( new Date().getTime() / 1000 )
} );
},
/**
* This is triggered when the dialog is opened.
*/
setCurrentEntry: function () {
this.currentEntry = new panels.model.historyEntry( {
data: JSON.stringify( this.builder.model.getPanelsData() ),
time: parseInt( new Date().getTime() / 1000 )
} );
this.selectedEntry = this.currentEntry;
this.previewEntry( this.currentEntry );
this.$( '.so-buttons .so-restore' ).addClass( 'disabled' );
},
/**
* Render the history entries in the sidebar
*/
renderHistoryEntries: function () {
// Set up an interval that will display the time since every 10 seconds
var thisView = this;
var c = this.$( '.history-entries' ).empty();
if ( this.currentEntry.get( 'data' ) !== this.revertEntry.get( 'data' ) || ! _.isEmpty( this.entries.models ) ) {
$( this.historyEntryTemplate( {title: panelsOptions.loc.history.revert, count: 1} ) )
.data( 'historyEntry', this.revertEntry )
.prependTo( c );
}
// Now load all the entries in this.entries
this.entries.each( function ( entry ) {
var html = thisView.historyEntryTemplate( {
title: panelsOptions.loc.history[entry.get( 'text' )],
count: entry.get( 'count' )
} );
$( html )
.data( 'historyEntry', entry )
.prependTo( c );
} );
$( this.historyEntryTemplate( {title: panelsOptions.loc.history['current'], count: 1} ) )
.data( 'historyEntry', this.currentEntry )
.addClass( 'so-selected' )
.prependTo( c );
// Handle loading and selecting
c.find( '.history-entry' ).on( 'click', function() {
var $$ = jQuery( this );
c.find( '.history-entry' ).not( $$ ).removeClass( 'so-selected' );
$$.addClass( 'so-selected' );
var entry = $$.data( 'historyEntry' );
thisView.selectedEntry = entry;
if ( thisView.selectedEntry.cid !== thisView.currentEntry.cid ) {
thisView.$( '.so-buttons .so-restore' ).removeClass( 'disabled' );
} else {
thisView.$( '.so-buttons .so-restore' ).addClass( 'disabled' );
}
thisView.previewEntry( entry );
} );
this.updateEntryTimes();
},
/**
* Preview an entry
*
* @param entry
*/
previewEntry: function ( entry ) {
var iframe = this.$( 'iframe.siteorigin-panels-history-iframe' );
iframe.hide();
this.previewScrollTop = iframe.contents().scrollTop();
this.$( 'form.history-form input[name="live_editor_panels_data"]' ).val( entry.get( 'data' ) );
this.$( 'form.history-form input[name="live_editor_post_ID"]' ).val( this.builder.config.postId );
this.$( 'form.history-form' ).trigger( 'submit' );
},
/**
* Restore the current entry
*/
restoreSelectedEntry: function () {
if ( this.$( '.so-buttons .so-restore' ).hasClass( 'disabled' ) ) {
return false;
}
if ( this.currentEntry.get( 'data' ) === this.selectedEntry.get( 'data' ) ) {
this.closeDialog();
return false;
}
// Add an entry for this restore event
if ( this.selectedEntry.get( 'text' ) !== 'restore' ) {
this.builder.addHistoryEntry( 'restore', this.builder.model.getPanelsData() );
}
this.builder.model.loadPanelsData( JSON.parse( this.selectedEntry.get( 'data' ) ) );
this.closeDialog();
return false;
},
/**
* Update the entry times for the list of entries down the side
*/
updateEntryTimes: function () {
var thisView = this;
this.$( '.history-entries .history-entry' ).each( function () {
var $$ = jQuery( this );
var time = $$.find( '.timesince' );
var entry = $$.data( 'historyEntry' );
time.html( thisView.timeSince( entry.get( 'time' ) ) );
} );
},
/**
* Gets the time since as a nice string.
*
* @param date
*/
timeSince: function ( time ) {
var diff = parseInt( new Date().getTime() / 1000 ) - time;
var parts = [];
var interval;
// There are 3600 seconds in an hour
if ( diff > 3600 ) {
interval = Math.floor( diff / 3600 );
if ( interval === 1 ) {
parts.push( panelsOptions.loc.time.hour.replace( '%d', interval ) );
} else {
parts.push( panelsOptions.loc.time.hours.replace( '%d', interval ) );
}
diff -= interval * 3600;
}
// There are 60 seconds in a minute
if ( diff > 60 ) {
interval = Math.floor( diff / 60 );
if ( interval === 1 ) {
parts.push( panelsOptions.loc.time.minute.replace( '%d', interval ) );
} else {
parts.push( panelsOptions.loc.time.minutes.replace( '%d', interval ) );
}
diff -= interval * 60;
}
if ( diff > 0 ) {
if ( diff === 1 ) {
parts.push( panelsOptions.loc.time.second.replace( '%d', diff ) );
} else {
parts.push( panelsOptions.loc.time.seconds.replace( '%d', diff ) );
}
}
// Return the amount of time ago
return _.isEmpty( parts ) ? panelsOptions.loc.time.now : panelsOptions.loc.time.ago.replace( '%s', parts.slice( 0, 2 ).join( ', ' ) );
}
} );
},{}],7:[function(require,module,exports){
var panels = window.panels, $ = jQuery;
module.exports = panels.view.dialog.extend( {
directoryTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-directory-items' ).html() ) ),
builder: null,
dialogClass: 'so-panels-dialog-prebuilt-layouts',
dialogIcon: 'layouts',
layoutCache: {},
currentTab: false,
directoryPage: 1,
events: {
'click .so-close': 'closeDialog',
'click .so-sidebar-tabs li a': 'tabClickHandler',
'click .so-content .layout': 'layoutClickHandler',
'keyup .so-sidebar-search': 'searchHandler',
// The directory items
'click .so-screenshot, .so-title': 'directoryItemClickHandler'
},
/**
* Initialize the prebuilt dialog.
*/
initializeDialog: function () {
var thisView = this;
this.on( 'open_dialog', function () {
thisView.$( '.so-sidebar-tabs li a' ).first().trigger( 'click' );
thisView.$( '.so-status' ).removeClass( 'so-panels-loading' );
} );
this.on( 'button_click', this.toolbarButtonClick, this );
},
/**
* Render the prebuilt layouts dialog
*/
render: function () {
this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-prebuilt' ).html(), {} ) );
this.initToolbar();
},
/**
*
* @param e
* @return {boolean}
*/
tabClickHandler: function ( e ) {
e.preventDefault();
// Reset selected item state when changing tabs
this.selectedLayoutItem = null;
this.uploadedLayout = null;
this.updateButtonState( false );
this.$( '.so-sidebar-tabs li' ).removeClass( 'tab-active' );
var $$ = $( e.target );
var tab = $$.attr( 'href' ).split( '#' )[1];
$$.parent().addClass( 'tab-active' );
var thisView = this;
// Empty everything
this.$( '.so-content' ).empty();
thisView.currentTab = tab;
if ( tab == 'import' ) {
this.displayImportExport();
} else {
this.displayLayoutDirectory( '', 1, tab );
}
thisView.$( '.so-sidebar-search' ).val( '' );
},
/**
* Display and setup the import/export form
*/
displayImportExport: function () {
var c = this.$( '.so-content' ).empty().removeClass( 'so-panels-loading' );
c.html( $( '#siteorigin-panels-dialog-prebuilt-importexport' ).html() );
var thisView = this;
var uploadUi = thisView.$( '.import-upload-ui' );
// Create the uploader
var uploader = new plupload.Uploader( {
runtimes: 'html5,silverlight,flash,html4',
browse_button: uploadUi.find( '.file-browse-button' ).get( 0 ),
container: uploadUi.get( 0 ),
drop_element: uploadUi.find( '.drag-upload-area' ).get( 0 ),
file_data_name: 'panels_import_data',
multiple_queues: false,
max_file_size: panelsOptions.plupload.max_file_size,
url: panelsOptions.plupload.url,
flash_swf_url: panelsOptions.plupload.flash_swf_url,
silverlight_xap_url: panelsOptions.plupload.silverlight_xap_url,
filters: [
{title: panelsOptions.plupload.filter_title, extensions: 'json'}
],
multipart_params: {
action: 'so_panels_import_layout'
},
init: {
PostInit: function ( uploader ) {
if ( uploader.features.dragdrop ) {
uploadUi.addClass( 'has-drag-drop' );
}
uploadUi.find( '.progress-precent' ).css( 'width', '0%' );
},
FilesAdded: function ( uploader ) {
uploadUi.find( '.file-browse-button' ).trigger( 'blur' );
uploadUi.find( '.drag-upload-area' ).removeClass( 'file-dragover' );
uploadUi.find( '.progress-bar' ).fadeIn( 'fast' );
thisView.$( '.js-so-selected-file' ).text( panelsOptions.loc.prebuilt_loading );
uploader.start();
},
UploadProgress: function ( uploader, file ) {
uploadUi.find( '.progress-precent' ).css( 'width', file.percent + '%' );
},
FileUploaded: function ( uploader, file, response ) {
var layout = JSON.parse( response.response );
if ( ! _.isUndefined( layout.widgets ) ) {
thisView.uploadedLayout = layout;
uploadUi.find( '.progress-bar' ).hide();
thisView.$( '.js-so-selected-file' ).text(
panelsOptions.loc.ready_to_insert.replace( '%s', file.name )
);
thisView.updateButtonState( true );
} else {
alert( panelsOptions.plupload.error_message );
}
},
Error: function () {
alert( panelsOptions.plupload.error_message );
}
}
} );
uploader.init();
if ( /Edge\/\d./i.test(navigator.userAgent) ){
// A very dirty fix for a Microsoft Edge issue.
// TODO find a more elegant fix if Edge gains market share
setTimeout( function(){
uploader.refresh();
}, 250 );
}
// This is
uploadUi.find( '.drag-upload-area' )
.on( 'dragover', function () {
$( this ).addClass( 'file-dragover' );
} )
.on( 'dragleave', function () {
$( this ).removeClass( 'file-dragover' );
} );
// Handle exporting the file
c.find( '.so-export' ).on( 'submit', function( e ) {
var $$ = $( this );
var panelsData = thisView.builder.model.getPanelsData();
var postName = $( 'input[name="post_title"], .editor-post-title__input' ).val();
if ( ! postName ) {
postName = $('input[name="post_ID"]').val();
} else if ( $( '.block-editor-page' ).length ) {
var currentBlockPosition = thisView.getCurrentBlockPosition();
if ( currentBlockPosition >= 0 ) {
postName += '-' + currentBlockPosition;
}
}
panelsData.name = postName;
$$.find( 'input[name="panels_export_data"]' ).val( JSON.stringify( panelsData ) );
} );
},
/**
* Return current block index.
*/
getCurrentBlockPosition: function() {
var selectedBlockClientId = wp.data.select( 'core/block-editor' ).getSelectedBlockClientId();
return wp.data.select( 'core/block-editor' ).getBlocks().findIndex( function ( block ) {
return block.clientId === selectedBlockClientId;
} );
},
/**
* Display the layout directory tab.
*
* @param query
*/
displayLayoutDirectory: function ( search, page, type ) {
var thisView = this;
var c = this.$( '.so-content' ).empty().addClass( 'so-panels-loading' );
if ( search === undefined ) {
search = '';
}
if ( page === undefined ) {
page = 1;
}
if ( type === undefined ) {
type = 'directory-siteorigin';
}
if ( type.match('^directory-') && ! panelsOptions.directory_enabled ) {
// Display the button to enable the prebuilt layout
c.removeClass( 'so-panels-loading' ).html( $( '#siteorigin-panels-directory-enable' ).html() );
c.find( '.so-panels-enable-directory' ).on( 'click', function( e ) {
e.preventDefault();
// Sent the query to enable the directory, then enable the directory
$.get(
panelsOptions.ajaxurl,
{action: 'so_panels_directory_enable'},
function () {
}
);
// Enable the layout directory
panelsOptions.directory_enabled = true;
c.addClass( 'so-panels-loading' );
thisView.displayLayoutDirectory( search, page, type );
} );
return;
}
// Get all the items for the current query
$.get(
panelsOptions.ajaxurl,
{
action: 'so_panels_layouts_query',
search: search,
page: page,
type: type,
builderType: this.builder.config.builderType,
},
function ( data ) {
// Skip this if we're no longer viewing the layout directory
if ( thisView.currentTab !== type ) {
return;
}
// Add the directory items
c.removeClass( 'so-panels-loading' ).html( thisView.directoryTemplate( data ) );
// Lets setup the next and previous buttons
var prev = c.find( '.so-previous' ), next = c.find( '.so-next' );
if ( page <= 1 ) {
prev.addClass( 'button-disabled' );
} else {
prev.on( 'click', function( e ) {
e.preventDefault();
thisView.displayLayoutDirectory( search, page - 1, thisView.currentTab );
} );
}
if ( page === data.max_num_pages || data.max_num_pages === 0 ) {
next.addClass( 'button-disabled' );
} else {
next.on( 'click', function( e ) {
e.preventDefault();
thisView.displayLayoutDirectory( search, page + 1, thisView.currentTab );
} );
}
// Handle nice preloading of the screenshots
c.find( '.so-screenshot' ).each( function () {
var $$ = $( this ), $a = $$.find( '.so-screenshot-wrapper' );
$a.css( 'height', ( $a.width() / 4 * 3 ) + 'px' ).addClass( 'so-loading' );
if ( $$.data( 'src' ) !== '' ) {
// Set the initial height
var $img = $( '' ).attr( 'src', $$.data( 'src' ) ).on( 'load', function () {
$a.removeClass( 'so-loading' ).css( 'height', 'auto' );
$img.appendTo( $a ).hide().fadeIn( 'fast' );
} );
} else {
$( '' ).attr( 'src', panelsOptions.prebuiltDefaultScreenshot ).appendTo( $a ).hide().fadeIn( 'fast' );
}
} );
// Set the title
c.find( '.so-directory-browse' ).html( data.title );
},
'json'
);
},
/**
* Set the selected state for the clicked layout directory item and remove previously selected item.
* Enable the toolbar buttons.
*/
directoryItemClickHandler: function ( e ) {
var $directoryItem = this.$( e.target ).closest( '.so-directory-item' );
this.$( '.so-directory-items' ).find( '.selected' ).removeClass( 'selected' );
$directoryItem.addClass( 'selected' );
this.selectedLayoutItem = {lid: $directoryItem.data( 'layout-id' ), type: $directoryItem.data( 'layout-type' )};
this.updateButtonState( true );
},
/**
* Load a particular layout into the builder.
*
* @param id
*/
toolbarButtonClick: function ( $button ) {
if ( ! this.canAddLayout() ) {
return false;
}
var position = $button.data( 'value' );
if ( _.isUndefined( position ) ) {
return false;
}
this.updateButtonState( false );
if ( $button.hasClass( 'so-needs-confirm' ) && ! $button.hasClass( 'so-confirmed' ) ) {
this.updateButtonState( true );
if ( $button.hasClass( 'so-confirming' ) ) {
return;
}
$button.addClass( 'so-confirming' );
var originalText = $button.html();
$button.html( '' + $button.data( 'confirm' ) );
setTimeout( function () {
$button.removeClass( 'so-confirmed' ).html( originalText );
}, 2500 );
setTimeout( function () {
$button.removeClass( 'so-confirming' );
$button.addClass( 'so-confirmed' );
}, 200 );
return false;
}
this.addingLayout = true;
if ( this.currentTab === 'import' ) {
this.addLayoutToBuilder( this.uploadedLayout, position );
} else {
this.loadSelectedLayout().then( function ( layout ) {
this.addLayoutToBuilder( layout, position );
}.bind( this ) );
}
},
canAddLayout: function () {
return (
this.selectedLayoutItem || this.uploadedLayout
) && ! this.addingLayout;
},
/**
* Load the layout according to selectedLayoutItem.
*/
loadSelectedLayout: function () {
this.setStatusMessage( panelsOptions.loc.prebuilt_loading, true );
var args = _.extend(
this.selectedLayoutItem,
{
action: 'so_panels_get_layout',
builderType: this.builder.config.builderType
}
);
var deferredLayout = new $.Deferred();
$.get(
panelsOptions.ajaxurl,
args,
function ( layout ) {
var msg = '';
if ( ! layout.success ) {
msg = layout.data.message;
deferredLayout.reject( layout.data );
} else {
deferredLayout.resolve( layout.data );
}
this.setStatusMessage( msg, false, ! layout.success );
this.updateButtonState( true );
}.bind( this )
);
return deferredLayout.promise();
},
/**
* Handle an update to the search
*/
searchHandler: function ( e ) {
if ( e.keyCode === 13 ) {
this.displayLayoutDirectory( $( e.currentTarget ).val(), 1, this.currentTab );
}
},
/**
* Attempt to set the 'Insert' button's state according to the `enabled` argument, also checking whether the
* requirements for inserting a layout have valid values.
*/
updateButtonState: function ( enabled ) {
enabled = enabled && (
this.selectedLayoutItem || this.uploadedLayout
);
var $button = this.$( '.so-import-layout' );
$button.prop( "disabled", ! enabled );
if ( enabled ) {
$button.removeClass( 'disabled' );
} else {
$button.addClass( 'disabled' );
}
},
addLayoutToBuilder: function ( layout, position ) {
this.builder.addHistoryEntry( 'prebuilt_loaded' );
this.builder.model.loadPanelsData( layout, position );
this.addingLayout = false;
this.closeDialog();
}
} );
},{}],8:[function(require,module,exports){
var panels = window.panels, $ = jQuery;
module.exports = panels.view.dialog.extend({
cellPreviewTemplate: _.template( panels.helpers.utils.processTemplate( $('#siteorigin-panels-dialog-row-cell-preview').html() ) ),
editableLabel: true,
events: {
'click .so-close': 'closeDialog',
// Toolbar buttons
'click .so-toolbar .so-save': 'saveHandler',
'click .so-toolbar .so-insert': 'insertHandler',
'click .so-toolbar .so-delete': 'deleteHandler',
'click .so-toolbar .so-duplicate': 'duplicateHandler',
// Changing the row
'change .row-set-form > *': 'setCellsFromForm',
'click .row-set-form button.set-row': 'setCellsFromForm',
},
rowView: null,
dialogIcon: 'add-row',
dialogClass: 'so-panels-dialog-row-edit',
styleType: 'row',
dialogType: 'edit',
/**
* The current settings, not yet saved to the model
*/
row: {
// This will be a clone of cells collection.
cells: null,
// The style settings of the row
style: {}
},
cellStylesCache: [],
initializeDialog: function () {
this.on('open_dialog', function () {
if (!_.isUndefined(this.model) && !_.isEmpty(this.model.get('cells'))) {
this.setRowModel(this.model);
} else {
this.setRowModel(null);
}
this.regenerateRowPreview();
this.renderStyles();
this.openSelectedCellStyles();
}, this);
// This is the default row layout
this.row = {
cells: new panels.collection.cells([{weight: 0.5}, {weight: 0.5}]),
style: {}
};
// Refresh panels data after both dialog form components are loaded
this.dialogFormsLoaded = 0;
var thisView = this;
this.on('form_loaded styles_loaded', function () {
this.dialogFormsLoaded++;
if (this.dialogFormsLoaded === 2) {
thisView.updateModel({
refreshArgs: {
silent: true
}
});
}
});
this.on('close_dialog', this.closeHandler);
this.on( 'edit_label', function ( text ) {
// If text is set to default values, just clear it.
if ( text === panelsOptions.loc.row.add || text === panelsOptions.loc.row.edit ) {
text = '';
}
this.model.set( 'label', text );
if ( _.isEmpty( text ) ) {
var title = this.dialogType === 'create' ? panelsOptions.loc.row.add : panelsOptions.loc.row.edit;
this.$( '.so-title').text( title );
}
}.bind( this ) );
},
/**
*
* @param dialogType Either "edit" or "create"
*/
setRowDialogType: function (dialogType) {
this.dialogType = dialogType;
},
/**
* Render the new row dialog
*/
render: function () {
var title = this.dialogType === 'create' ? panelsOptions.loc.row.add : panelsOptions.loc.row.edit;
this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-row' ).html(), {
title: title,
dialogType: this.dialogType
} ) );
var titleElt = this.$( '.so-title' );
if ( this.model.has( 'label' ) && ! _.isEmpty( this.model.get( 'label' ) ) ) {
titleElt.text( this.model.get( 'label' ) );
}
this.$( '.so-edit-title' ).val( titleElt.text() );
if (!this.builder.supports('addRow')) {
this.$('.so-buttons .so-duplicate').remove();
}
if (!this.builder.supports('deleteRow')) {
this.$('.so-buttons .so-delete').remove();
}
if (!_.isUndefined(this.model)) {
// Set the initial value of the
this.$( 'input[name="cells"].so-row-field' ).val( this.model.get( 'cells' ).length );
if ( this.model.has( 'ratio' ) ) {
this.$( 'select[name="ratio"].so-row-field' ).val( this.model.get( 'ratio' ) );
}
if ( this.model.has( 'ratio_direction' ) ) {
this.$( 'select[name="ratio_direction"].so-row-field' ).val( this.model.get( 'ratio_direction' ) );
}
}
this.$( 'input.so-row-field' ).on( 'keyup', function() {
$(this).trigger('change');
});
return this;
},
renderStyles: function () {
if ( this.styles ) {
this.styles.off( 'styles_loaded' );
this.styles.remove();
}
// Now we need to attach the style window
this.styles = new panels.view.styles();
this.styles.model = this.model;
this.styles.render('row', this.builder.config.postId, {
builderType: this.builder.config.builderType,
dialog: this
});
var $rightSidebar = this.$('.so-sidebar.so-right-sidebar');
this.styles.attach( $rightSidebar );
// Handle the loading class
this.styles.on('styles_loaded', function (hasStyles) {
if ( ! hasStyles ) {
// If we don't have styles remove the view.
this.styles.remove();
// If the sidebar is empty, hide it.
if ( $rightSidebar.children().length === 0 ) {
$rightSidebar.closest('.so-panels-dialog').removeClass('so-panels-dialog-has-right-sidebar');
$rightSidebar.hide();
}
}
}, this);
},
/**
* Set the row model we'll be using for this dialog.
*
* @param model
*/
setRowModel: function (model) {
this.model = model;
if (_.isEmpty(this.model)) {
return this;
}
// Set the rows to be a copy of the model
this.row = {
cells: this.model.get('cells').clone(),
style: {},
ratio: this.model.get('ratio'),
ratio_direction: this.model.get('ratio_direction'),
};
// Set the initial value of the cell field.
this.$( 'input[name="cells"].so-row-field' ).val( this.model.get( 'cells' ).length );
if ( this.model.has( 'ratio' ) ) {
this.$( 'select[name="ratio"].so-row-field' ).val( this.model.get( 'ratio' ) );
}
if ( this.model.has( 'ratio_direction' ) ) {
this.$( 'select[name="ratio_direction"].so-row-field' ).val( this.model.get( 'ratio_direction' ) );
}
this.clearCellStylesCache();
return this;
},
/**
* Regenerate the row preview and resizing interface.
*/
regenerateRowPreview: function () {
var thisDialog = this;
var rowPreview = this.$('.row-preview');
// If no selected cell, select the first cell.
var selectedIndex = this.getSelectedCellIndex();
rowPreview.empty();
var timeout;
// Represent the cells
this.row.cells.each(function (cellModel, i) {
var newCell = $(this.cellPreviewTemplate({weight: cellModel.get('weight')}));
rowPreview.append(newCell);
if(i == selectedIndex) {
newCell.find('.preview-cell-in').addClass('cell-selected');
}
var prevCell = newCell.prev();
var handle;
if (prevCell.length) {
handle = $('');
handle
.appendTo(newCell)
.on( 'dblclick', function () {
var prevCellModel = thisDialog.row.cells.at(i - 1);
var t = cellModel.get('weight') + prevCellModel.get('weight');
cellModel.set('weight', t / 2);
prevCellModel.set('weight', t / 2);
thisDialog.scaleRowWidths();
});
handle.draggable({
axis: 'x',
containment: rowPreview,
start: function (e, ui) {
// Create the clone for the current cell
var newCellClone = newCell.clone().appendTo(ui.helper).css({
position: 'absolute',
top: '0',
width: newCell.outerWidth(),
left: 6,
height: newCell.outerHeight()
});
newCellClone.find('.resize-handle').remove();
// Create the clone for the previous cell
var prevCellClone = prevCell.clone().appendTo(ui.helper).css({
position: 'absolute',
top: '0',
width: prevCell.outerWidth(),
right: 6,
height: prevCell.outerHeight()
});
prevCellClone.find('.resize-handle').remove();
$(this).data({
'newCellClone': newCellClone,
'prevCellClone': prevCellClone
});
// Hide the
newCell.find('> .preview-cell-in').css('visibility', 'hidden');
prevCell.find('> .preview-cell-in').css('visibility', 'hidden');
},
drag: function (e, ui) {
// Calculate the new cell and previous cell widths as a percent
var cellWeight = thisDialog.row.cells.at(i).get('weight');
var prevCellWeight = thisDialog.row.cells.at(i - 1).get('weight');
var ncw = cellWeight - (
(
ui.position.left + 6
) / rowPreview.width()
);
var pcw = prevCellWeight + (
(
ui.position.left + 6
) / rowPreview.width()
);
var helperLeft = ui.helper.offset().left - rowPreview.offset().left - 6;
$( this ).data( 'newCellClone' ).css( 'width', rowPreview.width() * ncw + 'px' )
.find('.preview-cell-weight').html(Math.round(ncw * 1000) / 10);
$( this ).data( 'prevCellClone' ).css( 'width', rowPreview.width() * pcw + 'px' )
.find('.preview-cell-weight').html(Math.round(pcw * 1000) / 10);
},
stop: function (e, ui) {
// Remove the clones
$(this).data('newCellClone').remove();
$(this).data('prevCellClone').remove();
// Reshow the main cells
newCell.find('.preview-cell-in').css('visibility', 'visible');
prevCell.find('.preview-cell-in').css('visibility', 'visible');
// Calculate the new cell weights
var offset = ui.position.left + 6;
var percent = offset / rowPreview.width();
// Ignore this if any of the cells are below 2% in width.
var cellModel = thisDialog.row.cells.at(i);
var prevCellModel = thisDialog.row.cells.at(i - 1);
if (cellModel.get('weight') - percent > 0.02 && prevCellModel.get('weight') + percent > 0.02) {
cellModel.set('weight', cellModel.get('weight') - percent);
prevCellModel.set('weight', prevCellModel.get('weight') + percent);
}
thisDialog.scaleRowWidths();
ui.helper.css('left', -6);
}
});
}
newCell.on( 'click', function( event ) {
if ( ! ( $(event.target).is('.preview-cell') || $(event.target).is('.preview-cell-in') ) ) {
return;
}
var cell = $(event.target);
cell.closest('.row-preview').find('.preview-cell .preview-cell-in').removeClass('cell-selected');
cell.addClass('cell-selected');
this.openSelectedCellStyles();
}.bind(this));
// Make this row weight click editable
newCell.find( '.preview-cell-weight' ).on( 'click', function( ci ) {
// Disable the draggable while entering values
thisDialog.$('.resize-handle').css('pointer-event', 'none').draggable('disable');
rowPreview.find('.preview-cell-weight').each(function () {
var $$ = jQuery(this).hide();
$('')
.val(parseFloat($$.html())).insertAfter($$)
.on( 'focus', function() {
clearTimeout(timeout);
})
.on( 'keyup', function( e ) {
if (e.keyCode !== 9) {
// Only register the interaction if the user didn't press tab
$(this).removeClass('no-user-interacted');
}
// Enter is clicked
if (e.keyCode === 13) {
e.preventDefault();
$( this ).trigger( 'blur' );
}
})
.on( 'keydown', function( e ) {
if (e.keyCode === 9) {
e.preventDefault();
// Tab will always cycle around the row inputs
var inputs = rowPreview.find('.preview-cell-weight-input');
var i = inputs.index($(this));
if (i === inputs.length - 1) {
inputs.eq( 0 ).trigger( 'focus' ).trigger( 'select' );
} else {
inputs.eq( i + 1 ).trigger( 'focus' ).trigger( 'select' );
}
}
})
.on( 'blur', function() {
rowPreview.find('.preview-cell-weight-input').each(function (i, el) {
if (isNaN(parseFloat($(el).val()))) {
$(el).val(Math.floor(thisDialog.row.cells.at(i).get('weight') * 1000) / 10);
}
});
timeout = setTimeout(function () {
// If there are no weight inputs, then skip this
if (rowPreview.find('.preview-cell-weight-input').length === 0) {
return false;
}
// Go through all the inputs
var rowWeights = [],
rowChanged = [],
changedSum = 0,
unchangedSum = 0;
rowPreview.find('.preview-cell-weight-input').each(function (i, el) {
var val = parseFloat($(el).val());
if (isNaN(val)) {
val = 1 / thisDialog.row.cells.length;
} else {
val = Math.round(val * 10) / 1000;
}
// Check within 3 decimal points
var changed = !$(el).hasClass('no-user-interacted');
rowWeights.push(val);
rowChanged.push(changed);
if (changed) {
changedSum += val;
} else {
unchangedSum += val;
}
});
if (changedSum > 0 && unchangedSum > 0 && (
1 - changedSum
) > 0) {
// Balance out the unchanged rows to occupy the weight left over by the changed sum
for (var i = 0; i < rowWeights.length; i++) {
if (!rowChanged[i]) {
rowWeights[i] = (
rowWeights[i] / unchangedSum
) * (
1 - changedSum
);
}
}
}
// Last check to ensure total weight is 1
var sum = _.reduce(rowWeights, function (memo, num) {
return memo + num;
});
rowWeights = rowWeights.map(function (w) {
return w / sum;
});
// Set the new cell weights and regenerate the preview.
if (Math.min.apply(Math, rowWeights) > 0.01) {
thisDialog.row.cells.each(function (cell, i) {
cell.set('weight', rowWeights[i]);
});
}
// Now lets animate the cells into their new widths
rowPreview.find('.preview-cell').each(function (i, el) {
var cellWeight = thisDialog.row.cells.at(i).get('weight');
$(el).animate({'width': Math.round(cellWeight * 1000) / 10 + "%"}, 250);
$(el).find('.preview-cell-weight-input').val(Math.round(cellWeight * 1000) / 10);
});
// So the draggable handle is not hidden.
rowPreview.find('.preview-cell').css('overflow', 'visible');
setTimeout(thisDialog.regenerateRowPreview.bind(thisDialog), 260);
}, 100);
})
.on( 'click', function () {
$( this ).trigger( 'select' );
});
});
$(this).siblings( '.preview-cell-weight-input' ).trigger( 'select');
});
}, this);
this.trigger('form_loaded', this);
},
getSelectedCellIndex: function() {
var selectedIndex = -1;
this.$('.preview-cell .preview-cell-in').each(function(index, el) {
if($(el).is('.cell-selected')) {
selectedIndex = index;
}
});
return selectedIndex;
},
openSelectedCellStyles: function() {
if (!_.isUndefined(this.cellStyles)) {
if (this.cellStyles.stylesLoaded) {
var style = {};
try {
style = this.getFormValues('.so-sidebar .so-visual-styles.so-cell-styles').style;
}
catch (err) {
console.log('Error retrieving cell styles - ' + err.message);
}
this.cellStyles.model.set('style', style);
}
this.cellStyles.detach();
}
this.cellStyles = this.getSelectedCellStyles();
if ( this.cellStyles ) {
var $rightSidebar = this.$( '.so-sidebar.so-right-sidebar' );
this.cellStyles.attach( $rightSidebar );
this.cellStyles.on( 'styles_loaded', function ( hasStyles ) {
if ( hasStyles ) {
$rightSidebar.closest('.so-panels-dialog').addClass('so-panels-dialog-has-right-sidebar');
$rightSidebar.show();
}
} );
}
},
getSelectedCellStyles: function () {
var cellIndex = this.getSelectedCellIndex();
if ( cellIndex > -1 ) {
var cellStyles = this.cellStylesCache[cellIndex];
if ( !cellStyles ) {
cellStyles = new panels.view.styles();
cellStyles.model = this.row.cells.at( cellIndex );
cellStyles.render( 'cell', this.builder.config.postId, {
builderType: this.builder.config.builderType,
dialog: this,
index: cellIndex,
} );
this.cellStylesCache[cellIndex] = cellStyles;
}
}
return cellStyles;
},
clearCellStylesCache: function () {
// Call remove() on all cell styles to remove data, event listeners etc.
this.cellStylesCache.forEach(function (cellStyles) {
cellStyles.remove();
cellStyles.off( 'styles_loaded' );
});
this.cellStylesCache = [];
},
/**
* Visually scale the row widths based on the cell weights
*/
scaleRowWidths: function () {
var thisDialog = this;
this.$('.row-preview .preview-cell').each(function (i, el) {
var cell = thisDialog.row.cells.at(i);
$(el)
.css('width', cell.get('weight') * 100 + "%")
.find('.preview-cell-weight').html(Math.round(cell.get('weight') * 1000) / 10);
});
},
/**
* Get the weights from the
*/
setCellsFromForm: function () {
try {
var f = {
'cells': parseInt(this.$('.row-set-form input[name="cells"]').val()),
'ratio': parseFloat(this.$('.row-set-form select[name="ratio"]').val()),
'direction': this.$('.row-set-form select[name="ratio_direction"]').val()
};
if (_.isNaN(f.cells)) {
f.cells = 1;
}
if (isNaN(f.ratio)) {
f.ratio = 1;
}
if (f.cells < 1) {
f.cells = 1;
this.$('.row-set-form input[name="cells"]').val(f.cells);
}
else if (f.cells > 12) {
f.cells = 12;
this.$('.row-set-form input[name="cells"]').val(f.cells);
}
this.$('.row-set-form select[name="ratio"]').val(f.ratio);
var cells = [];
var cellCountChanged = (
this.row.cells.length !== f.cells
);
// Now, lets create some cells
var currentWeight = 1;
for (var i = 0; i < f.cells; i++) {
cells.push(currentWeight);
currentWeight *= f.ratio;
}
// Now lets make sure that the row weights add up to 1
var totalRowWeight = _.reduce(cells, function (memo, weight) {
return memo + weight;
});
cells = _.map(cells, function (cell) {
return cell / totalRowWeight;
});
// Don't return cells that are too small
cells = _.filter(cells, function (cell) {
return cell > 0.01;
});
if (f.direction === 'left') {
cells = cells.reverse();
}
// Discard deleted cells.
this.row.cells = new panels.collection.cells(this.row.cells.first(cells.length));
_.each(cells, function (cellWeight, index) {
var cell = this.row.cells.at(index);
if (!cell) {
cell = new panels.model.cell({weight: cellWeight, row: this.model});
this.row.cells.add(cell);
} else {
cell.set('weight', cellWeight);
}
}.bind(this));
this.row.ratio = f.ratio;
this.row.ratio_direction = f.direction;
if (cellCountChanged) {
this.regenerateRowPreview();
} else {
var thisDialog = this;
// Now lets animate the cells into their new widths
this.$('.preview-cell').each(function (i, el) {
var cellWeight = thisDialog.row.cells.at(i).get('weight');
$(el).animate({'width': Math.round(cellWeight * 1000) / 10 + "%"}, 250);
$(el).find('.preview-cell-weight').html(Math.round(cellWeight * 1000) / 10);
});
// So the draggable handle is not hidden.
this.$('.preview-cell').css('overflow', 'visible');
setTimeout(thisDialog.regenerateRowPreview.bind(thisDialog), 260);
}
}
catch (err) {
console.log('Error setting cells - ' + err.message);
}
// Remove the button primary class
this.$('.row-set-form .so-button-row-set').removeClass('button-primary');
},
/**
* Handle a click on the dialog left bar tab
*/
tabClickHandler: function ($t) {
if ($t.attr('href') === '#row-layout') {
this.$('.so-panels-dialog').addClass('so-panels-dialog-has-right-sidebar');
} else {
this.$('.so-panels-dialog').removeClass('so-panels-dialog-has-right-sidebar');
}
},
/**
* Update the current model with what we have in the dialog
*/
updateModel: function (args) {
args = _.extend({
refresh: true,
refreshArgs: null
}, args);
// Set the cells
if (!_.isEmpty(this.model)) {
this.model.setCells( this.row.cells );
this.model.set( 'ratio', this.row.ratio );
this.model.set( 'ratio_direction', this.row.ratio_direction );
}
// Update the row styles if they've loaded
if (!_.isUndefined(this.styles) && this.styles.stylesLoaded) {
// This is an edit dialog, so there are styles
var style = {};
try {
style = this.getFormValues('.so-sidebar .so-visual-styles.so-row-styles').style;
}
catch (err) {
console.log('Error retrieving row styles - ' + err.message);
}
this.model.set('style', style);
}
// Update the cell styles if any are showing.
if (!_.isUndefined(this.cellStyles) && this.cellStyles.stylesLoaded) {
var style = {};
try {
style = this.getFormValues('.so-sidebar .so-visual-styles.so-cell-styles').style;
}
catch (err) {
console.log('Error retrieving cell styles - ' + err.message);
}
this.cellStyles.model.set('style', style);
}
if (args.refresh) {
this.builder.model.refreshPanelsData(args.refreshArgs);
}
},
/**
* Insert the new row
*/
insertHandler: function () {
this.builder.addHistoryEntry('row_added');
this.updateModel();
var activeCell = this.builder.getActiveCell({
createCell: false,
});
var options = {};
if (activeCell !== null) {
options.at = this.builder.model.get('rows').indexOf(activeCell.row) + 1;
}
// Set up the model and add it to the builder
this.model.collection = this.builder.model.get('rows');
this.builder.model.get('rows').add(this.model, options);
this.closeDialog();
this.builder.model.refreshPanelsData();
return false;
},
/**
* We'll just save this model and close the dialog
*/
saveHandler: function () {
this.builder.addHistoryEntry('row_edited');
this.updateModel();
this.closeDialog();
this.builder.model.refreshPanelsData();
return false;
},
/**
* The user clicks delete, so trigger deletion on the row model
*/
deleteHandler: function () {
// Trigger a destroy on the model that will happen with a visual indication to the user
this.rowView.visualDestroyModel();
this.closeDialog({silent: true});
return false;
},
/**
* Duplicate this row
*/
duplicateHandler: function () {
this.builder.addHistoryEntry('row_duplicated');
var duplicateRow = this.model.clone(this.builder.model);
this.builder.model.get('rows').add( duplicateRow, {
at: this.builder.model.get('rows').indexOf(this.model) + 1
} );
this.closeDialog({silent: true});
return false;
},
closeHandler: function() {
this.clearCellStylesCache();
if( ! _.isUndefined(this.cellStyles) ) {
this.cellStyles = undefined;
}
},
});
},{}],9:[function(require,module,exports){
var panels = window.panels, $ = jQuery;
var jsWidget = require( '../view/widgets/js-widget' );
module.exports = panels.view.dialog.extend( {
builder: null,
sidebarWidgetTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-dialog-widget-sidebar-widget' ).html() ) ),
dialogClass: 'so-panels-dialog-edit-widget',
dialogIcon: 'add-widget',
widgetView: false,
savingWidget: false,
editableLabel: true,
events: {
'click .so-close': 'saveHandler',
'click .so-nav.so-previous': 'navToPrevious',
'click .so-nav.so-next': 'navToNext',
// Action handlers
'click .so-toolbar .so-delete': 'deleteHandler',
'click .so-toolbar .so-duplicate': 'duplicateHandler'
},
initializeDialog: function () {
var thisView = this;
this.listenTo( this.model, 'change:values', this.handleChangeValues );
this.listenTo( this.model, 'destroy', this.remove );
// Refresh panels data after both dialog form components are loaded
this.dialogFormsLoaded = 0;
this.on( 'form_loaded styles_loaded', function () {
this.dialogFormsLoaded ++;
if ( this.dialogFormsLoaded === 2 ) {
thisView.updateModel( {
refreshArgs: {
silent: true
}
} );
}
} );
this.on( 'edit_label', function ( text ) {
// If text is set to default value, just clear it.
if ( text === panelsOptions.widgets[ this.model.get( 'class' ) ][ 'title' ] ) {
text = '';
}
this.model.set( 'label', text );
if ( _.isEmpty( text ) ) {
this.$( '.so-title' ).text( this.model.getWidgetField( 'title' ) );
}
}.bind( this ) );
},
/**
* Render the widget dialog.
*/
render: function () {
// Render the dialog and attach it to the builder interface
this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-widget' ).html(), {} ) );
this.loadForm();
var title = this.model.getWidgetField( 'title' );
this.$( '.so-title .widget-name' ).html( title );
this.$( '.so-edit-title' ).val( title );
if( ! this.builder.supports( 'addWidget' ) ) {
this.$( '.so-buttons .so-duplicate' ).remove();
}
if( ! this.builder.supports( 'deleteWidget' ) ) {
this.$( '.so-buttons .so-delete' ).remove();
}
// Now we need to attach the style window
this.styles = new panels.view.styles();
this.styles.model = this.model;
this.styles.render( 'widget', this.builder.config.postId, {
builderType: this.builder.config.builderType,
dialog: this
} );
var $rightSidebar = this.$( '.so-sidebar.so-right-sidebar' );
this.styles.attach( $rightSidebar );
// Handle the loading class
this.styles.on( 'styles_loaded', function ( hasStyles ) {
// If we don't have styles remove the empty sidebar.
if ( ! hasStyles ) {
$rightSidebar.closest( '.so-panels-dialog' ).removeClass( 'so-panels-dialog-has-right-sidebar' );
$rightSidebar.remove();
}
}, this );
},
/**
* Get the previous widget editing dialog by looking at the dom.
* @returns {*}
*/
getPrevDialog: function () {
var widgets = this.builder.$( '.so-cells .cell .so-widget' );
if ( widgets.length <= 1 ) {
return false;
}
var currentIndex = widgets.index( this.widgetView.$el );
if ( currentIndex === 0 ) {
return false;
} else {
var widgetView;
do {
widgetView = widgets.eq( --currentIndex ).data( 'view' );
if ( ! _.isUndefined( widgetView ) && ! widgetView.model.get( 'read_only' ) ) {
return widgetView.getEditDialog();
}
} while( ! _.isUndefined( widgetView ) && currentIndex > 0 );
}
return false;
},
/**
* Get the next widget editing dialog by looking at the dom.
* @returns {*}
*/
getNextDialog: function () {
var widgets = this.builder.$( '.so-cells .cell .so-widget' );
if ( widgets.length <= 1 ) {
return false;
}
var currentIndex = widgets.index( this.widgetView.$el );
if ( currentIndex === widgets.length - 1 ) {
return false;
} else {
var widgetView;
do {
widgetView = widgets.eq( ++currentIndex ).data( 'view' );
if ( ! _.isUndefined( widgetView ) && ! widgetView.model.get( 'read_only' ) ) {
return widgetView.getEditDialog();
}
} while( ! _.isUndefined( widgetView ) );
}
return false;
},
/**
* Load the widget form from the server.
* This is called when rendering the dialog for the first time.
*/
loadForm: function () {
// don't load the form if this dialog hasn't been rendered yet
if ( ! this.$( '> *' ).length ) {
return;
}
this.$( '.so-content' ).addClass( 'so-panels-loading' );
var data = {
'action': 'so_panels_widget_form',
'widget': this.model.get( 'class' ),
'instance': JSON.stringify( this.model.get( 'values' ) ),
'raw': this.model.get( 'raw' )
};
var $soContent = this.$( '.so-content' );
$.post( panelsOptions.ajaxurl, data, null, 'html' )
.done( function ( result ) {
// Add in the CID of the widget model
var html = result.replace( /{\$id}/g, this.model.cid );
// Load this content into the form
$soContent
.removeClass( 'so-panels-loading' )
.html( html );
// Trigger all the necessary events
this.trigger( 'form_loaded', this );
// For legacy compatibility, trigger a panelsopen event
this.$( '.panel-dialog' ).trigger( 'panelsopen' );
// If the main dialog is closed from this point on, save the widget content
this.on( 'close_dialog', this.updateModel, this );
var widgetContent = $soContent.find( '> .widget-content' );
// If there's a widget content wrapper, this is one of the new widgets in WP 4.8 which need some special
// handling in JS.
if ( widgetContent.length > 0 ) {
jsWidget.addWidget( $soContent, this.model.widget_id );
}
}.bind( this ) )
.fail( function ( error ) {
var html;
if ( error && error.responseText ) {
html = error.responseText;
} else {
html = panelsOptions.forms.loadingFailed;
}
$soContent
.removeClass( 'so-panels-loading' )
.html( html );
} );
},
/**
* Save the widget from the form to the model
*/
updateModel: function ( args ) {
args = _.extend( {
refresh: true,
refreshArgs: null
}, args );
// Get the values from the form and assign the new values to the model
this.savingWidget = true;
if ( ! this.model.get( 'missing' ) ) {
// Only get the values for non missing widgets.
var values = this.getFormValues();
if ( _.isUndefined( values.widgets ) ) {
values = {};
} else {
values = values.widgets;
values = values[Object.keys( values )[0]];
}
this.model.setValues( values );
this.model.set( 'raw', true ); // We've saved from the widget form, so this is now raw
}
if ( this.styles.stylesLoaded ) {
// If the styles view has loaded
var style = {};
try {
style = this.getFormValues( '.so-sidebar .so-visual-styles' ).style;
}
catch ( e ) {
}
this.model.set( 'style', style );
}
this.savingWidget = false;
if ( args.refresh ) {
this.builder.model.refreshPanelsData( args.refreshArgs );
}
},
/**
*
*/
handleChangeValues: function () {
if ( ! this.savingWidget ) {
// Reload the form when we've changed the model and we're not currently saving from the form
this.loadForm();
}
},
/**
* Save a history entry for this widget. Called when the dialog is closed.
*/
saveHandler: function () {
this.builder.addHistoryEntry( 'widget_edited' );
this.closeDialog();
},
/**
* When the user clicks delete.
*
* @returns {boolean}
*/
deleteHandler: function () {
this.widgetView.visualDestroyModel();
this.closeDialog( {silent: true} );
this.builder.model.refreshPanelsData();
return false;
},
duplicateHandler: function () {
// Call the widget duplicate handler directly
this.widgetView.duplicateHandler();
this.closeDialog( {silent: true} );
this.builder.model.refreshPanelsData();
return false;
}
} );
},{"../view/widgets/js-widget":32}],10:[function(require,module,exports){
var panels = window.panels, $ = jQuery;
module.exports = panels.view.dialog.extend( {
builder: null,
widgetTemplate: _.template( panels.helpers.utils.processTemplate( $( '#siteorigin-panels-dialog-widgets-widget' ).html() ) ),
filter: {},
dialogClass: 'so-panels-dialog-add-widget',
dialogIcon: 'add-widget',
events: {
'click .so-close': 'closeDialog',
'click .widget-type': 'widgetClickHandler',
'keyup .so-sidebar-search': 'searchHandler'
},
/**
* Initialize the widget adding dialog
*/
initializeDialog: function () {
this.on( 'open_dialog', function () {
this.filter.search = '';
this.filterWidgets( this.filter );
}, this );
this.on( 'open_dialog_complete', function () {
// Clear the search and re-filter the widgets when we open the dialog
this.$( '.so-sidebar-search' ).val( '' ).trigger( 'focus' );
this.balanceWidgetHeights();
} );
// We'll implement a custom tab click handler
this.on( 'tab_click', this.tabClickHandler, this );
},
render: function () {
// Render the dialog and attach it to the builder interface
this.renderDialog( this.parseDialogContent( $( '#siteorigin-panels-dialog-widgets' ).html(), {} ) );
// Add all the widgets
_.each( panelsOptions.widgets, function ( widget ) {
var $w = $( this.widgetTemplate( {
title: widget.title,
description: widget.description
} ) );
if ( _.isUndefined( widget.icon ) ) {
widget.icon = 'dashicons dashicons-admin-generic';
}
$( '' ).addClass( widget.icon ).prependTo( $w.find( '.widget-type-wrapper' ) );
$w.data( 'class', widget.class ).appendTo( this.$( '.widget-type-list' ) );
}, this );
// Add the sidebar tabs
var tabs = this.$( '.so-sidebar-tabs' );
_.each( panelsOptions.widget_dialog_tabs, function ( tab, key ) {
$( this.dialogTabTemplate( {'title': tab.title, 'tab': key} ) ).data( {
'message': tab.message,
'filter': tab.filter
} ).appendTo( tabs );
}, this );
// We'll be using tabs, so initialize them
this.initTabs();
var thisDialog = this;
$( window ).on( 'resize', function() {
thisDialog.balanceWidgetHeights();
} );
},
/**
* Handle a tab being clicked
*/
tabClickHandler: function ( $t ) {
// Get the filter from the tab, and filter the widgets
this.filter = $t.parent().data( 'filter' );
this.filter.search = this.$( '.so-sidebar-search' ).val();
var message = $t.parent().data( 'message' );
if ( _.isEmpty( message ) ) {
message = '';
}
this.$( '.so-toolbar .so-status' ).html( message );
this.filterWidgets( this.filter );
return false;
},
/**
* Handle changes to the search value
*/
searchHandler: function ( e ) {
if( e.which === 13 ) {
var visibleWidgets = this.$( '.widget-type-list .widget-type:visible' );
if( visibleWidgets.length === 1 ) {
visibleWidgets.trigger( 'click' );
}
}
else {
this.filter.search = $( e.target ).val().trim();
this.filterWidgets( this.filter );
}
},
/**
* Filter the widgets that we're displaying
* @param filter
*/
filterWidgets: function ( filter ) {
if ( _.isUndefined( filter ) ) {
filter = {};
}
if ( _.isUndefined( filter.groups ) ) {
filter.groups = '';
}
this.$( '.widget-type-list .widget-type' ).each( function () {
var $$ = $( this ), showWidget;
var widgetClass = $$.data( 'class' );
var widgetData = (
! _.isUndefined( panelsOptions.widgets[widgetClass] )
) ? panelsOptions.widgets[widgetClass] : null;
if ( _.isEmpty( filter.groups ) ) {
// This filter doesn't specify groups, so show all
showWidget = true;
} else if ( widgetData !== null && ! _.isEmpty( _.intersection( filter.groups, panelsOptions.widgets[widgetClass].groups ) ) ) {
// This widget is in the filter group
showWidget = true;
} else {
// This widget is not in the filter group
showWidget = false;
}
// This can probably be done with a more intelligent operator
if ( showWidget ) {
if ( ! _.isUndefined( filter.search ) && filter.search !== '' ) {
// Check if the widget title contains the search term
if ( widgetData.title.toLowerCase().indexOf( filter.search.toLowerCase() ) === - 1 ) {
showWidget = false;
}
}
}
if ( showWidget ) {
$$.show();
} else {
$$.hide();
}
} );
// Balance the tags after filtering
this.balanceWidgetHeights();
},
/**
* Add the widget to the current builder
*
* @param e
*/
widgetClickHandler: function ( e ) {
// Add the history entry
this.builder.trigger('before_user_adds_widget');
this.builder.addHistoryEntry( 'widget_added' );
var $w = $( e.currentTarget );
var widget = new panels.model.widget( {
class: $w.data( 'class' )
} );
// Add the widget to the cell model
widget.cell = this.builder.getActiveCell();
widget.cell.get('widgets').add( widget );
this.closeDialog();
this.builder.model.refreshPanelsData();
this.builder.trigger('after_user_adds_widget', widget);
},
/**
* Balance widgets in a given row so they have enqual height.
* @param e
*/
balanceWidgetHeights: function ( e ) {
var widgetRows = [[]];
var previousWidget = null;
// Work out how many widgets there are per row
var perRow = Math.round( this.$( '.widget-type' ).parent().width() / this.$( '.widget-type' ).width() );
// Add clears to create balanced rows
this.$( '.widget-type' )
.css( 'clear', 'none' )
.filter( ':visible' )
.each( function ( i, el ) {
if ( i % perRow === 0 && i !== 0 ) {
$( el ).css( 'clear', 'both' );
}
} );
// Group the widgets into rows
this.$( '.widget-type-wrapper' )
.css( 'height', 'auto' )
.filter( ':visible' )
.each( function ( i, el ) {
var $el = $( el );
if ( previousWidget !== null && previousWidget.position().top !== $el.position().top ) {
widgetRows[widgetRows.length] = [];
}
previousWidget = $el;
widgetRows[widgetRows.length - 1].push( $el );
} );
// Balance the height of the widgets within the row.
_.each( widgetRows, function ( row, i ) {
var maxHeight = _.max( row.map( function ( el ) {
return el.height();
} ) );
// Set the height of each widget in the row
_.each( row, function ( el ) {
el.height( maxHeight );
} );
} );
}
} );
},{}],11:[function(require,module,exports){
module.exports = {
/**
* Check if we have copy paste available.
* @returns {boolean|*}
*/
canCopyPaste: function(){
return typeof(Storage) !== "undefined" && panelsOptions.user;
},
/**
* Set the model that we're going to store in the clipboard
*/
setModel: function( model ){
if( ! this.canCopyPaste() ) {
return false;
}
var serial = panels.helpers.serialize.serialize( model );
if( model instanceof panels.model.row ) {
serial.thingType = 'row-model';
} else if( model instanceof panels.model.widget ) {
serial.thingType = 'widget-model';
}
// Store this in local storage
localStorage[ 'panels_clipboard_' + panelsOptions.user ] = JSON.stringify( serial );
return true;
},
/**
* Check if the current model stored in the clipboard is the expected type
*/
isModel: function( expected ){
if( ! this.canCopyPaste() ) {
return false;
}
var clipboardObject = localStorage[ 'panels_clipboard_' + panelsOptions.user ];
if( clipboardObject !== undefined ) {
clipboardObject = JSON.parse(clipboardObject);
return clipboardObject.thingType && clipboardObject.thingType === expected;
}
return false;
},
/**
* Get the model currently stored in the clipboard
*/
getModel: function( expected ){
if( ! this.canCopyPaste() ) {
return null;
}
var clipboardObject = localStorage[ 'panels_clipboard_' + panelsOptions.user ];
if( clipboardObject !== undefined ) {
clipboardObject = JSON.parse( clipboardObject );
if( clipboardObject.thingType && clipboardObject.thingType === expected ) {
return panels.helpers.serialize.unserialize( clipboardObject, clipboardObject.thingType, null );
}
}
return null;
},
};
},{}],12:[function(require,module,exports){
module.exports = {
isBlockEditor: function() {
return typeof wp.blocks !== 'undefined';
},
isClassicEditor: function( builder ) {
return builder.attachedToEditor && builder.$el.is( ':visible' );
},
}
},{}],13:[function(require,module,exports){
module.exports = {
/**
* Lock window scrolling for the main overlay
*/
lock: function () {
if ( jQuery( 'body' ).css( 'overflow' ) === 'hidden' ) {
return;
}
// lock scroll position, but retain settings for later
var scrollPosition = [
self.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
self.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
];
jQuery( 'body' )
.data( {
'scroll-position': scrollPosition
} )
.css( 'overflow', 'hidden' );
if( ! _.isUndefined( scrollPosition ) ) {
window.scrollTo( scrollPosition[0], scrollPosition[1] );
}
},
/**
* Unlock window scrolling
*/
unlock: function () {
if ( jQuery( 'body' ).css( 'overflow' ) !== 'hidden' ) {
return;
}
// Check that there are no more dialogs or a live editor
if ( ! jQuery( '.so-panels-dialog-wrapper' ).is( ':visible' ) && ! jQuery( '.so-panels-live-editor' ).is( ':visible' ) ) {
jQuery( 'body' ).css( 'overflow', 'visible' );
var scrollPosition = jQuery( 'body' ).data( 'scroll-position' );
if( ! _.isUndefined( scrollPosition ) ) {
window.scrollTo( scrollPosition[0], scrollPosition[1] );
}
}
},
};
},{}],14:[function(require,module,exports){
/*
This is a modified version of https://github.com/underdogio/backbone-serialize/
*/
/* global Backbone, module, panels */
module.exports = {
serialize: function( thing ){
var val;
if( thing instanceof Backbone.Model ) {
var retObj = {};
for ( var key in thing.attributes ) {
if (thing.attributes.hasOwnProperty( key ) ) {
// Skip these to avoid recursion
if( key === 'builder' || key === 'collection' ) { continue; }
// If the value is a Model or a Collection, then serialize them as well
val = thing.attributes[key];
if ( val instanceof Backbone.Model || val instanceof Backbone.Collection ) {
retObj[key] = this.serialize( val );
} else {
// Otherwise, save the original value
retObj[key] = val;
}
}
}
return retObj;
}
else if( thing instanceof Backbone.Collection ) {
// Walk over all of our models
var retArr = [];
for ( var i = 0; i < thing.models.length; i++ ) {
// If the model is serializable, then serialize it
val = thing.models[i];
if ( val instanceof Backbone.Model || val instanceof Backbone.Collection ) {
retArr.push( this.serialize( val ) );
} else {
// Otherwise (it is an object), return it in its current form
retArr.push( val );
}
}
// Return the serialized models
return retArr;
}
},
unserialize: function( thing, thingType, parent ) {
var retObj;
switch( thingType ) {
case 'row-model' :
retObj = new panels.model.row();
retObj.builder = parent;
var atts = { style: thing.style };
if ( thing.hasOwnProperty( 'label' ) ) {
atts.label = thing.label;
}
if ( thing.hasOwnProperty( 'color_label' ) ) {
atts.color_label = thing.color_label;
}
retObj.set( atts );
retObj.setCells( this.unserialize( thing.cells, 'cell-collection', retObj ) );
break;
case 'cell-model' :
retObj = new panels.model.cell();
retObj.row = parent;
retObj.set( 'weight', thing.weight );
retObj.set( 'style', thing.style );
retObj.set( 'widgets', this.unserialize( thing.widgets, 'widget-collection', retObj ) );
break;
case 'widget-model' :
retObj = new panels.model.widget();
retObj.cell = parent;
for ( var key in thing ) {
if ( thing.hasOwnProperty( key ) ) {
retObj.set( key, thing[key] );
}
}
retObj.set( 'widget_id', panels.helpers.utils.generateUUID() );
break;
case 'cell-collection':
retObj = new panels.collection.cells();
for( var i = 0; i < thing.length; i++ ) {
retObj.push( this.unserialize( thing[i], 'cell-model', parent ) );
}
break;
case 'widget-collection':
retObj = new panels.collection.widgets();
for( var i = 0; i < thing.length; i++ ) {
retObj.push( this.unserialize( thing[i], 'widget-model', parent ) );
}
break;
default:
console.log( 'Unknown Thing - ' + thingType );
break;
}
return retObj;
}
};
},{}],15:[function(require,module,exports){
module.exports = {
generateUUID: function(){
var d = new Date().getTime();
if( window.performance && typeof window.performance.now === "function" ){
d += performance.now(); //use high-precision timer if available
}
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace( /[xy]/g, function(c) {
var r = (d + Math.random()*16)%16 | 0;
d = Math.floor(d/16);
return ( c == 'x' ? r : (r&0x3|0x8) ).toString(16);
} );
return uuid;
},
processTemplate: function ( s ) {
if ( _.isUndefined( s ) || _.isNull( s ) ) {
return '';
}
s = s.replace( /{{%/g, '<%' );
s = s.replace( /%}}/g, '%>' );
s = s.trim();
return s;
},
// From this SO post: http://stackoverflow.com/questions/6139107/programmatically-select-text-in-a-contenteditable-html-element
selectElementContents: function( element ) {
var range = document.createRange();
range.selectNodeContents( element );
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange( range );
},
}
},{}],16:[function(require,module,exports){
/* global _, jQuery, panels */
var panels = window.panels, $ = jQuery;
module.exports = function ( config, force ) {
return this.each( function () {
var $$ = jQuery( this );
if ( $$.data( 'soPanelsBuilderWidgetInitialized' ) && ! force ) {
return;
}
var widgetId = $$.closest( 'form' ).find( '.widget-id' ).val();
// Create a config for this specific widget
var thisConfig = $.extend(
true,
{
builderSupports: $$.data( 'builder-supports' ),
},
config
);
// Exit if this isn't a real widget
if ( ! _.isUndefined( widgetId ) && widgetId.indexOf( '__i__' ) > - 1 ) {
return;
}
// Create the main builder model
var builderModel = new panels.model.builder();
// Now for the view to display the builder
var builderView = new panels.view.builder( {
model: builderModel,
config: thisConfig
} );
// Save panels data when we close the dialog, if we're in a dialog
var dialog = $$.closest( '.so-panels-dialog-wrapper' ).data( 'view' );
if ( ! _.isUndefined( dialog ) ) {
dialog.on( 'close_dialog', function () {
builderModel.refreshPanelsData();
} );
dialog.on( 'open_dialog_complete', function () {
// Make sure the new layout widget is always properly setup
builderView.trigger( 'builder_resize' );
} );
dialog.model.on( 'destroy', function () {
// Destroy the builder
builderModel.emptyRows().destroy();
} );
// Set the parent for all the sub dialogs
builderView.setDialogParents( panelsOptions.loc.layout_widget, dialog );
}
// Basic setup for the builder
var isWidget = Boolean( $$.closest( '.widget-content' ).length );
builderView
.render()
.attach( {
container: $$,
dialog: isWidget || $$.data('mode') === 'dialog',
type: $$.data( 'type' )
} )
.setDataField( $$.find( 'input.panels-data' ) );
if ( isWidget || $$.data('mode') === 'dialog' ) {
// Set up the dialog opening
builderView.setDialogParents( panelsOptions.loc.layout_widget, builderView.dialog );
$$.find( '.siteorigin-panels-display-builder' ).on( 'click', function( e ) {
e.preventDefault();
builderView.dialog.openDialog();
} );
} else {
// Remove the dialog opener button, this is already being displayed in a page builder dialog.
$$.find( '.siteorigin-panels-display-builder' ).parent().remove();
}
// Trigger a global jQuery event after we've setup the builder view
$( document ).trigger( 'panels_setup', builderView );
$$.data( 'soPanelsBuilderWidgetInitialized', true );
} );
};
},{}],17:[function(require,module,exports){
/**
* Everything we need for SiteOrigin Page Builder.
*
* @copyright Greg Priday 2013 - 2016 -
* @license GPL 3.0 http://www.gnu.org/licenses/gpl.html
*/
/* global Backbone, _, jQuery, tinyMCE, panelsOptions, plupload, confirm, console, require */
var panels = {};
// Store everything globally
window.panels = panels;
window.siteoriginPanels = panels;
// Helpers
panels.helpers = {};
panels.helpers.clipboard = require( './helpers/clipboard' );
panels.helpers.utils = require( './helpers/utils' );
panels.helpers.editor = require( './helpers/editor' );
panels.helpers.serialize = require( './helpers/serialize' );
panels.helpers.pageScroll = require( './helpers/page-scroll' );
// The models
panels.model = {};
panels.model.widget = require( './model/widget' );
panels.model.cell = require( './model/cell' );
panels.model.row = require( './model/row' );
panels.model.builder = require( './model/builder' );
panels.model.historyEntry = require( './model/history-entry' );
// The collections
panels.collection = {};
panels.collection.widgets = require( './collection/widgets' );
panels.collection.cells = require( './collection/cells' );
panels.collection.rows = require( './collection/rows' );
panels.collection.historyEntries = require( './collection/history-entries' );
// The views
panels.view = {};
panels.view.widget = require( './view/widget' );
panels.view.cell = require( './view/cell' );
panels.view.row = require( './view/row' );
panels.view.builder = require( './view/builder' );
panels.view.dialog = require( './view/dialog' );
panels.view.styles = require( './view/styles' );
panels.view.liveEditor = require( './view/live-editor' );
// The dialogs
panels.dialog = {};
panels.dialog.builder = require( './dialog/builder' );
panels.dialog.widgets = require( './dialog/widgets' );
panels.dialog.widget = require( './dialog/widget' );
panels.dialog.prebuilt = require( './dialog/prebuilt' );
panels.dialog.row = require( './dialog/row' );
panels.dialog.history = require( './dialog/history' );
// The utils
panels.utils = {};
panels.utils.menu = require( './utils/menu' );
// jQuery Plugins
jQuery.fn.soPanelsSetupBuilderWidget = require( './jquery/setup-builder-widget' );
// Set up Page Builder if we're on the main interface
jQuery( function ( $ ) {
var container,
field,
form,
builderConfig;
var $panelsMetabox = $( '#siteorigin-panels-metabox' );
form = $( 'form#post' );
if ( $panelsMetabox.length && form.length ) {
// This is usually the case when we're in the post edit interface
container = $panelsMetabox;
field = $panelsMetabox.find( '.siteorigin-panels-data-field' );
builderConfig = {
editorType: 'tinyMCE',
postId: $( '#post_ID' ).val(),
editorId: '#content',
builderType: $panelsMetabox.data( 'builder-type' ),
builderSupports: $panelsMetabox.data( 'builder-supports' ),
loadOnAttach: panelsOptions.loadOnAttach && $( '#auto_draft' ).val() == 1,
loadLiveEditor: $panelsMetabox.data('live-editor') == 1,
liveEditorPreview: container.data('preview-url')
};
}
else if ( $( '.siteorigin-panels-builder-form' ).length ) {
// We're dealing with another interface like the custom home page interface
var $$ = $( '.siteorigin-panels-builder-form' );
container = $$.find( '.siteorigin-panels-builder-container' );
field = $$.find( 'input[name="panels_data"]' );
form = $$;
builderConfig = {
editorType: 'standalone',
postId: $$.data( 'post-id' ),
editorId: '#post_content',
builderType: $$.data( 'type' ),
builderSupports: $$.data( 'builder-supports' ),
loadLiveEditor: false,
liveEditorPreview: $$.data( 'preview-url' )
};
}
if ( ! _.isUndefined( container ) ) {
// If we have a container, then set up the main builder
var panels = window.siteoriginPanels;
// Create the main builder model
var builderModel = new panels.model.builder();
// Now for the view to display the builder
var builderView = new panels.view.builder( {
model: builderModel,
config: builderConfig
} );
// Trigger an event before the panels setup to allow adding listeners for various builder events which are
// triggered during initial setup.
$(document).trigger('before_panels_setup', builderView);
// Set up the builder view
builderView
.render()
.attach( {
container: container
} )
.setDataField( field )
.attachToEditor();
// When the form is submitted, update the panels data
form.on( 'submit', function() {
// Refresh the data
builderModel.refreshPanelsData();
} );
container.removeClass( 'so-panels-loading' );
// Trigger a global jQuery event after we've setup the builder view. Everything is accessible form there
$( document ).trigger( 'panels_setup', builderView, window.panels );
// Make this globally available for things like Yoast compatibility.
window.soPanelsBuilderView = builderView;
}
// Setup new widgets when they're added in the standard widget interface
$( document ).on( 'widget-added', function ( e, widget ) {
$( widget ).find( '.siteorigin-page-builder-widget' ).soPanelsSetupBuilderWidget();
} );
// Setup existing widgets on the page (for the widgets interface)
if ( ! $( 'body' ).hasClass( 'wp-customizer' ) ) {
$( function () {
$( '.siteorigin-page-builder-widget' ).soPanelsSetupBuilderWidget();
} );
}
// A global escape handler
$(window).on('keyup', function(e){
// [Esc] to close
if ( e.which === 27 ) {
// Trigger a click on the last visible Page Builder window
$( '.so-panels-dialog-wrapper, .so-panels-live-editor' ).filter(':visible')
.last().find('.so-title-bar .so-close, .live-editor-close').trigger( 'click' );
}
});
} );
},{"./collection/cells":1,"./collection/history-entries":2,"./collection/rows":3,"./collection/widgets":4,"./dialog/builder":5,"./dialog/history":6,"./dialog/prebuilt":7,"./dialog/row":8,"./dialog/widget":9,"./dialog/widgets":10,"./helpers/clipboard":11,"./helpers/editor":12,"./helpers/page-scroll":13,"./helpers/serialize":14,"./helpers/utils":15,"./jquery/setup-builder-widget":16,"./model/builder":18,"./model/cell":19,"./model/history-entry":20,"./model/row":21,"./model/widget":22,"./utils/menu":23,"./view/builder":24,"./view/cell":25,"./view/dialog":26,"./view/live-editor":27,"./view/row":28,"./view/styles":29,"./view/widget":30}],18:[function(require,module,exports){
module.exports = Backbone.Model.extend({
layoutPosition: {
BEFORE: 'before',
AFTER: 'after',
REPLACE: 'replace',
},
rows: {},
defaults: {
'data': {
'widgets': [],
'grids': [],
'grid_cells': []
}
},
initialize: function () {
// These are the main rows in the interface
this.set( 'rows', new panels.collection.rows() );
},
/**
* Add a new row to this builder.
*
* @param attrs
* @param cells
* @param options
*/
addRow: function (attrs, cells, options) {
options = _.extend({
noAnimate: false
}, options);
var cellCollection = new panels.collection.cells(cells);
attrs = _.extend({
collection: this.get('rows'),
cells: cellCollection,
}, attrs);
// Create the actual row
var row = new panels.model.row(attrs);
row.builder = this;
this.get('rows').add( row, options );
return row;
},
/**
* Load the panels data into the builder
*
* @param data Object the layout and widgets data to load.
* @param position string Where to place the new layout. Allowed options are 'before', 'after'. Anything else will
* cause the new layout to replace the old one.
*/
loadPanelsData: function ( data, position ) {
try {
if ( position === this.layoutPosition.BEFORE ) {
data = this.concatPanelsData( data, this.getPanelsData() );
} else if ( position === this.layoutPosition.AFTER ) {
data = this.concatPanelsData( this.getPanelsData(), data );
}
// Start by destroying any rows that currently exist. This will in turn destroy cells, widgets and all the associated views
this.emptyRows();
// This will empty out the current rows and reload the builder data.
this.set( 'data', JSON.parse( JSON.stringify( data ) ), {silent: true} );
var cit = 0;
var rows = [];
if ( _.isUndefined( data.grid_cells ) ) {
this.trigger( 'load_panels_data' );
return;
}
var gi;
for ( var ci = 0; ci < data.grid_cells.length; ci ++ ) {
gi = parseInt( data.grid_cells[ci].grid );
if ( _.isUndefined( rows[gi] ) ) {
rows[gi] = [];
}
rows[gi].push( data.grid_cells[ci] );
}
var builderModel = this;
_.each( rows, function ( row, i ) {
var rowAttrs = {};
if ( ! _.isUndefined( data.grids[i].style ) ) {
rowAttrs.style = data.grids[i].style;
}
if ( ! _.isUndefined( data.grids[i].ratio) ) {
rowAttrs.ratio = data.grids[i].ratio;
}
if ( ! _.isUndefined( data.grids[i].ratio_direction) ) {
rowAttrs.ratio_direction = data.grids[i].ratio_direction
}
if ( ! _.isUndefined( data.grids[i].color_label) ) {
rowAttrs.color_label = data.grids[i].color_label;
}
if ( ! _.isUndefined( data.grids[i].label) ) {
rowAttrs.label = data.grids[i].label;
}
// This will create and add the row model and its cells
builderModel.addRow(rowAttrs, row, {noAnimate: true} );
} );
if ( _.isUndefined( data.widgets ) ) {
return;
}
// Add the widgets
_.each( data.widgets, function ( widgetData ) {
var panels_info = null;
if ( ! _.isUndefined( widgetData.panels_info ) ) {
panels_info = widgetData.panels_info;
delete widgetData.panels_info;
} else {
panels_info = widgetData.info;
delete widgetData.info;
}
var row = builderModel.get('rows').at( parseInt( panels_info.grid ) );
var cell = row.get('cells').at( parseInt( panels_info.cell ) );
var newWidget = new panels.model.widget( {
class: panels_info.class,
values: widgetData
} );
if ( ! _.isUndefined( panels_info.style ) ) {
newWidget.set( 'style', panels_info.style );
}
if ( ! _.isUndefined( panels_info.read_only ) ) {
newWidget.set( 'read_only', panels_info.read_only );
}
if ( ! _.isUndefined( panels_info.widget_id ) ) {
newWidget.set( 'widget_id', panels_info.widget_id );
}
else {
newWidget.set( 'widget_id', panels.helpers.utils.generateUUID() );
}
if ( ! _.isUndefined( panels_info.label ) ) {
newWidget.set( 'label', panels_info.label );
}
newWidget.cell = cell;
cell.get('widgets').add( newWidget, { noAnimate: true } );
} );
this.trigger( 'load_panels_data' );
}
catch ( err ) {
console.log( 'Error loading data: ' + err.message );
}
},
/**
* Concatenate the second set of Page Builder data to the first. There is some validation of input, but for the most
* part it's up to the caller to ensure the Page Builder data is well formed.
*/
concatPanelsData: function ( panelsDataA, panelsDataB ) {
if ( _.isUndefined( panelsDataB ) || _.isUndefined( panelsDataB.grids ) || _.isEmpty( panelsDataB.grids ) ||
_.isUndefined( panelsDataB.grid_cells ) || _.isEmpty( panelsDataB.grid_cells ) ) {
return panelsDataA;
}
if ( _.isUndefined( panelsDataA ) || _.isUndefined( panelsDataA.grids ) || _.isEmpty( panelsDataA.grids ) ) {
return panelsDataB;
}
var gridsBOffset = panelsDataA.grids.length;
var widgetsBOffset = ! _.isUndefined( panelsDataA.widgets ) ? panelsDataA.widgets.length : 0;
var newPanelsData = {grids: [], 'grid_cells': [], 'widgets': []};
// Concatenate grids (rows)
newPanelsData.grids = panelsDataA.grids.concat( panelsDataB.grids );
// Create a copy of panelsDataA grid_cells and widgets
if ( ! _.isUndefined( panelsDataA.grid_cells ) ) {
newPanelsData.grid_cells = panelsDataA.grid_cells.slice();
}
if ( ! _.isUndefined( panelsDataA.widgets ) ) {
newPanelsData.widgets = panelsDataA.widgets.slice();
}
var i;
// Concatenate grid cells (row columns)
for ( i = 0; i < panelsDataB.grid_cells.length; i ++ ) {
var gridCellB = panelsDataB.grid_cells[i];
gridCellB.grid = parseInt( gridCellB.grid ) + gridsBOffset;
newPanelsData.grid_cells.push( gridCellB );
}
// Concatenate widgets
if ( ! _.isUndefined( panelsDataB.widgets ) ) {
for ( i = 0; i < panelsDataB.widgets.length; i ++ ) {
var widgetB = panelsDataB.widgets[i];
widgetB.panels_info.grid = parseInt( widgetB.panels_info.grid ) + gridsBOffset;
widgetB.panels_info.id = parseInt( widgetB.panels_info.id ) + widgetsBOffset;
newPanelsData.widgets.push( widgetB );
}
}
return newPanelsData;
},
/**
* Convert the content of the builder into a object that represents the page builder data
*/
getPanelsData: function () {
var builder = this;
var data = {
'widgets': [],
'grids': [],
'grid_cells': []
};
var widgetId = 0;
this.get('rows').each( function ( row, ri ) {
row.get('cells').each( function ( cell, ci ) {
cell.get('widgets').each( function ( widget, wi ) {
// Add the data for the widget, including the panels_info field.
var panels_info = {
class: widget.get( 'class' ),
raw: widget.get( 'raw' ),
grid: ri,
cell: ci,
// Strictly this should be an index
id: widgetId ++,
widget_id: widget.get( 'widget_id' ),
style: widget.get( 'style' ),
label: widget.get( 'label' ),
};
if( _.isEmpty( panels_info.widget_id ) ) {
panels_info.widget_id = panels.helpers.utils.generateUUID();
}
var values = _.extend( _.clone( widget.get( 'values' ) ), {
panels_info: panels_info
} );
data.widgets.push( values );
} );
// Add the cell info
data.grid_cells.push( {
grid: ri,
index: ci,
weight: cell.get( 'weight' ),
style: cell.get( 'style' ),
} );
} );
data.grids.push( {
cells: row.get('cells').length,
style: row.get( 'style' ),
ratio: row.get('ratio'),
ratio_direction: row.get('ratio_direction'),
color_label: row.get( 'color_label' ),
label: row.get( 'label' ),
} );
} );
return data;
},
/**
* This will check all the current entries and refresh the panels data
*/
refreshPanelsData: function ( args ) {
args = _.extend( {
silent: false
}, args );
var oldData = this.get( 'data' );
var newData = this.getPanelsData();
this.set( 'data', newData, {silent: true} );
if ( ! args.silent && JSON.stringify( newData ) !== JSON.stringify( oldData ) ) {
// The default change event doesn't trigger on deep changes, so we'll trigger our own
this.trigger( 'change' );
this.trigger( 'change:data' );
this.trigger( 'refresh_panels_data', newData, args );
}
},
/**
* Empty all the rows and the cells/widgets they contain.
*/
emptyRows: function () {
_.invoke( this.get('rows').toArray(), 'destroy' );
this.get('rows').reset();
return this;
},
isValidLayoutPosition: function ( position ) {
return position === this.layoutPosition.BEFORE ||
position === this.layoutPosition.AFTER ||
position === this.layoutPosition.REPLACE;
},
/**
* Convert HTML into Panels Data
* @param html
*/
getPanelsDataFromHtml: function( html, editorClass ){
var thisModel = this;
var $html = jQuery( '
' + html + '
' );
if( $html.find('.panel-layout .panel-grid').length ) {
// This looks like Page Builder html, lets try parse it
var panels_data = {
grids: [],
grid_cells: [],
widgets: [],
};
// The Regex object that'll match SiteOrigin widgets
var re = new RegExp( panelsOptions.siteoriginWidgetRegex , "i" );
var decodeEntities = (function() {
// this prevents any overhead from creating the object each time
var element = document.createElement('div');
function decodeHTMLEntities (str) {
if(str && typeof str === 'string') {
// strip script/html tags
str = str.replace(/