Move conditionalFieldEnabled to EditableFormField as isDisplayed

This commit is contained in:
Maxime Rainville 2020-01-10 17:36:04 +13:00
parent b53619477c
commit a0cedaeb38
4 changed files with 263 additions and 55 deletions

View File

@ -2,6 +2,7 @@
namespace SilverStripe\UserForms\Form;
use InvalidArgumentException;
use SilverStripe\Dev\Debug;
use SilverStripe\Forms\FileField;
use SilverStripe\Forms\FormField;
@ -27,7 +28,7 @@ class UserFormsRequiredFields extends RequiredFields
*
* @param array $data
*
* @return boolean
* @return bool
*/
public function php($data)
{
@ -58,18 +59,14 @@ class UserFormsRequiredFields extends RequiredFields
// get editable form field - owns display rules for field
$editableFormField = $this->getEditableFormFieldByName($fieldName);
$error = false;
// validate if there are no display rules or the field is conditionally visible
if (!$this->hasDisplayRules($editableFormField) ||
$this->conditionalFieldEnabled($editableFormField, $data)) {
$error = $this->validateRequired($formField, $data);
}
// Validate if the field is displayed
$error =
$editableFormField->isDisplayed($data) &&
$this->validateRequired($formField, $data);
// handle error case
if ($formField && $error) {
$this->handleError($formField, $fieldName);
$valid = false;
}
}
@ -77,57 +74,35 @@ class UserFormsRequiredFields extends RequiredFields
return $valid;
}
/**
* Retrieve an Editable Form field by its name.
* @param string $name
* @return EditableFormField
*/
private function getEditableFormFieldByName($name)
{
return EditableFormField::get()->filter(['name' => $name])->first();
}
$field = EditableFormField::get()->filter(['Name' => $name])->first();
private function hasDisplayRules($field)
{
return ($field->DisplayRules()->count() > 0);
}
private function conditionalFieldEnabled($editableFormField, $data)
{
$displayRules = $editableFormField->DisplayRules();
$conjunction = $editableFormField->DisplayRulesConjunctionNice();
$displayed = ($editableFormField->ShowOnLoadNice() === 'show');
// && start with true and find and condition that doesn't satisfy
// || start with false and find and condition that satisfies
$conditionsSatisfied = ($conjunction === '&&');
foreach ($displayRules as $rule) {
$controllingField = EditableFormField::get()->byID($rule->ConditionFieldID);
if ($controllingField->DisplayRules()->count() > 0) { // controllingField is also a conditional field
// recursively check - if any of the dependant fields are hidden, then this field cannot be visible.
if ($this->conditionalFieldEnabled($controllingField, $data)) {
return false;
};
}
$ruleSatisfied = $rule->validateAgainstFormData($data);
if ($conjunction === '||' && $ruleSatisfied) {
$conditionsSatisfied = true;
break;
}
if ($conjunction === '&&' && !$ruleSatisfied) {
$conditionsSatisfied = false;
break;
}
if ($field) {
return $field;
}
// initially displayed - condition fails || initially hidden, condition passes
return ($displayed xor $conditionsSatisfied);
// This should happen if form field data got corrupted
throw new InvalidArgumentException(sprintf(
'Could not find EditableFormField with name `%s`',
$name
));
}
// logic replicated from php() method of parent class SilverStripe\Forms\RequiredFields
// TODO refactor to share with parent (would require corrosponding change in framework)
private function validateRequired($field, $data)
/**
* Check if the validation rules for the specified field are met by the provided data.
*
* @note Logic replicated from php() method of parent class `SilverStripe\Forms\RequiredFields`
* @param EditableFormField $field
* @param array $data
* @return bool
*/
private function validateRequired(FormField $field, array $data)
{
$error = false;
$fieldName = $field->getName();

View File

@ -57,7 +57,7 @@ use Symbiote\GridFieldExtensions\GridFieldEditableColumns;
* @property boolean $ShowOnLoad
* @property string $DisplayRulesConjunction
* @method UserDefinedForm Parent() Parent page
* @method DataList DisplayRules() List of EditableCustomRule objects
* @method DataList|EditableCustomRule[] DisplayRules() List of EditableCustomRule objects
* @mixin Versioned
*/
class EditableFormField extends DataObject
@ -977,6 +977,48 @@ class EditableFormField extends DataObject
return (count($result['selectors'])) ? $result : null;
}
/**
* Check if this EditableFormField is displayed based on its DisplayRules and the provided data.
* @param array $data
* @return bool
*/
public function isDisplayed(array $data)
{
$displayRules = $this->DisplayRules();
if ($displayRules->count() === 0) {
// If no display rule have been defined, isDisplayed equals the ShowOnLoad property
return $this->ShowOnLoad;
}
$conjunction = $this->DisplayRulesConjunctionNice();
// && start with true and find and condition that doesn't satisfy
// || start with false and find and condition that satisfies
$conditionsSatisfied = ($conjunction === '&&');
foreach ($displayRules as $rule) {
$controllingField = $rule->ConditionField();
// recursively check - if any of the dependant fields are hidden, assume the rule can not be satisfied
$ruleSatisfied = $controllingField->isDisplayed($data) && $rule->validateAgainstFormData($data);
if ($conjunction === '||' && $ruleSatisfied) {
$conditionsSatisfied = true;
break;
}
if ($conjunction === '&&' && !$ruleSatisfied) {
$conditionsSatisfied = false;
break;
}
}
// initially displayed - condition fails || initially hidden, condition passes
$startDisplayed = $this->ShowOnLoad;
return ($startDisplayed xor $conditionsSatisfied);
}
/**
* Replaces the set DisplayRulesConjunction with their JS logical operators
* @return string

View File

@ -232,4 +232,58 @@ class EditableFormFieldTest extends FunctionalTest
$this->assertContains('/images/editabletextfield.png', $field->getIcon());
}
public function displayedProvider()
{
$one = ['basic_text_name' => 'foobar'];
$two = array_merge($one, ['basic_text_name_2' => 'foobar']);
return [
'no display rule AND' => ['alwaysVisible', [], true],
'no display rule OR' => ['alwaysVisibleOr', [], true],
'no display rule hidden AND' => ['neverVisible', [], false],
'no display rule hidden OR' => ['neverVisibleOr', [], false],
'1 unmet display rule AND' => ['singleDisplayRule', [], false],
'1 met display rule AND' => ['singleDisplayRule', $one, true],
'1 unmet display rule OR' => ['singleDisplayRuleOr', [], false],
'1 met display rule OR' => ['singleDisplayRuleOr', $one, true],
'1 unmet hide rule AND' => ['singleHiddingRule', [], true],
'1 met hide rule AND' => ['singleHiddingRule', $one, false],
'1 unmet hide rule OR' => ['singleHiddingRuleOr', [], true],
'1 met hide rule OR' => ['singleHiddingRuleOr', $one, false],
'multi display rule AND none met' => ['multiDisplayRule', [], false],
'multi display rule AND partially met' => ['multiDisplayRule', $one, false],
'multi display rule AND all met' => ['multiDisplayRule', $two, true],
'multi display rule OR none met' => ['multiDisplayRuleOr', [], false],
'multi display rule OR partially met' => ['multiDisplayRuleOr', $one, true],
'multi display rule OR all met' => ['multiDisplayRuleOr', $two, true],
'multi hide rule AND none met' => ['multiHiddingRule', [], true],
'multi hide rule AND partially met' => ['multiHiddingRule', $one, true],
'multi hide rule AND all met' => ['multiHiddingRule', $two, false],
'multi hide rule OR none met' => ['multiHiddingRuleOr', [], true],
'multi hide rule OR partially met' => ['multiHiddingRuleOr', $one, false],
'multi hide rule OR all met' => ['multiHiddingRuleOr', $two, false],
];
}
/**
* @param $fieldName
* @param $data
* @param $expected
* @dataProvider displayedProvider
*/
public function testIsDisplayed($fieldName, $data, bool $expected)
{
/** @var EditableFormField $field */
$field = $this->objFromFixture(EditableTextField::class, $fieldName);
$this->assertEquals($expected, $field->isDisplayed($data));
}
}

View File

@ -4,7 +4,7 @@ SilverStripe\UserForms\Model\EditableFormField\EditableTextField:
Title: Basic Text Field
basic-text-2:
Name: basic_text_name
Name: basic_text_name_2
Title: Basic Text Field
required-text:
@ -23,6 +23,83 @@ SilverStripe\UserForms\Model\EditableFormField\EditableTextField:
DisplayRulesConjunction: And
ShowOnLoad: false
# No rule
alwaysVisible:
Name: AlwaysVisible
Title: "This field is always visible"
ShowOnLoad: true
DisplayRulesConjunction: And
alwaysVisibleOr:
Name: AlwaysVisibleOr
Title: "This field is always visible"
ShowOnLoad: true
DisplayRulesConjunction: Or
neverVisible:
Name: NeverVisible
Title: "This field is never visible"
ShowOnLoad: false
DisplayRulesConjunction: And
neverVisibleOr:
Name: NeverVisibleOr
Title: "This field is never visible"
ShowOnLoad: false
DisplayRulesConjunction: Or
# Single rule
singleDisplayRule:
Name: SingleDisplayRule
Title: "This field will be displayed if the display rule is tripped"
ShowOnLoad: false
DisplayRulesConjunction: And
singleDisplayRuleOr:
Name: SingleDisplayRuleOr
Title: "This field will be displayed if the display rule is tripped"
ShowOnLoad: false
DisplayRulesConjunction: Or
singleHiddingRule:
Name: SingleHiddingRule
Title: "This field will be hidden if the display rule is tripped"
ShowOnLoad: true
DisplayRulesConjunction: And
singleHiddingRuleOr:
Name: SingleHiddingRuleOr
Title: "This field will be hidden if the display rule is tripped"
ShowOnLoad: true
DisplayRulesConjunction: Or
# Multi rule
multiDisplayRule:
Name: MultiDisplayRule
Title: "This field will be displayed if displayed if all the rule are met"
ShowOnLoad: false
DisplayRulesConjunction: And
multiDisplayRuleOr:
Name: MultiDisplayRuleOr
Title: "This field will be displayed if at least one rule is met"
ShowOnLoad: false
DisplayRulesConjunction: Or
multiHiddingRule:
Name: MultiHiddingRule
Title: "This field will be hidden if all the rule are met"
ShowOnLoad: true
DisplayRulesConjunction: And
multiHiddingRuleOr:
Name: MultiHiddingRuleOr
Title: "This field will be hidden if one rule is met"
ShowOnLoad: true
DisplayRulesConjunction: Or
SilverStripe\UserForms\Model\EditableCustomRule:
rule1:
Display: Show
@ -35,6 +112,66 @@ SilverStripe\UserForms\Model\EditableCustomRule:
ConditionOption: HasValue
FieldValue: 6
# Single rules
ruleSingleDisplay:
Display: Show
ConditionOption: IsNotBlank
ConditionField: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.basic-text
Parent: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.singleDisplayRule
ruleSingleDisplayOr:
Display: Show
ConditionOption: IsNotBlank
ConditionField: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.basic-text
Parent: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.singleDisplayRuleOr
ruleSingleHidding:
Display: Show
ConditionOption: IsNotBlank
ConditionField: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.basic-text
Parent: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.singleHiddingRule
ruleSingleHiddingOr:
Display: Show
ConditionOption: IsNotBlank
ConditionField: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.basic-text
Parent: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.singleHiddingRuleOr
# Multi rules
ruleMultiDisplay1:
ConditionOption: IsNotBlank
ConditionField: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.basic-text
Parent: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.multiDisplayRule
ruleMultiDisplay2:
ConditionOption: IsNotBlank
ConditionField: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.basic-text-2
Parent: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.multiDisplayRule
ruleMultiDisplayOr1:
ConditionOption: IsNotBlank
ConditionField: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.basic-text
Parent: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.multiDisplayRuleOr
ruleMultiDisplayOr2:
ConditionOption: IsNotBlank
ConditionField: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.basic-text-2
Parent: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.multiDisplayRuleOr
ruleMultiHidding1:
ConditionOption: IsNotBlank
ConditionField: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.basic-text
Parent: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.multiHiddingRule
ruleMultiHidding2:
ConditionOption: IsNotBlank
ConditionField: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.basic-text-2
Parent: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.multiHiddingRule
ruleMultiHiddingOr1:
ConditionOption: IsNotBlank
ConditionField: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.basic-text
Parent: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.multiHiddingRuleOr
ruleMultiHiddingOr2:
ConditionOption: IsNotBlank
ConditionField: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.basic-text-2
Parent: =>SilverStripe\UserForms\Model\EditableFormField\EditableTextField.multiHiddingRuleOr
SilverStripe\UserForms\Model\EditableFormField\EditableOption:
option-1:
Name: Option1