assets/dist/js/admin-form-builder.js
/*!
* Torro Forms Version 1.0.8 (https://torro-forms.com)
* Licensed under GNU General Public License v2 (or later) (http://www.gnu.org/licenses/gpl-2.0.html)
*/
window.torro = window.torro || {};
( function( torro, $, _, i18n ) {
'use strict';
var instanceCount = 0,
initialized = [],
callbacks = {},
builder;
/**
* A form builder instance.
*
* @class
*
* @param {string} selector DOM selector for the wrapping element for the UI.
*/
function Builder( selector ) {
instanceCount++;
callbacks[ 'builder' + instanceCount ] = [];
this.instanceNumber = instanceCount;
this.$el = $( selector );
}
_.extend( Builder.prototype, {
/**
* Available element types.
*
* @since 1.0.0
* @access public
* @type {torro.Builder.ElementTypes}
*/
elementTypes: undefined,
/**
* Current form model.
*
* @since 1.0.0
* @access public
* @type {torro.Builder.FormModel}
*/
form: undefined,
/**
* Form view object.
*
* @since 1.0.0
* @access public
* @type {torro.Builder.FormView}
*/
formView: undefined,
/**
* Initializes the form builder.
*
* @since 1.0.0
* @access public
*/
init: function() {
if ( ! this.$el.length ) {
console.error( i18n.couldNotInitCanvas );
return;
}
torro.api.init()
.done( _.bind( function() {
( new torro.api.collections.ElementTypes() ).fetch({
data: {
context: 'edit'
},
context: this,
success: function( elementTypes ) {
this.elementTypes = torro.Builder.ElementTypes.fromApiCollection( elementTypes );
if ( 'auto-draft' !== $( '#original_post_status' ).val() ) {
( new torro.api.models.Form({
id: parseInt( $( '#post_ID' ).val(), 10 )
}) ).fetch({
data: {
context: 'edit',
_embed: true
},
context: this,
success: function( form ) {
$( document ).ready( _.bind( function() {
var i;
initialized.push( this.instanceCount );
this.setupInitialData( form.attributes );
this.setupViews();
for ( i in callbacks[ 'builder' + this.instanceCount ] ) {
callbacks[ 'builder' + this.instanceCount ][ i ]( this );
}
delete callbacks[ 'builder' + this.instanceCount ];
}, this ) );
},
error: function() {
$( document ).ready( _.bind( function() {
this.fail( i18n.couldNotLoadData );
}, this ) );
}
});
} else {
$( document ).ready( _.bind( function() {
var i;
initialized.push( this.instanceCount );
this.setupInitialData();
this.setupViews();
for ( i in callbacks[ 'builder' + this.instanceCount ] ) {
callbacks[ 'builder' + this.instanceCount ][ i ]( this );
}
delete callbacks[ 'builder' + this.instanceCount ];
}, this ) );
}
},
error: function() {
$( document ).ready( _.bind( function() {
this.fail( i18n.couldNotLoadData );
}, this ) );
}
});
}, this ) )
.fail( _.bind( function() {
$( document ).ready( _.bind( function() {
this.fail( i18n.couldNotLoadData );
}, this ) );
}, this ) );
},
/**
* Sets up initial data for the form builder.
*
* This method only works if the form builder has been initialized.
*
* @since 1.0.0
* @access public
*
* @param {object|undefined} form REST API form response including embedded data, or
* undefined if this is a new form.
*/
setupInitialData: function( form ) {
var container, element, elementChoice, elementSetting, elementParents, i;
if ( ! _.contains( initialized, this.instanceCount ) ) {
return;
}
if ( form ) {
this.form = new torro.Builder.FormModel( form );
if ( form._embedded.containers && form._embedded.containers[0] ) {
this.form.containers.add( form._embedded.containers[0] );
if ( form._embedded.elements && form._embedded.elements[0] ) {
elementParents = {};
for ( i = 0; i < form._embedded.elements[0].length; i++ ) {
element = form._embedded.elements[0][ i ];
container = this.form.containers.get( element.container_id );
if ( container ) {
container.elements.add( element );
elementParents[ element.id ] = element.container_id;
}
}
if ( form._embedded.element_choices && form._embedded.element_choices[0] ) {
for ( i = 0; i < form._embedded.element_choices[0].length; i++ ) {
elementChoice = form._embedded.element_choices[0][ i ];
if ( elementParents[ elementChoice.element_id ] ) {
container = this.form.containers.get( elementParents[ elementChoice.element_id ] );
if ( container ) {
element = container.elements.get( elementChoice.element_id );
if ( element ) {
element.element_choices.add( elementChoice );
}
}
}
}
}
if ( form._embedded.element_settings && form._embedded.element_settings[0] ) {
for ( i = 0; i < form._embedded.element_settings[0].length; i++ ) {
elementSetting = form._embedded.element_settings[0][ i ];
if ( elementParents[ elementSetting.element_id ] ) {
container = this.form.containers.get( elementParents[ elementSetting.element_id ] );
if ( container ) {
element = container.elements.get( elementSetting.element_id );
if ( element ) {
element.setElementSetting( elementSetting );
}
}
}
}
}
}
}
} else {
this.form = new torro.Builder.FormModel({});
this.form.containers.add({});
}
},
/**
* Sets up form builder views.
*
* This method only works if the form builder has been initialized.
*
* @since 1.0.0
* @access public
*/
setupViews: function() {
if ( ! _.contains( initialized, this.instanceCount ) ) {
return;
}
this.formView = new torro.Builder.FormView( this.$el, this.form );
this.formView.render();
},
/**
* Adds a callback that will be executed once the form builder has been initialized.
*
* If the form builder has already been initialized, the callback will be executed
* immediately.
*
* @since 1.0.0
* @access public
*
* @param {function} callback Callback to execute. Should accept the form builder instance
* as parameter.
*/
onLoad: function( callback ) {
if ( _.isUndefined( callbacks[ 'builder' + this.instanceCount ] ) ) {
callback( this );
return;
}
callbacks[ 'builder' + this.instanceCount ].push( callback );
},
/**
* Shows a failure message for the form builder in the UI.
*
* @since 1.0.0
* @access public
*
* @param {string} message Failure message to display.
*/
fail: function( message ) {
var compiled = torro.template( 'failure' );
this.$el.find( '.drag-drop-area' ).addClass( 'is-empty' ).html( compiled({ message: message }) );
},
/**
* Registers a function to be called whenever a certain form builder hook is triggered.
*
* @since 1.0.0
* @access public
*
* @param {string} hook Hook name.
* @param {function} callback Callback function to execute.
*/
on: function( hook, callback ) {
hook = 'torro.' + hook;
this.$el.on( hook, function() {
// Pass on all arguments except the event.
var args = Array.prototype.slice.call( arguments, 1 );
if ( args.length ) {
callback.apply( undefined, args );
} else {
callback.apply( undefined, undefined );
}
});
},
/**
* Triggers a hook for the form builder.
*
* @since 1.0.0
* @access public
*
* @param {string} hook Hook name.
* @param {array} data Optional. Arguments to pass to each callback.
*/
trigger: function( hook, data ) {
hook = 'torro.' + hook;
data = data || [];
this.$el.trigger( hook, data );
}
});
torro.Builder = Builder;
/**
* Returns the main form builder instance.
*
* It will be instantiated and initialized if it does not exist yet.
*
* @since 1.0.0
* @access public
*/
torro.Builder.getInstance = function() {
if ( ! builder ) {
builder = new Builder( '#torro-form-canvas' );
builder.init();
}
return builder;
};
// Scaffold the AddElement namespace for modal functionality.
torro.Builder.AddElement = {
State: {},
View: {}
};
torro.getFieldName = function( model, attribute ) {
var groupSlug;
if ( model instanceof torro.Builder.FormModel ) {
groupSlug = 'forms';
} else if ( model instanceof torro.Builder.ContainerModel ) {
groupSlug = 'containers';
} else if ( model instanceof torro.Builder.ElementModel ) {
groupSlug = 'elements';
} else if ( model instanceof torro.Builder.ElementChoiceModel ) {
groupSlug = 'element_choices';
} else if ( model instanceof torro.Builder.ElementSettingModel ) {
groupSlug = 'element_settings';
}
if ( ! groupSlug ) {
return;
}
return 'torro_' + groupSlug + '[' + model.get( 'id' ) + '][' + attribute + ']';
};
torro.getDeletedFieldName = function( model ) {
var groupSlug;
if ( model instanceof torro.Builder.FormModel ) {
groupSlug = 'forms';
} else if ( model instanceof torro.Builder.ContainerModel ) {
groupSlug = 'containers';
} else if ( model instanceof torro.Builder.ElementModel ) {
groupSlug = 'elements';
} else if ( model instanceof torro.Builder.ElementChoiceModel ) {
groupSlug = 'element_choices';
} else if ( model instanceof torro.Builder.ElementSettingModel ) {
groupSlug = 'element_settings';
}
if ( ! groupSlug ) {
return;
}
return 'torro_deleted_' + groupSlug + '[]';
};
torro.askConfirmation = function( message, successCallback ) {
var $dialog = $( '<div />' );
$dialog.html( message );
$( 'body' ).append( $dialog );
$dialog.dialog({
dialogClass: 'wp-dialog torro-dialog',
modal: true,
autoOpen: true,
closeOnEscape: true,
minHeight: 80,
buttons: [
{
text: i18n.yes,
click: function() {
successCallback();
$( this ).dialog( 'close' );
$( this ).remove();
}
},
{
text: i18n.no,
click: function() {
$( this ).dialog( 'close' );
$( this ).remove();
}
}
]
});
};
torro.Builder.i18n = i18n;
}( window.torro, window.jQuery, window._, window.torroBuilderI18n ) );
( function( torroBuilder, _ ) {
'use strict';
/**
* An element type.
*
* @class
*
* @param {object} attributes Element type attributes.
*/
function ElementType( attributes ) {
this.attributes = attributes;
}
_.extend( ElementType.prototype, {
/**
* Returns the element type slug.
*
* @since 1.0.0
* @access public
*
* @returns {string} Element type slug.
*/
getSlug: function() {
return this.attributes.slug;
},
/**
* Returns the element type title.
*
* @since 1.0.0
* @access public
*
* @returns {string} Element type title.
*/
getTitle: function() {
return this.attributes.title;
},
/**
* Returns the element type description.
*
* @since 1.0.0
* @access public
*
* @returns {string} Element type description.
*/
getDescription: function() {
return this.attributes.description;
},
/**
* Returns the element type icon CSS class.
*
* @since 1.0.0
* @access public
*
* @returns {string} Element type icon CSS class.
*/
getIconCssClass: function() {
return this.attributes.icon_css_class;
},
/**
* Returns the element type icon SVG ID.
*
* @since 1.0.0
* @access public
*
* @returns {string} Element type icon SVG ID.
*/
getIconSvgId: function() {
return this.attributes.icon_svg_id;
},
/**
* Returns the element type icon URL.
*
* @since 1.0.0
* @access public
*
* @returns {string} Element type icon URL.
*/
getIconUrl: function() {
return this.attributes.icon_url;
},
/**
* Checks whether the element type is a non input element type.
*
* @since 1.0.0
* @access public
*
* @returns {string} True if the element type is a non input element type, false otherwise.
*/
isNonInput: function() {
return this.attributes.non_input;
},
/**
* Checks whether the element type is evaluable.
*
* @since 1.0.0
* @access public
*
* @returns {string} True if the element type is evaluable, false otherwise.
*/
isEvaluable: function() {
return this.attributes.evaluable;
},
/**
* Checks whether the element type contains multiple fields.
*
* @since 1.0.0
* @access public
*
* @returns {string} True if the element type contains multiple fields, false otherwise.
*/
isMultiField: function() {
return this.attributes.multifield;
},
/**
* Returns the settings sections that belong to the element type.
*
* @since 1.0.0
* @access public
*
* @returns {object[]} Element type sections.
*/
getSections: function() {
return this.attributes.sections;
},
/**
* Returns the settings fields that belong to the element type.
*
* @since 1.0.0
* @access public
*
* @returns {object[]} Element type fields.
*/
getFields: function() {
return this.attributes.fields;
}
});
torroBuilder.ElementType = ElementType;
})( window.torro.Builder, window._ );
( function( torroBuilder, _ ) {
'use strict';
/**
* A list of available element types.
*
* @class
*
* @param {torro.Builder.ElementType[]} elementTypes Registered element type objects.
*/
function ElementTypes( elementTypes ) {
var i;
this.types = {};
for ( i in elementTypes ) {
this.types[ elementTypes[ i ].getSlug() ] = elementTypes[ i ];
}
}
_.extend( ElementTypes.prototype, {
/**
* Returns a specific element type.
*
* @since 1.0.0
* @access public
*
* @returns {torro.Builder.ElementType|undefined} Element type object, or undefined if not available.
*/
get: function( slug ) {
if ( _.isUndefined( this.types[ slug ] ) ) {
return undefined;
}
return this.types[ slug ];
},
/**
* Returns all element types.
*
* @since 1.0.0
* @access public
*
* @returns {torro.Builder.ElementType[]} All element type objects.
*/
getAll: function() {
return this.types;
}
});
/**
* Generates an element types list instance from a REST API response.
*
* @since 1.0.0
* @access public
*
* @returns {torro.Builder.ElementTypes} Element types object.
*/
ElementTypes.fromApiCollection = function( collection ) {
var elementTypes = [];
collection.each( function( model ) {
var attributes = _.extend({}, model.attributes );
if ( attributes._links ) {
delete attributes._links;
}
if ( attributes._embedded ) {
delete attributes._embedded;
}
elementTypes.push( new torroBuilder.ElementType( attributes ) );
});
return new ElementTypes( elementTypes );
};
torroBuilder.ElementTypes = ElementTypes;
})( window.torro.Builder, window._ );
( function( torro, _, Backbone, wp ) {
'use strict';
var Frame = wp.media.view.Frame;
var AddElementFrame;
AddElementFrame = Frame.extend({
className: 'media-frame',
template: torro.template( 'add-element-frame' ),
regions: [ 'menu', 'title', 'content', 'toolbar' ],
events: {
'click div.media-frame-title h1': 'toggleMenu'
},
initialize: function() {
Frame.prototype.initialize.apply( this, arguments );
_.defaults( this.options, {
title: '',
buttonLabel: '',
modal: true,
collection: [],
state: 'element-type-library'
});
this.$el.addClass( 'wp-core-ui' );
if ( this.options.modal ) {
this.modal = new wp.media.view.Modal({
controller: this,
title: this.options.title
});
this.modal.content( this );
}
this.on( 'attach', _.bind( this.views.ready, this.views ), this );
this.on( 'attach', this.showMenu, this );
this.createCollection();
this.createStates();
this.bindHandlers();
this.title.mode( 'default' );
},
render: function() {
if ( ! this.state() && this.options.state ) {
this.setState( this.options.state );
}
return Frame.prototype.render.apply( this, arguments );
},
createCollection: function() {
var collection = this.options.collection;
var elementTypes;
if ( ! ( collection instanceof Backbone.Collection ) ) {
elementTypes = [];
if ( collection instanceof torro.Builder.ElementTypes ) {
_.each( collection.getAll(), function( elementType ) {
elementTypes.push( elementType.attributes );
});
} else if ( collection ) {
elementTypes = collection;
}
this.options.collection = new Backbone.Collection( elementTypes );
}
},
createStates: function() {
this.states.add([
new torro.Builder.AddElement.State.ElementTypeLibrary({
title: this.options.title,
collection: this.options.collection,
priority: 20
})
]);
},
bindHandlers: function() {
this.on( 'menu:create:default', this.createMenu, this );
this.on( 'title:create:default', this.createTitle, this );
this.on( 'content:create:select-element-type', this.createContent, this );
this.on( 'toolbar:create:insert-element', this.createToolbar, this );
this.on( 'title:render', function( view ) {
view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
});
},
createMenu: function( menu ) {
menu.view = new wp.media.view.Menu({
controller: this
});
},
toggleMenu: function() {
this.$el.find( '.media-menu' ).toggleClass( 'visible' );
},
createTitle: function( title ) {
title.view = new wp.media.View({
controller: this,
tagName: 'h1'
});
},
createContent: function( content ) {
content.view = new torro.Builder.AddElement.View.ElementTypesBrowser({
controller: this,
collection: this.options.collection
});
},
createToolbar: function( toolbar ) {
var controller = this;
toolbar.view = new torro.Builder.AddElement.View.InsertElementToolbar({
controller: this,
items: {
insert: {
style: 'primary',
text: this.options.buttonLabel,
priority: 80,
requires: { selected: true },
click: function() {
var state = controller.state();
var selected = state.get( 'selected' );
controller.close();
state.trigger( 'insert', selected ).reset();
}
}
}
});
},
showMenu: function() {
// This fixes that the menu is not shown otherwise.
this.$el.removeClass( 'hide-menu' );
}
});
_.each([ 'open', 'close', 'attach', 'detach', 'escape' ], function( method ) {
AddElementFrame.prototype[ method ] = function() {
if ( this.modal ) {
this.modal[ method ].apply( this.modal, arguments );
}
return this;
};
});
torro.Builder.AddElement.View.Frame = AddElementFrame;
})( window.torro, window._, window.Backbone, window.wp );
( function( torro, Backbone, wp ) {
'use strict';
var State = wp.media.controller.State;
var ElementTypeLibrary;
ElementTypeLibrary = State.extend({
defaults: {
id: 'element-type-library',
title: torro.Builder.i18n.selectElementType,
menu: 'default',
content: 'select-element-type',
toolbar: 'insert-element'
},
initialize: function() {
if ( ! this.get( 'collection' ) ) {
this.set( 'collection', new Backbone.Collection( [] ) );
}
this.set( 'selected', null );
},
reset: function() {
this.set( 'selected', null );
}
});
torro.Builder.AddElement.State.ElementTypeLibrary = ElementTypeLibrary;
})( window.torro, window.Backbone, window.wp );
( function( torro, $, _, Backbone, wp ) {
'use strict';
var View = wp.media.View;
var ElementTypesBrowser;
ElementTypesBrowser = View.extend({
tagName: 'div',
className: 'element-types-browser',
template: torro.template( 'element-types-browser' ),
events: {
'click .torro-element-type': 'setSelected',
'keyup .torro-element-type': 'setSelected'
},
initialize: function() {
View.prototype.initialize.apply( this, arguments );
_.defaults( this.options, {
collection: []
});
if ( ! ( this.options.collection instanceof Backbone.Collection ) ) {
this.options.collection = new Backbone.Collection( this.options.collection );
}
this.controller.state().on( 'change:selected', this.listenToSelected, this );
},
prepare: function() {
var data = {
elementTypes: this.options.collection.toJSON(),
selectedElementType: this.controller.state().get( 'selected' )
};
return data;
},
setSelected: function( e ) {
if ( 'keyup' === e.type && 32 !== e.keyCode ) {
return;
}
if ( e.currentTarget && e.currentTarget.dataset.slug ) {
this.controller.state().set( 'selected', e.currentTarget.dataset.slug );
}
},
listenToSelected: function( state, selected ) {
this.$el.find( '.torro-element-type' ).each( function() {
var $this = $( this );
$this.toggleClass( 'is-selected', $this.data( 'slug' ) === selected );
});
}
});
torro.Builder.AddElement.View.ElementTypesBrowser = ElementTypesBrowser;
})( window.torro, window.jQuery, window._, window.Backbone, window.wp );
( function( torro, _, wp ) {
'use strict';
var Toolbar = wp.media.view.Toolbar;
var InsertElementToolbar;
InsertElementToolbar = Toolbar.extend({
initialize: function() {
wp.media.view.Toolbar.prototype.initialize.apply( this, arguments );
this.controller.state().on( 'change:selected', this.refresh, this );
},
refresh: function() {
var selected = this.controller.state().get( 'selected' );
_.each( this._views, function( button ) {
var disabled = false;
if ( ! button.model || ! button.options || ! button.options.requires ) {
return;
}
if ( button.options.requires && button.options.requires.selected && ( ! selected || ! selected.length ) ) {
disabled = true;
}
button.model.set( 'disabled', disabled );
});
}
});
torro.Builder.AddElement.View.InsertElementToolbar = InsertElementToolbar;
})( window.torro, window._, window.wp );
( function( torroBuilder, torro, _, Backbone ) {
'use strict';
/**
* Base for a form builder model.
*
* This model has no persistence with the server.
*
* @class
* @augments Backbone.Model
*/
torroBuilder.BaseModel = Backbone.Model.extend({
/**
* Related REST links.
*
* @since 1.0.0
* @access public
* @type {object}
*/
links: {},
/**
* Instantiates a new model.
*
* Overrides constructor in order to strip out unnecessary attributes.
*
* @since 1.0.0
* @access public
*
* @param {object} [attributes] Model attributes.
* @param {object} [options] Options for the model behavior.
*/
constructor: function( attributes, options ) {
var attrs = attributes || {};
var idAttribute = this.idAttribute || Backbone.Model.prototype.idAttribute || 'id';
if ( attrs._links ) {
this.links = attrs._links;
}
attrs = _.omit( attrs, [ '_links', '_embedded' ] );
if ( ! attrs[ idAttribute ] ) {
attrs[ idAttribute ] = torro.generateTempId();
}
Backbone.Model.apply( this, [ attrs, options ] );
},
/**
* Synchronizes the model with the server.
*
* Overrides synchronization in order to disable synchronization.
*
* @since 1.0.0
* @access public
*
* @returns {boolean} True on success, false on failure.
*/
sync: function( method, model, options ) {
if ( 'create' === method && model.has( model.idAttribute ) ) {
if ( ! options.attrs ) {
options.attrs = model.toJSON( options );
}
options.attrs = _.omit( options.attrs, model.idAttribute );
}
return false;
},
/**
* Checks whether this model is new.
*
* @since 1.0.0
* @access public
*
* @return {boolean} True if the model is new, false otherwise.
*/
isNew: function() {
return ! this.has( this.idAttribute ) || torro.isTempId( this.get( this.idAttribute ) );
}
});
})( window.torro.Builder, window.torro, window._, window.Backbone );
( function( torroBuilder, torro, _, Backbone ) {
'use strict';
/**
* Base for a form builder collection.
*
* This collection has no persistence with the server.
*
* @class
* @augments Backbone.Collection
*/
torroBuilder.BaseCollection = Backbone.Collection.extend({
/**
* Model class for the collection.
*
* @since 1.0.0
* @access public
* @property {function}
*/
model: torroBuilder.BaseModel,
/**
* Default properties for the collection.
*
* @since 1.0.0
* @access public
* @property {object}
*/
defaultProps: {},
/**
* Instantiates a new collection.
*
* Sets up collection properties.
*
* @since 1.0.0
* @access public
*
* @param {object[]} [models] Models for the collection.
* @param {object} [options] Options for the model behavior.
*/
constructor: function( models, options ) {
var props = _.defaults( options && options.props || {}, this.defaultProps );
this.props = new Backbone.Model( props );
if ( this.urlEndpoint ) {
this.url = torro.api.root + torro.api.versionString + this.urlEndpoint;
}
Backbone.Collection.apply( this, arguments );
},
/**
* Synchronizes the collection with the server.
*
* Overrides synchronization in order to disable synchronization.
*
* @since 1.0.0
* @access public
*
* @returns {boolean} True on success, false on failure.
*/
sync: function() {
return false;
}
});
})( window.torro.Builder, window.torro, window._, window.Backbone );
( function( torroBuilder, _ ) {
'use strict';
/**
* A single container.
*
* @class
* @augments torro.Builder.BaseModel
*/
torroBuilder.ContainerModel = torroBuilder.BaseModel.extend({
/**
* Returns container defaults.
*
* @since 1.0.0
* @access public
*
* @returns {object} Container defaults.
*/
defaults: function() {
return _.extend( _.clone({
id: 0,
form_id: 0,
label: '',
sort: 0
}), this.collection.getDefaultAttributes() );
},
/**
* Element collection.
*
* @since 1.0.0
* @access public
* @property {object}
*/
elements: undefined,
/**
* Instantiates a new model.
*
* Overrides constructor in order to strip out unnecessary attributes.
*
* @since 1.0.0
* @access public
*
* @param {object} [attributes] Model attributes.
* @param {object} [options] Options for the model behavior.
*/
constructor: function( attributes, options ) {
attributes = attributes || {};
torroBuilder.BaseModel.apply( this, [ attributes, options ] );
this.elements = new torroBuilder.ElementCollection([], {
props: {
container_id: this.get( 'id' )
},
comparator: 'sort'
});
}
});
})( window.torro.Builder, window._ );
( function( torroBuilder, _ ) {
'use strict';
/**
* A single element choice.
*
* @class
* @augments torro.Builder.BaseModel
*/
torroBuilder.ElementChoiceModel = torroBuilder.BaseModel.extend({
/**
* Returns element choice defaults.
*
* @since 1.0.0
* @access public
*
* @returns {object} Element choice defaults.
*/
defaults: function() {
return _.extend( _.clone({
id: 0,
element_id: 0,
field: '',
value: '',
sort: 0
}), this.collection.getDefaultAttributes() );
}
});
})( window.torro.Builder, window._ );
( function( torroBuilder, _ ) {
'use strict';
/**
* A single element.
*
* @class
* @augments torro.Builder.BaseModel
*/
torroBuilder.ElementModel = torroBuilder.BaseModel.extend({
/**
* Returns element defaults.
*
* @since 1.0.0
* @access public
*
* @returns {object} Element defaults.
*/
defaults: function() {
return _.extend( _.clone({
id: 0,
container_id: 0,
label: '',
sort: 0,
type: 'textfield'
}), this.collection.getDefaultAttributes() );
},
/**
* Element type object.
*
* @since 1.0.0
* @access public
* @property {object}
*/
element_type: null,
/**
* Element choice collection.
*
* @since 1.0.0
* @access public
* @property {object}
*/
element_choices: null,
/**
* Element setting collection.
*
* @since 1.0.0
* @access public
* @property {object}
*/
element_settings: undefined,
/**
* Identifier of the currently active section.
*
* @since 1.0.0
* @access public
* @property {string}
*/
active_section: undefined,
/**
* Instantiates a new model.
*
* Overrides constructor in order to strip out unnecessary attributes.
*
* @since 1.0.0
* @access public
*
* @param {object} [attributes] Model attributes.
* @param {object} [options] Options for the model behavior.
*/
constructor: function( attributes, options ) {
torroBuilder.BaseModel.apply( this, [ attributes, options ] );
this.element_choices = new torroBuilder.ElementChoiceCollection([], {
props: {
element_id: this.get( 'id' )
},
comparator: 'sort'
});
this.element_settings = new torroBuilder.ElementSettingCollection([], {
props: {
element_id: this.get( 'id' )
}
});
this.listenTypeChanged( this, this.get( 'type' ) );
this.on( 'change:type', this.listenTypeChanged, this );
},
setElementSetting: function( elementSetting ) {
var existingSetting, index;
if ( elementSetting.attributes ) {
elementSetting = elementSetting.attributes;
}
existingSetting = this.element_settings.findWhere({
name: elementSetting.name
});
if ( ! existingSetting ) {
return false;
}
index = this.element_settings.indexOf( existingSetting );
this.element_settings.remove( existingSetting );
this.element_settings.add( elementSetting, {
at: index
});
return true;
},
setActiveSection: function( section ) {
if ( section === this.active_section ) {
return;
}
this.active_section = section;
this.trigger( 'changeActiveSection', this, this.active_section );
},
getActiveSection: function() {
return this.active_section;
},
listenTypeChanged: function( element, type ) {
var sections, settingFields, settingNames, oldSettings = {};
element.element_type = torroBuilder.getInstance().elementTypes.get( type );
if ( ! element.element_type ) {
return;
}
this.trigger( 'changeElementType', element, element.element_type );
sections = element.element_type.getSections();
if ( sections.length ) {
element.setActiveSection( sections[0].slug );
}
settingFields = element.element_type.getFields().filter( function( field ) {
return ! field.is_label && ! field.is_choices;
});
settingNames = settingFields.map( function( settingField ) {
return settingField.slug;
});
element.element_settings.each( function( elementSetting ) {
if ( settingNames.includes( elementSetting.name ) ) {
oldSettings[ elementSetting.name ] = elementSetting.attributes;
}
});
element.element_settings.reset();
_.each( settingFields, function( settingField ) {
if ( oldSettings[ settingField.slug ] ) {
element.element_settings.add( oldSettings[ settingField.slug ] );
} else {
element.element_settings.create({
name: settingField.slug,
value: settingField['default'] || null
});
}
});
}
});
})( window.torro.Builder, window._ );
( function( torroBuilder, _ ) {
'use strict';
/**
* A single element setting.
*
* @class
* @augments torro.Builder.BaseModel
*/
torroBuilder.ElementSettingModel = torroBuilder.BaseModel.extend({
/**
* Returns element choice defaults.
*
* @since 1.0.0
* @access public
*
* @returns {object} Element choice defaults.
*/
defaults: function() {
return _.extend( _.clone({
id: 0,
element_id: 0,
name: '',
value: ''
}), this.collection.getDefaultAttributes() );
}
});
})( window.torro.Builder, window._ );
( function( torroBuilder ) {
'use strict';
/**
* A single form.
*
* @class
* @augments torro.Builder.BaseModel
*/
torroBuilder.FormModel = torroBuilder.BaseModel.extend({
/**
* Form defaults.
*
* @since 1.0.0
* @access public
* @property {object}
*/
defaults: {
id: 0,
title: '',
slug: '',
author: 0,
status: 'draft',
timestamp: 0,
timestamp_modified: 0
},
/**
* Container collection.
*
* @since 1.0.0
* @access public
* @property {object}
*/
containers: undefined,
/**
* Instantiates a new model.
*
* Overrides constructor in order to strip out unnecessary attributes.
*
* @since 1.0.0
* @access public
*
* @param {object} [attributes] Model attributes.
* @param {object} [options] Options for the model behavior.
*/
constructor: function( attributes, options ) {
var containerProps;
torroBuilder.BaseModel.apply( this, [ attributes, options ] );
containerProps = {
form_id: this.get( 'id' )
};
this.containers = new torroBuilder.ContainerCollection([], {
props: containerProps,
comparator: 'sort'
});
}
});
})( window.torro.Builder );
( function( torroBuilder, _ ) {
'use strict';
/**
* A collection of containers.
*
* @class
* @augments torro.Builder.BaseCollection
*/
torroBuilder.ContainerCollection = torroBuilder.BaseCollection.extend({
/**
* Model class for the container collection.
*
* @since 1.0.0
* @access public
* @property {function}
*/
model: torroBuilder.ContainerModel,
/**
* REST endpoint URL part for accessing containers.
*
* @since 1.0.0
* @access public
* @type {string}
*/
urlEndpoint: 'containers',
/**
* Default properties for the collection.
*
* @since 1.0.0
* @access public
* @property {object}
*/
defaultProps: {
selected: false,
form_id: 0
},
/**
* Returns container defaults.
*
* @since 1.0.0
* @access public
*
* @returns {object} Container defaults.
*/
getDefaultAttributes: function() {
var labelPlaceholder = torroBuilder.i18n.defaultContainerLabel;
var labelNumber = this.length + 1;
var sort = this.length;
var last;
if ( this.length ) {
last = this.at( this.length - 1 );
if ( last ) {
sort = last.get( 'sort' ) + 1;
if ( last.get( 'label' ) === labelPlaceholder.replace( '%s', sort ) ) {
labelNumber = sort + 1;
}
}
}
return {
form_id: this.props.get( 'form_id' ),
label: labelPlaceholder.replace( '%s', labelNumber ),
sort: sort
};
},
initialize: function() {
this.on( 'add', _.bind( this.maybeUpdateSelectedOnAdd, this ) );
this.on( 'remove', _.bind( this.maybeUpdateSelectedOnRemove, this ) );
},
maybeUpdateSelectedOnAdd: function( container ) {
if ( container ) {
this.props.set( 'selected', container.get( 'id' ) );
}
},
maybeUpdateSelectedOnRemove: function( container, containers, options ) {
var index = options.index ? options.index - 1 : options.index;
if ( container && this.props.get( 'selected' ) === container.get( 'id' ) ) {
if ( this.length ) {
this.props.set( 'selected', this.at( index ).get( 'id' ) );
} else {
this.props.set( 'selected', false );
}
}
}
});
})( window.torro.Builder, window._ );
( function( torroBuilder ) {
'use strict';
/**
* A collection of element choices.
*
* @class
* @augments torro.Builder.BaseCollection
*/
torroBuilder.ElementChoiceCollection = torroBuilder.BaseCollection.extend({
/**
* Model class for the element choice collection.
*
* @since 1.0.0
* @access public
* @property {function}
*/
model: torroBuilder.ElementChoiceModel,
/**
* REST endpoint URL part for accessing element choices.
*
* @since 1.0.0
* @access public
* @type {string}
*/
urlEndpoint: 'element_choices',
/**
* Default properties for the collection.
*
* @since 1.0.0
* @access public
* @property {object}
*/
defaultProps: {
element_id: 0
},
/**
* Returns element choice defaults.
*
* @since 1.0.0
* @access public
*
* @returns {object} Element choice defaults.
*/
getDefaultAttributes: function() {
return {
element_id: this.props.get( 'element_id' ),
sort: this.length
};
}
});
})( window.torro.Builder );
( function( torroBuilder ) {
'use strict';
/**
* A collection of elements.
*
* @class
* @augments torro.Builder.BaseCollection
*/
torroBuilder.ElementCollection = torroBuilder.BaseCollection.extend({
/**
* Model class for the element collection.
*
* @since 1.0.0
* @access public
* @property {function}
*/
model: torroBuilder.ElementModel,
/**
* REST endpoint URL part for accessing elements.
*
* @since 1.0.0
* @access public
* @type {string}
*/
urlEndpoint: 'elements',
/**
* Default properties for the collection.
*
* @since 1.0.0
* @access public
* @property {object}
*/
defaultProps: {
active: [],
container_id: 0
},
/**
* Returns element defaults.
*
* @since 1.0.0
* @access public
*
* @returns {object} Element defaults.
*/
getDefaultAttributes: function() {
return {
container_id: this.props.get( 'container_id' ),
sort: this.length
};
},
toggleActive: function( id ) {
var active = this.props.get( 'active' );
var index = active.indexOf( id );
if ( index > -1 ) {
active.splice( index, 1 );
} else {
active.push( id );
}
this.props.set( 'active', active );
this.props.trigger( 'toggleActive', this, active, {});
}
});
})( window.torro.Builder );
( function( torroBuilder ) {
'use strict';
/**
* A collection of element settings.
*
* @class
* @augments torro.Builder.BaseCollection
*/
torroBuilder.ElementSettingCollection = torroBuilder.BaseCollection.extend({
/**
* Model class for the element setting collection.
*
* @since 1.0.0
* @access public
* @property {function}
*/
model: torroBuilder.ElementSettingModel,
/**
* REST endpoint URL part for accessing element settings.
*
* @since 1.0.0
* @access public
* @type {string}
*/
urlEndpoint: 'element_settings',
/**
* Default properties for the collection.
*
* @since 1.0.0
* @access public
* @property {object}
*/
defaultProps: {
element_id: 0
},
/**
* Returns element setting defaults.
*
* @since 1.0.0
* @access public
*
* @returns {object} Element setting defaults.
*/
getDefaultAttributes: function() {
return {
element_id: this.props.get( 'element_id' ),
sort: this.length
};
}
});
})( window.torro.Builder );
( function( torroBuilder ) {
'use strict';
/**
* A collection of forms.
*
* @class
* @augments torro.Builder.BaseCollection
*/
torroBuilder.FormCollection = torroBuilder.BaseCollection.extend({
/**
* Model class for the form collection.
*
* @since 1.0.0
* @access public
* @property {function}
*/
model: torroBuilder.FormModel,
/**
* REST endpoint URL part for accessing forms.
*
* @since 1.0.0
* @access public
* @type {string}
*/
urlEndpoint: 'forms'
});
})( window.torro.Builder );
( function( torro, $, _ ) {
'use strict';
/**
* A container view.
*
* @class
*
* @param {torro.Builder.Container} container Container model.
* @param {object} options View options.
*/
function ContainerView( container, options ) {
var id = container.get( 'id' );
var selected = container.get( 'id' ) === container.collection.props.get( 'selected' );
this.container = container;
this.options = options || {};
this.tabTemplate = torro.template( 'container-tab' );
this.panelTemplate = torro.template( 'container-panel' );
this.footerPanelTemplate = torro.template( 'container-footer-panel' );
this.$tab = $( '<button />' );
this.$tab.attr( 'type', 'button' );
this.$tab.attr( 'id', 'container-tab-' + id );
this.$tab.addClass( 'torro-form-canvas-tab' );
this.$tab.attr( 'aria-controls', 'container-panel-' + id + ' container-footer-panel-' + id );
this.$tab.attr( 'aria-selected', selected ? 'true' : 'false' );
this.$tab.attr( 'role', 'tab' );
this.$panel = $( '<div />' );
this.$panel.attr( 'id', 'container-panel-' + id );
this.$panel.addClass( 'torro-form-canvas-panel' );
this.$panel.attr( 'aria-labelledby', 'container-tab-' + id );
this.$panel.attr( 'aria-hidden', selected ? 'false' : 'true' );
this.$panel.attr( 'role', 'tabpanel' );
this.$footerPanel = $( '<div />' );
this.$footerPanel.attr( 'id', 'container-footer-panel-' + id );
this.$footerPanel.addClass( 'torro-form-canvas-panel' );
this.$footerPanel.attr( 'aria-labelledby', 'container-tab-' + id );
this.$footerPanel.attr( 'aria-hidden', selected ? 'false' : 'true' );
this.$footerPanel.attr( 'role', 'tabpanel' );
this.addElementFrame = new torro.Builder.AddElement.View.Frame({
title: torro.Builder.i18n.selectElementType,
buttonLabel: torro.Builder.i18n.insertIntoContainer,
collection: torro.Builder.getInstance().elementTypes
});
}
_.extend( ContainerView.prototype, {
render: function() {
var i;
this.$tab.html( this.tabTemplate( this.container.attributes ) );
this.$panel.html( this.panelTemplate( this.container.attributes ) );
this.$footerPanel.html( this.footerPanelTemplate( this.container.attributes ) );
for ( i = 0; i < this.container.elements.length; i++ ) {
this.listenAddElement( this.container.elements.at( i ) );
}
this.attach();
},
destroy: function() {
this.detach();
this.$tab.remove();
this.$panel.remove();
this.$footerPanel.remove();
},
attach: function() {
this.container.on( 'remove', this.listenRemove, this );
this.container.elements.on( 'add', this.listenAddElement, this );
this.container.on( 'change:label', this.listenChangeLabel, this );
this.container.on( 'change:sort', this.listenChangeSort, this );
this.container.collection.props.on( 'change:selected', this.listenChangeSelected, this );
this.addElementFrame.on( 'insert', this.addElement, this );
this.$tab.on( 'click', _.bind( this.setSelected, this ) );
this.$tab.on( 'dblclick', _.bind( this.editLabel, this ) );
this.$panel.on( 'click', '.add-element-toggle', _.bind( this.openAddElementFrame, this ) );
this.$footerPanel.on( 'click', '.delete-container-button', _.bind( this.deleteContainer, this ) );
this.$panel.find( '.drag-drop-area' ).sortable({
handle: '.torro-element-header',
items: '.torro-element',
placeholder: 'torro-element-placeholder',
tolerance: 'pointer',
start: function( e, ui ) {
ui.placeholder.height( ui.item.height() );
},
update: _.bind( this.updateElementsSorted, this )
});
},
detach: function() {
this.$panel.find( 'drag-drop-area' ).sortable( 'destroy' );
this.$footerPanel.off( 'click', '.delete-container-button', _.bind( this.deleteContainer, this ) );
this.$panel.off( 'click', '.add-element-toggle', _.bind( this.openAddElementFrame, this ) );
this.$tab.off( 'dblclick', _.bind( this.editLabel, this ) );
this.$tab.off( 'click', _.bind( this.setSelected, this ) );
this.addElementFrame.off( 'insert', this.addElement, this );
this.container.collection.props.off( 'change:selected', this.listenChangeSelected, this );
this.container.off( 'change:sort', this.listenChangeSort, this );
this.container.off( 'change:label', this.listenChangeLabel, this );
this.container.elements.off( 'add', this.listenAddContainer, this );
this.container.off( 'remove', this.listenRemove, this );
},
listenRemove: function() {
var id = this.container.get( 'id' );
if ( ! torro.isTempId( id ) ) {
$( '#torro-deleted-wrap' ).append( '<input type="hidden" name="' + torro.getDeletedFieldName( this.container ) + '" value="' + id + '" />' );
}
this.destroy();
torro.Builder.getInstance().trigger( 'removeContainer', [ this.container, this ] );
},
listenAddElement: function( element ) {
var view = new torro.Builder.ElementView( element, this.options );
var $dragDropArea = this.$panel.find( '.drag-drop-area' );
$dragDropArea.append( view.$wrap );
view.render();
if ( $dragDropArea.sortable( 'instance' ) ) {
$dragDropArea.sortable( 'refresh' );
}
torro.Builder.getInstance().trigger( 'addElement', [ element, view ] );
},
listenChangeLabel: function( container, label ) {
var name = torro.escapeSelector( torro.getFieldName( this.container, 'label' ) );
this.$panel.find( 'input[name="' + name + '"]' ).val( label );
},
listenChangeSort: function( container, sort ) {
var name = torro.escapeSelector( torro.getFieldName( this.container, 'sort' ) );
this.$panel.find( 'input[name="' + name + '"]' ).val( sort );
},
listenChangeSelected: function( props, selected ) {
if ( selected === this.container.get( 'id' ) ) {
this.$tab.attr( 'aria-selected', 'true' );
this.$panel.attr( 'aria-hidden', 'false' );
this.$footerPanel.attr( 'aria-hidden', 'false' );
} else {
this.$tab.attr( 'aria-selected', 'false' );
this.$panel.attr( 'aria-hidden', 'true' );
this.$footerPanel.attr( 'aria-hidden', 'true' );
}
},
setSelected: function() {
this.container.collection.props.set( 'selected', this.container.get( 'id' ) );
},
editLabel: function() {
var container = this.container;
var $original = this.$tab.find( 'span' );
var $replacement;
if ( ! $original.length ) {
return;
}
$replacement = $( '<input />' );
$replacement.attr( 'type', 'text' );
$replacement.val( $original.text() );
$replacement.on( 'keydown blur', function( event ) {
var proceed = false;
var value;
if ( 'keydown' === event.type ) {
if ( 13 === event.which ) {
proceed = true;
event.preventDefault();
} else if ( [ 32, 37, 38, 39, 40 ].includes( event.which ) ) {
event.stopPropagation();
}
} else if ( 'blur' === event.type ) {
proceed = true;
} else {
event.stopPropagation();
}
if ( ! proceed ) {
return;
}
value = $replacement.val();
container.set( 'label', value );
$original.text( value );
$replacement.replaceWith( $original );
$original.focus();
});
$original.replaceWith( $replacement );
$replacement.focus();
},
openAddElementFrame: function() {
this.addElementFrame.open();
},
addElement: function( selectedElementType ) {
var element;
if ( ! selectedElementType ) {
return;
}
element = this.container.elements.create({
type: selectedElementType
});
this.container.elements.toggleActive( element.get( 'id' ) );
},
deleteContainer: function() {
torro.askConfirmation( torro.Builder.i18n.confirmDeleteContainer, _.bind( function() {
this.container.collection.remove( this.container );
}, this ) );
},
updateElementsSorted: function( e, ui ) {
var container = this.container;
ui.item.parent().find( '.torro-element' ).each( function( index ) {
var $element = $( this );
var element = container.elements.get( $element.attr( 'id' ).replace( 'torro-element-', '' ) );
element.set( 'sort', index );
});
container.elements.sort();
}
});
torro.Builder.ContainerView = ContainerView;
})( window.torro, window.jQuery, window._ );
( function( torro, $, _, fieldsAPI, dummyFieldManager ) {
'use strict';
function deepClone( input ) {
var output = _.clone( input );
_.each( output, function( value, key ) {
var temp, i;
if ( _.isArray( value ) ) {
temp = [];
for ( i = 0; i < value.length; i++ ) {
if ( _.isObject( value[ i ] ) ) {
temp.push( deepClone( value[ i ] ) );
} else {
temp.push( value[ i ] );
}
}
output[ key ] = temp;
} else if ( _.isObject( value ) ) {
output[ key ] = deepClone( value );
}
});
return output;
}
function getObjectReplaceableFields( obj ) {
var fields = {};
_.each( obj, function( value, key ) {
if ( _.isObject( value ) && ! _.isArray( value ) ) {
value = getObjectReplaceableFields( value );
if ( ! _.isEmpty( value ) ) {
fields[ key ] = value;
}
} else if ( _.isString( value ) ) {
if ( value.match( /%([A-Za-z0-9]+)%/g ) ) {
fields[ key ] = value;
}
}
});
return fields;
}
function replaceObjectFields( obj, replacements, fields ) {
if ( _.isUndefined( fields ) ) {
fields = getObjectReplaceableFields( obj );
}
function _doReplacements( match, name ) {
if ( ! _.isUndefined( replacements[ name ] ) ) {
return replacements[ name ];
}
return match;
}
_.each( fields, function( value, key ) {
if ( _.isObject( value ) ) {
if ( ! _.isObject( obj[ key ] ) ) {
obj[ key ] = {};
}
replaceObjectFields( obj[ key ], replacements, value );
} else {
obj[ key ] = value.replace( /%([A-Za-z0-9]+)%/g, _doReplacements );
}
});
}
function generateItem( itemInitial, index ) {
var newItem = _.deepClone( itemInitial );
replaceObjectFields( newItem, {
index: index,
indexPlus1: index + 1
});
return newItem;
}
function getElementFieldId( element, field ) {
return 'torro_element_' + element.get( 'id' ) + '_' + field;
}
function parseFields( fields, element ) {
var parsedFields = [];
var hasLabel = false;
_.each( fields, function( field ) {
var parsedField;
var elementChoices;
var elementSetting;
var tempId;
if ( _.isUndefined( field.type ) || _.isUndefined( dummyFieldManager.fields[ 'dummy_' + field.type ] ) ) {
return;
}
parsedField = deepClone( dummyFieldManager.fields[ 'dummy_' + field.type ] );
parsedField.section = field.section;
parsedField.label = field.label;
parsedField.description = field.description;
parsedField['default'] = field['default'] || null;
if ( field.is_choices ) {
elementChoices = element.element_choices.where({
field: _.isString( field.is_choices ) ? field.is_choices : '_main'
});
tempId = torro.generateTempId();
parsedField.repeatable = true;
parsedField.repeatableLimit = 0;
parsedField.id = getElementFieldId( element, field.slug );
parsedField.labelAttrs.id = parsedField.id + '-label';
parsedField.itemInitial.currentValue = parsedField['default'];
parsedField.itemInitial['default'] = parsedField['default'];
parsedField.itemInitial.element_id = element.get( 'id' );
parsedField.itemInitial.field = _.isString( field.is_choices ) ? field.is_choices : '_main';
parsedField.itemInitial.id = parsedField.id + '-%indexPlus1%';
parsedField.itemInitial.label = torro.Builder.i18n.elementChoiceLabel.replace( '%s', '%indexPlus1%' );
parsedField.itemInitial.name = 'torro_element_choices[' + tempId + '_%index%][value]';
parsedField.itemInitial.section = parsedField.section;
parsedField.itemInitial.sort = '%index%';
parsedField.itemInitial.inputAttrs.id = parsedField.itemInitial.id;
parsedField.itemInitial.inputAttrs.name = parsedField.itemInitial.name;
if ( _.isArray( field.input_classes ) ) {
parsedField.itemInitial.inputAttrs['class'] += ' ' + field.input_classes.join( ' ' );
}
parsedField.itemInitial.labelAttrs.id = parsedField.itemInitial.id + '-label';
parsedField.itemInitial.labelAttrs['for'] = parsedField.itemInitial.id;
parsedField.itemInitial.wrapAttrs.id = parsedField.itemInitial.id + '-wrap';
parsedField.wrapAttrs = deepClone( parsedField.itemInitial.wrapAttrs );
parsedField.wrapAttrs.id = parsedField.id + '-wrap';
_.each( elementChoices, function( elementChoice, index ) {
var newItem = generateItem( parsedField.itemInitial, index );
newItem.name = torro.getFieldName( elementChoice, 'value' );
newItem.inputAttrs.name = newItem.name;
newItem.currentValue = elementChoice.get( 'value' );
parsedField.items.push( newItem );
});
} else {
if ( field.repeatable ) {
// Repeatable fields are currently not supported.
return;
}
if ( field.is_label ) {
// Only one label field is allowed.
if ( hasLabel ) {
return;
}
hasLabel = true;
parsedField.id = getElementFieldId( element, 'label' );
parsedField.name = torro.getFieldName( element, 'label' );
parsedField.currentValue = element.get( 'label' );
} else {
elementSetting = element.element_settings.findWhere({
name: field.slug
});
if ( ! elementSetting ) {
return;
}
parsedField.id = getElementFieldId( element, elementSetting.get( 'id' ) );
parsedField.name = torro.getFieldName( elementSetting, 'value' );
parsedField.currentValue = elementSetting.get( 'value' );
parsedField._element_setting = _.clone( elementSetting.attributes );
parsedField.inputAttrs['data-element-setting-id'] = elementSetting.get( 'id' );
}
// Manage special fields per type.
switch ( parsedField.slug ) {
case 'autocomplete':
if ( ! _.isUndefined( field.autocomplete ) ) {
parsedField.autocomplete = deepClone( field.autocomplete );
}
break;
case 'color':
parsedField.inputAttrs.maxlength = 7;
break;
case 'datetime':
if ( ! _.isUndefined( field.store ) ) {
parsedField.store = field.store;
parsedField.inputAttrs['data-store'] = field.store;
}
if ( ! _.isUndefined( field.min ) ) {
parsedField.inputAttrs.min = field.min;
}
if ( ! _.isUndefined( field.max ) ) {
parsedField.inputAttrs.max = field.max;
}
break;
case 'map':
case 'media':
if ( ! _.isUndefined( field.store ) ) {
parsedField.store = field.store;
parsedField.inputAttrs['data-store'] = field.store;
}
break;
case 'number':
case 'range':
if ( ! _.isUndefined( field.min ) ) {
parsedField.inputAttrs.min = field.min;
}
if ( ! _.isUndefined( field.max ) ) {
parsedField.inputAttrs.max = field.max;
}
if ( ! _.isUndefined( field.step ) ) {
parsedField.inputAttrs.step = field.step;
}
if ( ! _.isUndefined( field.unit ) ) {
parsedField.unit = field.unit;
}
break;
case 'radio':
case 'multibox':
case 'select':
case 'multiselect':
if ( ! _.isUndefined( field.choices ) ) {
parsedField.choices = deepClone( field.choices );
} else {
parsedField.choices = {};
}
break;
case 'text':
if ( ! _.isUndefined( field.maxlength ) ) {
parsedField.inputAttrs.maxlength = field.maxlength;
}
if ( ! _.isUndefined( field.pattern ) ) {
parsedField.inputAttrs.pattern = field.pattern;
}
break;
case 'textarea':
if ( ! _.isUndefined( field.rows ) ) {
parsedField.inputAttrs.rows = field.rows;
}
break;
case 'wysiwyg':
if ( ! _.isUndefined( field.wpautop ) ) {
parsedField.wpautop = field.wpautop;
parsedField.inputAttrs['data-wpautop'] = field.wpautop;
}
if ( ! _.isUndefined( field.media_buttons ) ) {
parsedField.media_buttons = field.media_buttons;
parsedField.inputAttrs['data-media-buttons'] = field.media_buttons;
}
if ( ! _.isUndefined( field.button_mode ) ) {
parsedField.button_mode = field.button_mode;
parsedField.inputAttrs['data-button-mode'] = field.button_mode;
}
break;
}
if ( null === parsedField.currentValue ) {
switch ( parsedField.slug ) {
case 'media':
if ( 'url' === parsedField.store ) {
parsedField.currentValue = '';
} else {
parsedField.currentValue = 0;
}
break;
case 'number':
case 'range':
if ( ! _.isUndefined( parsedField.inputAttrs.min ) ) {
parsedField.currentValue = parsedField.inputAttrs.min;
} else {
parsedField.currentValue = 0;
}
break;
case 'multibox':
case 'multiselect':
parsedField.currentValue = [];
break;
default:
parsedField.currentValue = '';
}
}
parsedField.inputAttrs.id = parsedField.id;
parsedField.inputAttrs.name = parsedField.name;
if ( _.isArray( field.input_classes ) ) {
parsedField.inputAttrs['class'] += ' ' + field.input_classes.join( ' ' );
}
if ( parsedField.description.length ) {
parsedField.inputAttrs['aria-describedby'] = parsedField.id + '-description';
}
parsedField.labelAttrs.id = parsedField.id + '-label';
parsedField.labelAttrs['for'] = parsedField.id;
parsedField.wrapAttrs.id = parsedField.id + '-wrap';
}
parsedFields.push( parsedField );
});
return parsedFields;
}
function sanitizeElementLabelForElementHeader( label ) {
var tmp;
// Strip HTML tags.
if ( label.length && -1 !== label.search( '<' ) ) {
tmp = document.createElement( 'div' );
tmp.innerHTML = label;
label = tmp.textContent.trim();
}
// Limit maximum length.
if ( label.length > 50 ) {
label = label.substring( 0, 47 ) + '...';
}
return label;
}
/**
* An element view.
*
* @class
*
* @param {torro.Builder.Element} element Element model.
* @param {object} options View options.
*/
function ElementView( element, options ) {
var id = element.get( 'id' );
this.element = element;
this.options = options || {};
this.wrapTemplate = torro.template( 'element' );
this.sectionTabTemplate = torro.template( 'element-section-tab' );
this.sectionPanelTemplate = torro.template( 'element-section-panel' );
this.fieldTemplate = torro.template( 'element-field' );
this.$wrap = $( '<div />' );
this.$wrap.attr( 'id', 'torro-element-' + id );
this.$wrap.addClass( 'torro-element' );
}
_.extend( ElementView.prototype, {
render: function() {
var templateData = this.element.attributes;
templateData.elementHeader = templateData.label ? sanitizeElementLabelForElementHeader( templateData.label ) : '';
templateData.type = this.element.element_type.attributes;
templateData.active = this.element.collection.props.get( 'active' ).includes( this.element.get( 'id' ) );
templateData.active_section = this.element.getActiveSection();
this.$wrap.html( this.wrapTemplate( templateData ) );
this.initializeSections();
this.initializeFields();
this.attach();
},
destroy: function() {
this.detach();
this.deinitializeFields();
this.deinitializeSections();
this.$wrap.remove();
},
initializeSections: function() {
var $sectionTabsWrap = this.$wrap.find( '.torro-element-content-tabs' );
var $sectionPanelsWrap = this.$wrap.find( '.torro-element-content-panels' );
var sections = this.element.element_type.getSections();
var element = this.element;
_.each( sections, _.bind( function( section ) {
var templateData = _.clone( section );
templateData.elementId = element.get( 'id' );
templateData.active = element.getActiveSection() === templateData.slug;
$sectionTabsWrap.append( this.sectionTabTemplate( templateData ) );
$sectionPanelsWrap.append( this.sectionPanelTemplate( templateData ) );
}, this ) );
},
deinitializeSections: function() {
var $sectionTabsWrap = this.$wrap.find( '.torro-element-content-tabs' );
var $sectionPanelsWrap = this.$wrap.find( '.torro-element-content-panels' );
$sectionTabsWrap.empty();
$sectionPanelsWrap.empty();
},
initializeFields: function() {
this.fieldManager = new fieldsAPI.FieldManager( parseFields( this.element.element_type.getFields(), this.element ), {
instanceId: 'torro_element_' + this.element.get( 'id' )
});
this.fieldViews = [];
_.each( this.fieldManager.models, _.bind( function( field ) {
var viewClassName = field.get( 'backboneView' );
var FieldView = fieldsAPI.FieldView;
var $sectionFieldsWrap = this.$wrap.find( '#element-panel-' + this.element.get( 'id' ) + '-' + field.get( 'section' ) + ' > .torro-element-fields' );
var view;
if ( ! $sectionFieldsWrap.length ) {
return;
}
$sectionFieldsWrap.append( this.fieldTemplate( field.attributes ) );
if ( viewClassName && 'FieldView' !== viewClassName && fieldsAPI.FieldView[ viewClassName ] ) {
FieldView = fieldsAPI.FieldView[ viewClassName ];
}
view = new FieldView({
model: field
});
view.renderLabel();
view.renderContent();
this.fieldViews.push( view );
}, this ) );
},
deinitializeFields: function() {
_.each( this.fieldViews, function( fieldView ) {
fieldView.remove();
});
this.$wrap.find( '.torro-element-fields' ).each( function() {
$( this ).empty();
});
this.fieldViews = [];
this.fieldManager = null;
},
attach: function() {
var updateElementChoicesSorted = _.bind( this.updateElementChoicesSorted, this );
this.element.on( 'remove', this.listenRemove, this );
this.element.on( 'change:label', this.listenChangeLabel, this );
this.element.on( 'change:type', this.listenChangeType, this );
this.element.on( 'change:sort', this.listenChangeSort, this );
this.element.on( 'changeElementType', this.listenChangeElementType, this );
this.element.on( 'changeActiveSection', this.listenChangeActiveSection, this );
this.element.collection.props.on( 'toggleActive', this.listenChangeActive, this );
_.each( this.fieldViews, _.bind( function( fieldView ) {
if ( fieldView.model.get( '_element_setting' ) ) {
fieldView.model.on( 'changeValue', _.bind( this.listenChangeElementSettingFieldValue, this ) );
} else if ( 'torrochoices' === fieldView.model.get( 'slug' ) ) {
fieldView.model.on( 'addItem', _.bind( this.listenAddElementChoiceField, this ) );
fieldView.model.on( 'removeItem', _.bind( this.listenRemoveElementChoiceField, this ) );
fieldView.model.on( 'changeItemValue', _.bind( this.listenChangeElementChoiceFieldValue, this ) );
}
}, this ) );
this.$wrap.on( 'click', '.torro-element-header', _.bind( this.toggleActive, this ) );
this.$wrap.on( 'click', '.torro-element-expand-button', _.bind( this.toggleActive, this ) );
this.$wrap.on( 'click', '.delete-element-button', _.bind( this.deleteElement, this ) );
this.$wrap.on( 'click', '.torro-element-content-tab', _.bind( this.changeActiveSection, this ) );
this.$wrap.on( 'keyup change', 'input[type="text"]#' + getElementFieldId( this.element, 'label' ), _.bind( this.updateLabel, this ) );
this.$wrap.find( '.plugin-lib-repeatable-torrochoices-wrap' ).each( function() {
$( this ).sortable({
containment: 'parent',
handle: '.torro-element-choice-move',
items: '.plugin-lib-repeatable-item',
placeholder: 'torro-element-choice-placeholder',
tolerance: 'pointer',
update: updateElementChoicesSorted
});
});
},
detach: function() {
this.$wrap.find( '.plugin-lib-repeatable-torrochoices-wrap' ).each( function() {
$( this ).sortable( 'destroy' );
});
this.$wrap.off( 'keyup change', 'input[type="text"]#' + getElementFieldId( this.element, 'label' ), _.bind( this.updateLabel, this ) );
this.$wrap.off( 'click', '.torro-element-content-tab', _.bind( this.changeActiveSection, this ) );
this.$wrap.off( 'click', '.delete-element-button', _.bind( this.deleteElement, this ) );
this.$wrap.off( 'click', '.torro-element-expand-button', _.bind( this.toggleActive, this ) );
this.$wrap.off( 'click', '.torro-element-header', _.bind( this.toggleActive, this ) );
_.each( this.fieldViews, _.bind( function( fieldView ) {
if ( fieldView.model.get( '_element_setting' ) ) {
fieldView.model.off( 'changeValue', _.bind( this.listenChangeElementSettingFieldValue, this ) );
} else if ( 'torrochoices' === fieldView.model.get( 'slug' ) ) {
fieldView.model.off( 'addItem', _.bind( this.listenAddElementChoiceField, this ) );
fieldView.model.off( 'removeItem', _.bind( this.listenRemoveElementChoiceField, this ) );
fieldView.model.off( 'changeItemValue', _.bind( this.listenChangeElementChoiceFieldValue, this ) );
}
}, this ) );
this.element.collection.props.off( 'toggleActive', this.listenChangeActive, this );
this.element.off( 'changeActiveSection', this.listenChangeActiveSection, this );
this.element.off( 'changeElementType', this.listenChangeElementType, this );
this.element.off( 'change:sort', this.listenChangeSort, this );
this.element.off( 'change:type', this.listenChangeType, this );
this.element.off( 'change:label', this.listenChangeLabel, this );
this.element.off( 'remove', this.listenRemove, this );
},
listenRemove: function() {
var id = this.element.get( 'id' );
if ( ! torro.isTempId( id ) ) {
$( '#torro-deleted-wrap' ).append( '<input type="hidden" name="' + torro.getDeletedFieldName( this.element ) + '" value="' + id + '" />' );
}
this.destroy();
torro.Builder.getInstance().trigger( 'removeElement', [ this.element, this ] );
},
listenChangeLabel: function( element, label ) {
var name = torro.escapeSelector( torro.getFieldName( this.element, 'label' ) );
var elementHeader = label;
this.$wrap.find( 'input[name="' + name + '"]' ).val( label );
if ( elementHeader ) {
elementHeader = sanitizeElementLabelForElementHeader( elementHeader );
if ( elementHeader.length ) {
this.$wrap.find( '.torro-element-header-title' ).text( elementHeader );
return;
}
}
this.$wrap.find( '.torro-element-header-title' ).text( this.element.element_type.getTitle() );
},
listenChangeType: function( element, type ) {
var name = torro.escapeSelector( torro.getFieldName( this.element, 'type' ) );
this.$wrap.find( 'input[name="' + name + '"]' ).val( type );
},
listenChangeSort: function( element, sort ) {
var name = torro.escapeSelector( torro.getFieldName( this.element, 'sort' ) );
this.$wrap.find( 'input[name="' + name + '"]' ).val( sort );
},
listenChangeElementType: function() {
this.deinitializeFields();
this.deinitializeSections();
this.initializeSections();
this.initializeFields();
},
listenChangeActiveSection: function( element, activeSection ) {
var $button = this.$wrap.find( '.torro-element-content-tab[data-slug="' + activeSection + '"]' );
this.$wrap.find( '.torro-element-content-tab' ).attr( 'aria-selected', 'false' );
this.$wrap.find( '.torro-element-content-panel' ).attr( 'aria-hidden', 'true' );
if ( $button.length ) {
$button.attr( 'aria-selected', 'true' );
this.$wrap.find( '#' + $button.attr( 'aria-controls' ) ).attr( 'aria-hidden', 'false' );
}
},
listenChangeActive: function( props, active ) {
if ( active.includes( this.element.get( 'id' ) ) ) {
this.$wrap.find( '.torro-element-expand-button' ).attr( 'aria-expanded', 'true' ).find( '.screen-reader-text' ).text( torro.Builder.i18n.hideContent );
this.$wrap.find( '.torro-element-content' ).addClass( 'is-expanded' );
} else {
this.$wrap.find( '.torro-element-expand-button' ).attr( 'aria-expanded', 'false' ).find( '.screen-reader-text' ).text( torro.Builder.i18n.showContent );
this.$wrap.find( '.torro-element-content' ).removeClass( 'is-expanded' );
}
this.$wrap.find( '.plugin-lib-repeatable-torrochoices-wrap' ).each( function() {
var $repeatableWrap = $( this );
if ( $repeatableWrap.sortable( 'instance' ) ) {
$repeatableWrap.sortable( 'refresh' );
}
});
},
listenChangeElementSettingFieldValue: function( model, value ) {
var elementSettingId = model.get( '_element_setting' ).id;
var elementSetting = this.element.element_settings.get( elementSettingId );
if ( ! elementSetting ) {
return;
}
elementSetting.set( 'value', value );
},
listenAddElementChoiceField: function( model, addedChoiceItem ) {
var elementChoiceId = addedChoiceItem.name.replace( 'torro_element_choices[', '' ).replace( '][value]', '' );
var $elementChoicesRepeatableWrap = $( '#torro_element_' + this.element.get( 'id' ) + '_choices_' + addedChoiceItem.field + '-repeatable-wrap' );
this.element.element_choices.create({
id: elementChoiceId,
field: addedChoiceItem.field
});
if ( $elementChoicesRepeatableWrap.sortable( 'instance' ) ) {
$elementChoicesRepeatableWrap.sortable( 'refresh' );
}
},
listenRemoveElementChoiceField: function( model, removedChoiceItem ) {
var elementChoiceId = removedChoiceItem.name.replace( 'torro_element_choices[', '' ).replace( '][value]', '' );
var elementChoice = this.element.element_choices.get( elementChoiceId );
this.element.element_choices.remove( elementChoiceId );
if ( ! torro.isTempId( elementChoiceId ) ) {
$( '#torro-deleted-wrap' ).append( '<input type="hidden" name="' + torro.getDeletedFieldName( elementChoice ) + '" value="' + elementChoiceId + '" />' );
}
},
listenChangeElementChoiceFieldValue: function( model, changedChoiceItem, value ) {
var elementChoiceId = changedChoiceItem.name.replace( 'torro_element_choices[', '' ).replace( '][value]', '' );
var elementChoice = this.element.element_choices.get( elementChoiceId );
elementChoice.set( 'value', value );
},
toggleActive: function( e ) {
e.stopPropagation();
this.element.collection.toggleActive( this.element.get( 'id' ) );
},
deleteElement: function() {
torro.askConfirmation( torro.Builder.i18n.confirmDeleteElement, _.bind( function() {
this.element.collection.remove( this.element );
}, this ) );
},
changeActiveSection: function( e ) {
var $button = $( e.target || e.delegateTarget );
this.element.setActiveSection( $button.data( 'slug' ) );
},
updateLabel: function( e ) {
var $input = $( e.target || e.delegateTarget );
this.element.set( 'label', $input.val() );
},
updateElementChoicesSorted: function( e, ui ) {
var element = this.element;
ui.item.parent().find( '.plugin-lib-repeatable-item' ).each( function( index ) {
var elementChoiceId = $( this ).find( 'input[type="text"]' ).attr( 'name' ).replace( 'torro_element_choices[', '' ).replace( '][value]', '' );
var elementChoice = element.element_choices.get( elementChoiceId );
elementChoice.set( 'sort', index );
// This is far from optimal, but we don't have element choice listeners at this point.
$( this ).find( 'input[name="' + torro.escapeSelector( 'torro_element_choices[' + elementChoiceId + '][sort]' ) + '"]' ).val( index );
});
element.element_choices.sort();
}
});
torro.Builder.ElementView = ElementView;
})( window.torro, window.jQuery, window._, window.pluginLibFieldsAPI, window.pluginLibFieldsAPIData.field_managers.torro_dummy_1 );
( function( torro, $, _ ) {
'use strict';
/**
* A form view.
*
* @class
*
* @param {jQuery} $canvas Form canvas div.
* @param {torro.Builder.Form} form Form model.
* @param {object} options View options.
*/
function FormView( $canvas, form, options ) {
this.form = form;
this.options = options || {};
this.canvasTemplate = torro.template( 'form-canvas' );
this.$canvas = $canvas;
}
_.extend( FormView.prototype, {
render: function() {
var $deletedWrap, i;
$deletedWrap = $( '<div />' );
$deletedWrap.attr( 'id', 'torro-deleted-wrap' );
$deletedWrap.css( 'display', 'none' );
this.$canvas.html( this.canvasTemplate( this.form.attributes ) );
this.$canvas.after( $deletedWrap );
this.$addButton = this.$canvas.find( '.add-button' );
this.$addPanel = this.$canvas.find( '.add-panel' );
this.checkHasContainers();
for ( i = 0; i < this.form.containers.length; i++ ) {
this.listenAddContainer( this.form.containers.at( i ) );
}
this.attach();
},
destroy: function() {
this.detach();
this.$canvas.empty();
},
attach: function() {
this.form.containers.on( 'add', this.listenAddContainer, this );
this.form.containers.on( 'add remove reset', this.checkHasContainers, this );
this.$addButton.on( 'click', _.bind( this.addContainer, this ) );
},
detach: function() {
this.$addButton.off( 'click', _.bind( this.addContainer, this ) );
this.form.containers.off( 'add remove reset', _.bind( this.checkHasContainers, this ) );
this.form.containers.off( 'add', this.listenAddContainer, this );
},
listenAddContainer: function( container ) {
var view = new torro.Builder.ContainerView( container, this.options );
view.$tab.insertBefore( this.$addButton );
view.$panel.insertBefore( this.$addPanel );
this.$canvas.find( '.torro-form-canvas-footer' ).append( view.$footerPanel );
view.render();
torro.Builder.getInstance().trigger( 'addContainer', [ container, view ] );
},
checkHasContainers: function() {
if ( this.form.containers.length ) {
this.$addButton.removeClass( 'is-active' );
this.$addPanel.attr( 'aria-hidden', 'true' );
} else {
this.$addButton.addClass( 'is-active' );
this.$addPanel.attr( 'aria-hidden', 'false' );
}
},
addContainer: function() {
this.form.containers.create();
}
});
torro.Builder.FormView = FormView;
})( window.torro, window.jQuery, window._ );
( function( $ ) {
'use strict';
$( '.torro-metabox-tab' ).on( 'click', function( e ) {
var $this = $( this );
var $all = $this.parent().children( '.torro-metabox-tab' );
e.preventDefault();
if ( 'true' === $this.attr( 'aria-selected' ) ) {
return;
}
$all.each( function() {
$( this ).attr( 'aria-selected', 'false' );
$( $( this ).attr( 'href' ) ).attr( 'aria-hidden', 'true' );
});
$this.attr( 'aria-selected', 'true' );
$( $this.attr( 'href' ) ).attr( 'aria-hidden', 'false' ).find( '.plugin-lib-map-control' ).each( function() {
$( this ).wpMapPicker( 'refresh' );
});
});
})( window.jQuery );
( function( torroBuilder ) {
'use strict';
torroBuilder.getInstance();
})( window.torro.Builder );