diff --git a/code/extensions/UserFormFieldEditorExtension.php b/code/extensions/UserFormFieldEditorExtension.php index db0bac9..7526216 100644 --- a/code/extensions/UserFormFieldEditorExtension.php +++ b/code/extensions/UserFormFieldEditorExtension.php @@ -34,12 +34,16 @@ class UserFormFieldEditorExtension extends DataExtension { public function getFieldEditorGrid() { $fields = $this->owner->Fields(); - $this->createInitialFormStep(); + $this->createInitialFormStep(true); $editableColumns = new GridFieldEditableColumns(); $editableColumns->setDisplayFields(array( 'ClassName' => function($record, $column, $grid) { - return DropdownField::create($column, '', $this->getEditableFieldClasses()); + if($record instanceof EditableFormStep) { + return new LabelField($column, "Page Break"); + } else { + return DropdownField::create($column, '', $this->getEditableFieldClasses()); + } }, 'Title' => function($record, $column, $grid) { return TextField::create($column, ' ') @@ -47,22 +51,28 @@ class UserFormFieldEditorExtension extends DataExtension { } )); + $config = GridFieldConfig::create() + ->addComponents( + $editableColumns, + new GridFieldButtonRow(), + $addField = new GridFieldAddNewInlineButton(), + $addStep = new GridFieldAddItemInlineButton('EditableFormStep'), + new GridFieldEditButton(), + new GridFieldDeleteAction(), + new GridFieldToolbarHeader(), + new GridFieldOrderableRows('Sort'), + new GridState_Component(), + 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, - GridFieldConfig::create() - ->addComponents( - $editableColumns, - new GridFieldButtonRow(), - new GridFieldAddNewInlineButton(), - new GridFieldEditButton(), - new GridFieldDeleteAction(), - new GridFieldToolbarHeader(), - new GridFieldOrderableRows('Sort'), - new GridState_Component(), - new GridFieldDetailForm() - ) + $config ); return $fieldEditor; @@ -72,24 +82,41 @@ class UserFormFieldEditorExtension extends DataExtension { * A UserForm must have at least one step. * If no steps exist, create an initial step, and put all fields inside it. * + * @param bool $force * @return void */ - public function createInitialFormStep() { - // If there's already an initial step, do nothing. - if ($this->owner->Fields()->filter('ClassName', 'EditableFormStep')->Count()) { + public function createInitialFormStep($force = false) { + // Only invoke once saved + if(!$this->owner->exists()) { 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; - $step->write(); + // Don't create steps on write if there are no formfields, as this + // can create duplicate first steps during publish of new records + if(!$force && !$firstField) { + return; + } - // Assign each field to the initial step. - foreach ($this->owner->Fields()->exclude('ID', $step->ID) as $field) { - $field->StepID = $step->ID; + // Re-apply sort to each field starting at 2 + $next = 2; + foreach($fields as $field) { + $field->Sort = $next++; $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; } + /** + * Ensure that at least one page exists at the start + */ + public function onAfterWrite() { + $this->createInitialFormStep(); + } + /** * @see SiteTree::doPublish * @param Page $original diff --git a/code/forms/UserForm.php b/code/forms/UserForm.php index 7787f12..8cf5644 100644 --- a/code/forms/UserForm.php +++ b/code/forms/UserForm.php @@ -65,82 +65,42 @@ class UserForm extends Form { 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 * by using {@link updateFormFields()} on an {@link Extension} subclass which * is applied to this controller. * + * This will be a list of top level composite steps + * * @return FieldList */ public function getFormFields() { $fields = new FieldList(); + $emptyStep = null; // Last empty step, which may or may not later have children - foreach($this->controller->Fields() as $editableField) { - // get the raw form field from the editable version - $field = $editableField->getFormField(); - - if(!$field) 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)); + foreach ($this->controller->Fields() as $field) { + // When we encounter a step, save it + if ($field instanceof EditableFormStep) { + $emptyStep = $field->getFormField(); + continue; } - // if this field is required add some - if($editableField->Required) { - $field->addExtraClass('requiredField'); - - if($identifier = UserDefinedForm::config()->required_identifier) { - - $title = $field->Title() ." ". $identifier . ""; - $field->setTitle($title); - } - } - // if this field has an extra class - if($extraClass = $editableField->ExtraClass) { - $field->addExtraClass(Convert::raw2att($extraClass)); + // Ensure that the last field is a step + if($emptyStep) { + // When we reach the first non-step field, any empty step will no longer be empty + $fields->push($emptyStep); + $emptyStep = null; + + } elseif(! $fields->last()) { + // If no steps have been saved yet, warn + trigger_error('Missing first step in form', E_USER_WARNING); + $fields->push(singleton('EditableFormStep')->getFormField()); } - // set the values passed by the url to the field - $request = $this->controller->getRequest(); - if($value = $request->getVar($field->getName())) { - $field->setValue($value); - } - - $fields->push($field); + $fields->last()->push($field->getFormField()); } $this->extend('updateFormFields', $fields); - return $fields; } diff --git a/code/forms/gridfield/GridFieldAddItemInlineButton.php b/code/forms/gridfield/GridFieldAddItemInlineButton.php new file mode 100644 index 0000000..b3b196a --- /dev/null +++ b/code/forms/gridfield/GridFieldAddItemInlineButton.php @@ -0,0 +1,246 @@ +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(); + } +} diff --git a/code/model/UserDefinedForm.php b/code/model/UserDefinedForm.php index 9e3e2d1..69e0feb 100755 --- a/code/model/UserDefinedForm.php +++ b/code/model/UserDefinedForm.php @@ -98,6 +98,7 @@ class UserDefinedForm extends Page { * @return FieldList */ public function getCMSFields() { + Requirements::css(USERFORMS_DIR . '/css/UserForm_cms.css'); $self = $this; @@ -365,9 +366,7 @@ class UserDefinedForm_Controller extends Page_Controller { */ public function Form() { $form = UserForm::create($this); - $this->generateConditionalJavascript(); - return $form; } diff --git a/code/model/editableformfields/EditableCheckboxGroupField.php b/code/model/editableformfields/EditableCheckboxGroupField.php index 8cdd8f2..17ea2fa 100755 --- a/code/model/editableformfields/EditableCheckboxGroupField.php +++ b/code/model/editableformfields/EditableCheckboxGroupField.php @@ -14,17 +14,15 @@ class EditableCheckboxGroupField extends EditableMultipleOptionField { private static $plural_name = "Checkbox Groups"; public function getFormField() { - $optionSet = $this->Options(); - $optionMap = $optionSet->map('EscapedTitle', 'Title'); - - $field = new UserFormsCheckboxSetField($this->Name, $this->Title, $optionMap); + $field = new UserFormsCheckboxSetField($this->Name, $this->EscapedTitle, $this->getOptionsMap()); // Set the default checked items - $defaultCheckedItems = $optionSet->filter('Default', 1); + $defaultCheckedItems = $this->getDefaultOptions(); if ($defaultCheckedItems->count()) { $field->setDefaultItems($defaultCheckedItems->map('EscapedTitle')->keys()); } + $this->doUpdateFormField($field); return $field; } diff --git a/code/model/editableformfields/EditableCountryDropdownField.php b/code/model/editableformfields/EditableCountryDropdownField.php index 4529e54..2485658 100644 --- a/code/model/editableformfields/EditableCountryDropdownField.php +++ b/code/model/editableformfields/EditableCountryDropdownField.php @@ -23,7 +23,9 @@ class EditableCountryDropdownField extends EditableFormField { } 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) { diff --git a/code/model/editableformfields/EditableDateField.php b/code/model/editableformfields/EditableDateField.php index 7146737..c16ab87 100755 --- a/code/model/editableformfields/EditableDateField.php +++ b/code/model/editableformfields/EditableDateField.php @@ -43,17 +43,9 @@ class EditableDateField extends EditableFormField { $defaultValue = $this->DefaultToToday ? SS_Datetime::now()->Format('Y-m-d') : $this->Default; - $field = EditableDateField_FormField::create( $this->Name, $this->Title, $defaultValue); + $field = EditableDateField_FormField::create( $this->Name, $this->EscapedTitle, $defaultValue); $field->setConfig('showcalendar', true); - - 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); - } - + $this->doUpdateFormField($field); return $field; } } diff --git a/code/model/editableformfields/EditableDropdown.php b/code/model/editableformfields/EditableDropdown.php index f9f6866..a0aa534 100755 --- a/code/model/editableformfields/EditableDropdown.php +++ b/code/model/editableformfields/EditableDropdown.php @@ -28,30 +28,14 @@ class EditableDropdown extends EditableMultipleOptionField { * @return DropdownField */ public function getFormField() { - $optionSet = $this->Options(); - $defaultOptions = $optionSet->filter('Default', 1); - $options = array(); + $field = DropdownField::create($this->Name, $this->EscapedTitle, $this->getOptionsMap()); - if($optionSet) { - foreach($optionSet as $option) { - $options[$option->Title] = $option->Title; - } + // Set default + $defaultOption = $this->getDefaultOptions()->first(); + if($defaultOption) { + $field->setValue($defaultOption->EscapedTitle); } - - $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); - } - + $this->doUpdateFormField($field); return $field; } } \ No newline at end of file diff --git a/code/model/editableformfields/EditableEmailField.php b/code/model/editableformfields/EditableEmailField.php index 7da9eb2..732828b 100755 --- a/code/model/editableformfields/EditableEmailField.php +++ b/code/model/editableformfields/EditableEmailField.php @@ -18,19 +18,8 @@ class EditableEmailField extends EditableFormField { } public function getFormField() { - - $field = EmailField::create($this->Name, $this->Title); - - 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); - + $field = EmailField::create($this->Name, $this->EscapedTitle, $this->Default); + $this->doUpdateFormField($field); return $field; } diff --git a/code/model/editableformfields/EditableFileField.php b/code/model/editableformfields/EditableFileField.php index 4489577..5cff5a2 100755 --- a/code/model/editableformfields/EditableFileField.php +++ b/code/model/editableformfields/EditableFileField.php @@ -35,7 +35,7 @@ class EditableFileField extends EditableFormField { } 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 $field->getValidator()->setAllowedExtensions( @@ -49,13 +49,7 @@ class EditableFileField extends EditableFormField { ); } - 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); - } + $this->doUpdateFormField($field); return $field; } diff --git a/code/model/editableformfields/EditableFormField.php b/code/model/editableformfields/EditableFormField.php index 26997af..429e0db 100755 --- a/code/model/editableformfields/EditableFormField.php +++ b/code/model/editableformfields/EditableFormField.php @@ -433,31 +433,13 @@ class EditableFormField extends DataObject { 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) * * @return string */ - public function getTitle() { - return Convert::raw2att($this->getField('Title')); + public function getEscapedTitle() { + return Convert::raw2xml($this->Title); } /** @@ -497,13 +479,59 @@ class EditableFormField extends DataObject { /** * Return a FormField to appear on the front end. Implement on - * your subclass + * your subclass. * * @return FormField */ public function getFormField() { 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() . " ". $identifier . ""; + $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 diff --git a/code/model/editableformfields/EditableFormHeading.php b/code/model/editableformfields/EditableFormHeading.php index f5c6234..90c84ef 100755 --- a/code/model/editableformfields/EditableFormHeading.php +++ b/code/model/editableformfields/EditableFormHeading.php @@ -55,10 +55,23 @@ class EditableFormHeading extends EditableFormField { } public function getFormField() { - $labelField = new HeaderField($this->Name, $this->Title, $this->Level); + $labelField = new HeaderField($this->Name, $this->EscapedTitle, $this->Level); $labelField->addExtraClass('FormHeading'); + $this->doUpdateFormField($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() { return !$this->HideFromReports; diff --git a/code/model/editableformfields/EditableFormStep.php b/code/model/editableformfields/EditableFormStep.php index 242422e..a2f6f2b 100644 --- a/code/model/editableformfields/EditableFormStep.php +++ b/code/model/editableformfields/EditableFormStep.php @@ -25,7 +25,6 @@ class EditableFormStep extends EditableFormField { $fields = parent::getCMSFields(); $fields->removeByName('MergeField'); - $fields->removeByName('StepID'); $fields->removeByName('Default'); $fields->removeByName('Validation'); $fields->removeByName('CustomRules'); @@ -37,7 +36,17 @@ class EditableFormStep extends EditableFormField { * @return FormField */ 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); + } } /** diff --git a/code/model/editableformfields/EditableLiteralField.php b/code/model/editableformfields/EditableLiteralField.php index 4625038..eaba903 100644 --- a/code/model/editableformfields/EditableLiteralField.php +++ b/code/model/editableformfields/EditableLiteralField.php @@ -103,12 +103,16 @@ class EditableLiteralField extends EditableFormField { } public function getFormField() { - $label = $this->Title - ? "" - : ""; - $classes = $this->Title ? "" : " nolabel"; + // Build label and css classes + $label = ''; + $classes = $this->ExtraClass; + if(empty($this->Title)) { + $classes .= " nolabel"; + } else { + $label = ""; + } - return new LiteralField( + $field = new LiteralField( "LiteralField[{$this->ID}]", sprintf( "
@@ -121,6 +125,9 @@ class EditableLiteralField extends EditableFormField { $this->Content ) ); + + // When dealing with literal fields there is no further customisation that can be added at this point + return $field; } public function showInReports() { diff --git a/code/model/editableformfields/EditableMemberListField.php b/code/model/editableformfields/EditableMemberListField.php index 44e0a7e..c2ead2c 100644 --- a/code/model/editableformfields/EditableMemberListField.php +++ b/code/model/editableformfields/EditableMemberListField.php @@ -42,7 +42,9 @@ class EditableMemberListField extends EditableFormField { } $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) { diff --git a/code/model/editableformfields/EditableMultipleOptionField.php b/code/model/editableformfields/EditableMultipleOptionField.php index bb71832..31b4b58 100644 --- a/code/model/editableformfields/EditableMultipleOptionField.php +++ b/code/model/editableformfields/EditableMultipleOptionField.php @@ -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() { - return user_error('Please implement getFormField() on '. $this->class, E_USER_ERROR); + protected function getOptionsMap() { + $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); } } diff --git a/code/model/editableformfields/EditableNumericField.php b/code/model/editableformfields/EditableNumericField.php index ca5d827..0176dfc 100755 --- a/code/model/editableformfields/EditableNumericField.php +++ b/code/model/editableformfields/EditableNumericField.php @@ -26,18 +26,9 @@ class EditableNumericField extends EditableFormField { * @return NumericField */ public function getFormField() { - $field = new NumericField($this->Name, $this->Title); + $field = new NumericField($this->Name, $this->EscapedTitle, $this->Default); $field->addExtraClass('number'); - $field->setValue($this->Default); - - 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); - } - + $this->doUpdateFormField($field); return $field; } diff --git a/code/model/editableformfields/EditableOption.php b/code/model/editableformfields/EditableOption.php index 7f53507..f2181c5 100644 --- a/code/model/editableformfields/EditableOption.php +++ b/code/model/editableformfields/EditableOption.php @@ -49,39 +49,6 @@ class EditableOption extends DataObject { 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() { return Convert::raw2att($this->Title); } diff --git a/code/model/editableformfields/EditableRadioField.php b/code/model/editableformfields/EditableRadioField.php index f48eeb8..3bce18b 100755 --- a/code/model/editableformfields/EditableRadioField.php +++ b/code/model/editableformfields/EditableRadioField.php @@ -25,22 +25,14 @@ class EditableRadioField extends EditableMultipleOptionField { } public function getFormField() { - $optionSet = $this->Options(); - $defaultOptions = $optionSet->filter('Default', 1); - $options = array(); + $field = OptionsetField::create($this->Name, $this->EscapedTitle, $this->getOptionsMap()); - if($optionSet) { - foreach($optionSet as $option) { - $options[$option->EscapedTitle] = $option->Title; - } + // Set default item + $defaultOption = $this->getDefaultOptions()->first(); + if($defaultOption) { + $field->setValue($defaultOption->EscapedTitle); } - - $field = OptionsetField::create($this->Name, $this->Title, $options); - - if($defaultOptions->count()) { - $field->setValue($defaultOptions->First()->EscapedTitle); - } - + $this->doUpdateFormField($field); return $field; } } diff --git a/code/model/editableformfields/EditableTextField.php b/code/model/editableformfields/EditableTextField.php index ba5399d..56c819d 100755 --- a/code/model/editableformfields/EditableTextField.php +++ b/code/model/editableformfields/EditableTextField.php @@ -65,22 +65,12 @@ class EditableTextField extends EditableFormField { */ public function getFormField() { if($this->Rows > 1) { - $field = TextareaField::create($this->Name, $this->Title); + $field = TextareaField::create($this->Name, $this->EscapedTitle, $this->Default); $field->setRows($this->Rows); } else { - $field = TextField::create($this->Name, $this->Title, null, $this->MaxLength); + $field = TextField::create($this->Name, $this->EscapedTitle, $this->Default, $this->MaxLength); } - - 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); - + $this->doUpdateFormField($field); return $field; } diff --git a/code/model/formfields/EditableCheckbox.php b/code/model/formfields/EditableCheckbox.php index 9527eda..e1e0bbc 100755 --- a/code/model/formfields/EditableCheckbox.php +++ b/code/model/formfields/EditableCheckbox.php @@ -32,16 +32,8 @@ class EditableCheckbox extends EditableFormField { } public function getFormField() { - $field = CheckboxField::create($this->Name, $this->Title, $this->CheckedDefault); - - 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 = CheckboxField::create($this->Name, $this->EscapedTitle, $this->CheckedDefault); + $this->doUpdateFormField($field); return $field; } diff --git a/css/UserForm_cms.css b/css/UserForm_cms.css new file mode 100644 index 0000000..7953279 --- /dev/null +++ b/css/UserForm_cms.css @@ -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; +} \ No newline at end of file diff --git a/javascript/GridFieldAddItemInlineButton.js b/javascript/GridFieldAddItemInlineButton.js new file mode 100644 index 0000000..a3df003 --- /dev/null +++ b/javascript/GridFieldAddItemInlineButton.js @@ -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); diff --git a/templates/UserForm.ss b/templates/UserForm.ss index 8aa1154..6b6bf3d 100644 --- a/templates/UserForm.ss +++ b/templates/UserForm.ss @@ -11,26 +11,26 @@
<% if $Legend %>$Legend<% end_if %> - <% loop $FormSteps %> -
- <% if $Top.DisplayErrorMessagesAtTop %> - - <% end_loop %> + <% end_loop %><% end_if %>
diff --git a/templates/gridfield/GridFieldAddItemInlineButton.ss b/templates/gridfield/GridFieldAddItemInlineButton.ss new file mode 100644 index 0000000..889482f --- /dev/null +++ b/templates/gridfield/GridFieldAddItemInlineButton.ss @@ -0,0 +1,3 @@ + diff --git a/templates/gridfield/GridFieldAddItemInlineButton_Row.ss b/templates/gridfield/GridFieldAddItemInlineButton_Row.ss new file mode 100644 index 0000000..fd345ac --- /dev/null +++ b/templates/gridfield/GridFieldAddItemInlineButton_Row.ss @@ -0,0 +1,13 @@ + diff --git a/tests/EditableFormFieldTest.php b/tests/EditableFormFieldTest.php index fe320c2..5b77e9b 100644 --- a/tests/EditableFormFieldTest.php +++ b/tests/EditableFormFieldTest.php @@ -80,27 +80,6 @@ class EditableFormFieldTest extends FunctionalTest { $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() { $dropdown = $this->objFromFixture('EditableDropdown','basic-dropdown'); diff --git a/tests/UserDefinedFormControllerTest.php b/tests/UserDefinedFormControllerTest.php index a56385e..02bdc51 100644 --- a/tests/UserDefinedFormControllerTest.php +++ b/tests/UserDefinedFormControllerTest.php @@ -6,7 +6,7 @@ class UserDefinedFormControllerTest extends FunctionalTest { - static $fixture_file = 'userforms/tests/UserDefinedFormTest.yml'; + static $fixture_file = 'UserDefinedFormTest.yml'; function testProcess() { $form = $this->setupFormFrontend(); @@ -83,7 +83,7 @@ class UserDefinedFormControllerTest extends FunctionalTest { function testForm() { $form = $this->objFromFixture('UserDefinedForm', 'basic-form-page'); - + $controller = new UserDefinedFormControllerTest_Controller($form); // test form @@ -106,30 +106,35 @@ class UserDefinedFormControllerTest extends FunctionalTest { $controller = new UserDefinedFormControllerTest_Controller($form); - $fields = $controller->Form()->getFormFields(); - - $this->assertEquals($fields->Count(), 1); + $formSteps = $controller->Form()->getFormFields(); + $firstStep = $formSteps->first(); + + $this->assertEquals($formSteps->Count(), 1); + $this->assertEquals($firstStep->getChildren()->Count(), 1); // custom error message on a form field $requiredForm = $this->objFromFixture('UserDefinedForm', 'validation-form'); $controller = new UserDefinedFormControllerTest_Controller($requiredForm); UserDefinedForm::config()->required_identifier = "*"; - - $fields = $controller->Form()->getFormFields(); - - $this->assertEquals($fields->First()->getCustomValidationMessage()->getValue(), 'Custom Error Message'); - $this->assertEquals($fields->First()->Title(), 'Required Text Field *'); + + $formSteps = $controller->Form()->getFormFields(); + $firstStep = $formSteps->first(); + $firstField = $firstStep->getChildren()->first(); + + $this->assertEquals('Custom Error Message', $firstField->getCustomValidationMessage()->getValue()); + $this->assertEquals($firstField->Title(), 'Required Text Field *'); // test custom right title - $field = $form->Fields()->First(); + $field = $form->Fields()->limit(1, 1)->First(); $field->RightTitle = 'Right Title'; $field->write(); $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 $emptyForm = $this->objFromFixture('UserDefinedForm', 'empty-form'); diff --git a/tests/UserDefinedFormTest.php b/tests/UserDefinedFormTest.php index fb7c8fc..4123f09 100644 --- a/tests/UserDefinedFormTest.php +++ b/tests/UserDefinedFormTest.php @@ -3,13 +3,11 @@ /** * @package userforms */ - class UserDefinedFormTest extends FunctionalTest { static $fixture_file = 'UserDefinedFormTest.yml'; - - function testRollbackToVersion() { + public function testRollbackToVersion() { $this->markTestSkipped( 'UserDefinedForm::rollback() has not been implemented completely' ); @@ -37,7 +35,7 @@ class UserDefinedFormTest extends FunctionalTest { $this->assertEquals($orignal->SubmitButtonText, 'Button Text'); } - function testGetCMSFields() { + public function testGetCMSFields() { $this->logInWithPermission('ADMIN'); $form = $this->objFromFixture('UserDefinedForm', 'basic-form-page'); @@ -49,7 +47,7 @@ class UserDefinedFormTest extends FunctionalTest { $this->assertTrue($fields->dataFieldByName('OnCompleteMessage') != null); } - function testEmailRecipientPopup() { + public function testEmailRecipientPopup() { $this->logInWithPermission('ADMIN'); $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"); $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'); $form->Fields()->add($dropdown); $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 $liveDropdown = Versioned::get_one_by_stage("EditableFormField", "Live", "\"EditableFormField_Live\".\"ID\" = $dropdown->ID"); @@ -176,11 +174,10 @@ class UserDefinedFormTest extends FunctionalTest { $form->doPublish(); $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 - $text = $form->Fields()->First(); - + $text = $form->Fields()->limit(1, 1)->First(); $text->Title = 'Edited title'; $text->write(); @@ -197,21 +194,20 @@ class UserDefinedFormTest extends FunctionalTest { $this->logInWithPermission('ADMIN'); $form = $this->objFromFixture('UserDefinedForm', 'basic-form-page'); $form->write(); - + $this->assertEquals(0, DB::query("SELECT COUNT(*) FROM \"EditableFormField_Live\"")->value()); $form->doPublish(); // assert that it exists and has a field $live = Versioned::get_one_by_stage("UserDefinedForm", "Live", "\"UserDefinedForm_Live\".\"ID\" = $form->ID"); $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 $form->doUnpublish(); $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() { diff --git a/tests/UserDefinedFormTest.yml b/tests/UserDefinedFormTest.yml index 07d47c9..a8fba3c 100644 --- a/tests/UserDefinedFormTest.yml +++ b/tests/UserDefinedFormTest.yml @@ -1,3 +1,13 @@ +EditableFormStep: + form1step1: + Title: 'Step 1' + form3step1: + Title: 'Step 1' + form4step1: + Title: 'Step 1' + form5step1: + Title: 'Step 1' + EditableOption: option-1: Name: Option1 @@ -183,7 +193,7 @@ UserDefinedForm_EmailRecipient: UserDefinedForm: basic-form-page: 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 form-with-reset-and-custom-action: @@ -193,15 +203,15 @@ UserDefinedForm: validation-form: Title: Validation Form - Fields: =>EditableTextField.required-text + Fields: =>EditableFormStep.form3step1,=>EditableTextField.required-text 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: Title: Empty Form filtered-form-page: 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