Move CMS Howtos

This commit is contained in:
Will Rossiter 2014-11-08 08:43:57 +13:00 committed by Cam Findlay
parent b7daa8d1ee
commit d01c8d7990
18 changed files with 535 additions and 553 deletions

View File

@ -0,0 +1,333 @@
title: ModelAdmin
summary: Create admin UI's for managing your data records.
# ModelAdmin
[api:ModelAdmin] provides a simple way to utilize the SilverStripe Admin UI with your own data models. It can create
searchables list and edit views of [api:DataObject] subclasses, and even provides import and export of your data.
It uses the framework's knowledge about the model to provide sensible defaults, allowing you to get started in a couple
of lines of code, while still providing a solid base for customization.
<div class="info" markdown="1">
The interface is mainly powered by the [api:GridField] class ([documentation](../forms/fields/gridfield)), which can
also be used in other areas of your application.
</div>
Let's assume we want to manage a simple product listing as a sample data model: A product can have a name, price, and
a category.
**mysite/code/Product.php**
:::php
<?php
class Product extends DataObject {
private static $db = array(
'Name' => 'Varchar',
'ProductCode' => 'Varchar',
'Price' => 'Currency'
);
private static $has_one = array(
'Category' => 'Category'
);
}
**mysite/code/Category.php**
:::php
<?php
class Category extends DataObject {
private static $db = array(
'Title' => 'Text'
);
private static $has_many = array(
'Products' => 'Product'
);
}
To create your own `ModelAdmin`, simply extend the base class, and edit the `$managed_models` property with the list of
DataObject's you want to scaffold an interface for. The class can manage multiple models in parallel, if required.
We'll name it `MyAdmin`, but the class name can be anything you want.
**mysite/code/MyAdmin.php**
:::php
<?php
class MyAdmin extends ModelAdmin {
private static $managed_models = array(
'Product',
'Category'
);
private static $url_segment = 'products';
private static $menu_title = 'My Product Admin';
}
This will automatically add a new menu entry to the SilverStripe Admin UI entitled `My Product Admin` and logged in
users will be able to upload and manage `Product` and `Category` instances through http://yoursite.com/admin/products.
<div class="alert" markdown="1">
After defining these classes, make sure you have rebuilt your SilverStripe database and flushed your cache.
</div>
## Permissions
Each new `ModelAdmin` subclass creates its' own [permission code](../security), for the example above this would be
`CMS_ACCESS_MyAdmin`. Users with access to the Admin UI will need to have this permission assigned through
`admin/security/` or have the `ADMIN` permission code in order to gain access to the controller.
<div class="notice" markdown="1">
For more information on the security and permission system see the [Security Documentation](../security)
</div>
The [api:DataObject] API has more granular permission control, which is enforced in [api:ModelAdmin] by default.
Available checks are `canEdit()`, `canCreate()`, `canView()` and `canDelete()`. Models check for administrator
permissions by default. For most cases, less restrictive checks make sense, e.g. checking for general CMS access rights.
**mysite/code/Category.php**
:::php
<?php
class Category extends DataObject {
// ...
public function canView($member = null) {
return Permission::check('CMS_ACCESS_MyAdmin', 'any', $member);
}
public function canEdit($member = null) {
return Permission::check('CMS_ACCESS_MyAdmin', 'any', $member);
}
public function canDelete($member = null) {
return Permission::check('CMS_ACCESS_MyAdmin', 'any', $member);
}
public function canCreate($member = null) {
return Permission::check('CMS_ACCESS_MyAdmin', 'any', $member);
}
## Searching Records
[api:ModelAdmin] uses the [SearchContext](../search/searchcontext) class to provide a search form, as well as get the
searched results. Every [api:DataObject] can have its own context, based on the fields which should be searchable. The
class makes a guess at how those fields should be searched, e.g. showing a checkbox for any boolean fields in your
`$db` definition.
To remove, add or modify searchable fields, define a new `[api:DataObject::$searchable_fields]` static on your model
class (see [SearchContext](../search/searchcontext) docs for details).
**mysite/code/Product.php**
:::php
<?php
class Product extends DataObject {
private static $searchable_fields = array(
'Name',
'ProductCode'
);
}
<div class="hint" markdown="1">
[SearchContext](../search/searchcontext) documentation has more information on providing the search functionality.
</div>
## Displaying Results
The results are shown in a tabular listing, powered by the [GridField](../forms/fields/gridfield), more specifically
the [api:GridFieldDataColumns] component. This component looks for a [api:DataObject::$summary_fields] static on your
model class, where you can add or remove columns. To change the title, use [api:DataObject::$field_labels].
**mysite/code/Page.php**
:::php
<?php
class Product extends DataObject {
private static $field_labels = array(
'Price' => 'Cost' // renames the column to "Cost"
);
private static $summary_fields = array(
'Name',
'Price'
);
}
The results list are retrieved from [api:SearchContext->getResults], based on the parameters passed through the search
form. If no search parameters are given, the results will show every record. Results are a [api:DataList] instance, so
can be customized by additional SQL filters, joins.
For example, we might want to exclude all products without prices in our sample `MyAdmin` implementation.
**mysite/code/MyAdmin.php**
:::php
<?php
class MyAdmin extends ModelAdmin {
public function getList() {
$list = parent::getList();
// Always limit by model class, in case you're managing multiple
if($this->modelClass == 'Product') {
$list = $list->exclude('Price', '0');
}
return $list;
}
}
You can also customize the search behavior directly on your `ModelAdmin` instance. For example, we might want to have a
checkbox which limits search results to expensive products (over $100).
**mysite/code/MyAdmin.php**
:::php
<?php
class MyAdmin extends ModelAdmin {
public function getSearchContext() {
$context = parent::getSearchContext();
if($this->modelClass == 'Product') {
$context->getFields()->push(new CheckboxField('q[ExpensiveOnly]', 'Only expensive stuff'));
}
return $context;
}
public function getList() {
$list = parent::getList();
$params = $this->request->requestVar('q'); // use this to access search parameters
if($this->modelClass == 'Product' && isset($params['ExpensiveOnly']) && $params['ExpensiveOnly']) {
$list = $list->exclude('Price:LessThan', '100');
}
return $list;
}
}
To alter how the results are displayed (via `[api:GridField]`), you can also overload the `getEditForm()` method. For
example, to add a new component.
**mysite/code/MyAdmin.php**
:::php
<?php
class MyAdmin extends ModelAdmin {
private static $managed_models = array(
'Product',
'Category'
);
// ...
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
// $gridFieldName is generated from the ModelClass, eg if the Class 'Product'
// is managed by this ModelAdmin, the GridField for it will also be named 'Product'
$gridFieldName = $this->sanitiseClassName($this->modelClass);
$gridField = $form->Fields()->fieldByName($gridFieldName);
// modify the list view.
$gridField->getConfig()->addComponent(new GridFieldFilterHeader());
return $form;
}
}
The above example will add the component to all `GridField`s (of all managed models). Alternatively we can also add it
to only one specific `GridField`:
**mysite/code/MyAdmin.php**
:::php
<?php
class MyAdmin extends ModelAdmin {
private static $managed_models = array(
'Product',
'Category'
);
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
$gridFieldName = 'Product';
$gridField = $form->Fields()->fieldByName($gridFieldName);
if ($gridField) {
$gridField->getConfig()->addComponent(new GridFieldFilterHeader());
}
return $form;
}
}
## Data Import
The `ModelAdmin` class provides import of CSV files through the [api:CsvBulkLoader] API. which has support for column
mapping, updating existing records, and identifying relationships - so its a powerful tool to get your data into a
SilverStripe database.
By default, each model management interface allows uploading a CSV file with all columns auto detected. To override
with a more specific importer implementation, use the [api:ModelAdmin::$model_importers] static.
## Data Export
Export is available as a CSV format through a button at the end of a results list. You can also export search results.
This is handled through the [api:GridFieldExportButton] component.
To customize the exported columns, create a new method called `getExportFields` in your `ModelAdmin`:
:::php
<?php
class MyAdmin extends ModelAdmin {
// ...
public function getExportFields() {
return array(
'Name' => 'Name',
'ProductCode' => 'Product Code',
'Category.Title' => 'Category'
);
}
}
## Related Documentation
* [GridField](../forms/fields/gridfield)
* [Permissions](../security/permissions)
* [SeachContext](../search/seachcontext)
## API Documentation
* [api:ModelAdmin]
* [api:LeftAndMain]
* [api:GridField]
* [api:DataList]
* [api:CsvBulkLoader]

View File

@ -1,6 +1,6 @@
# CMS layout title: Admin Layout
## Overview # CMS layout
The CMS markup is structured into "panels", which are the base units containing interface components (or other panels), The CMS markup is structured into "panels", which are the base units containing interface components (or other panels),
as declared by the class `cms-panel`. Panels can be made collapsible, and get the ability to be resized and aligned with as declared by the class `cms-panel`. Panels can be made collapsible, and get the ability to be resized and aligned with
@ -8,7 +8,7 @@ a layout manager, in our case [jLayout](http://www.bramstein.com/projects/jlayou
declarations (mostly dimensions and positioning) via JavaScript. declarations (mostly dimensions and positioning) via JavaScript.
We've established a convention for a `redraw` method on each panel and UI component that need to update their content as We've established a convention for a `redraw` method on each panel and UI component that need to update their content as
a result of changes to their position, size or visibility. This method would usually be invoked by the parent container. a result of changes to their position, size or visibility. This method would usually be invoked by the parent container.
The layout manager does not dynamically track changes to panel sizes - we have to trigger laying out manually each time The layout manager does not dynamically track changes to panel sizes - we have to trigger laying out manually each time
we need an update to happen (for example from `window::onresize` event, or panel toggling). It then cascades through the we need an update to happen (for example from `window::onresize` event, or panel toggling). It then cascades through the
@ -21,7 +21,7 @@ The easiest way to update the layout of the CMS is to call `redraw` on the top-l
This causes the framework to: This causes the framework to:
* reset the _threeColumnCompressor_ algorithm with the current layout options (that can be set via * reset the _threeColumnCompressor_ algorithm with the current layout options (that can be set via
`updateLayoutOptions`) `updateLayoutOptions`)
* trigger `layout` which cascades into all children resizing and positioning subordinate elements (this is internal * trigger `layout` which cascades into all children resizing and positioning subordinate elements (this is internal
to the layout manager) to the layout manager)
@ -56,9 +56,9 @@ Layout manager will automatically apply algorithms to the children of `.cms-cont
`data-layout-type` attribute. Let's take the content toolbar as an example of a second-level layout application: `data-layout-type` attribute. Let's take the content toolbar as an example of a second-level layout application:
:::html :::html
<div class="cms-content-tools west cms-panel cms-panel-layout" <div class="cms-content-tools west cms-panel cms-panel-layout"
data-expandOnClick="true" data-expandOnClick="true"
data-layout-type="border" data-layout-type="border"
id="cms-content-tools-CMSMain"> id="cms-content-tools-CMSMain">
<%-- content utilising border's north, south, east, west and center classes --%> <%-- content utilising border's north, south, east, west and center classes --%>
</div> </div>
@ -67,15 +67,15 @@ For detailed discussion on available algorithms refer to
[jLayout algorithms](https://github.com/bramstein/jlayout#layout-algorithms). [jLayout algorithms](https://github.com/bramstein/jlayout#layout-algorithms).
Our [Howto: Extend the CMS Interface](../howto/extend-cms-interface) has a practical example on how to add a bottom Our [Howto: Extend the CMS Interface](../howto/extend-cms-interface) has a practical example on how to add a bottom
panel to the CMS UI. panel to the CMS UI.
### Methods ### Methods
The following methods are available as an interface to underlying _threeColumnCompressor_ algorithm on the The following methods are available as an interface to underlying _threeColumnCompressor_ algorithm on the
`.cms-container` entwine: `.cms-container` entwine:
* **getLayoutOptions**: get currently used _threeColumnCompressor_ options. * **getLayoutOptions**: get currently used _threeColumnCompressor_ options.
* **updateLayoutOptions**: change specified options and trigger the laying out: * **updateLayoutOptions**: change specified options and trigger the laying out:
`$('.cms-container').updateLayoutOptions({mode: 'split'});` `$('.cms-container').updateLayoutOptions({mode: 'split'});`
* **splitViewMode**: enable side by side editing. * **splitViewMode**: enable side by side editing.
* **contentViewMode**: only menu and content areas are shown. * **contentViewMode**: only menu and content areas are shown.
@ -91,7 +91,7 @@ You might have noticed that the top-level `.cms-container` has the `data-layout-
_threeColumnCompressor_ algorithm for the layout of the menu, content and preview columns of the CMS. The annotated code _threeColumnCompressor_ algorithm for the layout of the menu, content and preview columns of the CMS. The annotated code
for this algorithm can be found in `LeftAndMain.Layout.js`. for this algorithm can be found in `LeftAndMain.Layout.js`.
Since the layout-type for the element is set to `custom` and will be ignored by the layout manager, we apply the Since the layout-type for the element is set to `custom` and will be ignored by the layout manager, we apply the
_threeColumnCompressor_ explicitly `LeftAndMain::redraw`. This way we also get a chance to provide options expected _threeColumnCompressor_ explicitly `LeftAndMain::redraw`. This way we also get a chance to provide options expected
by the algorithm that are initially taken from the `LeftAndMain::LayoutOptions` entwine variable. by the algorithm that are initially taken from the `LeftAndMain::LayoutOptions` entwine variable.

View File

@ -2,41 +2,41 @@
## Overview ## Overview
With the addition of side-by-side editing, the preview has the ability to appear With the addition of side-by-side editing, the preview has the ability to appear
within the CMS window when editing content in the _Pages_ section of the CMS. within the CMS window when editing content in the _Pages_ section of the CMS.
The site is rendered into an iframe. It will update itself whenever the content The site is rendered into an iframe. It will update itself whenever the content
is saved, and relevant pages will be loaded for editing when the user navigates is saved, and relevant pages will be loaded for editing when the user navigates
around in the preview. around in the preview.
The root element for preview is `.cms-preview` which maintains the internal The root element for preview is `.cms-preview` which maintains the internal
states necessary for rendering within the entwine properties. It provides states necessary for rendering within the entwine properties. It provides
function calls for transitioning between these states and has the ability to function calls for transitioning between these states and has the ability to
update the appearance of the option selectors. update the appearance of the option selectors.
In terms of backend support, it relies on `SilverStripeNavigator` to be rendered In terms of backend support, it relies on `SilverStripeNavigator` to be rendered
into the `.cms-edit-form`. _LeftAndMain_ will automatically take care of into the `.cms-edit-form`. _LeftAndMain_ will automatically take care of
generating it as long as the `*_SilverStripeNavigator` template is found - generating it as long as the `*_SilverStripeNavigator` template is found -
first segment has to match current _LeftAndMain_-derived class (e.g. first segment has to match current _LeftAndMain_-derived class (e.g.
`LeftAndMain_SilverStripeNavigator`). `LeftAndMain_SilverStripeNavigator`).
We use `ss.preview` entwine namespace for all preview-related entwines. We use `ss.preview` entwine namespace for all preview-related entwines.
<div class="notice" markdown='1'> <div class="notice" markdown='1'>
Caveat: `SilverStripeNavigator` and `CMSPreviewable` interface currently only Caveat: `SilverStripeNavigator` and `CMSPreviewable` interface currently only
support SiteTree objects that are _Versioned_. They are not general enough for support SiteTree objects that are _Versioned_. They are not general enough for
using on any other DataObject. That pretty much limits the extendability of the using on any other DataObject. That pretty much limits the extendability of the
feature. feature.
</div> </div>
## Configuration and Defaults ## Configuration and Defaults
Like most of the CMS, the preview UI is powered by Like most of the CMS, the preview UI is powered by
[jQuery entwine](https://github.com/hafriedlander/jquery.entwine). This means [jQuery entwine](https://github.com/hafriedlander/jquery.entwine). This means
its defaults are configured through JavaScript, by setting entwine properties. its defaults are configured through JavaScript, by setting entwine properties.
In order to achieve this, create a new file `mysite/javascript/MyLeftAndMain.Preview.js`. In order to achieve this, create a new file `mysite/javascript/MyLeftAndMain.Preview.js`.
In the following example we configure three aspects: In the following example we configure three aspects:
* Set the default mode from "split view" to a full "edit view" * Set the default mode from "split view" to a full "edit view"
* Make a wider mobile preview * Make a wider mobile preview
* Increase minimum space required by preview before auto-hiding * Increase minimum space required by preview before auto-hiding
@ -83,32 +83,32 @@ To understand how layouts are handled in the CMS UI, have a look at the
## Enabling preview ## Enabling preview
The frontend decides on the preview being enabled or disabled based on the The frontend decides on the preview being enabled or disabled based on the
presence of the `.cms-previewable` class. If this class is not found the preview presence of the `.cms-previewable` class. If this class is not found the preview
will remain hidden, and the layout will stay in the _content_ mode. will remain hidden, and the layout will stay in the _content_ mode.
If the class is found, frontend looks for the `SilverStripeNavigator` structure If the class is found, frontend looks for the `SilverStripeNavigator` structure
and moves it to the `.cms-preview-control` panel at the bottom of the preview. and moves it to the `.cms-preview-control` panel at the bottom of the preview.
This structure supplies preview options such as state selector. This structure supplies preview options such as state selector.
If the navigator is not found, the preview appears in the GUI, but is shown as If the navigator is not found, the preview appears in the GUI, but is shown as
"blocked" - i.e. displaying the "preview unavailable" overlay. "blocked" - i.e. displaying the "preview unavailable" overlay.
The preview can be affected by calling `enablePreview` and `disablePreview`. You The preview can be affected by calling `enablePreview` and `disablePreview`. You
can check if the preview is active by inspecting the `IsPreviewEnabled` entwine can check if the preview is active by inspecting the `IsPreviewEnabled` entwine
property. property.
## Preview states ## Preview states
States are the site stages: _live_, _stage_ etc. Preview states are picked up States are the site stages: _live_, _stage_ etc. Preview states are picked up
from the `SilverStripeNavigator`. You can invoke the state change by calling: from the `SilverStripeNavigator`. You can invoke the state change by calling:
```js ```js
$('.cms-preview').entwine('.ss.preview').changeState('StageLink'); $('.cms-preview').entwine('.ss.preview').changeState('StageLink');
``` ```
Note the state names come from `SilverStripeNavigatorItems` class names - thus Note the state names come from `SilverStripeNavigatorItems` class names - thus
the _Link_ in their names. This call will also redraw the state selector to fit the _Link_ in their names. This call will also redraw the state selector to fit
with the internal state. See `AllowedStates` in `.cms-preview` entwine for the with the internal state. See `AllowedStates` in `.cms-preview` entwine for the
list of supported states. list of supported states.
@ -120,8 +120,8 @@ You can get the current state by calling:
## Preview sizes ## Preview sizes
This selector defines how the preview iframe is rendered, and try to emulate This selector defines how the preview iframe is rendered, and try to emulate
different device sizes. The options are hardcoded. The option names map directly different device sizes. The options are hardcoded. The option names map directly
to CSS classes applied to the `.cms-preview` and are as follows: to CSS classes applied to the `.cms-preview` and are as follows:
* _auto_: responsive layout * _auto_: responsive layout
@ -129,8 +129,8 @@ to CSS classes applied to the `.cms-preview` and are as follows:
* _tablet_ * _tablet_
* _mobile_ * _mobile_
You can switch between different types of display sizes programmatically, which You can switch between different types of display sizes programmatically, which
has the benefit of redrawing the related selector and maintaining a consistent has the benefit of redrawing the related selector and maintaining a consistent
internal state: internal state:
```js ```js
@ -145,15 +145,15 @@ You can find out current size by calling:
## Preview modes ## Preview modes
Preview modes map to the modes supported by the _threeColumnCompressor_ layout Preview modes map to the modes supported by the _threeColumnCompressor_ layout
algorithm, see [layout reference](../reference/layout) for more details. You algorithm, see [layout reference](../reference/layout) for more details. You
can change modes by calling: can change modes by calling:
```js ```js
$('.cms-preview').entwine('.ss.preview').changeMode('preview'); $('.cms-preview').entwine('.ss.preview').changeMode('preview');
``` ```
Currently active mode is stored on the `.cms-container` along with related Currently active mode is stored on the `.cms-container` along with related
internal states of the layout. You can reach it by calling: internal states of the layout. You can reach it by calling:
```js ```js
@ -161,10 +161,10 @@ internal states of the layout. You can reach it by calling:
``` ```
<div class="notice" markdown='1'> <div class="notice" markdown='1'>
Caveat: the `.preview-mode-selector` appears twice, once in the preview and Caveat: the `.preview-mode-selector` appears twice, once in the preview and
second time in the CMS actions area as `#preview-mode-dropdown-in-cms`. This is second time in the CMS actions area as `#preview-mode-dropdown-in-cms`. This is
done because the user should still have access to the mode selector even if done because the user should still have access to the mode selector even if
preview is not visible. Currently CMS Actions are a separate area to the preview preview is not visible. Currently CMS Actions are a separate area to the preview
option selectors, even if they try to appear as one horizontal bar. option selectors, even if they try to appear as one horizontal bar.
</div> </div>

View File

@ -2,7 +2,7 @@
## Introduction ## Introduction
A lot can be achieved in SilverStripe by adding properties and form fields A lot can be achieved in SilverStripe by adding properties and form fields
to your own page types (via `[api:SiteTree->getCMSFields()]`), as well as creating to your own page types (via `[api:SiteTree->getCMSFields()]`), as well as creating
your own data management interfaces through `[api:ModelAdmin]`. But sometimes your own data management interfaces through `[api:ModelAdmin]`. But sometimes
you'll want to go deeper and tailor the underlying interface to your needs as well. you'll want to go deeper and tailor the underlying interface to your needs as well.
@ -22,7 +22,7 @@ While SilverStripe is intended to work with JavaScript only,
we're following the principles of "[Progressive Enhancement](http://en.wikipedia.org/wiki/Progressive_enhancement)" we're following the principles of "[Progressive Enhancement](http://en.wikipedia.org/wiki/Progressive_enhancement)"
where feasible, relying on a comparatively light layer of JavaScript to enhance where feasible, relying on a comparatively light layer of JavaScript to enhance
forms and markup generated on the server. This allows seamless customization of forms and markup generated on the server. This allows seamless customization of
aspects like form fields. We're explaining this philosophy in more detail aspects like form fields. We're explaining this philosophy in more detail
on our [blog](http://www.silverstripe.org/the-3-0-ui-a-better-framework-for-your-ideas/)). on our [blog](http://www.silverstripe.org/the-3-0-ui-a-better-framework-for-your-ideas/)).
All CSS in the CMS UI is written in the [SCSS language extensions](http://sass-lang.com/) All CSS in the CMS UI is written in the [SCSS language extensions](http://sass-lang.com/)
@ -30,7 +30,7 @@ and the [Compass framework](http://compass-style.org/), which helps
us maintain expressive and concise style declarations. The files are located in `framework/admin/scss` us maintain expressive and concise style declarations. The files are located in `framework/admin/scss`
(and if you have the `cms` module installed, in `cms/scss`), and are compiled to a `css` folder on the (and if you have the `cms` module installed, in `cms/scss`), and are compiled to a `css` folder on the
same directory path. Changes to the SCSS files can be automatically converted by installing same directory path. Changes to the SCSS files can be automatically converted by installing
the ["compass" module](https://github.com/silverstripe-labs/silverstripe-compass) for SilverStripe, the ["compass" module](https://github.com/silverstripe-labs/silverstripe-compass) for SilverStripe,
although [installing the compass framework](http://compass-style.org/install/) directly works as well. although [installing the compass framework](http://compass-style.org/install/) directly works as well.
Each file describes its purpose at the top of the declarations. Note that you can write Each file describes its purpose at the top of the declarations. Note that you can write
plain CSS without SCSS for your custom CMS interfaces as well, we just mandate SCSS for core usage. plain CSS without SCSS for your custom CMS interfaces as well, we just mandate SCSS for core usage.
@ -44,7 +44,7 @@ As there's a whole lot of CSS driving the CMS, we have certain best practives ar
(which might change later on). A more structural name could be `cms-menu` (or `cms-tools-menu` for a more specific version) (which might change later on). A more structural name could be `cms-menu` (or `cms-tools-menu` for a more specific version)
* Class naming: Use the `cms-` class prefix for major components in the cms interface, * Class naming: Use the `cms-` class prefix for major components in the cms interface,
and the `ss-ui-` prefix for extensions to jQuery UI. Don't use the `ui-` class prefix, its reserved for jQuery UI built-in styles. and the `ss-ui-` prefix for extensions to jQuery UI. Don't use the `ui-` class prefix, its reserved for jQuery UI built-in styles.
* Use jQuery UI's built-in styles where possible, e.g. `ui-widget` for a generic container, or `ui-state-highlight` * Use jQuery UI's built-in styles where possible, e.g. `ui-widget` for a generic container, or `ui-state-highlight`
to highlight a specific component. See the [jQuery UI Theming API](http://jqueryui.com/docs/Theming/API) for a full list. to highlight a specific component. See the [jQuery UI Theming API](http://jqueryui.com/docs/Theming/API) for a full list.
See our [system requirements](../installation/server-requirements) for a list of supported browsers. See our [system requirements](../installation/server-requirements) for a list of supported browsers.
@ -67,7 +67,7 @@ We can use this to create a different base template with `LeftAndMain.ss`
(which corresponds to the `LeftAndMain` PHP controller class). (which corresponds to the `LeftAndMain` PHP controller class).
In case you want to retain the main CMS structure (which is recommended), In case you want to retain the main CMS structure (which is recommended),
just create your own "Content" template (e.g. `MyCMSController_Content.ss`), just create your own "Content" template (e.g. `MyCMSController_Content.ss`),
which is in charge of rendering the main content area apart from the CMS menu. which is in charge of rendering the main content area apart from the CMS menu.
Depending on the complexity of your layout, you'll also need to overload the Depending on the complexity of your layout, you'll also need to overload the
"EditForm" template (e.g. `MyCMSController_EditForm.ss`), e.g. to implement "EditForm" template (e.g. `MyCMSController_EditForm.ss`), e.g. to implement
@ -77,9 +77,9 @@ This requires manual assignment of the template to your form instance, see `[api
Often its useful to have a "tools" panel in between the menu and your content, Often its useful to have a "tools" panel in between the menu and your content,
usually occupied by a search form or navigational helper. usually occupied by a search form or navigational helper.
In this case, you can either overload the full base template as described above. In this case, you can either overload the full base template as described above.
To avoid duplicating all this template code, you can also use the special `[api:LeftAndMain->Tools()]` and To avoid duplicating all this template code, you can also use the special `[api:LeftAndMain->Tools()]` and
`[api:LeftAndMain->EditFormTools()]` methods available in `LeftAndMain`. `[api:LeftAndMain->EditFormTools()]` methods available in `LeftAndMain`.
These placeholders are populated by auto-detected templates, These placeholders are populated by auto-detected templates,
with the naming convention of "<controller classname>_Tools.ss" and "<controller classname>_EditFormTools.ss". with the naming convention of "<controller classname>_Tools.ss" and "<controller classname>_EditFormTools.ss".
So to add or "subclass" a tools panel, simply create this file and it's automatically picked up. So to add or "subclass" a tools panel, simply create this file and it's automatically picked up.
@ -101,7 +101,7 @@ e.g. after saving a record (which requires a form refresh), or switching the sec
Depending on where in the DOM hierarchy you want to use a form, Depending on where in the DOM hierarchy you want to use a form,
custom templates and additional CSS classes might be required for correct operation. custom templates and additional CSS classes might be required for correct operation.
For example, the "EditForm" has specific view and logic JavaScript behaviour For example, the "EditForm" has specific view and logic JavaScript behaviour
which can be enabled via adding the "cms-edit-form" class. which can be enabled via adding the "cms-edit-form" class.
In order to set the correct layout classes, we also need a custom template. In order to set the correct layout classes, we also need a custom template.
To obey the inheritance chain, we use `$this->getTemplatesWithSuffix('_EditForm')` for To obey the inheritance chain, we use `$this->getTemplatesWithSuffix('_EditForm')` for
selecting the most specific template (so `MyAdmin_EditForm.ss`, if it exists). selecting the most specific template (so `MyAdmin_EditForm.ss`, if it exists).
@ -115,7 +115,7 @@ Basic example form in a CMS controller subclass:
class MyAdmin extends LeftAndMain { class MyAdmin extends LeftAndMain {
function getEditForm() { function getEditForm() {
return CMSForm::create( return CMSForm::create(
$this, $this,
'EditForm', 'EditForm',
new FieldSet( new FieldSet(
TabSet::create( TabSet::create(
@ -139,14 +139,14 @@ Basic example form in a CMS controller subclass:
} }
} }
Note: Usually you don't need to worry about these settings, Note: Usually you don't need to worry about these settings,
and will simply call `parent::getEditForm()` to modify an existing, and will simply call `parent::getEditForm()` to modify an existing,
correctly configured form. correctly configured form.
## JavaScript through jQuery.entwine ## JavaScript through jQuery.entwine
[jQuery.entwine](https://github.com/hafriedlander/jquery.entwine) is a thirdparty library [jQuery.entwine](https://github.com/hafriedlander/jquery.entwine) is a thirdparty library
which allows us to attach behaviour to DOM elements in a flexible and structured mannger. which allows us to attach behaviour to DOM elements in a flexible and structured mannger.
It replaces the `behaviour.js` library used in previous versions of the CMS interface. It replaces the `behaviour.js` library used in previous versions of the CMS interface.
See [Topics: JavaScript](../topics/javascript) for more information on how to use it. See [Topics: JavaScript](../topics/javascript) for more information on how to use it.
In the CMS interface, all entwine rules should be placed in the "ss" entwine namespace. In the CMS interface, all entwine rules should be placed in the "ss" entwine namespace.
@ -154,13 +154,13 @@ If you want to call methods defined within these rules outside of entwine logic,
you have to use this namespace, e.g. `$('.cms-menu').entwine('ss').collapse()`. you have to use this namespace, e.g. `$('.cms-menu').entwine('ss').collapse()`.
Note that only functionality that is custom to the CMS application needs to be built Note that only functionality that is custom to the CMS application needs to be built
in jQuery.entwine, we're trying to reuse library code wherever possible. in jQuery.entwine, we're trying to reuse library code wherever possible.
The most prominent example of this is the usage of [jQuery UI](http://jqueryui.com) for The most prominent example of this is the usage of [jQuery UI](http://jqueryui.com) for
dialogs and buttons. dialogs and buttons.
The CMS includes the jQuery.entwine inspector. Press Ctrl+` ("backtick") to bring down the inspector. The CMS includes the jQuery.entwine inspector. Press Ctrl+` ("backtick") to bring down the inspector.
You can then click on any element in the CMS to see which entwine methods are bound to You can then click on any element in the CMS to see which entwine methods are bound to
any particular element. any particular element.
## JavaScript and CSS dependencies via Requirements and Ajax ## JavaScript and CSS dependencies via Requirements and Ajax
@ -181,23 +181,23 @@ so don't place a rule applying to all form buttons inside `ModelAdmin.js`.
The CMS relies heavily on Ajax-loading of interfaces, so each interface and the JavaScript The CMS relies heavily on Ajax-loading of interfaces, so each interface and the JavaScript
driving it have to assume its underlying DOM structure is appended via an Ajax callback driving it have to assume its underlying DOM structure is appended via an Ajax callback
rather than being available when the browser window first loads. rather than being available when the browser window first loads.
jQuery.entwine is effectively an advanced version of [jQuery.live](http://api.jquery.com/live/) jQuery.entwine is effectively an advanced version of [jQuery.live](http://api.jquery.com/live/)
and [jQuery.delegate](http://api.jquery.com/delegate/), so takes care of dynamic event binding. and [jQuery.delegate](http://api.jquery.com/delegate/), so takes care of dynamic event binding.
Most interfaces will require their own JavaScript and CSS files, so the Ajax loading has Most interfaces will require their own JavaScript and CSS files, so the Ajax loading has
to ensure they're loaded unless already present. A custom-built library called to ensure they're loaded unless already present. A custom-built library called
`jQuery.ondemand` (located in `framework/thirdparty`) takes care of this transparently - `jQuery.ondemand` (located in `framework/thirdparty`) takes care of this transparently -
so as a developer just declare your dependencies through the `[api:Requirements]` API. so as a developer just declare your dependencies through the `[api:Requirements]` API.
## Ajax Loading and Browser History ## Ajax Loading and Browser History
SilverStripe uses the HTML5 browser history to modify the URL without a complete window refresh, SilverStripe uses the HTML5 browser history to modify the URL without a complete window refresh,
and load its UI via Ajax by hooking into browser navigation events (through the and load its UI via Ajax by hooking into browser navigation events (through the
[history.js](https://github.com/balupton/History.js/) wrapper library). [history.js](https://github.com/balupton/History.js/) wrapper library).
This technique has an impact on how any Ajax load needs to happen: This technique has an impact on how any Ajax load needs to happen:
In order to support browser history (and change the URL state), In order to support browser history (and change the URL state),
a CMS developer needs to fire a navigation event rather than invoking the Ajax call directly. a CMS developer needs to fire a navigation event rather than invoking the Ajax call directly.
The main point of contact here is `$('.cms-container').loadPanel(<url>, <title>, <data>)` The main point of contact here is `$('.cms-container').loadPanel(<url>, <title>, <data>)`
in `LeftAndMain.js`. The `data` object can contain additional state which is required in `LeftAndMain.js`. The `data` object can contain additional state which is required
@ -352,7 +352,7 @@ Note: To avoid double processing, the first response body is usually empty.
By loading mostly HTML responses, we don't have an easy way to communicate By loading mostly HTML responses, we don't have an easy way to communicate
information which can't be directly contained in the produced HTML. information which can't be directly contained in the produced HTML.
For example, the currently used controller class might've changed due to a "redirect", For example, the currently used controller class might've changed due to a "redirect",
which affects the currently active menu entry. We're using HTTP response headers to contain this data which affects the currently active menu entry. We're using HTTP response headers to contain this data
without affecting the response body. without affecting the response body.
@ -388,7 +388,7 @@ SilverStripe automatically applies a [jQuery UI button style](http://jqueryui.co
to all elements with the class `.ss-ui-button`. We've extended the jQuery UI widget a bit to all elements with the class `.ss-ui-button`. We've extended the jQuery UI widget a bit
to support defining icons via HTML5 data attributes (see `ssui.core.js`). to support defining icons via HTML5 data attributes (see `ssui.core.js`).
These icon identifiers relate to icon files in `framework/admin/images/btn-icons`, These icon identifiers relate to icon files in `framework/admin/images/btn-icons`,
and are sprited into a single file through SCSS and the Compass framework and are sprited into a single file through SCSS and the Compass framework
(see [tutorial](http://compass-style.org/help/tutorials/spriting/)). (see [tutorial](http://compass-style.org/help/tutorials/spriting/)).
Compass also creates the correct CSS classes to show those sprites via background images Compass also creates the correct CSS classes to show those sprites via background images
(see `framework/admin/scss/_sprites.scss`). (see `framework/admin/scss/_sprites.scss`).
@ -435,7 +435,7 @@ by the [jstree](http://jstree.com) library. It is configured through
HTML5 metadata generated on its container (see the `data-hints` attribute). HTML5 metadata generated on its container (see the `data-hints` attribute).
For more information, see the [Howto: Customize the CMS tree](../howto/customize-cms-tree). For more information, see the [Howto: Customize the CMS tree](../howto/customize-cms-tree).
Note that a similar tree logic is also used for the Note that a similar tree logic is also used for the
form fields to select one or more entries from those hierarchies form fields to select one or more entries from those hierarchies
(`[api:TreeDropdownField]` and `[api:TreeMultiselectField]`). (`[api:TreeDropdownField]` and `[api:TreeMultiselectField]`).
@ -487,7 +487,7 @@ Form template with custom tab navigation (trimmed down):
<% loop Fields %>$FieldHolder<% end_loop %> <% loop Fields %>$FieldHolder<% end_loop %>
</fieldset> </fieldset>
</div> </div>
</form> </form>
Tabset template without tab navigation (e.g. `CMSTabset.ss`) Tabset template without tab navigation (e.g. `CMSTabset.ss`)

View File

@ -0,0 +1,28 @@
title: WYSIWYG Styles
summary: Add custom CSS properties to the rich-text editor.
# WYSIWYG Styles
SilverStripe lets you customize the style of content in the CMS. This is done by setting up a CSS file called
`editor.css` in either your theme or in your `mysite` folder. This is set through
:::php
HtmlEditorConfig::get('cms')->setOption('ContentCSS', project() . '/css/editor.css');
Will load the `mysite/css/editor.css` file.
Any CSS classes within this file will be automatically added to the `WYSIWYG` editors 'style' dropdown. For instance, to
add the color 'red' as an option within the `WYSIWYG` add the following to the `editor.css`
:::css
.red {
color: red;
}
<div class="notice" markdown="1">
After you have defined the `editor.css` make sure you clear your SilverStripe cache for it to take effect.
</div>
## API Documentation
* [api:HtmlEditorConfig]

View File

@ -23,7 +23,7 @@ jQuery allows you to write complex behavior in a couple of lines of JavaScript.
be reused can be custom code without further encapsulation. For example, a button rollover effect doesn't require a full be reused can be custom code without further encapsulation. For example, a button rollover effect doesn't require a full
plugin. See "[How jQuery Works](http://docs.jquery.com/How_jQuery_Works)" for a good introduction. plugin. See "[How jQuery Works](http://docs.jquery.com/How_jQuery_Works)" for a good introduction.
You should write all your custom jQuery code in a closure. You should write all your custom jQuery code in a closure.
:::javascript :::javascript
(function($) { (function($) {
@ -34,7 +34,7 @@ You should write all your custom jQuery code in a closure.
## jQuery Plugins ## jQuery Plugins
A jQuery Plugin is essentially a method call which can act on a collection of DOM elements. It is contained within the A jQuery Plugin is essentially a method call which can act on a collection of DOM elements. It is contained within the
`jQuery.fn` namespace, and attaches itself automatically to all jQuery collections. The basics for are outlined in the `jQuery.fn` namespace, and attaches itself automatically to all jQuery collections. The basics for are outlined in the
official [jQuery Plugin Authoring](http://docs.jquery.com/Plugins/Authoring) documentation. official [jQuery Plugin Authoring](http://docs.jquery.com/Plugins/Authoring) documentation.

View File

@ -59,7 +59,7 @@ Here we initialise the button based on the backend check, and assume that the bu
} }
// ... // ...
} }
## Frontend support ## ## Frontend support ##
As with the *Save* and *Save & publish* buttons, you might want to add some scripted reactions to user actions on the As with the *Save* and *Save & publish* buttons, you might want to add some scripted reactions to user actions on the
@ -116,7 +116,7 @@ extras.
Continuing our example let's add a "constructive" style to our *Clean-up* button. First you need to be able to add Continuing our example let's add a "constructive" style to our *Clean-up* button. First you need to be able to add
custom JS code into the CMS. You can do this by adding a new source file, here custom JS code into the CMS. You can do this by adding a new source file, here
`mysite/javascript/CMSMain.CustomActionsExtension.js`, and requiring it `mysite/javascript/CMSMain.CustomActionsExtension.js`, and requiring it
through a YAML configuration value: `LeftAndMain.extra_requirements_javascript`. through a YAML configuration value: `LeftAndMain.extra_requirements_javascript`.
Set it to 'mysite/javascript/CMSMain.CustomActionsExtension.js'. Set it to 'mysite/javascript/CMSMain.CustomActionsExtension.js'.

View File

@ -14,7 +14,7 @@ at the last position within the field, and expects unescaped HTML content.
->setDescription('More <strong>detailed</strong> help'); ->setDescription('More <strong>detailed</strong> help');
To show the help text as a tooltip instead of inline, To show the help text as a tooltip instead of inline,
add a `.cms-description-tooltip` class. add a `.cms-description-tooltip` class.
:::php :::php
TextField::create('MyText', 'My Text Label') TextField::create('MyText', 'My Text Label')

View File

@ -2,53 +2,53 @@
## Adding a administration panel ## Adding a administration panel
Every time you add a new extension of the `[api:LeftAndMain]` class to the CMS, Every time you add a new extension of the `[api:LeftAndMain]` class to the CMS,
SilverStripe will automatically create a new `[api:CMSMenuItem]` for it SilverStripe will automatically create a new `[api:CMSMenuItem]` for it
The most popular extension of LeftAndMain is a `[api:ModelAdmin]` class, so The most popular extension of LeftAndMain is a `[api:ModelAdmin]` class, so
for a more detailed introduction to creating new `ModelAdmin` interfaces, read for a more detailed introduction to creating new `ModelAdmin` interfaces, read
the [ModelAdmin reference](../reference/modeladmin). the [ModelAdmin reference](../reference/modeladmin).
In this document we'll take the `ProductAdmin` class used in the In this document we'll take the `ProductAdmin` class used in the
[ModelAdmin reference](../reference/modeladmin#setup) and so how we can change [ModelAdmin reference](../reference/modeladmin#setup) and so how we can change
the menu behaviour by using the `$menu_title` and `$menu_icon` statics to the menu behaviour by using the `$menu_title` and `$menu_icon` statics to
provide a custom title and icon. provide a custom title and icon.
### Defining a Custom Icon ### Defining a Custom Icon
First we'll need a custom icon. For this purpose SilverStripe uses 16x16 First we'll need a custom icon. For this purpose SilverStripe uses 16x16
black-and-transparent PNG graphics. In this case we'll place the icon in black-and-transparent PNG graphics. In this case we'll place the icon in
`mysite/images`, but you are free to use any location. `mysite/images`, but you are free to use any location.
:::php :::php
class ProductAdmin extends ModelAdmin { class ProductAdmin extends ModelAdmin {
// ... // ...
private static $menu_icon = 'mysite/images/product-icon.png'; private static $menu_icon = 'mysite/images/product-icon.png';
} }
### Defining a Custom Title ### Defining a Custom Title
The title of menu entries is configured through the `$menu_title` static. The title of menu entries is configured through the `$menu_title` static.
If its not defined, the CMS falls back to using the class name of the If its not defined, the CMS falls back to using the class name of the
controller, removing the "Admin" bit at the end. controller, removing the "Admin" bit at the end.
:::php :::php
class ProductAdmin extends ModelAdmin { class ProductAdmin extends ModelAdmin {
// ... // ...
private static $menu_title = 'My Custom Admin'; private static $menu_title = 'My Custom Admin';
} }
In order to localize the menu title in different languages, use the In order to localize the menu title in different languages, use the
`<classname>.MENUTITLE` entity name, which is automatically created when running `<classname>.MENUTITLE` entity name, which is automatically created when running
the i18n text collection. the i18n text collection.
For more information on language and translations, please refer to the For more information on language and translations, please refer to the
[i18n](../reference/ii8n) docs. [i18n](../reference/ii8n) docs.
## Adding an external link to the menu ## Adding an external link to the menu
On top of your administration windows, the menu can also have external links On top of your administration windows, the menu can also have external links
(e.g. to external reference). In this example, we're going to add a link to (e.g. to external reference). In this example, we're going to add a link to
Google to the menu. Google to the menu.
First, we need to define a `[api:LeftAndMainExtension]` which will contain our First, we need to define a `[api:LeftAndMainExtension]` which will contain our
@ -69,7 +69,7 @@ button configuration.
// the link you want to item to go to // the link you want to item to go to
$link = 'http://google.com'; $link = 'http://google.com';
// priority controls the ordering of the link in the stack. The // priority controls the ordering of the link in the stack. The
// lower the number, the lower in the list // lower the number, the lower in the list
$priority = -2; $priority = -2;
@ -83,14 +83,14 @@ button configuration.
} }
} }
To have the link appear, make sure you add the extension to the `LeftAndMain` To have the link appear, make sure you add the extension to the `LeftAndMain`
class. For more information about configuring extensions see the class. For more information about configuring extensions see the
[DataExtension reference](../reference/dataextension). [DataExtension reference](../reference/dataextension).
:::php :::php
LeftAndMain::add_extension('CustomLeftAndMain') LeftAndMain::add_extension('CustomLeftAndMain')
## Related ## Related
* [How to extend the CMS interface](extend-cms-interface) * [How to extend the CMS interface](extend-cms-interface)

View File

@ -39,11 +39,11 @@ code like this:
... ...
</ul> </ul>
... ...
By applying the proper style sheet, the snippet html above could produce the look of: By applying the proper style sheet, the snippet html above could produce the look of:
![Page Node Screenshot](../_images/tree_node.png "Page Node") ![Page Node Screenshot](../_images/tree_node.png "Page Node")
SiteTree is a `[api:DataObject]` which is versioned by `[api:Versioned]` extension. SiteTree is a `[api:DataObject]` which is versioned by `[api:Versioned]` extension.
Each node can optionally have publication status flags, e.g. "Removed from draft". Each node can optionally have publication status flags, e.g. "Removed from draft".
Each flag has a unique identifier, which is also used as a CSS class for easier styling. Each flag has a unique identifier, which is also used as a CSS class for easier styling.
@ -66,7 +66,7 @@ __Example: using a subclass__
public function getScheduledToPublish(){ public function getScheduledToPublish(){
// return either true or false // return either true or false
} }
public function getStatusFlags($cached = true) { public function getStatusFlags($cached = true) {
$flags = parent::getStatusFlags($cached); $flags = parent::getStatusFlags($cached);
$flags['scheduledtopublish'] = "Scheduled To Publish"; $flags['scheduledtopublish'] = "Scheduled To Publish";

View File

@ -2,12 +2,12 @@
## Introduction ## ## Introduction ##
The CMS interface works just like any other part of your website: It consists of The CMS interface works just like any other part of your website: It consists of
PHP controllers, templates, CSS stylesheets and JavaScript. Because it uses the PHP controllers, templates, CSS stylesheets and JavaScript. Because it uses the
same base elements, it is relatively easy to extend. same base elements, it is relatively easy to extend.
As an example, we're going to add a permanent "bookmarks" link list to popular pages As an example, we're going to add a permanent "bookmarks" link list to popular pages
into the main CMS menu. A page can be bookmarked by a CMS author through a into the main CMS menu. A page can be bookmarked by a CMS author through a
simple checkbox. simple checkbox.
For a deeper introduction to the inner workings of the CMS, please refer to our For a deeper introduction to the inner workings of the CMS, please refer to our
@ -15,8 +15,8 @@ guide on [CMS Architecture](../reference/cms-architecture).
## Overload a CMS template ## ## Overload a CMS template ##
If you place a template with an identical name into your application template If you place a template with an identical name into your application template
directory (usually `mysite/templates/`), it'll take priority over the built-in directory (usually `mysite/templates/`), it'll take priority over the built-in
one. one.
CMS templates are inherited based on their controllers, similar to subclasses of CMS templates are inherited based on their controllers, similar to subclasses of
@ -24,10 +24,10 @@ the common `Page` object (a new PHP class `MyPage` will look for a `MyPage.ss` t
We can use this to create a different base template with `LeftAndMain.ss` We can use this to create a different base template with `LeftAndMain.ss`
(which corresponds to the `LeftAndMain` PHP controller class). (which corresponds to the `LeftAndMain` PHP controller class).
Copy the template markup of the base implementation at `framework/admin/templates/Includes/LeftAndMain_Menu.ss` Copy the template markup of the base implementation at `framework/admin/templates/Includes/LeftAndMain_Menu.ss`
into `mysite/templates/Includes/LeftAndMain_Menu.ss`. It will automatically be picked up by into `mysite/templates/Includes/LeftAndMain_Menu.ss`. It will automatically be picked up by
the CMS logic. Add a new section into the `<ul class="cms-menu-list">` the CMS logic. Add a new section into the `<ul class="cms-menu-list">`
:::ss :::ss
... ...
<ul class="cms-menu-list"> <ul class="cms-menu-list">
@ -40,15 +40,15 @@ the CMS logic. Add a new section into the `<ul class="cms-menu-list">`
</li> </li>
</ul> </ul>
... ...
Refresh the CMS interface with `admin/?flush=all`, and you should see those Refresh the CMS interface with `admin/?flush=all`, and you should see those
hardcoded links underneath the left-hand menu. We'll make these dynamic further down. hardcoded links underneath the left-hand menu. We'll make these dynamic further down.
## Include custom CSS in the CMS ## Include custom CSS in the CMS
In order to show the links a bit separated from the other menu entries, In order to show the links a bit separated from the other menu entries,
we'll add some CSS, and get it to load we'll add some CSS, and get it to load
with the CMS interface. Paste the following content into a new file called with the CMS interface. Paste the following content into a new file called
`mysite/css/BookmarkedPages.css`: `mysite/css/BookmarkedPages.css`:
:::css :::css
@ -64,9 +64,9 @@ Load the new CSS file into the CMS, by setting the `LeftAndMain.extra_requiremen
## Create a "bookmark" flag on pages ## ## Create a "bookmark" flag on pages ##
Now we'll define which pages are actually bookmarked, a flag that is stored in Now we'll define which pages are actually bookmarked, a flag that is stored in
the database. For this we need to decorate the page record with a the database. For this we need to decorate the page record with a
`DataExtension`. Create a new file called `mysite/code/BookmarkedPageExtension.php` `DataExtension`. Create a new file called `mysite/code/BookmarkedPageExtension.php`
and insert the following code. and insert the following code.
:::php :::php
@ -77,7 +77,7 @@ and insert the following code.
private static $db = array( private static $db = array(
'IsBookmarked' => 'Boolean' 'IsBookmarked' => 'Boolean'
); );
public function updateCMSFields(FieldList $fields) { public function updateCMSFields(FieldList $fields) {
$fields->addFieldToTab('Root.Main', $fields->addFieldToTab('Root.Main',
new CheckboxField('IsBookmarked', "Show in CMS bookmarks?") new CheckboxField('IsBookmarked', "Show in CMS bookmarks?")
@ -98,8 +98,8 @@ Refresh the CMS, open a page for editing and you should see the new checkbox.
## Retrieve the list of bookmarks from the database ## Retrieve the list of bookmarks from the database
One piece in the puzzle is still missing: How do we get the list of bookmarked One piece in the puzzle is still missing: How do we get the list of bookmarked
pages from the database into the template we've already created (with hardcoded pages from the database into the template we've already created (with hardcoded
links)? Again, we extend a core class: The main CMS controller called links)? Again, we extend a core class: The main CMS controller called
`LeftAndMain`. `LeftAndMain`.
Add the following code to a new file `mysite/code/BookmarkedLeftAndMainExtension.php`; Add the following code to a new file `mysite/code/BookmarkedLeftAndMainExtension.php`;
@ -113,7 +113,7 @@ Add the following code to a new file `mysite/code/BookmarkedLeftAndMainExtension
return Page::get()->filter("IsBookmarked", 1); return Page::get()->filter("IsBookmarked", 1);
} }
} }
Enable the extension in your [configuration file](/topics/configuration) Enable the extension in your [configuration file](/topics/configuration)
:::yml :::yml
@ -137,50 +137,50 @@ and replace it with the following:
## Extending the CMS actions ## Extending the CMS actions
CMS actions follow a principle similar to the CMS fields: they are built in the CMS actions follow a principle similar to the CMS fields: they are built in the
backend with the help of `FormFields` and `FormActions`, and the frontend is backend with the help of `FormFields` and `FormActions`, and the frontend is
responsible for applying a consistent styling. responsible for applying a consistent styling.
The following conventions apply: The following conventions apply:
* New actions can be added by redefining `getCMSActions`, or adding an extension * New actions can be added by redefining `getCMSActions`, or adding an extension
with `updateCMSActions`. with `updateCMSActions`.
* It is required the actions are contained in a `FieldSet` (`getCMSActions` * It is required the actions are contained in a `FieldSet` (`getCMSActions`
returns this already). returns this already).
* Standalone buttons are created by adding a top-level `FormAction` (no such * Standalone buttons are created by adding a top-level `FormAction` (no such
button is added by default). button is added by default).
* Button groups are created by adding a top-level `CompositeField` with * Button groups are created by adding a top-level `CompositeField` with
`FormActions` in it. `FormActions` in it.
* A `MajorActions` button group is already provided as a default. * A `MajorActions` button group is already provided as a default.
* Drop ups with additional actions that appear as links are created via a * Drop ups with additional actions that appear as links are created via a
`TabSet` and `Tabs` with `FormActions` inside. `TabSet` and `Tabs` with `FormActions` inside.
* A `ActionMenus.MoreOptions` tab is already provided as a default and contains * A `ActionMenus.MoreOptions` tab is already provided as a default and contains
some minor actions. some minor actions.
* You can override the actions completely by providing your own * You can override the actions completely by providing your own
`getAllCMSFields`. `getAllCMSFields`.
Let's walk through a couple of examples of adding new CMS actions in `getCMSActions`. Let's walk through a couple of examples of adding new CMS actions in `getCMSActions`.
First of all we can add a regular standalone button anywhere in the set. Here First of all we can add a regular standalone button anywhere in the set. Here
we are inserting it in the front of all other actions. We could also add a we are inserting it in the front of all other actions. We could also add a
button group (`CompositeField`) in a similar fashion. button group (`CompositeField`) in a similar fashion.
:::php :::php
$fields->unshift(FormAction::create('normal', 'Normal button')); $fields->unshift(FormAction::create('normal', 'Normal button'));
We can affect the existing button group by manipulating the `CompositeField` We can affect the existing button group by manipulating the `CompositeField`
already present in the `FieldList`. already present in the `FieldList`.
:::php :::php
$fields->fieldByName('MajorActions')->push(FormAction::create('grouped', 'New group button')); $fields->fieldByName('MajorActions')->push(FormAction::create('grouped', 'New group button'));
Another option is adding actions into the drop-up - best place for placing Another option is adding actions into the drop-up - best place for placing
infrequently used minor actions. infrequently used minor actions.
:::php :::php
$fields->addFieldToTab('ActionMenus.MoreOptions', FormAction::create('minor', 'Minor action')); $fields->addFieldToTab('ActionMenus.MoreOptions', FormAction::create('minor', 'Minor action'));
We can also easily create new drop-up menus by defining new tabs within the We can also easily create new drop-up menus by defining new tabs within the
`TabSet`. `TabSet`.
:::php :::php
@ -190,12 +190,12 @@ We can also easily create new drop-up menus by defining new tabs within the
Empty tabs will be automatically removed from the `FieldList` to prevent clutter. Empty tabs will be automatically removed from the `FieldList` to prevent clutter.
</div> </div>
New actions will need associated controller handlers to work. You can use a New actions will need associated controller handlers to work. You can use a
`LeftAndMainExtension` to provide one. Refer to [Controller documentation](../topics/controller) `LeftAndMainExtension` to provide one. Refer to [Controller documentation](../topics/controller)
for instructions on setting up handlers. for instructions on setting up handlers.
To make the actions more user-friendly you can also use alternating buttons as To make the actions more user-friendly you can also use alternating buttons as
detailed in the [CMS Alternating Button](../reference/cms-alternating-button) detailed in the [CMS Alternating Button](../reference/cms-alternating-button)
how-to. how-to.
## Summary ## Summary

View File

@ -0,0 +1,22 @@
## Extending existing ModelAdmin
Sometimes you'll work with ModelAdmins from other modules. To customize these interfaces, you can always subclass. But there's
also another tool at your disposal: The `[api:Extension]` API.
:::php
class MyAdminExtension extends Extension {
// ...
public function updateEditForm(&$form) {
$form->Fields()->push(/* ... */)
}
}
Now enable this extension through your `[config.yml](/topics/configuration)` file.
:::yml
MyAdmin:
extensions:
- MyAdminExtension
The following extension points are available: `updateEditForm()`, `updateSearchContext()`,
`updateSearchForm()`, `updateList()`, `updateImportForm`.

View File

@ -0,0 +1,13 @@
title: Customising the Admin Interface
summary: Extend the admin view to provide custom behavior or new features for CMS and admin users.
introduction: The Admin interface can be extended to provide additional functionality to users and custom interfaces for managing data.
The Admin interface is bundled within the SilverStripe Framework but is most commonly used in conjunction with the `CMS`
module. The main class for displaying the interface is a specialized [api:Controller] called [api:LeftAndMain], named
as it is designed around a left hand navigation and a main edit form.
[CHILDREN]
## How to's
[CHILDREN Folder="How_Tos"]

View File

@ -1,106 +0,0 @@
# Typography
## Introduction
SilverStripe lets you customise the style of content in the CMS.
## Usage
This is done by setting up a CSS file called (projectname)/css/typography.css.
You also need to create a file called (projectname)/css/editor.css with the following content:
:::css
/**
* This support file is used to style the WYSIWYG editor in the CMS
*/
@import "typography.css";
body.mceContentBody {
min-height: 200px;
font-size: 62.5%;
}
body.mceContentBody a.broken {
background-color: #FF7B71;
border: 1px red solid;
}
In typography.css you can define styles of any of the tags that will get created by the editor:
* P, BLOCKQUOTE
* H1-6
* UL, OL, LI
* TABLE
* STRONG, EM, U
* A
It's important to realise that this CSS file is included directly into the CMS system, and if you aren't careful, you
can alter the styling of other parts of the interface. While this is novel, it can be dangerous and is probably not
what you're after.
The way around this is to limit all your styling selectors to elements inside something with `class="typography"`. The
other half of this is to put `class="typography"` onto any area in your template where you would like the styling to be
applied.
**WRONG**
:::css
CSS:
h1, h2 {
color: #F77;
}
Template:
<div>
$Content
</div>
**RIGHT**
:::css
CSS:
.typography h1, .typography h2 {
color: #F77;
}
Template:
<div class="typography">
$Content
</div>
If you would to include different styles for different sections of your site, you can use class names the same as the
name of the data fields. This example sets up different paragraph styles for 2 HTML editor fields called Content and
OtherContent:
:::css
.Content.typography p {
font-size: 12px;
}
.OtherContent.typography p {
font-size: 10px;
}
### Removing the typography class
Sometimes, it's not enough to add a class, you also want to remove the typography class. You can use the
`[api:HTMLEditorField]` method setCSSClass.
This example sets another CSS class typographybis:
:::php
public function getCMSFields() {
...
$htmleditor = new HTMLEditorField("ContentBis", "Content Bis");
$htmleditor->setCSSClass('typographybis');
$fields->addFieldToTab("Root.Content", $htmleditor);
...
return $fields;
}
**Note:** This functionality will be available in the version 2.0.2 of the CMS.

View File

@ -1,301 +0,0 @@
# ModelAdmin
## Introduction
Provides a simple way to utilize the SilverStripe CMS UI with your own data models,
and create searchable list and edit views of them, and even providing import and export of your data.
It uses the framework's knowledge about the model to provide sensible defaults,
allowing you to get started in a couple of lines of code,
while still providing a solid base for customization.
The interface is mainly powered by the [GridField](/reference/grid-field) class,
which can also be used in other CMS areas (e.g. to manage a relation on a `SiteTree`
record in the standard CMS interface).
## Setup
Let's assume we want to manage a simple product listing as a sample data model:
A product can have a name, price, and a category.
:::php
class Product extends DataObject {
private static $db = array('Name' => 'Varchar', 'ProductCode' => 'Varchar', 'Price' => 'Currency');
private static $has_one = array('Category' => 'Category');
}
class Category extends DataObject {
private static $db = array('Title' => 'Text');
private static $has_many = array('Products' => 'Product');
}
To create your own `ModelAdmin`, simply extend the base class,
and edit the `$managed_models` property with the list of
data objects you want to scaffold an interface for.
The class can manage multiple models in parallel, if required.
We'll name it `MyAdmin`, but the class name can be anything you want.
:::php
class MyAdmin extends ModelAdmin {
private static $managed_models = array('Product','Category'); // Can manage multiple models
private static $url_segment = 'products'; // Linked as /admin/products/
private static $menu_title = 'My Product Admin';
}
This will automatically add a new menu entry to the CMS, and you're ready to go!
Try opening http://localhost/admin/products/?flush=all.
## Permissions
Each new `ModelAdmin` subclass creates its own [permission code](/reference/permission),
for the example above this would be `CMS_ACCESS_MyAdmin`. Users with access to the CMS
need to have this permission assigned through `admin/security/` in order to gain
access to the controller (unless they're admins).
The `DataObject` API has more granular permission control, which is enforced in ModelAdmin by default.
Available checks are `canEdit()`, `canCreate()`, `canView()` and `canDelete()`.
Models check for administrator permissions by default. For most cases,
less restrictive checks make sense, e.g. checking for general CMS access rights.
:::php
class Category extends DataObject {
// ...
public function canView($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
public function canEdit($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
public function canDelete($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
public function canCreate($member = null) {
return Permission::check('CMS_ACCESS_CMSMain', 'any', $member);
}
## Search Fields
ModelAdmin uses the [SearchContext](/reference/searchcontext) class to provide
a search form, as well as get the searched results. Every DataObject can have its own context,
based on the fields which should be searchable. The class makes a guess at how those fields
should be searched, e.g. showing a checkbox for any boolean fields in your `$db` definition.
To remove, add or modify searchable fields, define a new `[api:DataObject::$searchable_fields]`
static on your model class (see [SearchContext](/reference/searchcontext) docs for details).
:::php
class Product extends DataObject {
// ...
private static $searchable_fields = array(
'Name',
'ProductCode'
// leaves out the 'Price' field, removing it from the search
);
}
For a more sophisticated customization, for example configuring the form fields
for the search form, override `DataObject->getCustomSearchContext()` on your model class.
## Result Columns
The results are shown in a tabular listing, powered by the [GridField](/reference/grid-field),
more specifically the `[api:GridFieldDataColumns]` component.
It looks for a `[api:DataObject::$summary_fields]` static on your model class,
where you can add or remove columns. To change the title, use `[api:DataObject::$field_labels]`.
:::php
class Product extends DataObject {
// ...
private static $field_labels = array(
'Price' => 'Cost' // renames the column to "Cost"
);
private static $summary_fields = array(
'Name',
'Price',
// leaves out the 'ProductCode' field, removing the column
);
}
## Results Customization
The results are retrieved from `[api:SearchContext->getResults()]`,
based on the parameters passed through the search form.
If no search parameters are given, the results will show every record.
Results are a `[api:DataList]` instance, so can be customized by additional
SQL filters, joins, etc (see [datamodel](/topics/datamodel) for more info).
For example, we might want to exclude all products without prices in our sample `MyAdmin` implementation.
:::php
class MyAdmin extends ModelAdmin {
// ...
public function getList() {
$list = parent::getList();
// Always limit by model class, in case you're managing multiple
if($this->modelClass == 'Product') {
$list = $list->exclude('Price', '0');
}
return $list;
}
}
You can also customize the search behaviour directly on your `ModelAdmin` instance.
For example, we might want to have a checkbox which limits search results to expensive products (over $100).
:::php
class MyAdmin extends ModelAdmin {
// ...
public function getSearchContext() {
$context = parent::getSearchContext();
if($this->modelClass == 'Product') {
$context->getFields()->push(new CheckboxField('q[ExpensiveOnly]', 'Only expensive stuff'));
}
return $context;
}
public function getList() {
$list = parent::getList();
$params = $this->request->requestVar('q'); // use this to access search parameters
if($this->modelClass == 'Product' && isset($params['ExpensiveOnly']) && $params['ExpensiveOnly']) {
$list = $list->exclude('Price:LessThan', '100');
}
return $list;
}
}
### GridField Customization
To alter how the results are displayed (via `[api:GridField]`), you can also overload the `getEditForm()` method. For example, to add a new component.
:::php
class MyAdmin extends ModelAdmin {
private static $managed_models = array('Product','Category');
// ...
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
// $gridFieldName is generated from the ModelClass, eg if the Class 'Product'
// is managed by this ModelAdmin, the GridField for it will also be named 'Product'
$gridFieldName = $this->sanitiseClassName($this->modelClass);
$gridField = $form->Fields()->fieldByName($gridFieldName);
$gridField->getConfig()->addComponent(new GridFieldFilterHeader());
return $form;
}
}
The above example will add the component to all `GridField`s (of all managed models). Alternatively we can also add it to only one specific `GridField`:
:::php
class MyAdmin extends ModelAdmin {
private static $managed_models = array('Product','Category');
// ...
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
$gridFieldName = 'Product';
$gridField = $form->Fields()->fieldByName($gridFieldName);
if ($gridField) {
$gridField->getConfig()->addComponent(new GridFieldFilterHeader());
}
return $form;
}
}
## Managing Relationships
Has-one relationships are simply implemented as a `[api:DropdownField]` by default.
Consider replacing it with a more powerful interface in case you have many records
(through customizing `[api:DataObject->getCMSFields]`).
Has-many and many-many relationships are usually handled via the [GridField](/reference/grid-field) class,
more specifically the `[api:GridFieldAddExistingAutocompleter]` and `[api:GridFieldRelationDelete]` components.
They provide a list/detail interface within a single record edited in your ModelAdmin.
The [GridField](/reference/grid-field) docs also explain how to manage
extra relation fields on join tables through its detail forms.
The autocompleter can also search attributes on relations,
based on the search fields defined through `[api:DataObject::searchableFields()]`.
## Permissions
`ModelAdmin` respects the permissions set on the model, through methods on your `DataObject` implementations:
`canView()`, `canEdit()`, `canDelete()`, and `canCreate`.
In terms of access control to the interface itself, every `ModelAdmin` subclass
creates its own "[permission code](/reference/permissions)", which can be assigned
to groups through the `admin/security` management interface. To further limit
permission, either override checks in `ModelAdmin->init()`, or define
more permission codes through the `ModelAdmin::$required_permission_codes` static.
## Data Import
The `ModelAdmin` class provides import of CSV files through the `[api:CsvBulkLoader]` API.
which has support for column mapping, updating existing records,
and identifying relationships - so its a powerful tool to get your data into a SilverStripe database.
By default, each model management interface allows uploading a CSV file
with all columns autodetected. To override with a more specific importer implementation,
use the `[api:ModelAdmin::$model_importers] static.
## Data Export
Export is also available, although at the moment only to the CSV format,
through a button at the end of a results list. You can also export search results.
It is handled through the `[api:GridFieldExportButton]` component.
To customize the exported columns, create a new method called `getExportFields` in your `ModelAdmin`:
:::php
class MyAdmin extends ModelAdmin {
// ...
public function getExportFields() {
return array(
'Name' => 'Name',
'ProductCode' => 'Product Code',
'Category.Title' => 'Category'
);
}
}
Dot syntax support allows you to select a field on a related `has_one` object.
## Extending existing ModelAdmins
Sometimes you'll work with ModelAdmins from other modules, e.g. the product management
of an ecommerce module. To customize this, you can always subclass. But there's
also another tool at your disposal: The `[api:Extension]` API.
:::php
class MyAdminExtension extends Extension {
// ...
public function updateEditForm(&$form) {
$form->Fields()->push(/* ... */)
}
}
Now enable this extension through your `[config.yml](/topics/configuration)` file.
:::yml
MyAdmin:
extensions:
- MyAdminExtension
The following extension points are available: `updateEditForm()`, `updateSearchContext()`,
`updateSearchForm()`, `updateList()`, `updateImportForm`.
## Customizing the interface
Interfaces like `ModelAdmin` can be customized in many ways:
* JavaScript behaviour (e.g. overwritten jQuery.entwine rules)
* CSS styles
* HTML markup through templates
In general, use your `ModelAdmin->init()` method to add additional requirements
through the [Requirements](/reference/requirements) API.
For an introduction how to customize the CMS templates, see our [CMS Architecture Guide](/reference/cms-architecture).
## Related
* [GridField](../reference/grid-field): The UI component powering ModelAdmin
* [Tutorial 5: Dataobject Relationship Management](../tutorials/5-dataobject-relationship-management)
* `[api:SearchContext]`
* [genericviews Module](http://silverstripe.org/generic-views-module)
* [Presentation about ModelAdmin at SupperHappyDevHouse Wellington](http://www.slideshare.net/chillu/modeladmin-in-silverstripe-23)
* [Reference: CMS Architecture](../reference/cms-architecture)
* [Howto: Extend the CMS Interface](../howto/extend-cms-interface)

View File

@ -1,7 +0,0 @@
summary: Extend the admin view to provide custom behavior or new features for CMS users.
[CHILDREN]
## How-to
[CHILDREN How_To]