Skip to content

Instantly share code, notes, and snippets.

@fedek6
Last active August 10, 2023 18:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fedek6/df585348868dcaa7e8613c979bd7a040 to your computer and use it in GitHub Desktop.
Save fedek6/df585348868dcaa7e8613c979bd7a040 to your computer and use it in GitHub Desktop.
act-input.js source (full documentation). https://www.advancedcustomfields.com/resources/javascript-api/
(function($, undefined){
/**
* acf
*
* description
*
* @date 14/12/17
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
// The global acf object
var acf = {};
// Set as a browser global
window.acf = acf;
/** @var object Data sent from PHP */
acf.data = {};
/**
* get
*
* Gets a specific data value
*
* @date 14/12/17
* @since 5.6.5
*
* @param string name
* @return mixed
*/
acf.get = function( name ){
return this.data[name] || null;
};
/**
* has
*
* Returns `true` if the data exists and is not null
*
* @date 14/12/17
* @since 5.6.5
*
* @param string name
* @return boolean
*/
acf.has = function( name ){
return this.get(name) !== null;
};
/**
* set
*
* Sets a specific data value
*
* @date 14/12/17
* @since 5.6.5
*
* @param string name
* @param mixed value
* @return this
*/
acf.set = function( name, value ){
this.data[ name ] = value;
return this;
};
/**
* uniqueId
*
* Returns a unique ID
*
* @date 9/11/17
* @since 5.6.3
*
* @param string prefix Optional prefix.
* @return string
*/
var idCounter = 0;
acf.uniqueId = function(prefix){
var id = ++idCounter + '';
return prefix ? prefix + id : id;
};
/**
* acf.uniqueArray
*
* Returns a new array with only unique values
* Credit: https://stackoverflow.com/questions/1960473/get-all-unique-values-in-an-array-remove-duplicates
*
* @date 23/3/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.uniqueArray = function( array ){
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
return array.filter( onlyUnique );
};
/**
* uniqid
*
* Returns a unique ID (PHP version)
*
* @date 9/11/17
* @since 5.6.3
* @source http://locutus.io/php/misc/uniqid/
*
* @param string prefix Optional prefix.
* @return string
*/
var uniqidSeed = '';
acf.uniqid = function(prefix, moreEntropy){
// discuss at: http://locutus.io/php/uniqid/
// original by: Kevin van Zonneveld (http://kvz.io)
// revised by: Kankrelune (http://www.webfaktory.info/)
// note 1: Uses an internal counter (in locutus global) to avoid collision
// example 1: var $id = uniqid()
// example 1: var $result = $id.length === 13
// returns 1: true
// example 2: var $id = uniqid('foo')
// example 2: var $result = $id.length === (13 + 'foo'.length)
// returns 2: true
// example 3: var $id = uniqid('bar', true)
// example 3: var $result = $id.length === (23 + 'bar'.length)
// returns 3: true
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;
};
if (!uniqidSeed) { // init seed with big random int
uniqidSeed = Math.floor(Math.random() * 0x75bcd15);
}
uniqidSeed++;
retId = prefix; // start with prefix, add current milliseconds hex string
retId += formatSeed(parseInt(new Date().getTime() / 1000, 10), 8);
retId += formatSeed(uniqidSeed, 5); // add seed hex string
if (moreEntropy) {
// for more entropy we add a float lower to 10
retId += (Math.random() * 10).toFixed(8).toString();
}
return retId;
};
/**
* strReplace
*
* Performs a string replace
*
* @date 14/12/17
* @since 5.6.5
*
* @param string search
* @param string replace
* @param string subject
* @return string
*/
acf.strReplace = function( search, replace, subject ){
return subject.split(search).join(replace);
};
/**
* strCamelCase
*
* Converts a string into camelCase
* Thanks to https://stackoverflow.com/questions/2970525/converting-any-string-into-camel-case
*
* @date 14/12/17
* @since 5.6.5
*
* @param string str
* @return string
*/
acf.strCamelCase = function( str ){
// replace [_-] characters with space
str = str.replace(/[_-]/g, ' ');
// camelCase
str = str.replace(/(?:^\w|\b\w|\s+)/g, function(match, index) {
if (+match === 0) return ""; // or if (/\s+/.test(match)) for white spaces
return index == 0 ? match.toLowerCase() : match.toUpperCase();
});
// return
return str;
};
/**
* strPascalCase
*
* Converts a string into PascalCase
* Thanks to https://stackoverflow.com/questions/1026069/how-do-i-make-the-first-letter-of-a-string-uppercase-in-javascript
*
* @date 14/12/17
* @since 5.6.5
*
* @param string str
* @return string
*/
acf.strPascalCase = function( str ){
var camel = acf.strCamelCase( str );
return camel.charAt(0).toUpperCase() + camel.slice(1);
};
/**
* acf.strSlugify
*
* Converts a string into a HTML class friendly slug
*
* @date 21/3/18
* @since 5.6.9
*
* @param string str
* @return string
*/
acf.strSlugify = function( str ){
return acf.strReplace( '_', '-', str.toLowerCase() );
};
acf.strSanitize = function( str ){
// 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 nonWord = /\W/g;
var mapping = function (c) {
return (map[c] !== undefined) ? map[c] : c;
};
// replace
str = str.replace(nonWord, mapping);
// lowercase
str = str.toLowerCase();
// return
return str;
};
/**
* acf.strMatch
*
* Returns the number of characters that match between two strings
*
* @date 1/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.strMatch = function( s1, s2 ){
// vars
var val = 0;
var min = Math.min( s1.length, s2.length );
// loop
for( var i = 0; i < min; i++ ) {
if( s1[i] !== s2[i] ) {
break;
}
val++;
}
// return
return val;
};
/**
* acf.decode
*
* description
*
* @date 13/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.decode = function( string ){
return $('<textarea/>').html( string ).text();
};
/**
* acf.strEscape
*
* description
*
* @date 3/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.strEscape = function( string ){
return $('<div>').text(string).html();
};
/**
* parseArgs
*
* Merges together defaults and args much like the WP wp_parse_args function
*
* @date 14/12/17
* @since 5.6.5
*
* @param object args
* @param object defaults
* @return object
*/
acf.parseArgs = function( args, defaults ){
if( typeof args !== 'object' ) args = {};
if( typeof defaults !== 'object' ) defaults = {};
return $.extend({}, defaults, args);
}
/**
* __
*
* Retrieve the translation of $text.
*
* @date 16/4/18
* @since 5.6.9
*
* @param string text Text to translate.
* @return string Translated text.
*/
if( window.acfL10n == undefined ) {
acfL10n = {};
}
acf.__ = function( text ){
return acfL10n[ text ] || text;
};
/**
* _x
*
* Retrieve translated string with gettext context.
*
* @date 16/4/18
* @since 5.6.9
*
* @param string text Text to translate.
* @param string context Context information for the translators.
* @return string Translated text.
*/
acf._x = function( text, context ){
return acfL10n[ text + '.' + context ] || acfL10n[ text ] || text;
};
/**
* _n
*
* Retrieve the plural or single form based on the amount.
*
* @date 16/4/18
* @since 5.6.9
*
* @param string single Single text to translate.
* @param string plural Plural text to translate.
* @param int number The number to compare against.
* @return string Translated text.
*/
acf._n = function( single, plural, number ){
if( number == 1 ) {
return acf.__(single);
} else {
return acf.__(plural);
}
};
acf.isArray = function( a ){
return Array.isArray(a);
};
acf.isObject = function( a ){
return ( typeof a === 'object' );
}
/**
* serialize
*
* description
*
* @date 24/12/17
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
var buildObject = function( obj, name, value ){
// replace [] with placeholder
name = name.replace('[]', '[%%index%%]');
// vars
var keys = name.match(/([^\[\]])+/g);
if( !keys ) return;
var length = keys.length;
var ref = obj;
// loop
for( var i = 0; i < length; i++ ) {
// vars
var key = String( keys[i] );
// value
if( i == length - 1 ) {
// %%index%%
if( key === '%%index%%' ) {
ref.push( value );
// default
} else {
ref[ key ] = value;
}
// path
} else {
// array
if( keys[i+1] === '%%index%%' ) {
if( !acf.isArray(ref[ key ]) ) {
ref[ key ] = [];
}
// object
} else {
if( !acf.isObject(ref[ key ]) ) {
ref[ key ] = {};
}
}
// crawl
ref = ref[ key ];
}
}
};
acf.serialize = function( $el, prefix ){
// vars
var obj = {};
var inputs = acf.serializeArray( $el );
// prefix
if( prefix !== undefined ) {
// filter and modify
inputs = inputs.filter(function( item ){
return item.name.indexOf(prefix) === 0;
}).map(function( item ){
item.name = item.name.slice(prefix.length);
return item;
});
}
// loop
for( var i = 0; i < inputs.length; i++ ) {
buildObject( obj, inputs[i].name, inputs[i].value );
}
// return
return obj;
};
/**
* acf.serializeArray
*
* Similar to $.serializeArray() but works with a parent wrapping element.
*
* @date 19/8/18
* @since 5.7.3
*
* @param jQuery $el The element or form to serialize.
* @return array
*/
acf.serializeArray = function( $el ){
return $el.find('select, textarea, input').serializeArray();
}
/**
* acf.serializeForAjax
*
* Returns an object containing name => value data ready to be encoded for Ajax.
*
* @date 17/12/18
* @since 5.8.0
*
* @param jQUery $el The element or form to serialize.
* @return object
*/
acf.serializeForAjax = function( $el ){
// vars
var data = {};
var index = {};
// Serialize inputs.
var inputs = acf.serializeArray( $el );
// Loop over inputs and build data.
inputs.map(function( item ){
// Append to array.
if( item.name.slice(-2) === '[]' ) {
data[ item.name ] = data[ item.name ] || [];
data[ item.name ].push( item.value );
// Append
} else {
data[ item.name ] = item.value;
}
});
// return
return data;
};
/**
* addAction
*
* Wrapper for acf.hooks.addAction
*
* @date 14/12/17
* @since 5.6.5
*
* @param n/a
* @return this
*/
/*
var prefixAction = function( action ){
return 'acf_' + action;
}
*/
acf.addAction = function( action, callback, priority, context ){
//action = prefixAction(action);
acf.hooks.addAction.apply(this, arguments);
return this;
};
/**
* removeAction
*
* Wrapper for acf.hooks.removeAction
*
* @date 14/12/17
* @since 5.6.5
*
* @param n/a
* @return this
*/
acf.removeAction = function( action, callback ){
//action = prefixAction(action);
acf.hooks.removeAction.apply(this, arguments);
return this;
};
/**
* doAction
*
* Wrapper for acf.hooks.doAction
*
* @date 14/12/17
* @since 5.6.5
*
* @param n/a
* @return this
*/
var actionHistory = {};
//var currentAction = false;
acf.doAction = function( action ){
//action = prefixAction(action);
//currentAction = action;
actionHistory[ action ] = 1;
acf.hooks.doAction.apply(this, arguments);
actionHistory[ action ] = 0;
return this;
};
/**
* doingAction
*
* Return true if doing action
*
* @date 14/12/17
* @since 5.6.5
*
* @param n/a
* @return this
*/
acf.doingAction = function( action ){
//action = prefixAction(action);
return (actionHistory[ action ] === 1);
};
/**
* didAction
*
* Wrapper for acf.hooks.doAction
*
* @date 14/12/17
* @since 5.6.5
*
* @param n/a
* @return this
*/
acf.didAction = function( action ){
//action = prefixAction(action);
return (actionHistory[ action ] !== undefined);
};
/**
* currentAction
*
* Wrapper for acf.hooks.doAction
*
* @date 14/12/17
* @since 5.6.5
*
* @param n/a
* @return this
*/
acf.currentAction = function(){
for( var k in actionHistory ) {
if( actionHistory[k] ) {
return k;
}
}
return false;
};
/**
* addFilter
*
* Wrapper for acf.hooks.addFilter
*
* @date 14/12/17
* @since 5.6.5
*
* @param n/a
* @return this
*/
acf.addFilter = function( action ){
//action = prefixAction(action);
acf.hooks.addFilter.apply(this, arguments);
return this;
};
/**
* removeFilter
*
* Wrapper for acf.hooks.removeFilter
*
* @date 14/12/17
* @since 5.6.5
*
* @param n/a
* @return this
*/
acf.removeFilter = function( action ){
//action = prefixAction(action);
acf.hooks.removeFilter.apply(this, arguments);
return this;
};
/**
* applyFilters
*
* Wrapper for acf.hooks.applyFilters
*
* @date 14/12/17
* @since 5.6.5
*
* @param n/a
* @return this
*/
acf.applyFilters = function( action ){
//action = prefixAction(action);
return acf.hooks.applyFilters.apply(this, arguments);
};
/**
* getArgs
*
* description
*
* @date 15/12/17
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.arrayArgs = function( args ){
return Array.prototype.slice.call( args );
};
/**
* extendArgs
*
* description
*
* @date 15/12/17
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
/*
acf.extendArgs = function( ){
var args = Array.prototype.slice.call( arguments );
var realArgs = args.shift();
Array.prototype.push.call(arguments, 'bar')
return Array.prototype.push.apply( args, arguments );
};
*/
// Preferences
// - use try/catch to avoid JS error if cookies are disabled on front-end form
try {
var preferences = JSON.parse(localStorage.getItem('acf')) || {};
} catch(e) {
var preferences = {};
}
/**
* getPreferenceName
*
* Gets the true preference name.
* Converts "this.thing" to "thing-123" if editing post 123.
*
* @date 11/11/17
* @since 5.6.5
*
* @param string name
* @return string
*/
var getPreferenceName = function( name ){
if( name.substr(0, 5) === 'this.' ) {
name = name.substr(5) + '-' + acf.get('post_id');
}
return name;
};
/**
* acf.getPreference
*
* Gets a preference setting or null if not set.
*
* @date 11/11/17
* @since 5.6.5
*
* @param string name
* @return mixed
*/
acf.getPreference = function( name ){
name = getPreferenceName( name );
return preferences[ name ] || null;
}
/**
* acf.setPreference
*
* Sets a preference setting.
*
* @date 11/11/17
* @since 5.6.5
*
* @param string name
* @param mixed value
* @return n/a
*/
acf.setPreference = function( name, value ){
name = getPreferenceName( name );
if( value === null ) {
delete preferences[ name ];
} else {
preferences[ name ] = value;
}
localStorage.setItem('acf', JSON.stringify(preferences));
}
/**
* acf.removePreference
*
* Removes a preference setting.
*
* @date 11/11/17
* @since 5.6.5
*
* @param string name
* @return n/a
*/
acf.removePreference = function( name ){
acf.setPreference(name, null);
};
/**
* remove
*
* Removes an element with fade effect
*
* @date 1/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.remove = function( props ){
// allow jQuery
if( props instanceof jQuery ) {
props = {
target: props
};
}
// defaults
props = acf.parseArgs(props, {
target: false,
endHeight: 0,
complete: function(){}
});
// action
acf.doAction('remove', props.target);
// tr
if( props.target.is('tr') ) {
removeTr( props );
// div
} else {
removeDiv( props );
}
};
/**
* removeDiv
*
* description
*
* @date 16/2/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
var removeDiv = function( props ){
// vars
var $el = props.target;
var height = $el.height();
var width = $el.width();
var margin = $el.css('margin');
var outerHeight = $el.outerHeight(true);
var style = $el.attr('style') + ''; // needed to copy
// wrap
$el.wrap('<div class="acf-temp-remove" style="height:' + outerHeight + 'px"></div>');
var $wrap = $el.parent();
// set pos
$el.css({
height: height,
width: width,
margin: margin,
position: 'absolute'
});
// fade wrap
setTimeout(function(){
$wrap.css({
opacity: 0,
height: props.endHeight
});
}, 50);
// remove
setTimeout(function(){
$el.attr('style', style);
$wrap.remove();
props.complete();
}, 301);
};
/**
* removeTr
*
* description
*
* @date 16/2/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
var removeTr = function( props ){
// vars
var $tr = props.target;
var height = $tr.height();
var children = $tr.children().length;
// create dummy td
var $td = $('<td class="acf-temp-remove" style="padding:0; height:' + height + 'px" colspan="' + children + '"></td>');
// fade away tr
$tr.addClass('acf-remove-element');
// update HTML after fade animation
setTimeout(function(){
$tr.html( $td );
}, 251);
// allow .acf-temp-remove to exist before changing CSS
setTimeout(function(){
// remove class
$tr.removeClass('acf-remove-element');
// collapse
$td.css({
height: props.endHeight
});
}, 300);
// remove
setTimeout(function(){
$tr.remove();
props.complete();
}, 451);
};
/**
* duplicate
*
* description
*
* @date 3/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.duplicate = function( args ){
// allow jQuery
if( args instanceof jQuery ) {
args = {
target: args
};
}
// vars
var timeout = 0;
// defaults
args = acf.parseArgs(args, {
target: false,
search: '',
replace: '',
before: function( $el ){},
after: function( $el, $el2 ){},
append: function( $el, $el2 ){
$el.after( $el2 );
timeout = 1;
}
});
// compatibility
args.target = args.target || args.$el;
// vars
var $el = args.target;
// search
args.search = args.search || $el.attr('data-id');
args.replace = args.replace || acf.uniqid();
// before
// - allow acf to modify DOM
// - fixes bug where select field option is not selected
args.before( $el );
acf.doAction('before_duplicate', $el);
// clone
var $el2 = $el.clone();
// rename
acf.rename({
target: $el2,
search: args.search,
replace: args.replace,
});
// remove classes
$el2.removeClass('acf-clone');
$el2.find('.ui-sortable').removeClass('ui-sortable');
// after
// - allow acf to modify DOM
args.after( $el, $el2 );
acf.doAction('after_duplicate', $el, $el2 );
// append
args.append( $el, $el2 );
/**
* Fires after an element has been duplicated and appended to the DOM.
*
* @date 30/10/19
* @since 5.8.7
*
* @param jQuery $el The original element.
* @param jQuery $el2 The duplicated element.
*/
acf.doAction('duplicate', $el, $el2 );
// append
// - allow element to be moved into a visible position before fire action
//var callback = function(){
acf.doAction('append', $el2);
//};
//if( timeout ) {
// setTimeout(callback, timeout);
//} else {
// callback();
//}
// return
return $el2;
};
/**
* rename
*
* description
*
* @date 7/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.rename = function( args ){
// allow jQuery
if( args instanceof jQuery ) {
args = {
target: args
};
}
// defaults
args = acf.parseArgs(args, {
target: false,
destructive: false,
search: '',
replace: '',
});
// vars
var $el = args.target;
var search = args.search || $el.attr('data-id');
var replace = args.replace || acf.uniqid('acf');
var replaceAttr = function(i, value){
return value.replace( search, replace );
}
// replace (destructive)
if( args.destructive ) {
var html = $el.outerHTML();
html = acf.strReplace( search, replace, html );
$el.replaceWith( html );
// replace
} else {
$el.attr('data-id', replace);
$el.find('[id*="' + search + '"]').attr('id', replaceAttr);
$el.find('[for*="' + search + '"]').attr('for', replaceAttr);
$el.find('[name*="' + search + '"]').attr('name', replaceAttr);
}
// return
return $el;
};
/**
* acf.prepareForAjax
*
* description
*
* @date 4/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.prepareForAjax = function( data ){
// required
data.nonce = acf.get('nonce');
data.post_id = acf.get('post_id');
// language
if( acf.has('language') ) {
data.lang = acf.get('language');
}
// filter for 3rd party customization
data = acf.applyFilters('prepare_for_ajax', data);
// return
return data;
};
/**
* acf.startButtonLoading
*
* description
*
* @date 5/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.startButtonLoading = function( $el ){
$el.prop('disabled', true);
$el.after(' <i class="acf-loading"></i>');
}
acf.stopButtonLoading = function( $el ){
$el.prop('disabled', false);
$el.next('.acf-loading').remove();
}
/**
* acf.showLoading
*
* description
*
* @date 12/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.showLoading = function( $el ){
$el.append('<div class="acf-loading-overlay"><i class="acf-loading"></i></div>');
};
acf.hideLoading = function( $el ){
$el.children('.acf-loading-overlay').remove();
};
/**
* acf.updateUserSetting
*
* description
*
* @date 5/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.updateUserSetting = function( name, value ){
var ajaxData = {
action: 'acf/ajax/user_setting',
name: name,
value: value
};
$.ajax({
url: acf.get('ajaxurl'),
data: acf.prepareForAjax(ajaxData),
type: 'post',
dataType: 'html'
});
};
/**
* acf.val
*
* description
*
* @date 8/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.val = function( $input, value, silent ){
// vars
var prevValue = $input.val();
// bail if no change
if( value === prevValue ) {
return false
}
// update value
$input.val( value );
// prevent select elements displaying blank value if option doesn't exist
if( $input.is('select') && $input.val() === null ) {
$input.val( prevValue );
return false;
}
// update with trigger
if( silent !== true ) {
$input.trigger('change');
}
// return
return true;
};
/**
* acf.show
*
* description
*
* @date 9/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.show = function( $el, lockKey ){
// unlock
if( lockKey ) {
acf.unlock($el, 'hidden', lockKey);
}
// bail early if $el is still locked
if( acf.isLocked($el, 'hidden') ) {
//console.log( 'still locked', getLocks( $el, 'hidden' ));
return false;
}
// $el is hidden, remove class and return true due to change in visibility
if( $el.hasClass('acf-hidden') ) {
$el.removeClass('acf-hidden');
return true;
// $el is visible, return false due to no change in visibility
} else {
return false;
}
};
/**
* acf.hide
*
* description
*
* @date 9/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.hide = function( $el, lockKey ){
// lock
if( lockKey ) {
acf.lock($el, 'hidden', lockKey);
}
// $el is hidden, return false due to no change in visibility
if( $el.hasClass('acf-hidden') ) {
return false;
// $el is visible, add class and return true due to change in visibility
} else {
$el.addClass('acf-hidden');
return true;
}
};
/**
* acf.isHidden
*
* description
*
* @date 9/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.isHidden = function( $el ){
return $el.hasClass('acf-hidden');
};
/**
* acf.isVisible
*
* description
*
* @date 9/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.isVisible = function( $el ){
return !acf.isHidden( $el );
};
/**
* enable
*
* description
*
* @date 12/3/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
var enable = function( $el, lockKey ){
// check class. Allow .acf-disabled to overrule all JS
if( $el.hasClass('acf-disabled') ) {
return false;
}
// unlock
if( lockKey ) {
acf.unlock($el, 'disabled', lockKey);
}
// bail early if $el is still locked
if( acf.isLocked($el, 'disabled') ) {
return false;
}
// $el is disabled, remove prop and return true due to change
if( $el.prop('disabled') ) {
$el.prop('disabled', false);
return true;
// $el is enabled, return false due to no change
} else {
return false;
}
};
/**
* acf.enable
*
* description
*
* @date 9/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.enable = function( $el, lockKey ){
// enable single input
if( $el.attr('name') ) {
return enable( $el, lockKey );
}
// find and enable child inputs
// return true if any inputs have changed
var results = false;
$el.find('[name]').each(function(){
var result = enable( $(this), lockKey );
if( result ) {
results = true;
}
});
return results;
};
/**
* disable
*
* description
*
* @date 12/3/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
var disable = function( $el, lockKey ){
// lock
if( lockKey ) {
acf.lock($el, 'disabled', lockKey);
}
// $el is disabled, return false due to no change
if( $el.prop('disabled') ) {
return false;
// $el is enabled, add prop and return true due to change
} else {
$el.prop('disabled', true);
return true;
}
};
/**
* acf.disable
*
* description
*
* @date 9/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.disable = function( $el, lockKey ){
// disable single input
if( $el.attr('name') ) {
return disable( $el, lockKey );
}
// find and enable child inputs
// return true if any inputs have changed
var results = false;
$el.find('[name]').each(function(){
var result = disable( $(this), lockKey );
if( result ) {
results = true;
}
});
return results;
};
/**
* acf.isset
*
* description
*
* @date 10/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.isset = function( obj /*, level1, level2, ... */ ) {
for( var i = 1; i < arguments.length; i++ ) {
if( !obj || !obj.hasOwnProperty(arguments[i]) ) {
return false;
}
obj = obj[ arguments[i] ];
}
return true;
};
/**
* acf.isget
*
* description
*
* @date 10/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.isget = function( obj /*, level1, level2, ... */ ) {
for( var i = 1; i < arguments.length; i++ ) {
if( !obj || !obj.hasOwnProperty(arguments[i]) ) {
return null;
}
obj = obj[ arguments[i] ];
}
return obj;
};
/**
* acf.getFileInputData
*
* description
*
* @date 10/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.getFileInputData = function( $input, callback ){
// vars
var value = $input.val();
// bail early if no value
if( !value ) {
return false;
}
// data
var data = {
url: value
};
// modern browsers
var file = acf.isget( $input[0], 'files', 0);
if( file ){
// update data
data.size = file.size;
data.type = file.type;
// image
if( file.type.indexOf('image') > -1 ) {
// vars
var windowURL = window.URL || window.webkitURL;
var img = new Image();
img.onload = function() {
// update
data.width = this.width;
data.height = this.height;
callback( data );
};
img.src = windowURL.createObjectURL( file );
} else {
callback( data );
}
} else {
callback( data );
}
};
/**
* acf.isAjaxSuccess
*
* description
*
* @date 18/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.isAjaxSuccess = function( json ){
return ( json && json.success );
};
/**
* acf.getAjaxMessage
*
* description
*
* @date 18/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.getAjaxMessage = function( json ){
return acf.isget( json, 'data', 'message' );
};
/**
* acf.getAjaxError
*
* description
*
* @date 18/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.getAjaxError = function( json ){
return acf.isget( json, 'data', 'error' );
};
/**
* acf.renderSelect
*
* Renders the innter html for a select field.
*
* @date 19/2/18
* @since 5.6.9
*
* @param jQuery $select The select element.
* @param array choices An array of choices.
* @return void
*/
acf.renderSelect = function( $select, choices ){
// vars
var value = $select.val();
var values = [];
// callback
var crawl = function( items ){
// vars
var itemsHtml = '';
// loop
items.map(function( item ){
// vars
var text = item.text || item.label || '';
var id = item.id || item.value || '';
// append
values.push(id);
// optgroup
if( item.children ) {
itemsHtml += '<optgroup label="' + acf.strEscape(text) + '">' + crawl( item.children ) + '</optgroup>';
// option
} else {
itemsHtml += '<option value="' + id + '"' + (item.disabled ? ' disabled="disabled"' : '') + '>' + acf.strEscape(text) + '</option>';
}
});
// return
return itemsHtml;
};
// update HTML
$select.html( crawl(choices) );
// update value
if( values.indexOf(value) > -1 ){
$select.val( value );
}
// return selected value
return $select.val();
};
/**
* acf.lock
*
* Creates a "lock" on an element for a given type and key
*
* @date 22/2/18
* @since 5.6.9
*
* @param jQuery $el The element to lock.
* @param string type The type of lock such as "condition" or "visibility".
* @param string key The key that will be used to unlock.
* @return void
*/
var getLocks = function( $el, type ){
return $el.data('acf-lock-'+type) || [];
};
var setLocks = function( $el, type, locks ){
$el.data('acf-lock-'+type, locks);
}
acf.lock = function( $el, type, key ){
var locks = getLocks( $el, type );
var i = locks.indexOf(key);
if( i < 0 ) {
locks.push( key );
setLocks( $el, type, locks );
}
};
/**
* acf.unlock
*
* Unlocks a "lock" on an element for a given type and key
*
* @date 22/2/18
* @since 5.6.9
*
* @param jQuery $el The element to lock.
* @param string type The type of lock such as "condition" or "visibility".
* @param string key The key that will be used to unlock.
* @return void
*/
acf.unlock = function( $el, type, key ){
var locks = getLocks( $el, type );
var i = locks.indexOf(key);
if( i > -1 ) {
locks.splice(i, 1);
setLocks( $el, type, locks );
}
// return true if is unlocked (no locks)
return (locks.length === 0);
};
/**
* acf.isLocked
*
* Returns true if a lock exists for a given type
*
* @date 22/2/18
* @since 5.6.9
*
* @param jQuery $el The element to lock.
* @param string type The type of lock such as "condition" or "visibility".
* @return void
*/
acf.isLocked = function( $el, type ){
return ( getLocks( $el, type ).length > 0 );
};
/**
* acf.isGutenberg
*
* Returns true if the Gutenberg editor is being used.
*
* @date 14/11/18
* @since 5.8.0
*
* @param vois
* @return bool
*/
acf.isGutenberg = function(){
return ( window.wp && wp.data && wp.data.select && wp.data.select( 'core/editor' ) );
};
/**
* acf.objectToArray
*
* Returns an array of items from the given object.
*
* @date 20/11/18
* @since 5.8.0
*
* @param object obj The object of items.
* @return array
*/
acf.objectToArray = function( obj ){
return Object.keys( obj ).map(function( key ){
return obj[key];
});
};
/**
* acf.debounce
*
* Returns a debounced version of the passed function which will postpone its execution until after `wait` milliseconds have elapsed since the last time it was invoked.
*
* @date 28/8/19
* @since 5.8.1
*
* @param function callback The callback function.
* @return int wait The number of milliseconds to wait.
*/
acf.debounce = function( callback, wait ){
var timeout;
return function(){
var context = this;
var args = arguments;
var later = function(){
callback.apply( context, args );
};
clearTimeout( timeout );
timeout = setTimeout( later, wait );
};
};
/**
* acf.throttle
*
* Returns a throttled version of the passed function which will allow only one execution per `limit` time period.
*
* @date 28/8/19
* @since 5.8.1
*
* @param function callback The callback function.
* @return int wait The number of milliseconds to wait.
*/
acf.throttle = function( callback, limit ){
var busy = false;
return function(){
if( busy ) return;
busy = true;
setTimeout(function(){
busy = false;
}, limit);
callback.apply( this, arguments );
};
};
/**
* acf.isInView
*
* Returns true if the given element is in view.
*
* @date 29/8/19
* @since 5.8.1
*
* @param elem el The dom element to inspect.
* @return bool
*/
acf.isInView = function( el ){
var rect = el.getBoundingClientRect();
return (
rect.top !== rect.bottom &&
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
};
/**
* acf.onceInView
*
* Watches for a dom element to become visible in the browser and then excecutes the passed callback.
*
* @date 28/8/19
* @since 5.8.1
*
* @param dom el The dom element to inspect.
* @param function callback The callback function.
*/
acf.onceInView = (function() {
// Define list.
var items = [];
var id = 0;
// Define check function.
var check = function() {
items.forEach(function( item ){
if( acf.isInView(item.el) ) {
item.callback.apply( this );
pop( item.id );
}
});
};
// And create a debounced version.
var debounced = acf.debounce( check, 300 );
// Define add function.
var push = function( el, callback ) {
// Add event listener.
if( !items.length ) {
$(window).on( 'scroll resize', debounced ).on( 'acfrefresh orientationchange', check );
}
// Append to list.
items.push({ id: id++, el: el, callback: callback });
}
// Define remove function.
var pop = function( id ) {
// Remove from list.
items = items.filter(function(item) {
return (item.id !== id);
});
// Clean up listener.
if( !items.length ) {
$(window).off( 'scroll resize', debounced ).off( 'acfrefresh orientationchange', check );
}
}
// Define returned function.
return function( el, callback ){
// Allow jQuery object.
if( el instanceof jQuery )
el = el[0];
// Execute callback if already in view or add to watch list.
if( acf.isInView(el) ) {
callback.apply( this );
} else {
push( el, callback );
}
}
})();
/**
* acf.once
*
* Creates a function that is restricted to invoking `func` once.
*
* @date 2/9/19
* @since 5.8.1
*
* @param function func The function to restrict.
* @return function
*/
acf.once = function( func ){
var i = 0;
return function(){
if( i++ > 0 ) {
return (func = undefined);
}
return func.apply(this, arguments);
}
}
/*
* 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;
};
/*
* 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);
};
}
// Set up actions from events
$(document).ready(function(){
acf.doAction('ready');
});
$(window).on('load', function(){
acf.doAction('load');
});
$(window).on('beforeunload', function(){
acf.doAction('unload');
});
$(window).on('resize', function(){
acf.doAction('resize');
});
$(document).on('sortstart', function( event, ui ) {
acf.doAction('sortstart', ui.item, ui.placeholder);
});
$(document).on('sortstop', function( event, ui ) {
acf.doAction('sortstop', ui.item, ui.placeholder);
});
})(jQuery);
( 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;
};
// instantiate
acf.hooks = new EventManager();
} )( window );
(function($, undefined){
// Cached regex to split keys for `addEvent`.
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
/**
* extend
*
* Helper function to correctly set up the prototype chain for subclasses
* Heavily inspired by backbone.js
*
* @date 14/12/17
* @since 5.6.5
*
* @param object protoProps New properties for this object.
* @return function.
*/
var extend = function( protoProps ) {
// vars
var Parent = this;
var Child;
// The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent constructor.
if( protoProps && protoProps.hasOwnProperty('constructor') ) {
Child = protoProps.constructor;
} else {
Child = function(){ return Parent.apply(this, arguments); };
}
// Add static properties to the constructor function, if supplied.
$.extend(Child, Parent);
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function and add the prototype properties.
Child.prototype = Object.create(Parent.prototype);
$.extend(Child.prototype, protoProps);
Child.prototype.constructor = Child;
// Set a convenience property in case the parent's prototype is needed later.
//Child.prototype.__parent__ = Parent.prototype;
// return
return Child;
};
/**
* Model
*
* Base class for all inheritence
*
* @date 14/12/17
* @since 5.6.5
*
* @param object props
* @return function.
*/
var Model = acf.Model = function(){
// generate uique client id
this.cid = acf.uniqueId('acf');
// set vars to avoid modifying prototype
this.data = $.extend(true, {}, this.data);
// pass props to setup function
this.setup.apply(this, arguments);
// store on element (allow this.setup to create this.$el)
if( this.$el && !this.$el.data('acf') ) {
this.$el.data('acf', this);
}
// initialize
var initialize = function(){
this.initialize();
this.addEvents();
this.addActions();
this.addFilters();
};
// initialize on action
if( this.wait && !acf.didAction(this.wait) ) {
this.addAction(this.wait, initialize);
// initialize now
} else {
initialize.apply(this);
}
};
// Attach all inheritable methods to the Model prototype.
$.extend(Model.prototype, {
// Unique model id
id: '',
// Unique client id
cid: '',
// jQuery element
$el: null,
// Data specific to this instance
data: {},
// toggle used when changing data
busy: false,
changed: false,
// Setup events hooks
events: {},
actions: {},
filters: {},
// class used to avoid nested event triggers
eventScope: '',
// action to wait until initialize
wait: false,
// action priority default
priority: 10,
/**
* get
*
* Gets a specific data value
*
* @date 14/12/17
* @since 5.6.5
*
* @param string name
* @return mixed
*/
get: function( name ) {
return this.data[name];
},
/**
* has
*
* Returns `true` if the data exists and is not null
*
* @date 14/12/17
* @since 5.6.5
*
* @param string name
* @return boolean
*/
has: function( name ) {
return this.get(name) != null;
},
/**
* set
*
* Sets a specific data value
*
* @date 14/12/17
* @since 5.6.5
*
* @param string name
* @param mixed value
* @return this
*/
set: function( name, value, silent ) {
// bail if unchanged
var prevValue = this.get(name);
if( prevValue == value ) {
return this;
}
// set data
this.data[ name ] = value;
// trigger events
if( !silent ) {
this.changed = true;
this.trigger('changed:' + name, [value, prevValue]);
this.trigger('changed', [name, value, prevValue]);
}
// return
return this;
},
/**
* inherit
*
* Inherits the data from a jQuery element
*
* @date 14/12/17
* @since 5.6.5
*
* @param jQuery $el
* @return this
*/
inherit: function( data ){
// allow jQuery
if( data instanceof jQuery ) {
data = data.data();
}
// extend
$.extend(this.data, data);
// return
return this;
},
/**
* prop
*
* mimics the jQuery prop function
*
* @date 4/6/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
prop: function(){
return this.$el.prop.apply(this.$el, arguments);
},
/**
* setup
*
* Run during constructor function
*
* @date 14/12/17
* @since 5.6.5
*
* @param n/a
* @return n/a
*/
setup: function( props ){
$.extend(this, props);
},
/**
* initialize
*
* Also run during constructor function
*
* @date 14/12/17
* @since 5.6.5
*
* @param n/a
* @return n/a
*/
initialize: function(){},
/**
* addElements
*
* Adds multiple jQuery elements to this object
*
* @date 9/5/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
addElements: function( elements ){
elements = elements || this.elements || null;
if( !elements || !Object.keys(elements).length ) return false;
for( var i in elements ) {
this.addElement( i, elements[i] );
}
},
/**
* addElement
*
* description
*
* @date 9/5/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
addElement: function( name, selector){
this[ '$' + name ] = this.$( selector );
},
/**
* addEvents
*
* Adds multiple event handlers
*
* @date 14/12/17
* @since 5.6.5
*
* @param object events {event1 : callback, event2 : callback, etc }
* @return n/a
*/
addEvents: function( events ){
events = events || this.events || null;
if( !events ) return false;
for( var key in events ) {
var match = key.match(delegateEventSplitter);
this.on(match[1], match[2], events[key]);
}
},
/**
* removeEvents
*
* Removes multiple event handlers
*
* @date 14/12/17
* @since 5.6.5
*
* @param object events {event1 : callback, event2 : callback, etc }
* @return n/a
*/
removeEvents: function( events ){
events = events || this.events || null;
if( !events ) return false;
for( var key in events ) {
var match = key.match(delegateEventSplitter);
this.off(match[1], match[2], events[key]);
}
},
/**
* getEventTarget
*
* Returns a jQUery element to tigger an event on
*
* @date 5/6/18
* @since 5.6.9
*
* @param jQuery $el The default jQuery element. Optional.
* @param string event The event name. Optional.
* @return jQuery
*/
getEventTarget: function( $el, event ){
return $el || this.$el || $(document);
},
/**
* validateEvent
*
* Returns true if the event target's closest $el is the same as this.$el
* Requires both this.el and this.$el to be defined
*
* @date 5/6/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
validateEvent: function( e ){
if( this.eventScope ) {
return $( e.target ).closest( this.eventScope ).is( this.$el );
} else {
return true;
}
},
/**
* proxyEvent
*
* Returns a new event callback function scoped to this model
*
* @date 29/3/18
* @since 5.6.9
*
* @param function callback
* @return function
*/
proxyEvent: function( callback ){
return this.proxy(function(e){
// validate
if( !this.validateEvent(e) ) {
return;
}
// construct args
var args = acf.arrayArgs( arguments );
var extraArgs = args.slice(1);
var eventArgs = [ e, $(e.currentTarget) ].concat( extraArgs );
// callback
callback.apply(this, eventArgs);
});
},
/**
* on
*
* Adds an event handler similar to jQuery
* Uses the instance 'cid' to namespace event
*
* @date 14/12/17
* @since 5.6.5
*
* @param string name
* @param string callback
* @return n/a
*/
on: function( a1, a2, a3, a4 ){
// vars
var $el, event, selector, callback, args;
// find args
if( a1 instanceof jQuery ) {
// 1. args( $el, event, selector, callback )
if( a4 ) {
$el = a1; event = a2; selector = a3; callback = a4;
// 2. args( $el, event, callback )
} else {
$el = a1; event = a2; callback = a3;
}
} else {
// 3. args( event, selector, callback )
if( a3 ) {
event = a1; selector = a2; callback = a3;
// 4. args( event, callback )
} else {
event = a1; callback = a2;
}
}
// element
$el = this.getEventTarget( $el );
// modify callback
if( typeof callback === 'string' ) {
callback = this.proxyEvent( this[callback] );
}
// modify event
event = event + '.' + this.cid;
// args
if( selector ) {
args = [ event, selector, callback ];
} else {
args = [ event, callback ];
}
// on()
$el.on.apply($el, args);
},
/**
* off
*
* Removes an event handler similar to jQuery
*
* @date 14/12/17
* @since 5.6.5
*
* @param string name
* @param string callback
* @return n/a
*/
off: function( a1, a2 ,a3 ){
// vars
var $el, event, selector, args;
// find args
if( a1 instanceof jQuery ) {
// 1. args( $el, event, selector )
if( a3 ) {
$el = a1; event = a2; selector = a3;
// 2. args( $el, event )
} else {
$el = a1; event = a2;
}
} else {
// 3. args( event, selector )
if( a2 ) {
event = a1; selector = a2;
// 4. args( event )
} else {
event = a1;
}
}
// element
$el = this.getEventTarget( $el );
// modify event
event = event + '.' + this.cid;
// args
if( selector ) {
args = [ event, selector ];
} else {
args = [ event ];
}
// off()
$el.off.apply($el, args);
},
/**
* trigger
*
* Triggers an event similar to jQuery
*
* @date 14/12/17
* @since 5.6.5
*
* @param string name
* @param string callback
* @return n/a
*/
trigger: function( name, args, bubbles ){
var $el = this.getEventTarget();
if( bubbles ) {
$el.trigger.apply( $el, arguments );
} else {
$el.triggerHandler.apply( $el, arguments );
}
return this;
},
/**
* addActions
*
* Adds multiple action handlers
*
* @date 14/12/17
* @since 5.6.5
*
* @param object actions {action1 : callback, action2 : callback, etc }
* @return n/a
*/
addActions: function( actions ){
actions = actions || this.actions || null;
if( !actions ) return false;
for( var i in actions ) {
this.addAction( i, actions[i] );
}
},
/**
* removeActions
*
* Removes multiple action handlers
*
* @date 14/12/17
* @since 5.6.5
*
* @param object actions {action1 : callback, action2 : callback, etc }
* @return n/a
*/
removeActions: function( actions ){
actions = actions || this.actions || null;
if( !actions ) return false;
for( var i in actions ) {
this.removeAction( i, actions[i] );
}
},
/**
* addAction
*
* Adds an action using the wp.hooks library
*
* @date 14/12/17
* @since 5.6.5
*
* @param string name
* @param string callback
* @return n/a
*/
addAction: function( name, callback, priority ){
//console.log('addAction', name, priority);
// defaults
priority = priority || this.priority;
// modify callback
if( typeof callback === 'string' ) {
callback = this[ callback ];
}
// add
acf.addAction(name, callback, priority, this);
},
/**
* removeAction
*
* Remove an action using the wp.hooks library
*
* @date 14/12/17
* @since 5.6.5
*
* @param string name
* @param string callback
* @return n/a
*/
removeAction: function( name, callback ){
acf.removeAction(name, this[ callback ]);
},
/**
* addFilters
*
* Adds multiple filter handlers
*
* @date 14/12/17
* @since 5.6.5
*
* @param object filters {filter1 : callback, filter2 : callback, etc }
* @return n/a
*/
addFilters: function( filters ){
filters = filters || this.filters || null;
if( !filters ) return false;
for( var i in filters ) {
this.addFilter( i, filters[i] );
}
},
/**
* addFilter
*
* Adds a filter using the wp.hooks library
*
* @date 14/12/17
* @since 5.6.5
*
* @param string name
* @param string callback
* @return n/a
*/
addFilter: function( name, callback, priority ){
// defaults
priority = priority || this.priority;
// modify callback
if( typeof callback === 'string' ) {
callback = this[ callback ];
}
// add
acf.addFilter(name, callback, priority, this);
},
/**
* removeFilters
*
* Removes multiple filter handlers
*
* @date 14/12/17
* @since 5.6.5
*
* @param object filters {filter1 : callback, filter2 : callback, etc }
* @return n/a
*/
removeFilters: function( filters ){
filters = filters || this.filters || null;
if( !filters ) return false;
for( var i in filters ) {
this.removeFilter( i, filters[i] );
}
},
/**
* removeFilter
*
* Remove a filter using the wp.hooks library
*
* @date 14/12/17
* @since 5.6.5
*
* @param string name
* @param string callback
* @return n/a
*/
removeFilter: function( name, callback ){
acf.removeFilter(name, this[ callback ]);
},
/**
* $
*
* description
*
* @date 16/12/17
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
$: function( selector ){
return this.$el.find( selector );
},
/**
* remove
*
* Removes the element and listenters
*
* @date 19/12/17
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
remove: function(){
this.removeEvents();
this.removeActions();
this.removeFilters();
this.$el.remove();
},
/**
* setTimeout
*
* description
*
* @date 16/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
setTimeout: function( callback, milliseconds ){
return setTimeout( this.proxy(callback), milliseconds );
},
/**
* time
*
* used for debugging
*
* @date 7/3/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
time: function(){
console.time( this.id || this.cid );
},
/**
* timeEnd
*
* used for debugging
*
* @date 7/3/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
timeEnd: function(){
console.timeEnd( this.id || this.cid );
},
/**
* show
*
* description
*
* @date 15/3/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
show: function(){
acf.show( this.$el );
},
/**
* hide
*
* description
*
* @date 15/3/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
hide: function(){
acf.hide( this.$el );
},
/**
* proxy
*
* Returns a new function scoped to this model
*
* @date 29/3/18
* @since 5.6.9
*
* @param function callback
* @return function
*/
proxy: function( callback ){
return $.proxy( callback, this );
}
});
// Set up inheritance for the model
Model.extend = extend;
// Global model storage
acf.models = {};
/**
* acf.getInstance
*
* This function will get an instance from an element
*
* @date 5/3/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.getInstance = function( $el ){
return $el.data('acf');
};
/**
* acf.getInstances
*
* This function will get an array of instances from multiple elements
*
* @date 5/3/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.getInstances = function( $el ){
var instances = [];
$el.each(function(){
instances.push( acf.getInstance( $(this) ) );
});
return instances;
};
})(jQuery);
(function($, undefined){
acf.models.Popup = acf.Model.extend({
data: {
title: '',
content: '',
width: 0,
height: 0,
loading: false,
},
events: {
'click [data-event="close"]': 'onClickClose',
'click .acf-close-popup': 'onClickClose',
},
setup: function( props ){
$.extend(this.data, props);
this.$el = $(this.tmpl());
},
initialize: function(){
this.render();
this.open();
},
tmpl: function(){
return [
'<div id="acf-popup">',
'<div class="acf-popup-box acf-box">',
'<div class="title"><h3></h3><a href="#" class="acf-icon -cancel grey" data-event="close"></a></div>',
'<div class="inner"></div>',
'<div class="loading"><i class="acf-loading"></i></div>',
'</div>',
'<div class="bg" data-event="close"></div>',
'</div>'
].join('');
},
render: function(){
// vars
var title = this.get('title');
var content = this.get('content');
var loading = this.get('loading');
var width = this.get('width');
var height = this.get('height');
// html
this.title( title );
this.content( content );
// width
if( width ) {
this.$('.acf-popup-box').css('width', width);
}
// height
if( height ) {
this.$('.acf-popup-box').css('min-height', height);
}
// loading
this.loading( loading );
// action
acf.doAction('append', this.$el);
},
update: function( props ){
this.data = acf.parseArgs(props, this.data);
this.render();
},
title: function( title ){
this.$('.title:first h3').html( title );
},
content: function( content ){
this.$('.inner:first').html( content );
},
loading: function( show ){
var $loading = this.$('.loading:first');
show ? $loading.show() : $loading.hide();
},
open: function(){
$('body').append( this.$el );
},
close: function(){
this.remove();
},
onClickClose: function( e, $el ){
e.preventDefault();
this.close();
}
});
/**
* newPopup
*
* Creates a new Popup with the supplied props
*
* @date 17/12/17
* @since 5.6.5
*
* @param object props
* @return object
*/
acf.newPopup = function( props ){
return new acf.models.Popup( props );
};
})(jQuery);
(function($, undefined){
acf.unload = new acf.Model({
wait: 'load',
active: true,
changed: false,
actions: {
'validation_failure': 'startListening',
'validation_success': 'stopListening'
},
events: {
'change form .acf-field': 'startListening',
'submit form': 'stopListening'
},
enable: function(){
this.active = true;
},
disable: function(){
this.active = false;
},
reset: function(){
this.stopListening();
},
startListening: function(){
// bail ealry if already changed, not active
if( this.changed || !this.active ) {
return;
}
// update
this.changed = true;
// add event
$(window).on('beforeunload', this.onUnload);
},
stopListening: function(){
// update
this.changed = false;
// remove event
$(window).off('beforeunload', this.onUnload);
},
onUnload: function(){
return acf.__('The changes you made will be lost if you navigate away from this page');
}
});
})(jQuery);
(function($, undefined){
var panel = new acf.Model({
events: {
'click .acf-panel-title': 'onClick',
},
onClick: function( e, $el ){
e.preventDefault();
this.toggle( $el.parent() );
},
isOpen: function( $el ) {
return $el.hasClass('-open');
},
toggle: function( $el ){
this.isOpen($el) ? this.close( $el ) : this.open( $el );
},
open: function( $el ){
$el.addClass('-open');
$el.find('.acf-panel-title i').attr('class', 'dashicons dashicons-arrow-down');
},
close: function( $el ){
$el.removeClass('-open');
$el.find('.acf-panel-title i').attr('class', 'dashicons dashicons-arrow-right');
}
});
})(jQuery);
(function($, undefined){
var Notice = acf.Model.extend({
data: {
text: '',
type: '',
timeout: 0,
dismiss: true,
target: false,
close: function(){}
},
events: {
'click .acf-notice-dismiss': 'onClickClose',
},
tmpl: function(){
return '<div class="acf-notice"></div>';
},
setup: function( props ){
$.extend(this.data, props);
this.$el = $(this.tmpl());
},
initialize: function(){
// render
this.render();
// show
this.show();
},
render: function(){
// class
this.type( this.get('type') );
// text
this.html( '<p>' + this.get('text') + '</p>' );
// close
if( this.get('dismiss') ) {
this.$el.append('<a href="#" class="acf-notice-dismiss acf-icon -cancel small"></a>');
this.$el.addClass('-dismiss');
}
// timeout
var timeout = this.get('timeout');
if( timeout ) {
this.away( timeout );
}
},
update: function( props ){
// update
$.extend(this.data, props);
// re-initialize
this.initialize();
// refresh events
this.removeEvents();
this.addEvents();
},
show: function(){
var $target = this.get('target');
if( $target ) {
$target.prepend( this.$el );
}
},
hide: function(){
this.$el.remove();
},
away: function( timeout ){
this.setTimeout(function(){
acf.remove( this.$el );
}, timeout );
},
type: function( type ){
// remove prev type
var prevType = this.get('type');
if( prevType ) {
this.$el.removeClass('-' + prevType);
}
// add new type
this.$el.addClass('-' + type);
// backwards compatibility
if( type == 'error' ) {
this.$el.addClass('acf-error-message');
}
},
html: function( html ){
this.$el.html( html );
},
text: function( text ){
this.$('p').html( text );
},
onClickClose: function( e, $el ){
e.preventDefault();
this.get('close').apply(this, arguments);
this.remove();
}
});
acf.newNotice = function( props ){
// ensure object
if( typeof props !== 'object' ) {
props = { text: props };
}
// instantiate
return new Notice( props );
};
var noticeManager = new acf.Model({
wait: 'prepare',
priority: 1,
initialize: function(){
// vars
var $notice = $('.acf-admin-notice');
// move to avoid WP flicker
if( $notice.length ) {
$('h1:first').after( $notice );
}
}
});
})(jQuery);
(function($, undefined){
/**
* postboxManager
*
* Manages postboxes on the screen.
*
* @date 25/5/19
* @since 5.8.1
*
* @param void
* @return void
*/
var postboxManager = new acf.Model({
wait: 'prepare',
priority: 1,
initialize: function(){
(acf.get('postboxes') || []).map( acf.newPostbox );
},
});
/**
* acf.getPostbox
*
* Returns a postbox instance.
*
* @date 23/9/18
* @since 5.7.7
*
* @param mixed $el Either a jQuery element or the postbox id.
* @return object
*/
acf.getPostbox = function( $el ){
// allow string parameter
if( typeof arguments[0] == 'string' ) {
$el = $('#' + arguments[0]);
}
// return instance
return acf.getInstance( $el );
};
/**
* acf.getPostboxes
*
* Returns an array of postbox instances.
*
* @date 23/9/18
* @since 5.7.7
*
* @param void
* @return array
*/
acf.getPostboxes = function(){
return acf.getInstances( $('.acf-postbox') );
};
/**
* acf.newPostbox
*
* Returns a new postbox instance for the given props.
*
* @date 20/9/18
* @since 5.7.6
*
* @param object props The postbox properties.
* @return object
*/
acf.newPostbox = function( props ){
return new acf.models.Postbox( props );
};
/**
* acf.models.Postbox
*
* The postbox model.
*
* @date 20/9/18
* @since 5.7.6
*
* @param void
* @return void
*/
acf.models.Postbox = acf.Model.extend({
data: {
id: '',
key: '',
style: 'default',
label: 'top',
edit: ''
},
setup: function( props ){
// compatibilty
if( props.editLink ) {
props.edit = props.editLink;
}
// extend data
$.extend(this.data, props);
// set $el
this.$el = this.$postbox();
},
$postbox: function(){
return $('#' + this.get('id'));
},
$hide: function(){
return $('#' + this.get('id') + '-hide');
},
$hideLabel: function(){
return this.$hide().parent();
},
$hndle: function(){
return this.$('> .hndle');
},
$inside: function(){
return this.$('> .inside');
},
isVisible: function(){
return this.$el.hasClass('acf-hidden');
},
initialize: function(){
// Add default class.
this.$el.addClass('acf-postbox');
// Remove 'hide-if-js class.
// This class is added by WP to postboxes that are hidden via the "Screen Options" tab.
this.$el.removeClass('hide-if-js');
// Add field group style class (ignore in block editor).
if( acf.get('editor') !== 'block' ) {
var style = this.get('style');
if( style !== 'default' ) {
this.$el.addClass( style );
}
}
// Add .inside class.
this.$inside().addClass('acf-fields').addClass('-' + this.get('label'));
// Append edit link.
var edit = this.get('edit');
if( edit ) {
this.$hndle().append('<a href="' + edit + '" class="dashicons dashicons-admin-generic acf-hndle-cog acf-js-tooltip" title="' + acf.__('Edit field group') + '"></a>');
}
// Show postbox.
this.show();
},
show: function(){
// Show label.
this.$hideLabel().show();
// toggle on checkbox
this.$hide().prop('checked', true);
// Show postbox
this.$el.show().removeClass('acf-hidden');
// Do action.
acf.doAction('show_postbox', this);
},
enable: function(){
acf.enable( this.$el, 'postbox' );
},
showEnable: function(){
this.enable();
this.show();
},
hide: function(){
// Hide label.
this.$hideLabel().hide();
// Hide postbox
this.$el.hide().addClass('acf-hidden');
// Do action.
acf.doAction('hide_postbox', this);
},
disable: function(){
acf.disable( this.$el, 'postbox' );
},
hideDisable: function(){
this.disable();
this.hide();
},
html: function( html ){
// Update HTML.
this.$inside().html( html );
// Do action.
acf.doAction('append', this.$el);
}
});
})(jQuery);
(function($, undefined){
acf.newTooltip = function( props ){
// ensure object
if( typeof props !== 'object' ) {
props = { text: props };
}
// confirmRemove
if( props.confirmRemove !== undefined ) {
props.textConfirm = acf.__('Remove');
props.textCancel = acf.__('Cancel');
return new TooltipConfirm( props );
// confirm
} else if( props.confirm !== undefined ) {
return new TooltipConfirm( props );
// default
} else {
return new Tooltip( props );
}
};
var Tooltip = acf.Model.extend({
data: {
text: '',
timeout: 0,
target: null
},
tmpl: function(){
return '<div class="acf-tooltip"></div>';
},
setup: function( props ){
$.extend(this.data, props);
this.$el = $(this.tmpl());
},
initialize: function(){
// render
this.render();
// append
this.show();
// position
this.position();
// timeout
var timeout = this.get('timeout');
if( timeout ) {
setTimeout( $.proxy(this.fade, this), timeout );
}
},
update: function( props ){
$.extend(this.data, props);
this.initialize();
},
render: function(){
this.html( this.get('text') );
},
show: function(){
$('body').append( this.$el );
},
hide: function(){
this.$el.remove();
},
fade: function(){
// add class
this.$el.addClass('acf-fade-up');
// remove
this.setTimeout(function(){
this.remove();
}, 250);
},
html: function( html ){
this.$el.html( html );
},
position: function(){
// vars
var $tooltip = this.$el;
var $target = this.get('target');
if( !$target ) return;
// Reset position.
$tooltip.removeClass('right left bottom top').css({ top: 0, left: 0 });
// Declare tollerance to edge of screen.
var tolerance = 10;
// Find target position.
var targetWidth = $target.outerWidth();
var targetHeight = $target.outerHeight();
var targetTop = $target.offset().top;
var targetLeft = $target.offset().left;
// Find tooltip position.
var tooltipWidth = $tooltip.outerWidth();
var tooltipHeight = $tooltip.outerHeight();
var tooltipTop = $tooltip.offset().top; // Should be 0, but WP media grid causes this to be 32 (toolbar padding).
// Assume default top alignment.
var top = targetTop - tooltipHeight - tooltipTop;
var left = targetLeft + (targetWidth / 2) - (tooltipWidth / 2);
// Check if too far left.
if( left < tolerance ) {
$tooltip.addClass('right');
left = targetLeft + targetWidth;
top = targetTop + (targetHeight / 2) - (tooltipHeight / 2) - tooltipTop;
// Check if too far right.
} else if( (left + tooltipWidth + tolerance) > $(window).width() ) {
$tooltip.addClass('left');
left = targetLeft - tooltipWidth;
top = targetTop + (targetHeight / 2) - (tooltipHeight / 2) - tooltipTop;
// Check if too far up.
} else if( top - $(window).scrollTop() < tolerance ) {
$tooltip.addClass('bottom');
top = targetTop + targetHeight - tooltipTop;
// No colision with edges.
} else {
$tooltip.addClass('top');
}
// update css
$tooltip.css({ 'top': top, 'left': left });
}
});
var TooltipConfirm = Tooltip.extend({
data: {
text: '',
textConfirm: '',
textCancel: '',
target: null,
targetConfirm: true,
confirm: function(){},
cancel: function(){},
context: false
},
events: {
'click [data-event="cancel"]': 'onCancel',
'click [data-event="confirm"]': 'onConfirm',
},
addEvents: function(){
// add events
acf.Model.prototype.addEvents.apply(this);
// vars
var $document = $(document);
var $target = this.get('target');
// add global 'cancel' click event
// - use timeout to avoid the current 'click' event triggering the onCancel function
this.setTimeout(function(){
this.on( $document, 'click', 'onCancel' );
});
// add target 'confirm' click event
// - allow setting to control this feature
if( this.get('targetConfirm') ) {
this.on( $target, 'click', 'onConfirm' );
}
},
removeEvents: function(){
// remove events
acf.Model.prototype.removeEvents.apply(this);
// vars
var $document = $(document);
var $target = this.get('target');
// remove custom events
this.off( $document, 'click' );
this.off( $target, 'click' );
},
render: function(){
// defaults
var text = this.get('text') || acf.__('Are you sure?');
var textConfirm = this.get('textConfirm') || acf.__('Yes');
var textCancel = this.get('textCancel') || acf.__('No');
// html
var html = [
text,
'<a href="#" data-event="confirm">' + textConfirm + '</a>',
'<a href="#" data-event="cancel">' + textCancel + '</a>'
].join(' ');
// html
this.html( html );
// class
this.$el.addClass('-confirm');
},
onCancel: function( e, $el ){
// prevent default
e.preventDefault();
e.stopImmediatePropagation();
// callback
var callback = this.get('cancel');
var context = this.get('context') || this;
callback.apply( context, arguments );
//remove
this.remove();
},
onConfirm: function( e, $el ){
// prevent default
e.preventDefault();
e.stopImmediatePropagation();
// callback
var callback = this.get('confirm');
var context = this.get('context') || this;
callback.apply( context, arguments );
//remove
this.remove();
}
});
// storage
acf.models.Tooltip = Tooltip;
acf.models.TooltipConfirm = TooltipConfirm;
/**
* tooltipManager
*
* description
*
* @date 17/4/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
var tooltipHoverHelper = new acf.Model({
tooltip: false,
events: {
'mouseenter .acf-js-tooltip': 'showTitle',
'mouseup .acf-js-tooltip': 'hideTitle',
'mouseleave .acf-js-tooltip': 'hideTitle'
},
showTitle: function( e, $el ){
// vars
var title = $el.attr('title');
// bail ealry if no title
if( !title ) {
return;
}
// clear title to avoid default browser tooltip
$el.attr('title', '');
// create
if( !this.tooltip ) {
this.tooltip = acf.newTooltip({
text: title,
target: $el
});
// update
} else {
this.tooltip.update({
text: title,
target: $el
});
}
},
hideTitle: function( e, $el ){
// hide tooltip
this.tooltip.hide();
// restore title
$el.attr('title', this.tooltip.get('text'));
}
});
})(jQuery);
(function($, undefined){
// vars
var storage = [];
/**
* acf.Field
*
* description
*
* @date 23/3/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.Field = acf.Model.extend({
// field type
type: '',
// class used to avoid nested event triggers
eventScope: '.acf-field',
// initialize events on 'ready'
wait: 'ready',
/**
* setup
*
* Called during the constructor function to setup this field ready for initialization
*
* @date 8/5/18
* @since 5.6.9
*
* @param jQuery $field The field element.
* @return void
*/
setup: function( $field ){
// set $el
this.$el = $field;
// inherit $field data
this.inherit( $field );
// inherit controll data
this.inherit( this.$control() );
},
/**
* val
*
* Sets or returns the field's value
*
* @date 8/5/18
* @since 5.6.9
*
* @param mixed val Optional. The value to set
* @return mixed
*/
val: function( val ){
// Set.
if( val !== undefined ) {
return this.setValue( val );
// Get.
} else {
return this.prop('disabled') ? null : this.getValue();
}
},
/**
* getValue
*
* returns the field's value
*
* @date 8/5/18
* @since 5.6.9
*
* @param void
* @return mixed
*/
getValue: function(){
return this.$input().val();
},
/**
* setValue
*
* sets the field's value and returns true if changed
*
* @date 8/5/18
* @since 5.6.9
*
* @param mixed val
* @return boolean. True if changed.
*/
setValue: function( val ){
return acf.val( this.$input(), val );
},
/**
* __
*
* i18n helper to be removed
*
* @date 8/5/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
__: function( string ){
return acf._e( this.type, string );
},
/**
* $control
*
* returns the control jQuery element used for inheriting data. Uses this.control setting.
*
* @date 8/5/18
* @since 5.6.9
*
* @param void
* @return jQuery
*/
$control: function(){
return false;
},
/**
* $input
*
* returns the input jQuery element used for saving values. Uses this.input setting.
*
* @date 8/5/18
* @since 5.6.9
*
* @param void
* @return jQuery
*/
$input: function(){
return this.$('[name]:first');
},
/**
* $inputWrap
*
* description
*
* @date 12/5/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
$inputWrap: function(){
return this.$('.acf-input:first');
},
/**
* $inputWrap
*
* description
*
* @date 12/5/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
$labelWrap: function(){
return this.$('.acf-label:first');
},
/**
* getInputName
*
* Returns the field's input name
*
* @date 8/5/18
* @since 5.6.9
*
* @param void
* @return string
*/
getInputName: function(){
return this.$input().attr('name') || '';
},
/**
* parent
*
* returns the field's parent field or false on failure.
*
* @date 8/5/18
* @since 5.6.9
*
* @param void
* @return object|false
*/
parent: function() {
// vars
var parents = this.parents();
// return
return parents.length ? parents[0] : false;
},
/**
* parents
*
* description
*
* @date 9/7/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
parents: function(){
// vars
var $parents = this.$el.parents('.acf-field');
// convert
var parents = acf.getFields( $parents );
// return
return parents;
},
show: function( lockKey, context ){
// show field and store result
var changed = acf.show( this.$el, lockKey );
// do action if visibility has changed
if( changed ) {
this.prop('hidden', false);
acf.doAction('show_field', this, context);
}
// return
return changed;
},
hide: function( lockKey, context ){
// hide field and store result
var changed = acf.hide( this.$el, lockKey );
// do action if visibility has changed
if( changed ) {
this.prop('hidden', true);
acf.doAction('hide_field', this, context);
}
// return
return changed;
},
enable: function( lockKey, context ){
// enable field and store result
var changed = acf.enable( this.$el, lockKey );
// do action if disabled has changed
if( changed ) {
this.prop('disabled', false);
acf.doAction('enable_field', this, context);
}
// return
return changed;
},
disable: function( lockKey, context ){
// disabled field and store result
var changed = acf.disable( this.$el, lockKey );
// do action if disabled has changed
if( changed ) {
this.prop('disabled', true);
acf.doAction('disable_field', this, context);
}
// return
return changed;
},
showEnable: function( lockKey, context ){
// enable
this.enable.apply(this, arguments);
// show and return true if changed
return this.show.apply(this, arguments);
},
hideDisable: function( lockKey, context ){
// disable
this.disable.apply(this, arguments);
// hide and return true if changed
return this.hide.apply(this, arguments);
},
showNotice: function( props ){
// ensure object
if( typeof props !== 'object' ) {
props = { text: props };
}
// remove old notice
if( this.notice ) {
this.notice.remove();
}
// create new notice
props.target = this.$inputWrap();
this.notice = acf.newNotice( props );
},
removeNotice: function( timeout ){
if( this.notice ) {
this.notice.away( timeout || 0 );
this.notice = false;
}
},
showError: function( message ){
// add class
this.$el.addClass('acf-error');
// add message
if( message !== undefined ) {
this.showNotice({
text: message,
type: 'error',
dismiss: false
});
}
// action
acf.doAction('invalid_field', this);
// add event
this.$el.one('focus change', 'input, select, textarea', $.proxy( this.removeError, this ));
},
removeError: function(){
// remove class
this.$el.removeClass('acf-error');
// remove notice
this.removeNotice( 250 );
// action
acf.doAction('valid_field', this);
},
trigger: function( name, args, bubbles ){
// allow some events to bubble
if( name == 'invalidField' ) {
bubbles = true;
}
// return
return acf.Model.prototype.trigger.apply(this, [name, args, bubbles]);
},
});
/**
* newField
*
* description
*
* @date 14/12/17
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.newField = function( $field ){
// vars
var type = $field.data('type');
var mid = modelId( type );
var model = acf.models[ mid ] || acf.Field;
// instantiate
var field = new model( $field );
// actions
acf.doAction('new_field', field);
// return
return field;
};
/**
* mid
*
* Calculates the model ID for a field type
*
* @date 15/12/17
* @since 5.6.5
*
* @param string type
* @return string
*/
var modelId = function( type ) {
return acf.strPascalCase( type || '' ) + 'Field';
};
/**
* registerFieldType
*
* description
*
* @date 14/12/17
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.registerFieldType = function( model ){
// vars
var proto = model.prototype;
var type = proto.type;
var mid = modelId( type );
// store model
acf.models[ mid ] = model;
// store reference
storage.push( type );
};
/**
* acf.getFieldType
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.getFieldType = function( type ){
var mid = modelId( type );
return acf.models[ mid ] || false;
}
/**
* acf.getFieldTypes
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.getFieldTypes = function( args ){
// defaults
args = acf.parseArgs(args, {
category: '',
// hasValue: true
});
// clonse available types
var types = [];
// loop
storage.map(function( type ){
// vars
var model = acf.getFieldType(type);
var proto = model.prototype;
// check operator
if( args.category && proto.category !== args.category ) {
return;
}
// append
types.push( model );
});
// return
return types;
};
})(jQuery);
(function($, undefined){
/**
* findFields
*
* Returns a jQuery selection object of acf fields.
*
* @date 14/12/17
* @since 5.6.5
*
* @param object $args {
* Optional. Arguments to find fields.
*
* @type string key The field's key (data-attribute).
* @type string name The field's name (data-attribute).
* @type string type The field's type (data-attribute).
* @type string is jQuery selector to compare against.
* @type jQuery parent jQuery element to search within.
* @type jQuery sibling jQuery element to search alongside.
* @type limit int The number of fields to find.
* @type suppressFilters bool Whether to allow filters to add/remove results. Default behaviour will ignore clone fields.
* }
* @return jQuery
*/
acf.findFields = function( args ){
// vars
var selector = '.acf-field';
var $fields = false;
// args
args = acf.parseArgs(args, {
key: '',
name: '',
type: '',
is: '',
parent: false,
sibling: false,
limit: false,
visible: false,
suppressFilters: false,
});
// filter args
if( !args.suppressFilters ) {
args = acf.applyFilters('find_fields_args', args);
}
// key
if( args.key ) {
selector += '[data-key="' + args.key + '"]';
}
// type
if( args.type ) {
selector += '[data-type="' + args.type + '"]';
}
// name
if( args.name ) {
selector += '[data-name="' + args.name + '"]';
}
// is
if( args.is ) {
selector += args.is;
}
// visibility
if( args.visible ) {
selector += ':visible';
}
// query
if( args.parent ) {
$fields = args.parent.find( selector );
} else if( args.sibling ) {
$fields = args.sibling.siblings( selector );
} else {
$fields = $( selector );
}
// filter
if( !args.suppressFilters ) {
$fields = $fields.not('.acf-clone .acf-field');
$fields = acf.applyFilters('find_fields', $fields);
}
// limit
if( args.limit ) {
$fields = $fields.slice( 0, args.limit );
}
// return
return $fields;
};
/**
* findField
*
* Finds a specific field with jQuery
*
* @date 14/12/17
* @since 5.6.5
*
* @param string key The field's key.
* @param jQuery $parent jQuery element to search within.
* @return jQuery
*/
acf.findField = function( key, $parent ){
return acf.findFields({
key: key,
limit: 1,
parent: $parent,
suppressFilters: true
});
};
/**
* getField
*
* Returns a field instance
*
* @date 14/12/17
* @since 5.6.5
*
* @param jQuery|string $field jQuery element or field key.
* @return object
*/
acf.getField = function( $field ){
// allow jQuery
if( $field instanceof jQuery ) {
// find fields
} else {
$field = acf.findField( $field );
}
// instantiate
var field = $field.data('acf');
if( !field ) {
field = acf.newField( $field );
}
// return
return field;
};
/**
* getFields
*
* Returns multiple field instances
*
* @date 14/12/17
* @since 5.6.5
*
* @param jQuery|object $fields jQuery elements or query args.
* @return array
*/
acf.getFields = function( $fields ){
// allow jQuery
if( $fields instanceof jQuery ) {
// find fields
} else {
$fields = acf.findFields( $fields );
}
// loop
var fields = [];
$fields.each(function(){
var field = acf.getField( $(this) );
fields.push( field );
});
// return
return fields;
};
/**
* findClosestField
*
* Returns the closest jQuery field element
*
* @date 9/4/18
* @since 5.6.9
*
* @param jQuery $el
* @return jQuery
*/
acf.findClosestField = function( $el ){
return $el.closest('.acf-field');
};
/**
* getClosestField
*
* Returns the closest field instance
*
* @date 22/1/18
* @since 5.6.5
*
* @param jQuery $el
* @return object
*/
acf.getClosestField = function( $el ){
var $field = acf.findClosestField( $el );
return this.getField( $field );
};
/**
* addGlobalFieldAction
*
* Sets up callback logic for global field actions
*
* @date 15/6/18
* @since 5.6.9
*
* @param string action
* @return void
*/
var addGlobalFieldAction = function( action ){
// vars
var globalAction = action;
var pluralAction = action + '_fields'; // ready_fields
var singleAction = action + '_field'; // ready_field
// global action
var globalCallback = function( $el /*, arg1, arg2, etc*/ ){
//console.log( action, arguments );
// get args [$el, ...]
var args = acf.arrayArgs( arguments );
var extraArgs = args.slice(1);
// find fields
var fields = acf.getFields({ parent: $el });
// check
if( fields.length ) {
// pluralAction
var pluralArgs = [ pluralAction, fields ].concat( extraArgs );
acf.doAction.apply(null, pluralArgs);
}
};
// plural action
var pluralCallback = function( fields /*, arg1, arg2, etc*/ ){
//console.log( pluralAction, arguments );
// get args [fields, ...]
var args = acf.arrayArgs( arguments );
var extraArgs = args.slice(1);
// loop
fields.map(function( field, i ){
//setTimeout(function(){
// singleAction
var singleArgs = [ singleAction, field ].concat( extraArgs );
acf.doAction.apply(null, singleArgs);
//}, i * 100);
});
};
// add actions
acf.addAction(globalAction, globalCallback);
acf.addAction(pluralAction, pluralCallback);
// also add single action
addSingleFieldAction( action );
}
/**
* addSingleFieldAction
*
* Sets up callback logic for single field actions
*
* @date 15/6/18
* @since 5.6.9
*
* @param string action
* @return void
*/
var addSingleFieldAction = function( action ){
// vars
var singleAction = action + '_field'; // ready_field
var singleEvent = action + 'Field'; // readyField
// single action
var singleCallback = function( field /*, arg1, arg2, etc*/ ){
//console.log( singleAction, arguments );
// get args [field, ...]
var args = acf.arrayArgs( arguments );
var extraArgs = args.slice(1);
// action variations (ready_field/type=image)
var variations = ['type', 'name', 'key'];
variations.map(function( variation ){
// vars
var prefix = '/' + variation + '=' + field.get(variation);
// singleAction
args = [ singleAction + prefix , field ].concat( extraArgs );
acf.doAction.apply(null, args);
});
// event
if( singleFieldEvents.indexOf(action) > -1 ) {
field.trigger(singleEvent, extraArgs);
}
};
// add actions
acf.addAction(singleAction, singleCallback);
}
// vars
var globalFieldActions = [ 'prepare', 'ready', 'load', 'append', 'remove', 'unmount', 'remount', 'sortstart', 'sortstop', 'show', 'hide', 'unload' ];
var singleFieldActions = [ 'valid', 'invalid', 'enable', 'disable', 'new' ];
var singleFieldEvents = [ 'remove', 'unmount', 'remount', 'sortstart', 'sortstop', 'show', 'hide', 'unload', 'valid', 'invalid', 'enable', 'disable' ];
// add
globalFieldActions.map( addGlobalFieldAction );
singleFieldActions.map( addSingleFieldAction );
/**
* fieldsEventManager
*
* Manages field actions and events
*
* @date 15/12/17
* @since 5.6.5
*
* @param void
* @param void
*/
var fieldsEventManager = new acf.Model({
id: 'fieldsEventManager',
events: {
'click .acf-field a[href="#"]': 'onClick',
'change .acf-field': 'onChange'
},
onClick: function( e ){
// prevent default of any link with an href of #
e.preventDefault();
},
onChange: function(){
// preview hack allows post to save with no title or content
$('#_acf_changed').val(1);
}
});
})(jQuery);
(function($, undefined){
var i = 0;
var Field = acf.Field.extend({
type: 'accordion',
wait: '',
$control: function(){
return this.$('.acf-fields:first');
},
initialize: function(){
// bail early if is cell
if( this.$el.is('td') ) return;
// enpoint
if( this.get('endpoint') ) {
return this.remove();
}
// vars
var $field = this.$el;
var $label = this.$labelWrap()
var $input = this.$inputWrap();
var $wrap = this.$control();
var $instructions = $input.children('.description');
// force description into label
if( $instructions.length ) {
$label.append( $instructions );
}
// table
if( this.$el.is('tr') ) {
// vars
var $table = this.$el.closest('table');
var $newLabel = $('<div class="acf-accordion-title"/>');
var $newInput = $('<div class="acf-accordion-content"/>');
var $newTable = $('<table class="' + $table.attr('class') + '"/>');
var $newWrap = $('<tbody/>');
// dom
$newLabel.append( $label.html() );
$newTable.append( $newWrap );
$newInput.append( $newTable );
$input.append( $newLabel );
$input.append( $newInput );
// modify
$label.remove();
$wrap.remove();
$input.attr('colspan', 2);
// update vars
$label = $newLabel;
$input = $newInput;
$wrap = $newWrap;
}
// add classes
$field.addClass('acf-accordion');
$label.addClass('acf-accordion-title');
$input.addClass('acf-accordion-content');
// index
i++;
// multi-expand
if( this.get('multi_expand') ) {
$field.attr('multi-expand', 1);
}
// open
var order = acf.getPreference('this.accordions') || [];
if( order[i-1] !== undefined ) {
this.set('open', order[i-1]);
}
if( this.get('open') ) {
$field.addClass('-open');
$input.css('display', 'block'); // needed for accordion to close smoothly
}
// add icon
$label.prepend( accordionManager.iconHtml({ open: this.get('open') }) );
// classes
// - remove 'inside' which is a #poststuff WP class
var $parent = $field.parent();
$wrap.addClass( $parent.hasClass('-left') ? '-left' : '' );
$wrap.addClass( $parent.hasClass('-clear') ? '-clear' : '' );
// append
$wrap.append( $field.nextUntil('.acf-field-accordion', '.acf-field') );
// clean up
$wrap.removeAttr('data-open data-multi_expand data-endpoint');
},
});
acf.registerFieldType( Field );
/**
* accordionManager
*
* Events manager for the acf accordion
*
* @date 14/2/18
* @since 5.6.9
*
* @param void
* @return void
*/
var accordionManager = new acf.Model({
actions: {
'unload': 'onUnload'
},
events: {
'click .acf-accordion-title': 'onClick',
'invalidField .acf-accordion': 'onInvalidField'
},
isOpen: function( $el ) {
return $el.hasClass('-open');
},
toggle: function( $el ){
if( this.isOpen($el) ) {
this.close( $el );
} else {
this.open( $el );
}
},
iconHtml: function( props ){
// Determine icon.
//if( acf.isGutenberg() ) {
// var icon = props.open ? 'arrow-up-alt2' : 'arrow-down-alt2';
//} else {
var icon = props.open ? 'arrow-down' : 'arrow-right';
//}
// Return HTML.
return '<i class="acf-accordion-icon dashicons dashicons-' + icon + '"></i>';
},
open: function( $el ){
// open
$el.find('.acf-accordion-content:first').slideDown().css('display', 'block');
$el.find('.acf-accordion-icon:first').replaceWith( this.iconHtml({ open: true }) );
$el.addClass('-open');
// action
acf.doAction('show', $el);
// close siblings
if( !$el.attr('multi-expand') ) {
$el.siblings('.acf-accordion.-open').each(function(){
accordionManager.close( $(this) );
});
}
},
close: function( $el ){
// close
$el.find('.acf-accordion-content:first').slideUp();
$el.find('.acf-accordion-icon:first').replaceWith( this.iconHtml({ open: false }) );
$el.removeClass('-open');
// action
acf.doAction('hide', $el);
},
onClick: function( e, $el ){
// prevent Defailt
e.preventDefault();
// open close
this.toggle( $el.parent() );
},
onInvalidField: function( e, $el ){
// bail early if already focused
if( this.busy ) {
return;
}
// disable functionality for 1sec (allow next validation to work)
this.busy = true;
this.setTimeout(function(){
this.busy = false;
}, 1000);
// open accordion
this.open( $el );
},
onUnload: function( e ){
// vars
var order = [];
// loop
$('.acf-accordion').each(function(){
var open = $(this).hasClass('-open') ? 1 : 0;
order.push(open);
});
// set
if( order.length ) {
acf.setPreference('this.accordions', order);
}
}
});
})(jQuery);
(function($, undefined){
var Field = acf.Field.extend({
type: 'button_group',
events: {
'click input[type="radio"]': 'onClick'
},
$control: function(){
return this.$('.acf-button-group');
},
$input: function(){
return this.$('input:checked');
},
setValue: function( val ){
this.$('input[value="' + val + '"]').prop('checked', true).trigger('change');
},
onClick: function( e, $el ){
// vars
var $label = $el.parent('label');
var selected = $label.hasClass('selected');
// remove previous selected
this.$('.selected').removeClass('selected');
// add active class
$label.addClass('selected');
// allow null
if( this.get('allow_null') && selected ) {
$label.removeClass('selected');
$el.prop('checked', false).trigger('change');
}
}
});
acf.registerFieldType( Field );
})(jQuery);
(function($, undefined){
var Field = acf.Field.extend({
type: 'checkbox',
events: {
'change input': 'onChange',
'click .acf-add-checkbox': 'onClickAdd',
'click .acf-checkbox-toggle': 'onClickToggle',
'click .acf-checkbox-custom': 'onClickCustom'
},
$control: function(){
return this.$('.acf-checkbox-list');
},
$toggle: function(){
return this.$('.acf-checkbox-toggle');
},
$input: function(){
return this.$('input[type="hidden"]');
},
$inputs: function(){
return this.$('input[type="checkbox"]').not('.acf-checkbox-toggle');
},
getValue: function(){
var val = [];
this.$(':checked').each(function(){
val.push( $(this).val() );
});
return val.length ? val : false;
},
onChange: function( e, $el ){
// Vars.
var checked = $el.prop('checked');
var $label = $el.parent('label');
var $toggle = this.$toggle();
// Add or remove "selected" class.
if( checked ) {
$label.addClass('selected');
} else {
$label.removeClass('selected');
}
// Update toggle state if all inputs are checked.
if( $toggle.length ) {
var $inputs = this.$inputs();
// all checked
if( $inputs.not(':checked').length == 0 ) {
$toggle.prop('checked', true);
} else {
$toggle.prop('checked', false);
}
}
},
onClickAdd: function( e, $el ){
var html = '<li><input class="acf-checkbox-custom" type="checkbox" checked="checked" /><input type="text" name="' + this.getInputName() + '[]" /></li>';
$el.parent('li').before( html );
},
onClickToggle: function( e, $el ){
// Vars.
var checked = $el.prop('checked');
var $inputs = this.$('input[type="checkbox"]');
var $labels = this.$('label');
// Update "checked" state.
$inputs.prop('checked', checked);
// Add or remove "selected" class.
if( checked ) {
$labels.addClass('selected');
} else {
$labels.removeClass('selected');
}
},
onClickCustom: function( e, $el ){
var checked = $el.prop('checked');
var $text = $el.next('input[type="text"]');
// checked
if( checked ) {
$text.prop('disabled', false);
// not checked
} else {
$text.prop('disabled', true);
// remove
if( $text.val() == '' ) {
$el.parent('li').remove();
}
}
}
});
acf.registerFieldType( Field );
})(jQuery);
(function($, undefined){
var Field = acf.Field.extend({
type: 'color_picker',
wait: 'load',
$control: function(){
return this.$('.acf-color-picker');
},
$input: function(){
return this.$('input[type="hidden"]');
},
$inputText: function(){
return this.$('input[type="text"]');
},
setValue: function( val ){
// update input (with change)
acf.val( this.$input(), val );
// update iris
this.$inputText().iris('color', val);
},
initialize: function(){
// vars
var $input = this.$input();
var $inputText = this.$inputText();
// event
var onChange = function( e ){
// timeout is required to ensure the $input val is correct
setTimeout(function(){
acf.val( $input, $inputText.val() );
}, 1);
}
// args
var args = {
defaultColor: false,
palettes: true,
hide: true,
change: onChange,
clear: onChange
};
// filter
var args = acf.applyFilters('color_picker_args', args, this);
// initialize
$inputText.wpColorPicker( args );
}
});
acf.registerFieldType( Field );
})(jQuery);
(function($, undefined){
var Field = acf.Field.extend({
type: 'date_picker',
events: {
'blur input[type="text"]': 'onBlur'
},
$control: function(){
return this.$('.acf-date-picker');
},
$input: function(){
return this.$('input[type="hidden"]');
},
$inputText: function(){
return this.$('input[type="text"]');
},
initialize: function(){
// save_format: compatibility with ACF < 5.0.0
if( this.has('save_format') ) {
return this.initializeCompatibility();
}
// vars
var $input = this.$input();
var $inputText = this.$inputText();
// args
var args = {
dateFormat: this.get('date_format'),
altField: $input,
altFormat: 'yymmdd',
changeYear: true,
yearRange: "-100:+100",
changeMonth: true,
showButtonPanel: true,
firstDay: this.get('first_day')
};
// filter
args = acf.applyFilters('date_picker_args', args, this);
// add date picker
acf.newDatePicker( $inputText, args );
// action
acf.doAction('date_picker_init', $inputText, args, this);
},
initializeCompatibility: function(){
// vars
var $input = this.$input();
var $inputText = this.$inputText();
// get and set value from alt field
$inputText.val( $input.val() );
// args
var args = {
dateFormat: this.get('date_format'),
altField: $input,
altFormat: this.get('save_format'),
changeYear: true,
yearRange: "-100:+100",
changeMonth: true,
showButtonPanel: true,
firstDay: this.get('first_day')
};
// filter for 3rd party customization
args = acf.applyFilters('date_picker_args', args, this);
// backup
var dateFormat = args.dateFormat;
// change args.dateFormat
args.dateFormat = this.get('save_format');
// add date picker
acf.newDatePicker( $inputText, args );
// now change the format back to how it should be.
$inputText.datepicker( 'option', 'dateFormat', dateFormat );
// action for 3rd party customization
acf.doAction('date_picker_init', $inputText, args, this);
},
onBlur: function(){
if( !this.$inputText().val() ) {
acf.val( this.$input(), '' );
}
}
});
acf.registerFieldType( Field );
// manager
var datePickerManager = new acf.Model({
priority: 5,
wait: 'ready',
initialize: function(){
// vars
var locale = acf.get('locale');
var rtl = acf.get('rtl');
var l10n = acf.get('datePickerL10n');
// bail ealry if no l10n
if( !l10n ) {
return false;
}
// bail ealry if no datepicker library
if( typeof $.datepicker === 'undefined' ) {
return false;
}
// rtl
l10n.isRTL = rtl;
// append
$.datepicker.regional[ locale ] = l10n;
$.datepicker.setDefaults(l10n);
}
});
// add
acf.newDatePicker = function( $input, args ){
// bail ealry if no datepicker library
if( typeof $.datepicker === 'undefined' ) {
return false;
}
// defaults
args = args || {};
// initialize
$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('<div class="acf-ui-datepicker" />');
}
};
})(jQuery);
(function($, undefined){
var Field = acf.models.DatePickerField.extend({
type: 'date_time_picker',
$control: function(){
return this.$('.acf-date-time-picker');
},
initialize: function(){
// vars
var $input = this.$input();
var $inputText = this.$inputText();
// args
var args = {
dateFormat: this.get('date_format'),
timeFormat: this.get('time_format'),
altField: $input,
altFieldTimeOnly: false,
altFormat: 'yy-mm-dd',
altTimeFormat: 'HH:mm:ss',
changeYear: true,
yearRange: "-100:+100",
changeMonth: true,
showButtonPanel: true,
firstDay: this.get('first_day'),
controlType: 'select',
oneLine: true
};
// filter
args = acf.applyFilters('date_time_picker_args', args, this);
// add date time picker
acf.newDateTimePicker( $inputText, args );
// action
acf.doAction('date_time_picker_init', $inputText, args, this);
}
});
acf.registerFieldType( Field );
// manager
var dateTimePickerManager = new acf.Model({
priority: 5,
wait: 'ready',
initialize: function(){
// vars
var locale = acf.get('locale');
var rtl = acf.get('rtl');
var l10n = acf.get('dateTimePickerL10n');
// bail ealry if no l10n
if( !l10n ) {
return false;
}
// bail ealry if no datepicker library
if( typeof $.timepicker === 'undefined' ) {
return false;
}
// rtl
l10n.isRTL = rtl;
// append
$.timepicker.regional[ locale ] = l10n;
$.timepicker.setDefaults(l10n);
}
});
// add
acf.newDateTimePicker = function( $input, args ){
// bail ealry if no datepicker library
if( typeof $.timepicker === 'undefined' ) {
return false;
}
// defaults
args = args || {};
// initialize
$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('<div class="acf-ui-datepicker" />');
}
};
})(jQuery);
(function($, undefined){
var Field = acf.Field.extend({
type: 'google_map',
map: false,
wait: 'load',
events: {
'click a[data-name="clear"]': 'onClickClear',
'click a[data-name="locate"]': 'onClickLocate',
'click a[data-name="search"]': 'onClickSearch',
'keydown .search': 'onKeydownSearch',
'keyup .search': 'onKeyupSearch',
'focus .search': 'onFocusSearch',
'blur .search': 'onBlurSearch',
'showField': 'onShow',
},
$control: function(){
return this.$('.acf-google-map');
},
$search: function(){
return this.$('.search');
},
$canvas: function(){
return this.$('.canvas');
},
setState: function( state ){
// Remove previous state classes.
this.$control().removeClass( '-value -loading -searching' );
// Determine auto state based of current value.
if( state === 'default' ) {
state = this.val() ? 'value' : '';
}
// Update state class.
if( state ) {
this.$control().addClass( '-' + state );
}
},
getValue: function(){
var val = this.$input().val();
if( val ) {
return JSON.parse( val )
} else {
return false;
}
},
setValue: function( val, silent ){
// Convert input value.
var valAttr = '';
if( val ) {
valAttr = JSON.stringify( val );
}
// Update input (with change).
acf.val( this.$input(), valAttr );
// Bail early if silent update.
if( silent ) {
return;
}
// Render.
this.renderVal( val );
/**
* Fires immediately after the value has changed.
*
* @date 12/02/2014
* @since 5.0.0
*
* @param object|string val The new value.
* @param object map The Google Map isntance.
* @param object field The field instance.
*/
acf.doAction('google_map_change', val, this.map, this);
},
renderVal: function( val ){
// Value.
if( val ) {
this.setState( 'value' );
this.$search().val( val.address );
this.setPosition( val.lat, val.lng );
// No value.
} else {
this.setState( '' );
this.$search().val( '' );
this.map.marker.setVisible( false );
}
},
newLatLng: function( lat, lng ){
return new google.maps.LatLng( parseFloat(lat), parseFloat(lng) );
},
setPosition: function( lat, lng ){
// Update marker position.
this.map.marker.setPosition({
lat: parseFloat(lat),
lng: parseFloat(lng)
});
// Show marker.
this.map.marker.setVisible( true );
// Center map.
this.center();
},
center: function(){
// Find marker position.
var position = this.map.marker.getPosition();
if( position ) {
var lat = position.lat();
var lng = position.lng();
// Or find default settings.
} else {
var lat = this.get('lat');
var lng = this.get('lng');
}
// Center map.
this.map.setCenter({
lat: parseFloat(lat),
lng: parseFloat(lng)
});
},
initialize: function(){
// Ensure Google API is loaded and then initialize map.
withAPI( this.initializeMap.bind(this) );
},
initializeMap: function(){
// Get value ignoring conditional logic status.
var val = this.getValue();
// Construct default args.
var args = acf.parseArgs(val, {
zoom: this.get('zoom'),
lat: this.get('lat'),
lng: this.get('lng')
});
// Create Map.
var mapArgs = {
scrollwheel: false,
zoom: parseInt( args.zoom ),
center: {
lat: parseFloat( args.lat ),
lng: parseFloat( args.lng )
},
mapTypeId: google.maps.MapTypeId.ROADMAP,
marker: {
draggable: true,
raiseOnDrag: true
},
autocomplete: {}
};
mapArgs = acf.applyFilters('google_map_args', mapArgs, this);
var map = new google.maps.Map( this.$canvas()[0], mapArgs );
// Create Marker.
var markerArgs = acf.parseArgs(mapArgs.marker, {
draggable: true,
raiseOnDrag: true,
map: map
});
markerArgs = acf.applyFilters('google_map_marker_args', markerArgs, this);
var marker = new google.maps.Marker( markerArgs );
// Maybe Create Autocomplete.
var autocomplete = false;
if( acf.isset(google, 'maps', 'places', 'Autocomplete') ) {
var autocompleteArgs = mapArgs.autocomplete || {};
autocompleteArgs = acf.applyFilters('google_map_autocomplete_args', autocompleteArgs, this);
autocomplete = new google.maps.places.Autocomplete( this.$search()[0], autocompleteArgs );
autocomplete.bindTo('bounds', map);
}
// Add map events.
this.addMapEvents( this, map, marker, autocomplete );
// Append references.
map.acf = this;
map.marker = marker;
map.autocomplete = autocomplete;
this.map = map;
// Set position.
if( val ) {
this.setPosition( val.lat, val.lng );
}
/**
* Fires immediately after the Google Map has been initialized.
*
* @date 12/02/2014
* @since 5.0.0
*
* @param object map The Google Map isntance.
* @param object marker The Google Map marker isntance.
* @param object field The field instance.
*/
acf.doAction('google_map_init', map, marker, this);
},
addMapEvents: function( field, map, marker, autocomplete ){
// Click map.
google.maps.event.addListener( map, 'click', function( e ) {
var lat = e.latLng.lat();
var lng = e.latLng.lng();
field.searchPosition( lat, lng );
});
// Drag marker.
google.maps.event.addListener( marker, 'dragend', function(){
var lat = this.getPosition().lat();
var lng = this.getPosition().lng();
field.searchPosition( lat, lng );
});
// Autocomplete search.
if( autocomplete ) {
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = this.getPlace();
field.searchPlace( place );
});
}
// Detect zoom change.
google.maps.event.addListener( map, 'zoom_changed', function(){
var val = field.val();
if( val ) {
val.zoom = map.getZoom();
field.setValue( val, true );
}
});
},
searchPosition: function( lat, lng ){
//console.log('searchPosition', lat, lng );
// Start Loading.
this.setState( 'loading' );
// Query Geocoder.
var latLng = { lat: lat, lng: lng };
geocoder.geocode({ location: latLng }, function( results, status ){
//console.log('searchPosition', arguments );
// End Loading.
this.setState( '' );
// Status failure.
if( status !== 'OK' ) {
this.showNotice({
text: acf.__('Location not found: %s').replace('%s', status),
type: 'warning'
});
// Success.
} else {
var val = this.parseResult( results[0] );
// Update value.
this.val( val );
}
}.bind( this ));
},
searchPlace: function( place ){
//console.log('searchPlace', place );
// Ignore empty search.
if( !place || !place.name ) {
return;
}
// No geometry (Custom address search).
if( !place.geometry ) {
return this.searchAddress( place.name );
}
// Parse place.
var val = this.parseResult( place );
// Update value.
this.val( val );
},
searchAddress: function( address ){
//console.log('searchAddress', address );
// Bail early if no address.
if( !address ) {
return;
}
// Allow "lat,lng" search.
var latLng = address.split(',');
if( latLng.length == 2 ) {
var lat = parseFloat(latLng[0]);
var lng = parseFloat(latLng[1]);
if( lat && lng ) {
return this.searchPosition( lat, lng );
}
}
// Start Loading.
this.setState( 'loading' );
// Query Geocoder.
geocoder.geocode({ address: address }, function( results, status ){
//console.log('searchPosition', arguments );
// End Loading.
this.setState( '' );
// Status failure.
if( status !== 'OK' ) {
this.showNotice({
text: acf.__('Location not found: %s').replace('%s', status),
type: 'warning'
});
// Success.
} else {
var val = this.parseResult( results[0] );
// Override address data with parameter allowing custom address to be defined in search.
val.address = address;
// Update value.
this.val( val );
}
}.bind( this ));
},
searchLocation: function(){
//console.log('searchLocation' );
// Check HTML5 geolocation.
if( !navigator.geolocation ) {
return alert( acf.__('Sorry, this browser does not support geolocation') );
}
// Start Loading.
this.setState( 'loading' );
// Query Geolocation.
navigator.geolocation.getCurrentPosition(
// Success.
function( results ){
// End Loading.
this.setState( '' );
// Search position.
var lat = results.coords.latitude;
var lng = results.coords.longitude;
this.searchPosition( lat, lng );
}.bind(this),
// Failure.
function( error ){
this.setState( '' );
}.bind(this)
);
},
/**
* parseResult
*
* Returns location data for the given GeocoderResult object.
*
* @date 15/10/19
* @since 5.8.6
*
* @param object obj A GeocoderResult object.
* @return object
*/
parseResult: function( obj ) {
// Construct basic data.
var result = {
address: obj.formatted_address,
lat: obj.geometry.location.lat(),
lng: obj.geometry.location.lng(),
};
// Add zoom level.
result.zoom = this.map.getZoom();
// Add place ID.
if( obj.place_id ) {
result.place_id = obj.place_id;
}
// Create search map for address component data.
var map = {
street_number: [ 'street_number' ],
street_name: [ 'street_address', 'route' ],
city: [ 'locality' ],
state: [
'administrative_area_level_1',
'administrative_area_level_2',
'administrative_area_level_3',
'administrative_area_level_4',
'administrative_area_level_5'
],
post_code: [ 'postal_code' ],
country: [ 'country' ]
};
// Loop over map.
for( var k in map ) {
var keywords = map[ k ];
// Loop over address components.
for( var i = 0; i < obj.address_components.length; i++ ) {
var component = obj.address_components[ i ];
var component_type = component.types[0];
// Look for matching component type.
if( keywords.indexOf(component_type) !== -1 ) {
// Append to result.
result[ k ] = component.long_name;
// Append short version.
if( component.long_name !== component.short_name ) {
result[ k + '_short' ] = component.short_name;
}
}
}
}
/**
* Filters the parsed result.
*
* @date 18/10/19
* @since 5.8.6
*
* @param object result The parsed result value.
* @param object obj The GeocoderResult object.
*/
return acf.applyFilters('google_map_result', result, obj, this.map, this);
},
onClickClear: function(){
this.val( false );
},
onClickLocate: function(){
this.searchLocation();
},
onClickSearch: function(){
this.searchAddress( this.$search().val() );
},
onFocusSearch: function( e, $el ){
this.setState( 'searching' );
},
onBlurSearch: function( e, $el ){
// Get saved address value.
var val = this.val();
var address = val ? val.address : '';
// Remove 'is-searching' if value has not changed.
if( $el.val() === address ) {
this.setState( 'default' );
}
},
onKeyupSearch: function( e, $el ){
// Clear empty value.
if( !$el.val() ) {
this.val( false );
}
},
// Prevent form from submitting.
onKeydownSearch: function( e, $el ){
if( e.which == 13 ) {
e.preventDefault();
$el.blur();
}
},
// Center map once made visible.
onShow: function(){
if( this.map ) {
this.setTimeout( this.center );
}
},
});
acf.registerFieldType( Field );
// Vars.
var loading = false;
var geocoder = false;
/**
* withAPI
*
* Loads the Google Maps API library and troggers callback.
*
* @date 28/3/19
* @since 5.7.14
*
* @param function callback The callback to excecute.
* @return void
*/
function withAPI( callback ) {
// Check if geocoder exists.
if( geocoder ) {
return callback();
}
// Check if geocoder API exists.
if( acf.isset(window, 'google', 'maps', 'Geocoder') ) {
geocoder = new google.maps.Geocoder();
return callback();
}
// Geocoder will need to be loaded. Hook callback to action.
acf.addAction( 'google_map_api_loaded', callback );
// Bail early if already loading API.
if( loading ) {
return;
}
// load api
var url = acf.get('google_map_api');
if( url ) {
// Set loading status.
loading = true;
// Load API
$.ajax({
url: url,
dataType: 'script',
cache: true,
success: function(){
geocoder = new google.maps.Geocoder();
acf.doAction('google_map_api_loaded');
}
});
}
}
})(jQuery);
(function($, undefined){
var Field = acf.Field.extend({
type: 'image',
$control: function(){
return this.$('.acf-image-uploader');
},
$input: function(){
return this.$('input[type="hidden"]');
},
events: {
'click a[data-name="add"]': 'onClickAdd',
'click a[data-name="edit"]': 'onClickEdit',
'click a[data-name="remove"]': 'onClickRemove',
'change input[type="file"]': 'onChange'
},
initialize: function(){
// add attribute to form
if( this.get('uploader') === 'basic' ) {
this.$el.closest('form').attr('enctype', 'multipart/form-data');
}
},
validateAttachment: function( attachment ){
// defaults
attachment = attachment || {};
// WP attachment
if( attachment.id !== undefined ) {
attachment = attachment.attributes;
}
// args
attachment = acf.parseArgs(attachment, {
url: '',
alt: '',
title: '',
caption: '',
description: '',
width: 0,
height: 0
});
// preview size
var url = acf.isget(attachment, 'sizes', this.get('preview_size'), 'url');
if( url !== null ) {
attachment.url = url;
}
// return
return attachment;
},
render: function( attachment ){
// vars
attachment = this.validateAttachment( attachment );
// update image
this.$('img').attr({
src: attachment.url,
alt: attachment.alt,
title: attachment.title
});
// vars
var val = attachment.id || '';
// update val
this.val( val );
// update class
if( val ) {
this.$control().addClass('has-value');
} else {
this.$control().removeClass('has-value');
}
},
// create a new repeater row and render value
append: function( attachment, parent ){
// create function to find next available field within parent
var getNext = function( field, parent ){
// find existing file fields within parent
var fields = acf.getFields({
key: field.get('key'),
parent: parent.$el
});
// find the first field with no value
for( var i = 0; i < fields.length; i++ ) {
if( !fields[i].val() ) {
return fields[i];
}
}
// return
return false;
}
// find existing file fields within parent
var field = getNext( this, parent );
// add new row if no available field
if( !field ) {
parent.$('.acf-button:last').trigger('click');
field = getNext( this, parent );
}
// render
if( field ) {
field.render( attachment );
}
},
selectAttachment: function(){
// vars
var parent = this.parent();
var multiple = (parent && parent.get('type') === 'repeater');
// new frame
var frame = acf.newMediaPopup({
mode: 'select',
type: 'image',
title: acf.__('Select Image'),
field: this.get('key'),
multiple: multiple,
library: this.get('library'),
allowedTypes: this.get('mime_types'),
select: $.proxy(function( attachment, i ) {
if( i > 0 ) {
this.append( attachment, parent );
} else {
this.render( attachment );
}
}, this)
});
},
editAttachment: function(){
// vars
var val = this.val();
// bail early if no val
if( !val ) return;
// popup
var frame = acf.newMediaPopup({
mode: 'edit',
title: acf.__('Edit Image'),
button: acf.__('Update Image'),
attachment: val,
field: this.get('key'),
select: $.proxy(function( attachment, i ) {
this.render( attachment );
}, this)
});
},
removeAttachment: function(){
this.render( false );
},
onClickAdd: function( e, $el ){
this.selectAttachment();
},
onClickEdit: function( e, $el ){
this.editAttachment();
},
onClickRemove: function( e, $el ){
this.removeAttachment();
},
onChange: function( e, $el ){
var $hiddenInput = this.$input();
acf.getFileInputData($el, function( data ){
$hiddenInput.val( $.param(data) );
});
}
});
acf.registerFieldType( Field );
})(jQuery);
(function($, undefined){
var Field = acf.models.ImageField.extend({
type: 'file',
$control: function(){
return this.$('.acf-file-uploader');
},
$input: function(){
return this.$('input[type="hidden"]');
},
validateAttachment: function( attachment ){
// defaults
attachment = attachment || {};
// WP attachment
if( attachment.id !== undefined ) {
attachment = attachment.attributes;
}
// args
attachment = acf.parseArgs(attachment, {
url: '',
alt: '',
title: '',
filename: '',
filesizeHumanReadable: '',
icon: '/wp-includes/images/media/default.png'
});
// return
return attachment;
},
render: function( attachment ){
// vars
attachment = this.validateAttachment( attachment );
// update image
this.$('img').attr({
src: attachment.icon,
alt: attachment.alt,
title: attachment.title
});
// update elements
this.$('[data-name="title"]').text( attachment.title );
this.$('[data-name="filename"]').text( attachment.filename ).attr( 'href', attachment.url );
this.$('[data-name="filesize"]').text( attachment.filesizeHumanReadable );
// vars
var val = attachment.id || '';
// update val
acf.val( this.$input(), val );
// update class
if( val ) {
this.$control().addClass('has-value');
} else {
this.$control().removeClass('has-value');
}
},
selectAttachment: function(){
// vars
var parent = this.parent();
var multiple = (parent && parent.get('type') === 'repeater');
// new frame
var frame = acf.newMediaPopup({
mode: 'select',
title: acf.__('Select File'),
field: this.get('key'),
multiple: multiple,
library: this.get('library'),
allowedTypes: this.get('mime_types'),
select: $.proxy(function( attachment, i ) {
if( i > 0 ) {
this.append( attachment, parent );
} else {
this.render( attachment );
}
}, this)
});
},
editAttachment: function(){
// vars
var val = this.val();
// bail early if no val
if( !val ) {
return false;
}
// popup
var frame = acf.newMediaPopup({
mode: 'edit',
title: acf.__('Edit File'),
button: acf.__('Update File'),
attachment: val,
field: this.get('key'),
select: $.proxy(function( attachment, i ) {
this.render( attachment );
}, this)
});
}
});
acf.registerFieldType( Field );
})(jQuery);
(function($, undefined){
var Field = acf.Field.extend({
type: 'link',
events: {
'click a[data-name="add"]': 'onClickEdit',
'click a[data-name="edit"]': 'onClickEdit',
'click a[data-name="remove"]': 'onClickRemove',
'change .link-node': 'onChange',
},
$control: function(){
return this.$('.acf-link');
},
$node: function(){
return this.$('.link-node');
},
getValue: function(){
// vars
var $node = this.$node();
// return false if empty
if( !$node.attr('href') ) {
return false;
}
// return
return {
title: $node.html(),
url: $node.attr('href'),
target: $node.attr('target')
};
},
setValue: function( val ){
// default
val = acf.parseArgs(val, {
title: '',
url: '',
target: ''
});
// vars
var $div = this.$control();
var $node = this.$node();
// remove class
$div.removeClass('-value -external');
// add class
if( val.url ) $div.addClass('-value');
if( val.target === '_blank' ) $div.addClass('-external');
// update text
this.$('.link-title').html( val.title );
this.$('.link-url').attr('href', val.url).html( val.url );
// update node
$node.html(val.title);
$node.attr('href', val.url);
$node.attr('target', val.target);
// update inputs
this.$('.input-title').val( val.title );
this.$('.input-target').val( val.target );
this.$('.input-url').val( val.url ).trigger('change');
},
onClickEdit: function( e, $el ){
acf.wpLink.open( this.$node() );
},
onClickRemove: function( e, $el ){
this.setValue( false );
},
onChange: function( e, $el ){
// get the changed value
var val = this.getValue();
// update inputs
this.setValue(val);
}
});
acf.registerFieldType( Field );
// manager
acf.wpLink = new acf.Model({
getNodeValue: function(){
var $node = this.get('node');
return {
title: acf.decode( $node.html() ),
url: $node.attr('href'),
target: $node.attr('target')
};
},
setNodeValue: function( val ){
var $node = this.get('node');
$node.text( val.title );
$node.attr('href', val.url);
$node.attr('target', val.target);
$node.trigger('change');
},
getInputValue: function(){
return {
title: $('#wp-link-text').val(),
url: $('#wp-link-url').val(),
target: $('#wp-link-target').prop('checked') ? '_blank' : ''
};
},
setInputValue: function( val ){
$('#wp-link-text').val( val.title );
$('#wp-link-url').val( val.url );
$('#wp-link-target').prop('checked', val.target === '_blank' );
},
open: function( $node ){
// add events
this.on('wplink-open', 'onOpen');
this.on('wplink-close', 'onClose');
// set node
this.set('node', $node);
// create textarea
var $textarea = $('<textarea id="acf-link-textarea" style="display:none;"></textarea>');
$('body').append( $textarea );
// vars
var val = this.getNodeValue();
// open popup
wpLink.open( 'acf-link-textarea', val.url, val.title, null );
},
onOpen: function(){
// always show title (WP will hide title if empty)
$('#wp-link-wrap').addClass('has-text-field');
// set inputs
var val = this.getNodeValue();
this.setInputValue( val );
},
close: function(){
wpLink.close();
},
onClose: function(){
// bail early if no node
// needed due to WP triggering this event twice
if( !this.has('node') ) {
return false;
}
// remove events
this.off('wplink-open');
this.off('wplink-close');
// set value
var val = this.getInputValue();
this.setNodeValue( val );
// remove textarea
$('#acf-link-textarea').remove();
// reset
this.set('node', null);
}
});
})(jQuery);
(function($, undefined){
var Field = acf.Field.extend({
type: 'oembed',
events: {
'click [data-name="clear-button"]': 'onClickClear',
'keypress .input-search': 'onKeypressSearch',
'keyup .input-search': 'onKeyupSearch',
'change .input-search': 'onChangeSearch'
},
$control: function(){
return this.$('.acf-oembed');
},
$input: function(){
return this.$('.input-value');
},
$search: function(){
return this.$('.input-search');
},
getValue: function(){
return this.$input().val();
},
getSearchVal: function(){
return this.$search().val();
},
setValue: function( val ){
// class
if( val ) {
this.$control().addClass('has-value');
} else {
this.$control().removeClass('has-value');
}
acf.val( this.$input(), val );
},
showLoading: function( show ){
acf.showLoading( this.$('.canvas') );
},
hideLoading: function(){
acf.hideLoading( this.$('.canvas') );
},
maybeSearch: function(){
// vars
var prevUrl = this.val();
var url = this.getSearchVal();
// no value
if( !url ) {
return this.clear();
}
// fix missing 'http://' - causes the oembed code to error and fail
if( url.substr(0, 4) != 'http' ) {
url = 'http://' + url;
}
// bail early if no change
if( url === prevUrl ) return;
// clear existing timeout
var timeout = this.get('timeout');
if( timeout ) {
clearTimeout( timeout );
}
// set new timeout
var callback = $.proxy(this.search, this, url);
this.set('timeout', setTimeout(callback, 300));
},
search: function( url ){
// ajax
var ajaxData = {
action: 'acf/fields/oembed/search',
s: url,
field_key: this.get('key')
};
// clear existing timeout
var xhr = this.get('xhr');
if( xhr ) {
xhr.abort();
}
// loading
this.showLoading();
// query
var xhr = $.ajax({
url: acf.get('ajaxurl'),
data: acf.prepareForAjax(ajaxData),
type: 'post',
dataType: 'json',
context: this,
success: function( json ){
// error
if( !json || !json.html ) {
json = {
url: false,
html: ''
}
}
// update vars
this.val( json.url );
this.$('.canvas-media').html( json.html );
},
complete: function(){
this.hideLoading();
}
});
this.set('xhr', xhr);
},
clear: function(){
this.val('');
this.$search().val('');
this.$('.canvas-media').html('');
},
onClickClear: function( e, $el ){
this.clear();
},
onKeypressSearch: function( e, $el ){
if( e.which == 13 ) {
e.preventDefault();
this.maybeSearch();
}
},
onKeyupSearch: function( e, $el ){
if( $el.val() ) {
this.maybeSearch();
}
},
onChangeSearch: function( e, $el ){
this.maybeSearch();
}
});
acf.registerFieldType( Field );
})(jQuery);
(function($, undefined){
var Field = acf.Field.extend({
type: 'radio',
events: {
'click input[type="radio"]': 'onClick',
},
$control: function(){
return this.$('.acf-radio-list');
},
$input: function(){
return this.$('input:checked');
},
$inputText: function(){
return this.$('input[type="text"]');
},
getValue: function(){
var val = this.$input().val();
if( val === 'other' && this.get('other_choice') ) {
val = this.$inputText().val();
}
return val;
},
onClick: function( e, $el ){
// vars
var $label = $el.parent('label');
var selected = $label.hasClass('selected');
var val = $el.val();
// remove previous selected
this.$('.selected').removeClass('selected');
// add active class
$label.addClass('selected');
// allow null
if( this.get('allow_null') && selected ) {
$label.removeClass('selected');
$el.prop('checked', false).trigger('change');
val = false;
}
// other
if( this.get('other_choice') ) {
// enable
if( val === 'other' ) {
this.$inputText().prop('disabled', false);
// disable
} else {
this.$inputText().prop('disabled', true);
}
}
}
});
acf.registerFieldType( Field );
})(jQuery);
(function($, undefined){
var Field = acf.Field.extend({
type: 'range',
events: {
'input input[type="range"]': 'onChange',
'change input': 'onChange'
},
$input: function(){
return this.$('input[type="range"]');
},
$inputAlt: function(){
return this.$('input[type="number"]');
},
setValue: function( val ){
this.busy = true;
// Update range input (with change).
acf.val( this.$input(), val );
// Update alt input (without change).
// Read in input value to inherit min/max validation.
acf.val( this.$inputAlt(), this.$input().val(), true );
this.busy = false;
},
onChange: function( e, $el ){
if( !this.busy ) {
this.setValue( $el.val() );
}
}
});
acf.registerFieldType( Field );
})(jQuery);
(function($, undefined){
var Field = acf.Field.extend({
type: 'relationship',
events: {
'keypress [data-filter]': 'onKeypressFilter',
'change [data-filter]': 'onChangeFilter',
'keyup [data-filter]': 'onChangeFilter',
'click .choices-list .acf-rel-item': 'onClickAdd',
'click [data-name="remove_item"]': 'onClickRemove',
},
$control: function(){
return this.$('.acf-relationship');
},
$list: function( list ) {
return this.$('.' + list + '-list');
},
$listItems: function( list ) {
return this.$list( list ).find('.acf-rel-item');
},
$listItem: function( list, id ) {
return this.$list( list ).find('.acf-rel-item[data-id="' + id + '"]');
},
getValue: function(){
var val = [];
this.$listItems('values').each(function(){
val.push( $(this).data('id') );
});
return val.length ? val : false;
},
newChoice: function( props ){
return [
'<li>',
'<span data-id="' + props.id + '" class="acf-rel-item">' + props.text + '</span>',
'</li>'
].join('');
},
newValue: function( props ){
return [
'<li>',
'<input type="hidden" name="' + this.getInputName() + '[]" value="' + props.id + '" />',
'<span data-id="' + props.id + '" class="acf-rel-item">' + props.text,
'<a href="#" class="acf-icon -minus small dark" data-name="remove_item"></a>',
'</span>',
'</li>'
].join('');
},
initialize: function(){
// Delay initialization until "interacted with" or "in view".
var delayed = this.proxy(acf.once(function(){
// Add sortable.
this.$list('values').sortable({
items: 'li',
forceHelperSize: true,
forcePlaceholderSize: true,
scroll: true,
update: this.proxy(function(){
this.$input().trigger('change');
})
});
// Avoid browser remembering old scroll position and add event.
this.$list('choices').scrollTop(0).on('scroll', this.proxy(this.onScrollChoices));
// Fetch choices.
this.fetch();
}));
// Bind "interacted with".
this.$el.one( 'mouseover', delayed );
this.$el.one( 'focus', 'input', delayed );
// Bind "in view".
acf.onceInView( this.$el, delayed );
},
onScrollChoices: function(e){
// bail early if no more results
if( this.get('loading') || !this.get('more') ) {
return;
}
// Scrolled to bottom
var $list = this.$list('choices');
var scrollTop = Math.ceil( $list.scrollTop() );
var scrollHeight = Math.ceil( $list[0].scrollHeight );
var innerHeight = Math.ceil( $list.innerHeight() );
var paged = this.get('paged') || 1;
if( (scrollTop + innerHeight) >= scrollHeight ) {
// update paged
this.set('paged', (paged+1));
// fetch
this.fetch();
}
},
onKeypressFilter: function( e, $el ){
// don't submit form
if( e.which == 13 ) {
e.preventDefault();
}
},
onChangeFilter: function( e, $el ){
// vars
var val = $el.val();
var filter = $el.data('filter');
// Bail early if filter has not changed
if( this.get(filter) === val ) {
return;
}
// update attr
this.set(filter, val);
// reset paged
this.set('paged', 1);
// fetch
if( $el.is('select') ) {
this.fetch();
// search must go through timeout
} else {
this.maybeFetch();
}
},
onClickAdd: function( e, $el ){
// vars
var val = this.val();
var max = parseInt( this.get('max') );
// can be added?
if( $el.hasClass('disabled') ) {
return false;
}
// validate
if( max > 0 && val && val.length >= max ) {
// add notice
this.showNotice({
text: acf.__('Maximum values reached ( {max} values )').replace('{max}', max),
type: 'warning'
});
return false;
}
// disable
$el.addClass('disabled');
// add
var html = this.newValue({
id: $el.data('id'),
text: $el.html()
});
this.$list('values').append( html )
// trigger change
this.$input().trigger('change');
},
onClickRemove: function( e, $el ){
// Prevent default here because generic handler wont be triggered.
e.preventDefault();
// vars
var $span = $el.parent();
var $li = $span.parent();
var id = $span.data('id');
// remove value
$li.remove();
// show choice
this.$listItem('choices', id).removeClass('disabled');
// trigger change
this.$input().trigger('change');
},
maybeFetch: function(){
// vars
var timeout = this.get('timeout');
// abort timeout
if( timeout ) {
clearTimeout( timeout );
}
// fetch
timeout = this.setTimeout(this.fetch, 300);
this.set('timeout', timeout);
},
getAjaxData: function(){
// load data based on element attributes
var ajaxData = this.$control().data();
for( var name in ajaxData ) {
ajaxData[ name ] = this.get( name );
}
// extra
ajaxData.action = 'acf/fields/relationship/query';
ajaxData.field_key = this.get('key');
// Filter.
ajaxData = acf.applyFilters( 'relationship_ajax_data', ajaxData, this );
// return
return ajaxData;
},
fetch: function(){
// abort XHR if this field is already loading AJAX data
var xhr = this.get('xhr');
if( xhr ) {
xhr.abort();
}
// add to this.o
var ajaxData = this.getAjaxData();
// clear html if is new query
var $choiceslist = this.$list( 'choices' );
if( ajaxData.paged == 1 ) {
$choiceslist.html('');
}
// loading
var $loading = $('<li><i class="acf-loading"></i> ' + acf.__('Loading') + '</li>');
$choiceslist.append($loading);
this.set('loading', true);
// callback
var onComplete = function(){
this.set('loading', false);
$loading.remove();
};
var onSuccess = function( json ){
// no results
if( !json || !json.results || !json.results.length ) {
// prevent pagination
this.set('more', false);
// add message
if( this.get('paged') == 1 ) {
this.$list('choices').append('<li>' + acf.__('No matches found') + '</li>');
}
// return
return;
}
// set more (allows pagination scroll)
this.set('more', json.more );
// get new results
var html = this.walkChoices(json.results);
var $html = $( html );
// apply .disabled to left li's
var val = this.val();
if( val && val.length ) {
val.map(function( id ){
$html.find('.acf-rel-item[data-id="' + id + '"]').addClass('disabled');
});
}
// append
$choiceslist.append( $html );
// merge together groups
var $prevLabel = false;
var $prevList = false;
$choiceslist.find('.acf-rel-label').each(function(){
var $label = $(this);
var $list = $label.siblings('ul');
if( $prevLabel && $prevLabel.text() == $label.text() ) {
$prevList.append( $list.children() );
$(this).parent().remove();
return;
}
// update vars
$prevLabel = $label;
$prevList = $list;
});
};
// get results
var xhr = $.ajax({
url: acf.get('ajaxurl'),
dataType: 'json',
type: 'post',
data: acf.prepareForAjax(ajaxData),
context: this,
success: onSuccess,
complete: onComplete
});
// set
this.set('xhr', xhr);
},
walkChoices: function( data ){
// walker
var walk = function( data ){
// vars
var html = '';
// is array
if( $.isArray(data) ) {
data.map(function(item){
html += walk( item );
});
// is item
} else if( $.isPlainObject(data) ) {
// group
if( data.children !== undefined ) {
html += '<li><span class="acf-rel-label">' + data.text + '</span><ul class="acf-bl">';
html += walk( data.children );
html += '</ul></li>';
// single
} else {
html += '<li><span class="acf-rel-item" data-id="' + data.id + '">' + data.text + '</span></li>';
}
}
// return
return html;
};
return walk( data );
}
});
acf.registerFieldType( Field );
})(jQuery);
(function($, undefined){
var Field = acf.Field.extend({
type: 'select',
select2: false,
wait: 'load',
events: {
'removeField': 'onRemove'
},
$input: function(){
return this.$('select');
},
initialize: function(){
// vars
var $select = this.$input();
// inherit data
this.inherit( $select );
// select2
if( this.get('ui') ) {
// populate ajax_data (allowing custom attribute to already exist)
var ajaxAction = this.get('ajax_action');
if( !ajaxAction ) {
ajaxAction = 'acf/fields/' + this.get('type') + '/query';
}
// select2
this.select2 = acf.newSelect2($select, {
field: this,
ajax: this.get('ajax'),
multiple: this.get('multiple'),
placeholder: this.get('placeholder'),
allowNull: this.get('allow_null'),
ajaxAction: ajaxAction,
});
}
},
onRemove: function(){
if( this.select2 ) {
this.select2.destroy();
}
}
});
acf.registerFieldType( Field );
})(jQuery);
(function($, undefined){
// vars
var CONTEXT = 'tab';
var Field = acf.Field.extend({
type: 'tab',
wait: '',
tabs: false,
tab: false,
findFields: function(){
return this.$el.nextUntil('.acf-field-tab', '.acf-field');
},
getFields: function(){
return acf.getFields( this.findFields() );
},
findTabs: function(){
return this.$el.prevAll('.acf-tab-wrap:first');
},
findTab: function(){
return this.$('.acf-tab-button');
},
initialize: function(){
// bail early if is td
if( this.$el.is('td') ) {
this.events = {};
return false;
}
// vars
var $tabs = this.findTabs();
var $tab = this.findTab();
var settings = acf.parseArgs($tab.data(), {
endpoint: false,
placement: '',
before: this.$el
});
// create wrap
if( !$tabs.length || settings.endpoint ) {
this.tabs = new Tabs( settings );
} else {
this.tabs = $tabs.data('acf');
}
// add tab
this.tab = this.tabs.addTab($tab, this);
},
isActive: function(){
return this.tab.isActive();
},
showFields: function(){
// show fields
this.getFields().map(function( field ){
field.show( this.cid, CONTEXT );
field.hiddenByTab = false;
}, this);
},
hideFields: function(){
// hide fields
this.getFields().map(function( field ){
field.hide( this.cid, CONTEXT );
field.hiddenByTab = this.tab;
}, this);
},
show: function( lockKey ){
// show field and store result
var visible = acf.Field.prototype.show.apply(this, arguments);
// check if now visible
if( visible ) {
// show tab
this.tab.show();
// check active tabs
this.tabs.refresh();
}
// return
return visible;
},
hide: function( lockKey ){
// hide field and store result
var hidden = acf.Field.prototype.hide.apply(this, arguments);
// check if now hidden
if( hidden ) {
// hide tab
this.tab.hide();
// reset tabs if this was active
if( this.isActive() ) {
this.tabs.reset();
}
}
// return
return hidden;
},
enable: function( lockKey ){
// enable fields
this.getFields().map(function( field ){
field.enable( CONTEXT );
});
},
disable: function( lockKey ){
// disable fields
this.getFields().map(function( field ){
field.disable( CONTEXT );
});
}
});
acf.registerFieldType( Field );
/**
* tabs
*
* description
*
* @date 8/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
var i = 0;
var Tabs = acf.Model.extend({
tabs: [],
active: false,
actions: {
'refresh': 'onRefresh'
},
data: {
before: false,
placement: 'top',
index: 0,
initialized: false,
},
setup: function( settings ){
// data
$.extend(this.data, settings);
// define this prop to avoid scope issues
this.tabs = [];
this.active = false;
// vars
var placement = this.get('placement');
var $before = this.get('before');
var $parent = $before.parent();
// add sidebar for left placement
if( placement == 'left' && $parent.hasClass('acf-fields') ) {
$parent.addClass('-sidebar');
}
// create wrap
if( $before.is('tr') ) {
this.$el = $('<tr class="acf-tab-wrap"><td colspan="2"><ul class="acf-hl acf-tab-group"></ul></td></tr>');
} else {
this.$el = $('<div class="acf-tab-wrap -' + placement + '"><ul class="acf-hl acf-tab-group"></ul></div>');
}
// append
$before.before( this.$el );
// set index
this.set('index', i, true);
i++;
},
initializeTabs: function(){
// find first visible tab
var tab = this.getVisible().shift();
// remember previous tab state
var order = acf.getPreference('this.tabs') || [];
var groupIndex = this.get('index');
var tabIndex = order[ groupIndex ];
if( this.tabs[ tabIndex ] && this.tabs[ tabIndex ].isVisible() ) {
tab = this.tabs[ tabIndex ];
}
// select
if( tab ) {
this.selectTab( tab );
} else {
this.closeTabs();
}
// set local variable used by tabsManager
this.set('initialized', true);
},
getVisible: function(){
return this.tabs.filter(function( tab ){
return tab.isVisible();
});
},
getActive: function(){
return this.active;
},
setActive: function( tab ){
return this.active = tab;
},
hasActive: function(){
return (this.active !== false);
},
isActive: function( tab ){
var active = this.getActive();
return (active && active.cid === tab.cid);
},
closeActive: function(){
if( this.hasActive() ) {
this.closeTab( this.getActive() );
}
},
openTab: function( tab ){
// close existing tab
this.closeActive();
// open
tab.open();
// set active
this.setActive( tab );
},
closeTab: function( tab ){
// close
tab.close();
// set active
this.setActive( false );
},
closeTabs: function(){
this.tabs.map( this.closeTab, this );
},
selectTab: function( tab ){
// close other tabs
this.tabs.map(function( t ){
if( tab.cid !== t.cid ) {
this.closeTab( t );
}
}, this);
// open
this.openTab( tab );
},
addTab: function( $a, field ){
// create <li>
var $li = $('<li></li>');
// append <a>
$li.append( $a );
// append
this.$('ul').append( $li );
// initialize
var tab = new Tab({
$el: $li,
field: field,
group: this,
});
// store
this.tabs.push( tab );
// return
return tab;
},
reset: function(){
// close existing tab
this.closeActive();
// find and active a tab
return this.refresh();
},
refresh: function(){
// bail early if active already exists
if( this.hasActive() ) {
return false;
}
// find next active tab
var tab = this.getVisible().shift();
// open tab
if( tab ) {
this.openTab( tab );
}
// return
return tab;
},
onRefresh: function(){
// only for left placements
if( this.get('placement') !== 'left' ) {
return;
}
// vars
var $parent = this.$el.parent();
var $list = this.$el.children('ul');
var attribute = $parent.is('td') ? 'height' : 'min-height';
// find height (minus 1 for border-bottom)
var height = $list.position().top + $list.outerHeight(true) - 1;
// add css
$parent.css(attribute, height);
}
});
var Tab = acf.Model.extend({
group: false,
field: false,
events: {
'click a': 'onClick'
},
index: function(){
return this.$el.index();
},
isVisible: function(){
return acf.isVisible( this.$el );
},
isActive: function(){
return this.$el.hasClass('active');
},
open: function(){
// add class
this.$el.addClass('active');
// show field
this.field.showFields();
},
close: function(){
// remove class
this.$el.removeClass('active');
// hide field
this.field.hideFields();
},
onClick: function( e, $el ){
// prevent default
e.preventDefault();
// toggle
this.toggle();
},
toggle: function(){
// bail early if already active
if( this.isActive() ) {
return;
}
// toggle this tab
this.group.openTab( this );
}
});
var tabsManager = new acf.Model({
priority: 50,
actions: {
'prepare': 'render',
'append': 'render',
'unload': 'onUnload',
'invalid_field': 'onInvalidField'
},
findTabs: function(){
return $('.acf-tab-wrap');
},
getTabs: function(){
return acf.getInstances( this.findTabs() );
},
render: function( $el ){
this.getTabs().map(function( tabs ){
if( !tabs.get('initialized') ) {
tabs.initializeTabs();
}
});
},
onInvalidField: function( field ){
// bail early if busy
if( this.busy ) {
return;
}
// ignore if not hidden by tab
if( !field.hiddenByTab ) {
return;
}
// toggle tab
field.hiddenByTab.toggle();
// ignore other invalid fields
this.busy = true;
this.setTimeout(function(){
this.busy = false;
}, 100);
},
onUnload: function(){
// vars
var order = [];
// loop
this.getTabs().map(function( group ){
var active = group.hasActive() ? group.getActive().index() : 0;
order.push(active);
});
// bail if no tabs
if( !order.length ) {
return;
}
// update
acf.setPreference('this.tabs', order);
}
});
})(jQuery);
(function($, undefined){
var Field = acf.models.SelectField.extend({
type: 'post_object',
});
acf.registerFieldType( Field );
})(jQuery);
(function($, undefined){
var Field = acf.models.SelectField.extend({
type: 'page_link',
});
acf.registerFieldType( Field );
})(jQuery);
(function($, undefined){
var Field = acf.models.SelectField.extend({
type: 'user',
});
acf.registerFieldType( Field );
})(jQuery);
(function($, undefined){
var Field = acf.Field.extend({
type: 'taxonomy',
data: {
'ftype': 'select'
},
select2: false,
wait: 'load',
events: {
'click a[data-name="add"]': 'onClickAdd',
'click input[type="radio"]': 'onClickRadio',
},
$control: function(){
return this.$('.acf-taxonomy-field');
},
$input: function(){
return this.getRelatedPrototype().$input.apply(this, arguments);
},
getRelatedType: function(){
// vars
var fieldType = this.get('ftype');
// normalize
if( fieldType == 'multi_select' ) {
fieldType = 'select';
}
// return
return fieldType;
},
getRelatedPrototype: function(){
return acf.getFieldType( this.getRelatedType() ).prototype;
},
getValue: function(){
return this.getRelatedPrototype().getValue.apply(this, arguments);
},
setValue: function(){
return this.getRelatedPrototype().setValue.apply(this, arguments);
},
initialize: function(){
this.getRelatedPrototype().initialize.apply(this, arguments);
},
onRemove: function(){
if( this.select2 ) {
this.select2.destroy();
}
},
onClickAdd: function( e, $el ){
// vars
var field = this;
var popup = false;
var $form = false;
var $name = false;
var $parent = false;
var $button = false;
var $message = false;
var notice = false;
// step 1.
var step1 = function(){
// popup
popup = acf.newPopup({
title: $el.attr('title'),
loading: true,
width: '300px'
});
// ajax
var ajaxData = {
action: 'acf/fields/taxonomy/add_term',
field_key: field.get('key')
};
// get HTML
$.ajax({
url: acf.get('ajaxurl'),
data: acf.prepareForAjax(ajaxData),
type: 'post',
dataType: 'html',
success: step2
});
};
// step 2.
var step2 = function( html ){
// update popup
popup.loading(false);
popup.content(html);
// vars
$form = popup.$('form');
$name = popup.$('input[name="term_name"]');
$parent = popup.$('select[name="term_parent"]');
$button = popup.$('.acf-submit-button');
// focus
$name.focus();
// submit form
popup.on('submit', 'form', step3);
};
// step 3.
var step3 = function( e, $el ){
// prevent
e.preventDefault();
e.stopImmediatePropagation();
// basic validation
if( $name.val() === '' ) {
$name.focus();
return false;
}
// disable
acf.startButtonLoading( $button );
// ajax
var ajaxData = {
action: 'acf/fields/taxonomy/add_term',
field_key: field.get('key'),
term_name: $name.val(),
term_parent: $parent.length ? $parent.val() : 0
};
$.ajax({
url: acf.get('ajaxurl'),
data: acf.prepareForAjax(ajaxData),
type: 'post',
dataType: 'json',
success: step4
});
};
// step 4.
var step4 = function( json ){
// enable
acf.stopButtonLoading( $button );
// remove prev notice
if( notice ) {
notice.remove();
}
// success
if( acf.isAjaxSuccess(json) ) {
// clear name
$name.val('');
// update term lists
step5( json.data );
// notice
notice = acf.newNotice({
type: 'success',
text: acf.getAjaxMessage(json),
target: $form,
timeout: 2000,
dismiss: false
});
} else {
// notice
notice = acf.newNotice({
type: 'error',
text: acf.getAjaxError(json),
target: $form,
timeout: 2000,
dismiss: false
});
}
// focus
$name.focus();
};
// step 5.
var step5 = function( term ){
// update parent dropdown
var $option = $('<option value="' + term.term_id + '">' + term.term_label + '</option>');
if( term.term_parent ) {
$parent.children('option[value="' + term.term_parent + '"]').after( $option );
} else {
$parent.append( $option );
}
// add this new term to all taxonomy field
var fields = acf.getFields({
type: 'taxonomy'
});
fields.map(function( otherField ){
if( otherField.get('taxonomy') == field.get('taxonomy') ) {
otherField.appendTerm( term );
}
});
// select
field.selectTerm( term.term_id );
};
// run
step1();
},
appendTerm: function( term ){
if( this.getRelatedType() == 'select' ) {
this.appendTermSelect( term );
} else {
this.appendTermCheckbox( term );
}
},
appendTermSelect: function( term ){
this.select2.addOption({
id: term.term_id,
text: term.term_label
});
},
appendTermCheckbox: function( term ){
// vars
var name = this.$('[name]:first').attr('name');
var $ul = this.$('ul:first');
// allow multiple selection
if( this.getRelatedType() == 'checkbox' ) {
name += '[]';
}
// create new li
var $li = $([
'<li data-id="' + term.term_id + '">',
'<label>',
'<input type="' + this.get('ftype') + '" value="' + term.term_id + '" name="' + name + '" /> ',
'<span>' + term.term_name + '</span>',
'</label>',
'</li>'
].join(''));
// find parent
if( term.term_parent ) {
// vars
var $parent = $ul.find('li[data-id="' + term.term_parent + '"]');
// update vars
$ul = $parent.children('ul');
// create ul
if( !$ul.exists() ) {
$ul = $('<ul class="children acf-bl"></ul>');
$parent.append( $ul );
}
}
// append
$ul.append( $li );
},
selectTerm: function( id ){
if( this.getRelatedType() == 'select' ) {
this.select2.selectOption( id );
} else {
var $input = this.$('input[value="' + id + '"]');
$input.prop('checked', true).trigger('change');
}
},
onClickRadio: function( e, $el ){
// vars
var $label = $el.parent('label');
var selected = $label.hasClass('selected');
// remove previous selected
this.$('.selected').removeClass('selected');
// add active class
$label.addClass('selected');
// allow null
if( this.get('allow_null') && selected ) {
$label.removeClass('selected');
$el.prop('checked', false).trigger('change');
}
}
});
acf.registerFieldType( Field );
})(jQuery);
(function($, undefined){
var Field = acf.models.DatePickerField.extend({
type: 'time_picker',
$control: function(){
return this.$('.acf-time-picker');
},
initialize: function(){
// vars
var $input = this.$input();
var $inputText = this.$inputText();
// args
var args = {
timeFormat: this.get('time_format'),
altField: $input,
altFieldTimeOnly: false,
altTimeFormat: 'HH:mm:ss',
showButtonPanel: true,
controlType: 'select',
oneLine: true,
closeText: acf.get('dateTimePickerL10n').selectText,
timeOnly: true,
};
// add custom 'Close = Select' functionality
args.onClose = function( value, dp_instance, t_instance ){
// vars
var $close = dp_instance.dpDiv.find('.ui-datepicker-close');
// if clicking close button
if( !value && $close.is(':hover') ) {
t_instance._updateDateTime();
}
};
// filter
args = acf.applyFilters('time_picker_args', args, this);
// add date time picker
acf.newTimePicker( $inputText, args );
// action
acf.doAction('time_picker_init', $inputText, args, this);
}
});
acf.registerFieldType( Field );
// add
acf.newTimePicker = function( $input, args ){
// bail ealry if no datepicker library
if( typeof $.timepicker === 'undefined' ) {
return false;
}
// defaults
args = args || {};
// initialize
$input.timepicker( args );
// wrap the datepicker (only if it hasn't already been wrapped)
if( $('body > #ui-datepicker-div').exists() ) {
$('body > #ui-datepicker-div').wrap('<div class="acf-ui-datepicker" />');
}
};
})(jQuery);
(function($, undefined){
var Field = acf.Field.extend({
type: 'true_false',
events: {
'change .acf-switch-input': 'onChange',
'focus .acf-switch-input': 'onFocus',
'blur .acf-switch-input': 'onBlur',
'keypress .acf-switch-input': 'onKeypress'
},
$input: function(){
return this.$('input[type="checkbox"]');
},
$switch: function(){
return this.$('.acf-switch');
},
getValue: function(){
return this.$input().prop('checked') ? 1 : 0;
},
initialize: function(){
this.render();
},
render: function(){
// vars
var $switch = this.$switch();
// bail ealry if no $switch
if( !$switch.length ) return;
// vars
var $on = $switch.children('.acf-switch-on');
var $off = $switch.children('.acf-switch-off');
var width = Math.max( $on.width(), $off.width() );
// bail ealry if no width
if( !width ) return;
// set widths
$on.css( 'min-width', width );
$off.css( 'min-width', width );
},
switchOn: function() {
this.$input().prop('checked', true);
this.$switch().addClass('-on');
},
switchOff: function() {
this.$input().prop('checked', false);
this.$switch().removeClass('-on');
},
onChange: function( e, $el ){
if( $el.prop('checked') ) {
this.switchOn();
} else {
this.switchOff();
}
},
onFocus: function( e, $el ){
this.$switch().addClass('-focus');
},
onBlur: function( e, $el ){
this.$switch().removeClass('-focus');
},
onKeypress: function( e, $el ){
// left
if( e.keyCode === 37 ) {
return this.switchOff();
}
// right
if( e.keyCode === 39 ) {
return this.switchOn();
}
}
});
acf.registerFieldType( Field );
})(jQuery);
(function($, undefined){
var Field = acf.Field.extend({
type: 'url',
events: {
'keyup input[type="url"]': 'onkeyup'
},
$control: function(){
return this.$('.acf-input-wrap');
},
$input: function(){
return this.$('input[type="url"]');
},
initialize: function(){
this.render();
},
isValid: function(){
// vars
var val = this.val();
// bail early if no val
if( !val ) {
return false;
}
// url
if( val.indexOf('://') !== -1 ) {
return true;
}
// protocol relative url
if( val.indexOf('//') === 0 ) {
return true;
}
// return
return false;
},
render: function(){
// add class
if( this.isValid() ) {
this.$control().addClass('-valid');
} else {
this.$control().removeClass('-valid');
}
},
onkeyup: function( e, $el ){
this.render();
}
});
acf.registerFieldType( Field );
})(jQuery);
(function($, undefined){
var Field = acf.Field.extend({
type: 'wysiwyg',
wait: 'load',
events: {
'mousedown .acf-editor-wrap.delay': 'onMousedown',
'unmountField': 'disableEditor',
'remountField': 'enableEditor',
'removeField': 'disableEditor'
},
$control: function(){
return this.$('.acf-editor-wrap');
},
$input: function(){
return this.$('textarea');
},
getMode: function(){
return this.$control().hasClass('tmce-active') ? 'visual' : 'text';
},
initialize: function(){
// initializeEditor if no delay
if( !this.$control().hasClass('delay') ) {
this.initializeEditor();
}
},
initializeEditor: function(){
// vars
var $wrap = this.$control();
var $textarea = this.$input();
var args = {
tinymce: true,
quicktags: true,
toolbar: this.get('toolbar'),
mode: this.getMode(),
field: this
};
// generate new id
var oldId = $textarea.attr('id');
var newId = acf.uniqueId('acf-editor-');
// store copy of textarea data
var data = $textarea.data();
// rename
acf.rename({
target: $wrap,
search: oldId,
replace: newId,
destructive: true
});
// update id
this.set('id', newId, true);
// initialize
acf.tinymce.initialize( newId, args );
// apply data to new textarea (acf.rename creates a new textarea element due to destructive mode)
// fixes bug where conditional logic "disabled" is lost during "screen_check"
this.$input().data(data);
},
onMousedown: function( e ){
// prevent default
e.preventDefault();
// remove delay class
var $wrap = this.$control();
$wrap.removeClass('delay');
$wrap.find('.acf-editor-toolbar').remove();
// initialize
this.initializeEditor();
},
enableEditor: function(){
if( this.getMode() == 'visual' ) {
acf.tinymce.enable( this.get('id') );
}
},
disableEditor: function(){
acf.tinymce.destroy( this.get('id') );
}
});
acf.registerFieldType( Field );
})(jQuery);
(function($, undefined){
// vars
var storage = [];
/**
* acf.Condition
*
* description
*
* @date 23/3/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.Condition = acf.Model.extend({
type: '', // used for model name
operator: '==', // rule operator
label: '', // label shown when editing fields
choiceType: 'input', // input, select
fieldTypes: [], // auto connect this conditions with these field types
data: {
conditions: false, // the parent instance
field: false, // the field which we query against
rule: {} // the rule [field, operator, value]
},
events: {
'change': 'change',
'keyup': 'change',
'enableField': 'change',
'disableField': 'change'
},
setup: function( props ){
$.extend(this.data, props);
},
getEventTarget: function( $el, event ){
return $el || this.get('field').$el;
},
change: function( e, $el ){
this.get('conditions').change( e );
},
match: function( rule, field ){
return false;
},
calculate: function(){
return this.match( this.get('rule'), this.get('field') );
},
choices: function( field ){
return '<input type="text" />';
}
});
/**
* acf.newCondition
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.newCondition = function( rule, conditions ){
// currently setting up conditions for fieldX, this field is the 'target'
var target = conditions.get('field');
// use the 'target' to find the 'trigger' field.
// - this field is used to setup the conditional logic events
var field = target.getField( rule.field );
// bail ealry if no target or no field (possible if field doesn't exist due to HTML error)
if( !target || !field ) {
return false;
}
// vars
var args = {
rule: rule,
target: target,
conditions: conditions,
field: field
};
// vars
var fieldType = field.get('type');
var operator = rule.operator;
// get avaibale conditions
var conditionTypes = acf.getConditionTypes({
fieldType: fieldType,
operator: operator,
});
// instantiate
var model = conditionTypes[0] || acf.Condition;
// instantiate
var condition = new model( args );
// return
return condition;
};
/**
* mid
*
* Calculates the model ID for a field type
*
* @date 15/12/17
* @since 5.6.5
*
* @param string type
* @return string
*/
var modelId = function( type ) {
return acf.strPascalCase( type || '' ) + 'Condition';
};
/**
* acf.registerConditionType
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.registerConditionType = function( model ){
// vars
var proto = model.prototype;
var type = proto.type;
var mid = modelId( type );
// store model
acf.models[ mid ] = model;
// store reference
storage.push( type );
};
/**
* acf.getConditionType
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.getConditionType = function( type ){
var mid = modelId( type );
return acf.models[ mid ] || false;
}
/**
* acf.registerConditionForFieldType
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.registerConditionForFieldType = function( conditionType, fieldType ){
// get model
var model = acf.getConditionType( conditionType );
// append
if( model ) {
model.prototype.fieldTypes.push( fieldType );
}
};
/**
* acf.getConditionTypes
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.getConditionTypes = function( args ){
// defaults
args = acf.parseArgs(args, {
fieldType: '',
operator: ''
});
// clonse available types
var types = [];
// loop
storage.map(function( type ){
// vars
var model = acf.getConditionType(type);
var ProtoFieldTypes = model.prototype.fieldTypes;
var ProtoOperator = model.prototype.operator;
// check fieldType
if( args.fieldType && ProtoFieldTypes.indexOf( args.fieldType ) === -1 ) {
return;
}
// check operator
if( args.operator && ProtoOperator !== args.operator ) {
return;
}
// append
types.push( model );
});
// return
return types;
};
})(jQuery);
(function($, undefined){
// vars
var CONTEXT = 'conditional_logic';
/**
* conditionsManager
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
var conditionsManager = new acf.Model({
id: 'conditionsManager',
priority: 20, // run actions later
actions: {
'new_field': 'onNewField',
},
onNewField: function( field ){
if( field.has('conditions') ) {
field.getConditions().render();
}
},
});
/**
* acf.Field.prototype.getField
*
* Finds a field that is related to another field
*
* @date 1/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
var getSiblingField = function( field, key ){
// find sibling (very fast)
var fields = acf.getFields({
key: key,
sibling: field.$el,
suppressFilters: true,
});
// find sibling-children (fast)
// needed for group fields, accordions, etc
if( !fields.length ) {
fields = acf.getFields({
key: key,
parent: field.$el.parent(),
suppressFilters: true,
});
}
// return
if( fields.length ) {
return fields[0];
}
return false;
};
acf.Field.prototype.getField = function( key ){
// get sibling field
var field = getSiblingField( this, key );
// return early
if( field ) {
return field;
}
// move up through each parent and try again
var parents = this.parents();
for( var i = 0; i < parents.length; i++ ) {
// get sibling field
field = getSiblingField( parents[i], key );
// return early
if( field ) {
return field;
}
}
// return
return false;
};
/**
* acf.Field.prototype.getConditions
*
* Returns the field's conditions instance
*
* @date 1/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.Field.prototype.getConditions = function(){
// instantiate
if( !this.conditions ) {
this.conditions = new Conditions( this );
}
// return
return this.conditions;
};
/**
* Conditions
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
var timeout = false;
var Conditions = acf.Model.extend({
id: 'Conditions',
data: {
field: false, // The field with "data-conditions" (target).
timeStamp: false, // Reference used during "change" event.
groups: [], // The groups of condition instances.
},
setup: function( field ){
// data
this.data.field = field;
// vars
var conditions = field.get('conditions');
// detect groups
if( conditions instanceof Array ) {
// detect groups
if( conditions[0] instanceof Array ) {
// loop
conditions.map(function(rules, i){
this.addRules( rules, i );
}, this);
// detect rules
} else {
this.addRules( conditions );
}
// detect rule
} else {
this.addRule( conditions );
}
},
change: function( e ){
// this function may be triggered multiple times per event due to multiple condition classes
// compare timestamp to allow only 1 trigger per event
if( this.get('timeStamp') === e.timeStamp ) {
return false;
} else {
this.set('timeStamp', e.timeStamp, true);
}
// render condition and store result
var changed = this.render();
},
render: function(){
return this.calculate() ? this.show() : this.hide();
},
show: function(){
return this.get('field').showEnable(this.cid, CONTEXT);
},
hide: function(){
return this.get('field').hideDisable(this.cid, CONTEXT);
},
calculate: function(){
// vars
var pass = false;
// loop
this.getGroups().map(function( group ){
// igrnore this group if another group passed
if( pass ) return;
// find passed
var passed = group.filter(function(condition){
return condition.calculate();
});
// if all conditions passed, update the global var
if( passed.length == group.length ) {
pass = true;
}
});
return pass;
},
hasGroups: function(){
return this.data.groups != null;
},
getGroups: function(){
return this.data.groups;
},
addGroup: function(){
var group = [];
this.data.groups.push( group );
return group;
},
hasGroup: function( i ){
return this.data.groups[i] != null;
},
getGroup: function( i ){
return this.data.groups[i];
},
removeGroup: function( i ){
this.data.groups[i].delete;
return this;
},
addRules: function( rules, group ){
rules.map(function( rule ){
this.addRule( rule, group );
}, this);
},
addRule: function( rule, group ){
// defaults
group = group || 0;
// vars
var groupArray;
// get group
if( this.hasGroup(group) ) {
groupArray = this.getGroup(group);
} else {
groupArray = this.addGroup();
}
// instantiate
var condition = acf.newCondition( rule, this );
// bail ealry if condition failed (field did not exist)
if( !condition ) {
return false;
}
// add rule
groupArray.push(condition);
},
hasRule: function(){
},
getRule: function( rule, group ){
// defaults
rule = rule || 0;
group = group || 0;
return this.data.groups[ group ][ rule ];
},
removeRule: function(){
}
});
})(jQuery);
(function($, undefined){
var __ = acf.__;
var parseString = function( val ){
return val ? '' + val : '';
};
var isEqualTo = function( v1, v2 ){
return ( parseString(v1).toLowerCase() === parseString(v2).toLowerCase() );
};
var isEqualToNumber = function( v1, v2 ){
return ( parseFloat(v1) === parseFloat(v2) );
};
var isGreaterThan = function( v1, v2 ){
return ( parseFloat(v1) > parseFloat(v2) );
};
var isLessThan = function( v1, v2 ){
return ( parseFloat(v1) < parseFloat(v2) );
};
var inArray = function( v1, array ){
// cast all values as string
array = array.map(function(v2){
return parseString(v2);
});
return (array.indexOf( v1 ) > -1);
}
var containsString = function( haystack, needle ){
return ( parseString(haystack).indexOf( parseString(needle) ) > -1 );
};
var matchesPattern = function( v1, pattern ){
var regexp = new RegExp(parseString(pattern), 'gi');
return parseString(v1).match( regexp );
};
/**
* hasValue
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param void
* @return void
*/
var HasValue = acf.Condition.extend({
type: 'hasValue',
operator: '!=empty',
label: __('Has any value'),
fieldTypes: [ 'text', 'textarea', 'number', 'range', 'email', 'url', 'password', 'image', 'file', 'wysiwyg', 'oembed', 'select', 'checkbox', 'radio', 'button_group', 'link', 'post_object', 'page_link', 'relationship', 'taxonomy', 'user', 'google_map', 'date_picker', 'date_time_picker', 'time_picker', 'color_picker' ],
match: function( rule, field ){
return (field.val() ? true : false);
},
choices: function( fieldObject ){
return '<input type="text" disabled="" />';
}
});
acf.registerConditionType( HasValue );
/**
* hasValue
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param void
* @return void
*/
var HasNoValue = HasValue.extend({
type: 'hasNoValue',
operator: '==empty',
label: __('Has no value'),
match: function( rule, field ){
return !HasValue.prototype.match.apply(this, arguments);
}
});
acf.registerConditionType( HasNoValue );
/**
* EqualTo
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param void
* @return void
*/
var EqualTo = acf.Condition.extend({
type: 'equalTo',
operator: '==',
label: __('Value is equal to'),
fieldTypes: [ 'text', 'textarea', 'number', 'range', 'email', 'url', 'password' ],
match: function( rule, field ){
if( $.isNumeric(rule.value) ) {
return isEqualToNumber( rule.value, field.val() );
} else {
return isEqualTo( rule.value, field.val() );
}
},
choices: function( fieldObject ){
return '<input type="text" />';
}
});
acf.registerConditionType( EqualTo );
/**
* NotEqualTo
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param void
* @return void
*/
var NotEqualTo = EqualTo.extend({
type: 'notEqualTo',
operator: '!=',
label: __('Value is not equal to'),
match: function( rule, field ){
return !EqualTo.prototype.match.apply(this, arguments);
}
});
acf.registerConditionType( NotEqualTo );
/**
* PatternMatch
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param void
* @return void
*/
var PatternMatch = acf.Condition.extend({
type: 'patternMatch',
operator: '==pattern',
label: __('Value matches pattern'),
fieldTypes: [ 'text', 'textarea', 'email', 'url', 'password', 'wysiwyg' ],
match: function( rule, field ){
return matchesPattern( field.val(), rule.value );
},
choices: function( fieldObject ){
return '<input type="text" placeholder="[a-z0-9]" />';
}
});
acf.registerConditionType( PatternMatch );
/**
* Contains
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param void
* @return void
*/
var Contains = acf.Condition.extend({
type: 'contains',
operator: '==contains',
label: __('Value contains'),
fieldTypes: [ 'text', 'textarea', 'number', 'email', 'url', 'password', 'wysiwyg', 'oembed', 'select' ],
match: function( rule, field ){
return containsString( field.val(), rule.value );
},
choices: function( fieldObject ){
return '<input type="text" />';
}
});
acf.registerConditionType( Contains );
/**
* TrueFalseEqualTo
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param void
* @return void
*/
var TrueFalseEqualTo = EqualTo.extend({
type: 'trueFalseEqualTo',
choiceType: 'select',
fieldTypes: [ 'true_false' ],
choices: function( field ){
return [
{
id: 1,
text: __('Checked')
}
];
},
});
acf.registerConditionType( TrueFalseEqualTo );
/**
* TrueFalseNotEqualTo
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param void
* @return void
*/
var TrueFalseNotEqualTo = NotEqualTo.extend({
type: 'trueFalseNotEqualTo',
choiceType: 'select',
fieldTypes: [ 'true_false' ],
choices: function( field ){
return [
{
id: 1,
text: __('Checked')
}
];
},
});
acf.registerConditionType( TrueFalseNotEqualTo );
/**
* SelectEqualTo
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param void
* @return void
*/
var SelectEqualTo = acf.Condition.extend({
type: 'selectEqualTo',
operator: '==',
label: __('Value is equal to'),
fieldTypes: [ 'select', 'checkbox', 'radio', 'button_group' ],
match: function( rule, field ){
var val = field.val();
if( val instanceof Array ) {
return inArray( rule.value, val );
} else {
return isEqualTo( rule.value, val );
}
},
choices: function( fieldObject ){
// vars
var choices = [];
var lines = fieldObject.$setting('choices textarea').val().split("\n");
// allow null
if( fieldObject.$input('allow_null').prop('checked') ) {
choices.push({
id: '',
text: __('Null')
});
}
// loop
lines.map(function( line ){
// split
line = line.split(':');
// default label to value
line[1] = line[1] || line[0];
// append
choices.push({
id: $.trim( line[0] ),
text: $.trim( line[1] )
});
});
// return
return choices;
},
});
acf.registerConditionType( SelectEqualTo );
/**
* SelectNotEqualTo
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param void
* @return void
*/
var SelectNotEqualTo = SelectEqualTo.extend({
type: 'selectNotEqualTo',
operator: '!=',
label: __('Value is not equal to'),
match: function( rule, field ){
return !SelectEqualTo.prototype.match.apply(this, arguments);
}
});
acf.registerConditionType( SelectNotEqualTo );
/**
* GreaterThan
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param void
* @return void
*/
var GreaterThan = acf.Condition.extend({
type: 'greaterThan',
operator: '>',
label: __('Value is greater than'),
fieldTypes: [ 'number', 'range' ],
match: function( rule, field ){
var val = field.val();
if( val instanceof Array ) {
val = val.length;
}
return isGreaterThan( val, rule.value );
},
choices: function( fieldObject ){
return '<input type="number" />';
}
});
acf.registerConditionType( GreaterThan );
/**
* LessThan
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param void
* @return void
*/
var LessThan = GreaterThan.extend({
type: 'lessThan',
operator: '<',
label: __('Value is less than'),
match: function( rule, field ){
var val = field.val();
if( val instanceof Array ) {
val = val.length;
}
return isLessThan( val, rule.value );
},
choices: function( fieldObject ){
return '<input type="number" />';
}
});
acf.registerConditionType( LessThan );
/**
* SelectedGreaterThan
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param void
* @return void
*/
var SelectionGreaterThan = GreaterThan.extend({
type: 'selectionGreaterThan',
label: __('Selection is greater than'),
fieldTypes: [ 'checkbox', 'select', 'post_object', 'page_link', 'relationship', 'taxonomy', 'user' ],
});
acf.registerConditionType( SelectionGreaterThan );
/**
* SelectedGreaterThan
*
* description
*
* @date 1/2/18
* @since 5.6.5
*
* @param void
* @return void
*/
var SelectionLessThan = LessThan.extend({
type: 'selectionLessThan',
label: __('Selection is less than'),
fieldTypes: [ 'checkbox', 'select', 'post_object', 'page_link', 'relationship', 'taxonomy', 'user' ],
});
acf.registerConditionType( SelectionLessThan );
})(jQuery);
(function($, undefined){
/**
* acf.newMediaPopup
*
* description
*
* @date 10/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.newMediaPopup = function( args ){
// args
var popup = null;
var args = acf.parseArgs(args, {
mode: 'select', // 'select', 'edit'
title: '', // 'Upload Image'
button: '', // 'Select Image'
type: '', // 'image', ''
field: false, // field instance
allowedTypes: '', // '.jpg, .png, etc'
library: 'all', // 'all', 'uploadedTo'
multiple: false, // false, true, 'add'
attachment: 0, // the attachment to edit
autoOpen: true, // open the popup automatically
open: function(){}, // callback after close
select: function(){}, // callback after select
close: function(){} // callback after close
});
// initialize
if( args.mode == 'edit' ) {
popup = new acf.models.EditMediaPopup( args );
} else {
popup = new acf.models.SelectMediaPopup( args );
}
// open popup (allow frame customization before opening)
if( args.autoOpen ) {
setTimeout(function(){
popup.open();
}, 1);
}
// action
acf.doAction('new_media_popup', popup);
// return
return popup;
};
/**
* getPostID
*
* description
*
* @date 10/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
var getPostID = function() {
var postID = acf.get('post_id');
return $.isNumeric(postID) ? postID : 0;
}
/**
* acf.getMimeTypes
*
* description
*
* @date 11/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.getMimeTypes = function(){
return this.get('mimeTypes');
};
acf.getMimeType = function( name ){
// vars
var allTypes = acf.getMimeTypes();
// search
if( allTypes[name] !== undefined ) {
return allTypes[name];
}
// some types contain a mixed key such as "jpg|jpeg|jpe"
for( var key in allTypes ) {
if( key.indexOf(name) !== -1 ) {
return allTypes[key];
}
}
// return
return false;
};
/**
* MediaPopup
*
* description
*
* @date 10/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
var MediaPopup = acf.Model.extend({
id: 'MediaPopup',
data: {},
defaults: {},
frame: false,
setup: function( props ){
$.extend(this.data, props);
},
initialize: function(){
// vars
var options = this.getFrameOptions();
// add states
this.addFrameStates( options );
// create frame
var frame = wp.media( options );
// add args reference
frame.acf = this;
// add events
this.addFrameEvents( frame, options );
// strore frame
this.frame = frame;
},
open: function(){
this.frame.open();
},
close: function(){
this.frame.close();
},
remove: function(){
this.frame.detach();
this.frame.remove();
},
getFrameOptions: function(){
// vars
var options = {
title: this.get('title'),
multiple: this.get('multiple'),
library: {},
states: []
};
// type
if( this.get('type') ) {
options.library.type = this.get('type');
}
// type
if( this.get('library') === 'uploadedTo' ) {
options.library.uploadedTo = getPostID();
}
// attachment
if( this.get('attachment') ) {
options.library.post__in = [ this.get('attachment') ];
}
// button
if( this.get('button') ) {
options.button = {
text: this.get('button')
};
}
// return
return options;
},
addFrameStates: function( options ){
// create query
var Query = wp.media.query( options.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( this.get('field') && acf.isset(Query, 'mirroring', 'args') ) {
Query.mirroring.args._acfuploader = this.get('field');
}
// add states
options.states.push(
// main state
new wp.media.controller.Library({
library: Query,
multiple: this.get('multiple'),
title: this.get('title'),
priority: 20,
filterable: 'all',
editable: true,
allowLocalEdits: true
})
);
// edit image functionality (added in WP 3.9)
if( acf.isset(wp, 'media', 'controller', 'EditImage') ) {
options.states.push( new wp.media.controller.EditImage() );
}
},
addFrameEvents: function( frame, options ){
// log all events
//frame.on('all', function( e ) {
// console.log( 'frame all: %o', e );
//});
// add class
frame.on('open',function() {
this.$el.closest('.media-modal').addClass('acf-media-modal -' + this.acf.get('mode') );
}, frame);
// edit image view
// source: media-views.js:2410 editImageContent()
frame.on('content:render:edit-image', function(){
var image = this.state().get('image');
var 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 );
// on select
frame.on('select', function() {
// vars
var selection = frame.state().get('selection');
// if selecting images
if( selection ) {
// loop
selection.each(function( attachment, i ){
frame.acf.get('select').apply( frame.acf, [attachment, i] );
});
}
});
// on close
frame.on('close',function(){
// callback and remove
setTimeout(function(){
frame.acf.get('close').apply( frame.acf );
frame.acf.remove();
}, 1);
});
}
});
/**
* acf.models.SelectMediaPopup
*
* description
*
* @date 10/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.models.SelectMediaPopup = MediaPopup.extend({
id: 'SelectMediaPopup',
setup: function( props ){
// default button
if( !props.button ) {
props.button = acf._x('Select', 'verb');
}
// parent
MediaPopup.prototype.setup.apply(this, arguments);
},
addFrameEvents: function( frame, options ){
// 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 = this.get('field');
// remove acf_field so future Uploaders won't inherit
frame.on('open', function(){
delete _wpPluploadSettings.defaults.multipart_params._acfuploader;
});
}
// browse
frame.on('content:activate:browse', function(){
// vars
var toolbar = false;
// populate above vars making sure to allow for failure
// perhaps toolbar does not exist because the frame open is Upload Files
try {
toolbar = frame.content.get().toolbar;
} catch(e) {
console.log(e);
return;
}
// callback
frame.acf.customizeFilters.apply(frame.acf, [toolbar]);
});
// parent
MediaPopup.prototype.addFrameEvents.apply(this, arguments);
},
customizeFilters: function( toolbar ){
// vars
var filters = toolbar.get('filters');
// image
if( this.get('type') == 'image' ) {
// update all
filters.filters.all.text = acf.__('All images');
// remove some filters
delete filters.filters.audio;
delete filters.filters.video;
delete filters.filters.image;
// update all filters to show images
$.each(filters.filters, function( i, filter ){
filter.props.type = filter.props.type || 'image';
});
}
// specific types
if( this.get('allowedTypes') ) {
// convert ".jpg, .png" into ["jpg", "png"]
var allowedTypes = this.get('allowedTypes').split(' ').join('').split('.').join('').split(',');
// loop
allowedTypes.map(function( name ){
// get type
var mimeType = acf.getMimeType( name );
// bail early if no type
if( !mimeType ) return;
// create new filter
var newFilter = {
text: mimeType,
props: {
status: null,
type: mimeType,
uploadedTo: null,
orderby: 'date',
order: 'DESC'
},
priority: 20
};
// append
filters.filters[ mimeType ] = newFilter;
});
}
// uploaded to post
if( this.get('library') === 'uploadedTo' ) {
// vars
var uploadedTo = this.frame.options.library.uploadedTo;
// remove some filters
delete filters.filters.unattached;
delete filters.filters.uploaded;
// add uploadedTo to filters
$.each(filters.filters, function( i, filter ){
filter.text += ' (' + acf.__('Uploaded to this post') + ')';
filter.props.uploadedTo = uploadedTo;
});
}
// add _acfuploader to filters
var field = this.get('field');
$.each(filters.filters, function( k, filter ){
filter.props._acfuploader = field;
});
// add _acfuplaoder to search
var search = toolbar.get('search');
search.model.attributes._acfuploader = field;
// render (custom function added to prototype)
if( filters.renderFilters ) {
filters.renderFilters();
}
}
});
/**
* acf.models.EditMediaPopup
*
* description
*
* @date 10/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.models.EditMediaPopup = MediaPopup.extend({
id: 'SelectMediaPopup',
setup: function( props ){
// default button
if( !props.button ) {
props.button = acf._x('Update', 'verb');
}
// parent
MediaPopup.prototype.setup.apply(this, arguments);
},
addFrameEvents: function( frame, options ){
// 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();
var selection = state.get('selection');
var attachment = wp.media.attachment( frame.acf.get('attachment') );
selection.add( attachment );
}, frame);
// parent
MediaPopup.prototype.addFrameEvents.apply(this, arguments);
}
});
/**
* customizePrototypes
*
* description
*
* @date 11/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
var customizePrototypes = new acf.Model({
id: 'customizePrototypes',
wait: 'ready',
initialize: function(){
// bail early if no media views
if( !acf.isset(window, 'wp', 'media', 'view') ) {
return;
}
// fix bug where CPT without "editor" does not set post.id setting which then prevents uploadedTo from working
var postID = getPostID();
if( postID && acf.isset(wp, 'media', 'view', 'settings', 'post') ) {
wp.media.view.settings.post.id = postID;
}
// customize
this.customizeAttachmentsButton();
this.customizeAttachmentsRouter();
this.customizeAttachmentFilters();
this.customizeAttachmentCompat();
this.customizeAttachmentLibrary();
},
customizeAttachmentsButton: function(){
// validate
if( !acf.isset(wp, 'media', 'view', 'Button') ) {
return;
}
// Extend
var Button = wp.media.view.Button;
wp.media.view.Button = Button.extend({
// Fix bug where "Select" button appears blank after editing an image.
// Do this by simplifying Button initialize function and avoid deleting this.options.
initialize: function() {
var options = _.defaults( this.options, this.defaults );
this.model = new Backbone.Model( options );
this.listenTo( this.model, 'change', this.render );
}
});
},
customizeAttachmentsRouter: function(){
// validate
if( !acf.isset(wp, 'media', 'view', 'Router') ) {
return;
}
// vars
var Parent = wp.media.view.Router;
// extend
wp.media.view.Router = Parent.extend({
addExpand: function(){
// vars
var $a = $([
'<a href="#" class="acf-expand-details">',
'<span class="is-closed"><span class="acf-icon -left small grey"></span>' + acf.__('Expand Details') + '</span>',
'<span class="is-open"><span class="acf-icon -right small grey"></span>' + acf.__('Collapse Details') + '</span>',
'</a>'
].join(''));
// add events
$a.on('click', function( e ){
e.preventDefault();
var $div = $(this).closest('.media-modal');
if( $div.hasClass('acf-expanded') ) {
$div.removeClass('acf-expanded');
} else {
$div.addClass('acf-expanded');
}
});
// append
this.$el.append( $a );
},
initialize: function(){
// initialize
Parent.prototype.initialize.apply( this, arguments );
// add buttons
this.addExpand();
// return
return this;
}
});
},
customizeAttachmentFilters: function(){
// validate
if( !acf.isset(wp, 'media', 'view', 'AttachmentFilters', 'All') ) {
return;
}
// vars
var Parent = wp.media.view.AttachmentFilters.All;
// renderFilters
// copied from media-views.js:6939
Parent.prototype.renderFilters = function(){
// Build `<option>` elements.
this.$el.html( _.chain( this.filters ).map( function( filter, value ) {
return {
el: $( '<option></option>' ).val( value ).html( filter.text )[0],
priority: filter.priority || 50
};
}, this ).sortBy('priority').pluck('el').value() );
};
},
customizeAttachmentCompat: function(){
// validate
if( !acf.isset(wp, 'media', 'view', 'AttachmentCompat') ) {
return;
}
// vars
var AttachmentCompat = wp.media.view.AttachmentCompat;
var timeout = false;
// extend
wp.media.view.AttachmentCompat = AttachmentCompat.extend({
render: function() {
// WP bug
// When multiple media frames exist on the same page (WP content, WYSIWYG, image, file ),
// WP creates multiple instances of this AttachmentCompat view.
// Each instance will attempt to render when a new modal is created.
// Use a property to avoid this and only render once per instance.
if( this.rendered ) {
return this;
}
// render HTML
AttachmentCompat.prototype.render.apply( this, arguments );
// when uploading, render is called twice.
// ignore first render by checking for #acf-form-data element
if( !this.$('#acf-form-data').length ) {
return this;
}
// clear timeout
clearTimeout( timeout );
// setTimeout
timeout = setTimeout($.proxy(function(){
this.rendered = true;
acf.doAction('append', this.$el);
}, this), 50);
// return
return this;
},
save: function( event ) {
var data = {};
if ( event ) {
event.preventDefault();
}
//_.each( this.$el.serializeArray(), function( pair ) {
// data[ pair.name ] = pair.value;
//});
// Serialize data more thoroughly to allow chckbox inputs to save.
data = acf.serializeForAjax(this.$el);
this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
}
});
},
customizeAttachmentLibrary: function(){
// validate
if( !acf.isset(wp, 'media', 'view', 'Attachment', 'Library') ) {
return;
}
// vars
var AttachmentLibrary = wp.media.view.Attachment.Library;
// extend
wp.media.view.Attachment.Library = AttachmentLibrary.extend({
render: function() {
// vars
var popup = acf.isget(this, 'controller', 'acf');
var attributes = acf.isget(this, 'model', 'attributes');
// check vars exist to avoid errors
if( popup && attributes ) {
// show errors
if( attributes.acf_errors ) {
this.$el.addClass('acf-disabled');
}
// disable selected
var selected = popup.get('selected');
if( selected && selected.indexOf(attributes.id) > -1 ) {
this.$el.addClass('acf-selected');
}
}
// render
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 = this.controller;
var errors = acf.isget(this, 'model', 'attributes', 'acf_errors');
var $sidebar = frame.$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.isget(this, 'model', 'attributes', 'filename');
// hide attachment details
// Gallery field continues to show previously selected attachment...
$sidebar.children().addClass('acf-hidden');
// append message
$sidebar.prepend([
'<div class="acf-selection-error">',
'<span class="selection-error-label">' + acf.__('Restricted') +'</span>',
'<span class="selection-error-filename">' + filename + '</span>',
'<span class="selection-error-message">' + errors + '</span>',
'</div>'
].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
return AttachmentLibrary.prototype.toggleSelection.apply( this, arguments );
}
});
}
});
})(jQuery);
(function($, undefined){
acf.screen = new acf.Model({
active: true,
xhr: false,
timeout: false,
wait: 'load',
events: {
'change #page_template': 'onChange',
'change #parent_id': 'onChange',
'change #post-formats-select': 'onChange',
'change .categorychecklist': 'onChange',
'change .tagsdiv': 'onChange',
'change .acf-taxonomy-field[data-save="1"]': 'onChange',
'change #product-type': 'onChange'
},
isPost: function(){
return acf.get('screen') === 'post';
},
isUser: function(){
return acf.get('screen') === 'user';
},
isTaxonomy: function(){
return acf.get('screen') === 'taxonomy';
},
isAttachment: function(){
return acf.get('screen') === 'attachment';
},
isNavMenu: function(){
return acf.get('screen') === 'nav_menu';
},
isWidget: function(){
return acf.get('screen') === 'widget';
},
isComment: function(){
return acf.get('screen') === 'comment';
},
getPageTemplate: function(){
var $el = $('#page_template');
return $el.length ? $el.val() : null;
},
getPageParent: function( e, $el ){
var $el = $('#parent_id');
return $el.length ? $el.val() : null;
},
getPageType: function( e, $el ){
return this.getPageParent() ? 'child' : 'parent';
},
getPostType: function(){
return $('#post_type').val();
},
getPostFormat: function( e, $el ){
var $el = $('#post-formats-select input:checked');
if( $el.length ) {
var val = $el.val();
return (val == '0') ? 'standard' : val;
}
return null;
},
getPostCoreTerms: function(){
// vars
var terms = {};
// serialize WP taxonomy postboxes
var data = acf.serialize( $('.categorydiv, .tagsdiv') );
// use tax_input (tag, custom-taxonomy) when possible.
// this data is already formatted in taxonomy => [terms].
if( data.tax_input ) {
terms = data.tax_input;
}
// append "category" which uses a different name
if( data.post_category ) {
terms.category = data.post_category;
}
// convert any string values (tags) into array format
for( var tax in terms ) {
if( !acf.isArray(terms[tax]) ) {
terms[tax] = terms[tax].split(/,[\s]?/);
}
}
// return
return terms;
},
getPostTerms: function(){
// Get core terms.
var terms = this.getPostCoreTerms();
// loop over taxonomy fields and add their values
acf.getFields({type: 'taxonomy'}).map(function( field ){
// ignore fields that don't save
if( !field.get('save') ) {
return;
}
// vars
var val = field.val();
var tax = field.get('taxonomy');
// check val
if( val ) {
// ensure terms exists
terms[ tax ] = terms[ tax ] || [];
// ensure val is an array
val = acf.isArray(val) ? val : [val];
// append
terms[ tax ] = terms[ tax ].concat( val );
}
});
// add WC product type
if( (productType = this.getProductType()) !== null ) {
terms.product_type = [productType];
}
// remove duplicate values
for( var tax in terms ) {
terms[tax] = acf.uniqueArray(terms[tax]);
}
// return
return terms;
},
getProductType: function(){
var $el = $('#product-type');
return $el.length ? $el.val() : null;
},
check: function(){
// bail early if not for post
if( acf.get('screen') !== 'post' ) {
return;
}
// abort XHR if is already loading AJAX data
if( this.xhr ) {
this.xhr.abort();
}
// vars
var ajaxData = acf.parseArgs(this.data, {
action: 'acf/ajax/check_screen',
screen: acf.get('screen'),
exists: []
});
// post id
if( this.isPost() ) {
ajaxData.post_id = acf.get('post_id');
}
// post type
if( (postType = this.getPostType()) !== null ) {
ajaxData.post_type = postType;
}
// page template
if( (pageTemplate = this.getPageTemplate()) !== null ) {
ajaxData.page_template = pageTemplate;
}
// page parent
if( (pageParent = this.getPageParent()) !== null ) {
ajaxData.page_parent = pageParent;
}
// page type
if( (pageType = this.getPageType()) !== null ) {
ajaxData.page_type = pageType;
}
// post format
if( (postFormat = this.getPostFormat()) !== null ) {
ajaxData.post_format = postFormat;
}
// post terms
if( (postTerms = this.getPostTerms()) !== null ) {
ajaxData.post_terms = postTerms;
}
// add array of existing postboxes to increase performance and reduce JSON HTML
acf.getPostboxes().map(function( postbox ){
ajaxData.exists.push( postbox.get('key') );
});
// filter
ajaxData = acf.applyFilters('check_screen_args', ajaxData);
// success
var onSuccess = function( json ){
// Check success.
if( acf.isAjaxSuccess(json) ) {
// Render post screen.
if( acf.get('screen') == 'post' ) {
this.renderPostScreen( json.data );
// Render user screen.
} else if( acf.get('screen') == 'user' ) {
this.renderUserScreen( json.data );
}
}
// action
acf.doAction('check_screen_complete', json.data, ajaxData);
};
// ajax
this.xhr = $.ajax({
url: acf.get('ajaxurl'),
data: acf.prepareForAjax( ajaxData ),
type: 'post',
dataType: 'json',
context: this,
success: onSuccess
});
},
onChange: function( e, $el ){
this.setTimeout(this.check, 1);
},
renderPostScreen: function( data ){
// Helper function to copy events
var copyEvents = function( $from, $to ){
var events = $._data($from[0]).events;
for( var type in events ) {
for( var i = 0; i < events[type].length; i++ ) {
$to.on( type, events[type][i].handler );
}
}
}
// Helper function to sort metabox.
var sortMetabox = function( id, ids ){
// Find position of id within ids.
var index = ids.indexOf( id );
// Bail early if index not found.
if( index == -1 ) {
return false;
}
// Loop over metaboxes behind (in reverse order).
for( var i = index-1; i >= 0; i-- ) {
if( $('#'+ids[i]).length ) {
return $('#'+ids[i]).after( $('#'+id) );
}
}
// Loop over metaboxes infront.
for( var i = index+1; i < ids.length; i++ ) {
if( $('#'+ids[i]).length ) {
return $('#'+ids[i]).before( $('#'+id) );
}
}
// Return false if not sorted.
return false;
};
// Keep track of visible and hidden postboxes.
data.visible = [];
data.hidden = [];
// Show these postboxes.
data.results = data.results.map(function( result, i ){
// vars
var postbox = acf.getPostbox( result.id );
// Prevent "acf_after_title" position in Block Editor.
if( acf.isGutenberg() && result.position == "acf_after_title" ) {
result.position = 'normal';
}
// Create postbox if doesn't exist.
if( !postbox ) {
// Create it.
var $postbox = $([
'<div id="' + result.id + '" class="postbox">',
'<button type="button" class="handlediv" aria-expanded="false">',
'<span class="screen-reader-text">Toggle panel: ' + result.title + '</span>',
'<span class="toggle-indicator" aria-hidden="true"></span>',
'</button>',
'<h2 class="hndle ui-sortable-handle">',
'<span>' + result.title + '</span>',
'</h2>',
'<div class="inside">',
result.html,
'</div>',
'</div>'
].join(''));
// Create new hide toggle.
if( $('#adv-settings').length ) {
var $prefs = $('#adv-settings .metabox-prefs');
var $label = $([
'<label for="' + result.id + '-hide">',
'<input class="hide-postbox-tog" name="' + result.id + '-hide" type="checkbox" id="' + result.id + '-hide" value="' + result.id + '" checked="checked">',
' ' + result.title,
'</label>'
].join(''));
// Copy default WP events onto checkbox.
copyEvents( $prefs.find('input').first(), $label.find('input') );
// Append hide label
$prefs.append( $label );
}
// Copy default WP events onto metabox.
if( $('.postbox').length ) {
copyEvents( $('.postbox .handlediv').first(), $postbox.children('.handlediv') );
copyEvents( $('.postbox .hndle').first(), $postbox.children('.hndle') );
}
// Append metabox to the bottom of "side-sortables".
if( result.position === 'side' ) {
$('#' + result.position + '-sortables').append( $postbox );
// Prepend metabox to the top of "normal-sortbables".
} else {
$('#' + result.position + '-sortables').prepend( $postbox );
}
// Position metabox amongst existing ACF metaboxes within the same location.
var order = [];
data.results.map(function( _result ){
if( result.position === _result.position && $('#' + result.position + '-sortables #' + _result.id).length ) {
order.push( _result.id );
}
});
sortMetabox(result.id, order)
// Check 'sorted' for user preference.
if( data.sorted ) {
// Loop over each position (acf_after_title, side, normal).
for( var position in data.sorted ) {
// Explode string into array of ids.
var order = data.sorted[position].split(',');
// Position metabox relative to order.
if( sortMetabox(result.id, order) ) {
break;
}
}
}
// Initalize it (modifies HTML).
postbox = acf.newPostbox( result );
// Trigger action.
acf.doAction('append', $postbox);
acf.doAction('append_postbox', postbox);
}
// show postbox
postbox.showEnable();
// append
data.visible.push( result.id );
// Return result (may have changed).
return result;
});
// Hide these postboxes.
acf.getPostboxes().map(function( postbox ){
if( data.visible.indexOf( postbox.get('id') ) === -1 ) {
// Hide postbox.
postbox.hideDisable();
// Append to data.
data.hidden.push( postbox.get('id') );
}
});
// Update style.
$('#acf-style').html( data.style );
// Do action.
acf.doAction( 'refresh_post_screen', data );
},
renderUserScreen: function( json ){
}
});
/**
* gutenScreen
*
* Adds compatibility with the Gutenberg edit screen.
*
* @date 11/12/18
* @since 5.8.0
*
* @param void
* @return void
*/
var gutenScreen = new acf.Model({
// Keep a reference to the most recent post attributes.
postEdits: {},
// Wait until load to avoid 'core' issues when loading taxonomies.
wait: 'load',
initialize: function(){
// Bail early if not Gutenberg.
if( !acf.isGutenberg() ) {
return;
}
// Listen for changes (use debounced version as this can fires often).
wp.data.subscribe( acf.debounce(this.onChange).bind(this) );
// Customize "acf.screen.get" functions.
acf.screen.getPageTemplate = this.getPageTemplate;
acf.screen.getPageParent = this.getPageParent;
acf.screen.getPostType = this.getPostType;
acf.screen.getPostFormat = this.getPostFormat;
acf.screen.getPostCoreTerms = this.getPostCoreTerms;
// Disable unload
acf.unload.disable();
// Refresh metaboxes since WP 5.3.
var wpMinorVersion = parseFloat( acf.get('wp_version') );
if( wpMinorVersion >= 5.3 ) {
this.addAction( 'refresh_post_screen', this.onRefreshPostScreen );
}
},
onChange: function(){
// Determine attributes that can trigger a refresh.
var attributes = [ 'template', 'parent', 'format' ];
// Append taxonomy attribute names to this list.
( wp.data.select( 'core' ).getTaxonomies() || [] ).map(function( taxonomy ){
attributes.push( taxonomy.rest_base );
});
// Get relevant current post edits.
var _postEdits = wp.data.select( 'core/editor' ).getPostEdits();
var postEdits = {};
attributes.map(function( k ){
if( _postEdits[k] !== undefined ) {
postEdits[k] = _postEdits[k];
}
});
// Detect change.
if( JSON.stringify(postEdits) !== JSON.stringify(this.postEdits) ) {
this.postEdits = postEdits;
// Check screen.
acf.screen.check();
}
},
getPageTemplate: function(){
return wp.data.select( 'core/editor' ).getEditedPostAttribute( 'template' );
},
getPageParent: function( e, $el ){
return wp.data.select( 'core/editor' ).getEditedPostAttribute( 'parent' );
},
getPostType: function(){
return wp.data.select( 'core/editor' ).getEditedPostAttribute( 'type' );
},
getPostFormat: function( e, $el ){
return wp.data.select( 'core/editor' ).getEditedPostAttribute( 'format' );
},
getPostCoreTerms: function(){
// vars
var terms = {};
// Loop over taxonomies.
var taxonomies = wp.data.select( 'core' ).getTaxonomies() || [];
taxonomies.map(function( taxonomy ){
// Append selected taxonomies to terms object.
var postTerms = wp.data.select( 'core/editor' ).getEditedPostAttribute( taxonomy.rest_base );
if( postTerms ) {
terms[ taxonomy.slug ] = postTerms;
}
});
// return
return terms;
},
/**
* onRefreshPostScreen
*
* Fires after the Post edit screen metaboxs are refreshed to update the Block Editor API state.
*
* @date 11/11/19
* @since 5.8.7
*
* @param object data The "check_screen" JSON response data.
* @return void
*/
onRefreshPostScreen: function( data ) {
// Extract vars.
var select = wp.data.select( 'core/edit-post' );
var dispatch = wp.data.dispatch( 'core/edit-post' );
// Load current metabox locations and data.
var locations = {};
select.getActiveMetaBoxLocations().map(function( location ){
locations[ location ] = select.getMetaBoxesPerLocation( location );
});
// Generate flat array of existing ids.
var ids = [];
for( var k in locations ) {
locations[k].map(function( m ){
ids.push( m.id );
});
}
// Append new ACF metaboxes (ignore those which already exist).
data.results.filter(function( r ){
return ( ids.indexOf( r.id ) === -1 );
}).map(function( result, i ){
// Ensure location exists.
var location = result.position;
locations[ location ] = locations[ location ] || [];
// Append.
locations[ location ].push({
id: result.id,
title: result.title
});
});
// Remove hidden ACF metaboxes.
for( var k in locations ) {
locations[k] = locations[k].filter(function( m ){
return ( data.hidden.indexOf( m.id ) === -1 );
});
}
// Update state.
dispatch.setAvailableMetaBoxesPerLocation( locations );
}
});
})(jQuery);
(function($, undefined){
/**
* acf.newSelect2
*
* description
*
* @date 13/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.newSelect2 = function( $select, props ){
// defaults
props = acf.parseArgs(props, {
allowNull: false,
placeholder: '',
multiple: false,
field: false,
ajax: false,
ajaxAction: '',
ajaxData: function( data ){ return data; },
ajaxResults: function( json ){ return json; },
});
// initialize
if( getVersion() == 4 ) {
var select2 = new Select2_4( $select, props );
} else {
var select2 = new Select2_3( $select, props );
}
// actions
acf.doAction('new_select2', select2);
// return
return select2;
};
/**
* getVersion
*
* description
*
* @date 13/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
function getVersion() {
// v4
if( acf.isset(window, 'jQuery', 'fn', 'select2', 'amd') ) {
return 4;
}
// v3
if( acf.isset(window, 'Select2') ) {
return 3;
}
// return
return false;
}
/**
* Select2
*
* description
*
* @date 13/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
var Select2 = acf.Model.extend({
setup: function( $select, props ){
$.extend(this.data, props);
this.$el = $select;
},
initialize: function(){
},
selectOption: function( value ){
var $option = this.getOption( value );
if( !$option.prop('selected') ) {
$option.prop('selected', true).trigger('change');
}
},
unselectOption: function( value ){
var $option = this.getOption( value );
if( $option.prop('selected') ) {
$option.prop('selected', false).trigger('change');
}
},
getOption: function( value ){
return this.$('option[value="' + value + '"]');
},
addOption: function( option ){
// defaults
option = acf.parseArgs(option, {
id: '',
text: '',
selected: false
});
// vars
var $option = this.getOption( option.id );
// append
if( !$option.length ) {
$option = $('<option></option>');
$option.html( option.text );
$option.attr('value', option.id);
$option.prop('selected', option.selected);
this.$el.append($option);
}
// chain
return $option;
},
getValue: function(){
// vars
var val = [];
var $options = this.$el.find('option:selected');
// bail early if no selected
if( !$options.exists() ) {
return val;
}
// sort by attribute
$options = $options.sort(function(a, b) {
return +a.getAttribute('data-i') - +b.getAttribute('data-i');
});
// loop
$options.each(function(){
var $el = $(this);
val.push({
$el: $el,
id: $el.attr('value'),
text: $el.text(),
});
});
// return
return val;
},
mergeOptions: function(){
},
getChoices: function(){
// callback
var crawl = function( $parent ){
// vars
var choices = [];
// loop
$parent.children().each(function(){
// vars
var $child = $(this);
// optgroup
if( $child.is('optgroup') ) {
choices.push({
text: $child.attr('label'),
children: crawl( $child )
});
// option
} else {
choices.push({
id: $child.attr('value'),
text: $child.text()
});
}
});
// return
return choices;
};
// crawl
return crawl( this.$el );
},
decodeChoices: function( choices ){
// callback
var crawl = function( items ){
items.map(function( item ){
item.text = acf.decode( item.text );
if( item.children ) {
item.children = crawl( item.children );
}
return item;
});
return items;
};
// crawl
return crawl( choices );
},
getAjaxData: function( params ){
// vars
var ajaxData = {
action: this.get('ajaxAction'),
s: params.term || '',
paged: params.page || 1
};
// field helper
var field = this.get('field');
if( field ) {
ajaxData.field_key = field.get('key');
}
// callback
var callback = this.get('ajaxData');
if( callback ) {
ajaxData = callback.apply( this, [ajaxData, params] );
}
// filter
ajaxData = acf.applyFilters( 'select2_ajax_data', ajaxData, this.data, this.$el, (field || false), this );
// return
return acf.prepareForAjax(ajaxData);
},
getAjaxResults: function( json, params ){
// defaults
json = acf.parseArgs(json, {
results: false,
more: false,
});
// decode
if( json.results ) {
json.results = this.decodeChoices(json.results);
}
// callback
var callback = this.get('ajaxResults');
if( callback ) {
json = callback.apply( this, [json, params] );
}
// filter
json = acf.applyFilters( 'select2_ajax_results', json, params, this );
// return
return json;
},
processAjaxResults: function( json, params ){
// vars
var json = this.getAjaxResults( json, params );
// change more to pagination
if( json.more ) {
json.pagination = { more: true };
}
// merge together groups
setTimeout($.proxy(this.mergeOptions, this), 1);
// return
return json;
},
destroy: function(){
// destroy via api
if( this.$el.data('select2') ) {
this.$el.select2('destroy');
}
// destory via HTML (duplicating HTML does not contain data)
this.$el.siblings('.select2-container').remove();
}
});
/**
* Select2_4
*
* description
*
* @date 13/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
var Select2_4 = Select2.extend({
initialize: function(){
// vars
var $select = this.$el;
var options = {
width: '100%',
allowClear: this.get('allowNull'),
placeholder: this.get('placeholder'),
multiple: this.get('multiple'),
data: [],
escapeMarkup: function( m ){ return m; }
};
// multiple
if( options.multiple ) {
// reorder options
this.getValue().map(function( item ){
item.$el.detach().appendTo( $select );
});
}
// remove conflicting atts
$select.removeData('ajax');
$select.removeAttr('data-ajax');
// ajax
if( this.get('ajax') ) {
options.ajax = {
url: acf.get('ajaxurl'),
delay: 250,
dataType: 'json',
type: 'post',
cache: false,
data: $.proxy(this.getAjaxData, this),
processResults: $.proxy(this.processAjaxResults, this),
};
}
// filter for 3rd party customization
//options = acf.applyFilters( 'select2_args', options, $select, this );
var field = this.get('field');
options = acf.applyFilters( 'select2_args', options, $select, this.data, (field || false), this );
// add select2
$select.select2( options );
// get container (Select2 v4 does not return this from constructor)
var $container = $select.next('.select2-container');
// multiple
if( options.multiple ) {
// vars
var $ul = $container.find('ul');
// sortable
$ul.sortable({
stop: function( e ) {
// loop
$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)
$select.trigger('change');
}
});
// on select, move to end
$select.on('select2:select', this.proxy(function( e ){
this.getOption( e.params.data.id ).detach().appendTo( this.$el );
}));
}
// add class
$container.addClass('-acf');
// action for 3rd party customization
acf.doAction('select2_init', $select, options, this.data, (field || false), this);
},
mergeOptions: function(){
// vars
var $prevOptions = false;
var $prevGroup = false;
// loop
$('.select2-results__option[role="group"]').each(function(){
// vars
var $options = $(this).children('ul');
var $group = $(this).children('strong');
// compare to previous
if( $prevGroup && $prevGroup.text() === $group.text() ) {
$prevOptions.append( $options.children() );
$(this).remove();
return;
}
// update vars
$prevOptions = $options;
$prevGroup = $group;
});
},
});
/**
* Select2_3
*
* description
*
* @date 13/1/18
* @since 5.6.5
*
* @param type $var Description. Default.
* @return type Description.
*/
var Select2_3 = Select2.extend({
initialize: function(){
// vars
var $select = this.$el;
var value = this.getValue();
var multiple = this.get('multiple');
var options = {
width: '100%',
allowClear: this.get('allowNull'),
placeholder: this.get('placeholder'),
separator: '||',
multiple: this.get('multiple'),
data: this.getChoices(),
escapeMarkup: function( m ){ return m; },
dropdownCss: {
'z-index': '999999999'
},
initSelection: function( element, callback ) {
if( multiple ) {
callback( value );
} else {
callback( value.shift() );
}
}
};
// get hidden input
var $input = $select.siblings('input');
if( !$input.length ) {
$input = $('<input type="hidden" />');
$select.before( $input );
}
// set input value
inputValue = value.map(function(item){ return item.id }).join('||');
$input.val( inputValue );
// multiple
if( options.multiple ) {
// reorder options
value.map(function( item ){
item.$el.detach().appendTo( $select );
});
}
// remove blank option as we have a clear all button
if( options.allowClear ) {
options.data = options.data.filter(function(item){
return item.id !== '';
});
}
// remove conflicting atts
$select.removeData('ajax');
$select.removeAttr('data-ajax');
// ajax
if( this.get('ajax') ) {
options.ajax = {
url: acf.get('ajaxurl'),
quietMillis: 250,
dataType: 'json',
type: 'post',
cache: false,
data: $.proxy(this.getAjaxData, this),
results: $.proxy(this.processAjaxResults, this),
};
}
// filter for 3rd party customization
var field = this.get('field');
options = acf.applyFilters( 'select2_args', options, $select, this.data, (field || false), this );
// add select2
$input.select2( options );
// get container
var $container = $input.select2('container');
// helper to find this select's option
var getOption = $.proxy(this.getOption, this);
// multiple
if( options.multiple ) {
// vars
var $ul = $container.find('ul');
// sortable
$ul.sortable({
stop: function() {
// loop
$ul.find('.select2-search-choice').each(function() {
// vars
var data = $(this).data('select2Data');
var $option = getOption( data.id );
// detach and re-append to end
$option.detach().appendTo( $select );
});
// trigger change on input (JS error if trigger on select)
$select.trigger('change');
}
});
}
// on select, create option and move to end
$input.on('select2-selecting', function( e ){
// vars
var item = e.choice;
var $option = getOption( item.id );
// create if doesn't exist
if( !$option.length ) {
$option = $('<option value="' + item.id + '">' + item.text + '</option>');
}
// detach and re-append to end
$option.detach().appendTo( $select );
});
// add class
$container.addClass('-acf');
// action for 3rd party customization
acf.doAction('select2_init', $select, options, this.data, (field || false), this);
// change
$input.on('change', function(){
var val = $input.val();
if( val.indexOf('||') ) {
val = val.split('||');
}
$select.val( val ).trigger('change');
});
// hide select
$select.hide();
},
mergeOptions: function(){
// vars
var $prevOptions = false;
var $prevGroup = false;
// loop
$('#select2-drop .select2-result-with-children').each(function(){
// vars
var $options = $(this).children('ul');
var $group = $(this).children('.select2-result-label');
// compare to previous
if( $prevGroup && $prevGroup.text() === $group.text() ) {
$prevGroup.append( $options.children() );
$(this).remove();
return;
}
// update vars
$prevOptions = $options;
$prevGroup = $group;
});
},
getAjaxData: function( term, page ){
// create Select2 v4 params
var params = {
term: term,
page: page
}
// return
return Select2.prototype.getAjaxData.apply(this, [params]);
},
});
// manager
var select2Manager = new acf.Model({
priority: 5,
wait: 'prepare',
actions: {
'duplicate': 'onDuplicate'
},
initialize: function(){
// vars
var locale = acf.get('locale');
var rtl = acf.get('rtl');
var l10n = acf.get('select2L10n');
var version = getVersion();
// bail ealry if no l10n
if( !l10n ) {
return false;
}
// bail early if 'en'
if( locale.indexOf('en') === 0 ) {
return false;
}
// initialize
if( version == 4 ) {
this.addTranslations4();
} else if( version == 3 ) {
this.addTranslations3();
}
},
addTranslations4: function(){
// vars
var l10n = acf.get('select2L10n');
var locale = acf.get('locale');
// modify local to match html[lang] attribute (used by Select2)
locale = locale.replace('_', '-');
// select2L10n
var select2L10n = {
errorLoading: function () {
return l10n.load_fail;
},
inputTooLong: function (args) {
var overChars = args.input.length - args.maximum;
if( overChars > 1 ) {
return l10n.input_too_long_n.replace( '%d', overChars );
}
return l10n.input_too_long_1;
},
inputTooShort: function( args ){
var remainingChars = args.minimum - args.input.length;
if( remainingChars > 1 ) {
return l10n.input_too_short_n.replace( '%d', remainingChars );
}
return l10n.input_too_short_1;
},
loadingMore: function () {
return l10n.load_more;
},
maximumSelected: function( args ) {
var maximum = args.maximum;
if( maximum > 1 ) {
return l10n.selection_too_long_n.replace( '%d', maximum );
}
return l10n.selection_too_long_1;
},
noResults: function () {
return l10n.matches_0;
},
searching: function () {
return l10n.searching;
}
};
// append
jQuery.fn.select2.amd.define('select2/i18n/' + locale, [], function(){
return select2L10n;
});
},
addTranslations3: function(){
// vars
var l10n = acf.get('select2L10n');
var locale = acf.get('locale');
// modify local to match html[lang] attribute (used by Select2)
locale = locale.replace('_', '-');
// select2L10n
var select2L10n = {
formatMatches: function( matches ) {
if( matches > 1 ) {
return l10n.matches_n.replace( '%d', matches );
}
return l10n.matches_1;
},
formatNoMatches: function() {
return l10n.matches_0;
},
formatAjaxError: function() {
return l10n.load_fail;
},
formatInputTooShort: function( input, min ) {
var remainingChars = min - input.length;
if( remainingChars > 1 ) {
return l10n.input_too_short_n.replace( '%d', remainingChars );
}
return l10n.input_too_short_1;
},
formatInputTooLong: function( input, max ) {
var overChars = input.length - max;
if( overChars > 1 ) {
return l10n.input_too_long_n.replace( '%d', overChars );
}
return l10n.input_too_long_1;
},
formatSelectionTooBig: function( maximum ) {
if( maximum > 1 ) {
return l10n.selection_too_long_n.replace( '%d', maximum );
}
return l10n.selection_too_long_1;
},
formatLoadMore: function() {
return l10n.load_more;
},
formatSearching: function() {
return l10n.searching;
}
};
// ensure locales exists
$.fn.select2.locales = $.fn.select2.locales || {};
// append
$.fn.select2.locales[ locale ] = select2L10n;
$.extend($.fn.select2.defaults, select2L10n);
},
onDuplicate: function( $el, $el2 ){
$el2.find('.select2-container').remove();
}
});
})(jQuery);
(function($, undefined){
acf.tinymce = {
/*
* defaults
*
* This function will return default mce and qt settings
*
* @type function
* @date 18/8/17
* @since 5.6.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
defaults: function(){
// bail early if no tinyMCEPreInit
if( typeof tinyMCEPreInit === 'undefined' ) return false;
// vars
var defaults = {
tinymce: tinyMCEPreInit.mceInit.acf_content,
quicktags: tinyMCEPreInit.qtInit.acf_content
};
// return
return defaults;
},
/*
* initialize
*
* This function will initialize the tinymce and quicktags instances
*
* @type function
* @date 18/8/17
* @since 5.6.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
initialize: function( id, args ){
// defaults
args = acf.parseArgs(args, {
tinymce: true,
quicktags: true,
toolbar: 'full',
mode: 'visual', // visual,text
field: false
});
// tinymce
if( args.tinymce ) {
this.initializeTinymce( id, args );
}
// quicktags
if( args.quicktags ) {
this.initializeQuicktags( id, args );
}
},
/*
* initializeTinymce
*
* This function will initialize the tinymce instance
*
* @type function
* @date 18/8/17
* @since 5.6.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
initializeTinymce: function( id, args ){
// vars
var $textarea = $('#'+id);
var defaults = this.defaults();
var toolbars = acf.get('toolbars');
var field = args.field || false;
var $field = field.$el || false;
// bail early
if( typeof tinymce === 'undefined' ) return false;
if( !defaults ) return false;
// check if exists
if( tinymce.get(id) ) {
return this.enable( id );
}
// settings
var init = $.extend( {}, defaults.tinymce, args.tinymce );
init.id = id;
init.selector = '#' + id;
// toolbar
var toolbar = args.toolbar;
if( toolbar && toolbars && toolbars[toolbar] ) {
for( var i = 1; i <= 4; i++ ) {
init[ 'toolbar' + i ] = toolbars[toolbar][i] || '';
}
}
// event
init.setup = function( ed ){
ed.on('change', function(e) {
ed.save(); // save to textarea
$textarea.trigger('change');
});
// Fix bug where Gutenberg does not hear "mouseup" event and tries to select multiple blocks.
ed.on('mouseup', function(e) {
var event = new MouseEvent('mouseup');
window.dispatchEvent(event);
});
// Temporarily comment out. May not be necessary due to wysiwyg field actions.
//ed.on('unload', function(e) {
// acf.tinymce.remove( id );
//});
};
// disable wp_autoresize_on (no solution yet for fixed toolbar)
init.wp_autoresize_on = false;
// Enable wpautop allowing value to save without <p> tags.
// Only if the "TinyMCE Advanced" plugin hasn't already set this functionality.
if( !init.tadv_noautop ) {
init.wpautop = true;
}
// hook for 3rd party customization
init = acf.applyFilters('wysiwyg_tinymce_settings', init, id, field);
// z-index fix (caused too many conflicts)
//if( acf.isset(tinymce,'ui','FloatPanel') ) {
// tinymce.ui.FloatPanel.zIndex = 900000;
//}
// store settings
tinyMCEPreInit.mceInit[ id ] = init;
// visual tab is active
if( args.mode == 'visual' ) {
// init
var result = tinymce.init( init );
// get editor
var ed = tinymce.get( id );
// validate
if( !ed ) {
return false;
}
// add reference
ed.acf = args.field;
// action
acf.doAction('wysiwyg_tinymce_init', ed, ed.id, init, field);
}
},
/*
* initializeQuicktags
*
* This function will initialize the quicktags instance
*
* @type function
* @date 18/8/17
* @since 5.6.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
initializeQuicktags: function( id, args ){
// vars
var defaults = this.defaults();
// bail early
if( typeof quicktags === 'undefined' ) return false;
if( !defaults ) return false;
// settings
var init = $.extend( {}, defaults.quicktags, args.quicktags );
init.id = id;
// filter
var field = args.field || false;
var $field = field.$el || false;
init = acf.applyFilters('wysiwyg_quicktags_settings', init, init.id, field);
// store settings
tinyMCEPreInit.qtInit[ id ] = init;
// init
var ed = quicktags( init );
// validate
if( !ed ) {
return false;
}
// generate HTML
this.buildQuicktags( ed );
// action for 3rd party customization
acf.doAction('wysiwyg_quicktags_init', ed, ed.id, init, field);
},
/*
* buildQuicktags
*
* This function will build the quicktags HTML
*
* @type function
* @date 18/8/17
* @since 5.6.0
*
* @param $post_id (int)
* @return $post_id (int)
*/
buildQuicktags: function( ed ){
var canvas, name, settings, theButtons, html, ed, id, i, use, instanceId,
defaults = ',strong,em,link,block,del,ins,img,ul,ol,li,code,more,close,';
canvas = ed.canvas;
name = ed.name;
settings = ed.settings;
html = '';
theButtons = {};
use = '';
instanceId = ed.id;
// set buttons
if ( settings.buttons ) {
use = ','+settings.buttons+',';
}
for ( i in edButtons ) {
if ( ! edButtons[i] ) {
continue;
}
id = edButtons[i].id;
if ( use && defaults.indexOf( ',' + id + ',' ) !== -1 && use.indexOf( ',' + id + ',' ) === -1 ) {
continue;
}
if ( ! edButtons[i].instance || edButtons[i].instance === instanceId ) {
theButtons[id] = edButtons[i];
if ( edButtons[i].html ) {
html += edButtons[i].html( name + '_' );
}
}
}
if ( use && use.indexOf(',dfw,') !== -1 ) {
theButtons.dfw = new QTags.DFWButton();
html += theButtons.dfw.html( name + '_' );
}
if ( 'rtl' === document.getElementsByTagName( 'html' )[0].dir ) {
theButtons.textdirection = new QTags.TextDirectionButton();
html += theButtons.textdirection.html( name + '_' );
}
ed.toolbar.innerHTML = html;
ed.theButtons = theButtons;
if ( typeof jQuery !== 'undefined' ) {
jQuery( document ).triggerHandler( 'quicktags-init', [ ed ] );
}
},
disable: function( id ){
this.destroyTinymce( id );
},
remove: function( id ){
this.destroyTinymce( id );
},
destroy: function( id ){
this.destroyTinymce( id );
},
destroyTinymce: function( id ){
// bail early
if( typeof tinymce === 'undefined' ) return false;
// get editor
var ed = tinymce.get( id );
// bail early if no editor
if( !ed ) return false;
// save
ed.save();
// destroy editor
ed.destroy();
// return
return true;
},
enable: function( id ){
this.enableTinymce( id );
},
enableTinymce: function( id ){
// bail early
if( typeof switchEditors === 'undefined' ) return false;
// bail ealry if not initialized
if( typeof tinyMCEPreInit.mceInit[ id ] === 'undefined' ) return false;
// toggle
switchEditors.go( id, 'tmce');
// return
return true;
}
};
var editorManager = new acf.Model({
// hook in before fieldsEventManager, conditions, etc
priority: 5,
actions: {
'prepare': 'onPrepare',
'ready': 'onReady',
},
onPrepare: function(){
// find hidden editor which may exist within a field
var $div = $('#acf-hidden-wp-editor');
// move to footer
if( $div.exists() ) {
$div.appendTo('body');
}
},
onReady: function(){
// Restore wp.editor functions used by tinymce removed in WP5.
if( acf.isset(window,'wp','oldEditor') ) {
wp.editor.autop = wp.oldEditor.autop;
wp.editor.removep = wp.oldEditor.removep;
}
// bail early if no tinymce
if( !acf.isset(window,'tinymce','on') ) return;
// restore default activeEditor
tinymce.on('AddEditor', function( data ){
// vars
var editor = data.editor;
// bail early if not 'acf'
if( editor.id.substr(0, 3) !== 'acf' ) return;
// override if 'content' exists
editor = tinymce.editors.content || editor;
// update vars
tinymce.activeEditor = editor;
wpActiveEditor = editor.id;
});
}
});
})(jQuery);
(function($, undefined){
/**
* Validator
*
* The model for validating forms
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return void
*/
var Validator = acf.Model.extend({
/** @var string The model identifier. */
id: 'Validator',
/** @var object The model data. */
data: {
/** @var array The form errors. */
errors: [],
/** @var object The form notice. */
notice: null,
/** @var string The form status. loading, invalid, valid */
status: ''
},
/** @var object The model events. */
events: {
'changed:status': 'onChangeStatus'
},
/**
* addErrors
*
* Adds errors to the form.
*
* @date 4/9/18
* @since 5.7.5
*
* @param array errors An array of errors.
* @return void
*/
addErrors: function( errors ){
errors.map( this.addError, this );
},
/**
* addError
*
* Adds and error to the form.
*
* @date 4/9/18
* @since 5.7.5
*
* @param object error An error object containing input and message.
* @return void
*/
addError: function( error ){
this.data.errors.push( error );
},
/**
* hasErrors
*
* Returns true if the form has errors.
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return bool
*/
hasErrors: function(){
return this.data.errors.length;
},
/**
* clearErrors
*
* Removes any errors.
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return void
*/
clearErrors: function(){
return this.data.errors = [];
},
/**
* getErrors
*
* Returns the forms errors.
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return array
*/
getErrors: function(){
return this.data.errors;
},
/**
* getFieldErrors
*
* Returns the forms field errors.
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return array
*/
getFieldErrors: function(){
// vars
var errors = [];
var inputs = [];
// loop
this.getErrors().map(function(error){
// bail early if global
if( !error.input ) return;
// update if exists
var i = inputs.indexOf(error.input);
if( i > -1 ) {
errors[ i ] = error;
// update
} else {
errors.push( error );
inputs.push( error.input );
}
});
// return
return errors;
},
/**
* getGlobalErrors
*
* Returns the forms global errors (errors without a specific input).
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return array
*/
getGlobalErrors: function(){
// return array of errors that contain no input
return this.getErrors().filter(function(error){
return !error.input;
});
},
/**
* showErrors
*
* Displays all errors for this form.
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return void
*/
showErrors: function(){
// bail early if no errors
if( !this.hasErrors() ) {
return;
}
// vars
var fieldErrors = this.getFieldErrors();
var globalErrors = this.getGlobalErrors();
// vars
var errorCount = 0;
var $scrollTo = false;
// loop
fieldErrors.map(function( error ){
// get input
var $input = this.$('[name="' + error.input + '"]').first();
// if $_POST value was an array, this $input may not exist
if( !$input.length ) {
$input = this.$('[name^="' + error.input + '"]').first();
}
// bail early if input doesn't exist
if( !$input.length ) {
return;
}
// increase
errorCount++;
// get field
var field = acf.getClosestField( $input );
// show error
field.showError( error.message );
// set $scrollTo
if( !$scrollTo ) {
$scrollTo = field.$el;
}
}, this);
// errorMessage
var errorMessage = acf.__('Validation failed');
globalErrors.map(function( error ){
errorMessage += '. ' + error.message;
});
if( errorCount == 1 ) {
errorMessage += '. ' + acf.__('1 field requires attention');
} else if( errorCount > 1 ) {
errorMessage += '. ' + acf.__('%d fields require attention').replace('%d', errorCount);
}
// notice
if( this.has('notice') ) {
this.get('notice').update({
type: 'error',
text: errorMessage
});
} else {
var notice = acf.newNotice({
type: 'error',
text: errorMessage,
target: this.$el
});
this.set('notice', notice);
}
// if no $scrollTo, set to message
if( !$scrollTo ) {
$scrollTo = this.get('notice').$el;
}
// timeout
setTimeout(function(){
$("html, body").animate({ scrollTop: $scrollTo.offset().top - ( $(window).height() / 2 ) }, 500);
}, 10);
},
/**
* onChangeStatus
*
* Update the form class when changing the 'status' data
*
* @date 4/9/18
* @since 5.7.5
*
* @param object e The event object.
* @param jQuery $el The form element.
* @param string value The new status.
* @param string prevValue The old status.
* @return void
*/
onChangeStatus: function( e, $el, value, prevValue ){
this.$el.removeClass('is-'+prevValue).addClass('is-'+value);
},
/**
* validate
*
* Vaildates the form via AJAX.
*
* @date 4/9/18
* @since 5.7.5
*
* @param object args A list of settings to customize the validation process.
* @return bool True if the form is valid.
*/
validate: function( args ){
// default args
args = acf.parseArgs(args, {
// trigger event
event: false,
// reset the form after submit
reset: false,
// loading callback
loading: function(){},
// complete callback
complete: function(){},
// failure callback
failure: function(){},
// success callback
success: function( $form ){
$form.submit();
}
});
// return true if is valid - allows form submit
if( this.get('status') == 'valid' ) {
return true;
}
// return false if is currently validating - prevents form submit
if( this.get('status') == 'validating' ) {
return false;
}
// return true if no ACF fields exist (no need to validate)
if( !this.$('.acf-field').length ) {
return true;
}
// if event is provided, create a new success callback.
if( args.event ) {
var event = $.Event(null, args.event);
args.success = function(){
acf.enableSubmit( $(event.target) ).trigger( event );
}
}
// action for 3rd party
acf.doAction('validation_begin', this.$el);
// lock form
acf.lockForm( this.$el );
// loading callback
args.loading( this.$el, this );
// update status
this.set('status', 'validating');
// success callback
var onSuccess = function( json ){
// validate
if( !acf.isAjaxSuccess(json) ) {
return;
}
// filter
var data = acf.applyFilters('validation_complete', json.data, this.$el, this);
// add errors
if( !data.valid ) {
this.addErrors( data.errors );
}
};
// complete
var onComplete = function(){
// unlock form
acf.unlockForm( this.$el );
// failure
if( this.hasErrors() ) {
// update status
this.set('status', 'invalid');
// action
acf.doAction('validation_failure', this.$el, this);
// display errors
this.showErrors();
// failure callback
args.failure( this.$el, this );
// success
} else {
// update status
this.set('status', 'valid');
// remove previous error message
if( this.has('notice') ) {
this.get('notice').update({
type: 'success',
text: acf.__('Validation successful'),
timeout: 1000
});
}
// action
acf.doAction('validation_success', this.$el, this);
acf.doAction('submit', this.$el);
// success callback (submit form)
args.success( this.$el, this );
// lock form
acf.lockForm( this.$el );
// reset
if( args.reset ) {
this.reset();
}
}
// complete callback
args.complete( this.$el, this );
// clear errors
this.clearErrors();
};
// serialize form data
var data = acf.serialize( this.$el );
data.action = 'acf/validate_save_post';
// ajax
$.ajax({
url: acf.get('ajaxurl'),
data: acf.prepareForAjax(data),
type: 'post',
dataType: 'json',
context: this,
success: onSuccess,
complete: onComplete
});
// return false to fail validation and allow AJAX
return false
},
/**
* setup
*
* Called during the constructor function to setup this instance
*
* @date 4/9/18
* @since 5.7.5
*
* @param jQuery $form The form element.
* @return void
*/
setup: function( $form ){
// set $el
this.$el = $form;
},
/**
* reset
*
* Rests the validation to be used again.
*
* @date 6/9/18
* @since 5.7.5
*
* @param void
* @return void
*/
reset: function(){
// reset data
this.set('errors', []);
this.set('notice', null);
this.set('status', '');
// unlock form
acf.unlockForm( this.$el );
}
});
/**
* getValidator
*
* Returns the instance for a given form element.
*
* @date 4/9/18
* @since 5.7.5
*
* @param jQuery $el The form element.
* @return object
*/
var getValidator = function( $el ){
// instantiate
var validator = $el.data('acf');
if( !validator ) {
validator = new Validator( $el );
}
// return
return validator;
};
/**
* acf.validateForm
*
* A helper function for the Validator.validate() function.
* Returns true if form is valid, or fetches a validation request and returns false.
*
* @date 4/4/18
* @since 5.6.9
*
* @param object args A list of settings to customize the validation process.
* @return bool
*/
acf.validateForm = function( args ){
return getValidator( args.form ).validate( args );
};
/**
* acf.enableSubmit
*
* Enables a submit button and returns the element.
*
* @date 30/8/18
* @since 5.7.4
*
* @param jQuery $submit The submit button.
* @return jQuery
*/
acf.enableSubmit = function( $submit ){
return $submit.removeClass('disabled');
};
/**
* acf.disableSubmit
*
* Disables a submit button and returns the element.
*
* @date 30/8/18
* @since 5.7.4
*
* @param jQuery $submit The submit button.
* @return jQuery
*/
acf.disableSubmit = function( $submit ){
return $submit.addClass('disabled');
};
/**
* acf.showSpinner
*
* Shows the spinner element.
*
* @date 4/9/18
* @since 5.7.5
*
* @param jQuery $spinner The spinner element.
* @return jQuery
*/
acf.showSpinner = function( $spinner ){
$spinner.addClass('is-active'); // add class (WP > 4.2)
$spinner.css('display', 'inline-block'); // css (WP < 4.2)
return $spinner;
};
/**
* acf.hideSpinner
*
* Hides the spinner element.
*
* @date 4/9/18
* @since 5.7.5
*
* @param jQuery $spinner The spinner element.
* @return jQuery
*/
acf.hideSpinner = function( $spinner ){
$spinner.removeClass('is-active'); // add class (WP > 4.2)
$spinner.css('display', 'none'); // css (WP < 4.2)
return $spinner;
};
/**
* acf.lockForm
*
* Locks a form by disabeling its primary inputs and showing a spinner.
*
* @date 4/9/18
* @since 5.7.5
*
* @param jQuery $form The form element.
* @return jQuery
*/
acf.lockForm = function( $form ){
// vars
var $wrap = findSubmitWrap( $form );
var $submit = $wrap.find('.button, [type="submit"]');
var $spinner = $wrap.find('.spinner, .acf-spinner');
// hide all spinners (hides the preview spinner)
acf.hideSpinner( $spinner );
// lock
acf.disableSubmit( $submit );
acf.showSpinner( $spinner.last() );
return $form;
};
/**
* acf.unlockForm
*
* Unlocks a form by enabeling its primary inputs and hiding all spinners.
*
* @date 4/9/18
* @since 5.7.5
*
* @param jQuery $form The form element.
* @return jQuery
*/
acf.unlockForm = function( $form ){
// vars
var $wrap = findSubmitWrap( $form );
var $submit = $wrap.find('.button, [type="submit"]');
var $spinner = $wrap.find('.spinner, .acf-spinner');
// unlock
acf.enableSubmit( $submit );
acf.hideSpinner( $spinner );
return $form;
};
/**
* findSubmitWrap
*
* An internal function to find the 'primary' form submit wrapping element.
*
* @date 4/9/18
* @since 5.7.5
*
* @param jQuery $form The form element.
* @return jQuery
*/
var findSubmitWrap = function( $form ){
// default post submit div
var $wrap = $form.find('#submitdiv');
if( $wrap.length ) {
return $wrap;
}
// 3rd party publish box
var $wrap = $form.find('#submitpost');
if( $wrap.length ) {
return $wrap;
}
// term, user
var $wrap = $form.find('p.submit').last();
if( $wrap.length ) {
return $wrap;
}
// front end form
var $wrap = $form.find('.acf-form-submit');
if( $wrap.length ) {
return $wrap;
}
// default
return $form;
};
/**
* acf.validation
*
* Global validation logic
*
* @date 4/4/18
* @since 5.6.9
*
* @param void
* @return void
*/
acf.validation = new acf.Model({
/** @var string The model identifier. */
id: 'validation',
/** @var bool The active state. Set to false before 'prepare' to prevent validation. */
active: true,
/** @var string The model initialize time. */
wait: 'prepare',
/** @var object The model actions. */
actions: {
'ready': 'addInputEvents',
'append': 'addInputEvents'
},
/** @var object The model events. */
events: {
'click input[type="submit"]': 'onClickSubmit',
'click button[type="submit"]': 'onClickSubmit',
//'click #editor .editor-post-publish-button': 'onClickSubmitGutenberg',
'click #save-post': 'onClickSave',
'submit form#post': 'onSubmitPost',
'submit form': 'onSubmit',
},
/**
* initialize
*
* Called when initializing the model.
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return void
*/
initialize: function(){
// check 'validation' setting
if( !acf.get('validation') ) {
this.active = false;
this.actions = {};
this.events = {};
}
},
/**
* enable
*
* Enables validation.
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return void
*/
enable: function(){
this.active = true;
},
/**
* disable
*
* Disables validation.
*
* @date 4/9/18
* @since 5.7.5
*
* @param void
* @return void
*/
disable: function(){
this.active = false;
},
/**
* reset
*
* Rests the form validation to be used again
*
* @date 6/9/18
* @since 5.7.5
*
* @param jQuery $form The form element.
* @return void
*/
reset: function( $form ){
getValidator( $form ).reset();
},
/**
* addInputEvents
*
* Adds 'invalid' event listeners to HTML inputs.
*
* @date 4/9/18
* @since 5.7.5
*
* @param jQuery $el The element being added / readied.
* @return void
*/
addInputEvents: function( $el ){
// Bug exists in Safari where custom "invalid" handeling prevents draft from saving.
if( acf.get('browser') === 'safari' )
return;
// vars
var $inputs = $('.acf-field [name]', $el);
// check
if( $inputs.length ) {
this.on( $inputs, 'invalid', 'onInvalid' );
}
},
/**
* onInvalid
*
* Callback for the 'invalid' event.
*
* @date 4/9/18
* @since 5.7.5
*
* @param object e The event object.
* @param jQuery $el The input element.
* @return void
*/
onInvalid: function( e, $el ){
// prevent default
// - prevents browser error message
// - also fixes chrome bug where 'hidden-by-tab' field throws focus error
e.preventDefault();
// vars
var $form = $el.closest('form');
// check form exists
if( $form.length ) {
// add error to validator
getValidator( $form ).addError({
input: $el.attr('name'),
message: e.target.validationMessage
});
// trigger submit on $form
// - allows for "save", "preview" and "publish" to work
$form.submit();
}
},
/**
* onClickSubmit
*
* Callback when clicking submit.
*
* @date 4/9/18
* @since 5.7.5
*
* @param object e The event object.
* @param jQuery $el The input element.
* @return void
*/
onClickSubmit: function( e, $el ){
// store the "click event" for later use in this.onSubmit()
this.set('originalEvent', e);
},
/**
* onClickSave
*
* Set ignore to true when saving a draft.
*
* @date 4/9/18
* @since 5.7.5
*
* @param object e The event object.
* @param jQuery $el The input element.
* @return void
*/
onClickSave: function( e, $el ) {
this.set('ignore', true);
},
/**
* onClickSubmitGutenberg
*
* Custom validation event for the gutenberg editor.
*
* @date 29/10/18
* @since 5.8.0
*
* @param object e The event object.
* @param jQuery $el The input element.
* @return void
*/
onClickSubmitGutenberg: function( e, $el ){
// validate
var valid = acf.validateForm({
form: $('#editor'),
event: e,
reset: true,
failure: function( $form, validator ){
var $notice = validator.get('notice').$el;
$notice.appendTo('.components-notice-list');
$notice.find('.acf-notice-dismiss').removeClass('small');
}
});
// if not valid, stop event and allow validation to continue
if( !valid ) {
e.preventDefault();
e.stopImmediatePropagation();
}
},
/**
* onSubmitPost
*
* Callback when the 'post' form is submit.
*
* @date 5/3/19
* @since 5.7.13
*
* @param object e The event object.
* @param jQuery $el The input element.
* @return void
*/
onSubmitPost: function( e, $el ) {
// Check if is preview.
if( $('input#wp-preview').val() === 'dopreview' ) {
// Ignore validation.
this.set('ignore', true);
// Unlock form to fix conflict with core "submit.edit-post" event causing all submit buttons to be disabled.
acf.unlockForm( $el )
}
},
/**
* onSubmit
*
* Callback when the form is submit.
*
* @date 4/9/18
* @since 5.7.5
*
* @param object e The event object.
* @param jQuery $el The input element.
* @return void
*/
onSubmit: function( e, $el ){
// Allow form to submit if...
if(
// Validation has been disabled.
!this.active
// Or this event is to be ignored.
|| this.get('ignore')
// Or this event has already been prevented.
|| e.isDefaultPrevented()
) {
// Return early and call reset function.
return this.allowSubmit();
}
// Validate form.
var valid = acf.validateForm({
form: $el,
event: this.get('originalEvent')
});
// If not valid, stop event to prevent form submit.
if( !valid ) {
e.preventDefault();
}
},
/**
* allowSubmit
*
* Resets data during onSubmit when the form is allowed to submit.
*
* @date 5/3/19
* @since 5.7.13
*
* @param void
* @return void
*/
allowSubmit: function(){
// Reset "ignore" state.
this.set('ignore', false);
// Reset "originalEvent" object.
this.set('originalEvent', false);
// Return true
return true;
}
});
})(jQuery);
(function($, undefined){
/**
* refreshHelper
*
* description
*
* @date 1/7/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
var refreshHelper = new acf.Model({
priority: 90,
initialize: function(){
this.refresh = acf.debounce( this.refresh, 0 );
},
actions: {
'new_field': 'refresh',
'show_field': 'refresh',
'hide_field': 'refresh',
'remove_field': 'refresh',
'unmount_field': 'refresh',
'remount_field': 'refresh',
},
refresh: function(){
acf.doAction('refresh');
$(window).trigger('acfrefresh');
}
});
/**
* mountHelper
*
* Adds compatiblity for the 'unmount' and 'remount' actions added in 5.8.0
*
* @date 7/3/19
* @since 5.7.14
*
* @param void
* @return void
*/
var mountHelper = new acf.Model({
priority: 1,
actions: {
'sortstart': 'onSortstart',
'sortstop': 'onSortstop'
},
onSortstart: function( $item ){
acf.doAction('unmount', $item);
},
onSortstop: function( $item ){
acf.doAction('remount', $item);
}
});
/**
* sortableHelper
*
* Adds compatibility for sorting a <tr> element
*
* @date 6/3/18
* @since 5.6.9
*
* @param void
* @return void
*/
var sortableHelper = new acf.Model({
actions: {
'sortstart': 'onSortstart'
},
onSortstart: function( $item, $placeholder ){
// if $item is a tr, apply some css to the elements
if( $item.is('tr') ) {
// replace $placeholder children with a single td
// fixes "width calculation issues" due to conditional logic hiding some children
$placeholder.html('<td style="padding:0;" colspan="' + $placeholder.children().length + '"></td>');
// add helper class to remove absolute positioning
$item.addClass('acf-sortable-tr-helper');
// set fixed widths for children
$item.children().each(function(){
$(this).width( $(this).width() );
});
// mimic height
$placeholder.height( $item.height() + 'px' );
// remove class
$item.removeClass('acf-sortable-tr-helper');
}
}
});
/**
* duplicateHelper
*
* Fixes browser bugs when duplicating an element
*
* @date 6/3/18
* @since 5.6.9
*
* @param void
* @return void
*/
var duplicateHelper = new acf.Model({
actions: {
'after_duplicate': 'onAfterDuplicate'
},
onAfterDuplicate: function( $el, $el2 ){
// get original values
var vals = [];
$el.find('select').each(function(i){
vals.push( $(this).val() );
});
// set duplicate values
$el2.find('select').each(function(i){
$(this).val( vals[i] );
});
}
});
/**
* tableHelper
*
* description
*
* @date 6/3/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
var tableHelper = new acf.Model({
id: 'tableHelper',
priority: 20,
actions: {
'refresh': 'renderTables'
},
renderTables: function( $el ){
// loop
var self = this;
$('.acf-table:visible').each(function(){
self.renderTable( $(this) );
});
},
renderTable: function( $table ){
// vars
var $ths = $table.find('> thead > tr:visible > th[data-key]');
var $tds = $table.find('> tbody > tr:visible > td[data-key]');
// bail early if no thead
if( !$ths.length || !$tds.length ) {
return false;
}
// visiblity
$ths.each(function( i ){
// vars
var $th = $(this);
var key = $th.data('key');
var $cells = $tds.filter('[data-key="' + key + '"]');
var $hidden = $cells.filter('.acf-hidden');
// always remove empty and allow cells to be hidden
$cells.removeClass('acf-empty');
// hide $th if all cells are hidden
if( $cells.length === $hidden.length ) {
acf.hide( $th );
// force all hidden cells to appear empty
} else {
acf.show( $th );
$hidden.addClass('acf-empty');
}
});
// clear width
$ths.css('width', 'auto');
// get visible
$ths = $ths.not('.acf-hidden');
// vars
var availableWidth = 100;
var colspan = $ths.length;
// set custom widths first
var $fixedWidths = $ths.filter('[data-width]');
$fixedWidths.each(function(){
var width = $(this).data('width');
$(this).css('width', width + '%');
availableWidth -= width;
});
// set auto widths
var $auoWidths = $ths.not('[data-width]');
if( $auoWidths.length ) {
var width = availableWidth / $auoWidths.length;
$auoWidths.css('width', width + '%');
availableWidth = 0;
}
// avoid stretching issue
if( availableWidth > 0 ) {
$ths.last().css('width', 'auto');
}
// update colspan on collapsed
$tds.filter('.-collapsed-target').each(function(){
// vars
var $td = $(this);
// check if collapsed
if( $td.parent().hasClass('-collapsed') ) {
$td.attr('colspan', $ths.length);
} else {
$td.removeAttr('colspan');
}
});
}
});
/**
* fieldsHelper
*
* description
*
* @date 6/3/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
var fieldsHelper = new acf.Model({
id: 'fieldsHelper',
priority: 30,
actions: {
'refresh': 'renderGroups'
},
renderGroups: function(){
// loop
var self = this;
$('.acf-fields:visible').each(function(){
self.renderGroup( $(this) );
});
},
renderGroup: function( $el ){
// vars
var top = 0;
var height = 0;
var $row = $();
// get fields
var $fields = $el.children('.acf-field[data-width]:visible');
// bail early if no fields
if( !$fields.length ) {
return false;
}
// bail ealry if is .-left
if( $el.hasClass('-left') ) {
$fields.removeAttr('data-width');
$fields.css('width', 'auto');
return false;
}
// reset fields
$fields.removeClass('-r0 -c0').css({'min-height': 0});
// loop
$fields.each(function( i ){
// vars
var $field = $(this);
var position = $field.position();
var thisTop = Math.ceil( position.top );
var thisLeft = Math.ceil( position.left );
// detect change in row
if( $row.length && thisTop > top ) {
// set previous heights
$row.css({'min-height': height+'px'});
// update position due to change in row above
position = $field.position();
thisTop = Math.ceil( position.top );
thisLeft = Math.ceil( position.left );
// reset vars
top = 0;
height = 0;
$row = $();
}
// rtl
if( acf.get('rtl') ) {
thisLeft = Math.ceil( $field.parent().width() - (position.left + $field.outerWidth()) );
}
// add classes
if( thisTop == 0 ) {
$field.addClass('-r0');
} else if( thisLeft == 0 ) {
$field.addClass('-c0');
}
// get height after class change
// - add 1 for subpixel rendering
var thisHeight = Math.ceil( $field.outerHeight() ) + 1;
// set height
height = Math.max( height, thisHeight );
// set y
top = Math.max( top, thisTop );
// append
$row = $row.add( $field );
});
// clean up
if( $row.length ) {
$row.css({'min-height': height+'px'});
}
}
});
})(jQuery);
(function($, undefined){
/**
* acf.newCompatibility
*
* Inserts a new __proto__ object compatibility layer
*
* @date 15/2/18
* @since 5.6.9
*
* @param object instance The object to modify.
* @param object compatibilty Optional. The compatibilty layer.
* @return object compatibilty
*/
acf.newCompatibility = function( instance, compatibilty ){
// defaults
compatibilty = compatibilty || {};
// inherit __proto_-
compatibilty.__proto__ = instance.__proto__;
// inject
instance.__proto__ = compatibilty;
// reference
instance.compatibility = compatibilty;
// return
return compatibilty;
};
/**
* acf.getCompatibility
*
* Returns the compatibility layer for a given instance
*
* @date 13/3/18
* @since 5.6.9
*
* @param object instance The object to look in.
* @return object|null compatibility The compatibility object or null on failure.
*/
acf.getCompatibility = function( instance ) {
return instance.compatibility || null;
};
/**
* acf (compatibility)
*
* Compatibility layer for the acf object
*
* @date 15/2/18
* @since 5.6.9
*
* @param void
* @return void
*/
var _acf = acf.newCompatibility(acf, {
// storage
l10n: {},
o: {},
fields: {},
// changed function names
update: acf.set,
add_action: acf.addAction,
remove_action: acf.removeAction,
do_action: acf.doAction,
add_filter: acf.addFilter,
remove_filter: acf.removeFilter,
apply_filters: acf.applyFilters,
parse_args: acf.parseArgs,
disable_el: acf.disable,
disable_form: acf.disable,
enable_el: acf.enable,
enable_form: acf.enable,
update_user_setting: acf.updateUserSetting,
prepare_for_ajax: acf.prepareForAjax,
is_ajax_success: acf.isAjaxSuccess,
remove_el: acf.remove,
remove_tr: acf.remove,
str_replace: acf.strReplace,
render_select: acf.renderSelect,
get_uniqid: acf.uniqid,
serialize_form: acf.serialize,
esc_html: acf.strEscape,
str_sanitize: acf.strSanitize,
});
_acf._e = function( k1, k2 ){
// defaults
k1 = k1 || '';
k2 = k2 || '';
// compability
var compatKey = k2 ? k1 + '.' + k2 : k1;
var compats = {
'image.select': 'Select Image',
'image.edit': 'Edit Image',
'image.update': 'Update Image'
};
if( compats[compatKey] ) {
return acf.__(compats[compatKey]);
}
// try k1
var string = this.l10n[ k1 ] || '';
// try k2
if( k2 ) {
string = string[ k2 ] || '';
}
// return
return string;
};
_acf.get_selector = function( s ) {
// vars
var selector = '.acf-field';
// bail early if no search
if( !s ) {
return selector;
}
// compatibility with object
if( $.isPlainObject(s) ) {
if( $.isEmptyObject(s) ) {
return selector;
} else {
for( var k in s ) { s = s[k]; break; }
}
}
// append
selector += '-' + s;
// replace underscores (split/join replaces all and is faster than regex!)
selector = acf.strReplace('_', '-', selector);
// remove potential double up
selector = acf.strReplace('field-field-', 'field-', selector);
// return
return selector;
};
_acf.get_fields = function( s, $el, all ){
// args
var args = {
is: s || '',
parent: $el || false,
suppressFilters: all || false,
};
// change 'field_123' to '.acf-field-123'
if( args.is ) {
args.is = this.get_selector( args.is );
}
// return
return acf.findFields(args);
};
_acf.get_field = function( s, $el ){
// get fields
var $fields = this.get_fields.apply(this, arguments);
// return
if( $fields.length ) {
return $fields.first();
} else {
return false;
}
};
_acf.get_closest_field = function( $el, s ){
return $el.closest( this.get_selector(s) );
};
_acf.get_field_wrap = function( $el ){
return $el.closest( this.get_selector() );
};
_acf.get_field_key = function( $field ){
return $field.data('key');
};
_acf.get_field_type = function( $field ){
return $field.data('type');
};
_acf.get_data = function( $el, defaults ){
return acf.parseArgs( $el.data(), defaults );
};
_acf.maybe_get = function( obj, key, value ){
// default
if( value === undefined ) {
value = null;
}
// get keys
keys = String(key).split('.');
// acf.isget
for( var i = 0; i < keys.length; i++ ) {
if( !obj.hasOwnProperty(keys[i]) ) {
return value;
}
obj = obj[ keys[i] ];
}
return obj;
};
/**
* hooks
*
* Modify add_action and add_filter functions to add compatibility with changed $field parameter
* Using the acf.add_action() or acf.add_filter() functions will interpret new field parameters as jQuery $field
*
* @date 12/5/18
* @since 5.6.9
*
* @param void
* @return void
*/
var compatibleArgument = function( arg ){
return ( arg instanceof acf.Field ) ? arg.$el : arg;
};
var compatibleArguments = function( args ){
return acf.arrayArgs( args ).map( compatibleArgument );
}
var compatibleCallback = function( origCallback ){
return function(){
// convert to compatible arguments
if( arguments.length ) {
var args = compatibleArguments(arguments);
// add default argument for 'ready', 'append' and 'load' events
} else {
var args = [ $(document) ];
}
// return
return origCallback.apply(this, args);
}
}
_acf.add_action = function( action, callback, priority, context ){
// handle multiple actions
var actions = action.split(' ');
var length = actions.length;
if( length > 1 ) {
for( var i = 0; i < length; i++) {
action = actions[i];
_acf.add_action.apply(this, arguments);
}
return this;
}
// single
var callback = compatibleCallback(callback);
return acf.addAction.apply(this, arguments);
};
_acf.add_filter = function( action, callback, priority, context ){
var callback = compatibleCallback(callback);
return acf.addFilter.apply(this, arguments);
};
/*
* 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 = {
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);
// append $field to event object (used in field group)
if( acf.field_group ) {
e.$field = e.$el.closest('.acf-field-object');
}
// 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({
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 ){
// vars
var $el = $(this);
var $field = acf.get_closest_field( $el, model.type );
// bail early if no field
if( !$field.length ) return;
// focus
if( !$field.is(model.$field) ) {
model.set('$field', $field);
}
// append to event
e.$el = $el;
e.$field = $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);
}
});
/**
* validation
*
* description
*
* @date 15/2/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
var _validation = acf.newCompatibility(acf.validation, {
remove_error: function( $field ){
acf.getField( $field ).removeError();
},
add_warning: function( $field, message ){
acf.getField( $field ).showNotice({
text: message,
type: 'warning',
timeout: 1000
});
},
fetch: acf.validateForm,
enableSubmit: acf.enableSubmit,
disableSubmit: acf.disableSubmit,
showSpinner: acf.showSpinner,
hideSpinner: acf.hideSpinner,
unlockForm: acf.unlockForm,
lockForm: acf.lockForm
});
/**
* tooltip
*
* description
*
* @date 15/2/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
_acf.tooltip = {
tooltip: function( text, $el ){
var tooltip = acf.newTooltip({
text: text,
target: $el
});
// return
return tooltip.$el;
},
temp: function( text, $el ){
var tooltip = acf.newTooltip({
text: text,
target: $el,
timeout: 250
});
},
confirm: function( $el, callback, text, button_y, button_n ){
var tooltip = acf.newTooltip({
confirm: true,
text: text,
target: $el,
confirm: function(){
callback(true);
},
cancel: function(){
callback(false);
}
});
},
confirm_remove: function( $el, callback ){
var tooltip = acf.newTooltip({
confirmRemove: true,
target: $el,
confirm: function(){
callback(true);
},
cancel: function(){
callback(false);
}
});
},
};
/**
* tooltip
*
* description
*
* @date 15/2/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
_acf.media = new acf.Model({
activeFrame: false,
actions: {
'new_media_popup': 'onNewMediaPopup'
},
frame: function(){
return this.activeFrame;
},
onNewMediaPopup: function( popup ){
this.activeFrame = popup.frame;
},
popup: function( props ){
// update props
if( props.mime_types ) {
props.allowedTypes = props.mime_types;
}
if( props.id ) {
props.attachment = props.id;
}
// new
var popup = acf.newMediaPopup( props );
// append
/*
if( props.selected ) {
popup.selected = props.selected;
}
*/
// return
return popup.frame;
}
});
/**
* Select2
*
* description
*
* @date 11/6/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
_acf.select2 = {
init: function( $select, args, $field ){
// compatible args
if( args.allow_null ) {
args.allowNull = args.allow_null;
}
if( args.ajax_action ) {
args.ajaxAction = args.ajax_action;
}
if( $field ) {
args.field = acf.getField($field);
}
// return
return acf.newSelect2( $select, args );
},
destroy: function( $select ){
return acf.getInstance( $select ).destroy();
},
};
/**
* postbox
*
* description
*
* @date 11/6/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
_acf.postbox = {
render: function( args ){
// compatible args
if( args.edit_url ) {
args.editLink = args.edit_url;
}
if( args.edit_title ) {
args.editTitle = args.edit_title;
}
// return
return acf.newPostbox( args );
}
};
/**
* acf.screen
*
* description
*
* @date 11/6/18
* @since 5.6.9
*
* @param type $var Description. Default.
* @return type Description.
*/
acf.newCompatibility(acf.screen, {
update: function(){
return this.set.apply(this, arguments);
},
fetch: acf.screen.check
});
_acf.ajax = acf.screen;
})(jQuery);
// @codekit-prepend "_acf.js";
// @codekit-prepend "_acf-hooks.js";
// @codekit-prepend "_acf-model.js";
// @codekit-prepend "_acf-popup.js";
// @codekit-prepend "_acf-unload.js";
// @codekit-prepend "_acf-panel.js";
// @codekit-prepend "_acf-notice.js";
// @codekit-prepend "_acf-postbox.js";
// @codekit-prepend "_acf-tooltip.js";
// @codekit-prepend "_acf-field.js";
// @codekit-prepend "_acf-fields.js";
// @codekit-prepend "_acf-field-accordion.js";
// @codekit-prepend "_acf-field-button-group.js";
// @codekit-prepend "_acf-field-checkbox.js";
// @codekit-prepend "_acf-field-color-picker.js";
// @codekit-prepend "_acf-field-date-picker.js";
// @codekit-prepend "_acf-field-date-time-picker.js";
// @codekit-prepend "_acf-field-google-map.js";
// @codekit-prepend "_acf-field-image.js";
// @codekit-prepend "_acf-field-file.js";
// @codekit-prepend "_acf-field-link.js";
// @codekit-prepend "_acf-field-oembed.js";
// @codekit-prepend "_acf-field-radio.js";
// @codekit-prepend "_acf-field-range.js";
// @codekit-prepend "_acf-field-relationship.js";
// @codekit-prepend "_acf-field-select.js";
// @codekit-prepend "_acf-field-tab.js";
// @codekit-prepend "_acf-field-post-object.js";
// @codekit-prepend "_acf-field-page-link.js";
// @codekit-prepend "_acf-field-user.js";
// @codekit-prepend "_acf-field-taxonomy.js";
// @codekit-prepend "_acf-field-time-picker.js";
// @codekit-prepend "_acf-field-true-false.js";
// @codekit-prepend "_acf-field-url.js";
// @codekit-prepend "_acf-field-wysiwyg.js";
// @codekit-prepend "_acf-condition.js";
// @codekit-prepend "_acf-conditions.js";
// @codekit-prepend "_acf-condition-types.js";
// @codekit-prepend "_acf-media.js";
// @codekit-prepend "_acf-screen.js";
// @codekit-prepend "_acf-select2.js";
// @codekit-prepend "_acf-tinymce.js";
// @codekit-prepend "_acf-validation.js";
// @codekit-prepend "_acf-helpers.js";
// @codekit-prepend "_acf-compatibility";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment