(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' ).attr( 'src', $$.data( 'src' ) ).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'} );
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').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)
.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)
.find('.preview-cell-weight').html(Math.round(ncw * 1000) / 10);
$(this).data('prevCellClone').css('width', rowPreview.width() * pcw)
.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.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').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($$)
.focus(function () {
clearTimeout(timeout);
})
.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).blur();
}
})
.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).focus().select();
} else {
inputs.eq(i + 1).focus().select();
}
}
})
.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);
})
.click(function () {
$(this).select();
});
});
$(this).siblings('.preview-cell-weight-input').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":31}],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( '' ).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 ).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.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 = {
/**
* 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] );
}
}
},
};
},{}],13:[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;
}
};
},{}],14:[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 );
},
}
},{}],15:[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, {}, 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' ).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 );
} );
};
},{}],16:[function(require,module,exports){
/**
* Everything we need for SiteOrigin Page Builder.
*
* @copyright Greg Priday 2013 - 2016 -