Merge remote-tracking branch 'origin/4' into 4.3

This commit is contained in:
Robbie Averill 2017-05-11 14:37:55 +12:00
commit 893a28e0c6
36 changed files with 782 additions and 310 deletions

View File

@ -6,6 +6,10 @@
*
* @method EditableFormField Parent()
* @package userforms
*
* @property string Display
* @property string ConditionOption
* @property string FieldValue
*/
class EditableCustomRule extends DataObject
{
@ -147,4 +151,91 @@ class EditableCustomRule extends DataObject
{
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';
}
}

View File

@ -6,7 +6,7 @@
class UserDefinedForm extends Page
{
/**
* @var string
*/
@ -205,6 +205,20 @@ SQL;
$config->addComponent($export = new GridFieldExportButton('buttons-after-left'));
$config->addComponent($print = new GridFieldPrintButton('buttons-after-left'));
// show user form items in the summary tab
$summaryarray = array(
'ID' => 'ID',
'Created' => 'Created',
'LastEdited' => 'Last Edited'
);
foreach(EditableFormField::get()->filter(array("ParentID" => $parentID)) as $eff) {
if($eff->ShowInSummary) {
$summaryarray[$eff->Name] = $eff->Title ?: $eff->Name;
}
}
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields($summaryarray);
/**
* Support for {@link https://github.com/colymba/GridFieldBulkEditingTools}
*/
@ -440,181 +454,27 @@ class UserDefinedForm_Controller extends Page_Controller
$rules = "";
$watch = array();
$watchLoad = array();
if ($this->Fields()) {
/** @var EditableFormField $field */
foreach ($this->Fields() as $field) {
$holderSelector = $field->getSelectorHolder();
// 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).closest(".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 ($result = $field->formatDisplayRules()) {
$watch[] = $result;
}
}
}
if ($watch) {
foreach ($watch as $key => $values) {
$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;
$rules .= $this->buildWatchJS($watch);
}
$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
if ($default || $rules) {
if ($rules) {
Requirements::customScript(<<<JS
(function($) {
$(document).ready(function() {
$default
$rules
})
})(jQuery);
(function($) {
$(document).ready(function() {
{$rules}
});
})(jQuery);
JS
, 'UserFormsConditional');
}
@ -713,7 +573,7 @@ JS
foreach ($recipients as $recipient) {
$email = new UserFormRecipientEmail($submittedFields);
$mergeFields = $this->getMergeFieldsMap($emailData['Fields']);
if ($attachments) {
foreach ($attachments as $file) {
if ($file->ID != 0) {
@ -725,7 +585,7 @@ JS
}
}
}
$parsedBody = SSViewer::execute_string($recipient->getEmailBodyContent(), $mergeFields);
if (!$recipient->SendPlain && $recipient->emailTemplateExists()) {
@ -871,12 +731,55 @@ JS
Session::clear('FormProcessed');
}
return $this->customise(array(
'Content' => $this->customise(array(
$data = array(
'Submission' => $submission,
'Link' => $referrer
))->renderWith('ReceivedFormSubmission'),
);
$this->extend('updateReceivedFormSubmissionData', $data);
return $this->customise(array(
'Content' => $this->customise($data)->renderWith('ReceivedFormSubmission'),
'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
$('.userform').on('{$events}',
"{$selectors}",
function (){
if({$operations}) {
$('{$target}').{$view}();
} else {
$('{$target}').{$opposite}();
}
});
EOS;
}
return $result;
}
}

View File

@ -14,6 +14,8 @@ class EditableCheckbox extends EditableFormField
private static $plural_name = 'Checkboxes';
protected $jsEventHandler = 'click';
private static $db = array(
'CheckedDefault' => 'Boolean' // from CustomSettings
);
@ -61,4 +63,8 @@ class EditableCheckbox extends EditableFormField
parent::migrateSettings($data);
}
public function isCheckBoxField() {
return true;
}
}

View File

@ -14,6 +14,8 @@ class EditableCheckboxGroupField extends EditableMultipleOptionField
private static $plural_name = "Checkbox Groups";
protected $jsEventHandler = 'click';
public function getFormField()
{
$field = new UserFormsCheckboxSetField($this->Name, $this->EscapedTitle, $this->getOptionsMap());
@ -59,4 +61,18 @@ class EditableCheckboxGroupField extends EditableMultipleOptionField
return "$(\"input[name='{$this->Name}[]']:first\")";
}
}
public function isCheckBoxField() {
return true;
}
public function getSelectorFieldOnly()
{
return "[name='{$this->Name}[]']";
}
public function isCheckBoxGroupField()
{
return true;
}
}

View File

@ -14,6 +14,8 @@ class EditableDateField extends EditableFormField
private static $plural_name = 'Date Fields';
private static $has_placeholder = true;
private static $db = array(
'DefaultToToday' => 'Boolean' // From customsettings
);

View File

@ -1,12 +1,15 @@
<?php
/**
* EditableDropdown
*
* Represents a modifiable dropdown (select) box on a form
*
* @property bool $UseEmptyString
* @property string $EmptyString
*
* @package userforms
*/
class EditableDropdown extends EditableMultipleOptionField
{
@ -14,6 +17,11 @@ class EditableDropdown extends EditableMultipleOptionField
private static $plural_name = 'Dropdowns';
private static $db = array(
'UseEmptyString' => 'Boolean',
'EmptyString' => 'Varchar(255)',
);
/**
* @return FieldList
*/
@ -21,6 +29,18 @@ class EditableDropdown extends EditableMultipleOptionField
{
$fields = parent::getCMSFields();
$fields->addFieldToTab(
'Root.Main',
CheckboxField::create('UseEmptyString')
->setTitle('Set default empty string')
);
$fields->addFieldToTab(
'Root.Main',
TextField::create('EmptyString')
->setTitle('Empty String')
);
$fields->removeByName('Default');
return $fields;
@ -35,6 +55,10 @@ class EditableDropdown extends EditableMultipleOptionField
->setFieldHolderTemplate('UserFormsField_holder')
->setTemplate('UserFormsDropdownField');
if ($this->UseEmptyString) {
$field->setEmptyString(($this->EmptyString) ? $this->EmptyString : '');
}
// Set default
$defaultOption = $this->getDefaultOptions()->first();
if ($defaultOption) {

23
code/model/editableformfields/EditableEmailField.php Executable file → Normal file
View File

@ -14,24 +14,7 @@ class EditableEmailField extends EditableFormField
private static $plural_name = 'Email Fields';
private static $db = array(
'Placeholder' => 'Varchar(255)'
);
public function getCMSFields()
{
$this->beforeUpdateCMSFields(function ($fields) {
$fields->addFieldToTab(
'Root.Main',
TextField::create(
'Placeholder',
_t('EditableTextField.PLACEHOLDER', 'Placeholder')
)
);
});
return parent::getCMSFields();
}
private static $has_placeholder = true;
public function getSetsOwnError()
{
@ -59,9 +42,5 @@ class EditableEmailField extends EditableFormField
parent::updateFormField($field);
$field->setAttribute('data-rule-email', true);
if ($this->Placeholder) {
$field->setAttribute('placeholder', $this->Placeholder);
}
}
}

View File

@ -14,6 +14,8 @@ use SilverStripe\Forms\SegmentField;
* @property int $Sort
* @property bool $Required
* @property string $CustomErrorMessage
* @property boolean $ShowOnLoad
* @property string $DisplayRulesConjunction
* @method UserDefinedForm Parent() Parent page
* @method DataList DisplayRules() List of EditableCustomRule objects
* @mixin Versioned
@ -60,6 +62,13 @@ class EditableFormField extends DataObject
*/
public static $allowed_css = array();
/**
* Set this to true to enable placeholder field for any given class
* @config
* @var bool
*/
private static $has_placeholder = false;
/**
* @config
* @var array
@ -84,14 +93,18 @@ class EditableFormField extends DataObject
"CustomSettings" => "Text", // @deprecated from 2.0
"Migrated" => "Boolean", // set to true when migrated
"ExtraClass" => "Text", // from CustomSettings
"RightTitle" => "Varchar(255)", // from CustomSettings
"ShowOnLoad" => "Boolean(1)", // from CustomSettings
);
"ExtraClass" => "Text", // from CustomSettings
"RightTitle" => "Varchar(255)", // from CustomSettings
"ShowOnLoad" => "Boolean(1)", // from CustomSettings
"ShowInSummary" => "Boolean",
"Placeholder" => "Varchar(255)",
'DisplayRulesConjunction' => 'Enum("And,Or","Or")',
);
private static $defaults = array(
'ShowOnLoad' => true,
);
private static $defaults = array(
'ShowOnLoad' => true,
);
/**
@ -125,6 +138,22 @@ class EditableFormField extends DataObject
*/
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
*
@ -161,6 +190,7 @@ class EditableFormField extends DataObject
_t('EditableFormField.TYPE', 'Type'),
$this->i18n_singular_name()
),
CheckboxField::create('ShowInSummary', _t('EditableFormField.SHOWINSUMMARY', 'Show in summary gridfield')),
LiteralField::create(
'MergeField',
_t(
@ -228,6 +258,17 @@ class EditableFormField extends DataObject
$fields->addFieldsToTab('Root.DisplayRules', $displayFields);
}
// Placeholder
if ($this->config()->has_placeholder) {
$fields->addFieldToTab(
'Root.Main',
TextField::create(
'Placeholder',
_t('EditableFormField.PLACEHOLDER', 'Placeholder')
)
);
}
$this->extend('updateCMSFields', $fields);
return $fields;
@ -243,35 +284,27 @@ class EditableFormField extends DataObject
// Check display rules
if ($this->Required) {
return new FieldList(
LabelField::create(_t(
LabelField::create(
_t(
'EditableFormField.DISPLAY_RULES_DISABLED',
'Display rules are not enabled for required fields. ' .
'Please uncheck "Is this field Required?" under "Validation" to re-enable.'
))->addExtraClass('message warning')
);
'Display rules are not enabled for required fields. Please uncheck "Is this field Required?" under "Validation" to re-enable.'))
->addExtraClass('message warning'));
}
$self = $this;
$allowedClasses = array_keys($this->getEditableFieldClasses(false));
$editableColumns = new GridFieldEditableColumns();
$editableColumns->setDisplayFields(array(
'Display' => '',
'ConditionFieldID' => function ($record, $column, $grid) use ($allowedClasses, $self) {
return DropdownField::create(
$column,
'',
EditableFormField::get()
->filter(array(
return DropdownField::create($column, '', EditableFormField::get()->filter(array(
'ParentID' => $self->ParentID,
'ClassName' => $allowedClasses
))
->exclude(array(
'ID' => $self->ID
))
->map('ID', 'Title')
);
'ClassName' => $allowedClasses,
))->exclude(array(
'ID' => $self->ID,
))->map('ID', 'Title'));
},
'ConditionOption' => function ($record, $column, $grid) {
$options = Config::inst()->get('EditableCustomRule', 'condition_options');
return DropdownField::create($column, '', $options);
},
'FieldValue' => function ($record, $column, $grid) {
@ -279,7 +312,7 @@ class EditableFormField extends DataObject
},
'ParentID' => function ($record, $column, $grid) use ($self) {
return HiddenField::create($column, '', $self->ID);
}
},
));
// Custom rules
@ -293,11 +326,20 @@ class EditableFormField extends DataObject
);
return new FieldList(
CheckboxField::create('ShowOnLoad')
->setDescription(_t(
'EditableFormField.SHOWONLOAD',
'Initial visibility before processing these rules'
)),
DropdownField::create('ShowOnLoad',
_t('EditableFormField.INITIALVISIBILITY', 'Initial visibility'),
array(
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(
'DisplayRules',
_t('EditableFormField.CUSTOMRULES', 'Custom Rules'),
@ -492,7 +534,7 @@ class EditableFormField extends DataObject
{
$this->publish($fromStage, $toStage, $createNewVersion);
$this->publishRules($fromStage, $toStage, $createNewVersion);
}
}
/**
* Publish all field rules
@ -819,6 +861,16 @@ class EditableFormField extends DataObject
if ($this->ExtraClass) {
$field->addExtraClass($this->ExtraClass);
}
// if ShowOnLoad is false hide the field
if (!$this->ShowOnLoad) {
$field->addExtraClass($this->ShowOnLoadNice());
}
// if this field has a placeholder
if ($this->Placeholder) {
$field->setAttribute('placeholder', $this->Placeholder);
}
}
/**
@ -913,7 +965,17 @@ class EditableFormField extends DataObject
*/
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}";
}
/**
@ -921,10 +983,20 @@ class EditableFormField extends DataObject
*
* @param EditableCustomRule $rule Custom rule this selector will be used with
* @param bool $forOnLoad Set to true if this will be invoked on load
*
* @return string
*/
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}']";
}
@ -984,4 +1056,95 @@ class EditableFormField extends DataObject
}
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;
}
}

0
code/model/editableformfields/EditableFormHeading.php Executable file → Normal file
View File

View File

@ -145,7 +145,7 @@ class EditableLiteralField extends EditableFormField
Convert::raw2htmlname($this->Name),
Convert::raw2att($classes),
$label,
$this->Content
$this->dbObject('Content')->forTemplate()
)
);

View File

@ -33,50 +33,52 @@ class EditableMultipleOptionField extends EditableFormField
*/
public function getCMSFields()
{
$fields = parent::getCMSFields();
$this->beforeUpdateCMSFields(function($fields) {
$editableColumns = new GridFieldEditableColumns();
$editableColumns->setDisplayFields(array(
'Title' => array(
'title' => _t('EditableMultipleOptionField.TITLE', 'Title'),
'callback' => function ($record, $column, $grid) {
return TextField::create($column);
}
),
'Value' => array(
'title' => _t('EditableMultipleOptionField.VALUE', 'Value'),
'callback' => function ($record, $column, $grid) {
return TextField::create($column);
}
),
'Default' => array(
'title' => _t('EditableMultipleOptionField.DEFAULT', 'Selected by default?'),
'callback' => function ($record, $column, $grid) {
return CheckboxField::create($column);
}
)
));
$editableColumns = new GridFieldEditableColumns();
$editableColumns->setDisplayFields(array(
'Title' => array(
'title' => _t('EditableMultipleOptionField.TITLE', 'Title'),
'callback' => function ($record, $column, $grid) {
return TextField::create($column);
}
),
'Value' => array(
'title' => _t('EditableMultipleOptionField.VALUE', 'Value'),
'callback' => function ($record, $column, $grid) {
return TextField::create($column);
}
),
'Default' => array(
'title' => _t('EditableMultipleOptionField.DEFAULT', 'Selected by default?'),
'callback' => function ($record, $column, $grid) {
return CheckboxField::create($column);
}
)
));
$optionsConfig = GridFieldConfig::create()
->addComponents(
new GridFieldToolbarHeader(),
new GridFieldTitleHeader(),
new GridFieldOrderableRows('Sort'),
$editableColumns,
new GridFieldButtonRow(),
new GridFieldAddNewInlineButton(),
new GridFieldDeleteAction()
);
$optionsConfig = GridFieldConfig::create()
->addComponents(
new GridFieldToolbarHeader(),
new GridFieldTitleHeader(),
new GridFieldOrderableRows('Sort'),
$editableColumns,
new GridFieldButtonRow(),
new GridFieldAddNewInlineButton(),
new GridFieldDeleteAction()
$optionsGrid = GridField::create(
'Options',
_t('EditableFormField.CUSTOMOPTIONS', 'Options'),
$this->Options(),
$optionsConfig
);
$optionsGrid = GridField::create(
'Options',
_t('EditableFormField.CUSTOMOPTIONS', 'Options'),
$this->Options(),
$optionsConfig
);
$fields->insertAfter(new Tab('Options', _t('EditableMultipleOptionField.OPTIONSTAB', 'Options')), 'Main');
$fields->addFieldToTab('Root.Options', $optionsGrid);
});
$fields->insertAfter(new Tab('Options', _t('EditableMultipleOptionField.OPTIONSTAB', 'Options')), 'Main');
$fields->addFieldToTab('Root.Options', $optionsGrid);
$fields = parent::getCMSFields();
return $fields;
}

View File

@ -14,10 +14,11 @@ class EditableNumericField extends EditableFormField
private static $plural_name = 'Numeric Fields';
private static $has_placeholder = true;
private static $db = array(
'MinValue' => 'Int',
'MaxValue' => 'Int',
'Placeholder' => 'Varchar(255)'
'MaxValue' => 'Int'
);
public function getSetsOwnError()
@ -25,21 +26,6 @@ class EditableNumericField extends EditableFormField
return true;
}
public function getCMSFields()
{
$this->beforeUpdateCMSFields(function ($fields) {
$fields->addFieldToTab(
'Root.Main',
TextField::create(
'Placeholder',
_t('EditableTextField.PLACEHOLDER', 'Placeholder')
)
);
});
return parent::getCMSFields();
}
/**
* @return NumericField
*/
@ -85,9 +71,5 @@ class EditableNumericField extends EditableFormField
if ($this->MaxValue) {
$field->setAttribute('data-rule-max', $this->MaxValue);
}
if ($this->Placeholder) {
$field->setAttribute('placeholder', $this->Placeholder);
}
}
}

View File

@ -46,4 +46,8 @@ class EditableRadioField extends EditableMultipleOptionField
$first = $forOnLoad ? ':first' : '';
return "$(\"input[name='{$this->Name}']{$first}\")";
}
public function isRadioField() {
return true;
}
}

54
code/model/editableformfields/EditableTextField.php Executable file → Normal file
View File

@ -14,11 +14,45 @@ class EditableTextField extends EditableFormField
private static $plural_name = 'Text Fields';
private static $has_placeholder = true;
private static $autocomplete_options = array(
'off' => 'Off',
'on' => 'On',
'name' => 'Full name',
'honorific-prefix' => 'Prefix or title',
'given-name' => 'First name',
'additional-name' => 'Additional name',
'family-name' => 'Family name',
'honorific-suffix' => 'Suffix (e.g Jr.)',
'nickname' => 'Nickname',
'email' => 'Email',
'organization-title' => 'Job title',
'organization' => 'Organization',
'street-address' => 'Street address',
'address-line1' => 'Address line 1',
'address-line2' => 'Address line 2',
'address-line3' => 'Address line 3',
'address-level1' => 'Address level 1',
'address-level2' => 'Address level 2',
'address-level3' => 'Address level 3',
'address-level4' => 'Address level 4',
'country' => 'Country',
'country-name' => 'Country name',
'postal-code' => 'Postal code',
'bday' => 'Birthday',
'sex' => 'Gender identity',
'tel' => 'Telephone number',
'url' => 'Home page'
);
protected $jsEventHandler = 'keyup';
private static $db = array(
'MinLength' => 'Int',
'MaxLength' => 'Int',
'Rows' => 'Int(1)',
'Placeholder' => 'Varchar(255)'
'Autocomplete' => 'Varchar(255)'
);
private static $defaults = array(
@ -41,11 +75,16 @@ class EditableTextField extends EditableFormField
$fields->addFieldToTab(
'Root.Main',
TextField::create(
'Placeholder',
_t('EditableTextField.PLACEHOLDER', 'Placeholder')
)
DropdownField::create(
'Autocomplete',
_t('EditableTextField.AUTOCOMPLETE', 'Autocomplete'),
$this->config()->get('autocomplete_options')
)->setDescription(_t(
'EditableTextField.AUTOCOMPLETE_DESCRIPTION',
'Supported browsers will attempt to populate this field automatically with the users information, use to set the value populated'
))
);
});
return parent::getCMSFields();
@ -113,8 +152,9 @@ class EditableTextField extends EditableFormField
$field->setAttribute('data-rule-maxlength', intval($this->MaxLength));
}
if ($this->Placeholder) {
$field->setAttribute('placeholder', $this->Placeholder);
if ($this->Autocomplete) {
$field->setAttribute('autocomplete', $this->Autocomplete);
}
}
}

View File

@ -131,6 +131,7 @@ class UserFormsUpgradeService
// - MinLength (EditableTextField)
// - MaxLength (EditableTextField)
// - Rows (EditableTextField)
// - Placeholder (EditableTextField / EditableEmailField / EditableNumericField)
$customSettings = $field->CustomSettings
? unserialize($field->CustomSettings)
@ -142,6 +143,11 @@ class UserFormsUpgradeService
}
$field->migrateSettings($customSettings);
if ($field->config()->has_placeholder) {
$this->migratePlaceholder($field, $field->ClassName);
}
$field->write();
}
@ -224,4 +230,62 @@ class UserFormsUpgradeService
{
return $this->quiet;
}
/**
* Migrate Placeholder data from field specific table to the EditableFormField table
*
* @param EditableFormField $field
* @param string $tableName
*/
private function migratePlaceholder($field, $tableName)
{
// Migrate Placeholder setting from $tableName table to EditableFormField table
if ($field->Placeholder) {
return;
}
// Check if draft table exists
if (!DB::get_schema()->hasTable($tableName)) {
// Check if _obsolete_ draft table exists
$tableName = '_obsolete_' . $tableName;
if (!DB::get_schema()->hasTable($tableName)) {
return;
}
}
// Check if old Placeholder column exists
if (!DB::get_schema()->hasField($tableName, 'Placeholder')) {
return;
}
// Fetch existing draft Placeholder value
$query = "SELECT \"Placeholder\" FROM \"$tableName\" WHERE \"ID\" = '$field->ID'";
$draftPlaceholder = DB::query($query)->value();
if (!$draftPlaceholder) {
return;
}
// Update draft Placeholder value
DB::prepared_query(
"UPDATE \"EditableFormField\" SET \"Placeholder\" = ? WHERE \"ID\" = ?",
array($draftPlaceholder, $field->ID)
);
$livePlaceholder = $draftPlaceholder;
// Check if live table exists
$tableName = $tableName . '_Live';
if (DB::get_schema()->hasTable($tableName)) {
// Fetch existing live Placeholder value
$query = "SELECT \"Placeholder\" FROM \"$tableName\" WHERE \"ID\" = '" . $field->ID . "'";
$livePlaceholder = DB::query($query)->value();
if (!$livePlaceholder) {
$livePlaceholder = $draftPlaceholder;
}
}
// Update live Placeholder value
DB::prepared_query(
"UPDATE \"EditableFormField_Live\" SET \"Placeholder\" = ? WHERE \"ID\" = ?",
array($draftPlaceholder, $field->ID)
);
}
}

View File

@ -37,7 +37,12 @@
},
"suggest": {
"colymba/gridfield-bulk-editing-tools": "Allows for bulk management of form submissions",
"silverstripe/secureassets": "Enables files uploaded via userforms to be secured from public access"
"silverstripe/secureassets": "Enables files uploaded via userforms to be secured from public access",
"silverstripe/gridfieldqueuedexport": "Export large submission as CSV through queued jobs in the background"
},
"extra": []
}
"extra": {
"branch-alias": {
"dev-4.x-dev": "4.4.x-dev"
}
}
}

View File

@ -72,3 +72,7 @@
margin-bottom: 5px;
font-weight: bold;
}
.userform .field.hide {
display: none;
}

View File

@ -18,3 +18,10 @@ it then grabs the columns for the live database it will create a backup of the t
are surplus.
To run the task login as Admin and go to to http://yoursite/dev/tasks/UserFormsColumnCleanTask
## My CSV export times out or runs out of memory
You likely have too many submissions to fit within the PHP constraints
on your server (execution time and memory). If you can't increase these limits,
consider installing the [gridfieldqueuedexport](https://github.com/silverstripe/silverstripe-gridfieldqueuedexport) module. It uses [queuedjobs](https://github.com/silverstripe-australia/silverstripe-queuedjobs) to export
submissions in the background, providing users with a progress indicator.

View File

@ -18,6 +18,7 @@ if (typeof(ss) === 'undefined' || typeof(ss.i18n) === 'undefined') {
"UserForms.HIDE_OPTIONS": "Skryť možnosti",
"UserForms.LEAVE_CONFIRMATION": "Máte neuložené zmeny!",
"UserForms.REMOVED_OPTION": "Voľba/možnosť odstránená",
"UserForms.SHOW_OPTIONS": "Zobraziť možnosti"
"UserForms.SHOW_OPTIONS": "Zobraziť možnosti",
"UserForms.ERROR_CONTAINER_HEADER": "Opravte prosím nasledujúce chyby a skúste to znova:"
});
}
}

View File

@ -64,6 +64,7 @@ de_DE:
NOTBLANK: 'Nicht leer'
NOTVALUE: 'Kein Wert'
OPTIONS: Optionen
PLACEHOLDER: Platzhalter
PLURALNAME: 'Editierbare Formularfelder'
REQUIRED: 'Pflichtfeld?'
RIGHTTITLE: 'Titel rechts'
@ -108,8 +109,9 @@ de_DE:
PLURALNAME: Radio-Buttons
SINGULARNAME: Radio-Button
EditableTextField:
AUTOCOMPLETE: 'Automatisch vervollständigen'
AUTOCOMPLETE_DESCRIPTION: 'Unterstützte Browser versuchen, dieses Feld automatisch mit den Benutzerinformationen zu füllen, verwenden, um den gefüllten Wert festzulegen'
NUMBERROWS: 'Anzahl der Zeilen'
PLACEHOLDER: Platzhalter
PLURALNAME: Textfelder
SINGULARNAME: Textfeld
TEXTLENGTH: Textlänge

View File

@ -83,6 +83,7 @@ en:
NOTBLANK: 'Not Blank'
NOTVALUE: 'Not Value'
OPTIONS: Options
PLACEHOLDER: Placeholder
PLURALNAME: 'Editable Form Fields'
REQUIRED: 'Is this field Required?'
REQUIRED_DESCRIPTION: 'Please note that conditional fields can''t be required'
@ -140,9 +141,10 @@ en:
PLURALNAME: 'Radio Groups'
SINGULARNAME: 'Radio Group'
EditableTextField:
AUTOCOMPLETE: 'Autocomplete'
AUTOCOMPLETE_DESCRIPTION: 'Supported browsers will attempt to populate this field automatically with the users information, use to set the value populated'
NUMBERROWS: 'Number of rows'
NUMBERROWS_DESCRIPTION: 'Fields with more than one row will be generated as a textarea'
PLACEHOLDER: Placeholder
PLURALNAME: 'Text Fields'
RANGE_TO: to
SINGULARNAME: 'Text Field'

View File

@ -83,6 +83,7 @@ eo:
NOTBLANK: 'Ne vaka'
NOTVALUE: 'Ne valoro'
OPTIONS: Agordoj
PLACEHOLDER: Lokokupilo
PLURALNAME: 'Redakteblaj formularaj kampoj'
REQUIRED: 'Ĉu ĉi tiu kampo estas nepra?'
REQUIRED_DESCRIPTION: 'Bonvolu noti ke kondiĉaj kampoj ne povas esti nepraj'
@ -142,7 +143,6 @@ eo:
EditableTextField:
NUMBERROWS: 'Nombro da vicoj'
NUMBERROWS_DESCRIPTION: 'Kampoj kun pli ol unu vico generiĝos kiel tekstareo'
PLACEHOLDER: Lokokupilo
PLURALNAME: 'Tekstaj kampoj'
RANGE_TO: al
SINGULARNAME: 'Teksta kampo'

View File

@ -106,6 +106,8 @@ es_ES:
PLURALNAME: 'Opciones editables'
SINGULARNAME: 'Opciones editables'
EditableTextField:
AUTOCOMPLETE: 'Autocompletar'
AUTOCOMPLETE_DESCRIPTION: 'Los navegadores compatibles intentarán rellenar este campo automáticamente con la información de los usuarios, usarla para establecer el valor poblado'
NUMBERROWS: 'Cantidad de filas'
PLURALNAME: 'Campos de texto'
SINGULARNAME: 'Campo de texto'

View File

@ -83,6 +83,7 @@ fi_FI:
NOTBLANK: 'Ei ole tyhjä'
NOTVALUE: 'Ei ole sama kuin'
OPTIONS: Valinnat
PLACEHOLDER: Opastusteksti
PLURALNAME: 'Muokattavat lomakekentät'
REQUIRED: 'Onko tämä kenttä pakollinen?'
REQUIRED_DESCRIPTION: 'Huomioithan, että ehdolliset kentät eivät voi olla pakollisia'
@ -142,7 +143,6 @@ fi_FI:
EditableTextField:
NUMBERROWS: 'Rivien määrä'
NUMBERROWS_DESCRIPTION: 'Kenttät, joissa on enemmän kuin yksi rivi, luodaan tekstialueena.'
PLACEHOLDER: Opastusteksti
PLURALNAME: Tekstikentät
RANGE_TO:
SINGULARNAME: Tekstikenttä

View File

@ -60,6 +60,9 @@ fr_FR:
DELETE: 'Remove this option'
DRAG: 'Drag to rearrange order of options'
LOCKED: 'These fields cannot be modified'
EditableTextField:
AUTOCOMPLETE: 'Autocomplétion'
AUTOCOMPLETE_DESCRIPTION: 'Les navigateurs pris en charge essaieront de remplir automatiquement ce champ avec les informations des utilisateurs, utilisez pour définir la valeur remplie'
Form:
FIELDISREQUIRED: '%s is required'
SubmittedFileField:

View File

@ -83,6 +83,7 @@ it:
NOTBLANK: 'Non vuoto'
NOTVALUE: 'Non valore'
OPTIONS: Opzioni
PLACEHOLDER: Segnaposto
PLURALNAME: 'Campi modulo modificabili'
REQUIRED: 'Questo campo è obbligatorio?'
REQUIRED_DESCRIPTION: 'Prego nota che i campi condizionali non possono essere obbligatori'
@ -140,9 +141,10 @@ it:
PLURALNAME: 'Gruppi di pulsanti a scelta singola'
SINGULARNAME: 'Gruppo di pulsanti a scelta singola'
EditableTextField:
AUTOCOMPLETE: 'completamento automatico'
AUTOCOMPLETE_DESCRIPTION: 'I browser supportati cercheranno di popolare automaticamente questo campo con l'informazione agli utenti, usare per impostare il valore popolata'
NUMBERROWS: 'Numero di righe'
NUMBERROWS_DESCRIPTION: 'Campi con più di una riga verranno generati come area testo'
PLACEHOLDER: Segnaposto
PLURALNAME: 'Campi testo'
RANGE_TO: a
SINGULARNAME: 'Campo testo'

View File

@ -103,6 +103,8 @@ pl_PL:
PLURALNAME: 'Pola wyboru'
SINGULARNAME: 'Pole wyboru'
EditableTextField:
AUTOCOMPLETE: 'autouzupełnienie'
AUTOCOMPLETE_DESCRIPTION: 'Obsługiwane przeglądarki podejmie próbę wypełnienia tego pola automatycznie z informacji użytkowników, należy ustawić wartość zaludnionych'
NUMBERROWS: 'Liczba wierszy'
PLURALNAME: 'Pola tekstowe'
SINGULARNAME: 'Pole tekstowe'

View File

@ -60,6 +60,9 @@ ru_RU:
DELETE: 'Remove this option'
DRAG: 'Drag to rearrange order of options'
LOCKED: 'These fields cannot be modified'
EditableTextField:
AUTOCOMPLETE: 'автозаполнения'
AUTOCOMPLETE_DESCRIPTION: 'Поддерживаемые браузеры будут пытаться автоматически заполнять это поле с информацией пользователей, используйте для установки значения населенную'
Form:
FIELDISREQUIRED: '%s is required'
SubmittedFileField:

View File

@ -83,6 +83,7 @@ sk:
NOTBLANK: Vyplnené
NOTVALUE: 'Nie zadanú hodnotu'
OPTIONS: Voľba/možnosť
PLACEHOLDER: 'Zástupná/Ukážková hodnota (placeholder)'
PLURALNAME: 'Formulárové polia'
REQUIRED: 'Je pole povinné/vyžadované?'
REQUIRED_DESCRIPTION: 'Všimnite si prosím, že podmienené polia nemôžu byť vyžadované.'
@ -142,7 +143,6 @@ sk:
EditableTextField:
NUMBERROWS: 'Počet riadkov'
NUMBERROWS_DESCRIPTION: 'Políčka s viac ako jedným riadkom sú generované ako textarea.'
PLACEHOLDER: 'Zástupná/Ukážková hodnota (placeholder)'
PLURALNAME: 'Textové polia'
RANGE_TO: do
SINGULARNAME: 'Textové pole'

View 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']);
}
}

View 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

View File

@ -0,0 +1,42 @@
<?php
/**
* Tests the {@see EditableDropdown} class
*/
class EditableDropdownTest extends SapphireTest
{
public static $fixture_file = 'userforms/tests/EditableFormFieldTest.yml';
public function setUp()
{
parent::setUp();
}
/**
* Tests that the field sets the empty string if set
*/
public function testFormField()
{
if (!$dropdown = EditableDropdown::get()->filter('UseEmptyString', true)->first()) {
$dropdown = $this->objFromFixture('EditableDropdown', 'basic-dropdown');
$dropdown->UseEmptyString = true;
$dropdown->EmptyString = 'My Default Empty String';
$dropdown->write();
}
$field = $dropdown->getFormField();
$this->assertEquals($field->getEmptyString(), 'My Default Empty String');
$alternateDropdown = $this->objFromFixture('EditableDropdown', 'department-dropdown');
$formField = $alternateDropdown->getFormField();
$this->assertFalse($formField->getHasEmptyDefault());
$alternateDropdown->UseEmptyString = true;
$alternateDropdown->write();
$this->assertEquals($formField->getEmptyString(), '');
}
}

View File

@ -204,4 +204,13 @@ class EditableFormFieldTest extends FunctionalTest
$this->assertEquals(10, $attributes['data-rule-minlength']);
$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']);
}
}

View File

@ -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:
rule1:
Display: Show
ConditionOption: HasValue
FieldValue: NZ
ConditionField: =>EditableFormField.countryTextField
Parent: =>EditableFormField.irdNumberField
rule-1:
Display: Hide
ConditionOption: HasValue
FieldValue: 6
EditableOption:
option-1:
Name: Option1
@ -144,4 +160,4 @@ UserDefinedForm:
Title: Custom Rules Form
Fields: =>EditableCheckbox.checkbox-with-rule, =>EditableTextField.basic-text-2
empty-form:
Title: Empty Form
Title: Empty Form

View File

@ -50,6 +50,26 @@ class UserDefinedFormTest extends FunctionalTest
$this->assertTrue($fields->dataFieldByName('OnCompleteMessage') != null);
}
public function testGetCMSFieldsShowInSummary()
{
$this->logInWithPermission('ADMIN');
$form = $this->objFromFixture('UserDefinedForm', 'summary-rules-form');
$fields = $form->getCMSFields();
$this->assertInstanceOf('GridField', $fields->dataFieldByName('Submissions'));
$submissionsgrid = $fields->dataFieldByName('Submissions');
$gridFieldDataColumns = $submissionsgrid->getConfig()->getComponentByType('GridFieldDataColumns');
$summaryFields = $gridFieldDataColumns->getDisplayFields($submissionsgrid);
$this->assertContains('SummaryShow', array_keys($summaryFields), 'Summary field not showing displayed field');
$this->assertNotContains('SummaryHide', array_keys($summaryFields), 'Summary field showing displayed field');
}
public function testEmailRecipientPopup()
{
$this->logInWithPermission('ADMIN');

View File

@ -106,6 +106,16 @@ EditableTextField:
Title: Required Text Field
Required: true
CustomErrorMessage: 'This field is required'
summary-show:
Name: SummaryShow
Title: Summary Text Field
ShowInSummary: true
summary-hide:
Name: SummaryHide
Title: Summary Text Field
ShowInSummary: false
EditableDropdown:
basic-dropdown:
@ -250,7 +260,12 @@ UserDefinedForm:
custom-rules-form:
Title: Custom Rules Form
Fields: =>EditableFormStep.form4step1,=>EditableCheckbox.checkbox-2, =>EditableTextField.basic-text-2
Fields: =>EditableCheckbox.checkbox-2, =>EditableTextField.basic-text-2
summary-rules-form:
Title: Summary Fields Form
Fields: =>EditableTextField.summary-show, =>EditableTextField.summary-hide
empty-form:
Title: Empty Form