( function( window, undefined ) { "use strict"; /** * Handles managing all events for whatever you plug it into. Priorities for hooks are based on lowest to highest in * that, lowest priority hooks are fired first. */ var EventManager = function() { /** * Maintain a reference to the object scope so our public methods never get confusing. */ var MethodsAvailable = { removeFilter : removeFilter, applyFilters : applyFilters, addFilter : addFilter, removeAction : removeAction, doAction : doAction, addAction : addAction, storage : getStorage }; /** * Contains the hooks that get registered with this EventManager. The array for storage utilizes a "flat" * object literal such that looking up the hook utilizes the native object literal hash. */ var STORAGE = { actions : {}, filters : {} }; function getStorage() { return STORAGE; }; /** * Adds an action to the event manager. * * @param action Must contain namespace.identifier * @param callback Must be a valid callback function before this action is added * @param [priority=10] Used to control when the function is executed in relation to other callbacks bound to the same hook * @param [context] Supply a value to be used for this */ function addAction( action, callback, priority, context ) { if( typeof action === 'string' && typeof callback === 'function' ) { priority = parseInt( ( priority || 10 ), 10 ); _addHook( 'actions', action, callback, priority, context ); } return MethodsAvailable; } /** * Performs an action if it exists. You can pass as many arguments as you want to this function; the only rule is * that the first argument must always be the action. */ function doAction( /* action, arg1, arg2, ... */ ) { var args = Array.prototype.slice.call( arguments ); var action = args.shift(); if( typeof action === 'string' ) { _runHook( 'actions', action, args ); } return MethodsAvailable; } /** * Removes the specified action if it contains a namespace.identifier & exists. * * @param action The action to remove * @param [callback] Callback function to remove */ function removeAction( action, callback ) { if( typeof action === 'string' ) { _removeHook( 'actions', action, callback ); } return MethodsAvailable; } /** * Adds a filter to the event manager. * * @param filter Must contain namespace.identifier * @param callback Must be a valid callback function before this action is added * @param [priority=10] Used to control when the function is executed in relation to other callbacks bound to the same hook * @param [context] Supply a value to be used for this */ function addFilter( filter, callback, priority, context ) { if( typeof filter === 'string' && typeof callback === 'function' ) { priority = parseInt( ( priority || 10 ), 10 ); _addHook( 'filters', filter, callback, priority, context ); } return MethodsAvailable; } /** * Performs a filter if it exists. You should only ever pass 1 argument to be filtered. The only rule is that * the first argument must always be the filter. */ function applyFilters( /* filter, filtered arg, arg2, ... */ ) { var args = Array.prototype.slice.call( arguments ); var filter = args.shift(); if( typeof filter === 'string' ) { return _runHook( 'filters', filter, args ); } return MethodsAvailable; } /** * Removes the specified filter if it contains a namespace.identifier & exists. * * @param filter The action to remove * @param [callback] Callback function to remove */ function removeFilter( filter, callback ) { if( typeof filter === 'string') { _removeHook( 'filters', filter, callback ); } return MethodsAvailable; } /** * Removes the specified hook by resetting the value of it. * * @param type Type of hook, either 'actions' or 'filters' * @param hook The hook (namespace.identifier) to remove * @private */ function _removeHook( type, hook, callback, context ) { if ( !STORAGE[ type ][ hook ] ) { return; } if ( !callback ) { STORAGE[ type ][ hook ] = []; } else { var handlers = STORAGE[ type ][ hook ]; var i; if ( !context ) { for ( i = handlers.length; i--; ) { if ( handlers[i].callback === callback ) { handlers.splice( i, 1 ); } } } else { for ( i = handlers.length; i--; ) { var handler = handlers[i]; if ( handler.callback === callback && handler.context === context) { handlers.splice( i, 1 ); } } } } } /** * Adds the hook to the appropriate storage container * * @param type 'actions' or 'filters' * @param hook The hook (namespace.identifier) to add to our event manager * @param callback The function that will be called when the hook is executed. * @param priority The priority of this hook. Must be an integer. * @param [context] A value to be used for this * @private */ function _addHook( type, hook, callback, priority, context ) { var hookObject = { callback : callback, priority : priority, context : context }; // Utilize 'prop itself' : http://jsperf.com/hasownproperty-vs-in-vs-undefined/19 var hooks = STORAGE[ type ][ hook ]; if( hooks ) { hooks.push( hookObject ); hooks = _hookInsertSort( hooks ); } else { hooks = [ hookObject ]; } STORAGE[ type ][ hook ] = hooks; } /** * Use an insert sort for keeping our hooks organized based on priority. This function is ridiculously faster * than bubble sort, etc: http://jsperf.com/javascript-sort * * @param hooks The custom array containing all of the appropriate hooks to perform an insert sort on. * @private */ function _hookInsertSort( hooks ) { var tmpHook, j, prevHook; for( var i = 1, len = hooks.length; i < len; i++ ) { tmpHook = hooks[ i ]; j = i; while( ( prevHook = hooks[ j - 1 ] ) && prevHook.priority > tmpHook.priority ) { hooks[ j ] = hooks[ j - 1 ]; --j; } hooks[ j ] = tmpHook; } return hooks; } /** * Runs the specified hook. If it is an action, the value is not modified but if it is a filter, it is. * * @param type 'actions' or 'filters' * @param hook The hook ( namespace.identifier ) to be ran. * @param args Arguments to pass to the action/filter. If it's a filter, args is actually a single parameter. * @private */ function _runHook( type, hook, args ) { var handlers = STORAGE[ type ][ hook ]; if ( !handlers ) { return (type === 'filters') ? args[0] : false; } var i = 0, len = handlers.length; if ( type === 'filters' ) { for ( ; i < len; i++ ) { args[ 0 ] = handlers[ i ].callback.apply( handlers[ i ].context, args ); } } else { for ( ; i < len; i++ ) { handlers[ i ].callback.apply( handlers[ i ].context, args ); } } return ( type === 'filters' ) ? args[ 0 ] : true; } // return all of the publicly available methods return MethodsAvailable; }; window.wp = window.wp || {}; window.wp.hooks = new EventManager(); } )( window ); var acf; (function($){ /* * exists * * This function will return true if a jQuery selection exists * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param n/a * @return (boolean) */ $.fn.exists = function() { return $(this).length>0; }; /* * outerHTML * * This function will return a string containing the HTML of the selected element * * @type function * @date 19/11/2013 * @since 5.0.0 * * @param $.fn * @return (string) */ $.fn.outerHTML = function() { return $(this).get(0).outerHTML; }; acf = { // vars l10n: {}, o: {}, /* * update * * This function will update a value found in acf.o * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param k (string) the key * @param v (mixed) the value * @return n/a */ update: function( k, v ){ this.o[ k ] = v; }, /* * get * * This function will return a value found in acf.o * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param k (string) the key * @return v (mixed) the value */ get: function( k ){ if( typeof this.o[ k ] !== 'undefined' ) { return this.o[ k ]; } return null; }, /* * _e * * This functiln will return a string found in acf.l10n * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param k1 (string) the first key to look for * @param k2 (string) the second key to look for * @return string (string) */ _e: function( k1, k2 ){ // defaults k2 = k2 || false; // get context var string = this.l10n[ k1 ] || ''; // get string if( k2 ) { string = string[ k2 ] || ''; } // return return string; }, /* * add_action * * This function uses wp.hooks to mimics WP add_action * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param * @return */ add_action: function() { // vars var a = arguments[0].split(' '), l = a.length; // loop for( var i = 0; i < l; i++) { /* // allow for special actions if( a[i].indexOf('initialize') !== -1 ) { a.push( a[i].replace('initialize', 'ready') ); a.push( a[i].replace('initialize', 'append') ); l = a.length; continue; } */ // prefix action arguments[0] = 'acf/' + a[i]; // add wp.hooks.addAction.apply(this, arguments); } // return return this; }, /* * remove_action * * This function uses wp.hooks to mimics WP remove_action * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param * @return */ remove_action: function() { // prefix action arguments[0] = 'acf/' + arguments[0]; wp.hooks.removeAction.apply(this, arguments); return this; }, /* * do_action * * This function uses wp.hooks to mimics WP do_action * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param * @return */ do_action: function() { //console.log('acf.do_action(%o)', arguments); // prefix action arguments[0] = 'acf/' + arguments[0]; wp.hooks.doAction.apply(this, arguments); return this; }, /* * add_filter * * This function uses wp.hooks to mimics WP add_filter * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param * @return */ add_filter: function() { // prefix action arguments[0] = 'acf/' + arguments[0]; wp.hooks.addFilter.apply(this, arguments); return this; }, /* * remove_filter * * This function uses wp.hooks to mimics WP remove_filter * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param * @return */ remove_filter: function() { // prefix action arguments[0] = 'acf/' + arguments[0]; wp.hooks.removeFilter.apply(this, arguments); return this; }, /* * apply_filters * * This function uses wp.hooks to mimics WP apply_filters * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param * @return */ apply_filters: function() { //console.log('acf.apply_filters(%o)', arguments); // prefix action arguments[0] = 'acf/' + arguments[0]; return wp.hooks.applyFilters.apply(this, arguments); }, /* * get_selector * * This function will return a valid selector for finding a field object * * @type function * @date 15/01/2015 * @since 5.1.5 * * @param s (string) * @return (string) */ get_selector: function( s ) { // defaults s = s || ''; // vars var selector = '.acf-field'; // compatibility with object if( $.isPlainObject(s) ) { if( $.isEmptyObject(s) ) { s = ''; } else { for( k in s ) { s = s[k]; break; } } } // search if( s ) { // append selector += '-' + s; // replace underscores (split/join replaces all and is faster than regex!) selector = selector.split('_').join('-'); // remove potential double up selector = selector.split('field-field-').join('field-'); } // return return selector; }, /* * get_fields * * This function will return a jQuery selection of fields * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param args (object) * @param $el (jQuery) element to look within * @param all (boolean) return all fields or allow filtering (for repeater) * @return $fields (jQuery) */ get_fields: function( s, $el, all ){ // debug //console.log( 'acf.get_fields(%o, %o, %o)', args, $el, all ); //console.time("acf.get_fields"); // defaults s = s || ''; $el = $el || false; all = all || false; // vars var selector = this.get_selector(s); // get child fields var $fields = $( selector, $el ); // append context to fields if also matches selector. // * Required for field group 'change_filed_type' append $tr to work if( $el !== false ) { $el.each(function(){ if( $(this).is(selector) ) { $fields = $fields.add( $(this) ); } }); } // filter out fields if( !all ) { // remove clone fields $fields = $fields.not('.acf-clone .acf-field'); // filter $fields = acf.apply_filters('get_fields', $fields); } //console.log('get_fields(%o, %o, %o) %o', s, $el, all, $fields); //console.log('acf.get_fields(%o):', this.get_selector(s) ); //console.timeEnd("acf.get_fields"); // return return $fields; }, /* * get_field * * This function will return a jQuery selection based on a field key * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param field_key (string) * @param $el (jQuery) element to look within * @return $field (jQuery) */ get_field: function( s, $el ){ // defaults s = s || ''; $el = $el || false; // get fields var $fields = this.get_fields(s, $el, true); // check if exists if( $fields.exists() ) { return $fields.first(); } // return return false; }, /* * get_closest_field * * This function will return the closest parent field * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param $el (jQuery) element to start from * @param args (object) * @return $field (jQuery) */ get_closest_field : function( $el, s ){ // defaults s = s || ''; // return return $el.closest( this.get_selector(s) ); }, /* * get_field_wrap * * This function will return the closest parent field * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param $el (jQuery) element to start from * @return $field (jQuery) */ get_field_wrap: function( $el ){ return $el.closest( this.get_selector() ); }, /* * get_field_key * * This function will return the field's key * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param $field (jQuery) * @return (string) */ get_field_key: function( $field ){ return $field.data('key'); }, /* * get_field_type * * This function will return the field's type * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param $field (jQuery) * @return (string) */ get_field_type: function( $field ){ return $field.data('type'); }, /* * get_data * * This function will return attribute data for a given elemnt * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param $el (jQuery) * @param name (mixed) * @return (mixed) */ get_data: function( $el, defaults ){ // get data var data = $el.data(); // defaults if( typeof defaults === 'object' ) { data = this.parse_args( data, defaults ); } // return return data; }, /* * get_uniqid * * This function will return a unique string ID * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param prefix (string) * @param more_entropy (boolean) * @return (string) */ get_uniqid : function( prefix, more_entropy ){ // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // + revised by: Kankrelune (http://www.webfaktory.info/) // % note 1: Uses an internal counter (in php_js global) to avoid collision // * example 1: uniqid(); // * returns 1: 'a30285b160c14' // * example 2: uniqid('foo'); // * returns 2: 'fooa30285b1cd361' // * example 3: uniqid('bar', true); // * returns 3: 'bara20285b23dfd1.31879087' if (typeof prefix === 'undefined') { prefix = ""; } var retId; var formatSeed = function (seed, reqWidth) { seed = parseInt(seed, 10).toString(16); // to hex str if (reqWidth < seed.length) { // so long we split return seed.slice(seed.length - reqWidth); } if (reqWidth > seed.length) { // so short we pad return Array(1 + (reqWidth - seed.length)).join('0') + seed; } return seed; }; // BEGIN REDUNDANT if (!this.php_js) { this.php_js = {}; } // END REDUNDANT if (!this.php_js.uniqidSeed) { // init seed with big random int this.php_js.uniqidSeed = Math.floor(Math.random() * 0x75bcd15); } this.php_js.uniqidSeed++; retId = prefix; // start with prefix, add current milliseconds hex string retId += formatSeed(parseInt(new Date().getTime() / 1000, 10), 8); retId += formatSeed(this.php_js.uniqidSeed, 5); // add seed hex string if (more_entropy) { // for more entropy we add a float lower to 10 retId += (Math.random() * 10).toFixed(8).toString(); } return retId; }, /* * serialize_form * * This function will create an object of data containing all form inputs within an element * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param $el (jQuery selection) * @return $post_id (int) */ serialize_form: function(){ return this.serialize.apply( this, arguments ); }, serialize: function( $el, prefix ){ // defaults prefix = prefix || ''; // vars var data = {}; var names = {}; var values = $el.find('select, textarea, input').serializeArray(); // populate data $.each( values, function( i, pair ) { // vars var name = pair.name; var value = pair.value; // prefix if( prefix ) { // bail early if does not contain if( name.indexOf(prefix) !== 0 ) return; // remove prefix name = name.slice(prefix.length); // name must not start as array piece if( name.slice(0, 1) == '[' ) { name = name.slice(1).replace(']', ''); } } // initiate name if( name.slice(-2) === '[]' ) { // remove [] name = name.slice(0, -2); // initiate counter if( typeof names[ name ] === 'undefined'){ names[ name ] = -1; } // increase counter names[ name ]++; // add key name += '[' + names[ name ] +']'; } // append to data data[ name ] = value; }); //console.log('serialize', data); // return return data; }, /* serialize: function( $el, prefix ){ // defaults prefix = prefix || ''; // vars var data = {}; var $inputs = $el.find('select, textarea, input'); // loop $inputs.each(function(){ // vars var $el = $(this); var name = $el.attr('name'); var val = $el.val(); // is array var is_array = ( name.slice(-2) === '[]' ); if( is_array ) { name = name.slice(0, -2); } // explode name var bits = name.split('['); var depth = bits.length; // loop for( var i = 0; i < depth; i++ ) { // vars var k = bits[i]; // end if( i == depth-1 ) { // not end } else { // must be object if( typeof data[k] !== 'object' ) { data[k] = {}; } } } bits.map(function( s ){ return s.replace(']', ''); }) }); }, */ /* * disable * * This function will disable an input * * @type function * @date 22/09/2016 * @since 5.4.0 * * @param $el (jQuery) * @param context (string) * @return n/a */ disable: function( $input, context ){ // defaults context = context || ''; // bail early if is .acf-disabled if( $input.hasClass('acf-disabled') ) return false; // always disable input $input.prop('disabled', true); // context if( context ) { // vars var disabled = $input.data('acf_disabled') || [], i = disabled.indexOf(context); // append context if not found if( i < 0 ) { // append disabled.push( context ); // update $input.data('acf_disabled', disabled); } } // return return true; }, /* * enable * * This function will enable an input * * @type function * @date 22/09/2016 * @since 5.4.0 * * @param $el (jQuery) * @param context (string) * @return n/a */ enable: function( $input, context ){ // defaults context = context || ''; // bail early if is .acf-disabled if( $input.hasClass('acf-disabled') ) return false; // vars var disabled = $input.data('acf_disabled') || []; // context if( context ) { // vars var i = disabled.indexOf(context); // remove context if found if( i > -1 ) { // delete disabled.splice(i, 1); // update $input.data('acf_disabled', disabled); } } // bail early if other disabled exist if( disabled.length ) return false; // enable input $input.prop('disabled', false); // return return true; }, /* * disable_el * * This function will disable all inputs within an element * * @type function * @date 22/09/2016 * @since 5.4.0 * * @param $el (jQuery) * @param context (string) * @return na */ disable_el: function( $el, context ) { // defaults context = context || ''; // loop $el.find('select, textarea, input').each(function(){ acf.disable( $(this), context ); }); }, disable_form: function( $el, context ) { this.disable_el.apply( this, arguments ); }, /* * enable_el * * This function will enable all inputs within an element * * @type function * @date 22/09/2016 * @since 5.4.0 * * @param $el (jQuery) * @param context (string) * @return na */ enable_el: function( $el, context ) { // defaults context = context || ''; // loop $el.find('select, textarea, input').each(function(){ acf.enable( $(this), context ); }); }, enable_form: function( $el, context ) { this.enable_el.apply( this, arguments ); }, /* * remove_tr * * This function will remove a tr element with animation * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param $tr (jQuery selection) * @param callback (function) runs on complete * @return n/a */ remove_tr : function( $tr, callback ){ // vars var height = $tr.height(), children = $tr.children().length; // add class $tr.addClass('acf-remove-element'); // after animation setTimeout(function(){ // remove class $tr.removeClass('acf-remove-element'); // vars $tr.html(''); $tr.children('td').animate({ height : 0}, 250, function(){ $tr.remove(); if( typeof(callback) == 'function' ) { callback(); } }); }, 250); }, /* * remove_el * * This function will remove an element with animation * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param $el (jQuery selection) * @param callback (function) runs on complete * @param end_height (int) * @return n/a */ remove_el : function( $el, callback, end_height ){ // defaults end_height = end_height || 0; // vars var height = $el.height(), width = $el.width(), margin = $el.css('margin'), outer_height = $el.outerHeight(true); // action acf.do_action('remove', $el); // create wrap $el.wrap('
'); var $wrap = $el.parent(); // set pos $el.css({ height: height, width: width, margin: margin, position: 'absolute' }); // fade setTimeout(function(){ // aniamte $wrap.css({ opacity: 0, height: end_height }); }, 50); // animate complete setTimeout(function(){ // remove wrap $wrap.remove(); // callback if( typeof(callback) == 'function' ) { callback.apply(this, arguments); } }, 301); }, /* * isset * * This function will return true if an object key exists * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param (object) * @param key1 (string) * @param key2 (string) * @param ... * @return (boolean) */ isset : function(){ var a = arguments, l = a.length, c = null, undef; if (l === 0) { throw new Error('Empty isset'); } c = a[0]; for (i = 1; i < l; i++) { if (a[i] === undef || c[ a[i] ] === undef) { return false; } c = c[ a[i] ]; } return true; }, /* * maybe_get * * This function will attempt to return a value and return null if not possible * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param obj (object) the array to look within * @param key (key) the array key to look for. Nested values may be found using '/' * @param value (mixed) the value returned if not found * @return (mixed) */ maybe_get: function( obj, key, value ){ // default if( typeof value == 'undefined' ) value = null; // convert type to string and split keys = String(key).split('.'); // loop through keys for( var i in keys ) { // vars var key = keys[i]; // bail ealry if not set if( typeof obj[ key ] === 'undefined' ) { return value; } // update obj obj = obj[ key ]; } // return return obj; }, /* * open_popup * * This function will create and open a popup modal * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param args (object) * @return n/a */ open_popup : function( args ){ // vars $popup = $('body > #acf-popup'); // already exists? if( $popup.exists() ) { return update_popup(args); } // template var tmpl = [ '
', '
', '

', '
', '
', '
', '
', '
' ].join(''); // append $('body').append( tmpl ); $('#acf-popup').on('click', '.bg, .acf-close-popup', function( e ){ e.preventDefault(); acf.close_popup(); }); // update return this.update_popup(args); }, /* * update_popup * * This function will update the content within a popup modal * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param args (object) * @return n/a */ update_popup : function( args ){ // vars $popup = $('#acf-popup'); // validate if( !$popup.exists() ) { return false } // defaults args = $.extend({}, { title : '', content : '', width : 0, height : 0, loading : false }, args); if( args.title ) { $popup.find('.title h3').html( args.title ); } if( args.content ) { $inner = $popup.find('.inner:first'); $inner.html( args.content ); acf.do_action('append', $inner); // update height $inner.attr('style', 'position: relative;'); args.height = $inner.outerHeight(); $inner.removeAttr('style'); } if( args.width ) { $popup.find('.acf-popup-box').css({ 'width' : args.width, 'margin-left' : 0 - (args.width / 2) }); } if( args.height ) { // add h3 height (44) args.height += 44; $popup.find('.acf-popup-box').css({ 'height' : args.height, 'margin-top' : 0 - (args.height / 2) }); } if( args.loading ) { $popup.find('.loading').show(); } else { $popup.find('.loading').hide(); } return $popup; }, /* * close_popup * * This function will close and remove a popup modal * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param n/a * @return n/a */ close_popup : function(){ // vars $popup = $('#acf-popup'); // already exists? if( $popup.exists() ) { $popup.remove(); } }, /* * update_user_setting * * This function will send an AJAX request to update a user setting * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param $post_id (int) * @return $post_id (int) */ update_user_setting : function( name, value ) { // ajax $.ajax({ url : acf.get('ajaxurl'), dataType : 'html', type : 'post', data : acf.prepare_for_ajax({ 'action' : 'acf/update_user_setting', 'name' : name, 'value' : value }) }); }, /* * prepare_for_ajax * * This function will prepare data for an AJAX request * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param args (object) * @return args */ prepare_for_ajax : function( args ) { // vars var data = { nonce : acf.get('nonce'), post_id : acf.get('post_id') }; // $.ajax() expects all args to be 'non-nested' $.each(args, function(k,v){ // object if( $.isPlainObject(v) && !$.isEmptyObject(v) ) { // loop $.each(v, function(k2,v2){ // convert string k2 = k2 + ''; // vars var i = k2.indexOf('['); // starts with [ if( i == 0 ) { k2 = k + k2; // contains [ } else if( i > 0 ) { k2 = k + '[' + k2.slice(0, i) + ']' + k2.slice(i); // no [ } else { k2 = k + '[' + k2 + ']'; } // append data[k2] = v2; }); // else } else { data[k] = v; } }); // filter for 3rd party customization data = acf.apply_filters('prepare_for_ajax', data); //console.log( 'prepare_for_ajax', data ); // return return data; }, /* * is_ajax_success * * This function will return true for a successful WP AJAX response * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param json (object) * @return (boolean) */ is_ajax_success : function( json ) { if( json && json.success ) { return true; } return false; }, /* * get_ajax_message * * This function will return an object containing error/message information * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param json (object) * @return (boolean) */ get_ajax_message: function( json ) { // vars var message = { text: '', type: 'error' }; // bail early if no json if( !json ) { return message; } // PHP error (too may themes will have warnings / errors. Don't show these in ACF taxonomy popup) /* if( typeof json === 'string' ) { message.text = json; return message; } */ // success if( json.success ) { message.type = 'success'; } // message if( json.data && json.data.message ) { message.text = json.data.message; } // error if( json.data && json.data.error ) { message.text = json.data.error; } // return return message; }, /* * is_in_view * * This function will return true if a jQuery element is visible in browser * * @type function * @date 8/09/2014 * @since 5.0.0 * * @param $el (jQuery) * @return (boolean) */ is_in_view: function( $el ) { // vars var elemTop = $el.offset().top, elemBottom = elemTop + $el.height(); // bail early if hidden if( elemTop === elemBottom ) { return false; } // more vars var docViewTop = $(window).scrollTop(), docViewBottom = docViewTop + $(window).height(); // return return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop)); }, /* * val * * This function will update an elements value and trigger the change event if different * * @type function * @date 16/10/2014 * @since 5.0.9 * * @param $el (jQuery) * @param val (mixed) * @return n/a */ val: function( $el, val ){ // vars var orig = $el.val(); // update value $el.val( val ); // trigger change if( val != orig ) { $el.trigger('change'); } }, /* * str_replace * * This function will perform a str replace similar to php function str_replace * * @type function * @date 1/05/2015 * @since 5.2.3 * * @param $search (string) * @param $replace (string) * @param $subject (string) * @return (string) */ str_replace: function( search, replace, subject ) { return subject.split(search).join(replace); }, /* * str_sanitize * * description * * @type function * @date 4/06/2015 * @since 5.2.3 * * @param $post_id (int) * @return $post_id (int) */ str_sanitize: function( string ) { // chars (https://jsperf.com/replace-foreign-characters) var map = { "À": "A", "Á": "A", "Â": "A", "Ã": "A", "Ä": "A", "Å": "A", "Æ": "AE", "Ç": "C", "È": "E", "É": "E", "Ê": "E", "Ë": "E", "Ì": "I", "Í": "I", "Î": "I", "Ï": "I", "Ð": "D", "Ñ": "N", "Ò": "O", "Ó": "O", "Ô": "O", "Õ": "O", "Ö": "O", "Ø": "O", "Ù": "U", "Ú": "U", "Û": "U", "Ü": "U", "Ý": "Y", "ß": "s", "à": "a", "á": "a", "â": "a", "ã": "a", "ä": "a", "å": "a", "æ": "ae", "ç": "c", "è": "e", "é": "e", "ê": "e", "ë": "e", "ì": "i", "í": "i", "î": "i", "ï": "i", "ñ": "n", "ò": "o", "ó": "o", "ô": "o", "õ": "o", "ö": "o", "ø": "o", "ù": "u", "ú": "u", "û": "u", "ü": "u", "ý": "y", "ÿ": "y", "Ā": "A", "ā": "a", "Ă": "A", "ă": "a", "Ą": "A", "ą": "a", "Ć": "C", "ć": "c", "Ĉ": "C", "ĉ": "c", "Ċ": "C", "ċ": "c", "Č": "C", "č": "c", "Ď": "D", "ď": "d", "Đ": "D", "đ": "d", "Ē": "E", "ē": "e", "Ĕ": "E", "ĕ": "e", "Ė": "E", "ė": "e", "Ę": "E", "ę": "e", "Ě": "E", "ě": "e", "Ĝ": "G", "ĝ": "g", "Ğ": "G", "ğ": "g", "Ġ": "G", "ġ": "g", "Ģ": "G", "ģ": "g", "Ĥ": "H", "ĥ": "h", "Ħ": "H", "ħ": "h", "Ĩ": "I", "ĩ": "i", "Ī": "I", "ī": "i", "Ĭ": "I", "ĭ": "i", "Į": "I", "į": "i", "İ": "I", "ı": "i", "IJ": "IJ", "ij": "ij", "Ĵ": "J", "ĵ": "j", "Ķ": "K", "ķ": "k", "Ĺ": "L", "ĺ": "l", "Ļ": "L", "ļ": "l", "Ľ": "L", "ľ": "l", "Ŀ": "L", "ŀ": "l", "Ł": "l", "ł": "l", "Ń": "N", "ń": "n", "Ņ": "N", "ņ": "n", "Ň": "N", "ň": "n", "ʼn": "n", "Ō": "O", "ō": "o", "Ŏ": "O", "ŏ": "o", "Ő": "O", "ő": "o", "Œ": "OE", "œ": "oe", "Ŕ": "R", "ŕ": "r", "Ŗ": "R", "ŗ": "r", "Ř": "R", "ř": "r", "Ś": "S", "ś": "s", "Ŝ": "S", "ŝ": "s", "Ş": "S", "ş": "s", "Š": "S", "š": "s", "Ţ": "T", "ţ": "t", "Ť": "T", "ť": "t", "Ŧ": "T", "ŧ": "t", "Ũ": "U", "ũ": "u", "Ū": "U", "ū": "u", "Ŭ": "U", "ŭ": "u", "Ů": "U", "ů": "u", "Ű": "U", "ű": "u", "Ų": "U", "ų": "u", "Ŵ": "W", "ŵ": "w", "Ŷ": "Y", "ŷ": "y", "Ÿ": "Y", "Ź": "Z", "ź": "z", "Ż": "Z", "ż": "z", "Ž": "Z", "ž": "z", "ſ": "s", "ƒ": "f", "Ơ": "O", "ơ": "o", "Ư": "U", "ư": "u", "Ǎ": "A", "ǎ": "a", "Ǐ": "I", "ǐ": "i", "Ǒ": "O", "ǒ": "o", "Ǔ": "U", "ǔ": "u", "Ǖ": "U", "ǖ": "u", "Ǘ": "U", "ǘ": "u", "Ǚ": "U", "ǚ": "u", "Ǜ": "U", "ǜ": "u", "Ǻ": "A", "ǻ": "a", "Ǽ": "AE", "ǽ": "ae", "Ǿ": "O", "ǿ": "o", // extra ' ': '_', '\'': '', '?': '', '/': '', '\\': '', '.': '', ',': '', '`': '', '>': '', '<': '', '"': '', '[': '', ']': '', '|': '', '{': '', '}': '', '(': '', ')': '' }; // vars var regexp = /\W/g, mapping = function (c) { return (typeof map[c] !== 'undefined') ? map[c] : c; }; // replace string = string.replace(regexp, mapping); // lower case string = string.toLowerCase(); // return return string; }, /* * addslashes * * This function mimics the PHP addslashes function. * Returns a string with backslashes before characters that need to be escaped. * * @type function * @date 9/1/17 * @since 5.5.0 * * @param text (string) * @return (string) */ addslashes: function(text){ return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); }, /* * render_select * * This function will update a select field with new choices * * @type function * @date 8/04/2014 * @since 5.0.0 * * @param $select * @param choices * @return n/a */ render_select: function( $select, choices ){ // vars var value = $select.val(); // clear choices $select.html(''); // bail early if no choices if( !choices ) { return; } // populate choices $.each(choices, function( i, item ){ // vars var $optgroup = $select; // add group if( item.group ) { $optgroup = $select.find('optgroup[label="' + item.group + '"]'); if( !$optgroup.exists() ) { $optgroup = $(''); $select.append( $optgroup ); } } // append select $optgroup.append( '' ); // selectedIndex if( value == item.value ) { $select.prop('selectedIndex', i); } }); }, /* * duplicate * * This function will duplicate and return an element * * @type function * @date 22/08/2015 * @since 5.2.3 * * @param $el (jQuery) object to be duplicated * @param attr (string) attrbute name where $el id can be found * @return $el2 (jQuery) */ duplicate: function( args ){ //console.time('duplicate'); // backwards compatibility // - array of settings added in v5.4.6 if( typeof args.length !== 'undefined' ) args = { $el: args }; // defaults args = acf.parse_args(args, { $el: false, search: '', replace: '', before: function( $el ){}, after: function( $el, $el2 ){}, append: function( $el, $el2 ){ $el.after( $el2 ); } }); // vars var $el = args.$el, $el2; // search if( !args.search ) args.search = $el.attr('data-id'); // replace if( !args.replace ) args.replace = acf.get_uniqid(); // before // - allow acf to modify DOM // - fixes bug where select field option is not selected args.before.apply( this, [$el] ); acf.do_action('before_duplicate', $el); // clone var $el2 = $el.clone(); // remove acf-clone (may be a clone) $el2.removeClass('acf-clone'); // remove JS functionality acf.do_action('remove', $el2); // find / replace if( args.search ) { // replace data $el2.attr('data-id', args.replace); // replace ids $el2.find('[id*="' + args.search + '"]').each(function(){ $(this).attr('id', $(this).attr('id').replace(args.search, args.replace) ); }); // replace names $el2.find('[name*="' + args.search + '"]').each(function(){ $(this).attr('name', $(this).attr('name').replace(args.search, args.replace) ); }); // replace label for $el2.find('label[for*="' + args.search + '"]').each(function(){ $(this).attr('for', $(this).attr('for').replace(args.search, args.replace) ); }); } // remove ui-sortable $el2.find('.ui-sortable').removeClass('ui-sortable'); // after // - allow acf to modify DOM acf.do_action('after_duplicate', $el, $el2 ); args.after.apply( this, [$el, $el2] ); // append args.append.apply( this, [$el, $el2] ); // add JS functionality // - allow element to be moved into a visible position before fire action setTimeout(function(){ acf.do_action('append', $el2); }, 1); //console.timeEnd('duplicate'); // return return $el2; }, decode: function( string ){ return $(''); // append textarea $node.before( $textarea ); // update vars this.active = true; this.$node = $node; this.$textarea = $textarea; // get atts var atts = this.atts(); // open link wpLink.open( 'acf-link-textarea', atts.url, atts.title, null ); // always show title (WP will hide title if empty) $('#wp-link-wrap').addClass('has-text-field'); }, reset: function(){ this.active = false; this.$textarea.remove(); this.$textarea = null; this.$node = null; }, _select: function( e, $li ){ // get inputs var val = this.inputs(); // update title if( !val.title ) { val.title = $li.find('.item-title').text(); this.inputs( val ); console.log(val); } }, _open: function( e ){ // bail early if not active if( !this.active ) return; // get atts var val = this.atts(); // update WP inputs this.inputs( val ); }, _close: function( e ){ // bail early if not active if( !this.active ) return; // reset vars // use timeout to allow _update() function to check vars setTimeout(function(){ acf.link.reset(); }, 100); }, _update: function( e ){ // bail early if not active if( !this.active ) return; // get atts var val = this.inputs(); // update node this.atts( val ); } }); // todo - listen to AJAX for wp-link-ajax and append post_id to value })(jQuery); (function($){ acf.media = acf.model.extend({ frames: [], mime_types: {}, actions: { 'ready': 'ready' }, /* * frame * * This function will return the current frame * * @type function * @date 11/04/2016 * @since 5.3.2 * * @param n/a * @return frame (object) */ frame: function(){ // vars var i = this.frames.length - 1; // bail early if no index if( i < 0 ) return false; // return return this.frames[ i ]; }, /* * destroy * * this function will destroy a frame * * @type function * @date 11/04/2016 * @since 5.3.8 * * @return frame (object) * @return n/a */ destroy: function( frame ) { // detach frame.detach(); frame.dispose(); // remove frame frame = null; this.frames.pop(); }, /* * popup * * This function will create a wp media popup frame * * @type function * @date 11/04/2016 * @since 5.3.8 * * @param args (object) * @return frame (object) */ popup: function( args ) { // vars var post_id = acf.get('post_id'), frame = false; // validate post_id if( !$.isNumeric(post_id) ) post_id = 0; // settings var settings = acf.parse_args( args, { mode: 'select', // 'select', 'edit' title: '', // 'Upload Image' button: '', // 'Select Image' type: '', // 'image', '' field: '', // 'field_123' mime_types: '', // 'pdf, etc' library: 'all', // 'all', 'uploadedTo' multiple: false, // false, true, 'add' attachment: 0, // the attachment to edit post_id: post_id, // the post being edited select: function(){} }); // id changed to attributes if( settings.id ) settings.attachment = settings.id; // create frame var frame = this.new_media_frame( settings ); // append this.frames.push( frame ); // open popup (allow frame customization before opening) setTimeout(function(){ frame.open(); }, 1); // return return frame; }, /* * _get_media_frame_settings * * This function will return an object containing frame settings * * @type function * @date 11/04/2016 * @since 5.3.8 * * @param frame (object) * @param settings (object) * @return frame (object) */ _get_media_frame_settings: function( frame, settings ){ // select if( settings.mode === 'select' ) { frame = this._get_select_frame_settings( frame, settings ); // edit } else if( settings.mode === 'edit' ) { frame = this._get_edit_frame_settings( frame, settings ); } // return return frame; }, _get_select_frame_settings: function( frame, settings ){ // type if( settings.type ) { frame.library.type = settings.type; } // library if( settings.library === 'uploadedTo' ) { frame.library.uploadedTo = settings.post_id; } // button frame._button = acf._e('media', 'select'); // return return frame; }, _get_edit_frame_settings: function( frame, settings ){ // post__in frame.library.post__in = [ settings.attachment ]; // button frame._button = acf._e('media', 'update'); // return return frame; }, /* * _add_media_frame_events * * This function will add events to the frame object * * @type function * @date 11/04/2016 * @since 5.3.8 * * @param $post_id (int) * @return $post_id (int) */ _add_media_frame_events: function( frame, settings ){ // log events /* frame.on('all', function( e ) { console.log( 'frame all: %o', e ); }); */ // add class frame.on('open',function() { // add class this.$el.closest('.media-modal').addClass('acf-media-modal -' +settings.mode ); }, frame); // edit image view // source: media-views.js:2410 editImageContent() frame.on('content:render:edit-image', function(){ var image = this.state().get('image'), view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); this.content.set( view ); // after creating the wrapper view, load the actual editor via an ajax call view.loadEditor(); }, frame); // update toolbar button frame.on( 'toolbar:create:select', function( toolbar ) { toolbar.view = new wp.media.view.Toolbar.Select({ text: frame.options._button, controller: this }); }, frame ); // select image frame.on('select', function() { // get selected images var state = frame.state(), image = state.get('image'), selection = state.get('selection'); // if editing image if( image ) { settings.select.apply( frame, [image, 0] ); return; } // if selecting images if( selection ) { // vars var i = 0; // loop selection.each(function( attachment ){ settings.select.apply( frame, [attachment, i] ); i++; }); return; } }); // close popup frame.on('close',function(){ setTimeout(function(){ acf.media.destroy( frame ); }, 500); }); // select if( settings.mode === 'select' ) { frame = this._add_select_frame_events( frame, settings ); // edit } else if( settings.mode === 'edit' ) { frame = this._add_edit_frame_events( frame, settings ); } // return return frame; }, _add_select_frame_events: function( frame, settings ){ // reference var self = this; // plupload // adds _acfuploader param to validate uploads if( acf.isset(_wpPluploadSettings, 'defaults', 'multipart_params') ) { // add _acfuploader so that Uploader will inherit _wpPluploadSettings.defaults.multipart_params._acfuploader = settings.field; // remove acf_field so future Uploaders won't inherit frame.on('open', function(){ delete _wpPluploadSettings.defaults.multipart_params._acfuploader; }); } // modify DOM frame.on('content:activate:browse', function(){ // populate above vars making sure to allow for failure try { var toolbar = frame.content.get().toolbar, filters = toolbar.get('filters'), search = toolbar.get('search'); } catch(e) { // one of the objects was 'undefined'... perhaps the frame open is Upload Files // console.log( 'error %o', e ); return; } // image if( settings.type == 'image' ) { // update all filters.filters.all.text = acf._e('image', 'all'); // remove some filters delete filters.filters.audio; delete filters.filters.video; // update all filters to show images $.each( filters.filters, function( k, filter ){ if( filter.props.type === null ) { filter.props.type = 'image'; } }); } // custom mime types if( settings.mime_types ) { // explode var extra_types = settings.mime_types.split(' ').join('').split('.').join('').split(','); // loop through mime_types $.each( extra_types, function( i, type ){ // find mime $.each( self.mime_types, function( t, mime ){ // continue if key does not match if( t.indexOf(type) === -1 ) { return; } // create new filter var filter = { text: type, props: { status: null, type: mime, uploadedTo: null, orderby: 'date', order: 'DESC' }, priority: 20 }; // append filter filters.filters[ mime ] = filter; }); }); } // uploaded to post if( settings.library == 'uploadedTo' ) { // remove some filters delete filters.filters.unattached; delete filters.filters.uploaded; // add 'uploadedTo' text filters.$el.parent().append('' + acf._e('image', 'uploadedTo') + ''); // add uploadedTo to filters $.each( filters.filters, function( k, filter ){ filter.props.uploadedTo = settings.post_id; }); } // add _acfuploader to filters $.each( filters.filters, function( k, filter ){ filter.props._acfuploader = settings.field; }); // add _acfuplaoder to search search.model.attributes._acfuploader = settings.field; // render if( typeof filters.refresh === 'function' ) { filters.refresh(); } }); // return return frame; }, _add_edit_frame_events: function( frame, settings ){ // add class frame.on('open',function() { // add class this.$el.closest('.media-modal').addClass('acf-expanded'); // set to browse if( this.content.mode() != 'browse' ) { this.content.mode('browse'); } // set selection var state = this.state(), selection = state.get('selection'), attachment = wp.media.attachment( settings.attachment ); selection.add( attachment ); }, frame); // return return frame; }, /* * new_media_frame * * this function will create a new media frame * * @type function * @date 11/04/2016 * @since 5.3.8 * * @param settings (object) * @return frame (object) */ new_media_frame: function( settings ){ // vars var attributes = { title: settings.title, multiple: settings.multiple, library: {}, states: [] }; // get options attributes = this._get_media_frame_settings( attributes, settings ); // create query var Query = wp.media.query( attributes.library ); // add _acfuploader // this is super wack! // if you add _acfuploader to the options.library args, new uploads will not be added to the library view. // this has been traced back to the wp.media.model.Query initialize function (which can't be overriden) // Adding any custom args will cause the Attahcments to not observe the uploader queue // To bypass this security issue, we add in the args AFTER the Query has been initialized // options.library._acfuploader = settings.field; if( acf.isset(Query, 'mirroring', 'args') ) { Query.mirroring.args._acfuploader = settings.field; } // add states attributes.states = [ // main state new wp.media.controller.Library({ library: Query, multiple: attributes.multiple, title: attributes.title, priority: 20, filterable: 'all', editable: true, // If the user isn't allowed to edit fields, // can they still edit it locally? allowLocalEdits: true }) ]; // edit image functionality (added in WP 3.9) if( acf.isset(wp, 'media', 'controller', 'EditImage') ) { attributes.states.push( new wp.media.controller.EditImage() ); } // create frame var frame = wp.media( attributes ); // add args reference frame.acf = settings; // add events frame = this._add_media_frame_events( frame, settings ); // return return frame; }, ready: function(){ // vars var version = acf.get('wp_version'), browser = acf.get('browser'), post_id = acf.get('post_id'); // update wp.media if( acf.isset(window,'wp','media','view','settings','post') && $.isNumeric(post_id) ) { wp.media.view.settings.post.id = post_id; } // append browser if( browser ) { $('body').addClass('browser-' + browser ); } // append version if( version ) { // ensure is string version = version + ''; // use only major version major = version.substr(0,1); // add body class $('body').addClass('major-' + major); } // customize wp.media views if( acf.isset(window, 'wp', 'media', 'view') ) { //this.customize_Attachments(); //this.customize_Query(); //this.add_AcfEmbed(); this.customize_Attachment(); this.customize_AttachmentFiltersAll(); this.customize_AttachmentCompat(); } }, /* add_AcfEmbed: function(){ //test urls //(image) jpg: http://www.ml24.net/img/ml24_design_process_scion_frs_3d_rendering.jpg //(image) svg: http://kompozer.net/images/svg/Mozilla_Firefox.svg //(file) pdf: http://martinfowler.com/ieeeSoftware/whenType.pdf //(video) mp4: https://videos.files.wordpress.com/kUJmAcSf/bbb_sunflower_1080p_30fps_normal_hd.mp4 // add view wp.media.view.AcfEmbed = wp.media.view.Embed.extend({ initialize: function() { // set attachments this.model.props.attributes = this.controller.acf.attachment || {}; // refresh wp.media.view.Embed.prototype.initialize.apply( this, arguments ); }, refresh: function() { // vars var attachment = acf.parse_args(this.model.props.attributes, { url: '', filename: '', title: '', caption: '', alt: '', description: '', type: '', ext: '' }); // update attachment if( attachment.url ) { // filename attachment.filename = attachment.url.split('/').pop().split('?')[0]; // update attachment.ext = attachment.filename.split('.').pop(); attachment.type = /(jpe?g|png|gif|svg)/i.test(attachment.ext) ? 'image': 'file'; } // auto generate title if( attachment.filename && !attachment.title ) { // replace attachment.title = attachment.filename.split('-').join(' ').split('_').join(' '); // uppercase first word attachment.title = attachment.title.charAt(0).toUpperCase() + attachment.title.slice(1); // remove extension attachment.title = attachment.title.replace('.'+attachment.ext, ''); // update model this.model.props.attributes.title = attachment.title; } // save somee extra data this.model.props.attributes.filename = attachment.filename; this.model.props.attributes.type = attachment.type; // always show image view // avoid this.model.set() to prevent listeners updating view this.model.attributes.type = 'image'; // refresh wp.media.view.Embed.prototype.refresh.apply( this, arguments ); // append title this.$el.find('.setting.caption').before([ '' ].join('')); // append description this.$el.find('.setting.alt-text').after([ '' ].join('')); // hide alt if( attachment.type !== 'image' ) { this.$el.find('.setting.alt-text').hide(); } } }); }, */ /* customize_Attachments: function(){ // vars var Attachments = wp.media.model.Attachments; wp.media.model.Attachments = Attachments.extend({ initialize: function( models, options ){ // console.log('My Attachments initialize: %o %o %o', this, models, options); // return return Attachments.prototype.initialize.apply( this, arguments ); }, sync: function( method, model, options ) { // console.log('My Attachments sync: %o %o %o %o', this, method, model, options); // return return Attachments.prototype.sync.apply( this, arguments ); } }); }, customize_Query: function(){ // console.log('customize Query!'); // vars var Query = wp.media.model.Query; wp.media.model.Query = {}; }, */ customize_Attachment: function(){ // vars var AttachmentLibrary = wp.media.view.Attachment.Library; // extend wp.media.view.Attachment.Library = AttachmentLibrary.extend({ render: function() { // vars var frame = acf.media.frame(), errors = acf.maybe_get(this, 'model.attributes.acf_errors'); // add class // also make sure frame exists to prevent this logic running on a WP popup (such as feature image) if( frame && errors ) { this.$el.addClass('acf-disabled'); } // return return AttachmentLibrary.prototype.render.apply( this, arguments ); }, /* * toggleSelection * * This function is called before an attachment is selected * A good place to check for errors and prevent the 'select' function from being fired * * @type function * @date 29/09/2016 * @since 5.4.0 * * @param options (object) * @return n/a */ toggleSelection: function( options ) { // vars // source: wp-includes/js/media-views.js:2880 var collection = this.collection, selection = this.options.selection, model = this.model, single = selection.single(); // vars var frame = acf.media.frame(), errors = acf.maybe_get(this, 'model.attributes.acf_errors'), $sidebar = this.controller.$el.find('.media-frame-content .media-sidebar'); // remove previous error $sidebar.children('.acf-selection-error').remove(); // show attachment details $sidebar.children().removeClass('acf-hidden'); // add message if( frame && errors ) { // vars var filename = acf.maybe_get(this, 'model.attributes.filename', ''); // hide attachment details // Gallery field continues to show previously selected attachment... $sidebar.children().addClass('acf-hidden'); // append message $sidebar.prepend([ '
', '' + acf._e('restricted') +'', '' + filename + '', '' + errors + '', '
' ].join('')); // reset selection (unselects all attachments) selection.reset(); // set single (attachment displayed in sidebar) selection.single( model ); // return and prevent 'select' form being fired return; } // return AttachmentLibrary.prototype.toggleSelection.apply( this, arguments ); } }); }, customize_AttachmentFiltersAll: function(){ // add function refresh wp.media.view.AttachmentFilters.All.prototype.refresh = function(){ // Build `' ).val( value ).html( filter.text )[0], priority: filter.priority || 50 }; }, this ).sortBy('priority').pluck('el').value() ); }; }, customize_AttachmentCompat: function(){ // vars var AttachmentCompat = wp.media.view.AttachmentCompat; // extend wp.media.view.AttachmentCompat = AttachmentCompat.extend({ add_acf_expand_button: function(){ // vars var $el = this.$el.closest('.media-modal'); // does button already exist? if( $el.find('.media-frame-router .acf-expand-details').exists() ) return; // create button var $a = $([ '', '' + acf._e('expand_details') + '', '' + acf._e('collapse_details') + '', '' ].join('')); // add events $a.on('click', function( e ){ e.preventDefault(); if( $el.hasClass('acf-expanded') ) { $el.removeClass('acf-expanded'); } else { $el.addClass('acf-expanded'); } }); // append $el.find('.media-frame-router').append( $a ); }, render: function() { // validate if( this.ignore_render ) return this; // reference var self = this; // add expand button setTimeout(function(){ self.add_acf_expand_button(); }, 0); // setup fields // The clearTimout is needed to prevent many setup functions from running at the same time clearTimeout( acf.media.render_timout ); acf.media.render_timout = setTimeout(function(){ acf.do_action('append', self.$el); }, 50); // return return AttachmentCompat.prototype.render.apply( this, arguments ); }, dispose: function() { // remove acf.do_action('remove', this.$el); // return return AttachmentCompat.prototype.dispose.apply( this, arguments ); }, save: function( e ) { if( e ) { e.preventDefault(); } // serialize form var data = acf.serialize(this.$el); // ignore render this.ignore_render = true; // save this.model.saveCompat( data ); } }); } }); })(jQuery); (function($){ acf.fields.oembed = acf.field.extend({ type: 'oembed', $el: null, events: { 'click [data-name="search-button"]': '_search', 'click [data-name="clear-button"]': '_clear', 'click [data-name="value-title"]': '_edit', 'keypress [data-name="search-input"]': '_keypress', 'keyup [data-name="search-input"]': '_keyup', 'blur [data-name="search-input"]': '_blur' }, /* * focus * * This function will setup variables when focused on a field * * @type function * @date 12/04/2016 * @since 5.3.8 * * @param n/a * @return n/a */ focus: function(){ // vars this.$el = this.$field.find('.acf-oembed'); this.$search = this.$el.find('[data-name="search-input"]'); this.$input = this.$el.find('[data-name="value-input"]'); this.$title = this.$el.find('[data-name="value-title"]'); this.$embed = this.$el.find('[data-name="value-embed"]'); // options this.o = acf.get_data( this.$el ); }, /* * maybe_search * * description * * @type function * @date 14/10/16 * @since 5.4.0 * * @param $post_id (int) * @return $post_id (int) */ maybe_search: function(){ // set url and focus var old_url = this.$input.val(), new_url = this.$search.val(); // bail early if no value if( !new_url ) { this.clear(); return; } // bail early if no change if( new_url == old_url ) return; // search this.search(); }, /* * search * * This function will search for an oembed * * @type function * @date 13/10/16 * @since 5.4.0 * * @param n/a * @return n/a */ search: function(){ // vars var s = this.$search.val(); // fix missing 'http://' - causes the oembed code to error and fail if( s.substr(0, 4) != 'http' ) { s = 'http://' + s; this.$search.val( s ); } // show loading this.$el.addClass('is-loading'); // AJAX data var ajax_data = acf.prepare_for_ajax({ 'action' : 'acf/fields/oembed/search', 's' : s, 'field_key' : this.$field.data('key') }); // abort XHR if this field is already loading AJAX data if( this.$el.data('xhr') ) this.$el.data('xhr').abort(); // get HTML var xhr = $.ajax({ url: acf.get('ajaxurl'), data: ajax_data, type: 'post', dataType: 'json', context: this, success: this.search_success }); // update el data this.$el.data('xhr', xhr); }, search_success: function( json ){ // vars var s = this.$search.val(); // remove loading this.$el.removeClass('is-loading'); // error if( !json || !json.html ) { this.$el.removeClass('has-value').addClass('has-error'); return; } // add classes this.$el.removeClass('has-error').addClass('has-value'); // update vars this.$input.val( s ); this.$title.html( s ); this.$embed.html( json.html ); }, clear: function(){ // update class this.$el.removeClass('has-error has-value'); // clear search this.$el.find('[data-name="search-input"]').val(''); // clear inputs this.$input.val(''); this.$title.html(''); this.$embed.html(''); }, edit: function(){ // add class this.$el.addClass('is-editing'); // set url and focus this.$search.val( this.$title.text() ).focus(); }, blur: function( $el ){ // remove class this.$el.removeClass('is-editing'); // maybe search this.maybe_search(); }, _search: function( e ){ // console.log('_search'); this.search(); }, _clear: function( e ){ // console.log('_clear'); this.clear(); }, _edit: function( e ){ // console.log('_clear'); this.edit(); }, _keypress: function( e ){ // console.log('_keypress'); // don't submit form if( e.which == 13 ) e.preventDefault(); }, _keyup: function( e ){ //console.log('_keypress', e.which); // bail early if no value if( !this.$search.val() ) return; // maybe search this.maybe_search(); }, _blur: function( e ){ // console.log('_blur'); this.blur(); } }); })(jQuery); (function($){ acf.fields.radio = acf.field.extend({ type: 'radio', $ul: null, actions: { 'ready': 'initialize', 'append': 'initialize' }, events: { 'click input[type="radio"]': 'click' }, focus: function(){ // focus on $select this.$ul = this.$field.find('.acf-radio-list'); // get options this.o = acf.get_data( this.$ul ); }, /* * initialize * * This function will fix a bug found in Chrome. * A radio input (for a given name) may only have 1 selected value. When used within a fc layout * multiple times (clone field), the selected value (default value) will not be checked. * This simply re-checks it. * * @type function * @date 30/08/2016 * @since 5.4.0 * * @param $post_id (int) * @return $post_id (int) */ initialize: function(){ // find selected input and check it this.$ul.find('.selected input').prop('checked', true); }, click: function(e){ // vars var $radio = e.$el, $label = $radio.parent('label'), selected = $label.hasClass('selected'), val = $radio.val(); // remove previous selected this.$ul.find('.selected').removeClass('selected'); // add active class $label.addClass('selected'); // allow null if( this.o.allow_null && selected ) { // unselect e.$el.prop('checked', false); $label.removeClass('selected'); val = false; // trigger change e.$el.trigger('change'); } // other if( this.o.other_choice ) { // vars var $other = this.$ul.find('input[type="text"]'); // show if( val === 'other' ) { $other.prop('disabled', false).attr('name', $radio.attr('name')); // hide } else { $other.prop('disabled', true).attr('name', ''); } } } }); })(jQuery); (function($){ acf.fields.relationship = acf.field.extend({ type: 'relationship', $el: null, $input: null, $filters: null, $choices: null, $values: null, actions: { 'ready': 'initialize', 'append': 'initialize' }, events: { 'keypress [data-filter]': 'submit_filter', 'change [data-filter]': 'change_filter', 'keyup [data-filter]': 'change_filter', 'click .choices .acf-rel-item': 'add_item', 'click [data-name="remove_item"]': 'remove_item' }, focus: function(){ // get elements this.$el = this.$field.find('.acf-relationship'); this.$input = this.$el.children('input[type="hidden"]'); this.$choices = this.$el.find('.choices'), this.$values = this.$el.find('.values'); // get options this.o = acf.get_data( this.$el ); }, initialize: function(){ // reference var self = this, $field = this.$field, $el = this.$el, $input = this.$input; // right sortable this.$values.children('.list').sortable({ items: 'li', forceHelperSize: true, forcePlaceholderSize: true, scroll: true, update: function(){ $input.trigger('change'); } }); this.$choices.children('.list').scrollTop(0).on('scroll', function(e){ // bail early if no more results if( $el.hasClass('is-loading') || $el.hasClass('is-empty') ) { return; } // Scrolled to bottom if( Math.ceil( $(this).scrollTop() ) + $(this).innerHeight() >= $(this).get(0).scrollHeight ) { // get paged var paged = $el.data('paged') || 1; // update paged $el.data('paged', (paged+1) ); // fetch self.set('$field', $field).fetch(); } }); /* // scroll event var maybe_fetch = function( e ){ console.log('scroll'); // remove listener $(window).off('scroll', maybe_fetch); // is field in view if( acf.is_in_view($field) ) { // fetch self.doFocus($field); self.fetch(); // return return; } // add listener setTimeout(function(){ $(window).on('scroll', maybe_fetch); }, 500); }; */ // fetch this.fetch(); }, /* show: function(){ console.log('show field: %o', this.o.xhr); // bail ealry if already loaded if( typeof this.o.xhr !== 'undefined' ) { return; } // is field in view if( acf.is_in_view(this.$field) ) { // fetch this.fetch(); } }, */ maybe_fetch: function(){ // reference var self = this, $field = this.$field; // abort timeout if( this.o.timeout ) { clearTimeout( this.o.timeout ); } // fetch var timeout = setTimeout(function(){ self.doFocus($field); self.fetch(); }, 300); this.$el.data('timeout', timeout); }, fetch: function(){ // reference var self = this, $field = this.$field; // add class this.$el.addClass('is-loading'); // abort XHR if this field is already loading AJAX data if( this.o.xhr ) { this.o.xhr.abort(); this.o.xhr = false; } // add to this.o this.o.action = 'acf/fields/relationship/query'; this.o.field_key = $field.data('key'); this.o.post_id = acf.get('post_id'); // ready for ajax var ajax_data = acf.prepare_for_ajax( this.o ); // clear html if is new query if( ajax_data.paged == 1 ) { this.$choices.children('.list').html('') } // add message this.$choices.find('ul:last').append('

' + acf._e('relationship', 'loading') + '

'); // get results var xhr = $.ajax({ url: acf.get('ajaxurl'), dataType: 'json', type: 'post', data: ajax_data, success: function( json ){ self.set('$field', $field).render( json ); } }); // update el data this.$el.data('xhr', xhr); }, render: function( json ){ // remove loading class this.$el.removeClass('is-loading is-empty'); // remove p tag this.$choices.find('p').remove(); // no results? if( !json || !json.results || !json.results.length ) { // add class this.$el.addClass('is-empty'); // add message if( this.o.paged == 1 ) { this.$choices.children('.list').append('

' + acf._e('relationship', 'empty') + '

'); } // return return; } // get new results var $new = $( this.walker(json.results) ); // apply .disabled to left li's this.$values.find('.acf-rel-item').each(function(){ $new.find('.acf-rel-item[data-id="' + $(this).data('id') + '"]').addClass('disabled'); }); // underline search match // consider removing due to bug where matched strings within HTML attributes caused incorrect results // Looks like Select2 v4 has moved away from highlighting results, so perhaps we should too if( this.o.s ) { // vars var s = this.o.s; // allow special characters to be used within regex s = acf.addslashes(s); // loop $new.find('.acf-rel-item').each(function(){ // vars var find = $(this).text(), replace = find.replace( new RegExp('(' + s + ')', 'gi'), '$1'); $(this).html( $(this).html().replace(find, replace) ); }); } // append this.$choices.children('.list').append( $new ); // merge together groups var label = '', $list = null; this.$choices.find('.acf-rel-label').each(function(){ if( $(this).text() == label ) { $list.append( $(this).siblings('ul').html() ); $(this).parent().remove(); return; } // update vars label = $(this).text(); $list = $(this).siblings('ul'); }); }, walker: function( data ){ // vars var s = ''; // loop through data if( $.isArray(data) ) { for( var k in data ) { s += this.walker( data[ k ] ); } } else if( $.isPlainObject(data) ) { // optgroup if( data.children !== undefined ) { s += '
  • ' + data.text + '
  • '; } else { s += '
  • ' + data.text + '
  • '; } } // return return s; }, submit_filter: function( e ){ // don't submit form if( e.which == 13 ) { e.preventDefault(); } }, change_filter: function( e ){ // vars var val = e.$el.val(), filter = e.$el.data('filter'); // Bail early if filter has not changed if( this.$el.data(filter) == val ) { return; } // update attr this.$el.data(filter, val); // reset paged this.$el.data('paged', 1); // fetch if( e.$el.is('select') ) { this.fetch(); // search must go through timeout } else { this.maybe_fetch(); } }, add_item: function( e ){ // max posts if( this.o.max > 0 ) { if( this.$values.find('.acf-rel-item').length >= this.o.max ) { alert( acf._e('relationship', 'max').replace('{max}', this.o.max) ); return; } } // can be added? if( e.$el.hasClass('disabled') ) { return false; } // disable e.$el.addClass('disabled'); // template var html = [ '
  • ', '', '' + e.$el.html(), '', '', '
  • '].join(''); // add new li this.$values.children('.list').append( html ) // trigger change on new_li this.$input.trigger('change'); // validation acf.validation.remove_error( this.$field ); }, remove_item : function( e ){ // vars var $span = e.$el.parent(), id = $span.data('id'); // remove $span.parent('li').remove(); // show this.$choices.find('.acf-rel-item[data-id="' + id + '"]').removeClass('disabled'); // trigger change on new_li this.$input.trigger('change'); } }); })(jQuery); (function($){ // globals var _select2, _select23, _select24; /* * acf.select2 * * all logic to create select2 instances * * @type function * @date 16/12/2015 * @since 5.3.2 * * @param n/a * @return n/a */ _select2 = acf.select2 = acf.model.extend({ // vars version: 0, version3: null, version4: null, // actions actions: { 'ready 1': 'ready' }, /* * ready * * This function will run on document ready * * @type function * @date 21/06/2016 * @since 5.3.8 * * @param n/a * @return n/a */ ready: function(){ // determine Select2 version this.version = this.get_version(); // ready this.do_function('ready'); }, /* * get_version * * This function will return the Select2 version * * @type function * @date 29/4/17 * @since 5.5.13 * * @param n/a * @return n/a */ get_version: function(){ if( acf.maybe_get(window, 'Select2') ) return 3; if( acf.maybe_get(window, 'jQuery.fn.select2.amd') ) return 4; return 0; }, /* * do_function * * This function will call the v3 or v4 equivelant function * * @type function * @date 28/4/17 * @since 5.5.13 * * @param name (string) * @param args (array) * @return (mixed) */ do_function: function( name, args ){ // defaults args = args || []; // vars var model = 'version'+this.version; // bail early if not set if( typeof this[model] === 'undefined' || typeof this[model][name] === 'undefined' ) return false; // run return this[model][name].apply( this, args ); }, /* * get_data * * This function will look at a $select element and return an object choices * * @type function * @date 24/12/2015 * @since 5.3.2 * * @param $select (jQuery) * @return (array) */ get_data: function( $select, data ){ // reference var self = this; // defaults data = data || []; // loop over children $select.children().each(function(){ // vars var $el = $(this); // optgroup if( $el.is('optgroup') ) { data.push({ 'text': $el.attr('label'), 'children': self.get_data( $el ) }); // option } else { data.push({ 'id': $el.attr('value'), 'text': $el.text() }); } }); // return return data; }, /* * decode_data * * This function will take an array of choices and decode the text * Changes '&' to '&' which fixes a bug (in Select2 v3 )when searching for '&' * * @type function * @date 24/12/2015 * @since 5.3.2 * * @param $select (jQuery) * @return (array) */ decode_data: function( data ) { // bail ealry if no data if( !data ) return []; //loop $.each(data, function(k, v){ // text data[ k ].text = acf.decode( v.text ); // children if( typeof v.children !== 'undefined' ) { data[ k ].children = _select2.decode_data(v.children); } }); // return return data; }, /* * count_data * * This function will take an array of choices and return the count * * @type function * @date 24/12/2015 * @since 5.3.2 * * @param data (array) * @return (int) */ count_data: function( data ) { // vars var i = 0; // bail ealry if no data if( !data ) return i; //loop $.each(data, function(k, v){ // increase i++; // children if( typeof v.children !== 'undefined' ) { i += v.children.length; } }); // return return i; }, /* * get_ajax_data * * This function will return an array of data to send via AJAX * * @type function * @date 19/07/2016 * @since 5.4.0 * * @param $post_id (int) * @return $post_id (int) */ get_ajax_data: function( args, params, $el, $field ){ // vars var data = acf.prepare_for_ajax({ action: args.ajax_action, field_key: args.key, s: params.term || '', paged: params.page || 1 }); // filter data = acf.apply_filters( 'select2_ajax_data', data, args, $el, $field ); // return return data; }, /* * get_ajax_results * * This function will return a valid AJAX response * * @type function * @date 19/07/2016 * @since 5.4.0 * * @param $post_id (int) * @return $post_id (int) */ get_ajax_results: function( data, params ){ // vars var valid = { results: [] }; // bail early if no data if( !data ) { data = valid; } // allow for an array of choices if( typeof data.results == 'undefined' ) { valid.results = data; data = valid; } // decode data.results = this.decode_data(data.results); // filter data = acf.apply_filters( 'select2_ajax_results', data, params ); // return return data; }, /* * get_value * * This function will return the selected options in a Select2 format * * @type function * @date 5/01/2016 * @since 5.3.2 * * @param $post_id (int) * @return $post_id (int) */ get_value: function( $select ){ // vars var val = [], $selected = $select.find('option:selected'); // bail early if no selected if( !$selected.exists() ) return val; // sort $selected = $selected.sort(function(a, b) { return +a.getAttribute('data-i') - +b.getAttribute('data-i'); }); // loop $selected.each(function(){ // vars var $el = $(this); // append val.push({ 'id': $el.attr('value'), 'text': $el.text(), '$el': $el }); }); // return return val; }, /* * get_input_value * * This function will return an array of values as per the hidden input * * @type function * @date 29/4/17 * @since 5.5.13 * * @param $input (jQuery) * @return (array) */ get_input_value: function( $input ) { return $input.val().split('||'); }, /* * sync_input_value * * This function will save the current selected values into the hidden input * * @type function * @date 29/4/17 * @since 5.5.13 * * @param $input (jQuery) * @param $select (jQuery) * @return n/a */ sync_input_value: function( $input, $select ) { $input.val( $select.val().join('||') ); }, /* * add_option * * This function will add an '); } }, /* * select_option * * This function will select an option * * @type function * @date 29/4/17 * @since 5.5.13 * * @param $select (jQuery) * @param value (string) * @return n/a */ select_option: function( $select, value ){ $select.find('option[value="'+value+'"]').prop('selected', true); $select.trigger('change'); }, /* * unselect_option * * This function will unselect an option * * @type function * @date 29/4/17 * @since 5.5.13 * * @param $select (jQuery) * @param value (string) * @return n/a */ unselect_option: function( $select, value ){ $select.find('option[value="'+value+'"]').prop('selected', false); $select.trigger('change'); }, /* * Select2 v3 or v4 functions * * description * * @type function * @date 29/4/17 * @since 5.5.10 * * @param $post_id (int) * @return $post_id (int) */ init: function( $select, args, $field ){ this.do_function( 'init', arguments ); }, destroy: function( $select ){ this.do_function( 'destroy', arguments ); }, add_value: function( $select, value, label ){ this.do_function( 'add_value', arguments ); }, remove_value: function( $select, value ){ this.do_function( 'remove_value', arguments ); }, remove_value: function( $select, value ){ this.do_function( 'remove_value', arguments ); } }); /* * Select2 v3 * * This model contains the Select2 v3 functions * * @type function * @date 28/4/17 * @since 5.5.10 * * @param n/a * @return n/a */ _select23 = _select2.version3 = { ready: function(){ // vars var locale = acf.get('locale'), rtl = acf.get('rtl') l10n = acf._e('select'); // bail ealry if no l10n if( !l10n ) return; // vars var l10n_functions = { formatMatches: function( matches ) { if ( 1 === matches ) { return l10n.matches_1; } return l10n.matches_n.replace('%d', matches); }, formatNoMatches: function() { return l10n.matches_0; }, formatAjaxError: function() { return l10n.load_fail; }, formatInputTooShort: function( input, min ) { var number = min - input.length; if ( 1 === number ) { return l10n.input_too_short_1; } return l10n.input_too_short_n.replace( '%d', number ); }, formatInputTooLong: function( input, max ) { var number = input.length - max; if ( 1 === number ) { return l10n.input_too_long_1; } return l10n.input_too_long_n.replace( '%d', number ); }, formatSelectionTooBig: function( limit ) { if ( 1 === limit ) { return l10n.selection_too_long_1; } return l10n.selection_too_long_n.replace( '%d', limit ); }, formatLoadMore: function() { return l10n.load_more; }, formatSearching: function() { return l10n.searching; } }; // ensure locales exists // older versions of Select2 did not have a locale storage $.fn.select2.locales = acf.maybe_get(window, 'jQuery.fn.select2.locales', {}); // append $.fn.select2.locales[ locale ] = l10n_functions; $.extend($.fn.select2.defaults, l10n_functions); }, set_data: function( $select, data ){ // v3 if( this.version == 3 ) { $select = $select.siblings('input'); } // set data $select.select2('data', data); }, append_data: function( $select, data ){ // v3 if( this.version == 3 ) { $select = $select.siblings('input'); } // vars var current = $select.select2('data') || []; // append current.push( data ); // set data $select.select2('data', current); }, /* * init_v3 * * This function will create a new Select2 for v3 * * @type function * @date 24/12/2015 * @since 5.3.2 * * @param $select (jQuery) * @return args (object) */ init: function( $select, args, $field ){ // defaults args = args || {}; $field = $field || null; // merge args = $.extend({ allow_null: false, placeholder: '', multiple: false, ajax: false, ajax_action: '' }, args); // vars var $input = $select.siblings('input'); // bail early if no input if( !$input.exists() ) return; // select2 args var select2_args = { width: '100%', containerCssClass: '-acf', allowClear: args.allow_null, placeholder: args.placeholder, multiple: args.multiple, separator: '||', data: [], escapeMarkup: function( m ){ return m; }, formatResult: function( result, container, query, escapeMarkup ){ // run default formatResult var text = $.fn.select2.defaults.formatResult( result, container, query, escapeMarkup ); // append description if( result.description ) { text += ' ' + result.description + ''; } // return return text; } }; // value var value = this.get_value( $select ); // multiple if( args.multiple ) { // vars var name = $select.attr('name'); // add hidden input to each multiple selection select2_args.formatSelection = function( object, $div ){ // vars var html = ''; // append input $div.parent().append(html); // return return object.text; } } else { // change array to single object value = acf.maybe_get(value, 0, false); // if no allow_null, this single select must contain a selection if( !args.allow_null && value ) { $input.val( value.id ); } } // remove the blank option as we have a clear all button! if( args.allow_null ) { $select.find('option[value=""]').remove(); } // get data select2_args.data = this.get_data( $select ); // initial selection select2_args.initSelection = function( element, callback ) { callback( value ); }; // ajax if( args.ajax ) { select2_args.ajax = { url: acf.get('ajaxurl'), dataType: 'json', type: 'post', cache: false, quietMillis: 250, data: function( term, page ) { // vars var params = { 'term': term, 'page': page }; // return return _select2.get_ajax_data(args, params, $input, $field); }, results: function( data, page ){ // vars var params = { 'page': page }; // merge together groups setTimeout(function(){ _select23.merge_results(); }, 1); // return return _select2.get_ajax_results(data, params); } }; } // attachment z-index fix select2_args.dropdownCss = { 'z-index' : '999999999' }; // append args select2_args.acf = args; // filter for 3rd party customization select2_args = acf.apply_filters( 'select2_args', select2_args, $select, args, $field ); // add select2 $input.select2( select2_args ); // vars var $container = $input.select2('container'); // reorder DOM // - this order is very important so don't change it // - $select goes first so the input can override it. Fixes issue where conditional logic will enable the select // - $input goes second to reset the input data // - container goes last to allow multiple hidden inputs to override $input $container.before( $select ); $container.before( $input ); // multiple if( args.multiple ) { // sortable $container.find('ul.select2-choices').sortable({ start: function() { $input.select2("onSortStart"); }, stop: function() { $input.select2("onSortEnd"); } }); } // disbale select $select.prop('disabled', true).addClass('acf-disabled acf-hidden'); // update select value // this fixes a bug where select2 appears blank after duplicating a post_object field (field settings). // the $select is disabled, so setting the value won't cause any issues (this is what select2 v4 does anyway). $input.on('change', function(e) { // add new data if( e.added ) { // add item _select2.add_option($select, e.added.id, e.added.text); } // select _select2.select_option($select, e.val); }); // action for 3rd party customization acf.do_action('select2_init', $input, select2_args, args, $field); }, /* * merge_results_v3 * * description * * @type function * @date 20/07/2016 * @since 5.4.0 * * @param $post_id (int) * @return $post_id (int) */ merge_results: function(){ // vars var label = '', $list = null; // loop $('#select2-drop .select2-result-with-children').each(function(){ // vars var $label = $(this).children('.select2-result-label'), $ul = $(this).children('.select2-result-sub'); // append group to previous if( $label.text() == label ) { $list.append( $ul.children() ); $(this).remove(); return; } // update vars label = $label.text(); $list = $ul; }); }, /* * destroy * * This function will destroy a Select2 * * @type function * @date 24/12/2015 * @since 5.3.2 * * @param $post_id (int) * @return $post_id (int) */ destroy: function( $select ){ // vars var $input = $select.siblings('input'); // destroy via api if( $input.data('select2') ) { $input.select2('destroy'); } // destory via HTML (duplicating HTML deos not contain data) $select.siblings('.select2-container').remove(); // enable select $select.prop('disabled', false).removeClass('acf-disabled acf-hidden'); $input.attr('style', ''); // fixes bug causing hidden select2 element }, add_value: function( $select, value, label ){ // add and select item _select2.add_option($select, value, label); _select2.select_option($select, value); // vars var $input = $select.siblings('input'); // new item var item = { 'id': value, 'text': label }; // single if( !$select.data('multiple') ) { return $input.select2('data', item); } // get existing value var values = $input.select2('data') || []; // append values.push(item); // set data return $input.select2('data', values); }, remove_value: function( $select, value ){ // unselect option _select2.unselect_option($select, value); // vars var $input = $select.siblings('input'), current = $input.select2('data'); // single if( !$select.data('multiple') ) { if( current && current.id == value ) { $input.select2('data', null); } // multiple } else { // filter current = $.grep(current, function( item ) { return item.id != value; }); // set data $input.select2('data', current); } } }; /* * Select2 v4 * * This model contains the Select2 v4 functions * * @type function * @date 28/4/17 * @since 5.5.10 * * @param n/a * @return n/a */ _select24 = _select2.version4 = { init: function( $select, args, $field ){ // defaults args = args || {}; $field = $field || null; // merge args = $.extend({ allow_null: false, placeholder: '', multiple: false, ajax: false, ajax_action: '' }, args); // vars var $input = $select.siblings('input'); // bail early if no input if( !$input.exists() ) return; // select2 args var select2_args = { width: '100%', allowClear: args.allow_null, placeholder: args.placeholder, multiple: args.multiple, separator: '||', data: [], escapeMarkup: function( m ){ return m; } }; // value var value = this.get_value( $select ); // multiple if( args.multiple ) { // reorder opts $.each(value, function( k, item ){ // detach and re-append to end item.$el.detach().appendTo( $select ); }); } else { // change array to single object value = acf.maybe_get(value, 0, ''); } /* // removed - Select2 does not show this value by default! // remove the blank option as we have a clear all button! if( args.allow_null ) { $select.find('option[value=""]').remove(); } */ // remove conflicting atts if( !args.ajax ) { $select.removeData('ajax'); $select.removeAttr('data-ajax'); } else { select2_args.ajax = { url: acf.get('ajaxurl'), delay: 250, dataType: 'json', type: 'post', cache: false, data: function( params ) { // return return _select2.get_ajax_data(args, params, $select, $field); }, processResults: function( data, params ){ // vars var results = _select2.get_ajax_results(data, params); // change to more if( results.more ) { results.pagination = { more: true }; } // merge together groups setTimeout(function(){ _select24.merge_results(); }, 1); // return return results } }; } // filter for 3rd party customization select2_args = acf.apply_filters( 'select2_args', select2_args, $select, args, $field ); // add select2 $select.select2( select2_args ); // get container (Select2 v4 deos not return this from constructor) var $container = $select.next('.select2-container'); // reorder DOM // - no need to reorder, the select field is needed to $_POST values // multiple if( args.multiple ) { // vars var $ul = $container.find('ul'); // sortable $ul.sortable({ stop: function( e ) { $ul.find('.select2-selection__choice').each(function() { // vars var $option = $( $(this).data('data').element ); // detach and re-append to end $option.detach().appendTo( $select ); // trigger change on input (JS error if trigger on select) $input.trigger('change'); // update input //_select2.sync_input_value( $input, $select ); }); } }); // on select, move to end $select.on('select2:select', function( e ){ // vars var $option = $(e.params.data.element); // detach and re-append to end $option.detach().appendTo( $select ); // trigger change //$select.trigger('change'); }); } /* // update input $select.on('select2:select', function( e ){ // update input _select2.sync_input_value( $input, $select ); }); $select.on('select2:unselect', function( e ){ // update input _select2.sync_input_value( $input, $select ); }); */ // clear value (allows null to be saved) $input.val(''); // add class $container.addClass('-acf'); // action for 3rd party customization acf.do_action('select2_init', $select, select2_args, args, $field); }, /* * merge_results_v4 * * description * * @type function * @date 20/07/2016 * @since 5.4.0 * * @param $post_id (int) * @return $post_id (int) */ merge_results: function(){ // vars var $prev_options = null, $prev_group = null; // loop $('.select2-results__option[role="group"]').each(function(){ // vars var $options = $(this).children('ul'), $group = $(this).children('strong'); // compare to previous if( $prev_group !== null && $group.text() == $prev_group.text() ) { $prev_options.append( $options.children() ); $(this).remove(); return; } // update vars $prev_options = $options; $prev_group = $group; }); }, add_value: function( $select, value, label ){ // add and select item _select2.add_option($select, value, label); _select2.select_option($select, value); }, remove_value: function( $select, value ){ // unselect _select2.unselect_option($select, value); }, destroy: function( $select ){ // destroy via api if( $select.data('select2') ) { $select.select2('destroy'); } // destory via HTML (duplicating HTML deos not contain data) $select.siblings('.select2-container').remove(); } }; /* * depreciated * * These functions have moved since v5.3.3 * * @type function * @date 11/12/2015 * @since 5.3.2 * * @param n/a * @return n/a */ acf.add_select2 = function( $select, args ) { _select2.init( $select, args ); } acf.remove_select2 = function( $select ) { _select2.destroy( $select ); } })(jQuery); (function($){ // select acf.fields.select = acf.field.extend({ type: 'select', $select: null, actions: { 'ready': 'render', 'append': 'render', 'remove': 'remove' }, focus: function(){ // focus on $select this.$select = this.$field.find('select'); // bail early if no select field if( !this.$select.exists() ) return; // get options this.o = acf.get_data( this.$select ); // customize o this.o = acf.parse_args(this.o, { 'ajax_action': 'acf/fields/'+this.type+'/query', 'key': this.$field.data('key') }); }, render: function(){ // validate ui if( !this.$select.exists() || !this.o.ui ) { return false; } acf.select2.init( this.$select, this.o, this.$field ); }, remove: function(){ // validate ui if( !this.$select.exists() || !this.o.ui ) { return false; } // remove select2 acf.select2.destroy( this.$select ); } }); // user acf.fields.user = acf.fields.select.extend({ type: 'user' }); // post_object acf.fields.post_object = acf.fields.select.extend({ type: 'post_object' }); // page_link acf.fields.page_link = acf.fields.select.extend({ type: 'page_link' }); })(jQuery); (function($){ acf.fields.tab = acf.field.extend({ type: 'tab', $el: null, $wrap: null, actions: { 'prepare': 'initialize', 'append': 'initialize', 'hide': 'hide', 'show': 'show' }, focus: function(){ // get elements this.$el = this.$field.find('.acf-tab'); // get options this.o = this.$el.data(); this.o.key = this.$field.data('key'); this.o.text = this.$el.html(); }, initialize: function(){ // bail early if is td if( this.$field.is('td') ) return; // add tab tab_manager.add_tab( this.$field, this.o ); }, hide: function( $field, context ){ // bail early if not conditional logic if( context != 'conditional_logic' ) return; // vars var key = $field.data('key'), $group = $field.prevAll('.acf-tab-wrap'), $a = $group.find('a[data-key="' + key + '"]'), $li = $a.parent(); // bail early if $group does not exist (clone field) if( !$group.exists() ) return; // hide li $li.addClass('hidden-by-conditional-logic'); // set timout to allow proceeding fields to hide first // without this, the tab field will hide all fields, regarless of if that field has it's own conditional logic rules setTimeout(function(){ // if this tab field was hidden by conditional_logic, disable it's children to prevent validation $field.nextUntil('.acf-field-tab', '.acf-field').each(function(){ // bail ealry if already hidden if( $(this).hasClass('hidden-by-conditional-logic') ) return; // hide field acf.conditional_logic.hide_field( $(this) ); // add parent reference $(this).addClass('-hbcl-' + key); }); // select other tab if active if( $li.hasClass('active') ) { $group.find('li:not(.hidden-by-conditional-logic):first a').trigger('click'); } }, 0); }, show: function( $field, context ){ // bail early if not conditional logic if( context != 'conditional_logic' ) return; // vars var key = $field.data('key'), $group = $field.prevAll('.acf-tab-wrap'), $a = $group.find('a[data-key="' + key + '"]'), $li = $a.parent(); // bail early if $group does not exist (clone field) if( !$group.exists() ) return; // show li $li.removeClass('hidden-by-conditional-logic'); // set timout to allow proceeding fields to hide first // without this, the tab field will hide all fields, regarless of if that field has it's own conditional logic rules setTimeout(function(){ // if this tab field was shown by conditional_logic, enable it's children to allow validation $field.siblings('.acf-field.-hbcl-' + key).each(function(){ // show field acf.conditional_logic.show_field( $(this) ); // remove parent reference $(this).removeClass('-hbcl-' + key); }); // select tab if no other active var $active = $li.siblings('.active'); if( !$active.exists() || $active.hasClass('hidden-by-conditional-logic') ) { $a.trigger('click'); } }, 0); } }); /* * tab_manager * * This model will handle adding tabs and groups * * @type function * @date 25/11/2015 * @since 5.3.2 * * @param $post_id (int) * @return $post_id (int) */ var tab_manager = acf.model.extend({ actions: { 'prepare 15': 'render', 'append 15': 'render', 'refresh 15': 'render' }, events: { 'click .acf-tab-button': '_click' }, render: function( $el ){ // find visible tab wraps $('.acf-tab-wrap', $el).each(function(){ // vars var $group = $(this), $wrap = $group.parent(); // trigger click if( !$group.find('li.active').exists() ) { $group.find('li:not(.hidden-by-conditional-logic):first a').trigger('click'); } if( $wrap.hasClass('-sidebar') ) { // vars var attribute = $wrap.is('td') ? 'height' : 'min-height'; // find height (minus 1 for border-bottom) var height = $group.position().top + $group.children('ul').outerHeight(true) - 1; // add css $wrap.css(attribute, height); } }); }, add_group: function( $field, settings ){ // vars var $wrap = $field.parent(), html = ''; // add sidebar to wrap if( $wrap.hasClass('acf-fields') && settings.placement == 'left' ) { $wrap.addClass('-sidebar'); // can't have side tab without sidebar } else { settings.placement = 'top'; } // generate html if( $wrap.is('tbody') ) { html = ''; } else { html = '
    '; } // save $group = $(html); // append $field.before( $group ); // return return $group; }, add_tab: function( $field, settings ){ //console.log('add_tab(%o, %o)', $field, settings); // vars var $group = $field.siblings('.acf-tab-wrap').last(); // add tab group if no group exists if( !$group.exists() ) { $group = this.add_group( $field, settings ); // add tab group if is endpoint } else if( settings.endpoint ) { $group = this.add_group( $field, settings ); } // vars var $li = $('
  • ' + settings.text + '
  • '); // hide li if( settings.text === '' ) $li.hide(); // add tab $group.find('ul').append( $li ); // conditional logic if( $field.hasClass('hidden-by-conditional-logic') ) { $li.addClass('hidden-by-conditional-logic'); } }, _click: function( e ){ // prevent default e.preventDefault(); // reference var self = this; // vars var $a = e.$el, $group = $a.closest('.acf-tab-wrap'), show = $a.data('key'), current = ''; // add and remove classes $a.parent().addClass('active').siblings().removeClass('active'); // loop over all fields until you hit another group $group.nextUntil('.acf-tab-wrap', '.acf-field').each(function(){ // vars var $field = $(this); // set current if( $field.data('type') == 'tab' ) { current = $field.data('key'); // bail early if endpoint is found if( $field.hasClass('endpoint') ) { // stop loop - current tab group is complete return false; } } // show if( current === show ) { // only show if hidden if( $field.hasClass('hidden-by-tab') ) { $field.removeClass('hidden-by-tab'); acf.do_action('show_field', $(this), 'tab'); } // hide } else { // only hide if not hidden if( !$field.hasClass('hidden-by-tab') ) { $field.addClass('hidden-by-tab'); acf.do_action('hide_field', $(this), 'tab'); } } }); // action for 3rd party customization acf.do_action('refresh', $group.parent() ); // blur $a.trigger('blur'); } }); /* * tab_validation * * This model will handle validation of fields within a tab group * * @type function * @date 25/11/2015 * @since 5.3.2 * * @param $post_id (int) * @return $post_id (int) */ var tab_validation = acf.model.extend({ active: 1, actions: { 'add_field_error': 'add_field_error' }, add_field_error: function( $field ){ // bail early if already focused if( !this.active ) { return; } // bail early if not hidden by tab if( !$field.hasClass('hidden-by-tab') ) { return; } // reference var self = this; // vars var $tab = $field.prevAll('.acf-field-tab:first'), $group = $field.prevAll('.acf-tab-wrap:first'); // focus $group.find('a[data-key="' + $tab.data('key') + '"]').trigger('click'); // disable functionality for 1sec (allow next validation to work) this.active = 0; setTimeout(function(){ self.active = 1; }, 1000); } }); })(jQuery); (function($){ acf.fields.time_picker = acf.field.extend({ type: 'time_picker', $el: null, $input: null, $hidden: null, o: {}, actions: { 'ready': 'initialize', 'append': 'initialize' }, events: { 'blur input[type="text"]': 'blur' }, focus: function(){ // get elements this.$el = this.$field.find('.acf-time-picker'); this.$input = this.$el.find('input[type="text"]'); this.$hidden = this.$el.find('input[type="hidden"]'); // get options this.o = acf.get_data( this.$el ); }, initialize: function(){ // bail ealry if no timepicker library if( typeof $.timepicker === 'undefined' ) return; // create options var args = { timeFormat: this.o.time_format, altField: this.$hidden, altFieldTimeOnly: false, altTimeFormat: 'HH:mm:ss', showButtonPanel: true, controlType: 'select', oneLine: true, closeText: acf._e('date_time_picker', 'selectText') }; // add custom 'Close = Select' functionality args.onClose = function( value, instance ){ // vars var $div = instance.dpDiv, $close = $div.find('.ui-datepicker-close'); // if clicking close button if( !value && $close.is(':hover') ) { // attempt to find new value value = acf.maybe_get(instance, 'settings.timepicker.formattedTime'); // bail early if no value if( !value ) return; // update value $.datepicker._setTime(instance); } }; // filter for 3rd party customization args = acf.apply_filters('time_picker_args', args, this.$field); // add date picker this.$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('
    '); } // action for 3rd party customization acf.do_action('time_picker_init', this.$input, args, this.$field); }, blur: function(){ if( !this.$input.val() ) { this.$hidden.val(''); } } }); })(jQuery); (function($){ acf.fields.true_false = acf.field.extend({ type: 'true_false', $switch: null, $input: null, actions: { 'prepare': 'render', 'append': 'render', 'show': 'render' }, events: { 'change .acf-switch-input': '_change', 'focus .acf-switch-input': '_focus', 'blur .acf-switch-input': '_blur', 'keypress .acf-switch-input': '_keypress' }, /* * focus * * This function will setup variables when focused on a field * * @type function * @date 12/04/2016 * @since 5.3.8 * * @param n/a * @return n/a */ focus: function(){ // vars this.$input = this.$field.find('.acf-switch-input'); this.$switch = this.$field.find('.acf-switch'); }, /* * render * * This function is used to setup basic upload form attributes * * @type function * @date 12/04/2016 * @since 5.3.8 * * @param n/a * @return n/a */ render: function(){ // bail ealry if no $switch if( !this.$switch.exists() ) return; // vars var $on = this.$switch.children('.acf-switch-on'), $off = this.$switch.children('.acf-switch-off') 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 ); }, /* * on * * description * * @type function * @date 10/1/17 * @since 5.5.0 * * @param $post_id (int) * @return $post_id (int) */ on: function() { //console.log('on'); this.$input.prop('checked', true); this.$switch.addClass('-on'); }, /* * off * * description * * @type function * @date 10/1/17 * @since 5.5.0 * * @param $post_id (int) * @return $post_id (int) */ off: function() { //console.log('off'); this.$input.prop('checked', false); this.$switch.removeClass('-on'); }, /* * change * * description * * @type function * @date 12/10/16 * @since 5.4.0 * * @param $post_id (int) * @return $post_id (int) */ _change: function( e ){ // vars var checked = e.$el.prop('checked'); // enable if( checked ) { this.on(); // disable } else { this.off(); } }, /* * _focus * * description * * @type function * @date 10/1/17 * @since 5.5.0 * * @param $post_id (int) * @return $post_id (int) */ _focus: function( e ){ this.$switch.addClass('-focus'); }, /* * _blur * * description * * @type function * @date 10/1/17 * @since 5.5.0 * * @param $post_id (int) * @return $post_id (int) */ _blur: function( e ){ this.$switch.removeClass('-focus'); }, /* * _keypress * * description * * @type function * @date 10/1/17 * @since 5.5.0 * * @param $post_id (int) * @return $post_id (int) */ _keypress: function( e ){ // left if( e.keyCode === 37 ) { return this.off(); } // right if( e.keyCode === 39 ) { return this.on(); } } }); })(jQuery); (function($){ // taxonomy acf.fields.taxonomy = acf.field.extend({ type: 'taxonomy', $el: null, actions: { 'ready': 'render', 'append': 'render', 'remove': 'remove' }, events: { 'click a[data-name="add"]': 'add_term' }, focus: function(){ // $el this.$el = this.$field.find('.acf-taxonomy-field'); // get options this.o = acf.get_data( this.$el ); // extra this.o.key = this.$field.data('key'); }, render: function(){ // attempt select2 var $select = this.$field.find('select'); // bail early if no select field if( !$select.exists() ) return; // select2 options var args = acf.get_data( $select ); // customize args args = acf.parse_args(args, { 'pagination': true, 'ajax_action': 'acf/fields/taxonomy/query', 'key': this.o.key }); // add select2 acf.select2.init( $select, args ); }, remove: function(){ // attempt select2 var $select = this.$field.find('select'); // validate ui if( !$select.exists() ) return false; // remove select2 acf.select2.destroy( $select ); }, add_term: function( e ){ // reference var self = this; // open popup acf.open_popup({ title: e.$el.attr('title') || e.$el.data('title'), loading: true, height: 220 }); // AJAX data var ajax_data = acf.prepare_for_ajax({ action: 'acf/fields/taxonomy/add_term', field_key: this.o.key }); // get HTML $.ajax({ url: acf.get('ajaxurl'), data: ajax_data, type: 'post', dataType: 'html', success: function(html){ self.add_term_confirm( html ); } }); }, add_term_confirm: function( html ){ // reference var self = this; // update popup acf.update_popup({ content : html }); // focus $('#acf-popup input[name="term_name"]').focus(); // events $('#acf-popup form').on('submit', function( e ){ // prevent default e.preventDefault(); // submit self.add_term_submit( $(this )); }); }, add_term_submit: function( $form ){ // reference var self = this; // vars var $submit = $form.find('.acf-submit'), $name = $form.find('input[name="term_name"]'), $parent = $form.find('select[name="term_parent"]'); // basic validation if( $name.val() === '' ) { $name.focus(); return false; } // show loading $submit.find('button').attr('disabled', 'disabled'); $submit.find('.acf-spinner').addClass('is-active'); // vars var ajax_data = acf.prepare_for_ajax({ action: 'acf/fields/taxonomy/add_term', field_key: this.o.key, term_name: $name.val(), term_parent: $parent.exists() ? $parent.val() : 0 }); // save term $.ajax({ url: acf.get('ajaxurl'), data: ajax_data, type: 'post', dataType: 'json', success: function( json ){ // vars var message = acf.get_ajax_message(json); // success if( acf.is_ajax_success(json) ) { // clear name $name.val(''); // update term lists self.append_new_term( json.data ); } // message if( message.text ) { $submit.find('span').html( message.text ); } }, complete: function(){ // reset button $submit.find('button').removeAttr('disabled'); // hide loading $submit.find('.acf-spinner').removeClass('is-active'); // remove message $submit.find('span').delay(1500).fadeOut(250, function(){ $(this).html(''); $(this).show(); }); // focus $name.focus(); } }); }, append_new_term: function( term ){ // vars var item = { id: term.term_id, text: term.term_label }; // append to all taxonomy lists $('.acf-taxonomy-field[data-taxonomy="' + this.o.taxonomy + '"]').each(function(){ // vars var type = $(this).data('type'); // bail early if not checkbox/radio if( type == 'radio' || type == 'checkbox' ) { // allow } else { return; } // vars var $hidden = $(this).children('input[type="hidden"]'), $ul = $(this).find('ul:first'), name = $hidden.attr('name'); // allow multiple selection if( type == 'checkbox' ) { name += '[]'; } // create new li var $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 = $(''); $parent.append( $ul ); } } // append $ul.append( $li ); }); // append to select $('#acf-popup #term_parent').each(function(){ // vars var $option = $(''); if( term.term_parent ) { $(this).children('option[value="' + term.term_parent + '"]').after( $option ); } else { $(this).append( $option ); } }); // set value switch( this.o.type ) { // select case 'select': //this.$el.children('input').select2('data', item); // vars var $select = this.$el.children('select'); acf.select2.add_value($select, term.term_id, term.term_label); break; case 'multi_select': /* // vars var $input = this.$el.children('input'), value = $input.select2('data') || []; // append value.push( item ); // update $input.select2('data', value); */ // vars var $select = this.$el.children('select'); acf.select2.add_value($select, term.term_id, term.term_label); break; case 'checkbox': case 'radio': // scroll to view var $holder = this.$el.find('.categorychecklist-holder'), $li = $holder.find('li[data-id="' + term.term_id + '"]'), offet = $holder.get(0).scrollTop + ( $li.offset().top - $holder.offset().top ); // check input $li.find('input').prop('checked', true); // scroll to bottom $holder.animate({scrollTop: offet}, '250'); break; } } }); })(jQuery); (function($){ acf.fields.url = acf.field.extend({ type: 'url', $input: null, actions: { 'ready': 'render', 'append': 'render' }, events: { 'keyup input[type="url"]': 'render' }, focus: function(){ this.$input = this.$field.find('input[type="url"]'); }, is_valid: function(){ // vars var val = this.$input.val(); if( val.indexOf('://') !== -1 ) { // url } else if( val.indexOf('//') === 0 ) { // protocol relative url } else { return false; } // return return true; }, render: function(){ // add class if( this.is_valid() ) { this.$input.parent().addClass('-valid'); // remove class } else { this.$input.parent().removeClass('-valid'); } } }); })(jQuery); (function($){ acf.validation = acf.model.extend({ actions: { 'ready': 'ready', 'append': 'ready' }, filters: { 'validation_complete': 'validation_complete' }, events: { 'click #save-post': 'click_ignore', 'click [type="submit"]': 'click_publish', 'submit form': 'submit_form', 'click .acf-error-message a': 'click_message' }, // vars active: 1, ignore: 0, busy: 0, valid: true, errors: [], // classes error_class: 'acf-error', message_class: 'acf-error-message', // el $trigger: null, /* * ready * * This function will add 'non bubbling' events * * @type function * @date 26/05/2015 * @since 5.2.3 * * @param $post_id (int) * @return $post_id (int) */ ready: function( $el ){ // reference $el.find('.acf-field input').filter('[type="number"], [type="email"], [type="url"]').on('invalid', function( e ){ // prvent defual // fixes chrome bug where 'hidden-by-tab' field throws focus error e.preventDefault(); // append to errors acf.validation.errors.push({ input: $(this).attr('name'), message: e.target.validationMessage }); // run validation acf.validation.fetch( $(this).closest('form') ); }); }, /* * validation_complete * * This function will modify the JSON response and add local 'invalid' errors * * @type function * @date 26/05/2015 * @since 5.2.3 * * @param $post_id (int) * @return $post_id (int) */ validation_complete: function( json, $form ) { // bail early if no local errors if( !this.errors.length ) return json; // set valid json.valid = 0; // require array json.errors = json.errors || []; // vars var inputs = []; // populate inputs if( json.errors.length ) { for( i in json.errors ) { inputs.push( json.errors[ i ].input ); } } // append if( this.errors.length ) { for( i in this.errors ) { // vars var error = this.errors[ i ]; // bail ealry if alreay exists if( $.inArray(error.input, inputs) !== -1 ) continue; // append json.errors.push( error ); } } // reset this.errors = []; // return return json; }, /* * click_message * * This function will dismiss the validation message * * @type function * @date 26/05/2015 * @since 5.2.3 * * @param $post_id (int) * @return $post_id (int) */ click_message: function( e ) { e.preventDefault(); acf.remove_el( e.$el.parent() ); }, /* * click_ignore * * This event is trigered via submit butons which ignore validation * * @type function * @date 4/05/2015 * @since 5.2.3 * * @param $post_id (int) * @return $post_id (int) */ click_ignore: function( e ) { this.ignore = 1; this.$trigger = e.$el; }, /* * click_publish * * This event is trigered via submit butons which trigger validation * * @type function * @date 4/05/2015 * @since 5.2.3 * * @param $post_id (int) * @return $post_id (int) */ click_publish: function( e ) { this.$trigger = e.$el; }, /* * submit_form * * description * * @type function * @date 4/05/2015 * @since 5.2.3 * * @param $post_id (int) * @return $post_id (int) */ submit_form: function( e ){ // bail early if not active if( !this.active ) { return true; } // ignore validation (only ignore once) if( this.ignore ) { this.ignore = 0; return true; } // bail early if this form does not contain ACF data if( !e.$el.find('#acf-form-data').exists() ) { return true; } // bail early if is preview var $preview = e.$el.find('#wp-preview'); if( $preview.exists() && $preview.val() ) { // WP will lock form, unlock it this.toggle( e.$el, 'unlock' ); return true; } // prevent default e.preventDefault(); // run validation this.fetch( e.$el ); }, /* * lock * * description * * @type function * @date 7/05/2015 * @since 5.2.3 * * @param $post_id (int) * @return $post_id (int) */ toggle: function( $form, state ){ // defaults state = state || 'unlock'; // debug //console.log('toggle %o, %o %o', this.$trigger, $form, state); // vars var $submit = null, $spinner = null, $parent = $('#submitdiv'); // 3rd party publish box if( !$parent.exists() ) { $parent = $('#submitpost'); } // term, user if( !$parent.exists() ) { $parent = $form.find('p.submit').last(); } // front end form if( !$parent.exists() ) { $parent = $form.find('.acf-form-submit'); } // default if( !$parent.exists() ) { $parent = $form; } // find elements // note: media edit page does not use .button, this is why we need to look for generic input[type="submit"] $submit = $parent.find('input[type="submit"], .button'); $spinner = $parent.find('.spinner, .acf-spinner'); // hide all spinners (hides the preview spinner) this.hide_spinner( $spinner ); // unlock if( state == 'unlock' ) { this.enable_submit( $submit ); // lock } else if( state == 'lock' ) { // show only last spinner (allow all spinners to be hidden - preview spinner + submit spinner) this.disable_submit( $submit ); this.show_spinner( $spinner.last() ); } }, /* * fetch * * description * * @type function * @date 4/05/2015 * @since 5.2.3 * * @param $post_id (int) * @return $post_id (int) */ fetch: function( $form ){ // bail aelry if already busy if( this.busy ) return false; // reference var self = this; // action for 3rd party acf.do_action('validation_begin'); // vars var data = acf.serialize($form); // append AJAX action data.action = 'acf/validate_save_post'; // prepare data = acf.prepare_for_ajax(data); // set busy this.busy = 1; // lock form this.toggle( $form, 'lock' ); // ajax $.ajax({ url: acf.get('ajaxurl'), data: data, type: 'post', dataType: 'json', success: function( json ){ // bail early if not json success if( !acf.is_ajax_success(json) ) { return; } self.fetch_success( $form, json.data ); }, complete: function(){ self.fetch_complete( $form ); } }); }, /* * fetch_complete * * description * * @type function * @date 4/05/2015 * @since 5.2.3 * * @param $post_id (int) * @return $post_id (int) */ fetch_complete: function( $form ){ // set busy this.busy = 0; // unlock so WP can publish form this.toggle( $form, 'unlock' ); // bail early if validationw as not valid if( !this.valid ) return; // update ignore (allow form submit to not run validation) this.ignore = 1; // remove previous error message var $message = $form.children('.acf-error-message'); if( $message.exists() ) { $message.addClass('-success'); $message.children('p').html( acf._e('validation_successful') ); // remove message setTimeout(function(){ acf.remove_el( $message ); }, 2000); } // remove hidden postboxes (this will stop them from being posted to save) $form.find('.acf-postbox.acf-hidden').remove(); // action for 3rd party customization acf.do_action('submit', $form); // submit form again if( this.$trigger ) { this.$trigger.click(); } else { $form.submit(); } // lock form this.toggle( $form, 'lock' ); }, /* * fetch_success * * description * * @type function * @date 4/05/2015 * @since 5.2.3 * * @param $post_id (int) * @return $post_id (int) */ fetch_success: function( $form, json ){ // filter for 3rd party customization json = acf.apply_filters('validation_complete', json, $form); // validate json if( !json || json.valid || !json.errors ) { // set valid (allows fetch_complete to run) this.valid = true; // action for 3rd party acf.do_action('validation_success'); // end function return; } // action for 3rd party acf.do_action('validation_failure'); // set valid (prevents fetch_complete from runing) this.valid = false; // reset trigger this.$trigger = null; // vars var $scrollTo = null, count = 0, message = acf._e('validation_failed'); // show field error messages if( json.errors && json.errors.length > 0 ) { for( var i in json.errors ) { // get error var error = json.errors[ i ]; // is error for a specific field? if( !error.input ) { // update message message += '. ' + error.message; // ignore following functionality continue; } // get input var $input = $form.find('[name="' + error.input + '"]').first(); // if $_POST value was an array, this $input may not exist if( !$input.exists() ) { $input = $form.find('[name^="' + error.input + '"]').first(); } // bail early if input doesn't exist if( !$input.exists() ) continue; // increase count++; // now get field var $field = acf.get_field_wrap( $input ); // add error this.add_error( $field, error.message ); // set $scrollTo if( $scrollTo === null ) { $scrollTo = $field; } } // message if( count == 1 ) { message += '. ' + acf._e('validation_failed_1'); } else if( count > 1 ) { message += '. ' + acf._e('validation_failed_2').replace('%d', count); } } // get $message var $message = $form.children('.acf-error-message'); if( !$message.exists() ) { $message = $('

    '); $form.prepend( $message ); } // update message $message.children('p').html( message ); // if no $scrollTo, set to message if( $scrollTo === null ) { $scrollTo = $message; } // timeout avoids flicker jump setTimeout(function(){ $("html, body").animate({ scrollTop: $scrollTo.offset().top - ( $(window).height() / 2 ) }, 500); }, 1); }, /* * add_error * * This function will add error markup to a field * * @type function * @date 4/05/2015 * @since 5.2.3 * * @param $field (jQuery) * @param message (string) * @return n/a */ add_error: function( $field, message ){ // reference var self = this; // add class $field.addClass(this.error_class); // add message if( message !== undefined ) { $field.children('.acf-input').children('.' + this.message_class).remove(); $field.children('.acf-input').prepend('

    ' + message + '

    '); } // add event var event = function(){ // remove error self.remove_error( $field ); // remove self $field.off('focus change', 'input, textarea, select', event); } $field.on('focus change', 'input, textarea, select', event); // hook for 3rd party customization acf.do_action('add_field_error', $field); }, /* * remove_error * * This function will remove error markup from a field * * @type function * @date 4/05/2015 * @since 5.2.3 * * @param $field (jQuery) * @return n/a */ remove_error: function( $field ){ // var var $message = $field.children('.acf-input').children('.' + this.message_class); // remove class $field.removeClass(this.error_class); // remove message setTimeout(function(){ acf.remove_el( $message ); }, 250); // hook for 3rd party customization acf.do_action('remove_field_error', $field); }, /* * add_warning * * This functino will add and auto remove an error message to a field * * @type function * @date 4/05/2015 * @since 5.2.3 * * @param $field (jQuery) * @param message (string) * @return n/a */ add_warning: function( $field, message ){ this.add_error( $field, message ); setTimeout(function(){ acf.validation.remove_error( $field ) }, 1000); }, /* * show_spinner * * This function will show a spinner element. Logic changed in WP 4.2 * * @type function * @date 3/05/2015 * @since 5.2.3 * * @param $spinner (jQuery) * @return n/a */ show_spinner: function( $spinner ){ // bail early if no spinner if( !$spinner.exists() ) { return; } // vars var wp_version = acf.get('wp_version'); // show if( parseFloat(wp_version) >= 4.2 ) { $spinner.addClass('is-active'); } else { $spinner.css('display', 'inline-block'); } }, /* * hide_spinner * * This function will hide a spinner element. Logic changed in WP 4.2 * * @type function * @date 3/05/2015 * @since 5.2.3 * * @param $spinner (jQuery) * @return n/a */ hide_spinner: function( $spinner ){ // bail early if no spinner if( !$spinner.exists() ) { return; } // vars var wp_version = acf.get('wp_version'); // hide if( parseFloat(wp_version) >= 4.2 ) { $spinner.removeClass('is-active'); } else { $spinner.css('display', 'none'); } }, /* * disable_submit * * This function will disable the $trigger is possible * * @type function * @date 3/05/2015 * @since 5.2.3 * * @param $spinner (jQuery) * @return n/a */ disable_submit: function( $submit ){ // bail early if no submit if( !$submit.exists() ) { return; } // add class $submit.addClass('disabled button-disabled button-primary-disabled'); }, /* * enable_submit * * This function will enable the $trigger is possible * * @type function * @date 3/05/2015 * @since 5.2.3 * * @param $spinner (jQuery) * @return n/a */ enable_submit: function( $submit ){ // bail early if no submit if( !$submit.exists() ) { return; } // remove class $submit.removeClass('disabled button-disabled button-primary-disabled'); } }); })(jQuery); (function($){ acf.fields.wysiwyg = acf.field.extend({ type: 'wysiwyg', $el: null, $textarea: null, toolbars: {}, events: { 'mousedown .acf-editor-wrap.delay': 'mousedown' }, actions: { 'load': 'initialize', 'append': 'initialize', 'remove': 'disable', 'sortstart': 'disable', 'sortstop': 'enable' }, focus: function(){ // get elements this.$el = this.$field.find('.wp-editor-wrap').last(); this.$textarea = this.$el.find('textarea'); // get options this.o = acf.get_data( this.$el ); this.o.id = this.$textarea.attr('id'); }, mousedown: function(e) { // prevent default e.preventDefault(); // remove delay class this.$el.removeClass('delay'); this.$el.find('.acf-editor-toolbar').remove(); // initialize this.initialize(); }, initialize: function(){ // bail early if delay if( this.$el.hasClass('delay') ) return; // bail early if no tinyMCEPreInit (needed by both tinymce and quicktags) if( typeof tinyMCEPreInit === 'undefined' ) return; // generate new id var old_id = this.o.id, new_id = acf.get_uniqid('acf-editor-'), html = this.$el.outerHTML(); // replace html = acf.str_replace( old_id, new_id, html ); // swap this.$el.replaceWith( html ); // update id this.o.id = new_id // initialize this.initialize_tinymce(); this.initialize_quicktags(); }, initialize_tinymce: function(){ // bail early if no tinymce if( typeof tinymce === 'undefined' ) return; // bail early if no tinyMCEPreInit.mceInit if( typeof tinyMCEPreInit.mceInit === 'undefined' ) return; // vars var mceInit = this.get_mceInit(); // append tinyMCEPreInit.mceInit[ mceInit.id ] = mceInit; // bail early if not visual active if( !this.$el.hasClass('tmce-active') ) return; // initialize try { // init tinymce.init( mceInit ); // vars var ed = tinyMCE.get( mceInit.id ); // action for 3rd party customization acf.do_action('wysiwyg_tinymce_init', ed, ed.id, mceInit, this.$field); } catch(e){} }, initialize_quicktags: function(){ // bail early if no quicktags if( typeof quicktags === 'undefined' ) return; // bail early if no tinyMCEPreInit.qtInit if( typeof tinyMCEPreInit.qtInit === 'undefined' ) return; // vars var qtInit = this.get_qtInit(); // append tinyMCEPreInit.qtInit[ qtInit.id ] = qtInit; // initialize try { // init var qtag = quicktags( qtInit ); // buttons this._buttonsInit( qtag ); // action for 3rd party customization acf.do_action('wysiwyg_quicktags_init', qtag, qtag.id, qtInit, this.$field); } catch(e){} }, get_mceInit : function(){ // reference var $field = this.$field; // vars var toolbar = this.get_toolbar( this.o.toolbar ), mceInit = $.extend({}, tinyMCEPreInit.mceInit.acf_content); // selector mceInit.selector = '#' + this.o.id; // id mceInit.id = this.o.id; // tinymce v4 mceInit.elements = this.o.id; // tinymce v3 // toolbar if( toolbar ) { var k = (tinymce.majorVersion < 4) ? 'theme_advanced_buttons' : 'toolbar'; for( var i = 1; i < 5; i++ ) { mceInit[ k + i ] = acf.isset(toolbar, i) ? toolbar[i] : ''; } } // events if( tinymce.majorVersion < 4 ) { mceInit.setup = function( ed ){ ed.onInit.add(function(ed, event) { // focus $(ed.getBody()).on('focus', function(){ acf.validation.remove_error( $field ); }); $(ed.getBody()).on('blur', function(){ // update the hidden textarea // - This fixes a bug when adding a taxonomy term as the form is not posted and the hidden textarea is never populated! // save to textarea ed.save(); // trigger change on textarea $field.find('textarea').trigger('change'); }); }); }; } else { mceInit.setup = function( ed ){ ed.on('focus', function(e) { acf.validation.remove_error( $field ); }); ed.on('change', function(e) { // save to textarea ed.save(); $field.find('textarea').trigger('change'); }); /* ed.on('blur', function(e) { // update the hidden textarea // - This fixes a but when adding a taxonomy term as the form is not posted and the hidden textarea is never populated! // save to textarea ed.save(); // trigger change on textarea $field.find('textarea').trigger('change'); }); */ /* ed.on('ResizeEditor', function(e) { // console.log(e); }); */ }; } // disable wp_autoresize_on (no solution yet for fixed toolbar) mceInit.wp_autoresize_on = false; // hook for 3rd party customization mceInit = acf.apply_filters('wysiwyg_tinymce_settings', mceInit, mceInit.id, this.$field); // return return mceInit; }, get_qtInit : function(){ // vars var qtInit = $.extend({}, tinyMCEPreInit.qtInit.acf_content); // id qtInit.id = this.o.id; // hook for 3rd party customization qtInit = acf.apply_filters('wysiwyg_quicktags_settings', qtInit, qtInit.id, this.$field); // return return qtInit; }, /* * disable * * This function will disable the tinymce for a given field * Note: txtarea_el is different from $textarea.val() and is the value that you see, not the value that you save. * this allows text like <--more--> to wok instead of showing as an image when the tinymce is removed * * @type function * @date 1/08/2014 * @since 5.0.0 * * @param n/a * @return n/a */ disable: function(){ try { // vars var ed = tinyMCE.get( this.o.id ) // save ed.save(); // destroy editor ed.destroy(); } catch(e) {} }, enable: function(){ try { // bail early if html mode if( this.$el.hasClass('tmce-active') ) { switchEditors.go( this.o.id, 'tmce'); } } catch(e) {} }, get_toolbar : function( name ){ // bail early if toolbar doesn't exist if( typeof this.toolbars[ name ] !== 'undefined' ) { return this.toolbars[ name ]; } // return return false; }, /* * _buttonsInit * * This function will add the quicktags HTML to a WYSIWYG field. Normaly, this is added via quicktags on document ready, * however, there is no support for 'append'. Source: wp-includes/js/quicktags.js:245 * * @type function * @date 1/08/2014 * @since 5.0.0 * * @param ed (object) quicktag object * @return n/a */ _buttonsInit: function( ed ) { var 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 = ''; // 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 === inst ) { theButtons[id] = edButtons[i]; if ( edButtons[i].html ) { html += edButtons[i].html(name + '_'); } } } if ( use && use.indexOf(',fullscreen,') !== -1 ) { theButtons.fullscreen = new qt.FullscreenButton(); html += theButtons.fullscreen.html(name + '_'); } if ( 'rtl' === document.getElementsByTagName('html')[0].dir ) { theButtons.textdirection = new qt.TextDirectionButton(); html += theButtons.textdirection.html(name + '_'); } ed.toolbar.innerHTML = html; ed.theButtons = theButtons; } }); /* * wysiwyg_manager * * This model will handle validation of fields within a tab group * * @type function * @date 25/11/2015 * @since 5.3.2 * * @param $post_id (int) * @return $post_id (int) */ var acf_content = acf.model.extend({ $div: null, actions: { 'ready': 'ready' }, ready: function(){ // vars this.$div = $('#acf-hidden-wp-editor'); // bail early if doesn't exist if( !this.$div.exists() ) return; // move to footer this.$div.appendTo('body'); // 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); // @codekit-prepend "../js/event-manager.js"; // @codekit-prepend "../js/acf.js"; // @codekit-prepend "../js/acf-ajax.js"; // @codekit-prepend "../js/acf-checkbox.js"; // @codekit-prepend "../js/acf-color-picker.js"; // @codekit-prepend "../js/acf-conditional-logic.js"; // @codekit-prepend "../js/acf-date-picker.js"; // @codekit-prepend "../js/acf-date-time-picker.js"; // @codekit-prepend "../js/acf-file.js"; // @codekit-prepend "../js/acf-google-map.js"; // @codekit-prepend "../js/acf-image.js"; // @codekit-prepend "../js/acf-link.js"; // @codekit-prepend "../js/acf-media.js"; // @codekit-prepend "../js/acf-oembed.js"; // @codekit-prepend "../js/acf-radio.js"; // @codekit-prepend "../js/acf-relationship.js"; // @codekit-prepend "../js/acf-select2.js"; // @codekit-prepend "../js/acf-select.js"; // @codekit-prepend "../js/acf-tab.js"; // @codekit-prepend "../js/acf-time-picker.js"; // @codekit-prepend "../js/acf-true-false.js"; // @codekit-prepend "../js/acf-taxonomy.js"; // @codekit-prepend "../js/acf-url.js"; // @codekit-prepend "../js/acf-validation.js"; // @codekit-prepend "../js/acf-wysiwyg.js";