BUG Fix form submission

BUG Fixed display logic
This commit is contained in:
Damian Mooyman 2015-08-13 11:31:37 +12:00
parent 6848b39ac0
commit f1c408d3f4
20 changed files with 273 additions and 155 deletions

View File

@ -37,7 +37,7 @@ class UserFormFieldEditorExtension extends DataExtension {
$this->createInitialFormStep(true); $this->createInitialFormStep(true);
$editableColumns = new GridFieldEditableColumns(); $editableColumns = new GridFieldEditableColumns();
$fieldClasses = $this->getEditableFieldClasses(); $fieldClasses = singleton('EditableFormField')->getEditableFieldClasses();
$editableColumns->setDisplayFields(array( $editableColumns->setDisplayFields(array(
'ClassName' => function($record, $column, $grid) use ($fieldClasses) { 'ClassName' => function($record, $column, $grid) use ($fieldClasses) {
if($record instanceof EditableFormField) { if($record instanceof EditableFormField) {
@ -121,32 +121,6 @@ class UserFormFieldEditorExtension extends DataExtension {
$fields->add($step); $fields->add($step);
} }
/**
* @return array
*/
public function getEditableFieldClasses() {
$classes = ClassInfo::getValidSubClasses('EditableFormField');
// Remove classes we don't want to display in the dropdown.
$editableFieldClasses = array();
foreach ($classes as $class) {
if(in_array($class, array('EditableFormField', 'EditableMultipleOptionField'))
|| Config::inst()->get($class, 'hidden')
) {
continue;
}
$singleton = singleton($class);
if(!$singleton->canCreate()) {
continue;
}
$editableFieldClasses[$class] = $singleton->i18n_singular_name();
}
return $editableFieldClasses;
}
/** /**
* Ensure that at least one page exists at the start * Ensure that at least one page exists at the start
*/ */

View File

@ -4,4 +4,41 @@
* Represents a page step in a form, which may contain form fields or other groups * Represents a page step in a form, which may contain form fields or other groups
*/ */
class UserFormsStepField extends UserFormsCompositeField { class UserFormsStepField extends UserFormsCompositeField {
private static $casting = array(
'StepNumber' => 'Int'
);
/**
* Numeric index (1 based) of this step
*
* Null if unassigned
*
* @var int|null
*/
protected $number = null;
public function FieldHolder($properties = array()) {
return $this->Field($properties);
}
/**
* Get the step number
*
* @return int|null
*/
public function getStepNumber() {
return $this->number;
}
/**
* Re-assign this step to another number
*
* @param type $number
* @return $this
*/
public function setStepNumber($number) {
$this->number = $number;
return $this;
}
} }

View File

@ -22,6 +22,12 @@ class UserForm extends Form {
$this->getRequiredFields() $this->getRequiredFields()
); );
// Number each page
$stepNumber = 1;
foreach($this->getSteps() as $step) {
$step->setStepNumber($stepNumber++);
}
if($controller->DisableCsrfSecurityToken) { if($controller->DisableCsrfSecurityToken) {
$this->disableSecurityToken(); $this->disableSecurityToken();
} }
@ -45,24 +51,21 @@ class UserForm extends Form {
} }
/** /**
* @return int * @return bool
*/ */
public function getDisplayErrorMessagesAtTop() { public function getDisplayErrorMessagesAtTop() {
return $this->controller->DisplayErrorMessagesAtTop; return (bool)$this->controller->DisplayErrorMessagesAtTop;
} }
/** /**
* @return array * Return the fieldlist, filtered to only contain steps
*
* @return ArrayList
*/ */
public function getNumberOfSteps() { public function getSteps() {
$steps = new ArrayList(); return $this->Fields()->filterByCallback(function($field) {
$numberOfSteps = $this->controller->Fields()->filter('ClassName', 'EditableFormStep')->Count(); return $field instanceof UserFormsStepField;
});
for($i = 0; $i < $numberOfSteps; $i++) {
$steps->push($i);
}
return $steps;
} }
/** /**
@ -123,9 +126,7 @@ class UserForm extends Form {
->filter('Required', true) ->filter('Required', true)
->column('Name'); ->column('Name');
$required = new RequiredFields($requiredNames); $required = new RequiredFields($requiredNames);
$this->extend('updateRequiredFields', $required); $this->extend('updateRequiredFields', $required);
return $required; return $required;
} }

View File

@ -394,15 +394,11 @@ class UserDefinedForm_Controller extends Page_Controller {
if($this->Fields()) { if($this->Fields()) {
foreach($this->Fields() as $field) { foreach($this->Fields() as $field) {
$fieldId = $field->Name; $holderSelector = $field->getSelectorHolder();
if($field instanceof EditableFormHeading) {
$fieldId = 'UserForm_Form_' . $field->Name;
}
// Is this Field Show by Default // Is this Field Show by Default
if(!$field->ShowOnLoad) { if(!$field->ShowOnLoad) {
$default .= "$(\"#" . $fieldId . "\").hide();\n"; $default .= "{$holderSelector}.hide();\n";
} }
// Check for field dependencies / default // Check for field dependencies / default
@ -411,22 +407,8 @@ class UserDefinedForm_Controller extends Page_Controller {
// Get the field which is effected // Get the field which is effected
$formFieldWatch = EditableFormField::get()->byId($rule->ConditionFieldID); $formFieldWatch = EditableFormField::get()->byId($rule->ConditionFieldID);
if($formFieldWatch->RecordClassName == 'EditableDropdown') { $fieldToWatch = $formFieldWatch->getSelectorField($rule);
// watch out for multiselect options - radios and check boxes $fieldToWatchOnLoad = $formFieldWatch->getSelectorField($rule, true);
$fieldToWatch = "$(\"select[name='" . $formFieldWatch->Name . "']\")";
$fieldToWatchOnLoad = $fieldToWatch;
} else if($formFieldWatch->RecordClassName == 'EditableCheckboxGroupField') {
// watch out for checkboxs as the inputs don't have values but are 'checked
$fieldToWatch = "$(\"input[name='" . $formFieldWatch->Name . "[" . $rule->FieldValue . "]']\")";
$fieldToWatchOnLoad = $fieldToWatch;
} else if($formFieldWatch->RecordClassName == 'EditableRadioField') {
$fieldToWatch = "$(\"input[name='" . $formFieldWatch->Name . "']\")";
// We only want to trigger on load once for the radio group - hence we focus on the first option only.
$fieldToWatchOnLoad = "$(\"input[name='" . $formFieldWatch->Name . "']:first\")";
} else {
$fieldToWatch = "$(\"input[name='" . $formFieldWatch->Name . "']\")";
$fieldToWatchOnLoad = $fieldToWatch;
}
// show or hide? // show or hide?
$view = ($rule->Display == 'Hide') ? 'hide' : 'show'; $view = ($rule->Display == 'Hide') ? 'hide' : 'show';
@ -436,7 +418,7 @@ class UserDefinedForm_Controller extends Page_Controller {
// @todo encapulsation // @todo encapulsation
$action = "change"; $action = "change";
if($formFieldWatch->ClassName == "EditableTextField") { if($formFieldWatch instanceof EditableTextField) {
$action = "keyup"; $action = "keyup";
} }
@ -454,11 +436,11 @@ class UserDefinedForm_Controller extends Page_Controller {
// and what should we evaluate // and what should we evaluate
switch($rule->ConditionOption) { switch($rule->ConditionOption) {
case 'IsNotBlank': case 'IsNotBlank':
$expression = ($checkboxField || $radioField) ? '$(this).prop("checked")' :'$(this).val() != ""'; $expression = ($checkboxField || $radioField) ? '$(this).is(":checked")' :'$(this).val() != ""';
break; break;
case 'IsBlank': case 'IsBlank':
$expression = ($checkboxField || $radioField) ? '!($(this).prop("checked"))' : '$(this).val() == ""'; $expression = ($checkboxField || $radioField) ? '!($(this).is(":checked"))' : '$(this).val() == ""';
break; break;
case 'HasValue': case 'HasValue':
@ -507,7 +489,7 @@ class UserDefinedForm_Controller extends Page_Controller {
$watch[$fieldToWatch][] = array( $watch[$fieldToWatch][] = array(
'expression' => $expression, 'expression' => $expression,
'field_id' => $fieldId, 'holder_selector' => $holderSelector,
'view' => $view, 'view' => $view,
'opposite' => $opposite, 'opposite' => $opposite,
'action' => $action 'action' => $action
@ -526,9 +508,9 @@ class UserDefinedForm_Controller extends Page_Controller {
foreach($values as $rule) { foreach($values as $rule) {
// Register conditional behaviour with an element, so it can be triggered from many places. // Register conditional behaviour with an element, so it can be triggered from many places.
$logic[] = sprintf( $logic[] = sprintf(
'if(%s) { $("#%s").%s(); } else { $("#%2$s").%s(); }', 'if(%s) { %s.%s(); } else { %2$s.%s(); }',
$rule['expression'], $rule['expression'],
$rule['field_id'], $rule['holder_selector'],
$rule['view'], $rule['view'],
$rule['opposite'] $rule['opposite']
); );

View File

@ -44,4 +44,14 @@ class EditableCheckboxGroupField extends EditableMultipleOptionField {
} }
return $result; return $result;
} }
public function getSelectorField(EditableCustomRule $rule, $forOnLoad = false) {
// watch out for checkboxs as the inputs don't have values but are 'checked
// @todo - Test this
if($rule->FieldValue) {
return "$(\"input[name='{$this->Name}[]'][value='{$rule->FieldValue}']\")";
} else {
return "$(\"input[name='{$this->Name}[]']:first\")";
}
}
} }

View File

@ -38,4 +38,8 @@ class EditableCountryDropdownField extends EditableFormField {
public function getIcon() { public function getIcon() {
return USERFORMS_DIR . '/images/editabledropdown.png'; return USERFORMS_DIR . '/images/editabledropdown.png';
} }
public function getSelectorField(EditableCustomRule $rule, $forOnLoad = false) {
return "$(\"select[name='{$this->Name}']\")";
}
} }

View File

@ -38,4 +38,8 @@ class EditableDropdown extends EditableMultipleOptionField {
$this->doUpdateFormField($field); $this->doUpdateFormField($field);
return $field; return $field;
} }
public function getSelectorField(EditableCustomRule $rule, $forOnLoad = false) {
return "$(\"select[name='{$this->Name}']\")";
}
} }

View File

@ -17,6 +17,13 @@ class EditableFieldGroup extends EditableFormField {
*/ */
private static $hidden = true; private static $hidden = true;
/**
* Non-data field type
*
* @var type
*/
private static $literal = true;
public function getCMSFields() { public function getCMSFields() {
$fields = parent::getCMSFields(); $fields = parent::getCMSFields();
$fields->removeByName(array('MergeField', 'Default', 'Validation', 'DisplayRules')); $fields->removeByName(array('MergeField', 'Default', 'Validation', 'DisplayRules'));

View File

@ -16,6 +16,14 @@ class EditableFieldGroupEnd extends EditableFormField {
* @var bool * @var bool
*/ */
private static $hidden = true; private static $hidden = true;
/**
* Non-data type
*
* @config
* @var bool
*/
private static $literal = true;
public function getCMSTitle() { public function getCMSTitle() {
$group = $this->Group(); $group = $this->Group();

View File

@ -16,6 +16,22 @@ class EditableFormField extends DataObject {
*/ */
private static $hidden = false; private static $hidden = false;
/**
* Define this field as abstract (not inherited)
*
* @config
* @var bool
*/
private static $abstract = true;
/**
* Flag this field type as non-data (e.g. literal, header, html)
*
* @config
* @var bool
*/
private static $literal = false;
/** /**
* Default sort order * Default sort order
* *
@ -176,21 +192,25 @@ class EditableFormField extends DataObject {
} }
// Validation // Validation
$fields->addFieldsToTab( $validationFields = $this->getFieldValidationOptions();
'Root.Validation', if($validationFields) {
$this->getFieldValidationOptions() $fields->addFieldsToTab(
); 'Root.Validation',
$this->getFieldValidationOptions()
);
}
$allowedClasses = array_keys($this->getEditableFieldClasses(false));
$editableColumns = new GridFieldEditableColumns(); $editableColumns = new GridFieldEditableColumns();
$editableColumns->setDisplayFields(array( $editableColumns->setDisplayFields(array(
'Display' => '', 'Display' => '',
'ConditionFieldID' => function($record, $column, $grid) { 'ConditionFieldID' => function($record, $column, $grid) use ($allowedClasses) {
return DropdownField::create( return DropdownField::create(
$column, $column,
'', '',
EditableFormField::get() EditableFormField::get()
->filter(array( ->filter(array(
'ParentID' => $this->ParentID 'ParentID' => $this->ParentID,
'ClassName' => $allowedClasses
)) ))
->exclude(array( ->exclude(array(
'ID' => $this->ID 'ID' => $this->ID
@ -651,4 +671,57 @@ class EditableFormField extends DataObject {
->setAttribute('placeholder', _t('EditableFormField.TITLE', 'Title')) ->setAttribute('placeholder', _t('EditableFormField.TITLE', 'Title'))
->setAttribute('data-placeholder', _t('EditableFormField.TITLE', 'Title')); ->setAttribute('data-placeholder', _t('EditableFormField.TITLE', 'Title'));
} }
/**
* Get the JS expression for selecting the holder for this field
*
* @return string
*/
public function getSelectorHolder() {
return "$(\"#{$this->Name}\")";
}
/**
* Gets the JS expression for selecting the value for this field
*
* @param EditableCustomRule $rule Custom rule this selector will be used with
* @param bool $forOnLoad Set to true if this will be invoked on load
*/
public function getSelectorField(EditableCustomRule $rule, $forOnLoad = false) {
return "$(\"input[name='{$this->Name}']\")";
}
/**
* Get the list of classes that can be selected and used as data-values
*
* @param $includeLiterals Set to false to exclude non-data fields
* @return array
*/
public function getEditableFieldClasses($includeLiterals = true) {
$classes = ClassInfo::getValidSubClasses('EditableFormField');
// Remove classes we don't want to display in the dropdown.
$editableFieldClasses = array();
foreach ($classes as $class) {
// Skip abstract / hidden classes
if(Config::inst()->get($class, 'abstract', Config::UNINHERITED) || Config::inst()->get($class, 'hidden')
) {
continue;
}
if(!$includeLiterals && Config::inst()->get($class, 'literal')) {
continue;
}
$singleton = singleton($class);
if(!$singleton->canCreate()) {
continue;
}
$editableFieldClasses[$class] = $singleton->i18n_singular_name();
}
return $editableFieldClasses;
}
} }

View File

@ -11,6 +11,8 @@ class EditableFormHeading extends EditableFormField {
private static $plural_name = 'Headings'; private static $plural_name = 'Headings';
private static $literal = true;
private static $db = array( private static $db = array(
'Level' => 'Int(3)', // From CustomSettings 'Level' => 'Int(3)', // From CustomSettings
'HideFromReports' => 'Boolean(0)' // from CustomSettings 'HideFromReports' => 'Boolean(0)' // from CustomSettings
@ -57,6 +59,7 @@ class EditableFormHeading extends EditableFormField {
public function getFormField() { public function getFormField() {
$labelField = new HeaderField($this->Name, $this->EscapedTitle, $this->Level); $labelField = new HeaderField($this->Name, $this->EscapedTitle, $this->Level);
$labelField->addExtraClass('FormHeading'); $labelField->addExtraClass('FormHeading');
$labelField->setAttribute('data-id', $this->Name);
$this->doUpdateFormField($labelField); $this->doUpdateFormField($labelField);
return $labelField; return $labelField;
} }
@ -80,4 +83,8 @@ class EditableFormHeading extends EditableFormField {
public function getFieldValidationOptions() { public function getFieldValidationOptions() {
return false; return false;
} }
public function getSelectorHolder() {
return "$(\":header[data-id='{$this->Name}']\")";
}
} }

View File

@ -13,6 +13,14 @@ class EditableLiteralField extends EditableFormField {
private static $plural_name = 'HTML Blocks'; private static $plural_name = 'HTML Blocks';
/**
* Mark as literal only
*
* @config
* @var bool
*/
private static $literal = true;
/** /**
* Get the name of the editor config to use for HTML sanitisation. Defaults to the active config. * Get the name of the editor config to use for HTML sanitisation. Defaults to the active config.
* *

View File

@ -14,6 +14,14 @@
*/ */
class EditableMultipleOptionField extends EditableFormField { class EditableMultipleOptionField extends EditableFormField {
/**
* Define this field as abstract (not inherited)
*
* @config
* @var bool
*/
private static $abstract = true;
private static $has_many = array( private static $has_many = array(
"Options" => "EditableOption" "Options" => "EditableOption"

View File

@ -35,4 +35,10 @@ class EditableRadioField extends EditableMultipleOptionField {
$this->doUpdateFormField($field); $this->doUpdateFormField($field);
return $field; return $field;
} }
public function getSelectorField(EditableCustomRule $rule, $forOnLoad = false) {
// We only want to trigger on load once for the radio group - hence we focus on the first option only.
$first = $forOnLoad ? ':first' : '';
return "$(\"input[name='{$this->Name}']{$first}\")";
}
} }

View File

@ -139,7 +139,7 @@ jQuery(function ($) {
*/ */
UserForm.prototype.setCurrentStep = function (step) { UserForm.prototype.setCurrentStep = function (step) {
// Make sure we're dealing with a form step. // Make sure we're dealing with a form step.
if (!step instanceof FormStep) { if (!(step instanceof FormStep)) {
return; return;
} }

View File

@ -1,20 +1,18 @@
<% cached "UserForms_Navigation", $LastEdited %> <% if $Steps.Count > 1 %>
<% if $NumberOfSteps.Count > "1" %> <div id="userform-progress" class="userform-progress" aria-hidden="true" style="display:none;">
<div id="userform-progress" class="userform-progress" aria-hidden="true" style="display:none;"> <h2 class="progress-title"></h2>
<h2 class="progress-title"></h2> <p>Step <span class="current-step-number">1</span> of $NumberOfSteps.Count</p>
<p>Step <span class="current-step-number">1</span> of $NumberOfSteps.Count</p> <div class="progress">
<div class="progress"> <div class="progress-bar" role="progressbar" aria-valuenow="1" aria-valuemin="1" aria-valuemax="$NumberOfSteps.Count"></div>
<div class="progress-bar" role="progressbar" aria-valuenow="1" aria-valuemin="1" aria-valuemax="$NumberOfSteps.Count"></div> </div>
</div> <nav>
<nav> <ul class="step-buttons">
<ul class="step-buttons"> <% loop $Steps %>
<% loop $NumberOfSteps %> <li class="step-button-wrapper<% if $First %> current<% end_if %>">
<li class="step-button-wrapper<% if $Pos == '1' %> current<% end_if %>">
<button class="step-button-jump js-align" disabled="disabled">$Pos</button><!-- Remove js-align class to remove javascript positioning --> <button class="step-button-jump js-align" disabled="disabled">$Pos</button><!-- Remove js-align class to remove javascript positioning -->
</li> </li>
<% end_loop %> <% end_loop %>
<ul> </ul>
</nav> </nav>
</div> </div>
<% end_if %> <% end_if %>
<% end_cached %>

View File

@ -0,0 +1,8 @@
<% if $Steps.Count > 1 %>
<fieldset class="error-container form-wide-errors" aria-hidden="true" style="display: none;">
<div>
<h4></h4>
<ul class="error-list"></ul>
</div>
</fieldset>
<% end_if %>

View File

@ -1,29 +1,21 @@
<% if $FirstLast == "first last" %> <% if $Form.Steps.Count > 1 %>
<% else %> <nav class="step-navigation" aria-hidden="true" style="display:none;">
<nav class="step-navigation" aria-hidden="true" style="display:none;"> <ul class="step-buttons">
<ul class="step-buttons"> <% if $StepNumber > 1 %>
<% if $FirstLast == "first" %> <li class="step-button-wrapper">
<% else %> <button class="step-button-prev">Prev</button>
<li class="step-button-wrapper"> </li>
<button class="step-button-prev">Prev</button> <% end_if %>
</li>
<% end_if %>
<% if $FirstLast == "last" %> <% if $StepNumber < $Form.Steps.Count %>
<li class="step-button-wrapper">
<% if $ContainingPage.Actions %> <button class="step-button-next">Next</button>
<% loop $ContainingPage.Actions %> </li>
<li class="step-button-wrapper"> <% else_if $Form.Actions %><% loop $Form.Actions %>
$Field <li class="step-button-wrapper">
</li> $Field
<% end_loop %> </li>
<% end_if %> <% end_loop %><% end_if %>
</ul>
<% else %> </nav>
<li class="step-button-wrapper">
<button class="step-button-next">Next</button>
</li>
<% end_if %>
</ul>
</nav>
<% end_if %> <% end_if %>

View File

@ -1,44 +1,19 @@
<% include UserFormProgress %>
<form $AttributesHTML> <form $AttributesHTML>
<% include UserFormProgress %>
<% include UserFormStepErrors %>
<% if $Message %> <% if $Message %>
<p id="{$FormName}_error" class="message $MessageType">$Message</p> <p id="{$FormName}_error" class="message $MessageType">$Message</p>
<% else %> <% else %>
<p id="{$FormName}_error" class="message $MessageType" aria-hidden="true" style="display: none;"></p> <p id="{$FormName}_error" class="message $MessageType" aria-hidden="true" style="display: none;"></p>
<% end_if %>
<% if $NumberOfSteps.Count > "1" %>
<fieldset class="error-container form-wide-errors" aria-hidden="true" style="display: none;">
<div>
<h4></h4>
<ul class="error-list"></ul>
</div>
</fieldset>
<% end_if %> <% end_if %>
<fieldset> <fieldset>
<% if $Legend %><legend>$Legend</legend><% end_if %> <% if $Legend %><legend>$Legend</legend><% end_if %>
<% loop $Fields %>
<% if $FormFields%><% loop $FormFields %> $FieldHolder
<fieldset id="step-$Pos" class="form-step" data-title="$Title"> <% end_loop %>
<% if $Top.DisplayErrorMessagesAtTop %>
<fieldset class="error-container" aria-hidden="true" style="display: none;">
<div>
<h4></h4>
<ul class="error-list"></ul>
</div>
</fieldset>
<% end_if %>
<% loop $Children %>
$FieldHolder
<% end_loop %>
<% include UserFormStepNav ContainingPage=$Top %>
</fieldset>
<% end_loop %><% end_if %>
<div class="clear"><!-- --></div> <div class="clear"><!-- --></div>
</fieldset> </fieldset>

View File

@ -0,0 +1,16 @@
<fieldset id="$Name" class="form-step $extraClass" data-title="$Title">
<% if $Form.DisplayErrorMessagesAtTop %>
<fieldset class="error-container" aria-hidden="true" style="display: none;">
<div>
<h4></h4>
<ul class="error-list"></ul>
</div>
</fieldset>
<% end_if %>
<% loop $Children %>
$FieldHolder
<% end_loop %>
<% include UserFormStepNav %>
</fieldset>