mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
FEATURE Frontend CRUD scaffolding with RecordController and CollectionController (not fully functional yet, needs correct Link() methods)
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@63659 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
78469b86e6
commit
d543305882
172
core/control/CollectionController.php
Normal file
172
core/control/CollectionController.php
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage control
|
||||||
|
*/
|
||||||
|
abstract class CollectionController extends Controller {
|
||||||
|
|
||||||
|
public $parentController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string $modelClass Subclass of {@link DataObject} that should be processed.
|
||||||
|
* You can influence the selection of records through {@link getRecords()}.
|
||||||
|
*/
|
||||||
|
protected $modelClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string|boolean $recordControllerClass Use a {@link RecordController} subclass
|
||||||
|
* to customize the detail viewing/editing behaviour.
|
||||||
|
*/
|
||||||
|
protected $recordControllerClass = 'RecordController';
|
||||||
|
|
||||||
|
static $url_handlers = array(
|
||||||
|
'' => 'index',
|
||||||
|
'$Action' => 'handleAction',
|
||||||
|
);
|
||||||
|
|
||||||
|
static $page_size = 20;
|
||||||
|
|
||||||
|
static $allowed_actions = array('index','search', 'SearchForm', 'ResultsForm');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $parentController
|
||||||
|
* @param string $modelClass
|
||||||
|
*/
|
||||||
|
function __construct($parentController = null, $modelClass = null) {
|
||||||
|
if($parentController) $this->parentController = $parent;
|
||||||
|
if($modelClass) $this->modelClass = $modelClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
parent::init();
|
||||||
|
|
||||||
|
Requirements::themedCSS('layout');
|
||||||
|
Requirements::themedCSS('typography');
|
||||||
|
Requirements::themedCSS('form');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends the model class to the URL.
|
||||||
|
*
|
||||||
|
* @return unknown
|
||||||
|
*/
|
||||||
|
function Link() {
|
||||||
|
die("not implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the class name of the model being managed.
|
||||||
|
*
|
||||||
|
* @return unknown
|
||||||
|
*/
|
||||||
|
function getModelClass() {
|
||||||
|
return $this->modelClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegate to the {@link RecordController} if a valid numeric ID appears in the URL
|
||||||
|
* segment.
|
||||||
|
*
|
||||||
|
* @param HTTPRequest $request
|
||||||
|
* @return RecordController
|
||||||
|
*/
|
||||||
|
function record($request) {
|
||||||
|
if(!$this->recordControllerClass) return $this->httpError(403);
|
||||||
|
|
||||||
|
$class = $this->recordControllerClass;
|
||||||
|
return new $class($this, $this->modelClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function index($request) {
|
||||||
|
return $this->render(array(
|
||||||
|
'Results' => $this->getRecords()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRecords($searchCriteria = array()) {
|
||||||
|
$start = ($this->request->getVar('start')) ? (int)$this->request->getVar('start') : 0;
|
||||||
|
$limit = $this->stat('page_size');
|
||||||
|
|
||||||
|
$context = singleton($this->modelClass)->getDefaultSearchContext();
|
||||||
|
$query = $context->getQuery($searchCriteria, null, array('start'=>$start,'limit'=>$limit));
|
||||||
|
$records = $context->getResults($searchCriteria, null, array('start'=>$start,'limit'=>$limit));
|
||||||
|
if($records) {
|
||||||
|
$records->setPageLimits($start, $limit, $query->unlimitedRowCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $records;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a search form for a single {@link DataObject} subclass.
|
||||||
|
*
|
||||||
|
* @return Form
|
||||||
|
*/
|
||||||
|
public function SearchForm() {
|
||||||
|
$context = singleton($this->modelClass)->getDefaultSearchContext();
|
||||||
|
$fields = $context->getSearchFields();
|
||||||
|
$form = new Form($this, "SearchForm",
|
||||||
|
$fields,
|
||||||
|
new FieldSet(
|
||||||
|
new FormAction('search', _t('MemberTableField.SEARCH'))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$form->setFormMethod('get');
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to render a data object collection, using the model context to provide filters
|
||||||
|
* and paging.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function search($data, $form, $request) {
|
||||||
|
return $this->render(array(
|
||||||
|
'Results' => $this->getRecords($form->getData()),
|
||||||
|
'SearchForm' => $form
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the search query generated on the SearchContext from
|
||||||
|
* {@link DataObject::getDefaultSearchContext()},
|
||||||
|
* and the current GET parameters on the request.
|
||||||
|
*
|
||||||
|
* @return SQLQuery
|
||||||
|
*/
|
||||||
|
function getSearchQuery($searchCriteria) {
|
||||||
|
$context = singleton($this->modelClass)->getDefaultSearchContext();
|
||||||
|
return $context->getQuery($searchCriteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function ModelNameSingular() {
|
||||||
|
return singleton($this->modelClass)->i18n_singular_name();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function ModelNamePlural() {
|
||||||
|
return singleton($this->modelClass)->i18n_plural_name();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to control permissions or completely disable
|
||||||
|
* links to detail records.
|
||||||
|
* @return boolean (Default: true)
|
||||||
|
*/
|
||||||
|
public function canDetailView() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
248
core/control/RecordController.php
Normal file
248
core/control/RecordController.php
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage control
|
||||||
|
*/
|
||||||
|
class RecordController extends Controller {
|
||||||
|
|
||||||
|
protected $modelClass;
|
||||||
|
|
||||||
|
protected $currentRecord;
|
||||||
|
|
||||||
|
static $allowed_actions = array('add','edit', 'view', 'EditForm', 'ViewForm');
|
||||||
|
|
||||||
|
static $url_handlers = array(
|
||||||
|
'' => 'index',
|
||||||
|
'add' => 'add',
|
||||||
|
'AddForm' => 'AddForm',
|
||||||
|
'$ID/$Action' => 'handleAction',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $parentController
|
||||||
|
* @param string $modelClass
|
||||||
|
*/
|
||||||
|
function __construct($parentController = null, $modelClass = null) {
|
||||||
|
if($parentController) $this->parentController = $parentController;
|
||||||
|
if($modelClass) $this->modelClass = $modelClass;
|
||||||
|
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
parent::init();
|
||||||
|
|
||||||
|
Requirements::themedCSS('layout');
|
||||||
|
Requirements::themedCSS('typography');
|
||||||
|
Requirements::themedCSS('form');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAction($request) {
|
||||||
|
if(is_numeric($request->latestParam('ID'))) {
|
||||||
|
$this->currentRecord = DataObject::get_by_id($this->modelClass, $request->latestParam('ID'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::handleAction($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link fragment - appends the current record ID to the URL.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function Link() {
|
||||||
|
die("not implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function index($request) {
|
||||||
|
return $this->view($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit action - shows a form for editing this record
|
||||||
|
*/
|
||||||
|
function edit($request) {
|
||||||
|
if(!$this->currentRecord) {
|
||||||
|
return $this->httpError(404);
|
||||||
|
}
|
||||||
|
if(!$this->currentRecord->canEdit(Member::currentUser())) {
|
||||||
|
return $this->httpError(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render(array(
|
||||||
|
'Form' => $this->EditForm()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a form for editing the attached model
|
||||||
|
*/
|
||||||
|
public function EditForm() {
|
||||||
|
$fields = $this->currentRecord->getFormFields();
|
||||||
|
$fields->push(new HiddenField("ID"));
|
||||||
|
|
||||||
|
$validator = ($this->currentRecord->hasMethod('getValidator')) ? $this->currentRecord->getValidator() : null;
|
||||||
|
|
||||||
|
$actions = new FieldSet(
|
||||||
|
new FormAction("doSave", "Save")
|
||||||
|
);
|
||||||
|
|
||||||
|
if($this->currentRecord->canDelete(Member::currentUser())) {
|
||||||
|
$actions->push($deleteAction = new FormAction('doDelete', 'Delete'));
|
||||||
|
$deleteAction->addExtraClass('delete');
|
||||||
|
}
|
||||||
|
|
||||||
|
$form = new Form($this, "EditForm", $fields, $actions, $validator);
|
||||||
|
$form->loadDataFrom($this->currentRecord);
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Postback action to save a record
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @param Form $form
|
||||||
|
* @param HTTPRequest $request
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
function doSave($data, $form, $request) {
|
||||||
|
if(!$this->currentRecord->canEdit(Member::currentUser())) {
|
||||||
|
return $this->httpError(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$form->saveInto($this->currentRecord);
|
||||||
|
$this->currentRecord->write();
|
||||||
|
|
||||||
|
$form->sessionMessage(
|
||||||
|
_t('RecordController.SAVESUCCESS','Saved record'),
|
||||||
|
'good'
|
||||||
|
);
|
||||||
|
|
||||||
|
Director::redirectBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the current record
|
||||||
|
*/
|
||||||
|
public function doDelete($data, $form, $request) {
|
||||||
|
if(!$this->currentRecord->canDelete(Member::currentUser())) {
|
||||||
|
return $this->httpError(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->currentRecord->delete();
|
||||||
|
$form->sessionMessage(
|
||||||
|
_t('RecordController.DELETESUCCESS','Successfully deleted record'),
|
||||||
|
'good'
|
||||||
|
);
|
||||||
|
|
||||||
|
Director::redirectBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the record view template.
|
||||||
|
*
|
||||||
|
* @param HTTPRequest $request
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
function view($request) {
|
||||||
|
if(!$this->currentRecord) {
|
||||||
|
return $this->httpError(404);
|
||||||
|
}
|
||||||
|
if(!$this->currentRecord->canDelete(Member::currentUser())) {
|
||||||
|
return $this->httpError(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render(array(
|
||||||
|
'Form' => $this->ViewForm()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a form for viewing the attached model
|
||||||
|
*
|
||||||
|
* @return Form
|
||||||
|
*/
|
||||||
|
public function ViewForm() {
|
||||||
|
$fields = $this->currentRecord->getFormFields();
|
||||||
|
$form = new Form($this, "EditForm", $fields, new FieldSet());
|
||||||
|
$form->loadDataFrom($this->currentRecord);
|
||||||
|
$form->makeReadonly();
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new model record.
|
||||||
|
*
|
||||||
|
* @param unknown_type $request
|
||||||
|
* @return unknown
|
||||||
|
*/
|
||||||
|
function add($request) {
|
||||||
|
if(!singleton($this->modelClass)->canCreate(Member::currentUser())) {
|
||||||
|
return $this->httpError(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render(array(
|
||||||
|
'Form' => $this->AddForm()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a form for editing the attached model
|
||||||
|
*/
|
||||||
|
public function AddForm() {
|
||||||
|
$newRecord = new $this->modelClass();
|
||||||
|
if($newRecord->hasMethod('getAddFormFields')) {
|
||||||
|
$fields = $newRecord->getAddFormFields();
|
||||||
|
} else {
|
||||||
|
$fields = $newRecord->getFormFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
$validator = ($newRecord->hasMethod('getValidator')) ? $newRecord->getValidator() : null;
|
||||||
|
|
||||||
|
$actions = new FieldSet(new FormAction("doAdd", "Add"));
|
||||||
|
|
||||||
|
$form = new Form($this, "AddForm", $fields, $actions, $validator);
|
||||||
|
|
||||||
|
return $form;
|
||||||
|
}
|
||||||
|
|
||||||
|
function doAdd($data, $form, $request) {
|
||||||
|
if(!singleton($this->modelClass)->canCreate(Member::currentUser())) {
|
||||||
|
return $this->httpError(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$className = $this->modelClass;
|
||||||
|
$model = new $className();
|
||||||
|
// We write before saveInto, since this will let us save has-many and many-many relationships :-)
|
||||||
|
$model->write();
|
||||||
|
$form->saveInto($model);
|
||||||
|
$model->write();
|
||||||
|
|
||||||
|
Director::redirect(Controller::join_links($this->Link(), $model->ID , 'edit'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function ModelNameSingular() {
|
||||||
|
return singleton($this->modelClass)->i18n_singular_name();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function ModelNamePlural() {
|
||||||
|
return singleton($this->modelClass)->i18n_plural_name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
22
templates/CollectionController.ss
Normal file
22
templates/CollectionController.ss
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
|
||||||
|
<head>
|
||||||
|
<% base_tag %>
|
||||||
|
$MetaTags
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="BgContainer">
|
||||||
|
<div id="Container">
|
||||||
|
<div id="Header">
|
||||||
|
<h1>$ModelNameSingular</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="Layout">
|
||||||
|
$Layout
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
48
templates/Layout/CollectionController.ss
Normal file
48
templates/Layout/CollectionController.ss
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<div class="typography">
|
||||||
|
<div id="Content">
|
||||||
|
|
||||||
|
<h2>$Title</h2>
|
||||||
|
|
||||||
|
<% if SearchForm %>
|
||||||
|
<h3><% _t('SEARCH','Search') %></h3>
|
||||||
|
$SearchForm
|
||||||
|
<% end_if %>
|
||||||
|
|
||||||
|
<% if Results %>
|
||||||
|
<ul class="records">
|
||||||
|
<% control Results %>
|
||||||
|
<li>
|
||||||
|
<% if Top.canDetailView %>
|
||||||
|
<a href="{$Top.Link}/record/$ID/view">$Title</a>
|
||||||
|
<% else %>
|
||||||
|
$Title
|
||||||
|
<% end_if %>
|
||||||
|
</li>
|
||||||
|
<% end_control %>
|
||||||
|
</ul>
|
||||||
|
<% else %>
|
||||||
|
<p class="message"><% _t('NORESULTSFOUND','No records found') %></p>
|
||||||
|
<% end_if %>
|
||||||
|
|
||||||
|
<% if Results.MoreThanOnePage %>
|
||||||
|
<div id="PageNumbers">
|
||||||
|
<% if Results.NotLastPage %>
|
||||||
|
<a class="next" href="$Results.NextLink"><% _t('NEXT','Next') %></a>
|
||||||
|
<% end_if %>
|
||||||
|
<% if Results.NotFirstPage %>
|
||||||
|
<a class="prev" href="$Results.PrevLink"><% _t('PREV','Prev') %></a>
|
||||||
|
<% end_if %>
|
||||||
|
<span>
|
||||||
|
<% control Results.Pages %>
|
||||||
|
<% if CurrentBool %>
|
||||||
|
$PageNum
|
||||||
|
<% else %>
|
||||||
|
<a href="$Link">$PageNum</a>
|
||||||
|
<% end_if %>
|
||||||
|
<% end_control %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<% end_if %>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
9
templates/Layout/RecordController.ss
Normal file
9
templates/Layout/RecordController.ss
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<div class="typography">
|
||||||
|
<div id="Content">
|
||||||
|
|
||||||
|
<h2>$Title</h2>
|
||||||
|
|
||||||
|
$Form
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
22
templates/RecordController.ss
Normal file
22
templates/RecordController.ss
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
|
||||||
|
<head>
|
||||||
|
<% base_tag %>
|
||||||
|
$MetaTags
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="BgContainer">
|
||||||
|
<div id="Container">
|
||||||
|
<div id="Header">
|
||||||
|
<h1>$ModelNameSingular</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="Layout">
|
||||||
|
$Layout
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user