diff --git a/.gitignore b/.gitignore index e43b0f9..92b0407 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .DS_Store +.sass-cache/ diff --git a/code/extensions/UserFormFieldEditorExtension.php b/code/extensions/UserFormFieldEditorExtension.php index 7526216..29e342b 100644 --- a/code/extensions/UserFormFieldEditorExtension.php +++ b/code/extensions/UserFormFieldEditorExtension.php @@ -37,17 +37,17 @@ class UserFormFieldEditorExtension extends DataExtension { $this->createInitialFormStep(true); $editableColumns = new GridFieldEditableColumns(); + $fieldClasses = $this->getEditableFieldClasses(); $editableColumns->setDisplayFields(array( - 'ClassName' => function($record, $column, $grid) { - if($record instanceof EditableFormStep) { - return new LabelField($column, "Page Break"); - } else { - return DropdownField::create($column, '', $this->getEditableFieldClasses()); + 'ClassName' => function($record, $column, $grid) use ($fieldClasses) { + if($record instanceof EditableFormField) { + return $record->getInlineClassnameField($column, $fieldClasses); } }, 'Title' => function($record, $column, $grid) { - return TextField::create($column, ' ') - ->setAttribute('placeholder', _t('UserDefinedForm.TITLE', 'Title')); + if($record instanceof EditableFormField) { + return $record->getInlineTitleField($column); + } } )); @@ -55,8 +55,13 @@ class UserFormFieldEditorExtension extends DataExtension { ->addComponents( $editableColumns, new GridFieldButtonRow(), - $addField = new GridFieldAddNewInlineButton(), - $addStep = new GridFieldAddItemInlineButton('EditableFormStep'), + GridFieldAddClassesButton::create('EditableFormField') + ->setButtonName(_t('UserFormFieldEditorExtension.ADD_FIELD', 'Add Field')) + ->setButtonClass('ss-ui-action-constructive'), + GridFieldAddClassesButton::create('EditableFormStep') + ->setButtonName(_t('UserFormFieldEditorExtension.ADD_PAGE_BREAK', 'Add Page Break')), + GridFieldAddClassesButton::create(array('EditableFieldGroup', 'EditableFieldGroupEnd')) + ->setButtonName(_t('UserFormFieldEditorExtension.ADD_FIELD_GROUP', 'Add Field Group')), new GridFieldEditButton(), new GridFieldDeleteAction(), new GridFieldToolbarHeader(), @@ -64,9 +69,6 @@ class UserFormFieldEditorExtension extends DataExtension { new GridState_Component(), new GridFieldDetailForm() ); - $addField->setTitle('Add Field'); - $addStep->setTitle('Add Page Break'); - $addStep->setExtraClass('uf-gridfield-steprow'); $fieldEditor = GridField::create( 'Fields', @@ -113,7 +115,7 @@ class UserFormFieldEditorExtension extends DataExtension { // Add step $step = EditableFormStep::create(); - $step->Title = _t('EditableFormStep.TITLE_FIRST', 'First Step'); + $step->Title = _t('EditableFormStep.TITLE_FIRST', 'First Page'); $step->Sort = 1; $step->write(); $fields->add($step); @@ -126,21 +128,20 @@ class UserFormFieldEditorExtension extends DataExtension { $classes = ClassInfo::getValidSubClasses('EditableFormField'); // Remove classes we don't want to display in the dropdown. - $classes = array_diff($classes, array( - 'EditableFormField', - 'EditableMultipleOptionField' - )); - $editableFieldClasses = array(); - - foreach ($classes as $key => $className) { - $singleton = singleton($className); - + 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[$className] = $singleton->i18n_singular_name(); + $editableFieldClasses[$class] = $singleton->i18n_singular_name(); } return $editableFieldClasses; diff --git a/code/extensions/UserFormValidator.php b/code/extensions/UserFormValidator.php new file mode 100644 index 0000000..90fb3ff --- /dev/null +++ b/code/extensions/UserFormValidator.php @@ -0,0 +1,108 @@ +filter('ParentID', $data['ID'])->sort('"Sort" ASC'); + + // Current nesting + $stack = array(); + foreach($fields as $field) { + if($field instanceof EditableFormStep) { + // Page at top level, or after another page is ok + if(empty($stack) || (count($stack) === 1 && $stack[0] instanceof EditableFormStep)) { + $stack = array($field); + continue; + } + + $this->validationError( + 'FormFields', + _t( + "UserFormValidator.UNEXPECTED_BREAK", + "Unexpected page break '{name}' inside nested field '{group}'", + array( + 'name' => $field->CMSTitle, + 'group' => end($stack)->CMSTitle + ) + ), + 'error' + ); + return false; + } + + // Validate no pages + if(empty($stack)) { + $this->validationError( + 'FormFields', + _t( + "UserFormValidator.NO_PAGE", + "Field '{name}' found before any pages", + array( + 'name' => $field->CMSTitle + ) + ), + 'error' + ); + return false; + } + + // Nest field group + if($field instanceof EditableFieldGroup) { + $stack[] = $field; + continue; + } + + // Unnest field group + if($field instanceof EditableFieldGroupEnd) { + $top = end($stack); + + // Check that the top is a group at all + if(!$top instanceof EditableFieldGroup) { + $this->validationError( + 'FormFields', + _t( + "UserFormValidator.UNEXPECTED_GROUP_END", + "'{name}' found without a matching group", + array( + 'name' => $field->CMSTitle + ) + ), + 'error' + ); + return false; + } + + // Check that the top is the right group + if($top->EndID != $field->ID) { + $this->validationError( + 'FormFields', + _t( + "UserFormValidator.WRONG_GROUP_END", + "'{name}' found closes the wrong group '{group}'", + array( + 'name' => $field->CMSTitle, + 'group' => $top->CMSTitle + ) + ), + 'error' + ); + return false; + } + + // Unnest group + array_pop($stack); + } + } + + return true; + } +} diff --git a/code/formfields/UserFormsCompositeField.php b/code/formfields/UserFormsCompositeField.php new file mode 100644 index 0000000..5188b54 --- /dev/null +++ b/code/formfields/UserFormsCompositeField.php @@ -0,0 +1,47 @@ +parent; + } + + public function setParent(UserFormsFieldContainer $parent) { + $this->parent = $parent; + return $this; + } + + public function processNext(EditableFormField $field) { + // When we find a step, bubble up to the top + if($field instanceof EditableFormStep) { + return $this->getParent()->processNext($field); + } + + // Skip over fields that don't generate formfields + $formField = $field->getFormField(); + if(!$formField) { + return $this; + } + + // Save this field + $this->push($formField); + + // Nest fields that are containers + if($formField instanceof UserFormsFieldContainer) { + return $formField->setParent($this); + } + + // Add any subsequent fields to this + return $this; + } +} diff --git a/code/formfields/UserFormsFieldContainer.php b/code/formfields/UserFormsFieldContainer.php new file mode 100644 index 0000000..b43f7b0 --- /dev/null +++ b/code/formfields/UserFormsFieldContainer.php @@ -0,0 +1,30 @@ +getFormField(); + if(!$formField) { + return $this; + } + + $this->push($formField); + + if($formField instanceof UserFormsFieldContainer) { + return $formField->setParent($this); + } + + return $this; + } + + public function getParent() { + // Field list does not have a parent + return null; + } + + public function setParent(UserFormsFieldContainer $parent) { + return $this; + } + +} diff --git a/code/formfields/UserFormsGroupField.php b/code/formfields/UserFormsGroupField.php new file mode 100644 index 0000000..8860766 --- /dev/null +++ b/code/formfields/UserFormsGroupField.php @@ -0,0 +1,27 @@ +setTag('fieldset'); + } + + public function getLegend() { + // Legend defaults to title + return parent::getLegend() ?: $this->Title(); + } + + public function processNext(EditableFormField $field) { + // When ending a group, jump up one level + if($field instanceof EditableFieldGroupEnd) { + return $this->getParent(); + } + + // Otherwise behave as per normal composite field + return parent::processNext($field); + } +} diff --git a/code/formfields/UserFormsStepField.php b/code/formfields/UserFormsStepField.php new file mode 100644 index 0000000..efcf563 --- /dev/null +++ b/code/formfields/UserFormsStepField.php @@ -0,0 +1,7 @@ +setClasses($classes); + $this->setFragment($targetFragment); + } + + /** + * Change the button name + * + * @param string $name + * @return $this + */ + public function setButtonName($name) { + $this->buttonName = $name; + return $this; + } + + /** + * Get the button name + * + * @return string + */ + public function getButtonName() { + return $this->buttonName; + } + + /** + * Gets the fragment name this button is rendered into. + * + * @return string + */ + public function getFragment() { + return $this->targetFragment; + } + + /** + * Sets the fragment name this button is rendered into. + * + * @param string $fragment + * @return GridFieldAddNewInlineButton $this + */ + public function setFragment($fragment) { + $this->targetFragment = $fragment; + return $this; + } + + /** + * Get extra button class + * + * @return string + */ + public function getButtonClass() { + return $this->buttonClass; + } + + /** + * Sets extra CSS classes for this button + * + * @param string $buttonClass + * @return $this + */ + public function setButtonClass($buttonClass) { + $this->buttonClass = $buttonClass; + return $this; + } + + + /** + * Get the classes of the objects to create + * + * @return array + */ + public function getClasses() { + return $this->modelClasses; + } + + /** + * Gets the list of classes which can be created, with checks for permissions. + * Will fallback to the default model class for the given DataGrid + * + * @param DataGrid $grid + * @return array + */ + public function getClassesCreate($grid) { + // Get explicit or fallback class list + $classes = $this->modelClasses; + if(empty($classes) && $grid) { + $classes = array($grid->getModelClass()); + } + + // Filter out classes without permission + return array_filter($classes, function($class) { + return singleton($class)->canCreate(); + }); + } + + /** + * Specify the classes to create + * + * @param array $classes + */ + public function setClasses($classes) { + if(!is_array($classes)) { + $classes = $classes ? array($classes) : array(); + } + $this->modelClasses = $classes; + } + + public function getHTMLFragments($grid) { + // Check create permission + $singleton = singleton($grid->getModelClass()); + if(!$singleton->canCreate()) { + return array(); + } + + // Get button name + $buttonName = $this->getButtonName(); + if(!$buttonName) { + // provide a default button name, can be changed by calling {@link setButtonName()} on this component + $objectName = $singleton->i18n_singular_name(); + $buttonName = _t('GridField.Add', 'Add {name}', array('name' => $objectName)); + } + + $data = new ArrayData(array( + 'Title' => $this->getButtonName(), + 'ButtonClass' => $this->getButtonClass(), + 'Link' => Controller::join_links($grid->Link(), $this->getAction()) + )); + + return array( + $this->getFragment() => $data->renderWith(__CLASS__) + ); + } + + /** + * Get the action suburl for this component + * + * @return type + */ + protected function getAction() { + $classes = implode('-', $this->getClasses()); + return Controller::join_links('add-classes', $classes); + } + + /** + * {@inheritDoc} + */ + public function getURLHandlers($grid) { + return array( + $this->getAction() => 'handleAdd' + ); + } + + /** + * Handles adding a new instance of a selected class. + * + * @param GridField $grid + * @param SS_HTTPRequest $request + */ + public function handleAdd($grid, $request) { + $classes = $this->getClassesCreate($grid); + + if(empty($classes)) { + throw new SS_HTTPResponse_Exception(400); + } + + // Add item to gridfield + $list = $grid->getList(); + foreach($classes as $class) { + $item = $class::create(); + $item->write(); + $list->add($item); + } + + // Return directly to the gridfield again + return $grid + ->getForm() + ->getController() + ->redirectBack(); + } +} diff --git a/code/forms/UserForm.php b/code/forms/UserForm.php index 556e370..cf16333 100644 --- a/code/forms/UserForm.php +++ b/code/forms/UserForm.php @@ -75,31 +75,11 @@ class UserForm extends Form { * @return FieldList */ public function getFormFields() { - $fields = new FieldList(); - $emptyStep = null; // Last empty step, which may or may not later have children - + $fields = new UserFormsFieldList(); + $target = $fields; foreach ($this->controller->Fields() as $field) { - // When we encounter a step, save it - if ($field instanceof EditableFormStep) { - $emptyStep = $field->getFormField(); - continue; - } - - // 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()); - } - - $fields->last()->push($field->getFormField()); + $target = $target->processNext($field); } - $this->extend('updateFormFields', $fields); return $fields; } diff --git a/code/forms/gridfield/GridFieldAddItemInlineButton.php b/code/forms/gridfield/GridFieldAddItemInlineButton.php deleted file mode 100644 index b3b196a..0000000 --- a/code/forms/gridfield/GridFieldAddItemInlineButton.php +++ /dev/null @@ -1,246 +0,0 @@ -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 69e0feb..5b5f215 100755 --- a/code/model/UserDefinedForm.php +++ b/code/model/UserDefinedForm.php @@ -177,6 +177,8 @@ SQL; $config->addComponent(new GridFieldDetailForm()); $config->addComponent($export = new GridFieldExportButton()); $config->addComponent($print = new GridFieldPrintButton()); + + Requirements::javascript(USERFORMS_DIR . '/javascript/Gridfield.js'); /** * Support for {@link https://github.com/colymba/GridFieldBulkEditingTools} @@ -284,6 +286,14 @@ SQL; DB::alteration_message('Migrated userforms', 'changed'); } + + + /** + * Validate formfields + */ + public function getCMSValidator() { + return new UserFormValidator(); + } } /** diff --git a/code/model/editableformfields/EditableFieldGroup.php b/code/model/editableformfields/EditableFieldGroup.php new file mode 100644 index 0000000..24c0bfe --- /dev/null +++ b/code/model/editableformfields/EditableFieldGroup.php @@ -0,0 +1,80 @@ + 'EditableFieldGroupEnd' + ); + + /** + * Disable selection of group class + * + * @config + * @var bool + */ + private static $hidden = true; + + public function getCMSFields() { + $fields = parent::getCMSFields(); + $fields->removeByName(array('MergeField', 'Default', 'Validation', 'DisplayRules')); + return $fields; + } + + public function getCMSTitle() { + return _t( + 'EditableFieldGroupEnd.FIELD_GROUP_START', + 'Start of {group}', + array( + 'group' => $this->Title ?: 'group' + ) + ); + } + + public function getInlineClassnameField($column, $fieldClasses) { + return new LabelField($column, $this->CMSTitle); + } + + public function showInReports() { + return false; + } + + public function getFormField() { + $field = UserFormsGroupField::create() + ->setTitle($this->EscapedTitle ?: false); + $this->doUpdateFormField($field); + return $field; + } + + 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); + } + } + + protected function onBeforeDelete() { + parent::onBeforeDelete(); + + // Ensures EndID is lazy-loaded for onAfterDelete + $this->EndID; + } + + protected function onAfterDelete() { + parent::onAfterDelete(); + + // Delete end + if(($end = $this->End()) && $end->exists()) { + $end->delete(); + } + } + +} diff --git a/code/model/editableformfields/EditableFieldGroupEnd.php b/code/model/editableformfields/EditableFieldGroupEnd.php new file mode 100644 index 0000000..e1a6e6f --- /dev/null +++ b/code/model/editableformfields/EditableFieldGroupEnd.php @@ -0,0 +1,86 @@ + 'EditableFieldGroup' + ); + + /** + * Disable selection of group class + * + * @config + * @var bool + */ + private static $hidden = true; + + public function getCMSTitle() { + $group = $this->Group(); + return _t( + 'EditableFieldGroupEnd.FIELD_GROUP_END', + 'End of {group}', + array( + 'group' => ($group && $group->exists() && $group->Title) ? $group->Title : 'group' + ) + ); + } + + public function getCMSFields() { + $fields = parent::getCMSFields(); + $fields->removeByName(array('MergeField', 'Default', 'Validation', 'DisplayRules')); + return $fields; + } + + public function getInlineClassnameField($column, $fieldClasses) { + return new LabelField($column, $this->CMSTitle); + } + + public function getInlineTitleField($column) { + return HiddenField::create($column); + } + + public function getFormField() { + return null; + } + + public function showInReports() { + return false; + } + + public function onAfterWrite() { + parent::onAfterWrite(); + + // If this is not attached to a group, find the first group prior to this + // with no end attached + $group = $this->Group(); + if(!($group && $group->exists()) && $this->ParentID) { + $group = EditableFieldGroup::get() + ->filter(array( + 'ParentID' => $this->ParentID, + 'Sort:LessThanOrEqual' => $this->Sort + )) + ->where('"EditableFieldGroup"."EndID" IS NULL OR "EditableFieldGroup"."EndID" = 0') + ->sort('"Sort" DESC') + ->first(); + + // When a group is found, attach it to this end + if($group) { + $group->EndID = $this->ID; + $group->write(); + } + } + } + + protected function onAfterDelete() { + parent::onAfterDelete(); + + // Delete group + if(($group = $this->Group()) && $group->exists()) { + $group->delete(); + } + } + +} diff --git a/code/model/editableformfields/EditableFormField.php b/code/model/editableformfields/EditableFormField.php index 1eb0a7d..1dc0c2a 100755 --- a/code/model/editableformfields/EditableFormField.php +++ b/code/model/editableformfields/EditableFormField.php @@ -8,6 +8,14 @@ */ class EditableFormField extends DataObject { + /** + * Set to true to hide from class selector + * + * @config + * @var bool + */ + private static $hidden = false; + /** * Default sort order * @@ -442,6 +450,10 @@ class EditableFormField extends DataObject { return Convert::raw2xml($this->Title); } + public function getCMSTitle() { + return $this->i18n_singular_name() . ' (' . $this->Title . ')'; + } + /** * @deprecated since version 4.0 */ @@ -616,4 +628,27 @@ class EditableFormField extends DataObject { } } } + + /** + * Get the formfield to use when editing this inline in gridfield + * + * @param string $column name of column + * @param array $fieldClasses List of allowed classnames if this formfield has a selectable class + * @return FormField + */ + public function getInlineClassnameField($column, $fieldClasses) { + return DropdownField::create($column, false, $fieldClasses); + } + + /** + * Get the formfield to use when editing the title inline + * + * @param string $column + * @return FormField + */ + public function getInlineTitleField($column) { + return TextField::create($column, false) + ->setAttribute('placeholder', _t('EditableFormField.TITLE', 'Title')) + ->setAttribute('data-placeholder', _t('EditableFormField.TITLE', 'Title')); + } } diff --git a/code/model/editableformfields/EditableFormStep.php b/code/model/editableformfields/EditableFormStep.php index a2f6f2b..a89face 100644 --- a/code/model/editableformfields/EditableFormStep.php +++ b/code/model/editableformfields/EditableFormStep.php @@ -6,17 +6,17 @@ */ class EditableFormStep extends EditableFormField { - /** - * @config - * @var string - */ - private static $singular_name = 'Step'; + private static $singular_name = 'Page Break'; + + private static $plural_name = 'Page Breaks'; /** + * Disable selection of step class + * * @config - * @var string + * @var bool */ - private static $plural_name = 'Steps'; + private static $hidden = true; /** * @return FieldList @@ -24,10 +24,7 @@ class EditableFormStep extends EditableFormField { public function getCMSFields() { $fields = parent::getCMSFields(); - $fields->removeByName('MergeField'); - $fields->removeByName('Default'); - $fields->removeByName('Validation'); - $fields->removeByName('CustomRules'); + $fields->removeByName(array('MergeField', 'Default', 'Validation', 'DisplayRules')); return $fields; } @@ -36,7 +33,7 @@ class EditableFormStep extends EditableFormField { * @return FormField */ public function getFormField() { - $field = CompositeField::create() + $field = UserFormsStepField::create() ->setTitle($this->EscapedTitle); $this->doUpdateFormField($field); return $field; @@ -55,4 +52,19 @@ class EditableFormStep extends EditableFormField { public function showInReports() { return false; } + + public function getInlineClassnameField($column, $fieldClasses) { + return new LabelField( + $column, + $this->CMSTitle + ); + } + + public function getCMSTitle() { + $title = $this->i18n_singular_name(); + if($this->Title) { + $title .= ' (' . $this->Title . ')'; + } + return $title; + } } diff --git a/config.rb b/config.rb new file mode 100644 index 0000000..ad60521 --- /dev/null +++ b/config.rb @@ -0,0 +1,12 @@ +require 'compass/import-once/activate' +# Require any additional compass plugins here. + +# Set this to the root of your project when deployed: +http_path = "/" +css_dir = "css" +sass_dir = "scss" +images_dir = "images" +javascripts_dir = "javascript" + +line_comments = false + diff --git a/css/UserForm.css b/css/UserForm.css index cf667cf..612f290 100644 --- a/css/UserForm.css +++ b/css/UserForm.css @@ -1,38 +1,37 @@ /** * Lightweight base styles for the front-end form. */ -.userform-progress .step-buttons, -.step-navigation .step-buttons { - margin-left: 0; -} - -.userform-progress .step-buttons { - position: relative; -} - -.userform-progress .step-button-jump { - position: absolute; - top: 0; -} - -.userform-progress .step-buttons .step-button-wrapper, -.step-navigation .step-buttons .step-button-wrapper { - display: inline-block; - list-style-type: none; -} - .userform-progress .progress { - position: relative; - height: 1em; - background: #eee; + position: relative; + height: 1em; + background: #eee; +} +.userform-progress .progress .progress-bar { + position: absolute; + height: 1em; + background: #666; +} +.userform-progress .step-buttons { + margin-left: 0; + position: relative; +} +.userform-progress .step-buttons .step-button-wrapper { + display: inline-block; + list-style-type: none; +} +.userform-progress .step-button-jump { + position: absolute; + top: 0; } -.userform-progress .progress .progress-bar { - position: absolute; - height: 1em; - background: #666; +.step-navigation .step-buttons { + margin-left: 0; +} +.step-navigation .step-buttons .step-button-wrapper { + display: inline-block; + list-style-type: none; } .userform { - clear: both; + clear: both; } diff --git a/css/UserForm_cms.css b/css/UserForm_cms.css index 7953279..0280219 100644 --- a/css/UserForm_cms.css +++ b/css/UserForm_cms.css @@ -1,24 +1,28 @@ - -.cms .ss-gridfield > div.ss-gridfield-buttonrow-before { - margin-bottom: 10px; - overflow: auto; -} - /** - * Styles for page steps + * Styles for cms */ -.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 .ss-gridfield > div.ss-gridfield-buttonrow-before { + margin-bottom: 10px; + overflow: auto; +} +.cms table.ss-gridfield-table .ss-gridfield-item[data-class='EditableFormStep'], .cms table.ss-gridfield-table .ss-gridfield-item[data-class='EditableFormStep']:hover { + background: #dae2e7; +} +.cms table.ss-gridfield-table .ss-gridfield-item[data-class='EditableFormStep'] label { + font-weight: bold; + color: #444; + font-size: 1.1em; +} +.cms table.ss-gridfield-table .ss-gridfield-item[data-class^='EditableFieldGroup'], .cms table.ss-gridfield-table .ss-gridfield-item[data-class^='EditableFieldGroup']:hover { + background: #E7EFF4; +} +.cms table.ss-gridfield-table .ss-gridfield-item[data-class^='EditableFieldGroup'] label { + font-weight: bold; +} +.cms table.ss-gridfield-table .ss-gridfield-item[data-class='EditableFieldGroupEnd'] .col-Title input { + display: none; +} +.cms table.ss-gridfield-table .ss-gridfield-item.inFieldGroup .col-reorder, +.cms table.ss-gridfield-table .ss-gridfield-item.inFieldGroup .handle { + background: #E7EFF4; } - -.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 deleted file mode 100644 index a3df003..0000000 --- a/javascript/GridFieldAddItemInlineButton.js +++ /dev/null @@ -1,41 +0,0 @@ -(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/javascript/Gridfield.js b/javascript/Gridfield.js new file mode 100644 index 0000000..ffedf66 --- /dev/null +++ b/javascript/Gridfield.js @@ -0,0 +1,23 @@ +/** + * form builder behaviour. + */ + + (function($) { + $.entwine('ss', function($) { + $(".ss-gridfield-orderable tbody").entwine({ + onmatch: function() { + this._super(); + + this.find('.ss-gridfield-item') + .removeClass('inFieldGroup'); + + this.find('.ss-gridfield-item[data-class="EditableFieldGroup"]') + .nextUntil('.ss-gridfield-item[data-class="EditableFieldGroupEnd"]') + .addClass('inFieldGroup'); + }, + onunmatch: function () { + this._super(); + } + }); + }); +}(jQuery)); diff --git a/javascript/UserForm.js b/javascript/UserForm.js index 1a4a4f3..f4bc055 100644 --- a/javascript/UserForm.js +++ b/javascript/UserForm.js @@ -322,6 +322,7 @@ jQuery(function ($) { this.$el = element instanceof jQuery ? element : $(element); this.$buttons = this.$el.find('.step-button-jump'); + this.$jsAlign = this.$el.find('.js-align'); // Update the progress bar when 'step' buttons are clicked. this.$buttons.each(function (i, stepButton) { @@ -337,9 +338,9 @@ jQuery(function ($) { }); // Spaces out the steps below progress bar evenly - this.$buttons.each(function (index, button) { + this.$jsAlign.each(function (index, button) { var $button = $(button), - leftPercent = (100 / (self.$buttons.length - 1) * index + '%'), + leftPercent = (100 / (self.$jsAlign.length - 1) * index + '%'), buttonOffset = -1 * ($button.innerWidth() / 2); $button.css({left: leftPercent, marginLeft: buttonOffset}); diff --git a/scss/UserForm.scss b/scss/UserForm.scss new file mode 100644 index 0000000..5842929 --- /dev/null +++ b/scss/UserForm.scss @@ -0,0 +1,47 @@ +/** + * Lightweight base styles for the front-end form. + */ + +.userform-progress { + .progress { + position: relative; + height: 1em; + background: #eee; + + .progress-bar { + position: absolute; + height: 1em; + background: #666; + } + } + + .step-buttons { + margin-left: 0; + position: relative; + + .step-button-wrapper { + display: inline-block; + list-style-type: none; + } + } + + .step-button-jump { + position: absolute; + top: 0; + } +} + +.step-navigation { + .step-buttons { + margin-left: 0; + + .step-button-wrapper { + display: inline-block; + list-style-type: none; + } + } +} + +.userform { + clear: both; +} diff --git a/scss/UserForm_cms.scss b/scss/UserForm_cms.scss new file mode 100644 index 0000000..52b2dad --- /dev/null +++ b/scss/UserForm_cms.scss @@ -0,0 +1,50 @@ +/** + * Styles for cms + */ + +.cms { + .ss-gridfield > div.ss-gridfield-buttonrow-before { + margin-bottom: 10px; + overflow: auto; + } + + table.ss-gridfield-table { + .ss-gridfield-item[data-class='EditableFormStep'] { + &, &:hover { + background: #dae2e7; + } + + label { + font-weight: bold; + color: #444; + font-size: 1.1em; + } + } + + .ss-gridfield-item[data-class^='EditableFieldGroup'] { + &, &:hover { + background: #E7EFF4; + } + + label { + font-weight: bold; + } + } + + .ss-gridfield-item[data-class='EditableFieldGroupEnd'] { + .col-Title input { + display: none; + } + } + + .ss-gridfield-item.inFieldGroup { + .col-reorder, + .handle { + background: #E7EFF4; + } + } + } +} + + + diff --git a/templates/Includes/UserFormProgress.ss b/templates/Includes/UserFormProgress.ss index 0af9a18..7496a89 100644 --- a/templates/Includes/UserFormProgress.ss +++ b/templates/Includes/UserFormProgress.ss @@ -10,7 +10,7 @@