( 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 ) { $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, name ){ //console.log('get_data(%o, %o)', name, $el); // get all datas if( typeof name === 'undefined' ) { return $el.data(); } // return return $el.data(name); }, /* * 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( $el ){ // vars var data = {}, names = {}; // selector $selector = $el.find('select, textarea, input'); // populate data $.each( $selector.serializeArray(), function( i, pair ) { // initiate name if( pair.name.slice(-2) === '[]' ) { // remove [] pair.name = pair.name.replace('[]', ''); // initiate counter if( typeof names[ pair.name ] === 'undefined'){ names[ pair.name ] = -1; } // increase counter names[ pair.name ]++; // add key pair.name += '[' + names[ pair.name ] +']'; } // append to data data[ pair.name ] = pair.value; }); // return return data; }, serialize: function( $el ){ return this.serialize_form( $el ); }, /* * disable_form * * This function will disable all inputs within an element * * @type function * @date 22/09/2016 * @since 5.4.0 * * @param $el (jQuery) * @return na */ disable_form: function( $el, context ) { // defaults context = context || ''; // loop $el.find('select, textarea, input').each(function(){ acf.disable( $(this), context ); }); }, /* * disable * * This function will disable an input * * @type function * @date 22/09/2016 * @since 5.4.0 * * @param $el (jQuery) * @return n/a */ disable: function( $input, context ){ // defaults context = context || ''; // bail early if is .acf-disabled if( $input.hasClass('acf-disabled') ) return false; // context if( context ) { // vars var attr = $input.attr('data-disabled'), disabled = attr ? attr.split(',') : [], i = disabled.indexOf(context); // bail early if already disabled if( i >= 0 ) return false; // append context disabled.push( context ); // join attr = disabled.join(','); // update context $input.attr('data-disabled', attr); } // disable input $input.prop('disabled', true); }, /* * enable_form * * This function will enable all inputs within an element * * @type function * @date 22/09/2016 * @since 5.4.0 * * @param $el (jQuery) * @return na */ enable_form: function( $el, context ) { // defaults context = context || ''; // loop $el.find('select, textarea, input').each(function(){ acf.enable( $(this), context ); }); }, /* * enable * * This function will enable an input * * @type function * @date 22/09/2016 * @since 5.4.0 * * @param $el (jQuery) * @return n/a */ enable: function( $input, context ){ // defaults context = context || ''; // bail early if is .acf-disabled if( $input.hasClass('acf-disabled') ) return false; // context if( context ) { // vars var attr = $input.attr('data-disabled'), disabled = attr ? attr.split(',') : [], i = disabled.indexOf(context); // bail early if no content or context does not match if( i < 0 ) return false; // delete disabled.splice(i, 1); // update attr attr = disabled.join(','); // update context $input.attr('data-disabled', attr); // bail early if other disableds exist if( attr ) return false; } // enable input $input.prop('disabled', false); }, /* * 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; // set layout $el.css({ height : $el.height(), width : $el.width(), position : 'absolute', //padding : 0 }); // wrap field $el.wrap( '
' ); // fade $el $el.animate({ opacity : 0 }, 250); // remove $el.parent('.acf-temp-wrap').animate({ height : end_height }, 250, function(){ $(this).remove(); if( typeof(callback) == 'function' ) { callback(); } }); }, /* * 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 args.nonce = acf.get('nonce'); args.post_id = acf.get('post_id'); // filter for 3rd party customization args = acf.apply_filters('prepare_for_ajax', args); // return return args; }, /* * 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 ) { // vars var string2 = '', replace = { 'æ': 'a', 'å': 'a', 'á': 'a', 'ä': 'a', 'č': 'c', 'ď': 'd', 'è': 'e', 'é': 'e', 'ě': 'e', 'ë': 'e', 'í': 'i', 'ĺ': 'l', 'ľ': 'l', 'ň': 'n', 'ø': 'o', 'ó': 'o', 'ô': 'o', 'ő': 'o', 'ö': 'o', 'ŕ': 'r', 'š': 's', 'ť': 't', 'ú': 'u', 'ů': 'u', 'ű': 'u', 'ü': 'u', 'ý': 'y', 'ř': 'r', 'ž': 'z', ' ': '_', '\'': '', '?': '', '/': '', '\\': '', '.': '', ',': '', '>': '', '<': '', '"': '', '[': '', ']': '', '|': '', '{': '', '}': '', '(': '', ')': '' }; // lowercase string = string.toLowerCase(); // loop through characters for( i = 0; i < string.length; i++ ) { // character var c = string.charAt(i); // override c with replacement if( typeof replace[c] !== 'undefined' ) { c = replace[c]; } // append string2 += c; } // return return string2; }, /* * 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 $('', '' ].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({ render: function() { // reference var self = this; // validate if( this.ignore_render ) return this; // add button setTimeout(function(){ // vars var $media_model = self.$el.closest('.media-modal'); // does button already exist? if( $media_model.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( $media_model.hasClass('acf-expanded') ) { $media_model.removeClass('acf-expanded'); } else { $media_model.addClass('acf-expanded'); } }); // append $media_model.find('.media-frame-router').append( $a ); }, 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_form(this.$el); // ignore render this.ignore_render = true; // save this.model.saveCompat( data ); } }); } }); })(jQuery); (function($){ acf.fields.oembed = { search : function( $el ){ // vars var s = $el.find('[data-name="search-input"]').val(); // fix missing 'http://' - causes the oembed code to error and fail if( s.substr(0, 4) != 'http' ) { s = 'http://' + s; $el.find('[data-name="search-input"]').val( s ); } // show loading $el.addClass('is-loading'); // AJAX data var ajax_data = acf.prepare_for_ajax({ 'action' : 'acf/fields/oembed/search', 's' : s, 'width' : acf.get_data($el, 'width'), 'height' : acf.get_data($el, 'height') }); // abort XHR if this field is already loading AJAX data if( $el.data('xhr') ) { $el.data('xhr').abort(); } // get HTML var xhr = $.ajax({ url: acf.get('ajaxurl'), data: ajax_data, type: 'post', dataType: 'html', success: function( html ){ $el.removeClass('is-loading'); // update from json acf.fields.oembed.search_success( $el, s, html ); // no results? if( !html ) { acf.fields.oembed.search_error( $el ); } } }); // update el data $el.data('xhr', xhr); }, search_success : function( $el, s, html ){ $el.removeClass('has-error').addClass('has-value'); $el.find('[data-name="value-input"]').val( s ); $el.find('[data-name="value-title"]').html( s ); $el.find('[data-name="value-embed"]').html( html ); }, search_error : function( $el ){ // update class $el.removeClass('has-value').addClass('has-error'); }, clear : function( $el ){ // update class $el.removeClass('has-error has-value'); // clear search $el.find('[data-name="search-input"]').val(''); // clear inputs $el.find('[data-name="value-input"]').val( '' ); $el.find('[data-name="value-title"]').html( '' ); $el.find('[data-name="value-embed"]').html( '' ); }, edit : function( $el ){ // update class $el.addClass('is-editing'); // set url and focus var url = $el.find('[data-name="value-title"]').text(); $el.find('[data-name="search-input"]').val( url ).focus() }, blur : function( $el ){ $el.removeClass('is-editing'); // set url and focus var old_url = $el.find('[data-name="value-title"]').text(), new_url = $el.find('[data-name="search-input"]').val(), embed = $el.find('[data-name="value-embed"]').html(); // bail early if no valu if( !new_url ) { this.clear( $el ); return; } // bail early if no change if( new_url == old_url ) { return; } this.search( $el ); } }; /* * Events * * jQuery events for this field * * @type function * @date 1/03/2011 * * @param N/A * @return N/A */ $(document).on('click', '.acf-oembed [data-name="search-button"]', function( e ){ e.preventDefault(); acf.fields.oembed.search( $(this).closest('.acf-oembed') ); $(this).blur(); }); $(document).on('click', '.acf-oembed [data-name="clear-button"]', function( e ){ e.preventDefault(); acf.fields.oembed.clear( $(this).closest('.acf-oembed') ); $(this).blur(); }); $(document).on('click', '.acf-oembed [data-name="value-title"]', function( e ){ e.preventDefault(); acf.fields.oembed.edit( $(this).closest('.acf-oembed') ); }); $(document).on('keypress', '.acf-oembed [data-name="search-input"]', function( e ){ // don't submit form if( e.which == 13 ) { e.preventDefault(); } }); $(document).on('keyup', '.acf-oembed [data-name="search-input"]', function( e ){ // bail early if no value if( ! $(this).val() ) { return; } // bail early for directional controls if( ! e.which ) { return; } acf.fields.oembed.search( $(this).closest('.acf-oembed') ); }); $(document).on('blur', '.acf-oembed [data-name="search-input"]', function(e){ acf.fields.oembed.blur( $(this).closest('.acf-oembed') ); }); })(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.find('.acf-hidden input'); 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 if( this.o.s ) { var s = this.o.s; $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($){ /* * acf.select2 * * all logic to create select2 instances * * @type function * @date 16/12/2015 * @since 5.3.2 * * @param n/a * @return n/a */ acf.select2 = acf.model.extend({ // vars version: 0, // actions actions: { 'ready 1': 'ready', }, /* * ready * * This function will setup vars * * @type function * @date 21/06/2016 * @since 5.3.8 * * @param n/a * @return n/a */ ready: function(){ // determine Select2 version if( acf.maybe_get(window, 'Select2') ) { this.version = 3; this.l10n_v3(); } else if( acf.maybe_get(window, 'jQuery.fn.select2.amd') ) { this.version = 4; } }, /* * l10n_v3 * * This function will set l10n for Select2 v3 * * @type function * @date 21/06/2016 * @since 5.3.8 * * @param n/a * @return n/a */ l10n_v3: 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); }, /* * init * * This function will initialize a Select2 instance * * @type function * @date 21/06/2016 * @since 5.3.8 * * @param $select (jQuery object) * @param args (object) * @return (mixed) */ init: function( $select, args ){ // bail early if no version found if( !this.version ) return; // defaults args = $.extend({ allow_null: false, placeholder: '', multiple: false, ajax: false, ajax_action: '', }, args); // v3 if( this.version == 3 ) { return this.init_v3( $select, args ); // v4 } else if( this.version == 4 ) { return this.init_v4( $select, args ); } // return return false; }, /* * 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 = acf.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 ){ // vars var data = acf.prepare_for_ajax({ action: args.ajax_action, field_key: args.key, s: params.term, paged: params.page }); // filter data = acf.apply_filters( 'select2_ajax_data', data, args, params ); // 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() }); }); // return return val; }, /* * 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_v3: function( $select, 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, ''); } // 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 acf.select2.get_ajax_data(args, params); }, results: function( data, page ){ // vars var params = { 'page': page }; // merge together groups setTimeout(function(){ acf.select2.merge_results_v3(); }, 1); // return return acf.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 ); // 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 ) { $select.append(''); } // update val $select.val( e.val ); }); }, /* * merge_results_v3 * * description * * @type function * @date 20/07/2016 * @since 5.4.0 * * @param $post_id (int) * @return $post_id (int) */ merge_results_v3: 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; }); }, init_v4: function( $select, 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; }, /* sorter: function (data) { console.log('sorter %o', data); return data; }, */ }; // 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.templateSelection = function( selection ){ return selection.text + ''; } */ } else { // change array to single object value = acf.maybe_get(value, 0, ''); } // 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 ); }; */ // 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 acf.select2.get_ajax_data(args, params); }, processResults: function( data, params ){ // vars var results = acf.select2.get_ajax_results(data, params); // change to more if( results.more ) { results.pagination = { more: true }; } // merge together groups setTimeout(function(){ acf.select2.merge_results_v4(); }, 1); // return return results } }; } // multiple /* if( args.multiple ) { $select.on('select2:select', function( e ){ console.log( 'select2:select %o &o', $(this), e ); // vars var $option = $(e.params.data.element); // move option to begining of select //$(this).prepend( $option ); }); } */ // reorder DOM // - no need to reorder, the select field is needed to $_POST values // filter for 3rd party customization select2_args = acf.apply_filters( 'select2_args', select2_args, $select, args ); // add select2 //console.log( '%o %o ', $select, select2_args ); var $container = $select.select2( select2_args ); // add class $container.addClass('-acf'); }, /* * merge_results_v4 * * description * * @type function * @date 20/07/2016 * @since 5.4.0 * * @param $post_id (int) * @return $post_id (int) */ merge_results_v4: 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; }); }, /* * 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 ){ // remove select2 container $select.siblings('.select2-container').remove(); // show input so that select2 can correctly render visible select2 container $select.siblings('input').show(); // enable select $select.prop('disabled', false).removeClass('acf-disabled acf-hidden'); } }); /* * 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 ) { acf.select2.init( $select, args ); } acf.remove_select2 = function( $select ) { acf.select2.destroy( $select ); } // 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 ); }, 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.text(); }, 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(){ // 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.addClass('active').timepicker( args ); // wrap the datepicker (only if it hasn't already been wrapped) if( $('body > #ui-datepicker-div').exists() ) { $('body > #ui-datepicker-div').wrap('
    '); } }, blur: function(){ if( !this.$input.val() ) { this.$hidden.val(''); } } }); })(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); break; case 'multi_select': // vars var $input = this.$el.children('input'), value = $input.select2('data') || []; // append value.push( item ); // update $input.select2('data', value); 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($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 $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: {}, 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'); }, initialize: function(){ // 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; // 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 { tinymce.init( mceInit ); } catch(e){} }, initialize_quicktags: function(){ // bail early if no quicktags if( typeof quicktags === 'undefined' ) return; // vars var qtInit = this.get_qtInit(); // append tinyMCEPreInit.qtInit[ qtInit.id ] = qtInit; // initialize try { var qtag = quicktags( qtInit ); this._buttonsInit( qtag ); } 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); // 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); // 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(){ // bail early if html mode if( this.$el.hasClass('tmce-active') && acf.isset(window,'switchEditors') ) { switchEditors.go( this.o.id, 'tmce'); } }, 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( typeof tinymce === 'undefined' ) 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-media.js"; // @codekit-prepend "../js/acf-oembed.js"; // @codekit-prepend "../js/acf-radio.js"; // @codekit-prepend "../js/acf-relationship.js"; // @codekit-prepend "../js/acf-select.js"; // @codekit-prepend "../js/acf-tab.js"; // @codekit-prepend "../js/acf-time-picker.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";