diff --git a/acf_repeater_collapser.php b/acf_repeater_collapser.php index 15adc4f..f915c19 100644 --- a/acf_repeater_collapser.php +++ b/acf_repeater_collapser.php @@ -3,22 +3,45 @@ * Plugin Name: Advanced Custom Fields Repeater Collapser * Plugin URI: https://github.com/mrwweb/ACF-Repeater-Collapser * Description: Provides a way to collapse and expand repeater field instances in order to enable better sorting. - * Version: 1.3.0 + * Version: 1.4.0 * Author: Mark Root-Wiley * Author URI: http://mrwweb.com */ +define( 'ACF_REPEATER_COLLAPSER_VERSION', '1.4.0' ); + /* Load the javascript and CSS files on the ACF admin pages */ // 11 helps take precedence over core styles add_action( 'acf/input/admin_enqueue_scripts', 'acf_repeater_collapser_assets', 11 ); function acf_repeater_collapser_assets() { - wp_enqueue_script( - 'acf_repeater_collapser_admin_js', - esc_url( plugins_url( 'js/acf_repeater_collapser_admin.js', __FILE__ ) ), - array( 'jquery' ) - ); - wp_enqueue_style( - 'acf_repeater_collapser_admin_css', - esc_url( plugins_url( 'css/acf_repeater_collapser_admin.css', __FILE__ ) ) - ); + $acf_version = acf()->settings['version']; + if( version_compare( $acf_version, '4.0', '>=' ) && version_compare( $acf_version, '5.0', '<' ) ) { + // version 4.X + wp_enqueue_script( + 'acf_repeater_collapser_admin_js', + esc_url( plugins_url( 'js/acf4_repeater_collapser_admin.js', __FILE__ ) ), + array( 'jquery' ), + ACF_REPEATER_COLLAPSER_VERSION + ); + wp_enqueue_style( + 'acf_repeater_collapser_admin_css', + esc_url( plugins_url( 'css/acf4_repeater_collapser_admin.css', __FILE__ ) ), + false, + ACF_REPEATER_COLLAPSER_VERSION + ); + } elseif( version_compare( $acf_version, '5.0', '>=' ) ) { + // version 5.X + wp_enqueue_script( + 'acf_repeater_collapser_admin_js', + esc_url( plugins_url( 'js/acf5_repeater_collapser_admin.js', __FILE__ ) ), + array( 'jquery' ), + ACF_REPEATER_COLLAPSER_VERSION + ); + wp_enqueue_style( + 'acf_repeater_collapser_admin_css', + esc_url( plugins_url( 'css/acf5_repeater_collapser_admin.css', __FILE__ ) ), + false, + ACF_REPEATER_COLLAPSER_VERSION + ); + } } \ No newline at end of file diff --git a/css/acf_repeater_collapser_admin.css b/css/acf4_repeater_collapser_admin.css similarity index 100% rename from css/acf_repeater_collapser_admin.css rename to css/acf4_repeater_collapser_admin.css diff --git a/css/acf5_repeater_collapser_admin.css b/css/acf5_repeater_collapser_admin.css new file mode 100644 index 0000000..02ea4c5 --- /dev/null +++ b/css/acf5_repeater_collapser_admin.css @@ -0,0 +1,92 @@ +/* Expand / Collapse Buttons */ +.field-repeater-toggle-all { + float:right; + margin: -5px 0 15px !important; +} + +.button.field-repeater-toggle-single { + padding: 1px 1px 0 1px; +} +.layout .field-repeater-toggle-single { + margin: 0 5px 0 -5px; +} +.repeater-button-cell { + background: #f9f9f9 !important; + padding: 0 !important; + vertical-align: middle !important; + text-align: center !important; + border-right-color: #e1e1e1 !important; + width: 30px !important; + /* Sorry 'bout the !importants. They are gross. Sadly, so are the ACF selectors I have to battle. */ +} +/* Grossness: http://css-tricks.com/absolutely-position-element-within-a-table-cell/ */ +.repeater-button-cell-div { + position: relative; +} + +.field_type-flexible_content .layout { + padding-left: 30px; + background: transparent; +} + +.field_type-flexible_content .field-repeater-toggle-single { + position: absolute; + top: 50%; + margin-top: -15px; + left: 5px; +} +/* Weird positioning exception */ +.field_type-flexible_content .field_type-repeater .field-repeater-toggle-single { + left: 8px; +} + +.field_type-flexible_content .acf-fc-layout-handle { + background: #fff; +} + +.field-repeater-toggle::before { + content: "\f506"; + display: inline; + -webkit-font-smoothing: antialiased; + font: normal 20px/1 'dashicons'; + vertical-align: text-bottom; + color: #777; +} +.field-repeater-toggle-all::before { + margin: 3px 5px 0 0; +} + +.collapsed-repeater .field-repeater-toggle-all::before, +.collapsed-row .field-repeater-toggle-single::before { + content: "\f211"; +} + +.acf_flexible_content, +.repeater { + clear: both; +} + +.acf-table { + position: relative; +} + +.collapsed-row .acf-table tr { + display: none; +} + +.collapsed-row .acf-table tr:first-child { + display: table-row; +} + +/* "Masks" the first field in order to prevent clicking on it */ +.collapsed-row .acf-table tr::after { + position: absolute; + top: 0; bottom: 0; left: 0px; right: 0; + display: block; + content: ' '; + background: -webkit-gradient(linear, left top, left bottom, from(rgba( 244, 244, 244, .2 )), to(rgba( 244, 244, 244, 1 ))); + background: -o-linear-gradient( rgba( 244, 244, 244, .2 ), rgba( 244, 244, 244, 1 ) ); + background: -moz-linear-gradient( rgba( 244, 244, 244, .2 ), rgba( 244, 244, 244, 1 ) ); + background: -webkit-linear-gradient( rgba( 244, 244, 244, .2 ), rgba( 244, 244, 244, 1 ) ); + background: linear-gradient( rgba( 244, 244, 244, .2 ), rgba( 244, 244, 244, 1 ) ); +} diff --git a/js/acf_repeater_collapser_admin.js b/js/acf4_repeater_collapser_admin.js similarity index 99% rename from js/acf_repeater_collapser_admin.js rename to js/acf4_repeater_collapser_admin.js index 3c85899..4180219 100644 --- a/js/acf_repeater_collapser_admin.js +++ b/js/acf4_repeater_collapser_admin.js @@ -37,7 +37,6 @@ jQuery(document).ready(function($) { // append single repeater collapse to each row of repeater field $('.field_type-repeater .row_layout .row,.field_type-repeater .row_layout .row-clone').each( function() { id = 'acf-repeater-' + i; - i++; $(this).prepend( $collapseSingleButtonTable ) .data('acf-row-collapsed', false).attr('aria-expanded', true) @@ -45,6 +44,8 @@ jQuery(document).ready(function($) { .attr('aria-live','off'); $('.field-repeater-toggle-single', $(this)).first() .attr('aria-controls',id); + + i++; }); // append single repeater collapse to flex fields diff --git a/js/acf5_repeater_collapser_admin.js b/js/acf5_repeater_collapser_admin.js new file mode 100644 index 0000000..bdc3cef --- /dev/null +++ b/js/acf5_repeater_collapser_admin.js @@ -0,0 +1,214 @@ +jQuery(document).ready(function($) { + + /** + * set up the buttons on the entire form + */ + function acfRepeaterCollapserInit() { + // HTML to put above each repeater instance + $collapseAllButton = ''; + $collapseSingleButtonTable = '
'; + $collapseSingleButton = ''; + + // find each repeater & flexible instance, add the button if the field uses the row layout + $('.field_type-repeater, .field_type-flexible_content').each( function() { + $repeater = $(this); + + // only use this on row layout + if( $( '.acf-input-table', $repeater ).hasClass('row-layout') ) { + $repeater.data('acf-rowset-collapsed', false).attr('aria-expanded', false); + + // first: nested, second: parent + if( $repeater.is( 'tr' ) ) { + $( '.acf-repeater', $repeater ).first().prepend( $collapseAllButton ); + $repeater + .data('acf-rowset-collapsed', false) + .data('acf-repeater-nested', true); + $('.acf-row,.acf-row.clone', $repeater ).data('acf-repeater-nested', true); + } else { + $repeater.prepend( $collapseAllButton ) + .data('acf-rowset-collapsed', false); + } + } + }); + + // iterator for adding IDs/aria-controls attributes to repeater buttons + i = 1; + // append single repeater collapse to each row of repeater field + $('.field_type-repeater .row-layout > tbody > .acf-row,.field_type-repeater > tbody > .row-layout .acf-row.clone').each( function() { + id = 'acf-repeater-' + i; + + $(this).prepend( $collapseSingleButtonTable ) + .data('acf-row-collapsed', false) + .attr('aria-expanded', true) + .attr('id','acf-repeater-' + i) + .attr('aria-live','off'); + $('.field-repeater-toggle-single', $(this)).first() + .attr('aria-controls',id); + + i++; + }); + + // append single repeater collapse to flex fields + $('.field_type-flexible_content .layout').each( function() { + if( $('.acf-input-table', $(this)).hasClass('row-layout') ) { + id = 'acf-repeater-' + i; + i++; + + $(this).prepend( $collapseSingleButton ) + .data('acf-row-collapsed', false) + .attr('aria-expanded', true) + .attr('id','acf-repeater-' + i) + .attr('aria-live','off'); + + $('.field-repeater-toggle-single', $(this)).first() + .attr('aria-controls',id); + } + }); + + // Bind click events to the toggle functions + // delegated to higher DOM element to handle dynamically added repeaters + $( '.field_type-repeater, .field_type-flexible_content' ).on( + 'click', + '.field-repeater-toggle-all', + acfRepeaterToggleAll + ); + $( '.field_type-repeater .row-layout,.field_type-flexible_content' ).on( + 'click', + '.field-repeater-toggle-single', + acfRepeaterToggleSingle + ); + + // prevent default flexible field collapsing for clarity + $('.field_type-flexible_content').on( + 'click', + '.acf-fc-layout-handle', + false + ); + } + + /** + * Collapse a row or rows + */ + function acfRepeaterCollapseRow( $rows ) { + $rowButtonText = $('.screen-reader-text', $rows); + $rows.addClass('collapsed-row') + .data('acf-row-collapsed', true) + .attr('aria-expanded', false); + $rowButtonText.text('Expand Row') + } + + /** + * Expand a row or rows + */ + function acfRepeaterExpandRow( $rows ) { + $rowButtonText = $('.screen-reader-text', $rows); + $rows.removeClass('collapsed-row') + .data('acf-row-collapsed', false) + .attr('aria-expanded', true); + $rowButtonText.text('Collapse Row'); + } + + /** + * Indicate a collapsed rowset + */ + function acfRepeaterExpandRowset( $wrapper ) { + $button = $('.field-repeater-toggle-all', $wrapper).first(); + + $wrapper.removeClass('collapsed-repeater') + .data('acf-rowset-collapsed', false); + $button.text('Collapse All Rows'); + } + + /** + * Indicate an expanded rowset + */ + function acfRepeaterCollapseRowset( $wrapper ) { + $button = $('.field-repeater-toggle-all', $wrapper).first(); + + $wrapper.addClass('collapsed-repeater') + .data('acf-rowset-collapsed', true); + $button.text('Expand All Rows'); + } + + /** + * toggles set of repeater rows or flexible fields + */ + function acfRepeaterToggleAll() { + $rowsetButton = $(this); + $rowsetWrapper = $(this).closest('.acf-field'); + + // select either nested or unnested repeater rows, not both + if( true === $rowsetWrapper.data('acf-repeater-nested') ) { + $rows = $('.acf-row:data(acf-repeater-nested),.layout', $rowsetWrapper); + } else { + $rows = $('.acf-row,.layout', $rowsetWrapper).not(':data(acf-repeater-nested)'); + } + + // toggle repeater state and all rows + if( true !== $rowsetWrapper.data('acf-rowset-collapsed') ) { + acfRepeaterCollapseRowset( $rowsetWrapper ); + acfRepeaterCollapseRow( $rows ); + } else { + acfRepeaterExpandRowset( $rowsetWrapper ); + acfRepeaterExpandRow( $rows ); + } + + // prevent bubbling up to parent repeater rowset + event.stopPropagation(); + } + + /** + * toggles single repeater row or flexible field + */ + function acfRepeaterToggleSingle() { + $rowButton = $(this); + $rowButtonText = $('.screen-reader-text', $rowButton); + $row = $rowButton.closest('.acf-row,.layout'); + $rowsetWrapper = $(this).closest('.acf-field'); + + // toggle the row state and button text + if( true !== $row.data('acf-row-collapsed') ) { + acfRepeaterCollapseRow( $row ); + } else { + acfRepeaterExpandRow( $row ); + } + + if( true === acfRepeaterAllCollapsed( $rowsetWrapper ) ) { + acfRepeaterCollapseRowset( $rowsetWrapper ); + } else { + acfRepeaterExpandRowset( $rowsetWrapper ); + } + + // prevent bubbling up to parent row button + event.stopPropagation(); + } + + /** + * check to see if all rows in a rowset are collapsed + * @param obj $rowsetWrapper jquery object + * @return bool true if all rows in rowset are collapsed + */ + function acfRepeaterAllCollapsed( $rowsetWrapper ) { + // select either nested or unnested repeater rows, not both + if( true === $rowsetWrapper.data('acf-repeater-nested') ) { + $rows = $('.acf-row:data(acf-repeater-nested),.layout:data(acf-repeater-nested)', $rowsetWrapper).not('.clone'); + } else { + $rows = $('.acf-row,.values .layout', $rowsetWrapper).not(':data(acf-repeater-nested)').not('.clone'); + } + + // store every row collapsed state in an array + var rowStates = new Array(); + $rows.each( function() { + rowStates.push( $(this).data('acf-row-collapsed') ); + }); + + // check if any rows are expanded + allCollapsed = 0 > $.inArray( false, rowStates ); + + return allCollapsed; + } + + // Initiatilize the plugin + acfRepeaterCollapserInit(); + +}); \ No newline at end of file diff --git a/readme.txt b/readme.txt index f78319a..678a7e8 100644 --- a/readme.txt +++ b/readme.txt @@ -3,8 +3,8 @@ Contributors: mrwweb Tags: advanced custom fields, acf, repeater Requires at least: 3.0.0 Donate link: https://www.networkforgood.org/donation/MakeDonation.aspx?ORGID2=522061398 -Tested up to: 3.9 -Stable tag: 1.3.0 +Tested up to: 4.0-beta3 +Stable tag: 1.4.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -12,37 +12,45 @@ Easier sorting for large repeated fields in the Advanced Custom Fields plugin. == Description == -**This plugin requires both the [Advanced Custom Fields](http://wordpress.org/extend/plugins/advanced-custom-fields/) plugin (version 4.0+ only) *AND* one or both of the [Repeater Field](http://www.advancedcustomfields.com/add-ons/repeater-field/) or [Flexible Content Field](http://www.advancedcustomfields.com/add-ons/flexible-content-field/) paid add-ons (or the PRO plugin for ACF 5.0+).** +The Repeater and Flexible Content Field features make ACF really powerful, but if you have more than a few fields with either, they becomes unwieldy to sort. This plugin collapses each instance of the repeated fields—**only for the "Row"/"Block" layout**—to allow for easy sorting. -Both add-ons make "ACF" much more powerful, but if you have more than few fields in either, it becomes unwieldy to sort. This plugin collapses each instance of the repeated fields—only for the "Row" layout with the Repeater Field—to allow for easy sorting. - -*To help identify each repeater field group when collapsed, the first field of each repeated field group is shown. See the screenshots for an example.* +*To help identify each repeater field group when collapsed, the first field of each repeated field group is shown. See the [screenshots](http://wordpress.org/plugins/advanced-custom-field-repeater-collapser/screenshots/) for an example.* **Want support for the table layout? Sponsor an update!** [Contact me](http://mrwweb.com/contact/) about sponsoring this feature if you're interested. -Contribute to [this plugin on GitHub](https://github.com/mrwweb/ACF-Repeater-Collapser). +------------------------------------------------ -This plugin began as a [support forum thread](http://support.advancedcustomfields.com/discussion/comment/16239) on the ACF site. +**Requirements:** + +* [Advanced Custom Fields](http://wordpress.org/extend/plugins/advanced-custom-fields/) 4.X *AND* one or both of the [Repeater Field](http://www.advancedcustomfields.com/add-ons/repeater-field/) or [Flexible Content Field](http://www.advancedcustomfields.com/add-ons/flexible-content-field/) paid add-ons +* ACF 5.X PRO +* Both ACF 4.X and ACF 5.X: Use the **Row/Block Layout** + +------------------------------------------------ + +Contribute to [this plugin on GitHub](https://github.com/mrwweb/ACF-Repeater-Collapser). -Sponsors: +**Sponsors:** * [Angie Meeker Designs](http://angiemeekerdesigns.com/) — Single Row Collapsing Development -Other contributors: +**Other contributors:** * [brasofilo](http://profiles.wordpress.org/brasofilo/) * [weskoop](http://profiles.wordpress.org/weskoop/) * [philiphetue](https://github.com/philiphetue) +This plugin began as a [support forum thread](http://support.advancedcustomfields.com/discussion/comment/16239) on the ACF site. + == Frequently Asked Questions == = Where is the "Collapse Rows" button? I don't see it. = -The plugin only supports the "Row" layout for repeaters. +The plugin only supports the "Row" layout ("Block" Layout in ACF 5) for repeaters. = Why don't rows collapse? / Why is the row only grayed out? = To help identify each repeater field group when collapsed, the first field of each repeated field group is shown. See the screenshots for an example. -As a work around, use a short field (like a text field or dropdown) as the first row in a repeatable fieldset. +When possible, use a short field (like a text field or dropdown) as the first row in a repeatable fieldset. = Can I collapse individual rows? = Now you can, as of version 1.3.0. @@ -66,8 +74,14 @@ With the addition of the ability to collapse single rows, the button now is alwa 6. New in v1.3.0, you can collapse individual fields! (Thanks to Angie Meeker Designs for sponsoring this feature.) == Changelog == += 1.4.0 (15 Aug 2014) = +* [New] **ACF5 Support** +* [Fix] aria-controls attribute fix +* Note: The plugin supports both ACF4 and ACF5; however, all future feature improvements will only be made for ACF 5.X. +* Did this plugin save you some time or help build a site for a client? [Buy me a beverage!](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=HH4FKZHK75MTN&lc=US&item_name=MRW%20Web%20Design%20%2f%20Mark%20Root%2dWiley&item_number=Buy%20Me%20a%20Beverage%21%20%2d%20ACF%20Repeater%20Collapser¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted). + = 1.3.0 (18 Jul 2014) = -* [New] Ability to collapse single fields +* [New] **Ability to collapse single fields** * [New] Added aria-expanded / aria-controls attributes on rows for improved accessibility * [Tweak] Clarified button text and added icons to buttons for improved UI * [Fix] Fixed errant closing tag on