我通过创建一个让用户管理香水(即香水/古龙水)的小网站来学习SilverStripe。用户添加成分(用于他们拥有的香水),然后添加他们的香水,此时他们选择哪些成分在他们添加的香水中。
我创建了Ingredient和Fragrance类,它们都扩展了DataObject。我还创建了IngredientsPage页面,允许用户添加/编辑/删除成分(由名称和描述组成)并列出到目前为止添加的所有成分,此页面功能齐全。我现在正在尝试创建FragrancesPage页面,该页面将允许用户添加/编辑/删除香水(由名称,描述和成分组成)并列出到目前为止添加的所有香水,但我有有些麻烦。
我所知道的唯一方法是创造一种香水和成分之间的关系(一种香水含有许多成分,一种成分属于许多香水)正在使用GridField(如果有更好的方法,请告诉我!),因为这是SilverStripe教程让你做的事情(虽然在教程中它是CMS而不是前端)。但是,只要我尝试将GridField添加到组合中,我就会进入错误页面,其中显示"服务器错误:抱歉,处理您的请求时出现问题。"。
我的代码如下。
为ingredient.php:
<?php
class Ingredient extends DataObject {
private static $db = array(
'Name' => 'Text',
'Description' => 'Text'
);
private static $belongs_many_many = array(
'Fragrances' => 'Fragrance'
);
}
?>
Fragrance.php:
<?php
class Fragrance extends DataObject {
private static $db = array(
'Name' => 'Text',
'Description' => 'Text'
);
private static $many_many = array(
'Ingredients' => 'Ingredient'
);
}
?>
FragrancesPage.php:
<?php
class FragrancesPage extends Page {
private static $icon = 'cms/images/treeicons/reports-file.png';
private static $description = 'Fragrances page';
}
class FragrancesPage_Controller extends Page_Controller {
private static $allowed_actions = array('FragranceAddForm');
function FragranceAddForm() {
$config = GridFieldConfig_RelationEditor::create();
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
'Name' => 'Name',
'Ingredient.Name' => 'Ingredient'
));
$fragrances_field = new GridField(
'Ingredients',
'Ingredient',
$this->Ingredients(),
$config
);
$fields = new FieldList(
new TextField('Name', 'Fragrance Name'),
new TextareaField('Description', 'Fragrance Description'),
$fragrances_field
);
$actions = new FieldList(
new FormAction('doFragranceAdd', 'Add Fragrance')
);
$validator = new RequiredFields('Name', 'Description');
return new Form($this, 'FragranceAddForm', $fields, $actions, $validator);
}
public function doFragranceAdd($data, $form) {
$submission = new Fragrance();
$form->saveInto($submission);
$submission->write();
return $this->redirectBack();
}
public function FragranceList() {
$submissions = Fragrance::get()->sort('Name');
return $submissions;
}
}
?>
如果我从FragrancesPage.php删除与GridField相关的所有内容,页面工作正常。我似乎无法让GridField工作,并且不知道在前端创建香水和成分之间关系的任何其他方式。如果IngredientsPage.php的代码也有帮助,请告诉我,我会添加它。
答案 0 :(得分:3)
我的猜测是你关闭了错误报告,这就是为什么你只看到这样一个含义少的错误信息
您应该在display_errors
,error_reporting
或php.ini
中启用.htaccess
和_ss_environment.php
(重要提示:将其设置为_config.php
将无效,因为它会被错误处理程序覆盖)
我在您的代码中看到的问题是尝试在$this->Ingredients()
上使用FragrancesPage
,但据我所知,只有类Fragrances
有一个方法Ingredients
(方法是'magically'为many_many关系创建的。)
另外,我认为你的setDisplayFields()
所以基本上你需要使用$fragrance->Ingredients()
而不是$this->Ingredients()
但这导致我们遇到下一个问题:你还没有香水。
遗憾的是,此时GridField
仅在您已有对象时才有效。这意味着您必须将其拆分为两种形式或使用替代方案。
选项1:使用CheckboxSetField来管理many_many关系。
这将不允许动态创建成分,它只会为您提供可以勾选以链接项目的复选框。
public function FragranceAddForm() {
$fragrances_field = new CheckboxSetField('Ingredients', 'Ingredient', Ingredient::get()->map());
$fields = new FieldList(
new TextField('Name', 'Fragrance Name'),
new TextareaField('Description', 'Fragrance Description'),
$fragrances_field
);
$actions = new FieldList(
new FormAction('doFragranceAdd', 'Add Fragrance')
);
$validator = new RequiredFields('Name', 'Description');
return new Form($this, __FUNCTION__, $fields, $actions, $validator);
}
public function doFragranceAdd($data, $form) {
$submission = new Fragrance();
$form->saveInto($submission);
$submission->write();
return $this->redirectBack();
}
选项2:以第二种形式使用GridField
这将允许动态创建成分,但是更多的工作。你可能会遇到GridField的一些麻烦,因为它尚未在前端进行全面测试。
(最近有一个问题,我在前端https://stackoverflow.com/a/22059197/1119263写了一些关于GridField问题的内容)
我想这个地方和前面为前端GridFields编写教程/工作示例一样好
我冒昧地重构你的代码以包含编辑功能,它在编辑页面上比在单独的表单上使用GridField要好得多。
如前所述,GridField在前端工作得不好,有一个模块可以缓解疼痛,但它仍然会在边缘粗糙,并且需要你做一些样式才能让它看起来很漂亮。 在Packagist或GitHub上找到该模块 (你需要t
class Ingredient extends DataObject {
private static $db = array(
'Name' => 'Text',
'Description' => 'Text'
);
private static $belongs_many_many = array(
'Fragrances' => 'Fragrance'
);
public function getCMSFields() {
// fields used by the GridField, don't let the CMSFields mislead you
return new FieldList(
TextField::create('Name', 'Name'),
TextAreaField::create('Description', 'Description')
);
}
}
class Fragrance extends DataObject {
private static $db = array(
'Name' => 'Text',
'Description' => 'Text'
);
private static $many_many = array(
'Ingredients' => 'Ingredient'
);
}
/**
* Form in a separate class, so we can reuse it.
* @param Controller $controller
* @param string $name
* @param Null|Fragrance $fragrance Either null to create a new one, or pass an existing to edit it and add Ingredients
* @return Form
*/
class FragranceForm extends Form {
public function __construct($controller, $name, $fragrance = null) {
if ($fragrance && $fragrance->isInDB()) {
// we can only use a GridField if the object exists and has already been saved
// gridfield needs jQuery
Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.min.js');
// ensure we don't have 2 versions of jQuery
Requirements::block(THIRDPARTY_DIR . '/jquery/jquery.js');
$config = FrontEndGridFieldConfig_RelationEditor::create();
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
'Name' => 'Name',
'Description' => 'Description',
));
$ingredientField = new FrontEndGridField(
'Ingredients',
'Ingredient',
$fragrance->Ingredients(),
$config
);
} else {
$ingredientField = new LiteralField('Ingredients', '<p>Ingredients can be added after saving</p>');
}
$fields = new FieldList(
new HiddenField('ID', ''),
new TextField('Name', 'Fragrance Name'),
new TextareaField('Description', 'Fragrance Description'),
$ingredientField
);
$actions = new FieldList(
new FormAction('doFragranceSave', 'Save Fragrance')
);
$validator = new RequiredFields('Name', 'Description');
// populate the fields (ID, Name and Description) with the values from $fragrance. This does not effect the GridField
if ($fragrance && $fragrance->exists()) {
$fields->fieldByName('ID')->setValue($fragrance->ID);
$fields->fieldByName('Name')->setValue($fragrance->Name);
$fields->fieldByName('Description')->setValue($fragrance->Description);
// there is actually a method for that, but we can't use it here,
// because fields are not set yet. we could do it after __construct, but then we would
// overwrite things set by the error handler, so lets just do it by hand
// $this->loadDataFrom($fragrance);
}
parent::__construct($controller, $name, $fields, $actions, $validator);
}
public function doFragranceSave($data, $form) {
if (isset($data['ID']) && $data['ID']) {
$id = (int)$data['ID'];
$fragrance = Fragrance::get()->byID($id);
}
if (!isset($fragrance) || !$fragrance || !$fragrance->exists()) {
// if the ID was invalid or we don't have one, create a new Fragrance
$fragrance = new Fragrance();
}
$form->saveInto($fragrance);
$fragrance->write();
// redirect to the edit page.
$controller = $this->getController();
$editLink = $controller->EditLink($fragrance->ID);
return $controller->redirect($editLink);
}
}
class FragrancesPage extends Page {
}
class FragrancesPage_Controller extends Page_Controller {
private static $allowed_actions = array(
'edit',
'AddForm',
'EditForm',
);
/**
* the default action
* @return ViewableData_Customised
*/
public function index() {
// $this->customise() lets you overwrite variables that you can use in the template later.
return $this->customise(array(
// set the AddForm to $Form instead of $AddForm, this way you can use $Form in template and can reuse the template
'Form' => $this->AddForm(),
));
}
/**
* edit action to edit an existing Fragrance
* links will look like this /FragrancesPage/edit/$ID
*
* @param SS_HTTPRequest $request
* @return SS_HTTPResponse|ViewableData_Customised
*/
public function edit(SS_HTTPRequest $request) {
$id = (int)$request->param('ID');
$fragrance = Fragrance::get()->byID($id);
if (!$fragrance || !$fragrance->exists()) {
// fragrance not found? display a 404 error page
return ErrorPage::response_for(404);
}
// now that we have a $fragrance, overwrite EditForm with a EditForm that contains the $fragrance
$form = $this->EditForm($fragrance);
$return = $this->customise(array(
// also overwrite Title and Content, to display info about what the user can do here
// if you don't overwrite that, it will display the Title and Content of the page
'Title' => 'Edit: ' . $fragrance->Name,
'Content' => '<p>you are editing an existing fragrance</p>',
// set the Form to $Form instead of $EditForm, this way you can use $Form in template and can reuse the template
'Form' => $form,
));
// per default SilverStripe will try to use the following templates: FragrancesPage_edit.ss > FragrancesPage.ss > Page.ss
// if you want to use a custom template here, you can specify that with ->renderWith()
// but you probably won't need that anyway
// $return = $return->renderWith(array('MyCustomTemplateName', 'Page'));
return $return;
}
public function AddForm() {
return new FragranceForm($this, __FUNCTION__);
}
public function EditForm($fragranceOrRequest = null) {
// unfortunately, GridField / FormFields in general are a bit clumsy and do forget what item they where
// suppose to edit, so we have to check what $fragranceOrRequest is and set/get the fragrance to/from session
if ($fragranceOrRequest && is_a($fragranceOrRequest, 'Fragrance')) {
$fragrance = $fragranceOrRequest;
Session::set('FragrancesPage.CurrentFragrance', $fragrance->ID);
} else {
$fragrance = Fragrance::get()->byID(Session::get('FragrancesPage.CurrentFragrance'));
}
if (!$fragrance || !$fragrance->exists()) {
// that's bad, some error has occurred, lets display an ugly 404 page
return $this->httpError(404);
}
return new FragranceForm($this, __FUNCTION__, $fragrance);
}
public function EditLink($ID) {
return $this->Link("edit/$ID");
}
}
我知道,如果您有任何疑问,可以随意学习SilverStripe,请随意评论,或者只是在IRC上戳我