window.butterbean = window.butterbean || {};
( function() {
// Bail if we don't have the JSON, which is passed in via `wp_localize_script()`.
if ( _.isUndefined( butterbean_data ) ) {
return;
}
/**
* Our global object. The `butterbean` object is just a wrapper to house everything
* in a single namespace.
*
* @since 1.0.0
* @access public
* @var object
*/
var api = butterbean = {
/**
* Houses the manager, section, and control views based on the `type`.
*
* @since 1.0.0
* @access public
* @var object
*/
views : { managers : {}, sections : {}, controls : {} },
/**
* Houses the manager, section, and control templates based on the `type`.
*
* @since 1.0.0
* @access public
* @var object
*/
templates : { managers : {}, sections : {}, controls : {} }
};
/**
* Creates a new manager view.
*
* @since 1.0.0
* @access public
* @param string $type
* @param object $args
* @return void
*/
api.views.register_manager = function( type, args ) {
if ( 'default' !== type )
this.managers[ type ] = this.managers.default.extend( args );
};
/**
* Returns a manager view.
*
* @since 1.0.0
* @access public
* @param string $type
* @return object
*/
api.views.get_manager = function( type ) {
if ( this.manager_exists( type ) )
return this.managers[ type ];
return this.managers.default;
};
/**
* Removes a manager view.
*
* @since 1.0.0
* @access public
* @param string $type
* @return void
*/
api.views.unregister_manager = function( type ) {
if ( 'default' !== type && this.manager_exists( type ) )
delete this.managers[ type ];
};
/**
* Checks if a manager view exists.
*
* @since 1.0.0
* @access public
* @param string $type
* @return bool
*/
api.views.manager_exists = function( type ) {
return this.managers.hasOwnProperty( type );
};
/**
* Creates a new section view.
*
* @since 1.0.0
* @access public
* @param string $type
* @param object $args
* @return void
*/
api.views.register_section = function( type, args ) {
if ( 'default' !== type )
this.sections[ type ] = this.sections.default.extend( args );
};
/**
* Returns a section view.
*
* @since 1.0.0
* @access public
* @param string $type
* @return object
*/
api.views.get_section = function( type ) {
if ( this.section_exists( type ) )
return this.sections[ type ];
return this.sections.default;
};
/**
* Removes a section view.
*
* @since 1.0.0
* @access public
* @param string $type
* @return void
*/
api.views.unregister_section = function( type ) {
if ( 'default' !== type && this.section_exists( type ) )
delete this.sections[ type ];
};
/**
* Checks if a section view exists.
*
* @since 1.0.0
* @access public
* @param string $type
* @return bool
*/
api.views.section_exists = function( type ) {
return this.sections.hasOwnProperty( type );
};
/**
* Creates a new control view.
*
* @since 1.0.0
* @access public
* @param string $type
* @param object $args
* @return void
*/
api.views.register_control = function( type, args ) {
if ( 'default' !== type )
this.controls[ type ] = this.controls.default.extend( args );
};
/**
* Returns a control view.
*
* @since 1.0.0
* @access public
* @param string $type
* @return object
*/
api.views.get_control = function( type ) {
if ( this.control_exists( type ) )
return this.controls[ type ];
return this.controls.default;
};
/**
* Removes a control view.
*
* @since 1.0.0
* @access public
* @param string $type
* @return void
*/
api.views.unregister_control = function( type ) {
if ( 'default' !== type && this.control_exists( type ) )
delete this.controls[ type ];
};
/**
* Checks if a control view exists.
*
* @since 1.0.0
* @access public
* @param string $type
* @return bool
*/
api.views.control_exists = function( type ) {
return this.controls.hasOwnProperty( type );
};
/**
* Creates a new manager template.
*
* @since 1.0.0
* @access public
* @param string $type
* @param object $args
* @return void
*/
api.templates.register_manager = function( type ) {
this.managers[ type ] = wp.template( 'butterbean-manager-' + type );
};
/**
* Returns a manager template.
*
* @since 1.0.0
* @access public
* @param string $type
* @return function
*/
api.templates.get_manager = function( type ) {
return this.manager_exists( type ) ? this.managers[ type ] : false;
};
/**
* Removes a manager template.
*
* @since 1.0.0
* @access public
* @param string $type
* @return void
*/
api.templates.unregister_manager = function( type ) {
if ( this.manager_exists( type ) )
delete this.managers[ type ];
};
/**
* Checks if a manager template exists.
*
* @since 1.0.0
* @access public
* @param string $type
* @return bool
*/
api.templates.manager_exists = function( type ) {
return this.managers.hasOwnProperty( type );
};
/**
* Creates a new section template.
*
* @since 1.0.0
* @access public
* @param string $type
* @param object $args
* @return void
*/
api.templates.register_section = function( type ) {
this.sections[ type ] = wp.template( 'butterbean-section-' + type );
};
/**
* Returns a section template.
*
* @since 1.0.0
* @access public
* @param string $type
* @return function
*/
api.templates.get_section = function( type ) {
return this.section_exists( type ) ? this.sections[ type ] : false;
};
/**
* Removes a section template.
*
* @since 1.0.0
* @access public
* @param string $type
* @return void
*/
api.templates.unregister_section = function( type ) {
if ( this.section_exists( type ) )
delete this.sections[ type ];
};
/**
* Checks if a section template exists.
*
* @since 1.0.0
* @access public
* @param string $type
* @return bool
*/
api.templates.section_exists = function( type ) {
return this.sections.hasOwnProperty( type );
};
/**
* Creates a new control template.
*
* @since 1.0.0
* @access public
* @param string $type
* @param object $args
* @return void
*/
api.templates.register_control = function( type ) {
this.controls[ type ] = wp.template( 'butterbean-control-' + type );
};
/**
* Returns a control template.
*
* @since 1.0.0
* @access public
* @param string $type
* @return function
*/
api.templates.get_control = function( type ) {
return this.control_exists( type ) ? this.controls[ type ] : false;
};
/**
* Removes a control template.
*
* @since 1.0.0
* @access public
* @param string $type
* @return void
*/
api.templates.unregister_control = function( type ) {
if ( this.control_exists( type ) )
delete this.controls[ type ];
};
/**
* Checks if a control template exists.
*
* @since 1.0.0
* @access public
* @param string $type
* @return bool
*/
api.templates.control_exists = function( type ) {
return this.controls.hasOwnProperty( type );
};
/**
* Renders our managers, sections, and controls.
*
* @since 1.0.0
* @access private
* @return void
*/
api.render = function() {
// Loop through each of the managers and render their api.views.
_.each( butterbean_data.managers, function( data ) {
// Create a new manager model with the JSON data for the manager.
var manager = new Manager( data );
// Get the manager view callback.
var callback = api.views.get_manager( data.type );
// Create a new manager view.
var view = new callback( { model : manager } );
// Get the meta box element.
var metabox = document.getElementById( 'butterbean-ui-' + manager.get( 'name' ) );
// Add the `.butterbean-ui` class to the meta box.
metabox.className += ' butterbean-ui';
// Render the manager view.
metabox.querySelector( '.inside' ).appendChild( view.render().el );
// Render the manager subviews.
view.subview_render();
// Call the view's ready method.
view.ready();
} );
};
/* === Templates === */
// Nav template.
var nav_template = wp.template( 'butterbean-nav' );
/* === Models === */
// Manager model (each manager is housed within a meta box).
var Manager = Backbone.Model.extend( {
defaults : {
name : '',
type : '',
sections : {},
controls : {}
}
} );
// Section model (each section belongs to a manager).
var Section = Backbone.Model.extend( {
defaults : {
name : '',
type : '',
label : '',
description : '',
icon : '',
manager : '',
active : '',
selected : false
}
} );
// Control model (each control belongs to a manager and section).
var Control = Backbone.Model.extend( {
defaults : {
name : '',
type : '',
label : '',
description : '',
icon : '',
value : '',
choices : {},
attr : '',
active : '',
manager : '',
section : '',
setting : ''
}
} );
/* === Collections === */
/**
* Stores our collection of section models.
*
* @since 1.0.0
* @access private
* @var object
*/
var Sections = Backbone.Collection.extend( {
model : Section
} );
/* === Views === */
/**
* The default manager view. Other views can extend this using the
* `butterbean.views.register_manager()` function.
*
* @since 1.0.0
* @access public
* @var object
*/
api.views.managers[ 'default' ] = Backbone.View.extend( {
// Wrapper element for the manager view.
tagName : 'div',
// Adds some custom attributes to the wrapper.
attributes : function() {
return {
'id' : 'butterbean-manager-' + this.model.get( 'name' ),
'class' : 'butterbean-manager butterbean-manager-' + this.model.get( 'type' )
};
},
// Initializes the view.
initialize : function() {
var type = this.model.get( 'type' );
// If there's not yet a template for this manager type, create it.
if ( ! api.templates.manager_exists( type ) )
api.templates.register_manager( type );
// Get the manager template.
this.template = api.templates.get_manager( type );
},
// Renders the manager.
render : function() {
this.el.innerHTML = this.template( this.model.toJSON() );
return this;
},
// Renders the manager's sections and controls.
// Important! This may change drastically in the future, possibly even
// taken out of the manager view altogether. It's for this reason that
// it's not recommended to create custom views for managers right now.
subview_render : function() {
// Create a new section collection.
var sections = new Sections();
// Loop through each section and add it to the collection.
_.each( this.model.get( 'sections' ), function( data ) {
sections.add( new Section( data ) );
} );
// Loop through each section in the collection and render its view.
sections.forEach( function( section, i ) {
// Create a new nav item view for the section.
var nav_view = new Nav_View( { model : section } );
// Render the nav item view.
document.querySelector( '#butterbean-ui-' + section.get( 'manager' ) + ' .butterbean-nav' ).appendChild( nav_view.render().el );
// Get the section view callback.
var callback = api.views.get_section( section.attributes.type );
// Create a new section view.
var view = new callback( { model : section } );
// Render the section view.
document.querySelector( '#butterbean-ui-' + section.get( 'manager' ) + ' .butterbean-content' ).appendChild( view.render().el );
// Call the section view's ready method.
view.ready();
// If the first model, set it to selected.
section.set( 'selected', 0 === i );
}, this );
// Loop through each control for the manager and render its view.
_.each( this.model.get( 'controls' ), function( data ) {
// Create a new control model.
var control = new Control( data );
// Get the control view callback.
var callback = api.views.get_control( data.type );
// Create a new control view.
var view = new callback( { model : control } );
// Render the view.
document.getElementById( 'butterbean-' + control.get( 'manager' ) + '-section-' + control.get( 'section' ) ).appendChild( view.render().el );
// Call the view's ready method.
view.ready();
} );
return this;
},
// Function that is executed *after* the view has been rendered.
// This is meant to be overwritten in sub-views.
ready : function() {}
} );
/**
* The default section view. Other views can extend this using the
* `butterbean.views.register_section()` function.
*
* @since 1.0.0
* @access public
* @var object
*/
api.views.sections[ 'default' ] = Backbone.View.extend( {
// Wrapper element for the section.
tagName : 'div',
// Adds custom attributes for the section wrapper.
attributes : function() {
return {
'id' : 'butterbean-' + this.model.get( 'manager' ) + '-section-' + this.model.get( 'name' ),
'class' : 'butterbean-section butterbean-section-' + this.model.get( 'type' ),
'aria-hidden' : ! this.model.get( 'selected' )
};
},
// Initializes the view.
initialize : function() {
// Add an event for when the model changes.
this.model.on( 'change', this.onchange, this );
// Get the section type.
var type = this.model.get( 'type' );
// If there's no template for this section type, create it.
if ( ! api.templates.section_exists( type ) )
api.templates.register_section( type );
// Gets the section template.
this.template = api.templates.get_section( type );
},
// Renders the section.
render : function() {
// Only render template if model is active.
if ( this.model.get( 'active' ) )
this.el.innerHTML = this.template( this.model.toJSON() );
return this;
},
// Executed when the model changes.
onchange : function() {
// Set the view's `aria-hidden` attribute based on whether the model is selected.
this.el.setAttribute( 'aria-hidden', ! this.model.get( 'selected' ) );
},
// Function that is executed *after* the view has been rendered.
// This is meant to be overwritten in sub-views.
ready : function() {}
} );
/**
* The nav item view for each section.
*
* @since 1.0.0
* @access public
* @var object
*/
var Nav_View = Backbone.View.extend( {
// Sets the template used.
template : nav_template,
// Wrapper element for the nav item.
tagName : 'li',
// Sets some custom attributes for the nav item wrapper.
attributes : function() {
return {
'aria-selected' : this.model.get( 'selected' )
};
},
// Initializes the nav item view.
initialize : function() {
this.model.on( 'change', this.render, this );
this.model.on( 'change', this.onchange, this );
},
// Renders the nav item.
render : function() {
// Only render template if model is active.
if ( this.model.get( 'active' ) )
this.el.innerHTML = this.template( this.model.toJSON() );
return this;
},
// Custom events.
events : {
'click a' : 'onselect'
},
// Executed when the section model changes.
onchange : function() {
// Set the `aria-selected` attibute based on the model selected state.
this.el.setAttribute( 'aria-selected', this.model.get( 'selected' ) );
},
// Executed when the link for the nav item is clicked.
onselect : function( event ) {
event.preventDefault();
// Loop through each of the models in the collection and set them to inactive.
_.each( this.model.collection.models, function( m ) {
m.set( 'selected', false );
}, this );
// Set this view's model to selected.
this.model.set( 'selected', true );
}
} );
/**
* The default control view. Other views can extend this using the
* `butterbean.views.register_control()` function.
*
* @since 1.0.0
* @access public
* @var object
*/
api.views.controls[ 'default' ] = Backbone.View.extend( {
// Wrapper element for the control.
tagName : 'div',
// Custom attributes for the control wrapper.
attributes : function() {
return {
'id' : 'butterbean-control-' + this.model.get( 'name' ),
'class' : 'butterbean-control butterbean-control-' + this.model.get( 'type' )
};
},
// Initiazlies the control view.
initialize : function() {
var type = this.model.get( 'type' );
// Only add a new control template if we have a different control type.
if ( ! api.templates.control_exists( type ) )
api.templates.register_control( type );
// Get the control template.
this.template = api.templates.get_control( type );
// Bind changes so that the view is re-rendered when the model changes.
_.bindAll( this, 'render' );
this.model.bind( 'change', this.render );
},
// Renders the control template.
render : function() {
// Only render template if model is active.
if ( this.model.get( 'active' ) )
this.el.innerHTML = this.template( this.model.toJSON() );
return this;
},
// Function that is executed *after* the view has been rendered.
// This is meant to be overwritten in sub-views.
ready : function() {}
} );
/**
* Adds the color control view.
*
* @since 1.0.0
*/
api.views.register_control( 'color', {
// Calls the core WP color picker for the control's input.
ready : function() {
var options = this.model.attributes.options;
jQuery( this.$el ).find( '.butterbean-color-picker' ).wpColorPicker( options );
}
} );
/**
* Adds the color palette view.
*
* @since 1.0.0
*/
api.views.register_control( 'palette', {
// Adds custom events.
events : {
'change input' : 'onselect'
},
// Executed when one of the color palette's value has changed.
// These are radio inputs.
onselect : function() {
// Get the value of the input.
var value = document.querySelector( '#' + this.el.id + ' input:checked' ).getAttribute( 'value' );
// Get all choices.
var choices = this.model.get( 'choices' );
// Loop through choices and change the selected value.
_.each( choices, function( choice, key ) {
choice.selected = key === value;
} );
// Because `choices` is an array, it's not recognized as a change. So, we
// have to manually trigger a change here so that the view gets re-rendered.
this.model.set( 'choices', choices ).trigger( 'change', this.model );
}
} );
/**
* Adds the image control view.
*
* @since 1.0.0
*/
api.views.register_control( 'image', {
// Adds custom events.
events : {
'click .butterbean-add-media' : 'showmodal',
'click .butterbean-change-media' : 'showmodal',
'click .butterbean-remove-media' : 'removemedia'
},
// Executed when the show modal button is clicked.
showmodal : function() {
// If we already have a media modal, open it.
if ( ! _.isUndefined( this.media_modal ) ) {
this.media_modal.open();
return;
}
// Create a new media modal.
this.media_modal = wp.media( {
frame : 'select',
multiple : false,
editing : true,
title : this.model.get( 'l10n' ).choose,
library : { type : 'image' },
button : { text: this.model.get( 'l10n' ).set }
} );
// Runs when an image is selected in the media modal.
this.media_modal.on( 'select', function() {
// Gets the JSON data for the first selection.
var media = this.media_modal.state().get( 'selection' ).first().toJSON();
// Size of image to display.
var size = this.model.attributes.size;
// Updates the model for the view.
this.model.set( {
src : media.sizes[ size ] ? media.sizes[ size ]['url'] : media.url,
alt : media.alt,
value : media.id
} );
}, this );
// Opens the media modal.
this.media_modal.open();
},
// Executed when the remove media button is clicked.
removemedia : function() {
// Updates the model for the view.
this.model.set( { src : '', alt : '', value : '' } );
}
} );
}() );
lite-vimeo {
font-size: 10px;
background-color: #000;
position: relative;
display: block;
contain: content;
background-position: center center;
background-size: cover;
background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgOCA0LjUiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBvbHlnb24gcG9pbnRzPSIwLDAgMSwwIDEsNC41IDAsNC41IiBmaWxsPSIjODVkY2U1Ii8+PHBvbHlnb24gcG9pbnRzPSIxLDAgMiwwIDIsNC41IDEsNC41IiBmaWxsPSIjZWJmMmY0Ii8+PHBvbHlnb24gcG9pbnRzPSIyLDAgMywwIDMsNC41IDIsNC41IiBmaWxsPSIjZmU1MjIxIi8+PHBvbHlnb24gcG9pbnRzPSIzLDAgNCwwIDQsNC41IDMsNC41IiBmaWxsPSIjZjdmYzhiIi8+PHBvbHlnb24gcG9pbnRzPSI0LDAgNSwwIDUsNC41IDQsNC41IiBmaWxsPSIjMTJhOWQxIi8+PHBvbHlnb24gcG9pbnRzPSI1LDAgNiwwIDYsNC41IDUsNC41IiBmaWxsPSIjNDEzNzMxIi8+PHBvbHlnb24gcG9pbnRzPSI2LDAgNywwIDcsNC41IDYsNC41IiBmaWxsPSIjYmQyZDA3Ii8+PHBvbHlnb24gcG9pbnRzPSI3LDAgOCwwIDgsNC41IDcsNC41IiBmaWxsPSIjY2ZlZDI1Ii8+PC9zdmc+");
cursor: pointer;
}
lite-vimeo.awb-lite-vimeo-no-background {
background-image: none;
}
/* gradient, vimeo doesn't have this */
/*lite-vimeo::before {
content: '';
display: block;
position: absolute;
top: 0;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==);
background-position: top;
background-repeat: repeat-x;
height: 60px;
padding-bottom: 50px;
width: 100%;
transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
}*/
/* responsive iframe with a 16:9 aspect ratio
thanks https://css-tricks.com/responsive-iframes/
*/
lite-vimeo::after {
content: "";
display: block;
padding-bottom: calc(100% / (16 / 9));
}
lite-vimeo > iframe {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
/* play button */
lite-vimeo > .ltv-playbtn {
width: 6.5em;
height: 4em;
background: rgba(23,35,34,.75);
z-index: 1;
opacity: 0.8;
border-radius: .5em; /* TODO: Consider replacing this with YT's actual svg. Eh. */
transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
outline: 0;
border: 0;
cursor: pointer;
}
lite-vimeo:hover > .ltv-playbtn {
background-color: rgb(0, 173, 239);
opacity: 1;
}
/* play button triangle */
lite-vimeo > .ltv-playbtn::before {
content: '';
border-style: solid;
border-width: 10px 0 10px 20px;
border-color: transparent transparent transparent #fff;
}
lite-vimeo > .ltv-playbtn,
lite-vimeo > .ltv-playbtn::before {
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
}
/* Post-click styles */
lite-vimeo.ltv-activated {
cursor: unset;
}
lite-vimeo.ltv-activated::before,
lite-vimeo.ltv-activated > .ltv-playbtn {
opacity: 0;
pointer-events: none;
}
We're sorry, but we can't find the page you were looking for. It's probably some thing we've done wrong but now we know about it and we'll try to fix it. In the meantime, try one of these options: