mirror of
https://github.com/silverstripe/silverstripe-userforms.git
synced 2024-10-22 17:05:42 +02:00
Merge remote-tracking branch 'open-sausages/feature/field-groups' into feature/multi-page-forms-v2
This commit is contained in:
commit
5c0f172ee5
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
.sass-cache/
|
||||||
|
@ -37,17 +37,17 @@ class UserFormFieldEditorExtension extends DataExtension {
|
|||||||
$this->createInitialFormStep(true);
|
$this->createInitialFormStep(true);
|
||||||
|
|
||||||
$editableColumns = new GridFieldEditableColumns();
|
$editableColumns = new GridFieldEditableColumns();
|
||||||
|
$fieldClasses = $this->getEditableFieldClasses();
|
||||||
$editableColumns->setDisplayFields(array(
|
$editableColumns->setDisplayFields(array(
|
||||||
'ClassName' => function($record, $column, $grid) {
|
'ClassName' => function($record, $column, $grid) use ($fieldClasses) {
|
||||||
if($record instanceof EditableFormStep) {
|
if($record instanceof EditableFormField) {
|
||||||
return new LabelField($column, "Page Break");
|
return $record->getInlineClassnameField($column, $fieldClasses);
|
||||||
} else {
|
|
||||||
return DropdownField::create($column, '', $this->getEditableFieldClasses());
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Title' => function($record, $column, $grid) {
|
'Title' => function($record, $column, $grid) {
|
||||||
return TextField::create($column, ' ')
|
if($record instanceof EditableFormField) {
|
||||||
->setAttribute('placeholder', _t('UserDefinedForm.TITLE', 'Title'));
|
return $record->getInlineTitleField($column);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -55,8 +55,13 @@ class UserFormFieldEditorExtension extends DataExtension {
|
|||||||
->addComponents(
|
->addComponents(
|
||||||
$editableColumns,
|
$editableColumns,
|
||||||
new GridFieldButtonRow(),
|
new GridFieldButtonRow(),
|
||||||
$addField = new GridFieldAddNewInlineButton(),
|
GridFieldAddClassesButton::create('EditableFormField')
|
||||||
$addStep = new GridFieldAddItemInlineButton('EditableFormStep'),
|
->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 GridFieldEditButton(),
|
||||||
new GridFieldDeleteAction(),
|
new GridFieldDeleteAction(),
|
||||||
new GridFieldToolbarHeader(),
|
new GridFieldToolbarHeader(),
|
||||||
@ -64,9 +69,6 @@ class UserFormFieldEditorExtension extends DataExtension {
|
|||||||
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(
|
$fieldEditor = GridField::create(
|
||||||
'Fields',
|
'Fields',
|
||||||
@ -113,7 +115,7 @@ class UserFormFieldEditorExtension extends DataExtension {
|
|||||||
|
|
||||||
// Add step
|
// Add step
|
||||||
$step = EditableFormStep::create();
|
$step = EditableFormStep::create();
|
||||||
$step->Title = _t('EditableFormStep.TITLE_FIRST', 'First Step');
|
$step->Title = _t('EditableFormStep.TITLE_FIRST', 'First Page');
|
||||||
$step->Sort = 1;
|
$step->Sort = 1;
|
||||||
$step->write();
|
$step->write();
|
||||||
$fields->add($step);
|
$fields->add($step);
|
||||||
@ -126,21 +128,20 @@ class UserFormFieldEditorExtension extends DataExtension {
|
|||||||
$classes = ClassInfo::getValidSubClasses('EditableFormField');
|
$classes = ClassInfo::getValidSubClasses('EditableFormField');
|
||||||
|
|
||||||
// Remove classes we don't want to display in the dropdown.
|
// Remove classes we don't want to display in the dropdown.
|
||||||
$classes = array_diff($classes, array(
|
|
||||||
'EditableFormField',
|
|
||||||
'EditableMultipleOptionField'
|
|
||||||
));
|
|
||||||
|
|
||||||
$editableFieldClasses = array();
|
$editableFieldClasses = array();
|
||||||
|
foreach ($classes as $class) {
|
||||||
|
if(in_array($class, array('EditableFormField', 'EditableMultipleOptionField'))
|
||||||
|
|| Config::inst()->get($class, 'hidden')
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($classes as $key => $className) {
|
$singleton = singleton($class);
|
||||||
$singleton = singleton($className);
|
|
||||||
|
|
||||||
if(!$singleton->canCreate()) {
|
if(!$singleton->canCreate()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$editableFieldClasses[$className] = $singleton->i18n_singular_name();
|
$editableFieldClasses[$class] = $singleton->i18n_singular_name();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $editableFieldClasses;
|
return $editableFieldClasses;
|
||||||
|
108
code/extensions/UserFormValidator.php
Normal file
108
code/extensions/UserFormValidator.php
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
class UserFormValidator extends RequiredFields {
|
||||||
|
public function php($data) {
|
||||||
|
if(!parent::php($data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip unsaved records
|
||||||
|
if(empty($data['ID']) || !is_numeric($data['ID'])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fields = EditableFormField::get()->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;
|
||||||
|
}
|
||||||
|
}
|
47
code/formfields/UserFormsCompositeField.php
Normal file
47
code/formfields/UserFormsCompositeField.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a composite field group, which may contain other groups
|
||||||
|
*/
|
||||||
|
abstract class UserFormsCompositeField extends CompositeField implements UserFormsFieldContainer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parent field
|
||||||
|
*
|
||||||
|
* @var UserFormsFieldContainer
|
||||||
|
*/
|
||||||
|
protected $parent = null;
|
||||||
|
|
||||||
|
public function getParent() {
|
||||||
|
return $this->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;
|
||||||
|
}
|
||||||
|
}
|
30
code/formfields/UserFormsFieldContainer.php
Normal file
30
code/formfields/UserFormsFieldContainer.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a field container which can iteratively process nested fields, converting it into a fieldset
|
||||||
|
*/
|
||||||
|
interface UserFormsFieldContainer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the next field in the list, returning the container to add the next field to.
|
||||||
|
*
|
||||||
|
* @param EditableFormField $field
|
||||||
|
* @return EditableContainerField
|
||||||
|
*/
|
||||||
|
public function processNext(EditableFormField $field);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the parent
|
||||||
|
*
|
||||||
|
* @param UserFormsFieldContainer $parent
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setParent(UserFormsFieldContainer $parent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the parent
|
||||||
|
*
|
||||||
|
* @return UserFormsFieldContainer
|
||||||
|
*/
|
||||||
|
public function getParent();
|
||||||
|
}
|
32
code/formfields/UserFormsFieldList.php
Normal file
32
code/formfields/UserFormsFieldList.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of formfields which allows for iterative processing of nested composite fields
|
||||||
|
*/
|
||||||
|
class UserFormsFieldList extends FieldList implements UserFormsFieldContainer {
|
||||||
|
|
||||||
|
public function processNext(EditableFormField $field) {
|
||||||
|
$formField = $field->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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
27
code/formfields/UserFormsGroupField.php
Normal file
27
code/formfields/UserFormsGroupField.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Front end composite field for userforms
|
||||||
|
*/
|
||||||
|
class UserFormsGroupField extends UserFormsCompositeField {
|
||||||
|
|
||||||
|
public function __construct($children = null) {
|
||||||
|
parent::__construct($children);
|
||||||
|
$this->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);
|
||||||
|
}
|
||||||
|
}
|
7
code/formfields/UserFormsStepField.php
Normal file
7
code/formfields/UserFormsStepField.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a page step in a form, which may contain form fields or other groups
|
||||||
|
*/
|
||||||
|
class UserFormsStepField extends UserFormsCompositeField {
|
||||||
|
}
|
225
code/forms/GridFieldAddClassesButton.php
Normal file
225
code/forms/GridFieldAddClassesButton.php
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A button which allows objects to be created with a specified classname(s)
|
||||||
|
*/
|
||||||
|
class GridFieldAddClassesButton extends Object implements GridField_HTMLProvider, GridField_URLHandler {
|
||||||
|
|
||||||
|
private static $allowed_actions = array(
|
||||||
|
'handleAdd'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of fragment to insert into
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $targetFragment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button title
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $buttonName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additonal CSS classes for the button
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $buttonClass = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class names
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $modelClasses = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $classes Class or list of classes to create.
|
||||||
|
* If you enter more than one class, each click of the "add" button will create one of each
|
||||||
|
* @param string $targetFragment The fragment to render the button into
|
||||||
|
*/
|
||||||
|
public function __construct($classes, $targetFragment = 'buttons-before-left') {
|
||||||
|
parent::__construct();
|
||||||
|
$this->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();
|
||||||
|
}
|
||||||
|
}
|
@ -75,31 +75,11 @@ class UserForm extends Form {
|
|||||||
* @return FieldList
|
* @return FieldList
|
||||||
*/
|
*/
|
||||||
public function getFormFields() {
|
public function getFormFields() {
|
||||||
$fields = new FieldList();
|
$fields = new UserFormsFieldList();
|
||||||
$emptyStep = null; // Last empty step, which may or may not later have children
|
$target = $fields;
|
||||||
|
|
||||||
foreach ($this->controller->Fields() as $field) {
|
foreach ($this->controller->Fields() as $field) {
|
||||||
// When we encounter a step, save it
|
$target = $target->processNext($field);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->extend('updateFormFields', $fields);
|
$this->extend('updateFormFields', $fields);
|
||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
@ -1,246 +0,0 @@
|
|||||||
<?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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -178,6 +178,8 @@ SQL;
|
|||||||
$config->addComponent($export = new GridFieldExportButton());
|
$config->addComponent($export = new GridFieldExportButton());
|
||||||
$config->addComponent($print = new GridFieldPrintButton());
|
$config->addComponent($print = new GridFieldPrintButton());
|
||||||
|
|
||||||
|
Requirements::javascript(USERFORMS_DIR . '/javascript/Gridfield.js');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Support for {@link https://github.com/colymba/GridFieldBulkEditingTools}
|
* Support for {@link https://github.com/colymba/GridFieldBulkEditingTools}
|
||||||
*/
|
*/
|
||||||
@ -284,6 +286,14 @@ SQL;
|
|||||||
|
|
||||||
DB::alteration_message('Migrated userforms', 'changed');
|
DB::alteration_message('Migrated userforms', 'changed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate formfields
|
||||||
|
*/
|
||||||
|
public function getCMSValidator() {
|
||||||
|
return new UserFormValidator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
80
code/model/editableformfields/EditableFieldGroup.php
Normal file
80
code/model/editableformfields/EditableFieldGroup.php
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies that this ends a group of fields
|
||||||
|
*/
|
||||||
|
class EditableFieldGroup extends EditableFormField {
|
||||||
|
|
||||||
|
private static $has_one = array(
|
||||||
|
'End' => '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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
86
code/model/editableformfields/EditableFieldGroupEnd.php
Normal file
86
code/model/editableformfields/EditableFieldGroupEnd.php
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies that this ends a group of fields
|
||||||
|
*/
|
||||||
|
class EditableFieldGroupEnd extends EditableFormField {
|
||||||
|
|
||||||
|
private static $belongs_to = array(
|
||||||
|
'Group' => '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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -8,6 +8,14 @@
|
|||||||
*/
|
*/
|
||||||
class EditableFormField extends DataObject {
|
class EditableFormField extends DataObject {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to true to hide from class selector
|
||||||
|
*
|
||||||
|
* @config
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private static $hidden = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default sort order
|
* Default sort order
|
||||||
*
|
*
|
||||||
@ -442,6 +450,10 @@ class EditableFormField extends DataObject {
|
|||||||
return Convert::raw2xml($this->Title);
|
return Convert::raw2xml($this->Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCMSTitle() {
|
||||||
|
return $this->i18n_singular_name() . ' (' . $this->Title . ')';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since version 4.0
|
* @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'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,17 +6,17 @@
|
|||||||
*/
|
*/
|
||||||
class EditableFormStep extends EditableFormField {
|
class EditableFormStep extends EditableFormField {
|
||||||
|
|
||||||
/**
|
private static $singular_name = 'Page Break';
|
||||||
* @config
|
|
||||||
* @var string
|
private static $plural_name = 'Page Breaks';
|
||||||
*/
|
|
||||||
private static $singular_name = 'Step';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Disable selection of step class
|
||||||
|
*
|
||||||
* @config
|
* @config
|
||||||
* @var string
|
* @var bool
|
||||||
*/
|
*/
|
||||||
private static $plural_name = 'Steps';
|
private static $hidden = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return FieldList
|
* @return FieldList
|
||||||
@ -24,10 +24,7 @@ class EditableFormStep extends EditableFormField {
|
|||||||
public function getCMSFields() {
|
public function getCMSFields() {
|
||||||
$fields = parent::getCMSFields();
|
$fields = parent::getCMSFields();
|
||||||
|
|
||||||
$fields->removeByName('MergeField');
|
$fields->removeByName(array('MergeField', 'Default', 'Validation', 'DisplayRules'));
|
||||||
$fields->removeByName('Default');
|
|
||||||
$fields->removeByName('Validation');
|
|
||||||
$fields->removeByName('CustomRules');
|
|
||||||
|
|
||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
@ -36,7 +33,7 @@ class EditableFormStep extends EditableFormField {
|
|||||||
* @return FormField
|
* @return FormField
|
||||||
*/
|
*/
|
||||||
public function getFormField() {
|
public function getFormField() {
|
||||||
$field = CompositeField::create()
|
$field = UserFormsStepField::create()
|
||||||
->setTitle($this->EscapedTitle);
|
->setTitle($this->EscapedTitle);
|
||||||
$this->doUpdateFormField($field);
|
$this->doUpdateFormField($field);
|
||||||
return $field;
|
return $field;
|
||||||
@ -55,4 +52,19 @@ class EditableFormStep extends EditableFormField {
|
|||||||
public function showInReports() {
|
public function showInReports() {
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
12
config.rb
Normal file
12
config.rb
Normal file
@ -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
|
||||||
|
|
@ -1,38 +1,37 @@
|
|||||||
/**
|
/**
|
||||||
* Lightweight base styles for the front-end form.
|
* 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 {
|
.userform-progress .progress {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
background: #eee;
|
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 {
|
.step-navigation .step-buttons {
|
||||||
position: absolute;
|
margin-left: 0;
|
||||||
height: 1em;
|
}
|
||||||
background: #666;
|
.step-navigation .step-buttons .step-button-wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.userform {
|
.userform {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
@ -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 .ss-gridfield > div.ss-gridfield-buttonrow-before {
|
||||||
.cms table.ss-gridfield-table .ss-gridfield-item[data-class='EditableFormStep'],
|
margin-bottom: 10px;
|
||||||
.cms table.ss-gridfield-table .ss-gridfield-inline-new.uf-gridfield-steprow:hover,
|
overflow: auto;
|
||||||
.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'], .cms table.ss-gridfield-table .ss-gridfield-item[data-class='EditableFormStep']:hover {
|
||||||
.cms table.ss-gridfield-table .ss-gridfield-inline-new.uf-gridfield-steprow label,
|
background: #dae2e7;
|
||||||
.cms table.ss-gridfield-table .ss-gridfield-item[data-class='EditableFormStep'] label
|
}
|
||||||
{
|
.cms table.ss-gridfield-table .ss-gridfield-item[data-class='EditableFormStep'] label {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #393939;
|
color: #444;
|
||||||
font-size: 1.1em;
|
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;
|
||||||
}
|
}
|
@ -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);
|
|
23
javascript/Gridfield.js
Normal file
23
javascript/Gridfield.js
Normal file
@ -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));
|
@ -322,6 +322,7 @@ jQuery(function ($) {
|
|||||||
|
|
||||||
this.$el = element instanceof jQuery ? element : $(element);
|
this.$el = element instanceof jQuery ? element : $(element);
|
||||||
this.$buttons = this.$el.find('.step-button-jump');
|
this.$buttons = this.$el.find('.step-button-jump');
|
||||||
|
this.$jsAlign = this.$el.find('.js-align');
|
||||||
|
|
||||||
// Update the progress bar when 'step' buttons are clicked.
|
// Update the progress bar when 'step' buttons are clicked.
|
||||||
this.$buttons.each(function (i, stepButton) {
|
this.$buttons.each(function (i, stepButton) {
|
||||||
@ -337,9 +338,9 @@ jQuery(function ($) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Spaces out the steps below progress bar evenly
|
// Spaces out the steps below progress bar evenly
|
||||||
this.$buttons.each(function (index, button) {
|
this.$jsAlign.each(function (index, button) {
|
||||||
var $button = $(button),
|
var $button = $(button),
|
||||||
leftPercent = (100 / (self.$buttons.length - 1) * index + '%'),
|
leftPercent = (100 / (self.$jsAlign.length - 1) * index + '%'),
|
||||||
buttonOffset = -1 * ($button.innerWidth() / 2);
|
buttonOffset = -1 * ($button.innerWidth() / 2);
|
||||||
|
|
||||||
$button.css({left: leftPercent, marginLeft: buttonOffset});
|
$button.css({left: leftPercent, marginLeft: buttonOffset});
|
||||||
|
47
scss/UserForm.scss
Normal file
47
scss/UserForm.scss
Normal file
@ -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;
|
||||||
|
}
|
50
scss/UserForm_cms.scss
Normal file
50
scss/UserForm_cms.scss
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
|||||||
<ul class="step-buttons">
|
<ul class="step-buttons">
|
||||||
<% loop $NumberOfSteps %>
|
<% loop $NumberOfSteps %>
|
||||||
<li class="step-button-wrapper<% if $Pos == '1' %> current<% end_if %>">
|
<li class="step-button-wrapper<% if $Pos == '1' %> current<% end_if %>">
|
||||||
<button class="step-button-jump" disabled="disabled">$Pos</button>
|
<button class="step-button-jump js-align" disabled="disabled">$Pos</button><!-- Remove js-align class to remove javascript positioning -->
|
||||||
</li>
|
</li>
|
||||||
<% end_loop %>
|
<% end_loop %>
|
||||||
<ul>
|
<ul>
|
||||||
|
3
templates/gridfield/GridFieldAddClassesButton.ss
Normal file
3
templates/gridfield/GridFieldAddClassesButton.ss
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<a href="$Link.ATT" class="action action-detail ss-ui-button ui-button ui-widget ui-state-default ui-corner-all new new-link $ButtonClass.ATT" data-icon="add">
|
||||||
|
$Title.XML
|
||||||
|
</a>
|
@ -1,3 +0,0 @@
|
|||||||
<button href="$Link" class="ss-gridfield-add-new-item-inline ss-ui-button" data-icon="add" data-template="$TemplateName.ATT">
|
|
||||||
$Title
|
|
||||||
</button>
|
|
@ -1,13 +0,0 @@
|
|||||||
<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>
|
|
Loading…
Reference in New Issue
Block a user