mirror of
https://github.com/silverstripe/silverstripe-userforms.git
synced 2024-10-22 15:05:42 +00:00
Enhancement: Implemented and/or display rules for UserForms
This commit is contained in:
parent
916dbf42c7
commit
a94f0e35aa
@ -6,6 +6,10 @@
|
|||||||
*
|
*
|
||||||
* @method EditableFormField Parent()
|
* @method EditableFormField Parent()
|
||||||
* @package userforms
|
* @package userforms
|
||||||
|
*
|
||||||
|
* @property string Display
|
||||||
|
* @property string ConditionOption
|
||||||
|
* @property string FieldValue
|
||||||
*/
|
*/
|
||||||
class EditableCustomRule extends DataObject
|
class EditableCustomRule extends DataObject
|
||||||
{
|
{
|
||||||
@ -147,4 +151,91 @@ class EditableCustomRule extends DataObject
|
|||||||
{
|
{
|
||||||
return $this->canDelete($member);
|
return $this->canDelete($member);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Substitutes configured rule logic with it's JS equivalents and returns them as array elements
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function buildExpression()
|
||||||
|
{
|
||||||
|
/** @var EditableFormField $formFieldWatch */
|
||||||
|
$formFieldWatch = $this->ConditionField();
|
||||||
|
//Encapsulated the action to the object
|
||||||
|
$action = $formFieldWatch->getJsEventHandler();
|
||||||
|
|
||||||
|
// is this field a special option field
|
||||||
|
$checkboxField = $formFieldWatch->isCheckBoxField();
|
||||||
|
$radioField = $formFieldWatch->isRadioField();
|
||||||
|
$target = sprintf('$("%s")', $formFieldWatch->getSelectorFieldOnly());
|
||||||
|
$fieldValue = Convert::raw2js($this->FieldValue);
|
||||||
|
|
||||||
|
$conditionOptions = array(
|
||||||
|
'ValueLessThan' => '<',
|
||||||
|
'ValueLessThanEqual' => '<=',
|
||||||
|
'ValueGreaterThan' => '>',
|
||||||
|
'ValueGreaterThanEqual' => '>='
|
||||||
|
);
|
||||||
|
// and what should we evaluate
|
||||||
|
switch ($this->ConditionOption) {
|
||||||
|
case 'IsNotBlank':
|
||||||
|
case 'IsBlank':
|
||||||
|
$expression = ($checkboxField || $radioField) ? "!{$target}.is(\":checked\")" : "{$target}.val() == ''";
|
||||||
|
if ($this->ConditionOption == 'IsNotBlank') {
|
||||||
|
//Negate
|
||||||
|
$expression = "!({$expression})";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'HasValue':
|
||||||
|
case 'ValueNot':
|
||||||
|
if ($checkboxField) {
|
||||||
|
if ($formFieldWatch->isCheckBoxGroupField()) {
|
||||||
|
$expression = sprintf("$.inArray('%s', %s.filter(':checked').map(function(){ return $(this).val();}).get()) > -1",
|
||||||
|
$fieldValue, $target);
|
||||||
|
} else {
|
||||||
|
$expression = "{$target}.prop('checked')";
|
||||||
|
}
|
||||||
|
} elseif ($radioField) {
|
||||||
|
// We cannot simply get the value of the radio group, we need to find the checked option first.
|
||||||
|
$expression = sprintf('%s.closest(".field, .control-group").find("input:checked").val() == "%s"',
|
||||||
|
$target, $fieldValue);
|
||||||
|
} else {
|
||||||
|
$expression = sprintf('%s.val() == "%s"', $target, $fieldValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->ConditionOption == 'ValueNot') {
|
||||||
|
//Negate
|
||||||
|
$expression = "!({$expression})";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'ValueLessThan':
|
||||||
|
case 'ValueLessThanEqual':
|
||||||
|
case 'ValueGreaterThan':
|
||||||
|
case 'ValueGreaterThanEqual':
|
||||||
|
$expression = sprintf('%s.val() %s parseFloat("%s")', $target,
|
||||||
|
$conditionOptions[$this->ConditionOption], $fieldValue);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new LogicException("Unhandled rule {$this->ConditionOption}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = array(
|
||||||
|
'operation' => $expression,
|
||||||
|
'event' => $action,
|
||||||
|
);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the opposite of the show/hide pairs of strings
|
||||||
|
*
|
||||||
|
* @param string $text
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function toggleDisplayText($text)
|
||||||
|
{
|
||||||
|
return (strtolower($text) === 'show') ? 'hide' : 'show';
|
||||||
|
}
|
||||||
}
|
}
|
@ -454,183 +454,29 @@ class UserDefinedForm_Controller extends Page_Controller
|
|||||||
$rules = "";
|
$rules = "";
|
||||||
|
|
||||||
$watch = array();
|
$watch = array();
|
||||||
$watchLoad = array();
|
|
||||||
|
|
||||||
if ($this->Fields()) {
|
if ($this->Fields()) {
|
||||||
|
/** @var EditableFormField $field */
|
||||||
foreach ($this->Fields() as $field) {
|
foreach ($this->Fields() as $field) {
|
||||||
$holderSelector = $field->getSelectorHolder();
|
if ($result = $field->formatDisplayRules()) {
|
||||||
|
$watch[] = $result;
|
||||||
// Is this Field Show by Default
|
|
||||||
if (!$field->ShowOnLoad) {
|
|
||||||
$default .= "{$holderSelector}.hide().trigger('userform.field.hide');\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for field dependencies / default
|
|
||||||
foreach ($field->EffectiveDisplayRules() as $rule) {
|
|
||||||
|
|
||||||
// Get the field which is effected
|
|
||||||
$formFieldWatch = EditableFormField::get()->byId($rule->ConditionFieldID);
|
|
||||||
|
|
||||||
// Skip deleted fields
|
|
||||||
if (!$formFieldWatch) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fieldToWatch = $formFieldWatch->getSelectorField($rule);
|
|
||||||
$fieldToWatchOnLoad = $formFieldWatch->getSelectorField($rule, true);
|
|
||||||
|
|
||||||
// show or hide?
|
|
||||||
$view = ($rule->Display == 'Hide') ? 'hide' : 'show';
|
|
||||||
$opposite = ($view == "show") ? "hide" : "show";
|
|
||||||
|
|
||||||
// what action do we need to keep track of. Something nicer here maybe?
|
|
||||||
// @todo encapulsation
|
|
||||||
$action = "change";
|
|
||||||
|
|
||||||
if ($formFieldWatch instanceof EditableTextField) {
|
|
||||||
$action = "keyup";
|
|
||||||
}
|
|
||||||
|
|
||||||
// is this field a special option field
|
|
||||||
$checkboxField = false;
|
|
||||||
$radioField = false;
|
|
||||||
|
|
||||||
if (in_array($formFieldWatch->ClassName, array('EditableCheckboxGroupField', 'EditableCheckbox'))) {
|
|
||||||
$action = "click";
|
|
||||||
$checkboxField = true;
|
|
||||||
} elseif ($formFieldWatch->ClassName == "EditableRadioField") {
|
|
||||||
$radioField = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// and what should we evaluate
|
|
||||||
switch ($rule->ConditionOption) {
|
|
||||||
case 'IsNotBlank':
|
|
||||||
$expression = ($checkboxField || $radioField) ? '$(this).is(":checked")' :'$(this).val() != ""';
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'IsBlank':
|
|
||||||
$expression = ($checkboxField || $radioField) ? '!($(this).is(":checked"))' : '$(this).val() == ""';
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'HasValue':
|
|
||||||
if ($checkboxField) {
|
|
||||||
$expression = '$(this).prop("checked")';
|
|
||||||
} elseif ($radioField) {
|
|
||||||
// We cannot simply get the value of the radio group, we need to find the checked option first.
|
|
||||||
$expression = '$(this).parents(".field, .control-group").find("input:checked").val()=="'. $rule->FieldValue .'"';
|
|
||||||
} else {
|
|
||||||
$expression = '$(this).val() == "'. $rule->FieldValue .'"';
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'ValueLessThan':
|
|
||||||
$expression = '$(this).val() < parseFloat("'. $rule->FieldValue .'")';
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'ValueLessThanEqual':
|
|
||||||
$expression = '$(this).val() <= parseFloat("'. $rule->FieldValue .'")';
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'ValueGreaterThan':
|
|
||||||
$expression = '$(this).val() > parseFloat("'. $rule->FieldValue .'")';
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'ValueGreaterThanEqual':
|
|
||||||
$expression = '$(this).val() >= parseFloat("'. $rule->FieldValue .'")';
|
|
||||||
|
|
||||||
break;
|
|
||||||
default: // ==HasNotValue
|
|
||||||
if ($checkboxField) {
|
|
||||||
$expression = '!$(this).prop("checked")';
|
|
||||||
} elseif ($radioField) {
|
|
||||||
// We cannot simply get the value of the radio group, we need to find the checked option first.
|
|
||||||
$expression = '$(this).parents(".field, .control-group").find("input:checked").val()!="'. $rule->FieldValue .'"';
|
|
||||||
} else {
|
|
||||||
$expression = '$(this).val() != "'. $rule->FieldValue .'"';
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($watch[$fieldToWatch])) {
|
|
||||||
$watch[$fieldToWatch] = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
$watch[$fieldToWatch][] = array(
|
|
||||||
'expression' => $expression,
|
|
||||||
'holder_selector' => $holderSelector,
|
|
||||||
'view' => $view,
|
|
||||||
'opposite' => $opposite,
|
|
||||||
'action' => $action
|
|
||||||
);
|
|
||||||
|
|
||||||
$watchLoad[$fieldToWatchOnLoad] = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($watch) {
|
if ($watch) {
|
||||||
foreach ($watch as $key => $values) {
|
$rules .= $this->buildWatchJS($watch);
|
||||||
$logic = array();
|
|
||||||
$actions = array();
|
|
||||||
|
|
||||||
foreach ($values as $rule) {
|
|
||||||
// Assign action
|
|
||||||
$actions[$rule['action']] = $rule['action'];
|
|
||||||
|
|
||||||
// Assign behaviour
|
|
||||||
$expression = $rule['expression'];
|
|
||||||
$holder = $rule['holder_selector'];
|
|
||||||
$view = $rule['view']; // hide or show
|
|
||||||
$opposite = $rule['opposite'];
|
|
||||||
// Generated javascript for triggering visibility
|
|
||||||
$logic[] = <<<"EOS"
|
|
||||||
if({$expression}) {
|
|
||||||
{$holder}
|
|
||||||
.{$view}()
|
|
||||||
.trigger('userform.field.{$view}');
|
|
||||||
} else {
|
|
||||||
{$holder}
|
|
||||||
.{$opposite}()
|
|
||||||
.trigger('userform.field.{$opposite}');
|
|
||||||
}
|
|
||||||
EOS;
|
|
||||||
}
|
|
||||||
|
|
||||||
$logic = implode("\n", $logic);
|
|
||||||
$rules .= $key.".each(function() {\n
|
|
||||||
$(this).data('userformConditions', function() {\n
|
|
||||||
$logic\n
|
|
||||||
}); \n
|
|
||||||
});\n";
|
|
||||||
foreach ($actions as $action) {
|
|
||||||
$rules .= $key.".$action(function() {
|
|
||||||
$(this).data('userformConditions').call(this);\n
|
|
||||||
});\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($watchLoad) {
|
|
||||||
foreach ($watchLoad as $key => $value) {
|
|
||||||
$rules .= $key.".each(function() {
|
|
||||||
$(this).data('userformConditions').call(this);\n
|
|
||||||
});\n";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only add customScript if $default or $rules is defined
|
// Only add customScript if $default or $rules is defined
|
||||||
if ($default || $rules) {
|
if ($rules) {
|
||||||
Requirements::customScript(<<<JS
|
Requirements::customScript(<<<JS
|
||||||
(function($) {
|
(function($) {
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$default
|
{$rules}
|
||||||
|
});
|
||||||
$rules
|
})(jQuery);
|
||||||
})
|
|
||||||
})(jQuery);
|
|
||||||
JS
|
JS
|
||||||
, 'UserFormsConditional');
|
, 'UserFormsConditional');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -897,4 +743,46 @@ JS
|
|||||||
'Form' => '',
|
'Form' => '',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outputs the required JS from the $watch input
|
||||||
|
*
|
||||||
|
* @param array $watch
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function buildWatchJS($watch)
|
||||||
|
{
|
||||||
|
$result = '';
|
||||||
|
foreach ($watch as $key => $rule) {
|
||||||
|
$events = implode(' ', $rule['events']);
|
||||||
|
$selectors = implode(', ', $rule['selectors']);
|
||||||
|
$conjunction = $rule['conjunction'];
|
||||||
|
$operations = implode(" {$conjunction} ", $rule['operations']);
|
||||||
|
$target = $rule['targetFieldID'];
|
||||||
|
$initialState = $rule['initialState'];
|
||||||
|
$view = $rule['view'];
|
||||||
|
$opposite = $rule['opposite'];
|
||||||
|
|
||||||
|
$result .= <<<EOS
|
||||||
|
\n
|
||||||
|
//Initial state
|
||||||
|
$('{$target}').{$initialState}();
|
||||||
|
|
||||||
|
$('.userform').on('{$events}',
|
||||||
|
"{$selectors}",
|
||||||
|
function (){
|
||||||
|
if({$operations}) {
|
||||||
|
$('{$target}').{$view}();
|
||||||
|
} else {
|
||||||
|
$('{$target}').{$opposite}();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
EOS;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ class EditableCheckbox extends EditableFormField
|
|||||||
|
|
||||||
private static $plural_name = 'Checkboxes';
|
private static $plural_name = 'Checkboxes';
|
||||||
|
|
||||||
|
protected $jsEventHandler = 'click';
|
||||||
|
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
'CheckedDefault' => 'Boolean' // from CustomSettings
|
'CheckedDefault' => 'Boolean' // from CustomSettings
|
||||||
);
|
);
|
||||||
@ -61,4 +63,8 @@ class EditableCheckbox extends EditableFormField
|
|||||||
|
|
||||||
parent::migrateSettings($data);
|
parent::migrateSettings($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isCheckBoxField() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ class EditableCheckboxGroupField extends EditableMultipleOptionField
|
|||||||
|
|
||||||
private static $plural_name = "Checkbox Groups";
|
private static $plural_name = "Checkbox Groups";
|
||||||
|
|
||||||
|
protected $jsEventHandler = 'click';
|
||||||
|
|
||||||
public function getFormField()
|
public function getFormField()
|
||||||
{
|
{
|
||||||
$field = new UserFormsCheckboxSetField($this->Name, $this->EscapedTitle, $this->getOptionsMap());
|
$field = new UserFormsCheckboxSetField($this->Name, $this->EscapedTitle, $this->getOptionsMap());
|
||||||
@ -59,4 +61,18 @@ class EditableCheckboxGroupField extends EditableMultipleOptionField
|
|||||||
return "$(\"input[name='{$this->Name}[]']:first\")";
|
return "$(\"input[name='{$this->Name}[]']:first\")";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isCheckBoxField() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSelectorFieldOnly()
|
||||||
|
{
|
||||||
|
return "[name='{$this->Name}[]']";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isCheckBoxGroupField()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ use SilverStripe\Forms\SegmentField;
|
|||||||
* @property int $Sort
|
* @property int $Sort
|
||||||
* @property bool $Required
|
* @property bool $Required
|
||||||
* @property string $CustomErrorMessage
|
* @property string $CustomErrorMessage
|
||||||
|
* @property boolean $ShowOnLoad
|
||||||
|
* @property string $DisplayRulesConjunction
|
||||||
* @method UserDefinedForm Parent() Parent page
|
* @method UserDefinedForm Parent() Parent page
|
||||||
* @method DataList DisplayRules() List of EditableCustomRule objects
|
* @method DataList DisplayRules() List of EditableCustomRule objects
|
||||||
*/
|
*/
|
||||||
@ -87,8 +89,10 @@ class EditableFormField extends DataObject
|
|||||||
"RightTitle" => "Varchar(255)", // from CustomSettings
|
"RightTitle" => "Varchar(255)", // from CustomSettings
|
||||||
"ShowOnLoad" => "Boolean(1)", // from CustomSettings
|
"ShowOnLoad" => "Boolean(1)", // from CustomSettings
|
||||||
"ShowInSummary" => "Boolean",
|
"ShowInSummary" => "Boolean",
|
||||||
|
'DisplayRulesConjunction' => 'Enum("And,Or","Or")',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
private static $defaults = array(
|
private static $defaults = array(
|
||||||
'ShowOnLoad' => true,
|
'ShowOnLoad' => true,
|
||||||
);
|
);
|
||||||
@ -125,6 +129,22 @@ class EditableFormField extends DataObject
|
|||||||
*/
|
*/
|
||||||
protected $readonly;
|
protected $readonly;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property holds the JS event which gets fired for this type of element
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $jsEventHandler = 'change';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the jsEventHandler property for the current object. Bearing in mind it could've been overridden.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getJsEventHandler()
|
||||||
|
{
|
||||||
|
return $this->jsEventHandler;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the visibility of an individual form field
|
* Set the visibility of an individual form field
|
||||||
*
|
*
|
||||||
@ -244,44 +264,36 @@ class EditableFormField extends DataObject
|
|||||||
// Check display rules
|
// Check display rules
|
||||||
if ($this->Required) {
|
if ($this->Required) {
|
||||||
return new FieldList(
|
return new FieldList(
|
||||||
LabelField::create(_t(
|
LabelField::create(
|
||||||
'EditableFormField.DISPLAY_RULES_DISABLED',
|
_t(
|
||||||
'Display rules are not enabled for required fields. ' .
|
'EditableFormField.DISPLAY_RULES_DISABLED',
|
||||||
'Please uncheck "Is this field Required?" under "Validation" to re-enable.'
|
'Display rules are not enabled for required fields. Please uncheck "Is this field Required?" under "Validation" to re-enable.'))
|
||||||
))->addExtraClass('message warning')
|
->addExtraClass('message warning'));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
$self = $this;
|
$self = $this;
|
||||||
$allowedClasses = array_keys($this->getEditableFieldClasses(false));
|
$allowedClasses = array_keys($this->getEditableFieldClasses(false));
|
||||||
$editableColumns = new GridFieldEditableColumns();
|
$editableColumns = new GridFieldEditableColumns();
|
||||||
$editableColumns->setDisplayFields(array(
|
$editableColumns->setDisplayFields(array(
|
||||||
'Display' => '',
|
'ConditionFieldID' => function ($record, $column, $grid) use ($allowedClasses, $self) {
|
||||||
'ConditionFieldID' => function ($record, $column, $grid) use ($allowedClasses, $self) {
|
return DropdownField::create($column, '', EditableFormField::get()->filter(array(
|
||||||
return DropdownField::create(
|
'ParentID' => $self->ParentID,
|
||||||
$column,
|
'ClassName' => $allowedClasses,
|
||||||
'',
|
))->exclude(array(
|
||||||
EditableFormField::get()
|
'ID' => $self->ID,
|
||||||
->filter(array(
|
))->map('ID', 'Title'));
|
||||||
'ParentID' => $self->ParentID,
|
},
|
||||||
'ClassName' => $allowedClasses
|
'ConditionOption' => function ($record, $column, $grid) {
|
||||||
))
|
$options = Config::inst()->get('EditableCustomRule', 'condition_options');
|
||||||
->exclude(array(
|
|
||||||
'ID' => $self->ID
|
return DropdownField::create($column, '', $options);
|
||||||
))
|
},
|
||||||
->map('ID', 'Title')
|
'FieldValue' => function ($record, $column, $grid) {
|
||||||
);
|
return TextField::create($column);
|
||||||
},
|
},
|
||||||
'ConditionOption' => function ($record, $column, $grid) {
|
'ParentID' => function ($record, $column, $grid) use ($self) {
|
||||||
$options = Config::inst()->get('EditableCustomRule', 'condition_options');
|
return HiddenField::create($column, '', $self->ID);
|
||||||
return DropdownField::create($column, '', $options);
|
},
|
||||||
},
|
));
|
||||||
'FieldValue' => function ($record, $column, $grid) {
|
|
||||||
return TextField::create($column);
|
|
||||||
},
|
|
||||||
'ParentID' => function ($record, $column, $grid) use ($self) {
|
|
||||||
return HiddenField::create($column, '', $self->ID);
|
|
||||||
}
|
|
||||||
));
|
|
||||||
|
|
||||||
// Custom rules
|
// Custom rules
|
||||||
$customRulesConfig = GridFieldConfig::create()
|
$customRulesConfig = GridFieldConfig::create()
|
||||||
@ -294,11 +306,20 @@ class EditableFormField extends DataObject
|
|||||||
);
|
);
|
||||||
|
|
||||||
return new FieldList(
|
return new FieldList(
|
||||||
CheckboxField::create('ShowOnLoad')
|
DropdownField::create('ShowOnLoad',
|
||||||
->setDescription(_t(
|
_t('EditableFormField.INITIALVISIBILITY', 'Initial visibility'),
|
||||||
'EditableFormField.SHOWONLOAD',
|
array(
|
||||||
'Initial visibility before processing these rules'
|
1 => 'Show',
|
||||||
)),
|
0 => 'Hide',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
DropdownField::create('DisplayRulesConjunction',
|
||||||
|
_t('EditableFormField.DISPLAYIF', 'Toggle visibility when'),
|
||||||
|
array(
|
||||||
|
'Or' => _t('UserDefinedForm.SENDIFOR', 'Any conditions are true'),
|
||||||
|
'And' => _t('UserDefinedForm.SENDIFAND', 'All conditions are true'),
|
||||||
|
)
|
||||||
|
),
|
||||||
GridField::create(
|
GridField::create(
|
||||||
'DisplayRules',
|
'DisplayRules',
|
||||||
_t('EditableFormField.CUSTOMRULES', 'Custom Rules'),
|
_t('EditableFormField.CUSTOMRULES', 'Custom Rules'),
|
||||||
@ -489,6 +510,7 @@ class EditableFormField extends DataObject
|
|||||||
{
|
{
|
||||||
$this->publish($fromStage, $toStage, $createNewVersion);
|
$this->publish($fromStage, $toStage, $createNewVersion);
|
||||||
|
|
||||||
|
|
||||||
$seenIDs = array();
|
$seenIDs = array();
|
||||||
|
|
||||||
// Don't forget to publish the related custom rules...
|
// Don't forget to publish the related custom rules...
|
||||||
@ -899,18 +921,38 @@ class EditableFormField extends DataObject
|
|||||||
*/
|
*/
|
||||||
public function getSelectorHolder()
|
public function getSelectorHolder()
|
||||||
{
|
{
|
||||||
return "$(\"#{$this->Name}\")";
|
return sprintf('$("%s")', $this->getSelectorOnly());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns only the JS identifier of a string, less the $(), which can be inserted elsewhere, for example when you
|
||||||
|
* want to perform selections on multiple selectors
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getSelectorOnly()
|
||||||
|
{
|
||||||
|
return "#{$this->Name}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the JS expression for selecting the value for this field
|
* Gets the JS expression for selecting the value for this field
|
||||||
*
|
*
|
||||||
* @param EditableCustomRule $rule Custom rule this selector will be used with
|
* @param EditableCustomRule $rule Custom rule this selector will be used with
|
||||||
* @param bool $forOnLoad Set to true if this will be invoked on load
|
* @param bool $forOnLoad Set to true if this will be invoked on load
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getSelectorField(EditableCustomRule $rule, $forOnLoad = false)
|
public function getSelectorField(EditableCustomRule $rule, $forOnLoad = false)
|
||||||
{
|
{
|
||||||
return "$(\"input[name='{$this->Name}']\")";
|
return sprintf("$(%s)", $this->getSelectorFieldOnly());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getSelectorFieldOnly()
|
||||||
|
{
|
||||||
|
return "[name='{$this->Name}']";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -970,4 +1012,95 @@ class EditableFormField extends DataObject
|
|||||||
}
|
}
|
||||||
return $this->DisplayRules();
|
return $this->DisplayRules();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts info from DisplayRules into array so UserDefinedForm->buildWatchJS can run through it.
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function formatDisplayRules()
|
||||||
|
{
|
||||||
|
$holderSelector = $this->getSelectorOnly();
|
||||||
|
$result = array(
|
||||||
|
'targetFieldID' => $holderSelector,
|
||||||
|
'conjunction' => $this->DisplayRulesConjunctionNice(),
|
||||||
|
'selectors' => array(),
|
||||||
|
'events' => array(),
|
||||||
|
'operations' => array(),
|
||||||
|
'initialState' => $this->ShowOnLoadNice(),
|
||||||
|
'view' => array(),
|
||||||
|
'opposite' => array(),
|
||||||
|
);
|
||||||
|
// Check for field dependencies / default
|
||||||
|
/** @var EditableCustomRule $rule */
|
||||||
|
foreach ($this->EffectiveDisplayRules() as $rule) {
|
||||||
|
// Get the field which is effected
|
||||||
|
/** @var EditableFormField $formFieldWatch */
|
||||||
|
$formFieldWatch = EditableFormField::get()->byId($rule->ConditionFieldID);
|
||||||
|
// Skip deleted fields
|
||||||
|
if (! $formFieldWatch) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$fieldToWatch = $formFieldWatch->getSelectorFieldOnly();
|
||||||
|
|
||||||
|
$expression = $rule->buildExpression();
|
||||||
|
if (! in_array($fieldToWatch, $result['selectors'])) {
|
||||||
|
$result['selectors'][] = $fieldToWatch;
|
||||||
|
}
|
||||||
|
if (! in_array($expression['event'], $result['events'])) {
|
||||||
|
$result['events'][] = $expression['event'];
|
||||||
|
}
|
||||||
|
$result['operations'][] = $expression['operation'];
|
||||||
|
|
||||||
|
//View/Show should read
|
||||||
|
$result['view'] = $rule->toggleDisplayText($result['initialState']);
|
||||||
|
$result['opposite'] = $rule->toggleDisplayText($result['view']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (count($result['selectors'])) ? $result : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the set DisplayRulesConjunction with their JS logical operators
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function DisplayRulesConjunctionNice()
|
||||||
|
{
|
||||||
|
return (strtolower($this->DisplayRulesConjunction) === 'or') ? '||' : '&&';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces boolean ShowOnLoad with its JS string equivalent
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function ShowOnLoadNice()
|
||||||
|
{
|
||||||
|
return ($this->ShowOnLoad) ? 'show' : 'hide';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this is of type EditableCheckBoxField
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isCheckBoxField()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this is of type EditableRadioField
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isRadioField()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determined is this is of type EditableCheckboxGroupField
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isCheckBoxGroupField()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,4 +46,8 @@ class EditableRadioField extends EditableMultipleOptionField
|
|||||||
$first = $forOnLoad ? ':first' : '';
|
$first = $forOnLoad ? ':first' : '';
|
||||||
return "$(\"input[name='{$this->Name}']{$first}\")";
|
return "$(\"input[name='{$this->Name}']{$first}\")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isRadioField() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,8 @@ class EditableTextField extends EditableFormField
|
|||||||
'url' => 'Home page'
|
'url' => 'Home page'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
protected $jsEventHandler = 'keyup';
|
||||||
|
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
'MinLength' => 'Int',
|
'MinLength' => 'Int',
|
||||||
'MaxLength' => 'Int',
|
'MaxLength' => 'Int',
|
||||||
|
31
tests/EditableCustomRuleTest.php
Normal file
31
tests/EditableCustomRuleTest.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class EditableCustomRulesTest
|
||||||
|
*/
|
||||||
|
class EditableCustomRuleTest extends SapphireTest
|
||||||
|
{
|
||||||
|
protected static $fixture_file = 'userforms/tests/EditableCustomRuleTest.yml';
|
||||||
|
|
||||||
|
public function testBuildExpression()
|
||||||
|
{
|
||||||
|
/** @var EditableCustomRule $rule1 */
|
||||||
|
$rule1 = $this->objFromFixture('EditableCustomRule', 'rule1');
|
||||||
|
$result1 = $rule1->buildExpression();
|
||||||
|
|
||||||
|
//Dropdowns expect change event
|
||||||
|
$this->assertEquals('change', $result1['event']);
|
||||||
|
$this->assertNotEmpty($result1['operation']);
|
||||||
|
//Check for equals sign
|
||||||
|
$this->assertContains('==', $result1['operation']);
|
||||||
|
|
||||||
|
/** @var EditableCustomRule $rule2 */
|
||||||
|
$rule2 = $this->objFromFixture('EditableCustomRule', 'rule2');
|
||||||
|
$result2 = $rule2->buildExpression();
|
||||||
|
//TextField expect change event
|
||||||
|
$this->assertEquals('keyup', $result2['event']);
|
||||||
|
$this->assertNotEmpty($result2['operation']);
|
||||||
|
//Check for greater than sign
|
||||||
|
$this->assertContains('>', $result2['operation']);
|
||||||
|
}
|
||||||
|
}
|
30
tests/EditableCustomRuleTest.yml
Normal file
30
tests/EditableCustomRuleTest.yml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
EditableFormField:
|
||||||
|
countryDropdown:
|
||||||
|
ClassName: EditableCountryDropdownField
|
||||||
|
Name: CountrySelection
|
||||||
|
Title: "Choose your country"
|
||||||
|
DisplayRulesConjunction: And
|
||||||
|
ShowOnLoad: false
|
||||||
|
irdNumberField:
|
||||||
|
ClassName: EditableTextField
|
||||||
|
Name: IRDNumber
|
||||||
|
Title: "Enter your IRD Number"
|
||||||
|
countryTextField:
|
||||||
|
ClassName: EditableTextField
|
||||||
|
Name: CountryTextSelection
|
||||||
|
Title: "Enter your country (2 digit prefix)"
|
||||||
|
DisplayRulesConjunction: And
|
||||||
|
ShowOnLoad: false
|
||||||
|
EditableCustomRule:
|
||||||
|
rule1:
|
||||||
|
Display: Show
|
||||||
|
ConditionOption: HasValue
|
||||||
|
FieldValue: NZ
|
||||||
|
ConditionField: =>EditableFormField.countryDropdown
|
||||||
|
Parent: =>EditableFormField.irdNumberField
|
||||||
|
rule2:
|
||||||
|
Display: Show
|
||||||
|
ConditionOption: ValueGreaterThan
|
||||||
|
FieldValue: 1
|
||||||
|
ConditionField: =>EditableFormField.countryTextField
|
||||||
|
Parent: =>EditableFormField.irdNumberField
|
@ -204,4 +204,13 @@ class EditableFormFieldTest extends FunctionalTest
|
|||||||
$this->assertEquals(10, $attributes['data-rule-minlength']);
|
$this->assertEquals(10, $attributes['data-rule-minlength']);
|
||||||
$this->assertEquals(20, $attributes['data-rule-maxlength']);
|
$this->assertEquals(20, $attributes['data-rule-maxlength']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testFormatDisplayRules()
|
||||||
|
{
|
||||||
|
/** @var EditableCheckbox $checkbox */
|
||||||
|
$checkbox = $this->objFromFixture('EditableFormField', 'irdNumberField');
|
||||||
|
$displayRules = $checkbox->formatDisplayRules();
|
||||||
|
$this->assertNotNull($displayRules);
|
||||||
|
$this->assertCount(1, $displayRules['operations']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,25 @@
|
|||||||
|
EditableFormField:
|
||||||
|
irdNumberField:
|
||||||
|
ClassName: EditableTextField
|
||||||
|
Name: IRDNumber
|
||||||
|
Title: "Enter your IRD Number"
|
||||||
|
countryTextField:
|
||||||
|
ClassName: EditableTextField
|
||||||
|
Name: CountryTextSelection
|
||||||
|
Title: "Enter your country (2 digit prefix)"
|
||||||
|
DisplayRulesConjunction: And
|
||||||
|
ShowOnLoad: false
|
||||||
EditableCustomRule:
|
EditableCustomRule:
|
||||||
|
rule1:
|
||||||
|
Display: Show
|
||||||
|
ConditionOption: HasValue
|
||||||
|
FieldValue: NZ
|
||||||
|
ConditionField: =>EditableFormField.countryTextField
|
||||||
|
Parent: =>EditableFormField.irdNumberField
|
||||||
rule-1:
|
rule-1:
|
||||||
Display: Hide
|
Display: Hide
|
||||||
ConditionOption: HasValue
|
ConditionOption: HasValue
|
||||||
FieldValue: 6
|
FieldValue: 6
|
||||||
|
|
||||||
EditableOption:
|
EditableOption:
|
||||||
option-1:
|
option-1:
|
||||||
Name: Option1
|
Name: Option1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user