Skip to content

Commit

Permalink
Internal: Repeater - Implement min_items functionality (#27377)
Browse files Browse the repository at this point in the history
## PR Checklist
<!-- 
Please check if your PR fulfills the following requirements:
**Filling out the template is required.** Any pull request that does not
include enough information to be reviewed in a timely manner may be
closed at the maintainers' discretion.
 -->
- [ ] The commit message follows our guidelines:
https://github.com/elementor/elementor/blob/master/.github/CONTRIBUTING.md


## PR Type
What kind of change does this PR introduce?
<!-- Please check the one that applies to this PR using "x" with no
spaces eg: [x]. -->
- [ ] Bugfix
- [x] Feature
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] CI related changes
- [ ] Documentation content changes
- [ ] Other... Please describe:

## Summary

This PR can be summarized in the following changelog entry:

* Add the ability to set a min number of items for a repeater

## Description
An explanation of what is done in this PR

* Add the ability to set a min number of items for a repeater

## Test instructions
This PR can be tested by following these steps:

*

## Quality assurance

- [x] I have tested this code to the best of my abilities
- [ ] I have added unittests to verify the code works as intended
- [ ] Docs have been added / updated (for bug fixes / features)

Fixes #

---------

Co-authored-by: Louis Wolmarans <louiswol94@gmail.com>
  • Loading branch information
nicoladj77 and louiswol94 committed May 22, 2024
1 parent 04aa234 commit 84996c6
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 5 deletions.
18 changes: 16 additions & 2 deletions assets/dev/js/editor/controls/repeater.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,26 @@ ControlRepeaterItemView = ControlBaseDataView.extend( {
this.$el.toggleClass( 'elementor-repeater-has-maximum-rows', maxItems <= this.collection.length );
},

getMinItems() {
let minItems = 0;

if ( this.model.get( 'min_items' ) && Number.isInteger( this.model.get( 'min_items' ) ) ) {
minItems = this.model.get( 'min_items' );
} else if ( this.model.get( 'prevent_empty' ) ) {
minItems = 1;
}

return minItems;
},

toggleMinRowsClass() {
if ( ! this.model.get( 'prevent_empty' ) ) {
const minItems = this.getMinItems();

if ( ! minItems ) {
return;
}

this.$el.toggleClass( 'elementor-repeater-has-minimum-rows', 1 >= this.collection.length );
this.$el.toggleClass( 'elementor-repeater-has-minimum-rows', minItems >= this.collection.length );
},

updateActiveRow() {
Expand Down
1 change: 1 addition & 0 deletions includes/autoloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ private static function init_classes_map() {
'Frontend' => 'includes/frontend.php',
'Group_Control_Base' => 'includes/controls/groups/base.php',
'Group_Control_Interface' => 'includes/interfaces/group-control.php',
'Has_Validation' => 'includes/interfaces/has-validation.php',
'Heartbeat' => 'includes/heartbeat.php',
'Images_Manager' => 'includes/managers/image.php',
'Maintenance' => 'includes/maintenance.php',
Expand Down
19 changes: 18 additions & 1 deletion includes/controls/repeater.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*
* @since 1.0.0
*/
class Control_Repeater extends Base_Data_Control {
class Control_Repeater extends Base_Data_Control implements Has_Validation {

/**
* Get repeater control type.
Expand Down Expand Up @@ -66,6 +66,7 @@ protected function get_default_settings() {
'prevent_empty' => true,
'is_repeater' => true,
'max_items' => 0,
'min_items' => 0,
'item_actions' => [
'add' => true,
'duplicate' => true,
Expand Down Expand Up @@ -181,4 +182,20 @@ public function content_template() {
<# } #>
<?php
}

public function validate( array $control_data ): bool {
if ( isset( $control_data['min_items'] ) ) {

if (
! isset( $control_data['default'] ) ||
count( $control_data['default'] ) < $control_data['min_items']
) {
throw new \Exception(
__( 'In a Repeater control, if you specify a minimum number of items, you must also specify a default value that contains at least that number of items.', 'elementor' )
);
}
}

return true;
}
}
14 changes: 14 additions & 0 deletions includes/interfaces/has-validation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Elementor;

/**
* Elementor has validation interface.
*
* @param array $control_data The value to validate.
* @return bool True on valid, throws an exception on error.
* @throws \Exception If validation fails.
*/
interface Has_Validation {
public function validate( array $control_data ): bool;
}
9 changes: 9 additions & 0 deletions includes/managers/controls.php
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,15 @@ public function add_control_to_stack( Controls_Stack $element, $control_id, $con
return false;
}

if ( $control_type_instance instanceof Has_Validation ) {
try {
$control_type_instance->validate( $control_data );
} catch ( \Exception $e ) {
_doing_it_wrong( sprintf( '%1$s::%2$s', __CLASS__, __FUNCTION__ ), esc_html( $e->getMessage() ), '3.23.0' );
return false;
}
}

if ( $control_type_instance instanceof Base_Data_Control ) {
$control_default_value = $control_type_instance->get_default_value();

Expand Down
1 change: 0 additions & 1 deletion modules/conversion-center/base/widget-link-in-bio-base.php
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,6 @@ protected function add_cta_controls() {
'max_items' => $config['content']['cta_section']['cta_max'] ?? 0,
'fields' => $repeater->get_controls(),
'title_field' => '{{{ cta_link_text }}}',
'prevent_empty' => true,
'button_text' => esc_html__( 'Add CTA Link', 'elementor' ),
'default' => $config['content']['cta_section']['cta_repeater_defaults'],
]
Expand Down
41 changes: 40 additions & 1 deletion tests/phpunit/elementor/includes/managers/test-controls.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ public function test_get_responsive_control_device_suffix_desktop() {

public function test_get_responsive_control_device_suffix_tablet() {
$device_suffix = Controls_Manager::get_responsive_control_device_suffix( static::$responsive_control_tablet_mock );

$this->assertEquals( '_tablet', $device_suffix );
}

Expand All @@ -58,6 +57,46 @@ public function test_delete_stack() {
$this->assertNull( Plugin::$instance->controls_manager->get_stacks( $widget->get_unique_name() ) );
}

/**
*
* @expectedIncorrectUsage Elementor\Controls_Manager::add_control_to_stack
*/
public function test_control_not_added_if_min_value_greather_than_default_items() {
$control_data = [
'type'=> Controls_Manager::REPEATER,
'tab'=>'content',
'section'=>'section_repeat',
'label'=>'Repeater Type',
'min_items' => 2,
];

$widget = new Mock_Widget( [
'settings' => [],
'id' => '1',
], [] );

$added = Plugin::$instance->controls_manager->add_control_to_stack( $widget, 'repeater_test', $control_data );

$this->assertFalse( $added );

$control_data['default'] = [
['key' => 'value'],
];

$added = Plugin::$instance->controls_manager->add_control_to_stack( $widget, 'repeater_test', $control_data );

$this->assertFalse( $added );

$control_data['default'] = [
['key' => 'value'],
['key' => 'value'],
];

$added = Plugin::$instance->controls_manager->add_control_to_stack( $widget, 'repeater_test', $control_data );

$this->assertTrue( $added );
}

public function test_clear_stack_cache() {
// Arrange
$control_data = [
Expand Down

0 comments on commit 84996c6

Please sign in to comment.