/* global ajaxurl, mla */ var jQuery; /* * This script requires the global "mla" object to be defined and include the following: * * properties: * mla.settings.useDashicons * * components: * mla.addTerm * mla.mlaList */ ( function( $ ) { /** * addTerm opens an area beneath a "checklist-style" taxonomy in the * Media/Assistant Bulk Edit area and Media/Add New Bulk Edit area. * It allows the user to add a term to the associated taxonomy. */ mla.addTerm = { /** * Attach new term functions to each taxonomy in the appropriate context * * @param {string} contextId a CSS "id=" selector, i.e. starting with "#" */ init: function( contextId ) { /* * contextId must be unique on the page, e.g., a
around the Bulk Edit or Quick Edit area. * Each taxonomy's '.categorydiv' must appear just once within the context. */ var context = $( contextId ); $( '.categorydiv', context ).each( function(){ var this_id = $(this).attr('id'), catAddBefore, catAddAfter, taxonomyParts, taxonomy; taxonomyParts = this_id.split('-'); taxonomyParts.shift(); // taxonomy- taxonomy = taxonomyParts.join('-'); // Add category button controls. $( '#new' + taxonomy, context ).one( 'focus', function() { $( this ).val( '' ).removeClass( 'form-input-tip' ); }); // On [enter] submit the taxonomy. $( '#new' + taxonomy, context ).keypress( function(event){ if( 13 === event.keyCode ) { event.preventDefault(); $( '#' + taxonomy + '-add-submit', context ).click(); } }); // After submitting a new taxonomy, re-focus the input field. $( '#' + taxonomy + '-add-submit', context ).click( function() { $( '#new' + taxonomy, context ).focus(); }); /** * Before adding a new taxonomy, disable submit button. * * @param {Object} s Taxonomy object which will be added. * * @returns {Object} */ catAddBefore = function( s ) { if ( !$( '#new' + taxonomy, context ).val() ) { return false; } s.data += '&' + $( ':checked', '#' + taxonomy +'-checklist', context ).serialize(); $( '#' + taxonomy + '-add-submit', context ).prop( 'disabled', true ); return s; }; /** * Re-enable submit button after a taxonomy has been added. * * Re-enable submit button. * If the taxonomy has a parent place the taxonomy underneath the parent. * * @param {Object} r Response. * @param {Object} s Taxonomy data. * * @returns void */ catAddAfter = function( r, s ) { var sup; $( '#' + taxonomy + '-add-submit', context ).prop( 'disabled', false ); // Update the "Parent Term" dropdown control(s) if ( 'undefined' != s.parsed.responses[0] && (sup = s.parsed.responses[0].supplemental.newcat_parent) ) { $( '.edit-fields-div' ).each( function(){ var drop = $( '#new' + taxonomy + '_parent', $(this) ); drop.before(sup); drop.remove(); }); } }; mla.mlaList.settings.context = context; $( '#' + taxonomy + 'checklist', context ).mlaList({ alt: '', context: context, // response: taxonomy + '-ajax-response', response: 'add-term-ajax-response', addBefore: catAddBefore, addAfter: catAddAfter }); $( '#' + taxonomy + '-add-toggle', context ).off(); // Add new taxonomy button toggles input form visibility. $( '#' + taxonomy + '-add-toggle', context ).click( function( e ) { e.preventDefault(); $( '#' + taxonomy + '-adder', context ).toggleClass( 'wp-hidden-children' ); $( '#new' + taxonomy, context ).focus(); // Hide the "? Search" text box when displaying the "+ Add New ..." text box $( '#' + taxonomy + '-searcher', context ).addClass( 'wp-hidden-children' ); return false; }); }); // .categorydiv.each, } }; // mla.addTerm /** * mlaList adapts the WordPress wpList object found in wp-includes/js/wp-lists.js * It handles the case where more than one instance of a taxonomy "metabox" occurs * on a page, e.g., the Bulk and Quick edit areas on the Media/Assistant admin page. */ mla.mlaList = { /** * @member {object} */ settings: { /** * URL for Ajax requests. * * @member {string} */ url: ajaxurl, /** * The HTTP method to use for Ajax requests. * * @member {string} */ type: 'POST', /** * jQuery context of the taxonomy's HTML elements. * * @member {object} */ context: null, /** * ID of the element the parsed Ajax response will be stored in. * * @member {string} */ response: 'ajax-response', /** * The type of list. * * @member {string} */ what: '', /** * CSS class name for alternate styling. * * @member {string} */ alt: 'alternate', /** * Offset to start alternate styling from. * * @member {number} */ altOffset: 0, /** * Color used in animation when adding an element. * * Can be 'none' to disable the animation. * * @member {string} */ addColor: '#ffff33', /** * Callback that's run before a request is made. * * @callback mlaList~confirm * @param {object} this * @param {HTMLElement} list The list DOM element. * @param {object} settings Settings for the current list. * @param {string} action The type of action to perform: 'add', 'delete', or 'dim'. * @param {string} backgroundColor Background color of the list's DOM element. * @returns {boolean} Whether to proceed with the action or not. */ confirm: null, /** * Callback that's run before an item gets added to the list. * * Allows to cancel the request. * * @callback mlaList~addBefore * @param {object} settings Settings for the Ajax request. * @returns {object|boolean} Settings for the Ajax request or false to abort. */ addBefore: null, /** * Callback that's run after an item got added to the list. * * @callback mlaList~addAfter * @param {XML} returnedResponse Raw response returned from the server. * @param {object} settings Settings for the Ajax request. * @param {jqXHR} settings.xml jQuery XMLHttpRequest object. * @param {string} settings.status Status of the request: 'success', 'notmodified', 'nocontent', 'error', * 'timeout', 'abort', or 'parsererror'. * @param {object} settings.parsed Parsed response object. */ addAfter: null }, /** * Finds a nonce. * * 1. Nonce in settings. * 2. `_ajax_nonce` value in element's href attribute. * 3. `_ajax_nonce` input field that is a descendant of element. * 4. `_wpnonce` value in element's href attribute. * 5. `_wpnonce` input field that is a descendant of element. * 6. 0 if none can be found. * * @param {jQuery} element Element that triggered the request. * @param {object} settings Settings for the Ajax request. * @returns {string|number} Nonce */ nonce: function( element, settings ) { var url = wpAjax.unserialize( element.attr( 'href' ) ), $element = $( '#' + settings.element, mla.mlaList.settings.context ); return settings.nonce || url._ajax_nonce || $element.find( 'input[name="_ajax_nonce"]' ).val() || url._wpnonce || $element.find( 'input[name="_wpnonce"]' ).val() || 0; }, /** * Extract list item data from a DOM element. * * Example 1: data-wp-lists="delete:the-comment-list:comment-{comment_ID}:66cc66:unspam=1" * Example 2: data-wp-lists="dim:the-comment-list:comment-{comment_ID}:unapproved:e7e7d3:e7e7d3:new=approved" * * Returns an unassociated array with the following data: * data[0] - Data identifier: 'list', 'add', 'delete', or 'dim'. * data[1] - ID of the corresponding list. If data[0] is 'list', the type of list ('comment', 'category', etc). * data[2] - ID of the parent element of all inputs necessary for the request. * data[3] - Hex color to be used in this request. If data[0] is 'dim', dim class. * data[4] - Additional arguments in query syntax that are added to the request. Example: 'post_id=1234'. * If data[0] is 'dim', dim add color. * data[5] - Only available if data[0] is 'dim', dim delete color. * data[6] - Only available if data[0] is 'dim', additional arguments in query syntax that are added to the request. * * Result for Example 1: * data[0] - delete * data[1] - the-comment-list * data[2] - comment-{comment_ID} * data[3] - 66cc66 * data[4] - unspam=1 * * @param {HTMLElement} element The DOM element. * @param {string} type The type of data to look for: 'list', 'add', 'delete', or 'dim'. * @returns {Array} Extracted list item data. */ parseData: function( element, type ) { var data = [], mlaListsData; try { mlaListsData = $( element ).data( 'wp-lists' ) || ''; mlaListsData = mlaListsData.match( new RegExp( type + ':[\\S]+' ) ); if ( mlaListsData ) { data = mlaListsData[0].split( ':' ); } } catch ( error ) {} return data; }, /** * Calls a confirm callback to verify the action that is about to be performed. * * @param {HTMLElement} list The DOM element. * @param {object} settings Settings for this list. * @param {string} action The type of action to perform: 'add', 'delete', or 'dim'. * @returns {object|boolean} Settings if confirmed, false if not. */ pre: function( list, settings, action ) { var $element, backgroundColor, confirmed; settings = $.extend( {}, this.mlaList.settings, { element: null, nonce: 0, target: list.get( 0 ) }, settings || {} ); if ( $.isFunction( settings.confirm ) ) { $element = $( '#' + settings.element, mla.mlaList.settings.context ); if ( 'add' !== action ) { backgroundColor = $element.css( 'backgroundColor' ); $element.css( 'backgroundColor', '#ff9966' ); } confirmed = settings.confirm.call( this, list, settings, action, backgroundColor ); if ( 'add' !== action ) { $element.css( 'backgroundColor', backgroundColor ); } if ( ! confirmed ) { return false; } } return settings; }, /** * Adds an item to the list via AJAX. * * @param {HTMLElement} element The DOM element. * @param {object} settings Settings for this list. * @returns {boolean} Whether the item was added. */ ajaxAdd: function( element, settings ) { var list = this, $element = $( element, mla.mlaList.settings.context ), data = mla.mlaList.parseData( $element, 'add' ), formValues, formData, parsedResponse, returnedResponse; settings = settings || {}; settings = mla.mlaList.pre.call( list, $element, settings, 'add' ); settings.element = data[2] || $element.prop( 'id' ) || settings.element || null; settings.addColor = data[3] ? '#' + data[3] : settings.addColor; if ( ! settings ) { return false; } if ( ! $element.is( '[id="' + settings.element + '-submit"]', mla.mlaList.settings.context ) ) { return ! mla.mlaList.add.call( list, $element, settings ); } if ( ! settings.element ) { return true; } settings.action = 'add-' + settings.what; settings.nonce = mla.mlaList.nonce( $element, settings ); if ( ! wpAjax.validateForm( mla.mlaList.settings.context.selector + ' #' + settings.element ) ) { return false; } settings.data = $.param( $.extend( { _ajax_nonce: settings.nonce, action: settings.action }, wpAjax.unserialize( data[4] || '' ) ) ); formValues = $( '#' + settings.element + ' :input', mla.mlaList.settings.context ).not( '[name="_ajax_nonce"], [name="_wpnonce"], [name="action"]' ); formData = $.isFunction( formValues.fieldSerialize ) ? formValues.fieldSerialize() : formValues.serialize(); if ( formData ) { settings.data += '&' + formData; } if ( $.isFunction( settings.addBefore ) ) { settings = settings.addBefore( settings ); if ( ! settings ) { return true; } } if ( ! settings.data.match( /_ajax_nonce=[a-f0-9]+/ ) ) { return true; } settings.success = function( response ) { var contextId = mla.mlaList.settings.context.selector.substring(1); parsedResponse = wpAjax.parseAjaxResponse( response, contextId + ' #' + settings.response, settings.element ); returnedResponse = response; if ( ! parsedResponse || parsedResponse.errors ) { return false; } if ( true === parsedResponse ) { return true; } // Add the new term, checked/selected, to the list $.each( parsedResponse.responses, function() { mla.mlaList.add.call( list, this.data, $.extend( {}, settings, { // this.firstChild.nodevalue position: this.position || 0, id: this.id || 0, oldId: this.oldId || null } ) ); } ); list.mlaList.recolor(); $( list ).trigger( 'mlaListAddEnd', [ settings, list.mlaList ] ); mla.mlaList.clear.call( list, '#' + settings.element ); }; // The addAfter function uses the supplemental data to update the "Parent Term" dropdown ontrol settings.complete = function( jqXHR, status ) { if ( $.isFunction( settings.addAfter ) ) { settings.addAfter( returnedResponse, $.extend( { xml: jqXHR, status: status, parsed: parsedResponse }, settings ) ); } }; $.ajax( settings ); return false; }, /** * Returns the background color of the passed element. * * @param {jQuery|string} element Element to check. * @returns {string} Background color value in HEX. Default: '#ffffff'. */ getColor: function( element ) { return $( element ).css( 'backgroundColor' ) || '#ffffff'; }, /** * Adds a new item to all other taxonomy checklists on the page. * * @param {HTMLElement} element A DOM element containing new item data. * @param {object} $list jQuery object for the current list. * @param {object} settings Settings for this list. */ addToOtherLists: function( element, $list, settings ) { $( '.edit-fields-div' ).each( function() { var $thisDiv = $( this ), thisId = $thisDiv.attr( 'id' ), targetId = settings.context.selector.substring(1), $checklist, $element, old, position, reference; if ( targetId !== thisId ) { $checklist = $( '.' + $list.attr('id'), $thisDiv ); $element = $( element ); old = false; $( 'input', $element ).removeAttr( 'checked' ) if ( settings.oldId ) { old = $( '#' + settings.what + '-' + settings.oldId, $checklist ); } if ( settings.id && ( settings.id !== settings.oldId || ! old || ! old.length ) ) { $( '#' + settings.what + '-' + settings.id, $checklist ).remove(); } if ( old && old.length ) { old.before( $element ); old.remove(); } else if ( isNaN( settings.position ) ) { position = 'after'; if ( '-' === settings.position.substr( 0, 1 ) ) { settings.position = settings.position.substr( 1 ); position = 'before'; } reference = $checklist.find( '#' + settings.position ); if ( 1 === reference.length ) { reference[position]( $element ); } else { $checklist.append( $element ); } } else if ( 'comment' !== settings.what || 0 === $( '#' + settings.element, $checklist ).length ) { if ( settings.position < 0 ) { $checklist.prepend( $element ); } else { $checklist.append( $element ); } } } }); }, /** * Adds a new item to the enclosing list. * * @param {HTMLElement} element A DOM element containing item data. * @param {object} settings Settings for this list. * @returns {boolean} Whether the item was added. */ add: function( element, settings ) { var $list = $( this ), $element = $( element ), old = false, position, reference; if ( 'string' === typeof settings ) { settings = { what: settings }; } settings = $.extend( { position: 0, id: 0, oldId: null }, this.mlaList.settings, settings ); if ( ! $element.length || ! settings.what ) { return false; } mla.mlaList.addToOtherLists( element, $list, settings ); if ( settings.oldId ) { old = $( '#' + settings.what + '-' + settings.oldId, settings.context ); } if ( settings.id && ( settings.id !== settings.oldId || ! old || ! old.length ) ) { $( '#' + settings.what + '-' + settings.id, settings.context ).remove(); } if ( old && old.length ) { old.before( $element ); old.remove(); } else if ( isNaN( settings.position ) ) { position = 'after'; if ( '-' === settings.position.substr( 0, 1 ) ) { settings.position = settings.position.substr( 1 ); position = 'before'; } reference = $list.find( '#' + settings.position ); if ( 1 === reference.length ) { reference[position]( $element ); } else { $list.append( $element ); } } else if ( 'comment' !== settings.what || 0 === $( '#' + settings.element, mla.mlaList.settings.context ).length ) { if ( settings.position < 0 ) { $list.prepend( $element ); } else { $list.append( $element ); } } if ( settings.alt ) { $element.toggleClass( settings.alt, ( $list.children( ':visible' ).index( $element[0] ) + settings.altOffset ) % 2 ); } if ( 'none' !== settings.addColor ) { $element.css( 'backgroundColor', settings.addColor ).animate( { backgroundColor: mla.mlaList.getColor( $element ) }, { complete: function() { $( this ).css( 'backgroundColor', '' ); } } ); } // Add event handlers. $list.each( function( index, list ) { list.mlaList.process( $element ); } ); return $element; }, /** * Clears all input fields within the element passed. * * @param {string} elementId ID of the element to check, including leading #. */ clear: function( elementId ) { var list = this, $element = $( elementId ), type, tagName; // Bail if we're within the list. if ( list.mlaList && $element.parents( '#' + list.id ).length ) { return; } // Check each input field. $element.find( ':input' ).each( function( index, input ) { // Bail if the form was marked to not to be cleared. if ( $( input ).parents( '.form-no-clear' ).length ) { return; } type = input.type.toLowerCase(); tagName = input.tagName.toLowerCase(); if ( 'text' === type || 'password' === type || 'textarea' === tagName ) { input.value = ''; } else if ( 'checkbox' === type || 'radio' === type ) { input.checked = false; } else if ( 'select' === tagName ) { input.selectedIndex = null; } } ); }, /** * Registers event handlers to add, delete, and dim items. * * @param {string} elementId */ process: function( elementId ) { var list = this, $element = $( elementId || document ); $element.on( 'submit', 'form[data-wp-lists^="add:' + list.id + ':"]', function() { return list.mlaList.add( this ); } ); $element.on( 'click', 'a[data-wp-lists^="add:' + list.id + ':"], input[data-wp-lists^="add:' + list.id + ':"]', function() { return list.mlaList.add( this ); } ); }, /** * Updates list item background colors. */ recolor: function() { var list = this, evenOdd = [':even', ':odd'], items; // Bail if there is no alternate class name specified. if ( ! list.mlaList.settings.alt ) { return; } items = $( '.list-item:visible', list ); if ( ! items.length ) { items = $( list ).children( ':visible' ); } if ( list.mlaList.settings.altOffset % 2 ) { evenOdd.reverse(); } items.filter( evenOdd[0] ).addClass( list.mlaList.settings.alt ).end(); items.filter( evenOdd[1] ).removeClass( list.mlaList.settings.alt ); }, /** * Sets up `process()` and `recolor()` functions. */ init: function() { var $list = this; $list.mlaList.process = function( element ) { $list.each( function() { this.mlaList.process( element ); } ); }; $list.mlaList.recolor = function() { $list.each( function() { this.mlaList.recolor(); } ); }; } }; var functions = { add: 'ajaxAdd', process: 'process', recolor: 'recolor' }; /** * Initializes mlaList object. * * @param {Object} settings * @param {string} settings.url URL for ajax calls. Default: ajaxurl. * @param {string} settings.type The HTTP method to use for Ajax requests. Default: 'POST'. * @param {string} settings.response ID of the element the parsed ajax response will be stored in. * Default: 'ajax-response'. * * @param {string} settings.what Default: ''. * @param {string} settings.alt CSS class name for alternate styling. Default: 'alternate'. * @param {number} settings.altOffset Offset to start alternate styling from. Default: 0. * @param {string} settings.addColor Hex code or 'none' to disable animation. Default: '#ffff33'. * * @param {mlaList~confirm} settings.confirm Callback that's run before a request is made. Default: null. * @param {mlaList~addBefore} settings.addBefore Callback that's run before an item gets added to the list. * Default: null. * @param {mlaList~addAfter} settings.addAfter Callback that's run after an item got added to the list. * Default: null. * @returns {$.fn} mlaList API function. */ $.fn.mlaList = function( settings ) { this.each( function( index, list ) { list.mlaList = { settings: $.extend( {}, mla.mlaList.settings, { what: mla.mlaList.parseData( list, 'list' )[1] || '' }, settings ) }; $.each( functions, function( func, callback ) { list.mlaList[func] = function( element, setting ) { return mla.mlaList[callback].call( list, element, setting ); }; } ); } ); mla.mlaList.init.call( this ); this.mlaList.process( settings.context ); return this; }; })( jQuery );