( function( window, undefined ) {
"use strict";
/**
* Handles managing all events for whatever you plug it into. Priorities for hooks are based on lowest to highest in
* that, lowest priority hooks are fired first.
*/
var EventManager = function() {
/**
* Maintain a reference to the object scope so our public methods never get confusing.
*/
var MethodsAvailable = {
removeFilter : removeFilter,
applyFilters : applyFilters,
addFilter : addFilter,
removeAction : removeAction,
doAction : doAction,
addAction : addAction,
storage : getStorage
};
/**
* Contains the hooks that get registered with this EventManager. The array for storage utilizes a "flat"
* object literal such that looking up the hook utilizes the native object literal hash.
*/
var STORAGE = {
actions : {},
filters : {}
};
function getStorage() {
return STORAGE;
};
/**
* Adds an action to the event manager.
*
* @param action Must contain namespace.identifier
* @param callback Must be a valid callback function before this action is added
* @param [priority=10] Used to control when the function is executed in relation to other callbacks bound to the same hook
* @param [context] Supply a value to be used for this
*/
function addAction( action, callback, priority, context ) {
if( typeof action === 'string' && typeof callback === 'function' ) {
priority = parseInt( ( priority || 10 ), 10 );
_addHook( 'actions', action, callback, priority, context );
}
return MethodsAvailable;
}
/**
* Performs an action if it exists. You can pass as many arguments as you want to this function; the only rule is
* that the first argument must always be the action.
*/
function doAction( /* action, arg1, arg2, ... */ ) {
var args = Array.prototype.slice.call( arguments );
var action = args.shift();
if( typeof action === 'string' ) {
_runHook( 'actions', action, args );
}
return MethodsAvailable;
}
/**
* Removes the specified action if it contains a namespace.identifier & exists.
*
* @param action The action to remove
* @param [callback] Callback function to remove
*/
function removeAction( action, callback ) {
if( typeof action === 'string' ) {
_removeHook( 'actions', action, callback );
}
return MethodsAvailable;
}
/**
* Adds a filter to the event manager.
*
* @param filter Must contain namespace.identifier
* @param callback Must be a valid callback function before this action is added
* @param [priority=10] Used to control when the function is executed in relation to other callbacks bound to the same hook
* @param [context] Supply a value to be used for this
*/
function addFilter( filter, callback, priority, context ) {
if( typeof filter === 'string' && typeof callback === 'function' ) {
priority = parseInt( ( priority || 10 ), 10 );
_addHook( 'filters', filter, callback, priority, context );
}
return MethodsAvailable;
}
/**
* Performs a filter if it exists. You should only ever pass 1 argument to be filtered. The only rule is that
* the first argument must always be the filter.
*/
function applyFilters( /* filter, filtered arg, arg2, ... */ ) {
var args = Array.prototype.slice.call( arguments );
var filter = args.shift();
if( typeof filter === 'string' ) {
return _runHook( 'filters', filter, args );
}
return MethodsAvailable;
}
/**
* Removes the specified filter if it contains a namespace.identifier & exists.
*
* @param filter The action to remove
* @param [callback] Callback function to remove
*/
function removeFilter( filter, callback ) {
if( typeof filter === 'string') {
_removeHook( 'filters', filter, callback );
}
return MethodsAvailable;
}
/**
* Removes the specified hook by resetting the value of it.
*
* @param type Type of hook, either 'actions' or 'filters'
* @param hook The hook (namespace.identifier) to remove
* @private
*/
function _removeHook( type, hook, callback, context ) {
if ( !STORAGE[ type ][ hook ] ) {
return;
}
if ( !callback ) {
STORAGE[ type ][ hook ] = [];
} else {
var handlers = STORAGE[ type ][ hook ];
var i;
if ( !context ) {
for ( i = handlers.length; i--; ) {
if ( handlers[i].callback === callback ) {
handlers.splice( i, 1 );
}
}
}
else {
for ( i = handlers.length; i--; ) {
var handler = handlers[i];
if ( handler.callback === callback && handler.context === context) {
handlers.splice( i, 1 );
}
}
}
}
}
/**
* Adds the hook to the appropriate storage container
*
* @param type 'actions' or 'filters'
* @param hook The hook (namespace.identifier) to add to our event manager
* @param callback The function that will be called when the hook is executed.
* @param priority The priority of this hook. Must be an integer.
* @param [context] A value to be used for this
* @private
*/
function _addHook( type, hook, callback, priority, context ) {
var hookObject = {
callback : callback,
priority : priority,
context : context
};
// Utilize 'prop itself' : http://jsperf.com/hasownproperty-vs-in-vs-undefined/19
var hooks = STORAGE[ type ][ hook ];
if( hooks ) {
hooks.push( hookObject );
hooks = _hookInsertSort( hooks );
}
else {
hooks = [ hookObject ];
}
STORAGE[ type ][ hook ] = hooks;
}
/**
* Use an insert sort for keeping our hooks organized based on priority. This function is ridiculously faster
* than bubble sort, etc: http://jsperf.com/javascript-sort
*
* @param hooks The custom array containing all of the appropriate hooks to perform an insert sort on.
* @private
*/
function _hookInsertSort( hooks ) {
var tmpHook, j, prevHook;
for( var i = 1, len = hooks.length; i < len; i++ ) {
tmpHook = hooks[ i ];
j = i;
while( ( prevHook = hooks[ j - 1 ] ) && prevHook.priority > tmpHook.priority ) {
hooks[ j ] = hooks[ j - 1 ];
--j;
}
hooks[ j ] = tmpHook;
}
return hooks;
}
/**
* Runs the specified hook. If it is an action, the value is not modified but if it is a filter, it is.
*
* @param type 'actions' or 'filters'
* @param hook The hook ( namespace.identifier ) to be ran.
* @param args Arguments to pass to the action/filter. If it's a filter, args is actually a single parameter.
* @private
*/
function _runHook( type, hook, args ) {
var handlers = STORAGE[ type ][ hook ];
if ( !handlers ) {
return (type === 'filters') ? args[0] : false;
}
var i = 0, len = handlers.length;
if ( type === 'filters' ) {
for ( ; i < len; i++ ) {
args[ 0 ] = handlers[ i ].callback.apply( handlers[ i ].context, args );
}
} else {
for ( ; i < len; i++ ) {
handlers[ i ].callback.apply( handlers[ i ].context, args );
}
}
return ( type === 'filters' ) ? args[ 0 ] : true;
}
// return all of the publicly available methods
return MethodsAvailable;
};
window.wp = window.wp || {};
window.wp.hooks = new EventManager();
} )( window );
var acf;
(function($){
/*
* exists
*
* This function will return true if a jQuery selection exists
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param n/a
* @return (boolean)
*/
$.fn.exists = function() {
return $(this).length>0;
};
/*
* outerHTML
*
* This function will return a string containing the HTML of the selected element
*
* @type function
* @date 19/11/2013
* @since 5.0.0
*
* @param $.fn
* @return (string)
*/
$.fn.outerHTML = function() {
return $(this).get(0).outerHTML;
};
acf = {
// vars
l10n: {},
o: {},
/*
* update
*
* This function will update a value found in acf.o
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param k (string) the key
* @param v (mixed) the value
* @return n/a
*/
update: function( k, v ){
this.o[ k ] = v;
},
/*
* get
*
* This function will return a value found in acf.o
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param k (string) the key
* @return v (mixed) the value
*/
get: function( k ){
if( typeof this.o[ k ] !== 'undefined' ) {
return this.o[ k ];
}
return null;
},
/*
* _e
*
* This functiln will return a string found in acf.l10n
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param k1 (string) the first key to look for
* @param k2 (string) the second key to look for
* @return string (string)
*/
_e: function( k1, k2 ){
// defaults
k2 = k2 || false;
// get context
var string = this.l10n[ k1 ] || '';
// get string
if( k2 ) {
string = string[ k2 ] || '';
}
// return
return string;
},
/*
* add_action
*
* This function uses wp.hooks to mimics WP add_action
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param
* @return
*/
add_action: function() {
// vars
var a = arguments[0].split(' '),
l = a.length;
// loop
for( var i = 0; i < l; i++) {
/*
// allow for special actions
if( a[i].indexOf('initialize') !== -1 ) {
a.push( a[i].replace('initialize', 'ready') );
a.push( a[i].replace('initialize', 'append') );
l = a.length;
continue;
}
*/
// prefix action
arguments[0] = 'acf/' + a[i];
// add
wp.hooks.addAction.apply(this, arguments);
}
// return
return this;
},
/*
* remove_action
*
* This function uses wp.hooks to mimics WP remove_action
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param
* @return
*/
remove_action: function() {
// prefix action
arguments[0] = 'acf/' + arguments[0];
wp.hooks.removeAction.apply(this, arguments);
return this;
},
/*
* do_action
*
* This function uses wp.hooks to mimics WP do_action
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param
* @return
*/
do_action: function() { //console.log('acf.do_action(%o)', arguments);
// prefix action
arguments[0] = 'acf/' + arguments[0];
wp.hooks.doAction.apply(this, arguments);
return this;
},
/*
* add_filter
*
* This function uses wp.hooks to mimics WP add_filter
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param
* @return
*/
add_filter: function() {
// prefix action
arguments[0] = 'acf/' + arguments[0];
wp.hooks.addFilter.apply(this, arguments);
return this;
},
/*
* remove_filter
*
* This function uses wp.hooks to mimics WP remove_filter
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param
* @return
*/
remove_filter: function() {
// prefix action
arguments[0] = 'acf/' + arguments[0];
wp.hooks.removeFilter.apply(this, arguments);
return this;
},
/*
* apply_filters
*
* This function uses wp.hooks to mimics WP apply_filters
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param
* @return
*/
apply_filters: function() { //console.log('acf.apply_filters(%o)', arguments);
// prefix action
arguments[0] = 'acf/' + arguments[0];
return wp.hooks.applyFilters.apply(this, arguments);
},
/*
* get_selector
*
* This function will return a valid selector for finding a field object
*
* @type function
* @date 15/01/2015
* @since 5.1.5
*
* @param s (string)
* @return (string)
*/
get_selector: function( s ) {
// defaults
s = s || '';
// vars
var selector = '.acf-field';
// compatibility with object
if( $.isPlainObject(s) ) {
if( $.isEmptyObject(s) ) {
s = '';
} else {
for( k in s ) { s = s[k]; break; }
}
}
// search
if( s ) {
// append
selector += '-' + s;
// replace underscores (split/join replaces all and is faster than regex!)
selector = selector.split('_').join('-');
// remove potential double up
selector = selector.split('field-field-').join('field-');
}
// return
return selector;
},
/*
* get_fields
*
* This function will return a jQuery selection of fields
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param args (object)
* @param $el (jQuery) element to look within
* @param all (boolean) return all fields or allow filtering (for repeater)
* @return $fields (jQuery)
*/
get_fields: function( s, $el, all ){
// debug
//console.log( 'acf.get_fields(%o, %o, %o)', args, $el, all );
//console.time("acf.get_fields");
// defaults
s = s || '';
$el = $el || false;
all = all || false;
// vars
var selector = this.get_selector(s);
// get child fields
var $fields = $( selector, $el );
// append context to fields if also matches selector.
// * Required for field group 'change_filed_type' append $tr to work
if( $el !== false ) {
$el.each(function(){
if( $(this).is(selector) ) {
$fields = $fields.add( $(this) );
}
});
}
// filter out fields
if( !all ) {
// remove clone fields
$fields = $fields.not('.acf-clone .acf-field');
// filter
$fields = acf.apply_filters('get_fields', $fields);
}
//console.log('get_fields(%o, %o, %o) %o', s, $el, all, $fields);
//console.log('acf.get_fields(%o):', this.get_selector(s) );
//console.timeEnd("acf.get_fields");
// return
return $fields;
},
/*
* get_field
*
* This function will return a jQuery selection based on a field key
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param field_key (string)
* @param $el (jQuery) element to look within
* @return $field (jQuery)
*/
get_field: function( s, $el ){
// defaults
s = s || '';
$el = $el || false;
// get fields
var $fields = this.get_fields(s, $el, true);
// check if exists
if( $fields.exists() ) {
return $fields.first();
}
// return
return false;
},
/*
* get_closest_field
*
* This function will return the closest parent field
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param $el (jQuery) element to start from
* @param args (object)
* @return $field (jQuery)
*/
get_closest_field : function( $el, s ){
// defaults
s = s || '';
// return
return $el.closest( this.get_selector(s) );
},
/*
* get_field_wrap
*
* This function will return the closest parent field
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param $el (jQuery) element to start from
* @return $field (jQuery)
*/
get_field_wrap: function( $el ){
return $el.closest( this.get_selector() );
},
/*
* get_field_key
*
* This function will return the field's key
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param $field (jQuery)
* @return (string)
*/
get_field_key: function( $field ){
return $field.data('key');
},
/*
* get_field_type
*
* This function will return the field's type
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param $field (jQuery)
* @return (string)
*/
get_field_type: function( $field ){
return $field.data('type');
},
/*
* get_data
*
* This function will return attribute data for a given elemnt
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param $el (jQuery)
* @param name (mixed)
* @return (mixed)
*/
get_data: function( $el, defaults ){
// get data
var data = $el.data();
// defaults
if( typeof defaults === 'object' ) {
data = this.parse_args( data, defaults );
}
// return
return data;
},
/*
* get_uniqid
*
* This function will return a unique string ID
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param prefix (string)
* @param more_entropy (boolean)
* @return (string)
*/
get_uniqid : function( prefix, more_entropy ){
// + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + revised by: Kankrelune (http://www.webfaktory.info/)
// % note 1: Uses an internal counter (in php_js global) to avoid collision
// * example 1: uniqid();
// * returns 1: 'a30285b160c14'
// * example 2: uniqid('foo');
// * returns 2: 'fooa30285b1cd361'
// * example 3: uniqid('bar', true);
// * returns 3: 'bara20285b23dfd1.31879087'
if (typeof prefix === 'undefined') {
prefix = "";
}
var retId;
var formatSeed = function (seed, reqWidth) {
seed = parseInt(seed, 10).toString(16); // to hex str
if (reqWidth < seed.length) { // so long we split
return seed.slice(seed.length - reqWidth);
}
if (reqWidth > seed.length) { // so short we pad
return Array(1 + (reqWidth - seed.length)).join('0') + seed;
}
return seed;
};
// BEGIN REDUNDANT
if (!this.php_js) {
this.php_js = {};
}
// END REDUNDANT
if (!this.php_js.uniqidSeed) { // init seed with big random int
this.php_js.uniqidSeed = Math.floor(Math.random() * 0x75bcd15);
}
this.php_js.uniqidSeed++;
retId = prefix; // start with prefix, add current milliseconds hex string
retId += formatSeed(parseInt(new Date().getTime() / 1000, 10), 8);
retId += formatSeed(this.php_js.uniqidSeed, 5); // add seed hex string
if (more_entropy) {
// for more entropy we add a float lower to 10
retId += (Math.random() * 10).toFixed(8).toString();
}
return retId;
},
/*
* serialize_form
*
* This function will create an object of data containing all form inputs within an element
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param $el (jQuery selection)
* @return $post_id (int)
*/
serialize_form: function(){
return this.serialize.apply( this, arguments );
},
serialize: function( $el, prefix ){
// defaults
prefix = prefix || '';
// vars
var data = {};
var names = {};
var values = $el.find('select, textarea, input').serializeArray();
// populate data
$.each( values, function( i, pair ) {
// vars
var name = pair.name;
var value = pair.value;
// prefix
if( prefix ) {
// bail early if does not contain
if( name.indexOf(prefix) !== 0 ) return;
// remove prefix
name = name.slice(prefix.length);
// name must not start as array piece
if( name.slice(0, 1) == '[' ) {
name = name.slice(1).replace(']', '');
}
}
// initiate name
if( name.slice(-2) === '[]' ) {
// remove []
name = name.slice(0, -2);
// initiate counter
if( typeof names[ name ] === 'undefined'){
names[ name ] = -1;
}
// increase counter
names[ name ]++;
// add key
name += '[' + names[ name ] +']';
}
// append to data
data[ name ] = value;
});
//console.log('serialize', data);
// return
return data;
},
/*
serialize: function( $el, prefix ){
// defaults
prefix = prefix || '';
// vars
var data = {};
var $inputs = $el.find('select, textarea, input');
// loop
$inputs.each(function(){
// vars
var $el = $(this);
var name = $el.attr('name');
var val = $el.val();
// is array
var is_array = ( name.slice(-2) === '[]' );
if( is_array ) {
name = name.slice(0, -2);
}
// explode name
var bits = name.split('[');
var depth = bits.length;
// loop
for( var i = 0; i < depth; i++ ) {
// vars
var k = bits[i];
// end
if( i == depth-1 ) {
// not end
} else {
// must be object
if( typeof data[k] !== 'object' ) {
data[k] = {};
}
}
}
bits.map(function( s ){ return s.replace(']', ''); })
});
},
*/
/*
* disable
*
* This function will disable an input
*
* @type function
* @date 22/09/2016
* @since 5.4.0
*
* @param $el (jQuery)
* @param context (string)
* @return n/a
*/
disable: function( $input, context ){
// defaults
context = context || '';
// bail early if is .acf-disabled
if( $input.hasClass('acf-disabled') ) return false;
// always disable input
$input.prop('disabled', true);
// context
if( context ) {
// vars
var disabled = $input.data('acf_disabled') || [],
i = disabled.indexOf(context);
// append context if not found
if( i < 0 ) {
// append
disabled.push( context );
// update
$input.data('acf_disabled', disabled);
}
}
// return
return true;
},
/*
* enable
*
* This function will enable an input
*
* @type function
* @date 22/09/2016
* @since 5.4.0
*
* @param $el (jQuery)
* @param context (string)
* @return n/a
*/
enable: function( $input, context ){
// defaults
context = context || '';
// bail early if is .acf-disabled
if( $input.hasClass('acf-disabled') ) return false;
// vars
var disabled = $input.data('acf_disabled') || [];
// context
if( context ) {
// vars
var i = disabled.indexOf(context);
// remove context if found
if( i > -1 ) {
// delete
disabled.splice(i, 1);
// update
$input.data('acf_disabled', disabled);
}
}
// bail early if other disabled exist
if( disabled.length ) return false;
// enable input
$input.prop('disabled', false);
// return
return true;
},
/*
* disable_el
*
* This function will disable all inputs within an element
*
* @type function
* @date 22/09/2016
* @since 5.4.0
*
* @param $el (jQuery)
* @param context (string)
* @return na
*/
disable_el: function( $el, context ) {
// defaults
context = context || '';
// loop
$el.find('select, textarea, input').each(function(){
acf.disable( $(this), context );
});
},
disable_form: function( $el, context ) {
this.disable_el.apply( this, arguments );
},
/*
* enable_el
*
* This function will enable all inputs within an element
*
* @type function
* @date 22/09/2016
* @since 5.4.0
*
* @param $el (jQuery)
* @param context (string)
* @return na
*/
enable_el: function( $el, context ) {
// defaults
context = context || '';
// loop
$el.find('select, textarea, input').each(function(){
acf.enable( $(this), context );
});
},
enable_form: function( $el, context ) {
this.enable_el.apply( this, arguments );
},
/*
* remove_tr
*
* This function will remove a tr element with animation
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param $tr (jQuery selection)
* @param callback (function) runs on complete
* @return n/a
*/
remove_tr : function( $tr, callback ){
// vars
var height = $tr.height(),
children = $tr.children().length;
// add class
$tr.addClass('acf-remove-element');
// after animation
setTimeout(function(){
// remove class
$tr.removeClass('acf-remove-element');
// vars
$tr.html('
');
$tr.children('td').animate({ height : 0}, 250, function(){
$tr.remove();
if( typeof(callback) == 'function' ) {
callback();
}
});
}, 250);
},
/*
* remove_el
*
* This function will remove an element with animation
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param $el (jQuery selection)
* @param callback (function) runs on complete
* @param end_height (int)
* @return n/a
*/
remove_el : function( $el, callback, end_height ){
// defaults
end_height = end_height || 0;
// vars
var height = $el.height(),
width = $el.width(),
margin = $el.css('margin'),
outer_height = $el.outerHeight(true);
// action
acf.do_action('remove', $el);
// create wrap
$el.wrap('');
var $wrap = $el.parent();
// set pos
$el.css({
height: height,
width: width,
margin: margin,
position: 'absolute'
});
// fade
setTimeout(function(){
// aniamte
$wrap.css({
opacity: 0,
height: end_height
});
}, 50);
// animate complete
setTimeout(function(){
// remove wrap
$wrap.remove();
// callback
if( typeof(callback) == 'function' ) {
callback.apply(this, arguments);
}
}, 301);
},
/*
* isset
*
* This function will return true if an object key exists
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param (object)
* @param key1 (string)
* @param key2 (string)
* @param ...
* @return (boolean)
*/
isset : function(){
var a = arguments,
l = a.length,
c = null,
undef;
if (l === 0) {
throw new Error('Empty isset');
}
c = a[0];
for (i = 1; i < l; i++) {
if (a[i] === undef || c[ a[i] ] === undef) {
return false;
}
c = c[ a[i] ];
}
return true;
},
/*
* maybe_get
*
* This function will attempt to return a value and return null if not possible
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param obj (object) the array to look within
* @param key (key) the array key to look for. Nested values may be found using '/'
* @param value (mixed) the value returned if not found
* @return (mixed)
*/
maybe_get: function( obj, key, value ){
// default
if( typeof value == 'undefined' ) value = null;
// convert type to string and split
keys = String(key).split('.');
// loop through keys
for( var i in keys ) {
// vars
var key = keys[i];
// bail ealry if not set
if( typeof obj[ key ] === 'undefined' ) {
return value;
}
// update obj
obj = obj[ key ];
}
// return
return obj;
},
/*
* open_popup
*
* This function will create and open a popup modal
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param args (object)
* @return n/a
*/
open_popup : function( args ){
// vars
$popup = $('body > #acf-popup');
// already exists?
if( $popup.exists() ) {
return update_popup(args);
}
// template
var tmpl = [
'
',
'
',
'
',
'',
'
',
'
',
'',
'
'
].join('');
// append
$('body').append( tmpl );
$('#acf-popup').on('click', '.bg, .acf-close-popup', function( e ){
e.preventDefault();
acf.close_popup();
});
// update
return this.update_popup(args);
},
/*
* update_popup
*
* This function will update the content within a popup modal
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param args (object)
* @return n/a
*/
update_popup : function( args ){
// vars
$popup = $('#acf-popup');
// validate
if( !$popup.exists() )
{
return false
}
// defaults
args = $.extend({}, {
title : '',
content : '',
width : 0,
height : 0,
loading : false
}, args);
if( args.title ) {
$popup.find('.title h3').html( args.title );
}
if( args.content ) {
$inner = $popup.find('.inner:first');
$inner.html( args.content );
acf.do_action('append', $inner);
// update height
$inner.attr('style', 'position: relative;');
args.height = $inner.outerHeight();
$inner.removeAttr('style');
}
if( args.width ) {
$popup.find('.acf-popup-box').css({
'width' : args.width,
'margin-left' : 0 - (args.width / 2)
});
}
if( args.height ) {
// add h3 height (44)
args.height += 44;
$popup.find('.acf-popup-box').css({
'height' : args.height,
'margin-top' : 0 - (args.height / 2)
});
}
if( args.loading ) {
$popup.find('.loading').show();
} else {
$popup.find('.loading').hide();
}
return $popup;
},
/*
* close_popup
*
* This function will close and remove a popup modal
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param n/a
* @return n/a
*/
close_popup : function(){
// vars
$popup = $('#acf-popup');
// already exists?
if( $popup.exists() )
{
$popup.remove();
}
},
/*
* update_user_setting
*
* This function will send an AJAX request to update a user setting
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
update_user_setting : function( name, value ) {
// ajax
$.ajax({
url : acf.get('ajaxurl'),
dataType : 'html',
type : 'post',
data : acf.prepare_for_ajax({
'action' : 'acf/update_user_setting',
'name' : name,
'value' : value
})
});
},
/*
* prepare_for_ajax
*
* This function will prepare data for an AJAX request
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param args (object)
* @return args
*/
prepare_for_ajax : function( args ) {
// vars
var data = {
nonce : acf.get('nonce'),
post_id : acf.get('post_id')
};
// $.ajax() expects all args to be 'non-nested'
$.each(args, function(k,v){
// object
if( $.isPlainObject(v) && !$.isEmptyObject(v) ) {
// loop
$.each(v, function(k2,v2){
// convert string
k2 = k2 + '';
// vars
var i = k2.indexOf('[');
// starts with [
if( i == 0 ) {
k2 = k + k2;
// contains [
} else if( i > 0 ) {
k2 = k + '[' + k2.slice(0, i) + ']' + k2.slice(i);
// no [
} else {
k2 = k + '[' + k2 + ']';
}
// append
data[k2] = v2;
});
// else
} else {
data[k] = v;
}
});
// filter for 3rd party customization
data = acf.apply_filters('prepare_for_ajax', data);
//console.log( 'prepare_for_ajax', data );
// return
return data;
},
/*
* is_ajax_success
*
* This function will return true for a successful WP AJAX response
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param json (object)
* @return (boolean)
*/
is_ajax_success : function( json ) {
if( json && json.success ) {
return true;
}
return false;
},
/*
* get_ajax_message
*
* This function will return an object containing error/message information
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param json (object)
* @return (boolean)
*/
get_ajax_message: function( json ) {
// vars
var message = {
text: '',
type: 'error'
};
// bail early if no json
if( !json ) {
return message;
}
// PHP error (too may themes will have warnings / errors. Don't show these in ACF taxonomy popup)
/*
if( typeof json === 'string' ) {
message.text = json;
return message;
}
*/
// success
if( json.success ) {
message.type = 'success';
}
// message
if( json.data && json.data.message ) {
message.text = json.data.message;
}
// error
if( json.data && json.data.error ) {
message.text = json.data.error;
}
// return
return message;
},
/*
* is_in_view
*
* This function will return true if a jQuery element is visible in browser
*
* @type function
* @date 8/09/2014
* @since 5.0.0
*
* @param $el (jQuery)
* @return (boolean)
*/
is_in_view: function( $el ) {
// vars
var elemTop = $el.offset().top,
elemBottom = elemTop + $el.height();
// bail early if hidden
if( elemTop === elemBottom ) {
return false;
}
// more vars
var docViewTop = $(window).scrollTop(),
docViewBottom = docViewTop + $(window).height();
// return
return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
},
/*
* val
*
* This function will update an elements value and trigger the change event if different
*
* @type function
* @date 16/10/2014
* @since 5.0.9
*
* @param $el (jQuery)
* @param val (mixed)
* @return n/a
*/
val: function( $el, val ){
// vars
var orig = $el.val();
// update value
$el.val( val );
// trigger change
if( val != orig ) {
$el.trigger('change');
}
},
/*
* str_replace
*
* This function will perform a str replace similar to php function str_replace
*
* @type function
* @date 1/05/2015
* @since 5.2.3
*
* @param $search (string)
* @param $replace (string)
* @param $subject (string)
* @return (string)
*/
str_replace: function( search, replace, subject ) {
return subject.split(search).join(replace);
},
/*
* str_sanitize
*
* description
*
* @type function
* @date 4/06/2015
* @since 5.2.3
*
* @param $post_id (int)
* @return $post_id (int)
*/
str_sanitize: function( string ) {
// chars (https://jsperf.com/replace-foreign-characters)
var map = {
"À": "A",
"Á": "A",
"Â": "A",
"Ã": "A",
"Ä": "A",
"Å": "A",
"Æ": "AE",
"Ç": "C",
"È": "E",
"É": "E",
"Ê": "E",
"Ë": "E",
"Ì": "I",
"Í": "I",
"Î": "I",
"Ï": "I",
"Ð": "D",
"Ñ": "N",
"Ò": "O",
"Ó": "O",
"Ô": "O",
"Õ": "O",
"Ö": "O",
"Ø": "O",
"Ù": "U",
"Ú": "U",
"Û": "U",
"Ü": "U",
"Ý": "Y",
"ß": "s",
"à": "a",
"á": "a",
"â": "a",
"ã": "a",
"ä": "a",
"å": "a",
"æ": "ae",
"ç": "c",
"è": "e",
"é": "e",
"ê": "e",
"ë": "e",
"ì": "i",
"í": "i",
"î": "i",
"ï": "i",
"ñ": "n",
"ò": "o",
"ó": "o",
"ô": "o",
"õ": "o",
"ö": "o",
"ø": "o",
"ù": "u",
"ú": "u",
"û": "u",
"ü": "u",
"ý": "y",
"ÿ": "y",
"Ā": "A",
"ā": "a",
"Ă": "A",
"ă": "a",
"Ą": "A",
"ą": "a",
"Ć": "C",
"ć": "c",
"Ĉ": "C",
"ĉ": "c",
"Ċ": "C",
"ċ": "c",
"Č": "C",
"č": "c",
"Ď": "D",
"ď": "d",
"Đ": "D",
"đ": "d",
"Ē": "E",
"ē": "e",
"Ĕ": "E",
"ĕ": "e",
"Ė": "E",
"ė": "e",
"Ę": "E",
"ę": "e",
"Ě": "E",
"ě": "e",
"Ĝ": "G",
"ĝ": "g",
"Ğ": "G",
"ğ": "g",
"Ġ": "G",
"ġ": "g",
"Ģ": "G",
"ģ": "g",
"Ĥ": "H",
"ĥ": "h",
"Ħ": "H",
"ħ": "h",
"Ĩ": "I",
"ĩ": "i",
"Ī": "I",
"ī": "i",
"Ĭ": "I",
"ĭ": "i",
"Į": "I",
"į": "i",
"İ": "I",
"ı": "i",
"IJ": "IJ",
"ij": "ij",
"Ĵ": "J",
"ĵ": "j",
"Ķ": "K",
"ķ": "k",
"Ĺ": "L",
"ĺ": "l",
"Ļ": "L",
"ļ": "l",
"Ľ": "L",
"ľ": "l",
"Ŀ": "L",
"ŀ": "l",
"Ł": "l",
"ł": "l",
"Ń": "N",
"ń": "n",
"Ņ": "N",
"ņ": "n",
"Ň": "N",
"ň": "n",
"ʼn": "n",
"Ō": "O",
"ō": "o",
"Ŏ": "O",
"ŏ": "o",
"Ő": "O",
"ő": "o",
"Œ": "OE",
"œ": "oe",
"Ŕ": "R",
"ŕ": "r",
"Ŗ": "R",
"ŗ": "r",
"Ř": "R",
"ř": "r",
"Ś": "S",
"ś": "s",
"Ŝ": "S",
"ŝ": "s",
"Ş": "S",
"ş": "s",
"Š": "S",
"š": "s",
"Ţ": "T",
"ţ": "t",
"Ť": "T",
"ť": "t",
"Ŧ": "T",
"ŧ": "t",
"Ũ": "U",
"ũ": "u",
"Ū": "U",
"ū": "u",
"Ŭ": "U",
"ŭ": "u",
"Ů": "U",
"ů": "u",
"Ű": "U",
"ű": "u",
"Ų": "U",
"ų": "u",
"Ŵ": "W",
"ŵ": "w",
"Ŷ": "Y",
"ŷ": "y",
"Ÿ": "Y",
"Ź": "Z",
"ź": "z",
"Ż": "Z",
"ż": "z",
"Ž": "Z",
"ž": "z",
"ſ": "s",
"ƒ": "f",
"Ơ": "O",
"ơ": "o",
"Ư": "U",
"ư": "u",
"Ǎ": "A",
"ǎ": "a",
"Ǐ": "I",
"ǐ": "i",
"Ǒ": "O",
"ǒ": "o",
"Ǔ": "U",
"ǔ": "u",
"Ǖ": "U",
"ǖ": "u",
"Ǘ": "U",
"ǘ": "u",
"Ǚ": "U",
"ǚ": "u",
"Ǜ": "U",
"ǜ": "u",
"Ǻ": "A",
"ǻ": "a",
"Ǽ": "AE",
"ǽ": "ae",
"Ǿ": "O",
"ǿ": "o",
// extra
' ': '_',
'\'': '',
'?': '',
'/': '',
'\\': '',
'.': '',
',': '',
'`': '',
'>': '',
'<': '',
'"': '',
'[': '',
']': '',
'|': '',
'{': '',
'}': '',
'(': '',
')': ''
};
// vars
var regexp = /\W/g,
mapping = function (c) { return (typeof map[c] !== 'undefined') ? map[c] : c; };
// replace
string = string.replace(regexp, mapping);
// lower case
string = string.toLowerCase();
// return
return string;
},
/*
* addslashes
*
* This function mimics the PHP addslashes function.
* Returns a string with backslashes before characters that need to be escaped.
*
* @type function
* @date 9/1/17
* @since 5.5.0
*
* @param text (string)
* @return (string)
*/
addslashes: function(text){
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
},
/*
* render_select
*
* This function will update a select field with new choices
*
* @type function
* @date 8/04/2014
* @since 5.0.0
*
* @param $select
* @param choices
* @return n/a
*/
render_select: function( $select, choices ){
// vars
var value = $select.val();
// clear choices
$select.html('');
// bail early if no choices
if( !choices ) {
return;
}
// populate choices
$.each(choices, function( i, item ){
// vars
var $optgroup = $select;
// add group
if( item.group ) {
$optgroup = $select.find('optgroup[label="' + item.group + '"]');
if( !$optgroup.exists() ) {
$optgroup = $('');
$select.append( $optgroup );
}
}
// append select
$optgroup.append( '' );
// selectedIndex
if( value == item.value ) {
$select.prop('selectedIndex', i);
}
});
},
/*
* duplicate
*
* This function will duplicate and return an element
*
* @type function
* @date 22/08/2015
* @since 5.2.3
*
* @param $el (jQuery) object to be duplicated
* @param attr (string) attrbute name where $el id can be found
* @return $el2 (jQuery)
*/
duplicate: function( args ){
//console.time('duplicate');
// backwards compatibility
// - array of settings added in v5.4.6
if( typeof args.length !== 'undefined' ) args = { $el: args };
// defaults
args = acf.parse_args(args, {
$el: false,
search: '',
replace: '',
before: function( $el ){},
after: function( $el, $el2 ){},
append: function( $el, $el2 ){ $el.after( $el2 ); }
});
// vars
var $el = args.$el,
$el2;
// search
if( !args.search ) args.search = $el.attr('data-id');
// replace
if( !args.replace ) args.replace = acf.get_uniqid();
// before
// - allow acf to modify DOM
// - fixes bug where select field option is not selected
args.before.apply( this, [$el] );
acf.do_action('before_duplicate', $el);
// clone
var $el2 = $el.clone();
// remove acf-clone (may be a clone)
$el2.removeClass('acf-clone');
// remove JS functionality
acf.do_action('remove', $el2);
// find / replace
if( args.search ) {
// replace data
$el2.attr('data-id', args.replace);
// replace ids
$el2.find('[id*="' + args.search + '"]').each(function(){
$(this).attr('id', $(this).attr('id').replace(args.search, args.replace) );
});
// replace names
$el2.find('[name*="' + args.search + '"]').each(function(){
$(this).attr('name', $(this).attr('name').replace(args.search, args.replace) );
});
// replace label for
$el2.find('label[for*="' + args.search + '"]').each(function(){
$(this).attr('for', $(this).attr('for').replace(args.search, args.replace) );
});
}
// remove ui-sortable
$el2.find('.ui-sortable').removeClass('ui-sortable');
// after
// - allow acf to modify DOM
acf.do_action('after_duplicate', $el, $el2 );
args.after.apply( this, [$el, $el2] );
// append
args.append.apply( this, [$el, $el2] );
// add JS functionality
// - allow element to be moved into a visible position before fire action
setTimeout(function(){
acf.do_action('append', $el2);
}, 1);
//console.timeEnd('duplicate');
// return
return $el2;
},
decode: function( string ){
return $('').html( string ).text();
},
/*
* parse_args
*
* This function will merge together defaults and args much like the WP wp_parse_args function
*
* @type function
* @date 11/04/2016
* @since 5.3.8
*
* @param args (object)
* @param defaults (object)
* @return args
*/
parse_args: function( args, defaults ) {
// defaults
if( typeof args !== 'object' ) args = {};
if( typeof defaults !== 'object' ) defaults = {};
// return
return $.extend({}, defaults, args);
},
/*
* enqueue_script
*
* This function will append a script to the page
*
* @source https://www.nczonline.net/blog/2009/06/23/loading-javascript-without-blocking/
* @type function
* @date 27/08/2016
* @since 5.4.0
*
* @param url (string)
* @param callback (function)
* @return na
*/
enqueue_script: function( url, callback ) {
// vars
var script = document.createElement('script');
// atts
script.type = "text/javascript";
script.src = url;
script.async = true;
// ie
if( script.readyState ) {
script.onreadystatechange = function(){
if( script.readyState == 'loaded' || script.readyState == 'complete' ){
script.onreadystatechange = null;
callback();
}
};
// normal browsers
} else {
script.onload = function(){
callback();
};
}
// append
document.body.appendChild(script);
}
};
/*
* acf.model
*
* This model acts as a scafold for action.event driven modules
*
* @type object
* @date 8/09/2014
* @since 5.0.0
*
* @param (object)
* @return (object)
*/
acf.model = {
// vars
actions: {},
filters: {},
events: {},
extend: function( args ){
// extend
var model = $.extend( {}, this, args );
// setup actions
$.each(model.actions, function( name, callback ){
model._add_action( name, callback );
});
// setup filters
$.each(model.filters, function( name, callback ){
model._add_filter( name, callback );
});
// setup events
$.each(model.events, function( name, callback ){
model._add_event( name, callback );
});
// return
return model;
},
_add_action: function( name, callback ) {
// split
var model = this,
data = name.split(' ');
// add missing priority
var name = data[0] || '',
priority = data[1] || 10;
// add action
acf.add_action(name, model[ callback ], priority, model);
},
_add_filter: function( name, callback ) {
// split
var model = this,
data = name.split(' ');
// add missing priority
var name = data[0] || '',
priority = data[1] || 10;
// add action
acf.add_filter(name, model[ callback ], priority, model);
},
_add_event: function( name, callback ) {
// vars
var model = this,
i = name.indexOf(' '),
event = (i > 0) ? name.substr(0,i) : name,
selector = (i > 0) ? name.substr(i+1) : '';
// event
var fn = function( e ){
// append $el to event object
e.$el = $(this);
// event
if( typeof model.event === 'function' ) {
e = model.event( e );
}
// callback
model[ callback ].apply(model, arguments);
};
// add event
if( selector ) {
$(document).on(event, selector, fn);
} else {
$(document).on(event, fn);
}
},
get: function( name, value ){
// defaults
value = value || null;
// get
if( typeof this[ name ] !== 'undefined' ) {
value = this[ name ];
}
// return
return value;
},
set: function( name, value ){
// set
this[ name ] = value;
// function for 3rd party
if( typeof this[ '_set_' + name ] === 'function' ) {
this[ '_set_' + name ].apply(this);
}
// return for chaining
return this;
}
};
/*
* field
*
* This model sets up many of the field's interactions
*
* @type function
* @date 21/02/2014
* @since 3.5.1
*
* @param n/a
* @return n/a
*/
acf.field = acf.model.extend({
// vars
type: '',
o: {},
$field: null,
_add_action: function( name, callback ) {
// vars
var model = this;
// update name
name = name + '_field/type=' + model.type;
// add action
acf.add_action(name, function( $field ){
// focus
model.set('$field', $field);
// callback
model[ callback ].apply(model, arguments);
});
},
_add_filter: function( name, callback ) {
// vars
var model = this;
// update name
name = name + '_field/type=' + model.type;
// add action
acf.add_filter(name, function( $field ){
// focus
model.set('$field', $field);
// callback
model[ callback ].apply(model, arguments);
});
},
_add_event: function( name, callback ) {
// vars
var model = this,
event = name.substr(0,name.indexOf(' ')),
selector = name.substr(name.indexOf(' ')+1),
context = acf.get_selector(model.type);
// add event
$(document).on(event, context + ' ' + selector, function( e ){
// append $el to event object
e.$el = $(this);
e.$field = acf.get_closest_field(e.$el, model.type);
// focus
model.set('$field', e.$field);
// callback
model[ callback ].apply(model, [e]);
});
},
_set_$field: function(){
// callback
if( typeof this.focus === 'function' ) {
this.focus();
}
},
// depreciated
doFocus: function( $field ){
return this.set('$field', $field);
}
});
/*
* field
*
* This model fires actions and filters for registered fields
*
* @type function
* @date 21/02/2014
* @since 3.5.1
*
* @param n/a
* @return n/a
*/
acf.fields = acf.model.extend({
actions: {
'prepare' : '_prepare',
'prepare_field' : '_prepare_field',
'ready' : '_ready',
'ready_field' : '_ready_field',
'append' : '_append',
'append_field' : '_append_field',
'load' : '_load',
'load_field' : '_load_field',
'remove' : '_remove',
'remove_field' : '_remove_field',
'sortstart' : '_sortstart',
'sortstart_field' : '_sortstart_field',
'sortstop' : '_sortstop',
'sortstop_field' : '_sortstop_field',
'show' : '_show',
'show_field' : '_show_field',
'hide' : '_hide',
'hide_field' : '_hide_field'
},
// prepare
_prepare: function( $el ){
acf.get_fields('', $el).each(function(){
acf.do_action('prepare_field', $(this));
});
},
_prepare_field: function( $el ){
acf.do_action('prepare_field/type=' + $el.data('type'), $el);
},
// ready
_ready: function( $el ){
acf.get_fields('', $el).each(function(){
acf.do_action('ready_field', $(this));
});
},
_ready_field: function( $el ){
acf.do_action('ready_field/type=' + $el.data('type'), $el);
},
// append
_append: function( $el ){
acf.get_fields('', $el).each(function(){
acf.do_action('append_field', $(this));
});
},
_append_field: function( $el ){
acf.do_action('append_field/type=' + $el.data('type'), $el);
},
// load
_load: function( $el ){
acf.get_fields('', $el).each(function(){
acf.do_action('load_field', $(this));
});
},
_load_field: function( $el ){
acf.do_action('load_field/type=' + $el.data('type'), $el);
},
// remove
_remove: function( $el ){
acf.get_fields('', $el).each(function(){
acf.do_action('remove_field', $(this));
});
},
_remove_field: function( $el ){
acf.do_action('remove_field/type=' + $el.data('type'), $el);
},
// sortstart
_sortstart: function( $el, $placeholder ){
acf.get_fields('', $el).each(function(){
acf.do_action('sortstart_field', $(this), $placeholder);
});
},
_sortstart_field: function( $el, $placeholder ){
acf.do_action('sortstart_field/type=' + $el.data('type'), $el, $placeholder);
},
// sortstop
_sortstop: function( $el, $placeholder ){
acf.get_fields('', $el).each(function(){
acf.do_action('sortstop_field', $(this), $placeholder);
});
},
_sortstop_field: function( $el, $placeholder ){
acf.do_action('sortstop_field/type=' + $el.data('type'), $el, $placeholder);
},
// hide
_hide: function( $el, context ){
acf.get_fields('', $el).each(function(){
acf.do_action('hide_field', $(this), context);
});
},
_hide_field: function( $el, context ){
acf.do_action('hide_field/type=' + $el.data('type'), $el, context);
},
// show
_show: function( $el, context ){
acf.get_fields('', $el).each(function(){
acf.do_action('show_field', $(this), context);
});
},
_show_field: function( $el, context ){
acf.do_action('show_field/type=' + $el.data('type'), $el, context);
}
});
/*
* ready
*
* description
*
* @type function
* @date 19/02/2014
* @since 5.0.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
$(document).ready(function(){
// action for 3rd party customization
acf.do_action('ready', $('body'));
});
/*
* load
*
* description
*
* @type function
* @date 19/02/2014
* @since 5.0.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
$(window).on('load', function(){
// action for 3rd party customization
acf.do_action('load', $('body'));
});
/*
* layout
*
* This model handles the width layout for fields
*
* @type function
* @date 21/02/2014
* @since 3.5.1
*
* @param n/a
* @return n/a
*/
acf.layout = acf.model.extend({
active: 0,
actions: {
'prepare 99': 'prepare',
'refresh': 'refresh'
},
prepare: function(){
// vars
this.active = 1;
// render
this.refresh();
},
refresh: function( $el ){
// bail early if not yet active
if( !this.active ) return;
// defaults
$el = $el || $('body');
// reference
var self = this;
// render
this.render_tables( $el );
this.render_groups( $el );
},
render_tables: function( $el ){
// reference
var self = this;
// vars
var $tables = $el.find('.acf-table:visible');
// appent self if is tr
if( $el.is('tr') ) {
$tables = $el.parent().parent();
}
// loop
$tables.each(function(){
self.render_table( $(this) );
});
},
render_table: function( $table ){
// vars
var $ths = $table.find('> thead th.acf-th'),
colspan = 1,
available_width = 100;
// bail early if no $ths
if( !$ths.exists() ) return;
// vars
var $trs = $table.find('> tbody > tr'),
$tds = $trs.find('> td.acf-field');
// remove clones if has visible rows
if( $trs.hasClass('acf-clone') && $trs.length > 1 ) {
$tds = $trs.not('.acf-clone').find('> td.acf-field');
}
// render th/td visibility
$ths.each(function(){
// vars
var $th = $(this),
key = $th.attr('data-key'),
$td = $tds.filter('[data-key="'+key+'"]');
// clear class
$td.removeClass('appear-empty');
$th.removeClass('hidden-by-conditional-logic');
// no td
if( !$td.exists() ) {
// do nothing
// if all td are hidden
} else if( $td.not('.hidden-by-conditional-logic').length == 0 ) {
$th.addClass('hidden-by-conditional-logic');
// if 1 or more td are visible
} else {
$td.filter('.hidden-by-conditional-logic').addClass('appear-empty');
}
});
// clear widths
$ths.css('width', 'auto');
// update $ths
$ths = $ths.not('.hidden-by-conditional-logic');
// set colspan
colspan = $ths.length;
// set custom widths first
$ths.filter('[data-width]').each(function(){
// vars
var width = parseInt( $(this).attr('data-width') );
// remove from available
available_width -= width;
// set width
$(this).css('width', width + '%');
});
// update $ths
$ths = $ths.not('[data-width]');
// set custom widths first
$ths.each(function(){
// cal width
var width = available_width / $ths.length;
// set width
$(this).css('width', width + '%');
});
// update colspan
$table.find('.acf-row .acf-field.-collapsed-target').removeAttr('colspan');
$table.find('.acf-row.-collapsed .acf-field.-collapsed-target').attr('colspan', colspan);
},
render_groups: function( $el ){
// reference
var self = this;
// vars
var $groups = $el.find('.acf-fields:visible');
// appent self if is '.acf-fields'
if( $el && $el.is('.acf-fields') ) {
$groups = $groups.add( $el );
}
// loop
$groups.each(function(){
self.render_group( $(this) );
});
},
render_group: function( $el ){
// vars
var $els = $(),
top = 0,
height = 0,
cell = -1;
// get fields
var $fields = $el.children('.acf-field[data-width]:visible');
// bail early if no fields
if( !$fields.exists() ) return;
// bail ealry if is .-left
if( $el.hasClass('-left') ) {
$fields.removeAttr('data-width');
$fields.css('width', 'auto');
return;
}
// reset fields
$fields.removeClass('acf-r0 acf-c0').css({'min-height': 0});
// loop
$fields.each(function( i ){
// vars
var $el = $(this),
this_top = $el.position().top;
// set top
if( i == 0 ) top = this_top;
// detect new row
if( this_top != top ) {
// set previous heights
$els.css({'min-height': (height+1)+'px'});
// reset
$els = $();
top = $el.position().top; // don't use variable as this value may have changed due to min-height css
height = 0;
cell = -1;
}
// increase
cell++;
// set height
height = ($el.outerHeight() > height) ? $el.outerHeight() : height;
// append
$els = $els.add( $el );
// add classes
if( this_top == 0 ) {
$el.addClass('acf-r0');
} else if( cell == 0 ) {
$el.addClass('acf-c0');
}
});
// clean up
if( $els.exists() ) {
$els.css({'min-height': (height+1)+'px'});
}
}
});
/*
* Force revisions
*
* description
*
* @type function
* @date 19/02/2014
* @since 5.0.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
$(document).on('change', '.acf-field input, .acf-field textarea, .acf-field select', function(){
// preview hack
var $input = $('#_acf_changed');
if( $input.length ) $input.val(1);
// action for 3rd party customization
acf.do_action('change', $(this));
});
/*
* preventDefault helper
*
* This function will prevent default of any link with an href of #
*
* @type function
* @date 24/07/2014
* @since 5.0.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
$(document).on('click', '.acf-field a[href="#"]', function( e ){
e.preventDefault();
});
/*
* unload
*
* This model handles the unload prompt
*
* @type function
* @date 21/02/2014
* @since 3.5.1
*
* @param n/a
* @return n/a
*/
acf.unload = acf.model.extend({
locked: 1,
active: 1,
changed: 0,
filters: {
'validation_complete': 'validation_complete'
},
actions: {
'ready': 'ready',
'change': 'on',
},
ready: function(){
// unlock in 1s to avoid JS 'trigger change' bugs
setTimeout(function(){
acf.unload.locked = 0;
}, 1000);
},
events: {
'submit form': 'off'
},
validation_complete: function( json, $form ){
if( json && json.errors ) {
this.on();
}
// return
return json;
},
on: function(){
// bail ealry if already changed, not active, or still locked
if( this.changed || !this.active || this.locked ) {
return;
}
// update
this.changed = 1;
// add event
$(window).on('beforeunload', this.unload);
},
off: function(){
// update
this.changed = 0;
// remove event
$(window).off('beforeunload', this.unload);
},
unload: function(){
// alert string
return acf._e('unload');
}
});
acf.tooltip = acf.model.extend({
events: {
'mouseenter .acf-js-tooltip': '_on',
'mouseup .acf-js-tooltip': '_off',
'mouseleave .acf-js-tooltip': '_off'
},
tooltip: function( text, $el ){
// vars
var $tooltip = $('
' + text + '
');
// append
$('body').append( $tooltip );
// position
var tolerance = 10;
target_w = $el.outerWidth(),
target_h = $el.outerHeight(),
target_t = $el.offset().top,
target_l = $el.offset().left,
tooltip_w = $tooltip.outerWidth(),
tooltip_h = $tooltip.outerHeight();
// calculate top
var top = target_t - tooltip_h,
left = target_l + (target_w / 2) - (tooltip_w / 2);
// too far left
if( left < tolerance ) {
$tooltip.addClass('right');
left = target_l + target_w;
top = target_t + (target_h / 2) - (tooltip_h / 2);
// too far right
} else if( (left + tooltip_w + tolerance) > $(window).width() ) {
$tooltip.addClass('left');
left = target_l - tooltip_w;
top = target_t + (target_h / 2) - (tooltip_h / 2);
// too far top
} else if( top - $(window).scrollTop() < tolerance ) {
$tooltip.addClass('bottom');
top = target_t + target_h;
} else {
$tooltip.addClass('top');
}
// update css
$tooltip.css({ 'top': top, 'left': left });
// return
return $tooltip;
},
confirm: function( $el, callback, text, button_y, button_n ){
// defaults
text = text || acf._e('are_you_sure');
button_y = button_y || ''+acf._e('yes')+'';
button_n = button_n || ''+acf._e('No')+'';
// vars
var $tooltip = this.tooltip( text + ' ' + button_y + ' ' + button_n , $el);
// add class
$tooltip.addClass('-confirm');
// events
var event = function( e, result ){
// prevent all listeners
e.preventDefault();
e.stopImmediatePropagation();
// remove events
$el.off('click', event_y);
$tooltip.off('click', '.acf-confirm-y', event_y);
$tooltip.off('click', '.acf-confirm-n', event_n);
$('body').off('click', event_n);
// remove tooltip
$tooltip.remove();
// callback
callback.apply(null, [result]);
};
var event_y = function( e ){
event( e, true );
};
var event_n = function( e ){
event( e, false );
};
// add events
$tooltip.on('click', '.acf-confirm-y', event_y);
$tooltip.on('click', '.acf-confirm-n', event_n);
$el.on('click', event_y);
$('body').on('click', event_n);
},
confirm_remove: function( $el, callback ){
// vars
text = false; // default
button_y = ''+acf._e('remove')+'';
button_n = ''+acf._e('cancel')+'';
// confirm
this.confirm( $el, callback, false, button_y, button_n );
},
_on: function( e ){
// vars
var title = e.$el.attr('title');
// bail ealry if no title
if( !title ) return;
// create tooltip
var $tooltip = this.tooltip( title, e.$el );
// store as data
e.$el.data('acf-tooltip', {
'title': title,
'$el': $tooltip
});
// clear title to avoid default browser tooltip
e.$el.attr('title', '');
},
_off: function( e ){
// vars
var tooltip = e.$el.data('acf-tooltip');
// bail early if no data
if( !tooltip ) return;
// remove tooltip
tooltip.$el.remove();
// restore title
e.$el.attr('title', tooltip.title);
}
});
acf.postbox = acf.model.extend({
events: {
'mouseenter .acf-postbox .handlediv': 'on',
'mouseleave .acf-postbox .handlediv': 'off'
},
on: function( e ){
e.$el.siblings('.hndle').addClass('hover');
},
off: function( e ){
e.$el.siblings('.hndle').removeClass('hover');
},
render: function( args ){
// defaults
args = $.extend({}, {
id: '',
key: '',
style: 'default',
label: 'top',
edit_url: '',
edit_title: '',
visibility: true
}, args);
// vars
var $postbox = $('#' + args.id),
$toggle = $('#' + args.id + '-hide'),
$label = $toggle.parent();
// add class
$postbox.addClass('acf-postbox');
$label.addClass('acf-postbox-toggle');
// remove class
$postbox.removeClass('hide-if-js');
$label.removeClass('hide-if-js');
// field group style
if( args.style !== 'default' ) {
$postbox.addClass( args.style );
}
// .inside class
$postbox.children('.inside').addClass('acf-fields').addClass('-' + args.label);
// visibility
if( args.visibility ) {
$toggle.prop('checked', true);
} else {
$postbox.addClass('acf-hidden');
$label.addClass('acf-hidden');
}
// edit_url
if( args.edit_url ) {
$postbox.children('.hndle').append('');
}
}
});
/*
* Sortable
*
* These functions will hook into the start and stop of a jQuery sortable event and modify the item and placeholder
*
* @type function
* @date 12/11/2013
* @since 5.0.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
acf.add_action('sortstart', function( $item, $placeholder ){
// if $item is a tr, apply some css to the elements
if( $item.is('tr') ) {
// temp set as relative to find widths
$item.css('position', 'relative');
// set widths for td children
$item.children().each(function(){
$(this).width($(this).width());
});
// revert position css
$item.css('position', 'absolute');
// add markup to the placeholder
$placeholder.html('
');
}
});
/*
* before & after duplicate
*
* This function will modify the DOM before it is cloned. Primarily fixes a cloning issue with select elements
*
* @type function
* @date 16/05/2014
* @since 5.0.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
acf.add_action('before_duplicate', function( $orig ){
// add 'selected' class
$orig.find('select option:selected').addClass('selected');
});
acf.add_action('after_duplicate', function( $orig, $duplicate ){
// set select values
$duplicate.find('select').each(function(){
// vars
var $select = $(this);
// bail early if is 'Stylized UI'
//if( $select.data('ui') ) return;
// vars
var val = [];
// loop
$select.find('option.selected').each(function(){
val.push( $(this).val() );
});
// set val
$select.val( val );
});
// remove 'selected' class
$orig.find('select option.selected').removeClass('selected');
$duplicate.find('select option.selected').removeClass('selected');
});
/*
acf.test_rtl = acf.model.extend({
actions: {
'ready': 'ready',
},
ready: function(){
$('html').attr('dir', 'rtl');
}
});
*/
/*
console.time("acf_test_ready");
console.time("acf_test_load");
acf.add_action('ready', function(){
console.timeEnd("acf_test_ready");
}, 999);
acf.add_action('load', function(){
console.timeEnd("acf_test_load");
}, 999);
*/
/*
* indexOf
*
* This function will provide compatibility for ie8
*
* @type function
* @date 5/3/17
* @since 5.5.10
*
* @param n/a
* @return n/a
*/
if( !Array.prototype.indexOf ) {
Array.prototype.indexOf = function(val) {
return $.inArray(val, this);
};
}
})(jQuery);
(function($){
acf.ajax = acf.model.extend({
active: false,
actions: {
'ready': 'ready'
},
events: {
'change #page_template': '_change_template',
'change #parent_id': '_change_parent',
'change #post-formats-select input': '_change_format',
'change .categorychecklist input': '_change_term',
'change .categorychecklist select': '_change_term',
'change .acf-taxonomy-field[data-save="1"] input': '_change_term',
'change .acf-taxonomy-field[data-save="1"] select': '_change_term'
},
o: {
//'post_id': 0,
//'page_template': 0,
//'page_parent': 0,
//'page_type': 0,
//'post_format': 0,
//'post_taxonomy': 0
},
xhr: null,
update: function( k, v ){
this.o[ k ] = v;
return this;
},
get: function( k ){
return this.o[ k ] || null;
},
ready: function(){
// update post_id
this.update('post_id', acf.get('post_id'));
// active
this.active = true;
},
/*
timeout: null,
maybe_fetch: function(){
// reference
var self = this;
// abort timeout
if( this.timeout ) {
clearTimeout( this.timeout );
}
// fetch
this.timeout = setTimeout(function(){
self.fetch();
}, 100);
},
*/
fetch: function(){
// bail early if not active
if( !this.active ) return;
// bail early if no ajax
if( !acf.get('ajax') ) return;
// abort XHR if is already loading AJAX data
if( this.xhr ) {
this.xhr.abort();
}
// vars
var self = this,
data = this.o;
// add action url
data.action = 'acf/post/get_field_groups';
// add ignore
data.exists = [];
$('.acf-postbox').not('.acf-hidden').each(function(){
data.exists.push( $(this).attr('id').substr(4) );
});
// ajax
this.xhr = $.ajax({
url: acf.get('ajaxurl'),
data: acf.prepare_for_ajax( data ),
type: 'post',
dataType: 'json',
success: function( json ){
if( acf.is_ajax_success( json ) ) {
self.render( json.data );
}
}
});
},
render: function( json ){
// hide
$('.acf-postbox').addClass('acf-hidden');
$('.acf-postbox-toggle').addClass('acf-hidden');
// reset style
$('#acf-style').html('');
// show the new postboxes
$.each(json, function( k, field_group ){
// vars
var $postbox = $('#acf-' + field_group.key),
$toggle = $('#acf-' + field_group.key + '-hide'),
$label = $toggle.parent();
// show
// use show() to force display when postbox has been hidden by 'Show on screen' toggle
$postbox.removeClass('acf-hidden hide-if-js').show();
$label.removeClass('acf-hidden hide-if-js').show();
$toggle.prop('checked', true);
// replace HTML if needed
var $replace = $postbox.find('.acf-replace-with-fields');
if( $replace.exists() ) {
$replace.replaceWith( field_group.html );
acf.do_action('append', $postbox);
}
// update style if needed
if( k === 0 ) {
$('#acf-style').html( field_group.style );
}
// enable inputs
$postbox.find('.acf-hidden-by-postbox').prop('disabled', false);
});
// disable inputs
$('.acf-postbox.acf-hidden').find('select, textarea, input').not(':disabled').each(function(){
$(this).addClass('acf-hidden-by-postbox').prop('disabled', true);
});
},
sync_taxonomy_terms: function(){
// vars
var values = [''];
// loop over term lists
$('.categorychecklist, .acf-taxonomy-field').each(function(){
// vars
var $el = $(this),
$checkbox = $el.find('input[type="checkbox"]').not(':disabled'),
$radio = $el.find('input[type="radio"]').not(':disabled'),
$select = $el.find('select').not(':disabled'),
$hidden = $el.find('input[type="hidden"]').not(':disabled');
// bail early if not a field which saves taxonomy terms to post
if( $el.is('.acf-taxonomy-field') && $el.attr('data-save') != '1' ) {
return;
}
// bail early if in attachment
if( $el.closest('.media-frame').exists() ) {
return;
}
// checkbox
if( $checkbox.exists() ) {
$checkbox.filter(':checked').each(function(){
values.push( $(this).val() );
});
} else if( $radio.exists() ) {
$radio.filter(':checked').each(function(){
values.push( $(this).val() );
});
} else if( $select.exists() ) {
$select.find('option:selected').each(function(){
values.push( $(this).val() );
});
} else if( $hidden.exists() ) {
$hidden.each(function(){
// ignor blank values
if( ! $(this).val() ) {
return;
}
values.push( $(this).val() );
});
}
});
// filter duplicates
values = values.filter (function (v, i, a) { return a.indexOf (v) == i });
// update screen
this.update( 'post_taxonomy', values ).fetch();
},
/*
* events
*
* description
*
* @type function
* @date 29/09/2015
* @since 5.2.3
*
* @param $post_id (int)
* @return $post_id (int)
*/
_change_template: function( e ){
// vars
var page_template = e.$el.val();
// update & fetch
this.update('page_template', page_template).fetch();
},
_change_parent: function( e ){
// vars
var page_type = 'parent',
page_parent = 0;
// if is child
if( e.$el.val() != "" ) {
page_type = 'child';
page_parent = e.$el.val();
}
// update & fetch
this.update('page_type', page_type).update('page_parent', page_parent).fetch();
},
_change_format: function( e ){
// vars
var post_format = e.$el.val();
// default
if( post_format == '0' ) {
post_format = 'standard';
}
// update & fetch
this.update('post_format', post_format).fetch();
},
_change_term: function( e ){
// reference
var self = this;
// bail early if within media popup
if( e.$el.closest('.media-frame').exists() ) {
return;
}
// set timeout to fix issue with chrome which does not register the change has yet happened
setTimeout(function(){
self.sync_taxonomy_terms();
}, 1);
}
});
})(jQuery);
(function($){
acf.fields.checkbox = acf.field.extend({
type: 'checkbox',
events: {
'change input': '_change',
'click .acf-add-checkbox': '_add'
},
/*
* focus
*
* This function will setup variables when focused on a field
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param n/a
* @return n/a
*/
focus: function(){
// get elements
this.$ul = this.$field.find('ul');
this.$input = this.$field.find('input[type="hidden"]');
},
add: function(){
// vars
var name = this.$input.attr('name') + '[]';
// vars
var html = '';
// append
this.$ul.find('.acf-add-checkbox').parent('li').before( html );
},
_change: function( e ){
// vars
var $ul = this.$ul,
$inputs = $ul.find('input[type="checkbox"]').not('.acf-checkbox-toggle'),
checked = e.$el.is(':checked');
// is toggle?
if( e.$el.hasClass('acf-checkbox-toggle') ) {
// toggle all
$inputs.prop('checked', checked).trigger('change');
// return
return;
}
// is custom
if( e.$el.hasClass('acf-checkbox-custom') ) {
// vars
var $text = e.$el.next('input[type="text"]');
// toggle disabled
e.$el.next('input[type="text"]').prop('disabled', !checked);
// remove complelety if no value
if( !checked && $text.val() == '' ) {
e.$el.parent('li').remove();
}
}
// bail early if no toggle
if( !$ul.find('.acf-checkbox-toggle').exists() ) {
return;
}
// determine if all inputs are checked
var checked = ( $inputs.not(':checked').length == 0 );
// update toggle
$ul.find('.acf-checkbox-toggle').prop('checked', checked);
},
_add: function( e ){
this.add();
}
});
})(jQuery);
(function($){
acf.fields.color_picker = acf.field.extend({
type: 'color_picker',
$input: null,
$hidden: null,
actions: {
'ready': 'initialize',
'append': 'initialize'
},
focus: function(){
this.$input = this.$field.find('input[type="text"]');
this.$hidden = this.$field.find('input[type="hidden"]');
},
initialize: function(){
// reference
var $input = this.$input,
$hidden = this.$hidden;
// trigger change function
var change_hidden = function(){
// timeout is required to ensure the $input val is correct
setTimeout(function(){
acf.val( $hidden, $input.val() );
}, 1);
}
// args
var args = {
defaultColor: false,
palettes: true,
hide: true,
change: change_hidden,
clear: change_hidden
}
// filter
var args = acf.apply_filters('color_picker_args', args, this.$field);
// iris
this.$input.wpColorPicker(args);
}
});
})(jQuery);
(function($){
acf.conditional_logic = acf.model.extend({
actions: {
'prepare 20': 'render',
'append 20': 'render'
},
events: {
'change .acf-field input': 'change',
'change .acf-field textarea': 'change',
'change .acf-field select': 'change'
},
items: {},
triggers: {},
/*
* add
*
* This function will add a set of conditional logic rules
*
* @type function
* @date 22/05/2015
* @since 5.2.3
*
* @param target (string) target field key
* @param groups (array) rule groups
* @return $post_id (int)
*/
add: function( target, groups ){
// debug
//console.log( 'conditional_logic.add(%o, %o)', target, groups );
// populate triggers
for( var i in groups ) {
// vars
var group = groups[i];
for( var k in group ) {
// vars
var rule = group[k],
trigger = rule.field,
triggers = this.triggers[ trigger ] || {};
// append trigger (sub field will simply override)
triggers[ target ] = target;
// update
this.triggers[ trigger ] = triggers;
}
}
// append items
this.items[ target ] = groups;
},
/*
* render
*
* This function will render all fields
*
* @type function
* @date 22/05/2015
* @since 5.2.3
*
* @param $post_id (int)
* @return $post_id (int)
*/
render: function( $el ){
// debug
//console.log('conditional_logic.render(%o)', $el);
// defaults
$el = $el || false;
// get targets
var $targets = acf.get_fields( '', $el, true );
// render fields
this.render_fields( $targets );
// action for 3rd party customization
acf.do_action('refresh', $el);
},
/*
* change
*
* This function is called when an input is changed and will render any fields which are considered targets of this trigger
*
* @type function
* @date 22/05/2015
* @since 5.2.3
*
* @param $post_id (int)
* @return $post_id (int)
*/
change: function( e ){
// debug
//console.log( 'conditional_logic.change(%o)', $input );
// vars
var $input = e.$el,
$field = acf.get_field_wrap( $input ),
key = $field.data('key');
// bail early if this field does not trigger any actions
if( typeof this.triggers[key] === 'undefined' ) {
return false;
}
// vars
$parent = $field.parent();
// update visibility
for( var i in this.triggers[ key ] ) {
// get the target key
var target_key = this.triggers[ key ][ i ];
// get targets
var $targets = acf.get_fields(target_key, $parent, true);
// render
this.render_fields( $targets );
}
// action for 3rd party customization
acf.do_action('refresh', $parent);
},
/*
* render_fields
*
* This function will render a selection of fields
*
* @type function
* @date 22/05/2015
* @since 5.2.3
*
* @param $post_id (int)
* @return $post_id (int)
*/
render_fields: function( $targets ) {
// reference
var self = this;
// loop over targets and render them
$targets.each(function(){
self.render_field( $(this) );
});
},
/*
* render_field
*
* This function will render a field
*
* @type function
* @date 22/05/2015
* @since 5.2.3
*
* @param $post_id (int)
* @return $post_id (int)
*/
render_field : function( $target ){
// vars
var key = $target.data('key');
// bail early if this field does not contain any conditional logic
if( typeof this.items[ key ] === 'undefined' ) {
return false;
}
// vars
var visibility = false;
// debug
//console.log( 'conditional_logic.render_field(%o)', $field );
// get conditional logic
var groups = this.items[ key ];
// calculate visibility
for( var i = 0; i < groups.length; i++ ) {
// vars
var group = groups[i],
match_group = true;
for( var k = 0; k < group.length; k++ ) {
// vars
var rule = group[k];
// get trigger for rule
var $trigger = this.get_trigger( $target, rule.field );
// break if rule did not validate
if( !this.calculate(rule, $trigger, $target) ) {
match_group = false;
break;
}
}
// set visibility if rule group did validate
if( match_group ) {
visibility = true;
break;
}
}
// hide / show field
if( visibility ) {
this.show_field( $target );
} else {
this.hide_field( $target );
}
},
/*
* show_field
*
* This function will show a field
*
* @type function
* @date 22/05/2015
* @since 5.2.3
*
* @param $post_id (int)
* @return $post_id (int)
*/
show_field: function( $field ){
// debug
//console.log('show_field(%o)', $field);
// vars
var key = $field.data('key');
// remove class
$field.removeClass( 'hidden-by-conditional-logic' );
// enable
acf.enable_form( $field, 'condition_'+key );
// action for 3rd party customization
acf.do_action('show_field', $field, 'conditional_logic' );
},
/*
* hide_field
*
* This function will hide a field
*
* @type function
* @date 22/05/2015
* @since 5.2.3
*
* @param $post_id (int)
* @return $post_id (int)
*/
hide_field : function( $field ){
// debug
//console.log('hide_field(%o)', $field);
// vars
var key = $field.data('key');
// add class
$field.addClass( 'hidden-by-conditional-logic' );
// disable
acf.disable_form( $field, 'condition_'+key );
// action for 3rd party customization
acf.do_action('hide_field', $field, 'conditional_logic' );
},
/*
* get_trigger
*
* This function will return the relevant $trigger for a $target
*
* @type function
* @date 22/05/2015
* @since 5.2.3
*
* @param $post_id (int)
* @return $post_id (int)
*/
get_trigger: function( $target, key ){
// vars
var selector = acf.get_selector( key );
// find sibling $trigger
var $trigger = $target.siblings( selector );
// parent trigger
if( !$trigger.exists() ) {
// vars
var parent = acf.get_selector();
// loop through parent fields and review their siblings too
$target.parents( parent ).each(function(){
// find sibling $trigger
$trigger = $(this).siblings( selector );
// bail early if $trigger is found
if( $trigger.exists() ) {
return false;
}
});
}
// bail early if no $trigger is found
if( !$trigger.exists() ) {
return false;
}
// return
return $trigger;
},
/*
* calculate
*
* This function will calculate if a rule matches based on the $trigger
*
* @type function
* @date 22/05/2015
* @since 5.2.3
*
* @param $post_id (int)
* @return $post_id (int)
*/
calculate : function( rule, $trigger, $target ){
// bail early if $trigger could not be found
if( !$trigger || !$target ) return false;
// debug
//console.log( 'calculate(%o, %o, %o)', rule, $trigger, $target);
// vars
var match = false,
type = $trigger.data('type');
// input with :checked
if( type == 'true_false' || type == 'checkbox' || type == 'radio' ) {
match = this.calculate_checkbox( rule, $trigger );
} else if( type == 'select' ) {
match = this.calculate_select( rule, $trigger );
}
// reverse if 'not equal to'
if( rule.operator === "!=" ) {
match = !match;
}
// return
return match;
},
calculate_checkbox: function( rule, $trigger ){
// look for selected input
var match = $trigger.find('input[value="' + rule.value + '"]:checked').exists();
// override for "allow null"
if( rule.value === '' && !$trigger.find('input:checked').exists() ) {
match = true;
}
// return
return match;
},
calculate_select: function( rule, $trigger ){
// vars
var $select = $trigger.find('select'),
val = $select.val();
// check for no value
if( !val && !$.isNumeric(val) ) {
val = '';
}
// convert to array
if( !$.isArray(val) ) {
val = [ val ];
}
// calc
match = ($.inArray(rule.value, val) > -1);
// return
return match;
}
});
})(jQuery);
(function($){
/*
* acf.datepicker
*
* description
*
* @type function
* @date 16/12/2015
* @since 5.3.2
*
* @param $post_id (int)
* @return $post_id (int)
*/
acf.datepicker = acf.model.extend({
actions: {
'ready 1': 'ready'
},
ready: function(){
// vars
var locale = acf.get('locale'),
rtl = acf.get('rtl')
l10n = acf._e('date_picker');
// bail ealry if no l10n (fiedl groups admin page)
if( !l10n ) return;
// bail ealry if no datepicker library
if( typeof $.datepicker === 'undefined' ) return;
// rtl
l10n.isRTL = rtl;
// append
$.datepicker.regional[ locale ] = l10n;
$.datepicker.setDefaults(l10n);
},
/*
* init
*
* This function will initialize JS
*
* @type function
* @date 2/06/2016
* @since 5.3.8
*
* @param $input (jQuery selector)
* @param args (object)
* @return n/a
*/
init: function( $input, args ){
// bail ealry if no datepicker library
if( typeof $.datepicker === 'undefined' ) return;
// defaults
args = args || {};
// add date picker
$input.datepicker( args );
// wrap the datepicker (only if it hasn't already been wrapped)
if( $('body > #ui-datepicker-div').exists() ) {
$('body > #ui-datepicker-div').wrap('');
}
},
/*
* init
*
* This function will remove JS
*
* @type function
* @date 2/06/2016
* @since 5.3.8
*
* @param $input (jQuery selector)
* @return n/a
*/
destroy: function( $input ){
// do nothing
}
});
acf.fields.date_picker = acf.field.extend({
type: 'date_picker',
$el: null,
$input: null,
$hidden: null,
o: {},
actions: {
'ready': 'initialize',
'append': 'initialize'
},
events: {
'blur input[type="text"]': 'blur'
},
focus: function(){
// get elements
this.$el = this.$field.find('.acf-date-picker');
this.$input = this.$el.find('input[type="text"]');
this.$hidden = this.$el.find('input[type="hidden"]');
// get options
this.o = acf.get_data( this.$el );
},
initialize: function(){
// save_format - compatibility with ACF < 5.0.0
if( this.o.save_format ) {
return this.initialize2();
}
// create options
var args = {
dateFormat: this.o.date_format,
altField: this.$hidden,
altFormat: 'yymmdd',
changeYear: true,
yearRange: "-100:+100",
changeMonth: true,
showButtonPanel: true,
firstDay: this.o.first_day
};
// filter for 3rd party customization
args = acf.apply_filters('date_picker_args', args, this.$field);
// add date picker
acf.datepicker.init( this.$input, args );
// action for 3rd party customization
acf.do_action('date_picker_init', this.$input, args, this.$field);
},
initialize2: function(){
// get and set value from alt field
this.$input.val( this.$hidden.val() );
// create options
var args = {
dateFormat: this.o.date_format,
altField: this.$hidden,
altFormat: this.o.save_format,
changeYear: true,
yearRange: "-100:+100",
changeMonth: true,
showButtonPanel: true,
firstDay: this.o.first_day
};
// filter for 3rd party customization
args = acf.apply_filters('date_picker_args', args, this.$field);
// backup
var dateFormat = args.dateFormat;
// change args.dateFormat
args.dateFormat = this.o.save_format;
// add date picker
acf.datepicker.init( this.$input, args );
// now change the format back to how it should be.
this.$input.datepicker( 'option', 'dateFormat', dateFormat );
// action for 3rd party customization
acf.do_action('date_picker_init', this.$input, args, this.$field);
},
blur: function(){
if( !this.$input.val() ) {
this.$hidden.val('');
}
}
});
})(jQuery);
(function($){
/*
* acf.datepicker
*
* description
*
* @type function
* @date 16/12/2015
* @since 5.3.2
*
* @param $post_id (int)
* @return $post_id (int)
*/
acf.datetimepicker = acf.model.extend({
actions: {
'ready 1': 'ready'
},
ready: function(){
// vars
var locale = acf.get('locale'),
rtl = acf.get('rtl')
l10n = acf._e('date_time_picker');
// bail ealry if no l10n (fiedl groups admin page)
if( !l10n ) return;
// bail ealry if no timepicker library
if( typeof $.timepicker === 'undefined' ) return;
// rtl
l10n.isRTL = rtl;
// append
$.timepicker.regional[ locale ] = l10n;
$.timepicker.setDefaults(l10n);
},
/*
* init
*
* This function will initialize JS
*
* @type function
* @date 2/06/2016
* @since 5.3.8
*
* @param $input (jQuery selector)
* @param args (object)
* @return n/a
*/
init: function( $input, args ){
// bail ealry if no timepicker library
if( typeof $.timepicker === 'undefined' ) return;
// defaults
args = args || {};
// add date picker
$input.datetimepicker( args );
// wrap the datepicker (only if it hasn't already been wrapped)
if( $('body > #ui-datepicker-div').exists() ) {
$('body > #ui-datepicker-div').wrap('');
}
},
/*
* init
*
* This function will remove JS
*
* @type function
* @date 2/06/2016
* @since 5.3.8
*
* @param $input (jQuery selector)
* @return n/a
*/
destroy: function( $input ){
// do nothing
}
});
acf.fields.date_time_picker = acf.field.extend({
type: 'date_time_picker',
$el: null,
$input: null,
$hidden: null,
o: {},
actions: {
'ready': 'initialize',
'append': 'initialize'
},
events: {
'blur input[type="text"]': 'blur'
},
focus: function(){
// get elements
this.$el = this.$field.find('.acf-date-time-picker');
this.$input = this.$el.find('input[type="text"]');
this.$hidden = this.$el.find('input[type="hidden"]');
// get options
this.o = acf.get_data( this.$el );
},
initialize: function(){
// create options
var args = {
dateFormat: this.o.date_format,
timeFormat: this.o.time_format,
altField: this.$hidden,
altFieldTimeOnly: false,
altFormat: 'yy-mm-dd',
altTimeFormat: 'HH:mm:ss',
changeYear: true,
yearRange: "-100:+100",
changeMonth: true,
showButtonPanel: true,
firstDay: this.o.first_day,
controlType: 'select',
oneLine: true
};
// filter for 3rd party customization
args = acf.apply_filters('date_time_picker_args', args, this.$field);
// add date time picker
acf.datetimepicker.init( this.$input, args );
// action for 3rd party customization
acf.do_action('date_time_picker_init', this.$input, args, this.$field);
},
blur: function(){
if( !this.$input.val() ) {
this.$hidden.val('');
}
}
});
})(jQuery);
(function($){
acf.fields.file = acf.field.extend({
type: 'file',
$el: null,
$input: null,
actions: {
'ready': 'initialize',
'append': 'initialize'
},
events: {
'click a[data-name="add"]': 'add',
'click a[data-name="edit"]': 'edit',
'click a[data-name="remove"]': 'remove',
'change input[type="file"]': 'change'
},
/*
* focus
*
* This function will setup variables when focused on a field
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param n/a
* @return n/a
*/
focus: function(){
// get elements
this.$el = this.$field.find('.acf-file-uploader');
this.$input = this.$el.find('input[type="hidden"]');
// get options
this.o = acf.get_data( this.$el );
},
/*
* initialize
*
* This function is used to setup basic upload form attributes
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param n/a
* @return n/a
*/
initialize: function(){
// add attribute to form
if( this.o.uploader == 'basic' ) {
this.$el.closest('form').attr('enctype', 'multipart/form-data');
}
},
/*
* prepare
*
* This function will prepare an object of attachment data
* selecting a library image vs embed an image via url return different data
* this function will keep the 2 consistent
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param attachment (object)
* @return data (object)
*/
prepare: function( attachment ) {
// defaults
attachment = attachment || {};
// bail ealry if already valid
if( attachment._valid ) return attachment;
// vars
var data = {
url: '',
alt: '',
title: '',
filename: '',
filesizeHumanReadable: '',
icon: '/wp-includes/images/media/default.png'
};
// wp image
if( attachment.id ) {
// update data
data = attachment.attributes;
}
// valid
data._valid = true;
// return
return data;
},
/*
* render
*
* This function will render the UI
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param attachment (obj)
* @return n/a
*/
render: function( data ){
// prepare
data = this.prepare(data);
// update els
this.$el.find('img').attr({
src: data.icon,
alt: data.alt,
title: data.title
});
this.$el.find('[data-name="title"]').text( data.title );
this.$el.find('[data-name="filename"]').text( data.filename ).attr( 'href', data.url );
this.$el.find('[data-name="filesize"]').text( data.filesizeHumanReadable );
// vars
var val = '';
// WP attachment
if( data.id ) {
val = data.id;
}
// update val
acf.val( this.$input, val );
// update class
if( val ) {
this.$el.addClass('has-value');
} else {
this.$el.removeClass('has-value');
}
},
/*
* add
*
* event listener
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param e (event)
* @return n/a
*/
add: function() {
// reference
var self = this,
$field = this.$field;
// get repeater
var $repeater = acf.get_closest_field( $field, 'repeater' );
// popup
var frame = acf.media.popup({
title: acf._e('file', 'select'),
mode: 'select',
type: '',
field: $field.data('key'),
multiple: $repeater.exists(),
library: this.o.library,
mime_types: this.o.mime_types,
select: function( attachment, i ) {
// select / add another image field?
if( i > 0 ) {
// vars
var key = $field.data('key'),
$tr = $field.closest('.acf-row');
// reset field
$field = false;
// find next image field
$tr.nextAll('.acf-row:visible').each(function(){
// get next $field
$field = acf.get_field( key, $(this) );
// bail early if $next was not found
if( !$field ) return;
// bail early if next file uploader has value
if( $field.find('.acf-file-uploader.has-value').exists() ) {
$field = false;
return;
}
// end loop if $next is found
return false;
});
// add extra row if next is not found
if( !$field ) {
$tr = acf.fields.repeater.doFocus( $repeater ).add();
// bail early if no $tr (maximum rows hit)
if( !$tr ) return false;
// get next $field
$field = acf.get_field( key, $tr );
}
}
// render
self.set('$field', $field).render( attachment );
}
});
},
/*
* edit
*
* event listener
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param e (event)
* @return n/a
*/
edit: function() {
// reference
var self = this,
$field = this.$field;
// vars
var val = this.$input.val();
// bail early if no val
if( !val ) return;
// popup
var frame = acf.media.popup({
title: acf._e('file', 'edit'),
button: acf._e('file', 'update'),
mode: 'edit',
attachment: val,
select: function( attachment, i ) {
// render
self.set('$field', $field).render( attachment );
}
});
},
/*
* remove
*
* event listener
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param e (event)
* @return n/a
*/
remove: function() {
// vars
var attachment = {};
// add file to field
this.render( attachment );
},
/*
* get_file_info
*
* This function will find basic file info and store it in a hidden input
*
* @type function
* @date 18/1/17
* @since 5.5.0
*
* @param $file_input (jQuery)
* @param $hidden_input (jQuery)
* @return n/a
*/
get_file_info: function( $file_input, $hidden_input ){
// vars
var val = $file_input.val(),
attachment = {};
// bail early if no value
if( !val ) {
$hidden_input.val('');
return;
}
// url
attachment.url = val;
// modern browsers
var files = $file_input[0].files;
if( files.length ){
// vars
var file = files[0];
// update
attachment.size = file.size;
attachment.type = file.type;
// image
if( file.type.indexOf('image') > -1 ) {
// vars
var _url = window.URL || window.webkitURL;
// temp image
var img = new Image();
img.onload = function () {
// update
attachment.width = this.width;
attachment.height = this.height;
// set hidden input value
$hidden_input.val( jQuery.param(attachment) );
};
img.src = _url.createObjectURL(file);
}
}
// set hidden input value
$hidden_input.val( jQuery.param(attachment) );
},
/*
* change
*
* This function will update the hidden input when selecting a basic file to add basic validation
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param e (event)
* @return n/a
*/
change: function( e ){
this.get_file_info( e.$el, this.$input );
}
});
})(jQuery);
(function($){
acf.fields.google_map = acf.field.extend({
type: 'google_map',
url: '',
$el: null,
$search: null,
timeout: null,
status : '', // '', 'loading', 'ready'
geocoder : false,
map : false,
maps : {},
$pending: $(),
actions: {
// have considered changing to 'load', however, could cause issues with devs expecting the API to exist earlier
'ready': 'initialize',
'append': 'initialize',
'show': 'show'
},
events: {
'click a[data-name="clear"]': '_clear',
'click a[data-name="locate"]': '_locate',
'click a[data-name="search"]': '_search',
'keydown .search': '_keydown',
'keyup .search': '_keyup',
'focus .search': '_focus',
'blur .search': '_blur',
//'paste .search': '_paste',
'mousedown .acf-google-map': '_mousedown'
},
focus: function(){
// get elements
this.$el = this.$field.find('.acf-google-map');
this.$search = this.$el.find('.search');
// get options
this.o = acf.get_data( this.$el );
this.o.id = this.$el.attr('id');
// get map
if( this.maps[ this.o.id ] ) {
this.map = this.maps[ this.o.id ];
}
},
/*
* is_ready
*
* This function will ensure google API is available and return a boolean for the current status
*
* @type function
* @date 19/11/2014
* @since 5.0.9
*
* @param n/a
* @return (boolean)
*/
is_ready: function(){
// reference
var self = this;
// ready
if( this.status == 'ready' ) return true;
// loading
if( this.status == 'loading' ) return false;
// check exists (optimal)
if( acf.isset(window, 'google', 'maps', 'places') ) {
this.status = 'ready';
return true;
}
// check exists (ok)
if( acf.isset(window, 'google', 'maps') ) {
this.status = 'ready';
}
// attempt load google.maps.places
if( this.url ) {
// set status
this.status = 'loading';
// enqueue
acf.enqueue_script(this.url, function(){
// set status
self.status = 'ready';
// initialize pending
self.initialize_pending();
});
}
// ready
if( this.status == 'ready' ) return true;
// return
return false;
},
/*
* initialize_pending
*
* This function will initialize pending fields
*
* @type function
* @date 27/08/2016
* @since 5.4.0
*
* @param n/a
* @return n/a
*/
initialize_pending: function(){
// reference
var self = this;
this.$pending.each(function(){
self.set('$field', $(this)).initialize();
});
// reset
this.$pending = $();
},
/*
* actions
*
* these functions are fired for this fields actions
*
* @type function
* @date 17/09/2015
* @since 5.2.3
*
* @param (mixed)
* @return n/a
*/
initialize: function(){
// add to pending
if( !this.is_ready() ) {
this.$pending = this.$pending.add( this.$field );
return false;
}
// load geocode
if( !this.geocoder ) {
this.geocoder = new google.maps.Geocoder();
}
// reference
var self = this,
$field = this.$field,
$el = this.$el,
$search = this.$search;
// input value may be cached by browser, so update the search input to match
$search.val( this.$el.find('.input-address').val() );
// map
var map_args = acf.apply_filters('google_map_args', {
scrollwheel: false,
zoom: parseInt(this.o.zoom),
center: new google.maps.LatLng(this.o.lat, this.o.lng),
mapTypeId: google.maps.MapTypeId.ROADMAP
}, this.$field);
// create map
this.map = new google.maps.Map( this.$el.find('.canvas')[0], map_args);
// search
if( acf.isset(window, 'google', 'maps', 'places', 'Autocomplete') ) {
// vars
var autocomplete = new google.maps.places.Autocomplete( this.$search[0] );
// bind
autocomplete.bindTo('bounds', this.map);
// event
google.maps.event.addListener(autocomplete, 'place_changed', function( e ) {
// vars
var place = this.getPlace();
// search
self.search( place );
});
// append
this.map.autocomplete = autocomplete;
}
// marker
var marker_args = acf.apply_filters('google_map_marker_args', {
draggable: true,
raiseOnDrag: true,
map: this.map
}, this.$field);
// add marker
this.map.marker = new google.maps.Marker( marker_args );
// add references
this.map.$el = $el;
this.map.$field = $field;
// value exists?
var lat = $el.find('.input-lat').val(),
lng = $el.find('.input-lng').val();
if( lat && lng ) {
this.update(lat, lng).center();
}
// events
google.maps.event.addListener( this.map.marker, 'dragend', function(){
// vars
var position = this.map.marker.getPosition(),
lat = position.lat(),
lng = position.lng();
self.update( lat, lng ).sync();
});
google.maps.event.addListener( this.map, 'click', function( e ) {
// vars
var lat = e.latLng.lat(),
lng = e.latLng.lng();
self.update( lat, lng ).sync();
});
// action for 3rd party customization
acf.do_action('google_map_init', this.map, this.map.marker, this.$field);
// add to maps
this.maps[ this.o.id ] = this.map;
},
search: function( place ){
// reference
var self = this;
// vars
var address = this.$search.val();
// bail ealry if no address
if( !address ) {
return false;
}
// update input
this.$el.find('.input-address').val( address );
// is lat lng?
var latLng = address.split(',');
if( latLng.length == 2 ) {
var lat = latLng[0],
lng = latLng[1];
if( $.isNumeric(lat) && $.isNumeric(lng) ) {
// parse
lat = parseFloat(lat);
lng = parseFloat(lng);
self.update( lat, lng ).center();
return;
}
}
// if place exists
if( place && place.geometry ) {
var lat = place.geometry.location.lat(),
lng = place.geometry.location.lng();
// update
self.update( lat, lng ).center();
// bail early
return;
}
// add class
this.$el.addClass('-loading');
self.geocoder.geocode({ 'address' : address }, function( results, status ){
// remove class
self.$el.removeClass('-loading');
// validate
if( status != google.maps.GeocoderStatus.OK ) {
console.log('Geocoder failed due to: ' + status);
return;
} else if( !results[0] ) {
console.log('No results found');
return;
}
// get place
place = results[0];
var lat = place.geometry.location.lat(),
lng = place.geometry.location.lng();
self.update( lat, lng ).center();
});
},
update: function( lat, lng ){
// vars
var latlng = new google.maps.LatLng( lat, lng );
// update inputs
acf.val( this.$el.find('.input-lat'), lat );
acf.val( this.$el.find('.input-lng'), lng );
// update marker
this.map.marker.setPosition( latlng );
// show marker
this.map.marker.setVisible( true );
// update class
this.$el.addClass('-value');
// validation
this.$field.removeClass('error');
// action
acf.do_action('google_map_change', latlng, this.map, this.$field);
// blur input
this.$search.blur();
// return for chaining
return this;
},
center: function(){
// vars
var position = this.map.marker.getPosition(),
lat = this.o.lat,
lng = this.o.lng;
// if marker exists, center on the marker
if( position ) {
lat = position.lat();
lng = position.lng();
}
var latlng = new google.maps.LatLng( lat, lng );
// set center of map
this.map.setCenter( latlng );
},
sync: function(){
// reference
var self = this;
// vars
var position = this.map.marker.getPosition(),
latlng = new google.maps.LatLng( position.lat(), position.lng() );
// add class
this.$el.addClass('-loading');
// load
this.geocoder.geocode({ 'latLng' : latlng }, function( results, status ){
// remove class
self.$el.removeClass('-loading');
// validate
if( status != google.maps.GeocoderStatus.OK ) {
console.log('Geocoder failed due to: ' + status);
return;
} else if( !results[0] ) {
console.log('No results found');
return;
}
// get location
var location = results[0];
// update title
self.$search.val( location.formatted_address );
// update input
acf.val( self.$el.find('.input-address'), location.formatted_address );
});
// return for chaining
return this;
},
refresh: function(){
// bail early if not ready
if( !this.is_ready() ) {
return false;
}
// trigger resize on map
google.maps.event.trigger(this.map, 'resize');
// center map
this.center();
},
show: function(){
// vars
var self = this,
$field = this.$field;
// center map when it is shown (by a tab / collapsed row)
// - use delay to avoid rendering issues with browsers (ensures div is visible)
setTimeout(function(){
self.set('$field', $field).refresh();
}, 10);
},
/*
* events
*
* these functions are fired for this fields events
*
* @type function
* @date 17/09/2015
* @since 5.2.3
*
* @param e
* @return n/a
*/
_clear: function( e ){ // console.log('_clear');
// remove Class
this.$el.removeClass('-value -loading -search');
// clear search
this.$search.val('');
// clear inputs
acf.val( this.$el.find('.input-address'), '' );
acf.val( this.$el.find('.input-lat'), '' );
acf.val( this.$el.find('.input-lng'), '' );
// hide marker
this.map.marker.setVisible( false );
},
_locate: function( e ){ // console.log('_locate');
// reference
var self = this;
// Try HTML5 geolocation
if( !navigator.geolocation ) {
alert( acf._e('google_map', 'browser_support') );
return this;
}
// add class
this.$el.addClass('-loading');
// load
navigator.geolocation.getCurrentPosition(function(position){
// remove class
self.$el.removeClass('-loading');
// vars
var lat = position.coords.latitude,
lng = position.coords.longitude;
self.update( lat, lng ).sync().center();
});
},
_search: function( e ){ // console.log('_search');
this.search();
},
_focus: function( e ){ // console.log('_focus');
// remove class
this.$el.removeClass('-value');
// toggle -search class
this._keyup();
},
_blur: function( e ){ // console.log('_blur');
// reference
var self = this;
// vars
var val = this.$el.find('.input-address').val();
// bail early if no val
if( !val ) {
return;
}
// revert search to hidden input value
this.timeout = setTimeout(function(){
self.$el.addClass('-value');
self.$search.val( val );
}, 100);
},
/*
_paste: function( e ){ console.log('_paste');
// reference
var $search = this.$search;
// blur search
$search.blur();
// clear timeout
this._mousedown(e);
// focus on input
setTimeout(function(){
$search.focus();
}, 1);
},
*/
_keydown: function( e ){ // console.log('_keydown');
// prevent form from submitting
if( e.which == 13 ) {
e.preventDefault();
}
},
_keyup: function( e ){ // console.log('_keyup');
// vars
var val = this.$search.val();
// toggle class
if( val ) {
this.$el.addClass('-search');
} else {
this.$el.removeClass('-search');
}
},
_mousedown: function( e ){ // console.log('_mousedown');
// reference
var self = this;
// clear timeout in 1ms (_mousedown will run before _blur)
setTimeout(function(){
clearTimeout( self.timeout );
}, 1);
}
});
})(jQuery);
(function($){
acf.fields.image = acf.field.extend({
type: 'image',
$el: null,
$input: null,
$img: null,
actions: {
'ready': 'initialize',
'append': 'initialize'
},
events: {
'click a[data-name="add"]': 'add',
'click a[data-name="edit"]': 'edit',
'click a[data-name="remove"]': 'remove',
'change input[type="file"]': 'change'
},
/*
* focus
*
* This function will setup variables when focused on a field
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param n/a
* @return n/a
*/
focus: function(){
// vars
this.$el = this.$field.find('.acf-image-uploader');
this.$input = this.$el.find('input[type="hidden"]');
this.$img = this.$el.find('img');
// options
this.o = acf.get_data( this.$el );
},
/*
* initialize
*
* This function is used to setup basic upload form attributes
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param n/a
* @return n/a
*/
initialize: function(){
// add attribute to form
if( this.o.uploader == 'basic' ) {
this.$el.closest('form').attr('enctype', 'multipart/form-data');
}
},
/*
* prepare
*
* This function will prepare an object of attachment data
* selecting a library image vs embed an image via url return different data
* this function will keep the 2 consistent
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param attachment (object)
* @return data (object)
*/
prepare: function( attachment ) {
// defaults
attachment = attachment || {};
// bail ealry if already valid
if( attachment._valid ) return attachment;
// vars
var data = {
url: '',
alt: '',
title: '',
caption: '',
description: '',
width: 0,
height: 0
};
// wp image
if( attachment.id ) {
// update data
data = attachment.attributes;
// maybe get preview size
data.url = acf.maybe_get(data, 'sizes.'+this.o.preview_size+'.url', data.url);
}
// valid
data._valid = true;
// return
return data;
},
/*
* render
*
* This function will render the UI
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param attachment (obj)
* @return n/a
*/
render: function( data ){
// prepare
data = this.prepare(data);
// update image
this.$img.attr({
src: data.url,
alt: data.alt,
title: data.title
});
// vars
var val = '';
// WP attachment
if( data.id ) {
val = data.id;
}
// update val
acf.val( this.$input, val );
// update class
if( val ) {
this.$el.addClass('has-value');
} else {
this.$el.removeClass('has-value');
}
},
/*
* add
*
* event listener
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param e (event)
* @return n/a
*/
add: function() {
// reference
var self = this,
$field = this.$field;
// get repeater
var $repeater = acf.get_closest_field( this.$field, 'repeater' );
// popup
var frame = acf.media.popup({
title: acf._e('image', 'select'),
mode: 'select',
type: 'image',
field: $field.data('key'),
multiple: $repeater.exists(),
library: this.o.library,
mime_types: this.o.mime_types,
select: function( attachment, i ) {
// select / add another image field?
if( i > 0 ) {
// vars
var key = $field.data('key'),
$tr = $field.closest('.acf-row');
// reset field
$field = false;
// find next image field
$tr.nextAll('.acf-row:visible').each(function(){
// get next $field
$field = acf.get_field( key, $(this) );
// bail early if $next was not found
if( !$field ) return;
// bail early if next file uploader has value
if( $field.find('.acf-image-uploader.has-value').exists() ) {
$field = false;
return;
}
// end loop if $next is found
return false;
});
// add extra row if next is not found
if( !$field ) {
$tr = acf.fields.repeater.doFocus( $repeater ).add();
// bail early if no $tr (maximum rows hit)
if( !$tr ) return false;
// get next $field
$field = acf.get_field( key, $tr );
}
}
// render
self.set('$field', $field).render( attachment );
}
});
},
/*
* edit
*
* event listener
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param e (event)
* @return n/a
*/
edit: function() {
// reference
var self = this,
$field = this.$field;
// vars
var val = this.$input.val();
// bail early if no val
if( !val ) return;
// popup
var frame = acf.media.popup({
title: acf._e('image', 'edit'),
button: acf._e('image', 'update'),
mode: 'edit',
attachment: val,
select: function( attachment, i ) {
// render
self.set('$field', $field).render( attachment );
}
});
},
/*
* remove
*
* event listener
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param e (event)
* @return n/a
*/
remove: function() {
// vars
var attachment = {};
// add file to field
this.render( attachment );
},
/*
* change
*
* This function will update the hidden input when selecting a basic file to add basic validation
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param e (event)
* @return n/a
*/
change: function( e ){
acf.fields.file.get_file_info( e.$el, this.$input );
}
});
})(jQuery);
(function($){
acf.fields.link = acf.field.extend({
type: 'link',
active: false,
$el: null,
$node: null,
events: {
'click a[data-name="add"]': 'add',
'click a[data-name="edit"]': 'edit',
'click a[data-name="remove"]': 'remove',
'change .link-node': 'change',
},
/*
* focus
*
* This function will setup variables when focused on a field
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param n/a
* @return n/a
*/
focus: function(){
// get elements
this.$el = this.$field.find('.acf-link');
this.$node = this.$el.find('.link-node');
},
add: function( e ){
acf.link.open( this.$node );
},
edit: function( e ){
this.add();
},
remove: function( e ){
this.val('');
},
change: function( e, value ){
// vars
var val = {
'title': this.$node.html(),
'url': this.$node.attr('href'),
'target': this.$node.attr('target')
};
// vars
this.val( val );
},
val: function( val ){
// default
val = acf.parse_args(val, {
'title': '',
'url': '',
'target': ''
});
// remove class
this.$el.removeClass('-value -external');
// add class
if( val.url ) this.$el.addClass('-value');
if( val.target === '_blank' ) this.$el.addClass('-external');
// update text
this.$el.find('.link-title').html( val.title );
this.$el.find('.link-url').attr('href', val.url).html( val.url );
// update inputs
this.$el.find('.input-title').val( val.title );
this.$el.find('.input-target').val( val.target );
this.$el.find('.input-url').val( val.url ).trigger('change');
// update node
this.$node.html(val.title);
this.$node.attr('href', val.url);
this.$node.attr('target', val.target);
}
});
/*
* acf.link
*
* This model will handle adding tabs and groups
*
* @type function
* @date 25/11/2015
* @since 5.3.2
*
* @param $post_id (int)
* @return $post_id (int)
*/
acf.link = acf.model.extend({
active: false,
$textarea: null,
$node: null,
events: {
'click #wp-link-submit': '_update',
//'river-select .query-results': '_select',
'wplink-open': '_open',
'wplink-close': '_close',
},
atts: function( value ){
// update
if( typeof value !== 'undefined' ) {
this.$node.html( value.title );
this.$node.attr('href', value.url);
this.$node.attr('target', value.target);
this.$node.trigger('change', [value]);
return true;
}
// get
return {
'title': this.$node.html(),
'url': this.$node.attr('href'),
'target': this.$node.attr('target')
};
},
inputs: function( value ){
// update
if( typeof value !== 'undefined' ) {
$('#wp-link-text').val( value.title );
$('#wp-link-url').val( value.url );
$('#wp-link-target').prop('checked', value.target === '_blank' );
return true;
}
// get
return {
'title': $('#wp-link-text').val(),
'url': $('#wp-link-url').val(),
'target': $('#wp-link-target').prop('checked') ? '_blank' : ''
};
},
open: function( $node ){
// create textarea
var $textarea = $('');
// append textarea
$node.before( $textarea );
// update vars
this.active = true;
this.$node = $node;
this.$textarea = $textarea;
// get atts
var atts = this.atts();
// open link
wpLink.open( 'acf-link-textarea', atts.url, atts.title, null );
// always show title (WP will hide title if empty)
$('#wp-link-wrap').addClass('has-text-field');
},
reset: function(){
this.active = false;
this.$textarea.remove();
this.$textarea = null;
this.$node = null;
},
_select: function( e, $li ){
// get inputs
var val = this.inputs();
// update title
if( !val.title ) {
val.title = $li.find('.item-title').text();
this.inputs( val );
console.log(val);
}
},
_open: function( e ){
// bail early if not active
if( !this.active ) return;
// get atts
var val = this.atts();
// update WP inputs
this.inputs( val );
},
_close: function( e ){
// bail early if not active
if( !this.active ) return;
// reset vars
// use timeout to allow _update() function to check vars
setTimeout(function(){
acf.link.reset();
}, 100);
},
_update: function( e ){
// bail early if not active
if( !this.active ) return;
// get atts
var val = this.inputs();
// update node
this.atts( val );
}
});
// todo - listen to AJAX for wp-link-ajax and append post_id to value
})(jQuery);
(function($){
acf.media = acf.model.extend({
frames: [],
mime_types: {},
actions: {
'ready': 'ready'
},
/*
* frame
*
* This function will return the current frame
*
* @type function
* @date 11/04/2016
* @since 5.3.2
*
* @param n/a
* @return frame (object)
*/
frame: function(){
// vars
var i = this.frames.length - 1;
// bail early if no index
if( i < 0 ) return false;
// return
return this.frames[ i ];
},
/*
* destroy
*
* this function will destroy a frame
*
* @type function
* @date 11/04/2016
* @since 5.3.8
*
* @return frame (object)
* @return n/a
*/
destroy: function( frame ) {
// detach
frame.detach();
frame.dispose();
// remove frame
frame = null;
this.frames.pop();
},
/*
* popup
*
* This function will create a wp media popup frame
*
* @type function
* @date 11/04/2016
* @since 5.3.8
*
* @param args (object)
* @return frame (object)
*/
popup: function( args ) {
// vars
var post_id = acf.get('post_id'),
frame = false;
// validate post_id
if( !$.isNumeric(post_id) ) post_id = 0;
// settings
var settings = acf.parse_args( args, {
mode: 'select', // 'select', 'edit'
title: '', // 'Upload Image'
button: '', // 'Select Image'
type: '', // 'image', ''
field: '', // 'field_123'
mime_types: '', // 'pdf, etc'
library: 'all', // 'all', 'uploadedTo'
multiple: false, // false, true, 'add'
attachment: 0, // the attachment to edit
post_id: post_id, // the post being edited
select: function(){}
});
// id changed to attributes
if( settings.id ) settings.attachment = settings.id;
// create frame
var frame = this.new_media_frame( settings );
// append
this.frames.push( frame );
// open popup (allow frame customization before opening)
setTimeout(function(){
frame.open();
}, 1);
// return
return frame;
},
/*
* _get_media_frame_settings
*
* This function will return an object containing frame settings
*
* @type function
* @date 11/04/2016
* @since 5.3.8
*
* @param frame (object)
* @param settings (object)
* @return frame (object)
*/
_get_media_frame_settings: function( frame, settings ){
// select
if( settings.mode === 'select' ) {
frame = this._get_select_frame_settings( frame, settings );
// edit
} else if( settings.mode === 'edit' ) {
frame = this._get_edit_frame_settings( frame, settings );
}
// return
return frame;
},
_get_select_frame_settings: function( frame, settings ){
// type
if( settings.type ) {
frame.library.type = settings.type;
}
// library
if( settings.library === 'uploadedTo' ) {
frame.library.uploadedTo = settings.post_id;
}
// button
frame._button = acf._e('media', 'select');
// return
return frame;
},
_get_edit_frame_settings: function( frame, settings ){
// post__in
frame.library.post__in = [ settings.attachment ];
// button
frame._button = acf._e('media', 'update');
// return
return frame;
},
/*
* _add_media_frame_events
*
* This function will add events to the frame object
*
* @type function
* @date 11/04/2016
* @since 5.3.8
*
* @param $post_id (int)
* @return $post_id (int)
*/
_add_media_frame_events: function( frame, settings ){
// log events
/*
frame.on('all', function( e ) {
console.log( 'frame all: %o', e );
});
*/
// add class
frame.on('open',function() {
// add class
this.$el.closest('.media-modal').addClass('acf-media-modal -' +settings.mode );
}, frame);
// edit image view
// source: media-views.js:2410 editImageContent()
frame.on('content:render:edit-image', function(){
var image = this.state().get('image'),
view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
this.content.set( view );
// after creating the wrapper view, load the actual editor via an ajax call
view.loadEditor();
}, frame);
// update toolbar button
frame.on( 'toolbar:create:select', function( toolbar ) {
toolbar.view = new wp.media.view.Toolbar.Select({
text: frame.options._button,
controller: this
});
}, frame );
// select image
frame.on('select', function() {
// get selected images
var state = frame.state(),
image = state.get('image'),
selection = state.get('selection');
// if editing image
if( image ) {
settings.select.apply( frame, [image, 0] );
return;
}
// if selecting images
if( selection ) {
// vars
var i = 0;
// loop
selection.each(function( attachment ){
settings.select.apply( frame, [attachment, i] );
i++;
});
return;
}
});
// close popup
frame.on('close',function(){
setTimeout(function(){
acf.media.destroy( frame );
}, 500);
});
// select
if( settings.mode === 'select' ) {
frame = this._add_select_frame_events( frame, settings );
// edit
} else if( settings.mode === 'edit' ) {
frame = this._add_edit_frame_events( frame, settings );
}
// return
return frame;
},
_add_select_frame_events: function( frame, settings ){
// reference
var self = this;
// plupload
// adds _acfuploader param to validate uploads
if( acf.isset(_wpPluploadSettings, 'defaults', 'multipart_params') ) {
// add _acfuploader so that Uploader will inherit
_wpPluploadSettings.defaults.multipart_params._acfuploader = settings.field;
// remove acf_field so future Uploaders won't inherit
frame.on('open', function(){
delete _wpPluploadSettings.defaults.multipart_params._acfuploader;
});
}
// modify DOM
frame.on('content:activate:browse', function(){
// populate above vars making sure to allow for failure
try {
var toolbar = frame.content.get().toolbar,
filters = toolbar.get('filters'),
search = toolbar.get('search');
} catch(e) {
// one of the objects was 'undefined'... perhaps the frame open is Upload Files
// console.log( 'error %o', e );
return;
}
// image
if( settings.type == 'image' ) {
// update all
filters.filters.all.text = acf._e('image', 'all');
// remove some filters
delete filters.filters.audio;
delete filters.filters.video;
// update all filters to show images
$.each( filters.filters, function( k, filter ){
if( filter.props.type === null ) {
filter.props.type = 'image';
}
});
}
// custom mime types
if( settings.mime_types ) {
// explode
var extra_types = settings.mime_types.split(' ').join('').split('.').join('').split(',');
// loop through mime_types
$.each( extra_types, function( i, type ){
// find mime
$.each( self.mime_types, function( t, mime ){
// continue if key does not match
if( t.indexOf(type) === -1 ) {
return;
}
// create new filter
var filter = {
text: type,
props: {
status: null,
type: mime,
uploadedTo: null,
orderby: 'date',
order: 'DESC'
},
priority: 20
};
// append filter
filters.filters[ mime ] = filter;
});
});
}
// uploaded to post
if( settings.library == 'uploadedTo' ) {
// remove some filters
delete filters.filters.unattached;
delete filters.filters.uploaded;
// add 'uploadedTo' text
filters.$el.parent().append('' + acf._e('image', 'uploadedTo') + '');
// add uploadedTo to filters
$.each( filters.filters, function( k, filter ){
filter.props.uploadedTo = settings.post_id;
});
}
// add _acfuploader to filters
$.each( filters.filters, function( k, filter ){
filter.props._acfuploader = settings.field;
});
// add _acfuplaoder to search
search.model.attributes._acfuploader = settings.field;
// render
if( typeof filters.refresh === 'function' ) {
filters.refresh();
}
});
// return
return frame;
},
_add_edit_frame_events: function( frame, settings ){
// add class
frame.on('open',function() {
// add class
this.$el.closest('.media-modal').addClass('acf-expanded');
// set to browse
if( this.content.mode() != 'browse' ) {
this.content.mode('browse');
}
// set selection
var state = this.state(),
selection = state.get('selection'),
attachment = wp.media.attachment( settings.attachment );
selection.add( attachment );
}, frame);
// return
return frame;
},
/*
* new_media_frame
*
* this function will create a new media frame
*
* @type function
* @date 11/04/2016
* @since 5.3.8
*
* @param settings (object)
* @return frame (object)
*/
new_media_frame: function( settings ){
// vars
var attributes = {
title: settings.title,
multiple: settings.multiple,
library: {},
states: []
};
// get options
attributes = this._get_media_frame_settings( attributes, settings );
// create query
var Query = wp.media.query( attributes.library );
// add _acfuploader
// this is super wack!
// if you add _acfuploader to the options.library args, new uploads will not be added to the library view.
// this has been traced back to the wp.media.model.Query initialize function (which can't be overriden)
// Adding any custom args will cause the Attahcments to not observe the uploader queue
// To bypass this security issue, we add in the args AFTER the Query has been initialized
// options.library._acfuploader = settings.field;
if( acf.isset(Query, 'mirroring', 'args') ) {
Query.mirroring.args._acfuploader = settings.field;
}
// add states
attributes.states = [
// main state
new wp.media.controller.Library({
library: Query,
multiple: attributes.multiple,
title: attributes.title,
priority: 20,
filterable: 'all',
editable: true,
// If the user isn't allowed to edit fields,
// can they still edit it locally?
allowLocalEdits: true
})
];
// edit image functionality (added in WP 3.9)
if( acf.isset(wp, 'media', 'controller', 'EditImage') ) {
attributes.states.push( new wp.media.controller.EditImage() );
}
// create frame
var frame = wp.media( attributes );
// add args reference
frame.acf = settings;
// add events
frame = this._add_media_frame_events( frame, settings );
// return
return frame;
},
ready: function(){
// vars
var version = acf.get('wp_version'),
browser = acf.get('browser'),
post_id = acf.get('post_id');
// update wp.media
if( acf.isset(window,'wp','media','view','settings','post') && $.isNumeric(post_id) ) {
wp.media.view.settings.post.id = post_id;
}
// append browser
if( browser ) {
$('body').addClass('browser-' + browser );
}
// append version
if( version ) {
// ensure is string
version = version + '';
// use only major version
major = version.substr(0,1);
// add body class
$('body').addClass('major-' + major);
}
// customize wp.media views
if( acf.isset(window, 'wp', 'media', 'view') ) {
//this.customize_Attachments();
//this.customize_Query();
//this.add_AcfEmbed();
this.customize_Attachment();
this.customize_AttachmentFiltersAll();
this.customize_AttachmentCompat();
}
},
/*
add_AcfEmbed: function(){
//test urls
//(image) jpg: http://www.ml24.net/img/ml24_design_process_scion_frs_3d_rendering.jpg
//(image) svg: http://kompozer.net/images/svg/Mozilla_Firefox.svg
//(file) pdf: http://martinfowler.com/ieeeSoftware/whenType.pdf
//(video) mp4: https://videos.files.wordpress.com/kUJmAcSf/bbb_sunflower_1080p_30fps_normal_hd.mp4
// add view
wp.media.view.AcfEmbed = wp.media.view.Embed.extend({
initialize: function() {
// set attachments
this.model.props.attributes = this.controller.acf.attachment || {};
// refresh
wp.media.view.Embed.prototype.initialize.apply( this, arguments );
},
refresh: function() {
// vars
var attachment = acf.parse_args(this.model.props.attributes, {
url: '',
filename: '',
title: '',
caption: '',
alt: '',
description: '',
type: '',
ext: ''
});
// update attachment
if( attachment.url ) {
// filename
attachment.filename = attachment.url.split('/').pop().split('?')[0];
// update
attachment.ext = attachment.filename.split('.').pop();
attachment.type = /(jpe?g|png|gif|svg)/i.test(attachment.ext) ? 'image': 'file';
}
// auto generate title
if( attachment.filename && !attachment.title ) {
// replace
attachment.title = attachment.filename.split('-').join(' ').split('_').join(' ');
// uppercase first word
attachment.title = attachment.title.charAt(0).toUpperCase() + attachment.title.slice(1);
// remove extension
attachment.title = attachment.title.replace('.'+attachment.ext, '');
// update model
this.model.props.attributes.title = attachment.title;
}
// save somee extra data
this.model.props.attributes.filename = attachment.filename;
this.model.props.attributes.type = attachment.type;
// always show image view
// avoid this.model.set() to prevent listeners updating view
this.model.attributes.type = 'image';
// refresh
wp.media.view.Embed.prototype.refresh.apply( this, arguments );
// append title
this.$el.find('.setting.caption').before([
''
].join(''));
// append description
this.$el.find('.setting.alt-text').after([
''
].join(''));
// hide alt
if( attachment.type !== 'image' ) {
this.$el.find('.setting.alt-text').hide();
}
}
});
},
*/
/*
customize_Attachments: function(){
// vars
var Attachments = wp.media.model.Attachments;
wp.media.model.Attachments = Attachments.extend({
initialize: function( models, options ){
// console.log('My Attachments initialize: %o %o %o', this, models, options);
// return
return Attachments.prototype.initialize.apply( this, arguments );
},
sync: function( method, model, options ) {
// console.log('My Attachments sync: %o %o %o %o', this, method, model, options);
// return
return Attachments.prototype.sync.apply( this, arguments );
}
});
},
customize_Query: function(){
// console.log('customize Query!');
// vars
var Query = wp.media.model.Query;
wp.media.model.Query = {};
},
*/
customize_Attachment: function(){
// vars
var AttachmentLibrary = wp.media.view.Attachment.Library;
// extend
wp.media.view.Attachment.Library = AttachmentLibrary.extend({
render: function() {
// vars
var frame = acf.media.frame(),
errors = acf.maybe_get(this, 'model.attributes.acf_errors');
// add class
// also make sure frame exists to prevent this logic running on a WP popup (such as feature image)
if( frame && errors ) {
this.$el.addClass('acf-disabled');
}
// return
return AttachmentLibrary.prototype.render.apply( this, arguments );
},
/*
* toggleSelection
*
* This function is called before an attachment is selected
* A good place to check for errors and prevent the 'select' function from being fired
*
* @type function
* @date 29/09/2016
* @since 5.4.0
*
* @param options (object)
* @return n/a
*/
toggleSelection: function( options ) {
// vars
// source: wp-includes/js/media-views.js:2880
var collection = this.collection,
selection = this.options.selection,
model = this.model,
single = selection.single();
// vars
var frame = acf.media.frame(),
errors = acf.maybe_get(this, 'model.attributes.acf_errors'),
$sidebar = this.controller.$el.find('.media-frame-content .media-sidebar');
// remove previous error
$sidebar.children('.acf-selection-error').remove();
// show attachment details
$sidebar.children().removeClass('acf-hidden');
// add message
if( frame && errors ) {
// vars
var filename = acf.maybe_get(this, 'model.attributes.filename', '');
// hide attachment details
// Gallery field continues to show previously selected attachment...
$sidebar.children().addClass('acf-hidden');
// append message
$sidebar.prepend([
'
'
].join(''));
// reset selection (unselects all attachments)
selection.reset();
// set single (attachment displayed in sidebar)
selection.single( model );
// return and prevent 'select' form being fired
return;
}
// return
AttachmentLibrary.prototype.toggleSelection.apply( this, arguments );
}
});
},
customize_AttachmentFiltersAll: function(){
// add function refresh
wp.media.view.AttachmentFilters.All.prototype.refresh = function(){
// Build `' ).val( value ).html( filter.text )[0],
priority: filter.priority || 50
};
}, this ).sortBy('priority').pluck('el').value() );
};
},
customize_AttachmentCompat: function(){
// vars
var AttachmentCompat = wp.media.view.AttachmentCompat;
// extend
wp.media.view.AttachmentCompat = AttachmentCompat.extend({
add_acf_expand_button: function(){
// vars
var $el = this.$el.closest('.media-modal');
// does button already exist?
if( $el.find('.media-frame-router .acf-expand-details').exists() ) return;
// create button
var $a = $([
'',
'' + acf._e('expand_details') + '',
'' + acf._e('collapse_details') + '',
''
].join(''));
// add events
$a.on('click', function( e ){
e.preventDefault();
if( $el.hasClass('acf-expanded') ) {
$el.removeClass('acf-expanded');
} else {
$el.addClass('acf-expanded');
}
});
// append
$el.find('.media-frame-router').append( $a );
},
render: function() {
// validate
if( this.ignore_render ) return this;
// reference
var self = this;
// add expand button
setTimeout(function(){
self.add_acf_expand_button();
}, 0);
// setup fields
// The clearTimout is needed to prevent many setup functions from running at the same time
clearTimeout( acf.media.render_timout );
acf.media.render_timout = setTimeout(function(){
acf.do_action('append', self.$el);
}, 50);
// return
return AttachmentCompat.prototype.render.apply( this, arguments );
},
dispose: function() {
// remove
acf.do_action('remove', this.$el);
// return
return AttachmentCompat.prototype.dispose.apply( this, arguments );
},
save: function( e ) {
if( e ) {
e.preventDefault();
}
// serialize form
var data = acf.serialize(this.$el);
// ignore render
this.ignore_render = true;
// save
this.model.saveCompat( data );
}
});
}
});
})(jQuery);
(function($){
acf.fields.oembed = acf.field.extend({
type: 'oembed',
$el: null,
events: {
'click [data-name="search-button"]': '_search',
'click [data-name="clear-button"]': '_clear',
'click [data-name="value-title"]': '_edit',
'keypress [data-name="search-input"]': '_keypress',
'keyup [data-name="search-input"]': '_keyup',
'blur [data-name="search-input"]': '_blur'
},
/*
* focus
*
* This function will setup variables when focused on a field
*
* @type function
* @date 12/04/2016
* @since 5.3.8
*
* @param n/a
* @return n/a
*/
focus: function(){
// vars
this.$el = this.$field.find('.acf-oembed');
this.$search = this.$el.find('[data-name="search-input"]');
this.$input = this.$el.find('[data-name="value-input"]');
this.$title = this.$el.find('[data-name="value-title"]');
this.$embed = this.$el.find('[data-name="value-embed"]');
// options
this.o = acf.get_data( this.$el );
},
/*
* maybe_search
*
* description
*
* @type function
* @date 14/10/16
* @since 5.4.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
maybe_search: function(){
// set url and focus
var old_url = this.$input.val(),
new_url = this.$search.val();
// bail early if no value
if( !new_url ) {
this.clear();
return;
}
// bail early if no change
if( new_url == old_url ) return;
// search
this.search();
},
/*
* search
*
* This function will search for an oembed
*
* @type function
* @date 13/10/16
* @since 5.4.0
*
* @param n/a
* @return n/a
*/
search: function(){
// vars
var s = this.$search.val();
// fix missing 'http://' - causes the oembed code to error and fail
if( s.substr(0, 4) != 'http' ) {
s = 'http://' + s;
this.$search.val( s );
}
// show loading
this.$el.addClass('is-loading');
// AJAX data
var ajax_data = acf.prepare_for_ajax({
'action' : 'acf/fields/oembed/search',
's' : s,
'field_key' : this.$field.data('key')
});
// abort XHR if this field is already loading AJAX data
if( this.$el.data('xhr') ) this.$el.data('xhr').abort();
// get HTML
var xhr = $.ajax({
url: acf.get('ajaxurl'),
data: ajax_data,
type: 'post',
dataType: 'json',
context: this,
success: this.search_success
});
// update el data
this.$el.data('xhr', xhr);
},
search_success: function( json ){
// vars
var s = this.$search.val();
// remove loading
this.$el.removeClass('is-loading');
// error
if( !json || !json.html ) {
this.$el.removeClass('has-value').addClass('has-error');
return;
}
// add classes
this.$el.removeClass('has-error').addClass('has-value');
// update vars
this.$input.val( s );
this.$title.html( s );
this.$embed.html( json.html );
},
clear: function(){
// update class
this.$el.removeClass('has-error has-value');
// clear search
this.$el.find('[data-name="search-input"]').val('');
// clear inputs
this.$input.val('');
this.$title.html('');
this.$embed.html('');
},
edit: function(){
// add class
this.$el.addClass('is-editing');
// set url and focus
this.$search.val( this.$title.text() ).focus();
},
blur: function( $el ){
// remove class
this.$el.removeClass('is-editing');
// maybe search
this.maybe_search();
},
_search: function( e ){ // console.log('_search');
this.search();
},
_clear: function( e ){ // console.log('_clear');
this.clear();
},
_edit: function( e ){ // console.log('_clear');
this.edit();
},
_keypress: function( e ){ // console.log('_keypress');
// don't submit form
if( e.which == 13 ) e.preventDefault();
},
_keyup: function( e ){ //console.log('_keypress', e.which);
// bail early if no value
if( !this.$search.val() ) return;
// maybe search
this.maybe_search();
},
_blur: function( e ){ // console.log('_blur');
this.blur();
}
});
})(jQuery);
(function($){
acf.fields.radio = acf.field.extend({
type: 'radio',
$ul: null,
actions: {
'ready': 'initialize',
'append': 'initialize'
},
events: {
'click input[type="radio"]': 'click'
},
focus: function(){
// focus on $select
this.$ul = this.$field.find('.acf-radio-list');
// get options
this.o = acf.get_data( this.$ul );
},
/*
* initialize
*
* This function will fix a bug found in Chrome.
* A radio input (for a given name) may only have 1 selected value. When used within a fc layout
* multiple times (clone field), the selected value (default value) will not be checked.
* This simply re-checks it.
*
* @type function
* @date 30/08/2016
* @since 5.4.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
initialize: function(){
// find selected input and check it
this.$ul.find('.selected input').prop('checked', true);
},
click: function(e){
// vars
var $radio = e.$el,
$label = $radio.parent('label'),
selected = $label.hasClass('selected'),
val = $radio.val();
// remove previous selected
this.$ul.find('.selected').removeClass('selected');
// add active class
$label.addClass('selected');
// allow null
if( this.o.allow_null && selected ) {
// unselect
e.$el.prop('checked', false);
$label.removeClass('selected');
val = false;
// trigger change
e.$el.trigger('change');
}
// other
if( this.o.other_choice ) {
// vars
var $other = this.$ul.find('input[type="text"]');
// show
if( val === 'other' ) {
$other.prop('disabled', false).attr('name', $radio.attr('name'));
// hide
} else {
$other.prop('disabled', true).attr('name', '');
}
}
}
});
})(jQuery);
(function($){
acf.fields.relationship = acf.field.extend({
type: 'relationship',
$el: null,
$input: null,
$filters: null,
$choices: null,
$values: null,
actions: {
'ready': 'initialize',
'append': 'initialize'
},
events: {
'keypress [data-filter]': 'submit_filter',
'change [data-filter]': 'change_filter',
'keyup [data-filter]': 'change_filter',
'click .choices .acf-rel-item': 'add_item',
'click [data-name="remove_item"]': 'remove_item'
},
focus: function(){
// get elements
this.$el = this.$field.find('.acf-relationship');
this.$input = this.$el.children('input[type="hidden"]');
this.$choices = this.$el.find('.choices'),
this.$values = this.$el.find('.values');
// get options
this.o = acf.get_data( this.$el );
},
initialize: function(){
// reference
var self = this,
$field = this.$field,
$el = this.$el,
$input = this.$input;
// right sortable
this.$values.children('.list').sortable({
items: 'li',
forceHelperSize: true,
forcePlaceholderSize: true,
scroll: true,
update: function(){
$input.trigger('change');
}
});
this.$choices.children('.list').scrollTop(0).on('scroll', function(e){
// bail early if no more results
if( $el.hasClass('is-loading') || $el.hasClass('is-empty') ) {
return;
}
// Scrolled to bottom
if( Math.ceil( $(this).scrollTop() ) + $(this).innerHeight() >= $(this).get(0).scrollHeight ) {
// get paged
var paged = $el.data('paged') || 1;
// update paged
$el.data('paged', (paged+1) );
// fetch
self.set('$field', $field).fetch();
}
});
/*
// scroll event
var maybe_fetch = function( e ){
console.log('scroll');
// remove listener
$(window).off('scroll', maybe_fetch);
// is field in view
if( acf.is_in_view($field) ) {
// fetch
self.doFocus($field);
self.fetch();
// return
return;
}
// add listener
setTimeout(function(){
$(window).on('scroll', maybe_fetch);
}, 500);
};
*/
// fetch
this.fetch();
},
/*
show: function(){
console.log('show field: %o', this.o.xhr);
// bail ealry if already loaded
if( typeof this.o.xhr !== 'undefined' ) {
return;
}
// is field in view
if( acf.is_in_view(this.$field) ) {
// fetch
this.fetch();
}
},
*/
maybe_fetch: function(){
// reference
var self = this,
$field = this.$field;
// abort timeout
if( this.o.timeout ) {
clearTimeout( this.o.timeout );
}
// fetch
var timeout = setTimeout(function(){
self.doFocus($field);
self.fetch();
}, 300);
this.$el.data('timeout', timeout);
},
fetch: function(){
// reference
var self = this,
$field = this.$field;
// add class
this.$el.addClass('is-loading');
// abort XHR if this field is already loading AJAX data
if( this.o.xhr ) {
this.o.xhr.abort();
this.o.xhr = false;
}
// add to this.o
this.o.action = 'acf/fields/relationship/query';
this.o.field_key = $field.data('key');
this.o.post_id = acf.get('post_id');
// ready for ajax
var ajax_data = acf.prepare_for_ajax( this.o );
// clear html if is new query
if( ajax_data.paged == 1 ) {
this.$choices.children('.list').html('')
}
// add message
this.$choices.find('ul:last').append('
' + acf._e('relationship', 'loading') + '
');
// get results
var xhr = $.ajax({
url: acf.get('ajaxurl'),
dataType: 'json',
type: 'post',
data: ajax_data,
success: function( json ){
self.set('$field', $field).render( json );
}
});
// update el data
this.$el.data('xhr', xhr);
},
render: function( json ){
// remove loading class
this.$el.removeClass('is-loading is-empty');
// remove p tag
this.$choices.find('p').remove();
// no results?
if( !json || !json.results || !json.results.length ) {
// add class
this.$el.addClass('is-empty');
// add message
if( this.o.paged == 1 ) {
this.$choices.children('.list').append('
' + acf._e('relationship', 'empty') + '
');
}
// return
return;
}
// get new results
var $new = $( this.walker(json.results) );
// apply .disabled to left li's
this.$values.find('.acf-rel-item').each(function(){
$new.find('.acf-rel-item[data-id="' + $(this).data('id') + '"]').addClass('disabled');
});
// underline search match
// consider removing due to bug where matched strings within HTML attributes caused incorrect results
// Looks like Select2 v4 has moved away from highlighting results, so perhaps we should too
if( this.o.s ) {
// vars
var s = this.o.s;
// allow special characters to be used within regex
s = acf.addslashes(s);
// loop
$new.find('.acf-rel-item').each(function(){
// vars
var find = $(this).text(),
replace = find.replace( new RegExp('(' + s + ')', 'gi'), '$1');
$(this).html( $(this).html().replace(find, replace) );
});
}
// append
this.$choices.children('.list').append( $new );
// merge together groups
var label = '',
$list = null;
this.$choices.find('.acf-rel-label').each(function(){
if( $(this).text() == label ) {
$list.append( $(this).siblings('ul').html() );
$(this).parent().remove();
return;
}
// update vars
label = $(this).text();
$list = $(this).siblings('ul');
});
},
walker: function( data ){
// vars
var s = '';
// loop through data
if( $.isArray(data) ) {
for( var k in data ) {
s += this.walker( data[ k ] );
}
} else if( $.isPlainObject(data) ) {
// optgroup
if( data.children !== undefined ) {
s += '
' + data.text + '
';
s += this.walker( data.children );
s += '
';
} else {
s += '
' + data.text + '
';
}
}
// return
return s;
},
submit_filter: function( e ){
// don't submit form
if( e.which == 13 ) {
e.preventDefault();
}
},
change_filter: function( e ){
// vars
var val = e.$el.val(),
filter = e.$el.data('filter');
// Bail early if filter has not changed
if( this.$el.data(filter) == val ) {
return;
}
// update attr
this.$el.data(filter, val);
// reset paged
this.$el.data('paged', 1);
// fetch
if( e.$el.is('select') ) {
this.fetch();
// search must go through timeout
} else {
this.maybe_fetch();
}
},
add_item: function( e ){
// max posts
if( this.o.max > 0 ) {
if( this.$values.find('.acf-rel-item').length >= this.o.max ) {
alert( acf._e('relationship', 'max').replace('{max}', this.o.max) );
return;
}
}
// can be added?
if( e.$el.hasClass('disabled') ) {
return false;
}
// disable
e.$el.addClass('disabled');
// template
var html = [
'
',
'',
'' + e.$el.html(),
'',
'',
'
'].join('');
// add new li
this.$values.children('.list').append( html )
// trigger change on new_li
this.$input.trigger('change');
// validation
acf.validation.remove_error( this.$field );
},
remove_item : function( e ){
// vars
var $span = e.$el.parent(),
id = $span.data('id');
// remove
$span.parent('li').remove();
// show
this.$choices.find('.acf-rel-item[data-id="' + id + '"]').removeClass('disabled');
// trigger change on new_li
this.$input.trigger('change');
}
});
})(jQuery);
(function($){
// globals
var _select2,
_select23,
_select24;
/*
* acf.select2
*
* all logic to create select2 instances
*
* @type function
* @date 16/12/2015
* @since 5.3.2
*
* @param n/a
* @return n/a
*/
_select2 = acf.select2 = acf.model.extend({
// vars
version: 0,
version3: null,
version4: null,
// actions
actions: {
'ready 1': 'ready'
},
/*
* ready
*
* This function will run on document ready
*
* @type function
* @date 21/06/2016
* @since 5.3.8
*
* @param n/a
* @return n/a
*/
ready: function(){
// determine Select2 version
this.version = this.get_version();
// ready
this.do_function('ready');
},
/*
* get_version
*
* This function will return the Select2 version
*
* @type function
* @date 29/4/17
* @since 5.5.13
*
* @param n/a
* @return n/a
*/
get_version: function(){
if( acf.maybe_get(window, 'Select2') ) return 3;
if( acf.maybe_get(window, 'jQuery.fn.select2.amd') ) return 4;
return 0;
},
/*
* do_function
*
* This function will call the v3 or v4 equivelant function
*
* @type function
* @date 28/4/17
* @since 5.5.13
*
* @param name (string)
* @param args (array)
* @return (mixed)
*/
do_function: function( name, args ){
// defaults
args = args || [];
// vars
var model = 'version'+this.version;
// bail early if not set
if( typeof this[model] === 'undefined' ||
typeof this[model][name] === 'undefined' ) return false;
// run
return this[model][name].apply( this, args );
},
/*
* get_data
*
* This function will look at a $select element and return an object choices
*
* @type function
* @date 24/12/2015
* @since 5.3.2
*
* @param $select (jQuery)
* @return (array)
*/
get_data: function( $select, data ){
// reference
var self = this;
// defaults
data = data || [];
// loop over children
$select.children().each(function(){
// vars
var $el = $(this);
// optgroup
if( $el.is('optgroup') ) {
data.push({
'text': $el.attr('label'),
'children': self.get_data( $el )
});
// option
} else {
data.push({
'id': $el.attr('value'),
'text': $el.text()
});
}
});
// return
return data;
},
/*
* decode_data
*
* This function will take an array of choices and decode the text
* Changes '&' to '&' which fixes a bug (in Select2 v3 )when searching for '&'
*
* @type function
* @date 24/12/2015
* @since 5.3.2
*
* @param $select (jQuery)
* @return (array)
*/
decode_data: function( data ) {
// bail ealry if no data
if( !data ) return [];
//loop
$.each(data, function(k, v){
// text
data[ k ].text = acf.decode( v.text );
// children
if( typeof v.children !== 'undefined' ) {
data[ k ].children = _select2.decode_data(v.children);
}
});
// return
return data;
},
/*
* count_data
*
* This function will take an array of choices and return the count
*
* @type function
* @date 24/12/2015
* @since 5.3.2
*
* @param data (array)
* @return (int)
*/
count_data: function( data ) {
// vars
var i = 0;
// bail ealry if no data
if( !data ) return i;
//loop
$.each(data, function(k, v){
// increase
i++;
// children
if( typeof v.children !== 'undefined' ) {
i += v.children.length;
}
});
// return
return i;
},
/*
* get_ajax_data
*
* This function will return an array of data to send via AJAX
*
* @type function
* @date 19/07/2016
* @since 5.4.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
get_ajax_data: function( args, params, $el, $field ){
// vars
var data = acf.prepare_for_ajax({
action: args.ajax_action,
field_key: args.key,
s: params.term || '',
paged: params.page || 1
});
// filter
data = acf.apply_filters( 'select2_ajax_data', data, args, $el, $field );
// return
return data;
},
/*
* get_ajax_results
*
* This function will return a valid AJAX response
*
* @type function
* @date 19/07/2016
* @since 5.4.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
get_ajax_results: function( data, params ){
// vars
var valid = {
results: []
};
// bail early if no data
if( !data ) {
data = valid;
}
// allow for an array of choices
if( typeof data.results == 'undefined' ) {
valid.results = data;
data = valid;
}
// decode
data.results = this.decode_data(data.results);
// filter
data = acf.apply_filters( 'select2_ajax_results', data, params );
// return
return data;
},
/*
* get_value
*
* This function will return the selected options in a Select2 format
*
* @type function
* @date 5/01/2016
* @since 5.3.2
*
* @param $post_id (int)
* @return $post_id (int)
*/
get_value: function( $select ){
// vars
var val = [],
$selected = $select.find('option:selected');
// bail early if no selected
if( !$selected.exists() ) return val;
// sort
$selected = $selected.sort(function(a, b) {
return +a.getAttribute('data-i') - +b.getAttribute('data-i');
});
// loop
$selected.each(function(){
// vars
var $el = $(this);
// append
val.push({
'id': $el.attr('value'),
'text': $el.text(),
'$el': $el
});
});
// return
return val;
},
/*
* get_input_value
*
* This function will return an array of values as per the hidden input
*
* @type function
* @date 29/4/17
* @since 5.5.13
*
* @param $input (jQuery)
* @return (array)
*/
get_input_value: function( $input ) {
return $input.val().split('||');
},
/*
* sync_input_value
*
* This function will save the current selected values into the hidden input
*
* @type function
* @date 29/4/17
* @since 5.5.13
*
* @param $input (jQuery)
* @param $select (jQuery)
* @return n/a
*/
sync_input_value: function( $input, $select ) {
$input.val( $select.val().join('||') );
},
/*
* add_option
*
* This function will add an ');
}
},
/*
* select_option
*
* This function will select an option
*
* @type function
* @date 29/4/17
* @since 5.5.13
*
* @param $select (jQuery)
* @param value (string)
* @return n/a
*/
select_option: function( $select, value ){
$select.find('option[value="'+value+'"]').prop('selected', true);
$select.trigger('change');
},
/*
* unselect_option
*
* This function will unselect an option
*
* @type function
* @date 29/4/17
* @since 5.5.13
*
* @param $select (jQuery)
* @param value (string)
* @return n/a
*/
unselect_option: function( $select, value ){
$select.find('option[value="'+value+'"]').prop('selected', false);
$select.trigger('change');
},
/*
* Select2 v3 or v4 functions
*
* description
*
* @type function
* @date 29/4/17
* @since 5.5.10
*
* @param $post_id (int)
* @return $post_id (int)
*/
init: function( $select, args, $field ){
this.do_function( 'init', arguments );
},
destroy: function( $select ){
this.do_function( 'destroy', arguments );
},
add_value: function( $select, value, label ){
this.do_function( 'add_value', arguments );
},
remove_value: function( $select, value ){
this.do_function( 'remove_value', arguments );
},
remove_value: function( $select, value ){
this.do_function( 'remove_value', arguments );
}
});
/*
* Select2 v3
*
* This model contains the Select2 v3 functions
*
* @type function
* @date 28/4/17
* @since 5.5.10
*
* @param n/a
* @return n/a
*/
_select23 = _select2.version3 = {
ready: function(){
// vars
var locale = acf.get('locale'),
rtl = acf.get('rtl')
l10n = acf._e('select');
// bail ealry if no l10n
if( !l10n ) return;
// vars
var l10n_functions = {
formatMatches: function( matches ) {
if ( 1 === matches ) {
return l10n.matches_1;
}
return l10n.matches_n.replace('%d', matches);
},
formatNoMatches: function() {
return l10n.matches_0;
},
formatAjaxError: function() {
return l10n.load_fail;
},
formatInputTooShort: function( input, min ) {
var number = min - input.length;
if ( 1 === number ) {
return l10n.input_too_short_1;
}
return l10n.input_too_short_n.replace( '%d', number );
},
formatInputTooLong: function( input, max ) {
var number = input.length - max;
if ( 1 === number ) {
return l10n.input_too_long_1;
}
return l10n.input_too_long_n.replace( '%d', number );
},
formatSelectionTooBig: function( limit ) {
if ( 1 === limit ) {
return l10n.selection_too_long_1;
}
return l10n.selection_too_long_n.replace( '%d', limit );
},
formatLoadMore: function() {
return l10n.load_more;
},
formatSearching: function() {
return l10n.searching;
}
};
// ensure locales exists
// older versions of Select2 did not have a locale storage
$.fn.select2.locales = acf.maybe_get(window, 'jQuery.fn.select2.locales', {});
// append
$.fn.select2.locales[ locale ] = l10n_functions;
$.extend($.fn.select2.defaults, l10n_functions);
},
set_data: function( $select, data ){
// v3
if( this.version == 3 ) {
$select = $select.siblings('input');
}
// set data
$select.select2('data', data);
},
append_data: function( $select, data ){
// v3
if( this.version == 3 ) {
$select = $select.siblings('input');
}
// vars
var current = $select.select2('data') || [];
// append
current.push( data );
// set data
$select.select2('data', current);
},
/*
* init_v3
*
* This function will create a new Select2 for v3
*
* @type function
* @date 24/12/2015
* @since 5.3.2
*
* @param $select (jQuery)
* @return args (object)
*/
init: function( $select, args, $field ){
// defaults
args = args || {};
$field = $field || null;
// merge
args = $.extend({
allow_null: false,
placeholder: '',
multiple: false,
ajax: false,
ajax_action: ''
}, args);
// vars
var $input = $select.siblings('input');
// bail early if no input
if( !$input.exists() ) return;
// select2 args
var select2_args = {
width: '100%',
containerCssClass: '-acf',
allowClear: args.allow_null,
placeholder: args.placeholder,
multiple: args.multiple,
separator: '||',
data: [],
escapeMarkup: function( m ){ return m; },
formatResult: function( result, container, query, escapeMarkup ){
// run default formatResult
var text = $.fn.select2.defaults.formatResult( result, container, query, escapeMarkup );
// append description
if( result.description ) {
text += ' ' + result.description + '';
}
// return
return text;
}
};
// value
var value = this.get_value( $select );
// multiple
if( args.multiple ) {
// vars
var name = $select.attr('name');
// add hidden input to each multiple selection
select2_args.formatSelection = function( object, $div ){
// vars
var html = '';
// append input
$div.parent().append(html);
// return
return object.text;
}
} else {
// change array to single object
value = acf.maybe_get(value, 0, false);
// if no allow_null, this single select must contain a selection
if( !args.allow_null && value ) {
$input.val( value.id );
}
}
// remove the blank option as we have a clear all button!
if( args.allow_null ) {
$select.find('option[value=""]').remove();
}
// get data
select2_args.data = this.get_data( $select );
// initial selection
select2_args.initSelection = function( element, callback ) {
callback( value );
};
// ajax
if( args.ajax ) {
select2_args.ajax = {
url: acf.get('ajaxurl'),
dataType: 'json',
type: 'post',
cache: false,
quietMillis: 250,
data: function( term, page ) {
// vars
var params = { 'term': term, 'page': page };
// return
return _select2.get_ajax_data(args, params, $input, $field);
},
results: function( data, page ){
// vars
var params = { 'page': page };
// merge together groups
setTimeout(function(){
_select23.merge_results();
}, 1);
// return
return _select2.get_ajax_results(data, params);
}
};
}
// attachment z-index fix
select2_args.dropdownCss = {
'z-index' : '999999999'
};
// append args
select2_args.acf = args;
// filter for 3rd party customization
select2_args = acf.apply_filters( 'select2_args', select2_args, $select, args, $field );
// add select2
$input.select2( select2_args );
// vars
var $container = $input.select2('container');
// reorder DOM
// - this order is very important so don't change it
// - $select goes first so the input can override it. Fixes issue where conditional logic will enable the select
// - $input goes second to reset the input data
// - container goes last to allow multiple hidden inputs to override $input
$container.before( $select );
$container.before( $input );
// multiple
if( args.multiple ) {
// sortable
$container.find('ul.select2-choices').sortable({
start: function() {
$input.select2("onSortStart");
},
stop: function() {
$input.select2("onSortEnd");
}
});
}
// disbale select
$select.prop('disabled', true).addClass('acf-disabled acf-hidden');
// update select value
// this fixes a bug where select2 appears blank after duplicating a post_object field (field settings).
// the $select is disabled, so setting the value won't cause any issues (this is what select2 v4 does anyway).
$input.on('change', function(e) {
// add new data
if( e.added ) {
// add item
_select2.add_option($select, e.added.id, e.added.text);
}
// select
_select2.select_option($select, e.val);
});
// action for 3rd party customization
acf.do_action('select2_init', $input, select2_args, args, $field);
},
/*
* merge_results_v3
*
* description
*
* @type function
* @date 20/07/2016
* @since 5.4.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
merge_results: function(){
// vars
var label = '',
$list = null;
// loop
$('#select2-drop .select2-result-with-children').each(function(){
// vars
var $label = $(this).children('.select2-result-label'),
$ul = $(this).children('.select2-result-sub');
// append group to previous
if( $label.text() == label ) {
$list.append( $ul.children() );
$(this).remove();
return;
}
// update vars
label = $label.text();
$list = $ul;
});
},
/*
* destroy
*
* This function will destroy a Select2
*
* @type function
* @date 24/12/2015
* @since 5.3.2
*
* @param $post_id (int)
* @return $post_id (int)
*/
destroy: function( $select ){
// vars
var $input = $select.siblings('input');
// destroy via api
if( $input.data('select2') ) {
$input.select2('destroy');
}
// destory via HTML (duplicating HTML deos not contain data)
$select.siblings('.select2-container').remove();
// enable select
$select.prop('disabled', false).removeClass('acf-disabled acf-hidden');
$input.attr('style', ''); // fixes bug causing hidden select2 element
},
add_value: function( $select, value, label ){
// add and select item
_select2.add_option($select, value, label);
_select2.select_option($select, value);
// vars
var $input = $select.siblings('input');
// new item
var item = {
'id': value,
'text': label
};
// single
if( !$select.data('multiple') ) {
return $input.select2('data', item);
}
// get existing value
var values = $input.select2('data') || [];
// append
values.push(item);
// set data
return $input.select2('data', values);
},
remove_value: function( $select, value ){
// unselect option
_select2.unselect_option($select, value);
// vars
var $input = $select.siblings('input'),
current = $input.select2('data');
// single
if( !$select.data('multiple') ) {
if( current && current.id == value ) {
$input.select2('data', null);
}
// multiple
} else {
// filter
current = $.grep(current, function( item ) {
return item.id != value;
});
// set data
$input.select2('data', current);
}
}
};
/*
* Select2 v4
*
* This model contains the Select2 v4 functions
*
* @type function
* @date 28/4/17
* @since 5.5.10
*
* @param n/a
* @return n/a
*/
_select24 = _select2.version4 = {
init: function( $select, args, $field ){
// defaults
args = args || {};
$field = $field || null;
// merge
args = $.extend({
allow_null: false,
placeholder: '',
multiple: false,
ajax: false,
ajax_action: ''
}, args);
// vars
var $input = $select.siblings('input');
// bail early if no input
if( !$input.exists() ) return;
// select2 args
var select2_args = {
width: '100%',
allowClear: args.allow_null,
placeholder: args.placeholder,
multiple: args.multiple,
separator: '||',
data: [],
escapeMarkup: function( m ){ return m; }
};
// value
var value = this.get_value( $select );
// multiple
if( args.multiple ) {
// reorder opts
$.each(value, function( k, item ){
// detach and re-append to end
item.$el.detach().appendTo( $select );
});
} else {
// change array to single object
value = acf.maybe_get(value, 0, '');
}
/*
// removed - Select2 does not show this value by default!
// remove the blank option as we have a clear all button!
if( args.allow_null ) {
$select.find('option[value=""]').remove();
}
*/
// remove conflicting atts
if( !args.ajax ) {
$select.removeData('ajax');
$select.removeAttr('data-ajax');
} else {
select2_args.ajax = {
url: acf.get('ajaxurl'),
delay: 250,
dataType: 'json',
type: 'post',
cache: false,
data: function( params ) {
// return
return _select2.get_ajax_data(args, params, $select, $field);
},
processResults: function( data, params ){
// vars
var results = _select2.get_ajax_results(data, params);
// change to more
if( results.more ) {
results.pagination = { more: true };
}
// merge together groups
setTimeout(function(){
_select24.merge_results();
}, 1);
// return
return results
}
};
}
// filter for 3rd party customization
select2_args = acf.apply_filters( 'select2_args', select2_args, $select, args, $field );
// add select2
$select.select2( select2_args );
// get container (Select2 v4 deos not return this from constructor)
var $container = $select.next('.select2-container');
// reorder DOM
// - no need to reorder, the select field is needed to $_POST values
// multiple
if( args.multiple ) {
// vars
var $ul = $container.find('ul');
// sortable
$ul.sortable({
stop: function( e ) {
$ul.find('.select2-selection__choice').each(function() {
// vars
var $option = $( $(this).data('data').element );
// detach and re-append to end
$option.detach().appendTo( $select );
// trigger change on input (JS error if trigger on select)
$input.trigger('change');
// update input
//_select2.sync_input_value( $input, $select );
});
}
});
// on select, move to end
$select.on('select2:select', function( e ){
// vars
var $option = $(e.params.data.element);
// detach and re-append to end
$option.detach().appendTo( $select );
// trigger change
//$select.trigger('change');
});
}
/*
// update input
$select.on('select2:select', function( e ){
// update input
_select2.sync_input_value( $input, $select );
});
$select.on('select2:unselect', function( e ){
// update input
_select2.sync_input_value( $input, $select );
});
*/
// clear value (allows null to be saved)
$input.val('');
// add class
$container.addClass('-acf');
// action for 3rd party customization
acf.do_action('select2_init', $select, select2_args, args, $field);
},
/*
* merge_results_v4
*
* description
*
* @type function
* @date 20/07/2016
* @since 5.4.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
merge_results: function(){
// vars
var $prev_options = null,
$prev_group = null;
// loop
$('.select2-results__option[role="group"]').each(function(){
// vars
var $options = $(this).children('ul'),
$group = $(this).children('strong');
// compare to previous
if( $prev_group !== null && $group.text() == $prev_group.text() ) {
$prev_options.append( $options.children() );
$(this).remove();
return;
}
// update vars
$prev_options = $options;
$prev_group = $group;
});
},
add_value: function( $select, value, label ){
// add and select item
_select2.add_option($select, value, label);
_select2.select_option($select, value);
},
remove_value: function( $select, value ){
// unselect
_select2.unselect_option($select, value);
},
destroy: function( $select ){
// destroy via api
if( $select.data('select2') ) {
$select.select2('destroy');
}
// destory via HTML (duplicating HTML deos not contain data)
$select.siblings('.select2-container').remove();
}
};
/*
* depreciated
*
* These functions have moved since v5.3.3
*
* @type function
* @date 11/12/2015
* @since 5.3.2
*
* @param n/a
* @return n/a
*/
acf.add_select2 = function( $select, args ) {
_select2.init( $select, args );
}
acf.remove_select2 = function( $select ) {
_select2.destroy( $select );
}
})(jQuery);
(function($){
// select
acf.fields.select = acf.field.extend({
type: 'select',
$select: null,
actions: {
'ready': 'render',
'append': 'render',
'remove': 'remove'
},
focus: function(){
// focus on $select
this.$select = this.$field.find('select');
// bail early if no select field
if( !this.$select.exists() ) return;
// get options
this.o = acf.get_data( this.$select );
// customize o
this.o = acf.parse_args(this.o, {
'ajax_action': 'acf/fields/'+this.type+'/query',
'key': this.$field.data('key')
});
},
render: function(){
// validate ui
if( !this.$select.exists() || !this.o.ui ) {
return false;
}
acf.select2.init( this.$select, this.o, this.$field );
},
remove: function(){
// validate ui
if( !this.$select.exists() || !this.o.ui ) {
return false;
}
// remove select2
acf.select2.destroy( this.$select );
}
});
// user
acf.fields.user = acf.fields.select.extend({
type: 'user'
});
// post_object
acf.fields.post_object = acf.fields.select.extend({
type: 'post_object'
});
// page_link
acf.fields.page_link = acf.fields.select.extend({
type: 'page_link'
});
})(jQuery);
(function($){
acf.fields.tab = acf.field.extend({
type: 'tab',
$el: null,
$wrap: null,
actions: {
'prepare': 'initialize',
'append': 'initialize',
'hide': 'hide',
'show': 'show'
},
focus: function(){
// get elements
this.$el = this.$field.find('.acf-tab');
// get options
this.o = this.$el.data();
this.o.key = this.$field.data('key');
this.o.text = this.$el.html();
},
initialize: function(){
// bail early if is td
if( this.$field.is('td') ) return;
// add tab
tab_manager.add_tab( this.$field, this.o );
},
hide: function( $field, context ){
// bail early if not conditional logic
if( context != 'conditional_logic' ) return;
// vars
var key = $field.data('key'),
$group = $field.prevAll('.acf-tab-wrap'),
$a = $group.find('a[data-key="' + key + '"]'),
$li = $a.parent();
// bail early if $group does not exist (clone field)
if( !$group.exists() ) return;
// hide li
$li.addClass('hidden-by-conditional-logic');
// set timout to allow proceeding fields to hide first
// without this, the tab field will hide all fields, regarless of if that field has it's own conditional logic rules
setTimeout(function(){
// if this tab field was hidden by conditional_logic, disable it's children to prevent validation
$field.nextUntil('.acf-field-tab', '.acf-field').each(function(){
// bail ealry if already hidden
if( $(this).hasClass('hidden-by-conditional-logic') ) return;
// hide field
acf.conditional_logic.hide_field( $(this) );
// add parent reference
$(this).addClass('-hbcl-' + key);
});
// select other tab if active
if( $li.hasClass('active') ) {
$group.find('li:not(.hidden-by-conditional-logic):first a').trigger('click');
}
}, 0);
},
show: function( $field, context ){
// bail early if not conditional logic
if( context != 'conditional_logic' ) return;
// vars
var key = $field.data('key'),
$group = $field.prevAll('.acf-tab-wrap'),
$a = $group.find('a[data-key="' + key + '"]'),
$li = $a.parent();
// bail early if $group does not exist (clone field)
if( !$group.exists() ) return;
// show li
$li.removeClass('hidden-by-conditional-logic');
// set timout to allow proceeding fields to hide first
// without this, the tab field will hide all fields, regarless of if that field has it's own conditional logic rules
setTimeout(function(){
// if this tab field was shown by conditional_logic, enable it's children to allow validation
$field.siblings('.acf-field.-hbcl-' + key).each(function(){
// show field
acf.conditional_logic.show_field( $(this) );
// remove parent reference
$(this).removeClass('-hbcl-' + key);
});
// select tab if no other active
var $active = $li.siblings('.active');
if( !$active.exists() || $active.hasClass('hidden-by-conditional-logic') ) {
$a.trigger('click');
}
}, 0);
}
});
/*
* tab_manager
*
* This model will handle adding tabs and groups
*
* @type function
* @date 25/11/2015
* @since 5.3.2
*
* @param $post_id (int)
* @return $post_id (int)
*/
var tab_manager = acf.model.extend({
actions: {
'prepare 15': 'render',
'append 15': 'render',
'refresh 15': 'render'
},
events: {
'click .acf-tab-button': '_click'
},
render: function( $el ){
// find visible tab wraps
$('.acf-tab-wrap', $el).each(function(){
// vars
var $group = $(this),
$wrap = $group.parent();
// trigger click
if( !$group.find('li.active').exists() ) {
$group.find('li:not(.hidden-by-conditional-logic):first a').trigger('click');
}
if( $wrap.hasClass('-sidebar') ) {
// vars
var attribute = $wrap.is('td') ? 'height' : 'min-height';
// find height (minus 1 for border-bottom)
var height = $group.position().top + $group.children('ul').outerHeight(true) - 1;
// add css
$wrap.css(attribute, height);
}
});
},
add_group: function( $field, settings ){
// vars
var $wrap = $field.parent(),
html = '';
// add sidebar to wrap
if( $wrap.hasClass('acf-fields') && settings.placement == 'left' ) {
$wrap.addClass('-sidebar');
// can't have side tab without sidebar
} else {
settings.placement = 'top';
}
// generate html
if( $wrap.is('tbody') ) {
html = '
';
} else {
html = '
';
}
// save
$group = $(html);
// append
$field.before( $group );
// return
return $group;
},
add_tab: function( $field, settings ){ //console.log('add_tab(%o, %o)', $field, settings);
// vars
var $group = $field.siblings('.acf-tab-wrap').last();
// add tab group if no group exists
if( !$group.exists() ) {
$group = this.add_group( $field, settings );
// add tab group if is endpoint
} else if( settings.endpoint ) {
$group = this.add_group( $field, settings );
}
// vars
var $li = $('