Merge pull request #2046 from wilr/leftandmain_requirements

FIX LeftAndMain::extra_requirements to accept non associative arrays.
This commit is contained in:
Ingo Schommer 2013-06-02 14:35:40 -07:00
commit f5f2864024
6 changed files with 210 additions and 96 deletions

View File

@ -378,29 +378,58 @@ class LeftAndMain extends Controller implements PermissionProvider {
$ie = isset($_SERVER['HTTP_USER_AGENT']) ? strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') : false;
if($ie) {
$version = substr($_SERVER['HTTP_USER_AGENT'], $ie + 5, 3);
if($version == 7) Requirements::css(FRAMEWORK_ADMIN_DIR . '/css/ie7.css');
else if($version == 8) Requirements::css(FRAMEWORK_ADMIN_DIR . '/css/ie8.css');
if($version == 7) {
Requirements::css(FRAMEWORK_ADMIN_DIR . '/css/ie7.css');
} else if($version == 8) {
Requirements::css(FRAMEWORK_ADMIN_DIR . '/css/ie8.css');
}
}
// Custom requirements
$extraJs = $this->stat('extra_requirements_javascript');
if($extraJs) foreach($extraJs as $file => $config) {
if($extraJs) {
foreach($extraJs as $file => $config) {
if(is_numeric($file)) {
$file = $config;
}
Requirements::javascript($file);
}
}
$extraCss = $this->stat('extra_requirements_css');
if($extraCss) foreach($extraCss as $file => $config) {
if($extraCss) {
foreach($extraCss as $file => $config) {
if(is_numeric($file)) {
$file = $config;
$config = array();
}
Requirements::css($file, isset($config['media']) ? $config['media'] : null);
}
}
$extraThemedCss = $this->stat('extra_requirements_themedCss');
if($extraThemedCss) foreach ($extraThemedCss as $file => $config) {
if($extraThemedCss) {
foreach ($extraThemedCss as $file => $config) {
if(is_numeric($file)) {
$file = $config;
$config = array();
}
Requirements::themedCSS($file, isset($config['media']) ? $config['media'] : null);
}
}
$dummy = null;
$this->extend('init', $dummy);
// The user's theme shouldn't affect the CMS, if, for example, they have replaced TableListField.ss or Form.ss.
// The user's theme shouldn't affect the CMS, if, for example, they have
// replaced TableListField.ss or Form.ss.
Config::inst()->update('SSViewer', 'theme_enabled', false);
}

View File

@ -1,6 +1,7 @@
<?php
/**
* @package cms
* @package framework
* @subpackage tests
*/
class LeftAndMainTest extends FunctionalTest {
@ -9,12 +10,47 @@ class LeftAndMainTest extends FunctionalTest {
protected $extraDataObjects = array('LeftAndMainTest_Object');
protected $backupCss, $backupJs, $backupCombined;
public function setUp() {
parent::setUp();
// @todo fix controller stack problems and re-activate
//$this->autoFollowRedirection = false;
CMSMenu::populate_menu();
$this->backupCss = Config::inst()->get('LeftAndMain', 'extra_requirements_css');
$this->backupJs = Config::inst()->get('LeftAndMain', 'extra_requirements_javascript');
$this->backupCombined = Requirements::get_combined_files_enabled();
Config::inst()->update('LeftAndMain', 'extra_requirements_css', array(
FRAMEWORK_DIR . '/tests/assets/LeftAndMainTest.css'
));
Config::inst()->update('LeftAndMain', 'extra_requirements_javascript', array(
FRAMEWORK_DIR . '/tests/assets/LeftAndMainTest.js'
));
Requirements::set_combined_files_enabled(false);
}
public function tearDown() {
parent::tearDown();
Config::inst()->update('LeftAndMain', 'extra_requirements_css', $this->backupCss);
Config::inst()->update('LeftAndMain', 'extra_requirements_javascript', $this->backupJs);
Requirements::set_combined_files_enabled($this->backupCombined);
}
public function testExtraCssAndJavascript() {
$admin = $this->objFromFixture('Member', 'admin');
$this->session()->inst_set('loggedInAs', $admin->ID);
$response = $this->get('LeftAndMainTest_Controller');
$this->assertRegExp('/tests\/assets\/LeftAndMainTest.css/i', $response->getBody(), "body should contain custom css");
$this->assertRegExp('/tests\/assets\/LeftAndMainTest.js/i', $response->getBody(), "body should contain custom js");
}
/**
@ -158,15 +194,22 @@ class LeftAndMainTest extends FunctionalTest {
$this->session()->inst_set('loggedInAs', null);
}
}
/**
* @package framework
* @subpackage tests
*/
class LeftAndMainTest_Controller extends LeftAndMain implements TestOnly {
protected $template = 'BlankPage';
private static $tree_class = 'LeftAndMainTest_Object';
}
/**
* @package framework
* @subpackage tests
*/
class LeftAndMainTest_Object extends DataObject implements TestOnly {
private static $db = array(

View File

@ -2,28 +2,31 @@
## Introduction ##
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 same base elements,
it is relatively easy to extend.
As an example, we're going to add a permanent "bookmarks" bar to popular pages at the bottom of the CMS.
A page can be bookmarked by a CMS author through a simple checkbox.
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
same base elements, it is relatively easy to extend.
As an example, we're going to add a permanent "bookmarks" bar to popular pages
at the bottom of the CMS. A page can be bookmarked by a CMS author through a
simple checkbox.
For a deeper introduction to the inner workings of the CMS, please refer to our
guide on [CMS Architecture](../reference/cms-architecture).
## Overload a CMS 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 one.
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
one.
CMS templates are inherited based on their controllers, similar to subclasses of
the common `Page` object (a new PHP class `MyPage` will look for a `MyPage.ss` template).
We can use this to create a different base template with `LeftAndMain.ss`
(which corresponds to the `LeftAndMain` PHP controller class).
Copy the template markup of the base implementation at `framework/admin/templates/LeftAndMain.ss` into
`mysite/templates/LeftAndMain.ss`. It will automatically be picked up by the CMS logic. Add a new section after the
`$Content` tag:
Copy the template markup of the base implementation at `framework/admin/templates/LeftAndMain.ss`
into `mysite/templates/LeftAndMain.ss`. It will automatically be picked up by
the CMS logic. Add a new section after the `$Content` tag:
:::ss
...
@ -39,21 +42,24 @@ Copy the template markup of the base implementation at `framework/admin/template
</div>
...
Refresh the CMS interface with `admin/?flush=all`, and you should see the new bottom bar with some hardcoded links.
We'll make these dynamic further down.
Refresh the CMS interface with `admin/?flush=all`, and you should see the new
bottom bar with some hardcoded links. We'll make these dynamic further down.
You might have noticed that we didn't write any JavaScript to add our layout manager.
The important piece of information is the `south` class in our new `<div>` structure,
plus the height value in our CSS. It instructs the existing parent layout how to render the element.
This layout manager ([jLayout](http://www.bramstein.com/projects/jlayout/))
allows us to build complex layouts with minimal JavaScript configuration.
You might have noticed that we didn't write any JavaScript to add our layout
manager. The important piece of information is the `south` class in our new
`<div>` structure, plus the height value in our CSS. It instructs the existing
parent layout how to render the element. This layout manager
([jLayout](http://www.bramstein.com/projects/jlayout/)) allows us to build
complex layouts with minimal JavaScript configuration.
See [layout reference](../reference/layout) for more specific information on CMS layouting.
See [layout reference](../reference/layout) for more specific information on
CMS layouting.
## Include custom CSS in the CMS
In order to show the links in one line, we'll add some CSS, and get it to load with the CMS interface.
Paste the following content into a new file called `mysite/css/BookmarkedPages.css`:
In order to show the links in one line, we'll add some CSS, and get it to load
with the CMS interface. Paste the following content into a new file called
`mysite/css/BookmarkedPages.css`:
:::css
.cms-bottom-bar {height: 20px; padding: 5px; background: #C6D7DF;}
@ -67,18 +73,23 @@ Load the new CSS file into the CMS, by setting the `LeftAndMain.extra_requiremen
:::yml
LeftAndMain:
extra_requirements_css:
mysite/css/BookmarkedPages.css:
- mysite/css/BookmarkedPages.css:
## Create a "bookmark" flag on pages ##
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 `DataExtension`.
Create a new file called `mysite/code/BookmarkedPageExtension.php` and insert the following code.
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
`DataExtension`. Create a new file called `mysite/code/BookmarkedPageExtension.php`
and insert the following code.
:::php
<?php
class BookmarkedPageExtension extends DataExtension {
private static $db = array('IsBookmarked' => 'Boolean');
private static $db = array(
'IsBookmarked' => 'Boolean'
);
public function updateCMSFields(FieldList $fields) {
$fields->addFieldToTab('Root.Main',
@ -100,14 +111,17 @@ Refresh the CMS, open a page for editing and you should see the new checkbox.
## Retrieve the list of bookmarks from the database
One piece in the puzzle is still missing: How do we get the list of bookmarked
pages from the datbase into the template we've already created (with hardcoded links)?
Again, we extend a core class: The main CMS controller called `LeftAndMain`.
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
`LeftAndMain`.
Add the following code to a new file `mysite/code/BookmarkedLeftAndMainExtension.php`;
:::php
<?php
class BookmarkedPagesLeftAndMainExtension extends LeftAndMainExtension {
public function BookmarkedPages() {
return Page::get()->filter("IsBookmarked", 1);
}
@ -133,39 +147,51 @@ and replace it with the following:
## Extending the CMS actions
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 responsible for applying a consistent styling.
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
responsible for applying a consistent styling.
The following conventions apply:
* New actions can be added by redefining `getCMSActions`, or adding an extension with `updateCMSActions`.
* It is required the actions are contained in a `FieldSet` (`getCMSActions` returns this already).
* Standalone buttons are created by adding a top-level `FormAction` (no such button is added by default).
* Button groups are created by adding a top-level `CompositeField` with `FormActions` in it.
* New actions can be added by redefining `getCMSActions`, or adding an extension
with `updateCMSActions`.
* It is required the actions are contained in a `FieldSet` (`getCMSActions`
returns this already).
* Standalone buttons are created by adding a top-level `FormAction` (no such
button is added by default).
* Button groups are created by adding a top-level `CompositeField` with
`FormActions` in it.
* A `MajorActions` button group is already provided as a default.
* Drop ups with additional actions that appear as links are created via a `TabSet` and `Tabs` with `FormActions` inside.
* A `ActionMenus.MoreOptions` tab is already provided as a default and contains some minor actions.
* You can override the actions completely by providing your own `getAllCMSFields`.
* Drop ups with additional actions that appear as links are created via a
`TabSet` and `Tabs` with `FormActions` inside.
* A `ActionMenus.MoreOptions` tab is already provided as a default and contains
some minor actions.
* You can override the actions completely by providing your own
`getAllCMSFields`.
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 we are inserting it in the front of all
other actions. We could also add a button group (`CompositeField`) in a similar fashion.
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
button group (`CompositeField`) in a similar fashion.
:::php
$fields->unshift(FormAction::create('normal', 'Normal button'));
We can affect the existing button group by manipulating the `CompositeField` already present in the `FieldList`.
We can affect the existing button group by manipulating the `CompositeField`
already present in the `FieldList`.
:::php
$fields->fieldByName('MajorActions')->push(FormAction::create('grouped', 'New group button'));
Another option is adding actions into the drop-up - best place for placing infrequently used minor actions.
Another option is adding actions into the drop-up - best place for placing
infrequently used minor actions.
:::php
$fields->addFieldToTab('ActionMenus.MoreOptions', FormAction::create('minor', 'Minor action'));
We can also easily create new drop-up menus by defining new tabs within the `TabSet`.
We can also easily create new drop-up menus by defining new tabs within the
`TabSet`.
:::php
$fields->addFieldToTab('ActionMenus.MyDropUp', FormAction::create('minor', 'Minor action in a new drop-up'));
@ -174,15 +200,18 @@ We can also easily create new drop-up menus by defining new tabs within the `Tab
Empty tabs will be automatically removed from the `FieldList` to prevent clutter.
</div>
New actions will need associated controller handlers to work. You can use a `LeftAndMainExtension` to provide one. Refer
to [Controller documentation](../topics/controller) for instructions on setting up handlers.
New actions will need associated controller handlers to work. You can use a
`LeftAndMainExtension` to provide one. Refer to [Controller documentation](../topics/controller)
for instructions on setting up handlers.
To make the actions more user-friendly you can also use alternating buttons as detailed in the [CMS Alternating
Button](../reference/cms-alternating-button) how-to.
To make the actions more user-friendly you can also use alternating buttons as
detailed in the [CMS Alternating Button](../reference/cms-alternating-button)
how-to.
## Summary
In a few lines of code, we've customized the look and feel of the CMS.
While this example is only scratching the surface, it includes most building
blocks and concepts for more complex extensions as well.

View File

@ -2,31 +2,37 @@
## Overview
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. 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 around in the preview.
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.
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
around in the preview.
The root element for preview is `.cms-preview` which maintains the internal states neccessary for rendering within the
entwine properties. It provides function calls for transitioning between these states and has the ability to update the
appearance of the option selectors.
The root element for preview is `.cms-preview` which maintains the internal
states necessary for rendering within the entwine properties. It provides
function calls for transitioning between these states and has the ability to
update the appearance of the option selectors.
In terms of backend support, it relies on `SilverStripeNavigator` to be rendered into the `.cms-edit-form`.
_LeftAndMain_ will automatically take care of generating it as long as the `*_SilverStripeNavigator` template is found -
first segment has to match current _LeftAndMain_-derived class (e.g. `LeftAndMain_SilverStripeNavigator`).
In terms of backend support, it relies on `SilverStripeNavigator` to be rendered
into the `.cms-edit-form`. _LeftAndMain_ will automatically take care of
generating it as long as the `*_SilverStripeNavigator` template is found -
first segment has to match current _LeftAndMain_-derived class (e.g.
`LeftAndMain_SilverStripeNavigator`).
We use `ss.preview` entwine namespace for all preview-related entwines.
<div class="notice" markdown='1'>
Caveat: `SilverStripeNavigator` and `CMSPreviewable` interface currently only 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 feature.
Caveat: `SilverStripeNavigator` and `CMSPreviewable` interface currently only
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
feature.
</div>
## Configuration and Defaults
Like most of the CMS, the preview UI is powered by
[jQuery entwine](https://github.com/hafriedlander/jquery.entwine).
This means its defaults are configured through JavaScript, by setting entwine properties.
[jQuery entwine](https://github.com/hafriedlander/jquery.entwine). This means
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 the following example we configure three aspects:
@ -69,7 +75,7 @@ to the `LeftAndMain.extra_requirements_javascript` [configuration value](/topics
:::yml
LeftAndMain
extra_requirements_javascript:
mysite/javascript/MyLeftAndMain.Preview.js:
- mysite/javascript/MyLeftAndMain.Preview.js:
In order to find out which configuration values are available, the source code
is your best reference at the moment - have a look in `framework/admin/javascript/LeftAndMain.Preview.js`.
@ -78,30 +84,33 @@ To understand how layouts are handled in the CMS UI, have a look at the
## Enabling preview
The frontend decides on the preview being enabled or disabled based on the presnce 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.
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
will remain hidden, and the layout will stay in the _content_ mode.
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. This structure supplies preview options such as state
selector.
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.
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 "blocked" - i.e. displaying the "preview
unavailable" overlay.
If the navigator is not found, the preview appears in the GUI, but is shown as
"blocked" - i.e. displaying the "preview unavailable" overlay.
The preview can be affected by calling `enablePreview` and `disablePreview`. You can check if the preview is active by
inspecting the `IsPreviewEnabled` entwine property.
The preview can be affected by calling `enablePreview` and `disablePreview`. You
can check if the preview is active by inspecting the `IsPreviewEnabled` entwine
property.
## Preview states
States are the site stages: _live_, _stage_ etc. Preview states are picked up from the `SilverStripeNavigator`.
You can invoke the state change by calling:
States are the site stages: _live_, _stage_ etc. Preview states are picked up
from the `SilverStripeNavigator`. You can invoke the state change by calling:
```js
$('.cms-preview').entwine('.ss.preview').changeState('StageLink');
```
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 with the internal state. See `AllowedStates` in `.cms-preview` entwine for the
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
with the internal state. See `AllowedStates` in `.cms-preview` entwine for the
list of supported states.
You can get the current state by calling:
@ -112,16 +121,18 @@ You can get the current state by calling:
## Preview sizes
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 to CSS classes applied to the `.cms-preview` and are as follows:
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
to CSS classes applied to the `.cms-preview` and are as follows:
* _auto_: responsive layout
* _desktop_
* _tablet_
* _mobile_
You can switch between different types of display sizes programmatically, which has the benefit of redrawing the
related selector and maintaining a consistent internal state:
You can switch between different types of display sizes programmatically, which
has the benefit of redrawing the related selector and maintaining a consistent
internal state:
```js
$('.cms-preview').entwine('.ss.preview').changeSize('auto');
@ -135,25 +146,27 @@ You can find out current size by calling:
## Preview modes
Preview modes map to the modes supported by the _threeColumnCompressor_ layout algorithm, see
[layout reference](../reference/layout) for more details. You can change modes by calling:
Preview modes map to the modes supported by the _threeColumnCompressor_ layout
algorithm, see [layout reference](../reference/layout) for more details. You
can change modes by calling:
```js
$('.cms-preview').entwine('.ss.preview').changeMode('preview');
```
Currently active mode is stored on the `.cms-container` along with related internal states of the layout. You can reach
it by calling:
Currently active mode is stored on the `.cms-container` along with related
internal states of the layout. You can reach it by calling:
```js
$('.cms-container').entwine('.ss').getLayoutOptions().mode;
```
<div class="notice" markdown='1'>
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 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 option selectors, even if they try
to appear as one horizontal bar.
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
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
option selectors, even if they try to appear as one horizontal bar.
</div>
## Preview API

View File

View File