API Directly create editable fields on add

This commit is contained in:
Damian Mooyman 2015-08-12 12:45:25 +12:00
parent a8ee26ec50
commit 5ba3d9b2e0
7 changed files with 234 additions and 377 deletions

View File

@ -60,15 +60,13 @@ class UserFormFieldEditorExtension extends DataExtension {
->addComponents(
$editableColumns,
new GridFieldButtonRow(),
GridFieldAddItemInlineButton::create('EditableFormField')
->setTitle(_t('UserFormFieldEditorExtension.ADD_FIELD', 'Add Field'))
GridFieldAddClassesButton::create('EditableFormField')
->setButtonName(_t('UserFormFieldEditorExtension.ADD_FIELD', 'Add Field'))
->setButtonClass('ss-ui-action-constructive'),
GridFieldAddItemInlineButton::create('EditableFormStep')
->setTitle(_t('UserFormFieldEditorExtension.ADD_PAGE_BREAK', 'Add Page Break'))
->setExtraClass('uf-gridfield-steprow'),
GridFieldAddItemInlineButton::create(array('EditableFieldGroup', 'EditableFieldGroupEnd'))
->setTitle(_t('UserFormFieldEditorExtension.ADD_FIELD_GROUP', 'Add Field Group'))
->setExtraClass('uf-gridfield-grouprow'),
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(),

View 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();
}
}

View File

@ -1,321 +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 extends Object implements GridField_HTMLProvider, GridField_SaveHandler {
/**
* Fragment id
*
* @var string
*/
protected $fragment;
/**
* Additonal CSS classes for the button
*
* @var string
*/
protected $buttonClass = null;
/**
* Button title
*
* @var string
*/
protected $title;
/**
* Class names
*
* @var array
*/
protected $modelClasses = null;
/**
* Extra CSS classes for this row
*
* @var string
*/
protected $extraClass = 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 $fragment The fragment to render the button into
*/
public function __construct($classes, $fragment = 'buttons-before-left') {
$this->setClasses($classes);
$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) {
foreach($this->getClasses() as $class) {
if(!singleton($class)->canCreate()) {
return array();
}
}
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/GridFieldExtensions.js');
// Build button content
$data = new ArrayData(array(
'Title' => $this->getTitle(),
'ButtonClass' => $this->getButtonClass(),
'TemplateNames' => json_encode($this->getRowTemplateNames())
));
// Build template body
$templates = '';
foreach($this->getClasses() as $class) {
$templates .= $this->getItemRowTemplateFor($grid, $editable, $class);
}
return array(
$this->getFragment() => $data->renderWith(__CLASS__),
'after' => $templates
);
}
/**
* Get the template for a given class
*
* @param GridField $grid
* @param GridFieldEditableColumns $editable
* @param string $class Name of class
* @return type
*/
protected function getItemRowTemplateFor(GridField $grid, GridFieldEditableColumns $editable, $class) {
$columns = new ArrayList();
$handled = array_keys($editable->getDisplayFields($grid));
$record = Object::create($class);
$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][{%%=o.num%%}][%s][%s]', $grid->getName(), __CLASS__, $class, $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' => $class,
'TemplateName' => $this->getRowTemplateNameFor($class)
));
return $data->renderWith('GridFieldAddItemTemplate');
}
public function handleSave(GridField $grid, DataObjectInterface $record) {
// Check that this submission relates to this component
$value = $grid->Value();
if(empty($value[__CLASS__]) || !is_array($value[__CLASS__])) {
return;
}
// The way that this component works is that only the first component added to a form will
// be responsible for saving all records for each submission, in order to ensure creation
// and sort order is maintained
$addInlineComponents = $grid->getConfig()->getComponentsByType(__CLASS__);
if($this !== $addInlineComponents->first()) {
return;
}
// Get allowed classes
$classes = array();
foreach($addInlineComponents as $component) {
$classes = array_merge($classes, $component->getClasses());
}
$classes = array_filter($classes, function($class) {
return singleton($class)->canCreate();
});
// Get form
$editable = $grid->getConfig()->getComponentByType('GridFieldEditableColumns');
$form = $editable->getForm($grid, $record);
// Process records matching the specified class
$list = $grid->getList();
foreach($value[__CLASS__] as $row) {
$fields = reset($row);
$class = key($row);
if(!in_array($class, $classes)) {
continue;
}
$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 classes of the objects to create
*
* @return array
*/
public function getClasses() {
return $this->modelClasses;
}
/**
* Specify the classes to create
*
* @param array $classes
*/
public function setClasses($classes) {
if(!is_array($classes)) {
$classes = array($classes);
}
$this->modelClasses = $classes;
}
/**
* 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
* @return $this
*/
public function setExtraClass($extraClass) {
$this->extraClass = $extraClass;
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 names of all item templates
*
* @return array
*/
public function getRowTemplateNames() {
$self = $this;
return array_map(function($class) use ($self) {
return $this->getRowTemplateNameFor($class);
}, $this->getClasses());
}
/**
* Get the template name for a single class
*
* @param string $class
* @return string
*/
public function getRowTemplateNameFor($class) {
return "ss-gridfield-add-inline-template-{$class}";
}
}

View File

@ -1,32 +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() {
// Create each template
var gridfield = this.getGridField();
$.each(this.data('template-names'), function(index, template) {
console.log(template);
gridfield.trigger("addnewiteminline", template);
});
return false;
}
});
});
})(jQuery);

View 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>

View File

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

View File

@ -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>