Add "Add page break" button

Fix unit test failures
This commit is contained in:
Damian Mooyman 2015-08-10 17:03:36 +12:00
parent 0ab7e27970
commit 9f112e3b23
30 changed files with 603 additions and 329 deletions

View File

@ -34,12 +34,16 @@ class UserFormFieldEditorExtension extends DataExtension {
public function getFieldEditorGrid() { public function getFieldEditorGrid() {
$fields = $this->owner->Fields(); $fields = $this->owner->Fields();
$this->createInitialFormStep(); $this->createInitialFormStep(true);
$editableColumns = new GridFieldEditableColumns(); $editableColumns = new GridFieldEditableColumns();
$editableColumns->setDisplayFields(array( $editableColumns->setDisplayFields(array(
'ClassName' => function($record, $column, $grid) { 'ClassName' => function($record, $column, $grid) {
if($record instanceof EditableFormStep) {
return new LabelField($column, "Page Break");
} else {
return DropdownField::create($column, '', $this->getEditableFieldClasses()); return DropdownField::create($column, '', $this->getEditableFieldClasses());
}
}, },
'Title' => function($record, $column, $grid) { 'Title' => function($record, $column, $grid) {
return TextField::create($column, ' ') return TextField::create($column, ' ')
@ -47,22 +51,28 @@ class UserFormFieldEditorExtension extends DataExtension {
} }
)); ));
$fieldEditor = GridField::create( $config = GridFieldConfig::create()
'Fields',
_t('UserDefinedForm.FIELDS', 'Fields'),
$fields,
GridFieldConfig::create()
->addComponents( ->addComponents(
$editableColumns, $editableColumns,
new GridFieldButtonRow(), new GridFieldButtonRow(),
new GridFieldAddNewInlineButton(), $addField = new GridFieldAddNewInlineButton(),
$addStep = new GridFieldAddItemInlineButton('EditableFormStep'),
new GridFieldEditButton(), new GridFieldEditButton(),
new GridFieldDeleteAction(), new GridFieldDeleteAction(),
new GridFieldToolbarHeader(), new GridFieldToolbarHeader(),
new GridFieldOrderableRows('Sort'), new GridFieldOrderableRows('Sort'),
new GridState_Component(), new GridState_Component(),
new GridFieldDetailForm() new GridFieldDetailForm()
) );
$addField->setTitle('Add Field');
$addStep->setTitle('Add Page Break');
$addStep->setExtraClass('uf-gridfield-steprow');
$fieldEditor = GridField::create(
'Fields',
_t('UserDefinedForm.FIELDS', 'Fields'),
$fields,
$config
); );
return $fieldEditor; return $fieldEditor;
@ -72,24 +82,41 @@ class UserFormFieldEditorExtension extends DataExtension {
* A UserForm must have at least one step. * A UserForm must have at least one step.
* If no steps exist, create an initial step, and put all fields inside it. * If no steps exist, create an initial step, and put all fields inside it.
* *
* @param bool $force
* @return void * @return void
*/ */
public function createInitialFormStep() { public function createInitialFormStep($force = false) {
// If there's already an initial step, do nothing. // Only invoke once saved
if ($this->owner->Fields()->filter('ClassName', 'EditableFormStep')->Count()) { if(!$this->owner->exists()) {
return; return;
} }
$step = EditableFormStep::create(); // Check if first field is a step
$fields = $this->owner->Fields();
$firstField = $fields->first();
if($firstField instanceof EditableFormStep) {
return;
}
$step->ParentID = $this->owner->ID; // Don't create steps on write if there are no formfields, as this
$step->write(); // can create duplicate first steps during publish of new records
if(!$force && !$firstField) {
return;
}
// Assign each field to the initial step. // Re-apply sort to each field starting at 2
foreach ($this->owner->Fields()->exclude('ID', $step->ID) as $field) { $next = 2;
$field->StepID = $step->ID; foreach($fields as $field) {
$field->Sort = $next++;
$field->write(); $field->write();
} }
// Add step
$step = EditableFormStep::create();
$step->Title = _t('EditableFormStep.TITLE_FIRST', 'First Step');
$step->Sort = 1;
$step->write();
$fields->add($step);
} }
/** /**
@ -119,6 +146,13 @@ class UserFormFieldEditorExtension extends DataExtension {
return $editableFieldClasses; return $editableFieldClasses;
} }
/**
* Ensure that at least one page exists at the start
*/
public function onAfterWrite() {
$this->createInitialFormStep();
}
/** /**
* @see SiteTree::doPublish * @see SiteTree::doPublish
* @param Page $original * @param Page $original

View File

@ -65,82 +65,42 @@ class UserForm extends Form {
return $steps; return $steps;
} }
/**
* Get the form steps.
*
* @return ArrayList
*/
public function getFormSteps() {
$steps = new ArrayList();
foreach ($this->controller->Fields() as $field) {
if ($field instanceof EditableFormStep) {
$steps->push($field->getFormField());
continue;
}
if(empty($steps->last())) {
trigger_error('Missing first step in form', E_USER_WARNING);
$steps->push(CompositeField::create());
}
$steps->last()->push($field->getFormField());
}
return $steps;
}
/** /**
* Get the form fields for the form on this page. Can modify this FieldSet * Get the form fields for the form on this page. Can modify this FieldSet
* by using {@link updateFormFields()} on an {@link Extension} subclass which * by using {@link updateFormFields()} on an {@link Extension} subclass which
* is applied to this controller. * is applied to this controller.
* *
* This will be a list of top level composite steps
*
* @return FieldList * @return FieldList
*/ */
public function getFormFields() { public function getFormFields() {
$fields = new FieldList(); $fields = new FieldList();
$emptyStep = null; // Last empty step, which may or may not later have children
foreach($this->controller->Fields() as $editableField) { foreach ($this->controller->Fields() as $field) {
// get the raw form field from the editable version // When we encounter a step, save it
$field = $editableField->getFormField(); if ($field instanceof EditableFormStep) {
$emptyStep = $field->getFormField();
if(!$field) continue; continue;
// set the error / formatting messages
$field->setCustomValidationMessage($editableField->getErrorMessage());
// set the right title on this field
if($right = $editableField->RightTitle) {
// Since this field expects raw html, safely escape the user data prior
$field->setRightTitle(Convert::raw2xml($right));
} }
// if this field is required add some // Ensure that the last field is a step
if($editableField->Required) { if($emptyStep) {
$field->addExtraClass('requiredField'); // When we reach the first non-step field, any empty step will no longer be empty
$fields->push($emptyStep);
$emptyStep = null;
if($identifier = UserDefinedForm::config()->required_identifier) { } elseif(! $fields->last()) {
// If no steps have been saved yet, warn
$title = $field->Title() ." <span class='required-identifier'>". $identifier . "</span>"; trigger_error('Missing first step in form', E_USER_WARNING);
$field->setTitle($title); $fields->push(singleton('EditableFormStep')->getFormField());
}
}
// if this field has an extra class
if($extraClass = $editableField->ExtraClass) {
$field->addExtraClass(Convert::raw2att($extraClass));
} }
// set the values passed by the url to the field $fields->last()->push($field->getFormField());
$request = $this->controller->getRequest();
if($value = $request->getVar($field->getName())) {
$field->setValue($value);
}
$fields->push($field);
} }
$this->extend('updateFormFields', $fields); $this->extend('updateFormFields', $fields);
return $fields; return $fields;
} }

View File

@ -0,0 +1,246 @@
<?php
/**
* Allows inline adding of classes with a default type
*
* Provides an alternative to GridFieldAddNewInlineButton, but allows you to set a classname
*/
class GridFieldAddItemInlineButton implements GridField_HTMLProvider, GridField_SaveHandler {
/**
* Fragment id
*
* @var string
*/
protected $fragment;
/**
* Button title
*
* @var string
*/
protected $title;
/**
* Class name
*
* @var string
*/
protected $modelClass = null;
/**
* Extra CSS classes for this row
*
* @var string
*/
protected $extraClass = null;
/**
* @param string $class Name of class to default to
* @param string $fragment The fragment to render the button into
*/
public function __construct($class, $fragment = 'buttons-before-left') {
$this->setClass($class);
$this->setFragment($fragment);
$this->setTitle(_t('GridFieldExtensions.ADD', 'Add'));
}
/**
* Gets the fragment name this button is rendered into.
*
* @return string
*/
public function getFragment() {
return $this->fragment;
}
/**
* Sets the fragment name this button is rendered into.
*
* @param string $fragment
* @return GridFieldAddNewInlineButton $this
*/
public function setFragment($fragment) {
$this->fragment = $fragment;
return $this;
}
/**
* Gets the button title text.
*
* @return string
*/
public function getTitle() {
return $this->title;
}
/**
* Sets the button title text.
*
* @param string $title
* @return GridFieldAddNewInlineButton $this
*/
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getHTMLFragments($grid) {
if($grid->getList() && !singleton($grid->getModelClass())->canCreate()) {
return array();
}
$fragment = $this->getFragment();
if(!$editable = $grid->getConfig()->getComponentByType('GridFieldEditableColumns')) {
throw new Exception('Inline adding requires the editable columns component');
}
Requirements::javascript(THIRDPARTY_DIR . '/javascript-templates/tmpl.js');
GridFieldExtensions::include_requirements();
Requirements::javascript(USERFORMS_DIR . '/javascript/GridFieldAddItemInlineButton.js');
$data = new ArrayData(array(
'Title' => $this->getTitle(),
'TemplateName' => $this->getRowTemplateName()
));
return array(
$fragment => $data->renderWith(__CLASS__),
'after' => $this->getItemRowTemplate($grid, $editable)
);
}
/**
* Because getRowTemplate is private
*
* @param GridField $grid
* @param GridFieldEditableColumns $editable
* @return type
*/
protected function getItemRowTemplate(GridField $grid, GridFieldEditableColumns $editable) {
$columns = new ArrayList();
$handled = array_keys($editable->getDisplayFields($grid));
$record = Object::create($this->getClass());
$fields = $editable->getFields($grid, $record);
foreach($grid->getColumns() as $column) {
$content = null;
if(in_array($column, $handled)) {
$field = $fields->dataFieldByName($column);
if($field) {
$field->setName(sprintf(
'%s[%s][%s][{%%=o.num%%}][%s]', $grid->getName(), __CLASS__, $this->getClass(), $field->getName()
));
} else {
$field = $fields->fieldByName($column);
}
if($field) {
$content = $field->Field();
}
}
$attrs = '';
foreach($grid->getColumnAttributes($record, $column) as $attr => $val) {
$attrs .= sprintf(' %s="%s"', $attr, Convert::raw2att($val));
}
$columns->push(new ArrayData(array(
'Content' => $content,
'Attributes' => $attrs,
'IsActions' => $column == 'Actions'
)));
}
$data = new ArrayData(array(
'Columns' => $columns,
'ExtraClass' => $this->getExtraClass(),
'RecordClass' => $this->getClass(),
'TemplateName' => $this->getRowTemplateName()
));
return $data->renderWith(__CLASS__ . '_Row');
}
public function handleSave(GridField $grid, DataObjectInterface $record) {
$list = $grid->getList();
$value = $grid->Value();
$class = $this->getClass();
if(!isset($value[__CLASS__][$class]) || !is_array($value[__CLASS__][$class])) {
return;
}
$editable = $grid->getConfig()->getComponentByType('GridFieldEditableColumns');
$form = $editable->getForm($grid, $record);
if(!singleton($class)->canCreate()) {
return;
}
// Process records matching the specified class
foreach($value[__CLASS__][$class] as $fields) {
$item = $class::create();
$extra = array();
$form->loadDataFrom($fields, Form::MERGE_CLEAR_MISSING);
$form->saveInto($item);
if($list instanceof ManyManyList) {
$extra = array_intersect_key($form->getData(), (array) $list->getExtraFields());
}
$item->write();
$list->add($item, $extra);
}
}
/**
* Get the class of the object to create
*
* @return string
*/
public function getClass() {
return $this->modelClass;
}
/**
* Specify the class to create
*
* @param string $class
*/
public function setClass($class) {
$this->modelClass = $class;
}
/**
* Get extra CSS classes for this row
*
* @return type
*/
public function getExtraClass() {
return $this->extraClass;
}
/**
* Sets extra CSS classes for this row
*
* @param string $extraClass
*/
public function setExtraClass($extraClass) {
$this->extraClass = $extraClass;
}
/**
* Get name of item template
*
* @return string
*/
public function getRowTemplateName() {
return 'ss-gridfield-add-inline-template-' . $this->getClass();
}
}

View File

@ -98,6 +98,7 @@ class UserDefinedForm extends Page {
* @return FieldList * @return FieldList
*/ */
public function getCMSFields() { public function getCMSFields() {
Requirements::css(USERFORMS_DIR . '/css/UserForm_cms.css');
$self = $this; $self = $this;
@ -365,9 +366,7 @@ class UserDefinedForm_Controller extends Page_Controller {
*/ */
public function Form() { public function Form() {
$form = UserForm::create($this); $form = UserForm::create($this);
$this->generateConditionalJavascript(); $this->generateConditionalJavascript();
return $form; return $form;
} }

View File

@ -14,17 +14,15 @@ class EditableCheckboxGroupField extends EditableMultipleOptionField {
private static $plural_name = "Checkbox Groups"; private static $plural_name = "Checkbox Groups";
public function getFormField() { public function getFormField() {
$optionSet = $this->Options(); $field = new UserFormsCheckboxSetField($this->Name, $this->EscapedTitle, $this->getOptionsMap());
$optionMap = $optionSet->map('EscapedTitle', 'Title');
$field = new UserFormsCheckboxSetField($this->Name, $this->Title, $optionMap);
// Set the default checked items // Set the default checked items
$defaultCheckedItems = $optionSet->filter('Default', 1); $defaultCheckedItems = $this->getDefaultOptions();
if ($defaultCheckedItems->count()) { if ($defaultCheckedItems->count()) {
$field->setDefaultItems($defaultCheckedItems->map('EscapedTitle')->keys()); $field->setDefaultItems($defaultCheckedItems->map('EscapedTitle')->keys());
} }
$this->doUpdateFormField($field);
return $field; return $field;
} }

View File

@ -23,7 +23,9 @@ class EditableCountryDropdownField extends EditableFormField {
} }
public function getFormField() { public function getFormField() {
return CountryDropdownField::create($this->Name, $this->Title); $field = CountryDropdownField::create($this->Name, $this->EscapedTitle);
$this->doUpdateFormField($field);
return $field;
} }
public function getValueFromData($data) { public function getValueFromData($data) {

View File

@ -43,17 +43,9 @@ class EditableDateField extends EditableFormField {
$defaultValue = $this->DefaultToToday $defaultValue = $this->DefaultToToday
? SS_Datetime::now()->Format('Y-m-d') ? SS_Datetime::now()->Format('Y-m-d')
: $this->Default; : $this->Default;
$field = EditableDateField_FormField::create( $this->Name, $this->Title, $defaultValue); $field = EditableDateField_FormField::create( $this->Name, $this->EscapedTitle, $defaultValue);
$field->setConfig('showcalendar', true); $field->setConfig('showcalendar', true);
$this->doUpdateFormField($field);
if ($this->Required) {
// Required validation can conflict so add the Required validation messages
// as input attributes
$errorMessage = $this->getErrorMessage()->HTML();
$field->setAttribute('data-rule-required', 'true');
$field->setAttribute('data-msg-required', $errorMessage);
}
return $field; return $field;
} }
} }

View File

@ -28,30 +28,14 @@ class EditableDropdown extends EditableMultipleOptionField {
* @return DropdownField * @return DropdownField
*/ */
public function getFormField() { public function getFormField() {
$optionSet = $this->Options(); $field = DropdownField::create($this->Name, $this->EscapedTitle, $this->getOptionsMap());
$defaultOptions = $optionSet->filter('Default', 1);
$options = array();
if($optionSet) { // Set default
foreach($optionSet as $option) { $defaultOption = $this->getDefaultOptions()->first();
$options[$option->Title] = $option->Title; if($defaultOption) {
$field->setValue($defaultOption->EscapedTitle);
} }
} $this->doUpdateFormField($field);
$field = DropdownField::create($this->Name, $this->Title, $options);
if ($this->Required) {
// Required validation can conflict so add the Required validation messages
// as input attributes
$errorMessage = $this->getErrorMessage()->HTML();
$field->setAttribute('data-rule-required', 'true');
$field->setAttribute('data-msg-required', $errorMessage);
}
if($defaultOptions->count()) {
$field->setValue($defaultOptions->First()->EscapedTitle);
}
return $field; return $field;
} }
} }

View File

@ -18,19 +18,8 @@ class EditableEmailField extends EditableFormField {
} }
public function getFormField() { public function getFormField() {
$field = EmailField::create($this->Name, $this->EscapedTitle, $this->Default);
$field = EmailField::create($this->Name, $this->Title); $this->doUpdateFormField($field);
if ($this->Required) {
// Required and Email validation can conflict so add the Required validation messages
// as input attributes
$errorMessage = $this->getErrorMessage()->HTML();
$field->setAttribute('data-rule-required', 'true');
$field->setAttribute('data-msg-required', $errorMessage);
}
$field->setValue($this->Default);
return $field; return $field;
} }

View File

@ -35,7 +35,7 @@ class EditableFileField extends EditableFormField {
} }
public function getFormField() { public function getFormField() {
$field = FileField::create($this->Name, $this->Title); $field = FileField::create($this->Name, $this->EscapedTitle);
// filter out '' since this would be a regex problem on JS end // filter out '' since this would be a regex problem on JS end
$field->getValidator()->setAllowedExtensions( $field->getValidator()->setAllowedExtensions(
@ -49,13 +49,7 @@ class EditableFileField extends EditableFormField {
); );
} }
if ($this->Required) { $this->doUpdateFormField($field);
// Required validation can conflict so add the Required validation messages
// as input attributes
$errorMessage = $this->getErrorMessage()->HTML();
$field->setAttribute('data-rule-required', 'true');
$field->setAttribute('data-msg-required', $errorMessage);
}
return $field; return $field;
} }

View File

@ -433,31 +433,13 @@ class EditableFormField extends DataObject {
return true; return true;
} }
/**
* Title field of the field in the backend of the page
*
* @return TextField
*/
public function TitleField() {
$label = _t('EditableFormField.ENTERQUESTION', 'Enter Question');
$field = new TextField('Title', $label, $this->getField('Title'));
$field->setName($this->getFieldName('Title'));
if(!$this->canEdit()) {
return $field->performReadonlyTransformation();
}
return $field;
}
/** /**
* Returns the Title for rendering in the front-end (with XML values escaped) * Returns the Title for rendering in the front-end (with XML values escaped)
* *
* @return string * @return string
*/ */
public function getTitle() { public function getEscapedTitle() {
return Convert::raw2att($this->getField('Title')); return Convert::raw2xml($this->Title);
} }
/** /**
@ -497,7 +479,7 @@ class EditableFormField extends DataObject {
/** /**
* Return a FormField to appear on the front end. Implement on * Return a FormField to appear on the front end. Implement on
* your subclass * your subclass.
* *
* @return FormField * @return FormField
*/ */
@ -505,6 +487,52 @@ class EditableFormField extends DataObject {
user_error("Please implement a getFormField() on your EditableFormClass ". $this->ClassName, E_USER_ERROR); user_error("Please implement a getFormField() on your EditableFormClass ". $this->ClassName, E_USER_ERROR);
} }
/**
* Updates a formfield with extensions
*
* @param FormField $field
*/
public function doUpdateFormField($field) {
$this->extend('beforeUpdateFormField', $field);
$this->updateFormField($field);
$this->extend('afterUpdateFormField', $field);
}
/**
* Updates a formfield with the additional metadata specified by this field
*
* @param FormField $field
*/
protected function updateFormField($field) {
// set the error / formatting messages
$field->setCustomValidationMessage($this->getErrorMessage());
// set the right title on this field
if($this->RightTitle) {
// Since this field expects raw html, safely escape the user data prior
$field->setRightTitle(Convert::raw2xml($this->RightTitle));
}
// if this field is required add some
if($this->Required) {
// Required validation can conflict so add the Required validation messages as input attributes
$errorMessage = $this->getErrorMessage()->HTML();
$field->addExtraClass('requiredField');
$field->setAttribute('data-rule-required', 'true');
$field->setAttribute('data-msg-required', $errorMessage);
if($identifier = UserDefinedForm::config()->required_identifier) {
$title = $field->Title() . " <span class='required-identifier'>". $identifier . "</span>";
$field->setTitle($title);
}
}
// if this field has an extra class
if($field->ExtraClass) {
$field->addExtraClass($field->ExtraClass);
}
}
/** /**
* Return the instance of the submission field class * Return the instance of the submission field class
* *

View File

@ -55,11 +55,24 @@ class EditableFormHeading extends EditableFormField {
} }
public function getFormField() { public function getFormField() {
$labelField = new HeaderField($this->Name, $this->Title, $this->Level); $labelField = new HeaderField($this->Name, $this->EscapedTitle, $this->Level);
$labelField->addExtraClass('FormHeading'); $labelField->addExtraClass('FormHeading');
$this->doUpdateFormField($labelField);
return $labelField; return $labelField;
} }
protected function updateFormField($field) {
// set the right title on this field
if($this->RightTitle) {
// Since this field expects raw html, safely escape the user data prior
$field->setRightTitle(Convert::raw2xml($this->RightTitle));
}
// if this field has an extra class
if($field->ExtraClass) {
$field->addExtraClass($field->ExtraClass);
}
}
public function showInReports() { public function showInReports() {
return !$this->HideFromReports; return !$this->HideFromReports;
} }

View File

@ -25,7 +25,6 @@ class EditableFormStep extends EditableFormField {
$fields = parent::getCMSFields(); $fields = parent::getCMSFields();
$fields->removeByName('MergeField'); $fields->removeByName('MergeField');
$fields->removeByName('StepID');
$fields->removeByName('Default'); $fields->removeByName('Default');
$fields->removeByName('Validation'); $fields->removeByName('Validation');
$fields->removeByName('CustomRules'); $fields->removeByName('CustomRules');
@ -37,7 +36,17 @@ class EditableFormStep extends EditableFormField {
* @return FormField * @return FormField
*/ */
public function getFormField() { public function getFormField() {
return CompositeField::create()->setTitle($this->Title); $field = CompositeField::create()
->setTitle($this->EscapedTitle);
$this->doUpdateFormField($field);
return $field;
}
protected function updateFormField($field) {
// if this field has an extra class
if($field->ExtraClass) {
$field->addExtraClass($field->ExtraClass);
}
} }
/** /**

View File

@ -103,12 +103,16 @@ class EditableLiteralField extends EditableFormField {
} }
public function getFormField() { public function getFormField() {
$label = $this->Title // Build label and css classes
? "<label class='left'>".Convert::raw2xml($this->Title)."</label>" $label = '';
: ""; $classes = $this->ExtraClass;
$classes = $this->Title ? "" : " nolabel"; if(empty($this->Title)) {
$classes .= " nolabel";
} else {
$label = "<label class='left'>{$this->EscapedTitle}</label>";
}
return new LiteralField( $field = new LiteralField(
"LiteralField[{$this->ID}]", "LiteralField[{$this->ID}]",
sprintf( sprintf(
"<div id='%s' class='field text%s'> "<div id='%s' class='field text%s'>
@ -121,6 +125,9 @@ class EditableLiteralField extends EditableFormField {
$this->Content $this->Content
) )
); );
// When dealing with literal fields there is no further customisation that can be added at this point
return $field;
} }
public function showInReports() { public function showInReports() {

View File

@ -42,7 +42,9 @@ class EditableMemberListField extends EditableFormField {
} }
$members = Member::map_in_groups($this->GroupID); $members = Member::map_in_groups($this->GroupID);
return new DropdownField($this->Name, $this->Title, $members); $field = new DropdownField($this->Name, $this->EscapedTitle, $members);
$this->doUpdateFormField($field);
return $field;
} }
public function getValueFromData($data) { public function getValueFromData($data) {

View File

@ -156,11 +156,25 @@ class EditableMultipleOptionField extends EditableFormField {
} }
/** /**
* Return the form field for this object in the front end form view * Gets map of field options suitable for use in a form
* *
* @return FormField * @return array
*/ */
public function getFormField() { protected function getOptionsMap() {
return user_error('Please implement getFormField() on '. $this->class, E_USER_ERROR); $optionSet = $this->Options();
$optionMap = $optionSet->map('EscapedTitle', 'Title');
if($optionMap instanceof SS_Map) {
return $optionMap->toArray();
}
return $optionMap;
}
/**
* Returns all default options
*
* @return SS_List
*/
protected function getDefaultOptions() {
return $this->Options()->filter('Default', 1);
} }
} }

View File

@ -26,18 +26,9 @@ class EditableNumericField extends EditableFormField {
* @return NumericField * @return NumericField
*/ */
public function getFormField() { public function getFormField() {
$field = new NumericField($this->Name, $this->Title); $field = new NumericField($this->Name, $this->EscapedTitle, $this->Default);
$field->addExtraClass('number'); $field->addExtraClass('number');
$field->setValue($this->Default); $this->doUpdateFormField($field);
if ($this->Required) {
// Required and numeric validation can conflict so add the
// required validation messages as input attributes
$errorMessage = $this->getErrorMessage()->HTML();
$field->setAttribute('data-rule-required', 'true');
$field->setAttribute('data-msg-required', $errorMessage);
}
return $field; return $field;
} }

View File

@ -49,39 +49,6 @@ class EditableOption extends DataObject {
return ($this->Parent()->canDelete($member)); return ($this->Parent()->canDelete($member));
} }
/**
* Template for the editing view of this option field
*/
public function EditSegment() {
return $this->renderWith('EditableOption');
}
/**
* The Title Field for this object
*
* @return FormField
*/
public function TitleField() {
return new TextField("Fields[{$this->ParentID}][{$this->ID}][Title]", null, $this->Title );
}
/**
* Name of this field in the form
*
* @return String
*/
public function FieldName() {
return "Fields[{$this->ParentID}][{$this->ID}]";
}
/**
* Make this option readonly
*/
public function ReadonlyOption() {
$this->readonly = true;
return $this->EditSegment();
}
public function getEscapedTitle() { public function getEscapedTitle() {
return Convert::raw2att($this->Title); return Convert::raw2att($this->Title);
} }

View File

@ -25,22 +25,14 @@ class EditableRadioField extends EditableMultipleOptionField {
} }
public function getFormField() { public function getFormField() {
$optionSet = $this->Options(); $field = OptionsetField::create($this->Name, $this->EscapedTitle, $this->getOptionsMap());
$defaultOptions = $optionSet->filter('Default', 1);
$options = array();
if($optionSet) { // Set default item
foreach($optionSet as $option) { $defaultOption = $this->getDefaultOptions()->first();
$options[$option->EscapedTitle] = $option->Title; if($defaultOption) {
$field->setValue($defaultOption->EscapedTitle);
} }
} $this->doUpdateFormField($field);
$field = OptionsetField::create($this->Name, $this->Title, $options);
if($defaultOptions->count()) {
$field->setValue($defaultOptions->First()->EscapedTitle);
}
return $field; return $field;
} }
} }

View File

@ -65,22 +65,12 @@ class EditableTextField extends EditableFormField {
*/ */
public function getFormField() { public function getFormField() {
if($this->Rows > 1) { if($this->Rows > 1) {
$field = TextareaField::create($this->Name, $this->Title); $field = TextareaField::create($this->Name, $this->EscapedTitle, $this->Default);
$field->setRows($this->Rows); $field->setRows($this->Rows);
} else { } else {
$field = TextField::create($this->Name, $this->Title, null, $this->MaxLength); $field = TextField::create($this->Name, $this->EscapedTitle, $this->Default, $this->MaxLength);
} }
$this->doUpdateFormField($field);
if ($this->Required) {
// Required validation can conflict so add the Required validation messages
// as input attributes
$errorMessage = $this->getErrorMessage()->HTML();
$field->setAttribute('data-rule-required', 'true');
$field->setAttribute('data-msg-required', $errorMessage);
}
$field->setValue($this->Default);
return $field; return $field;
} }

View File

@ -32,16 +32,8 @@ class EditableCheckbox extends EditableFormField {
} }
public function getFormField() { public function getFormField() {
$field = CheckboxField::create($this->Name, $this->Title, $this->CheckedDefault); $field = CheckboxField::create($this->Name, $this->EscapedTitle, $this->CheckedDefault);
$this->doUpdateFormField($field);
if ($this->Required) {
// Required validation can conflict so add the Required validation messages
// as input attributes
$errorMessage = $this->getErrorMessage()->HTML();
$field->setAttribute('data-rule-required', 'true');
$field->setAttribute('data-msg-required', $errorMessage);
}
return $field; return $field;
} }

24
css/UserForm_cms.css Normal file
View File

@ -0,0 +1,24 @@
.cms .ss-gridfield > div.ss-gridfield-buttonrow-before {
margin-bottom: 10px;
overflow: auto;
}
/**
* Styles for page steps
*/
.cms table.ss-gridfield-table .ss-gridfield-inline-new.uf-gridfield-steprow,
.cms table.ss-gridfield-table .ss-gridfield-item[data-class='EditableFormStep'],
.cms table.ss-gridfield-table .ss-gridfield-inline-new.uf-gridfield-steprow:hover,
.cms table.ss-gridfield-table .ss-gridfield-item[data-class='EditableFormStep']:hover
{
background: #dae2e7;
}
.cms table.ss-gridfield-table .ss-gridfield-inline-new.uf-gridfield-steprow label,
.cms table.ss-gridfield-table .ss-gridfield-item[data-class='EditableFormStep'] label
{
font-weight: bold;
color: #393939;
font-size: 1.1em;
}

View File

@ -0,0 +1,41 @@
(function($) {
$.entwine("ss", function($) {
// See gridfieldextensions/javascript/GridFieldExtensions.js
$(".ss-gridfield.ss-gridfield-editable").entwine({
onaddnewiteminline: function(e, template) {
var tmpl = window.tmpl;
var row = this.find("." + template);
var num = this.data("add-inline-num") || 1;
tmpl.cache[template] = tmpl(row.html());
this.find("tbody").append(tmpl(template, { num: num }));
this.find(".ss-gridfield-no-items").hide();
this.data("add-inline-num", num + 1);
}
});
$(".ss-gridfield-add-new-item-inline").entwine({
onclick: function() {
// Get custom class from button
var template = this.data('template');
this.getGridField().trigger("addnewiteminline", template);
return false;
}
});
$(".ss-gridfield-delete-inline").entwine({
onclick: function() {
var msg = ss.i18n._t("GridFieldExtensions.CONFIRMDEL", "Are you sure you want to delete this?");
if(confirm(msg)) {
this.parents("tr").remove();
}
return false;
}
});
});
})(jQuery);

View File

@ -11,7 +11,7 @@
<fieldset> <fieldset>
<% if $Legend %><legend>$Legend</legend><% end_if %> <% if $Legend %><legend>$Legend</legend><% end_if %>
<% loop $FormSteps %> <% if $FormFields%><% loop $FormFields %>
<fieldset class="form-step"> <fieldset class="form-step">
<% if $Top.DisplayErrorMessagesAtTop %> <% if $Top.DisplayErrorMessagesAtTop %>
<fieldset class="error-container" aria-hidden="true" style="display: none;"> <fieldset class="error-container" aria-hidden="true" style="display: none;">
@ -30,7 +30,7 @@
<% include UserFormStepNav ContainingPage=$Top %> <% include UserFormStepNav ContainingPage=$Top %>
</fieldset> </fieldset>
<% end_loop %> <% end_loop %><% end_if %>
<div class="clear"><!-- --></div> <div class="clear"><!-- --></div>
</fieldset> </fieldset>

View File

@ -0,0 +1,3 @@
<button href="$Link" class="ss-gridfield-add-new-item-inline ss-ui-button" data-icon="add" data-template="$TemplateName.ATT">
$Title
</button>

View File

@ -0,0 +1,13 @@
<script type="text/x-tmpl" class="$TemplateName">
<tr class="ss-gridfield-inline-new $ExtraClass.ATT">
<% loop $Columns %>
<% if $IsActions %>
<td$Attributes>
<button class="ss-gridfield-delete-inline gridfield-button-delete ss-ui-button" data-icon="cross-circle"></button>
</td>
<% else %>
<td$Attributes>$Content</td>
<% end_if %>
<% end_loop %>
</tr>
</script>

View File

@ -80,27 +80,6 @@ class EditableFormFieldTest extends FunctionalTest {
$this->assertEquals(array('Option 5' => 'Option 5', 'Option 6' => 'Option 6'), $values); $this->assertEquals(array('Option 5' => 'Option 5', 'Option 6' => 'Option 6'), $values);
} }
function testTitleField() {
$text = $this->objFromFixture('EditableTextField', 'basic-text');
$this->logInWithPermission('ADMIN');
$title = $text->TitleField();
$this->assertThat($title, $this->isInstanceOf('TextField'));
$this->assertEquals($title->Title(), "Enter Question");
$this->assertEquals($title->Value(), "Basic Text Field");
$member = Member::currentUser();
$member->logOut();
// read only version
$title = $text->TitleField();
$this->assertThat($title, $this->isInstanceOf('ReadonlyField'));
$this->assertEquals($title->Title(), "Enter Question");
$this->assertEquals($title->Value(), "Basic Text Field");
}
function testMultipleOptionDuplication() { function testMultipleOptionDuplication() {
$dropdown = $this->objFromFixture('EditableDropdown','basic-dropdown'); $dropdown = $this->objFromFixture('EditableDropdown','basic-dropdown');

View File

@ -6,7 +6,7 @@
class UserDefinedFormControllerTest extends FunctionalTest { class UserDefinedFormControllerTest extends FunctionalTest {
static $fixture_file = 'userforms/tests/UserDefinedFormTest.yml'; static $fixture_file = 'UserDefinedFormTest.yml';
function testProcess() { function testProcess() {
$form = $this->setupFormFrontend(); $form = $this->setupFormFrontend();
@ -106,9 +106,11 @@ class UserDefinedFormControllerTest extends FunctionalTest {
$controller = new UserDefinedFormControllerTest_Controller($form); $controller = new UserDefinedFormControllerTest_Controller($form);
$fields = $controller->Form()->getFormFields(); $formSteps = $controller->Form()->getFormFields();
$firstStep = $formSteps->first();
$this->assertEquals($fields->Count(), 1); $this->assertEquals($formSteps->Count(), 1);
$this->assertEquals($firstStep->getChildren()->Count(), 1);
// custom error message on a form field // custom error message on a form field
$requiredForm = $this->objFromFixture('UserDefinedForm', 'validation-form'); $requiredForm = $this->objFromFixture('UserDefinedForm', 'validation-form');
@ -116,20 +118,23 @@ class UserDefinedFormControllerTest extends FunctionalTest {
UserDefinedForm::config()->required_identifier = "*"; UserDefinedForm::config()->required_identifier = "*";
$fields = $controller->Form()->getFormFields(); $formSteps = $controller->Form()->getFormFields();
$firstStep = $formSteps->first();
$firstField = $firstStep->getChildren()->first();
$this->assertEquals($fields->First()->getCustomValidationMessage()->getValue(), 'Custom Error Message'); $this->assertEquals('Custom Error Message', $firstField->getCustomValidationMessage()->getValue());
$this->assertEquals($fields->First()->Title(), 'Required Text Field <span class=\'required-identifier\'>*</span>'); $this->assertEquals($firstField->Title(), 'Required Text Field <span class=\'required-identifier\'>*</span>');
// test custom right title // test custom right title
$field = $form->Fields()->First(); $field = $form->Fields()->limit(1, 1)->First();
$field->RightTitle = 'Right Title'; $field->RightTitle = 'Right Title';
$field->write(); $field->write();
$controller = new UserDefinedFormControllerTest_Controller($form); $controller = new UserDefinedFormControllerTest_Controller($form);
$fields = $controller->Form()->getFormFields(); $formSteps = $controller->Form()->getFormFields();
$firstStep = $formSteps->first();
$this->assertEquals($fields->First()->RightTitle(), "Right Title"); $this->assertEquals($firstStep->getChildren()->First()->RightTitle(), "Right Title");
// test empty form // test empty form
$emptyForm = $this->objFromFixture('UserDefinedForm', 'empty-form'); $emptyForm = $this->objFromFixture('UserDefinedForm', 'empty-form');

View File

@ -3,13 +3,11 @@
/** /**
* @package userforms * @package userforms
*/ */
class UserDefinedFormTest extends FunctionalTest { class UserDefinedFormTest extends FunctionalTest {
static $fixture_file = 'UserDefinedFormTest.yml'; static $fixture_file = 'UserDefinedFormTest.yml';
public function testRollbackToVersion() {
function testRollbackToVersion() {
$this->markTestSkipped( $this->markTestSkipped(
'UserDefinedForm::rollback() has not been implemented completely' 'UserDefinedForm::rollback() has not been implemented completely'
); );
@ -37,7 +35,7 @@ class UserDefinedFormTest extends FunctionalTest {
$this->assertEquals($orignal->SubmitButtonText, 'Button Text'); $this->assertEquals($orignal->SubmitButtonText, 'Button Text');
} }
function testGetCMSFields() { public function testGetCMSFields() {
$this->logInWithPermission('ADMIN'); $this->logInWithPermission('ADMIN');
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page'); $form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
@ -49,7 +47,7 @@ class UserDefinedFormTest extends FunctionalTest {
$this->assertTrue($fields->dataFieldByName('OnCompleteMessage') != null); $this->assertTrue($fields->dataFieldByName('OnCompleteMessage') != null);
} }
function testEmailRecipientPopup() { public function testEmailRecipientPopup() {
$this->logInWithPermission('ADMIN'); $this->logInWithPermission('ADMIN');
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page'); $form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
@ -160,13 +158,13 @@ class UserDefinedFormTest extends FunctionalTest {
$live = Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $form->ID"); $live = Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $form->ID");
$this->assertNotNull($live); $this->assertNotNull($live);
$this->assertEquals($live->Fields()->Count(), 1); $this->assertEquals(2, $live->Fields()->Count()); // one page and one field
$dropdown = $this->objFromFixture('EditableDropdown', 'basic-dropdown'); $dropdown = $this->objFromFixture('EditableDropdown', 'basic-dropdown');
$form->Fields()->add($dropdown); $form->Fields()->add($dropdown);
$stage = Versioned::get_one_by_stage("UserDefinedForm", "Stage", "\"UserDefinedForm\".\"ID\" = $form->ID"); $stage = Versioned::get_one_by_stage("UserDefinedForm", "Stage", "\"UserDefinedForm\".\"ID\" = $form->ID");
$this->assertEquals($stage->Fields()->Count(), 2); $this->assertEquals(3, $stage->Fields()->Count());
// should not have published the dropdown // should not have published the dropdown
$liveDropdown = Versioned::get_one_by_stage("EditableFormField", "Live", "\"EditableFormField_Live\".\"ID\" = $dropdown->ID"); $liveDropdown = Versioned::get_one_by_stage("EditableFormField", "Live", "\"EditableFormField_Live\".\"ID\" = $dropdown->ID");
@ -176,11 +174,10 @@ class UserDefinedFormTest extends FunctionalTest {
$form->doPublish(); $form->doPublish();
$live = Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $form->ID"); $live = Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $form->ID");
$this->assertEquals($live->Fields()->Count(), 2); $this->assertEquals(3, $live->Fields()->Count());
// edit the title // edit the title
$text = $form->Fields()->First(); $text = $form->Fields()->limit(1, 1)->First();
$text->Title = 'Edited title'; $text->Title = 'Edited title';
$text->write(); $text->write();
@ -197,21 +194,20 @@ class UserDefinedFormTest extends FunctionalTest {
$this->logInWithPermission('ADMIN'); $this->logInWithPermission('ADMIN');
$form = $this->objFromFixture('UserDefinedForm', 'basic-form-page'); $form = $this->objFromFixture('UserDefinedForm', 'basic-form-page');
$form->write(); $form->write();
$this->assertEquals(0, DB::query("SELECT COUNT(*) FROM \"EditableFormField_Live\"")->value());
$form->doPublish(); $form->doPublish();
// assert that it exists and has a field // assert that it exists and has a field
$live = Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $form->ID"); $live = Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $form->ID");
$this->assertTrue(isset($live)); $this->assertTrue(isset($live));
$this->assertEquals(DB::query("SELECT COUNT(*) FROM \"EditableFormField_Live\"")->value(), 1); $this->assertEquals(2, DB::query("SELECT COUNT(*) FROM \"EditableFormField_Live\"")->value());
// unpublish // unpublish
$form->doUnpublish(); $form->doUnpublish();
$this->assertNull(Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $form->ID")); $this->assertNull(Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $form->ID"));
$this->assertEquals(DB::query("SELECT COUNT(*) FROM \"EditableFormField_Live\"")->value(), 0); $this->assertEquals(0, DB::query("SELECT COUNT(*) FROM \"EditableFormField_Live\"")->value());
} }
function testDoRevertToLive() { function testDoRevertToLive() {

View File

@ -1,3 +1,13 @@
EditableFormStep:
form1step1:
Title: 'Step 1'
form3step1:
Title: 'Step 1'
form4step1:
Title: 'Step 1'
form5step1:
Title: 'Step 1'
EditableOption: EditableOption:
option-1: option-1:
Name: Option1 Name: Option1
@ -183,7 +193,7 @@ UserDefinedForm_EmailRecipient:
UserDefinedForm: UserDefinedForm:
basic-form-page: basic-form-page:
Title: User Defined Form Title: User Defined Form
Fields: =>EditableTextField.basic-text Fields: =>EditableFormStep.form1step1,=>EditableTextField.basic-text
EmailRecipients: =>UserDefinedForm_EmailRecipient.recipient-1, =>UserDefinedForm_EmailRecipient.no-html, =>UserDefinedForm_EmailRecipient.no-data EmailRecipients: =>UserDefinedForm_EmailRecipient.recipient-1, =>UserDefinedForm_EmailRecipient.no-html, =>UserDefinedForm_EmailRecipient.no-data
form-with-reset-and-custom-action: form-with-reset-and-custom-action:
@ -193,15 +203,15 @@ UserDefinedForm:
validation-form: validation-form:
Title: Validation Form Title: Validation Form
Fields: =>EditableTextField.required-text Fields: =>EditableFormStep.form3step1,=>EditableTextField.required-text
custom-rules-form: custom-rules-form:
Title: Custom Rules Form Title: Custom Rules Form
Fields: =>EditableCheckbox.checkbox-2, =>EditableTextField.basic-text-2 Fields: =>EditableFormStep.form4step1,=>EditableCheckbox.checkbox-2, =>EditableTextField.basic-text-2
empty-form: empty-form:
Title: Empty Form Title: Empty Form
filtered-form-page: filtered-form-page:
Title: 'Page with filtered recipients' Title: 'Page with filtered recipients'
Fields: =>EditableCheckboxGroupField.checkbox-group, =>EditableTextField.your-name-field, =>EditableTextField.street-field, =>EditableTextField.city-field Fields: =>EditableFormStep.form5step1,=>EditableCheckboxGroupField.checkbox-group, =>EditableTextField.your-name-field, =>EditableTextField.street-field, =>EditableTextField.city-field
EmailRecipients: =>UserDefinedForm_EmailRecipient.unfiltered-recipient-1, =>UserDefinedForm_EmailRecipient.filtered-recipient-1, =>UserDefinedForm_EmailRecipient.filtered-recipient-2 EmailRecipients: =>UserDefinedForm_EmailRecipient.unfiltered-recipient-1, =>UserDefinedForm_EmailRecipient.filtered-recipient-1, =>UserDefinedForm_EmailRecipient.filtered-recipient-2