Adding CKEditor custom editor styles in a theme (Content block/rich text editor)
This tutorial is a continuation of the Adding Redactor custom styles in a theme (content block/rich text editor) tutorial. If you are learning about adding custom editor styles for the first time, please read that tutorial first.
With the release of Concrete CMS version 8, the Redactor rich text editor has been replaced with CKEditor. Existing styles written for Redactor are compatible with CKEditor by converting them into a form CKEditor can use. These Redactor custom editor styles don't take full advantage of what CKEditor can do and are limited to applying classes to basic block-level elements and wrapping text selections in a span and applying classes to it. CKEditor in comparison offers block-level, object, inline, and custom style types.
This tutorial requires Concrete version 8.2 or greater.
CKEditor Style Types
Block
Block styles are applied to block-level elements. They work by targeting a specific block-level element or replacing a selected block-level element. A block style will use one of the following block-level elements: address
, div
, h1
, h2
, h3
, h4
, h5
, h6
, p
, and pre
. Block styles can use one or more elements.
Single versus multiple element block styles
Block styles behave differently if the style contains one element or multiple elements. When more than one block-level element is used in a style, the style is treated as an object style.
When applied to a block-level element that is different from the block style element, the block style element will replace the block-level element. An example would be if your block style element was div
and you selected text within a p
text block, after applying the block style the p
element would be replaced with div
.
Example: single element
array(
'title' => t('Red Border Div'),
'element' => 'div',
'attributes' => array('class' => 'red-border')
)
When this style is applied to a block-level element, that block-level element is replaced with div
and is assigned the class "red-border".
If multiple block style elements are used, there will be no element replacement, instead only elements that match the block style elements are targeted. An example would be if your block style elements were h1
, h2
, and h3
, the custom style would only be available for those three elements. If you selected text in a p
element, that custom style would not be available.
Example: multiple elements
array(
'title' => t('Green H1-H3'),
'element' => array('h1', 'h2', 'h3'),
'attributes' => array('class' => 'green-heading')
)
This style targets h1
, h2
, and h3
elements only and adds the class "green-heading".
Object
Objects styles are applied to selectable non-text objects. An object style will use one of the following elements: a
, embed
, hr
, img
, li
, object
, ol
, table
, td
, tr
, and ul
. They don't replace elements or wrap selected text, instead they just target elements. Object styles can use one or more elements.
Example: single element
array(
'title' => t('Light Grey Zebra Stripe Table'),
'element' => 'table',
'attributes' => array('class' => 'light-grey-zebra-table')
)
This style targets table
elements only and adds the "light-grey-zebra-table" class.
Example: multiple elements
array(
'title' => t('Red Lists'),
'element' => array('ol', 'ul'),
'attributes' => array('class' => 'red-list')
)
This style targets ol
and ul
elements only and adds the "red-list" class.
Inline
Inline styles are applied to inline elements and text selections. They work by targeting specific inline elements or wrapping a text selection in an inline element. An inline style can use inline elements not already defined in other style types. Inline styles can use one or more elements.
Single versus multiple element inline styles
Inline styles behave differently if the style contains one element or multiple elements. When more than one inline element is used in a style, the style is treated as an object style.
When using one style element, after you apply the style to a text selection, the text selection will be wrapped in the inline style element. An example would be if your style element was span
and you selected some text, after applying the custom style the selected text would be wrapped in a span
element.
Example: single element
array(
'title' => t('Yellow Highlight'),
'element' => 'span',
'attributes' => array('class' => 'yellow-highlight')
)
This style will wrap a text selection in a span
element and apply the class "yellow-highlight".
If multiple style elements are used, the selected text will not be wrapped in an inline element, instead only elements that match the inline style elements are targeted. An example would be if your style elements were sup
and sub
, the custom style would only be available for those two elements. If the selected text was in a span
element, that custom style would not be available.
Example: multiple elements
array(
'title' => t('Red Subscript Superscript'),
'element' => array('sub', 'sup'),
'attributes' => array('class' => 'red-sub-sup')
)
This style will target only sub
and sup
elements and will apply the "red-sub-sup" class.
Custom
CKEditor plugins can define special style handlers which can be applied in certain situations. One type of custom handler is used for widgets. Widget styles only accept class attributes, other style definitions will be ignored. An example widget in Concrete is the Enhanced Image plugin.
Example: Enhanced Image custom widget style
array(
'title' => t('Blue Border Circle'),
'type' => 'widget',
'widget' => 'image',
'attributes' => array('class' => 'blue-border img-circle')
)
This custom style targets images added by the Enhanced Image plugin widget.
CKEditor Style Rules
title
The title
rule is the custom style name that will be displayed in the styles drop-down. This rule is required and must be unique.
Example:
array(
'title' => t('Style Name'),
'element' => 'element_name',
'attributes' => array('attribute_name' => 'attribute_value')
)
element
The element
rule is the element(s) that will be targeted, the element to be replaced with, or the element to be wrapped with. Single and multiple elements can be used. When multiple elements are used, they are added as values in an array.
Example: single element
array(
'title' => t('Style Name'),
'element' => 'element_name',
'attributes' => array('attribute_name' => 'attribute_value')
)
Example: multiple elements
array(
'title' => t('Style Name'),
'element' => array('element_name1', 'element_name2'),
'attributes' => array('attribute_name' => 'attribute_value')
)
styles
The styles
rule is used to define inline CSS that will be applied to an element. Multiple CSS declarations can be used, with each declaration added as a key value pair. The CSS property is the key and the CSS value is the value.
Example: single CSS declaration
array(
'title' => t('Style Name'),
'element' => 'element_name',
'styles' => array('css_property' => 'css_value')
)
Example: multiple CSS declarations
array(
'title' => t('Style Name'),
'element' => 'element_name',
'styles' => array(
'css_property1' => 'css_value1',
'css_property2' => 'css_value2',
)
)
attributes
The attributes
rule is used to define element attributes that will be applied to an element. Example element attributes are class, ID, and data attribute, but can be any attribute supported by a specific element. Multiple attributes can be used, with each attribute name and attribute value added as a key value pair. The attribute name is the key and the attribute value is the value.
Example: single attribute
array(
'title' => t('Style Name'),
'element' => 'element_name',
'attributes' => array('attribute_name' => 'attribute_value')
)
Example: multiple attributes
array(
'title' => t('Style Name'),
'element' => 'element_name',
'attributes' => array(
'attribute_name1' => 'attribute_value1',
'attribute_name2' => 'attribute_value2',
)
)
type
The type
rule is used in combination with the widget
rule and tells CKEditor that this is a custom widget style. The value of the type
rule must be set to widget
.
Example:
array(
'title' => t('Style Name'),
'type' => 'widget',
'widget' => 'widget_name',
'attributes' => array('class' => 'class-name')
)
widget
The widget
rule is the name of the widget that the custom widget style will apply to. When using this rule, the type
rule must also be set.
Example:
array(
'title' => t('Style Name'),
'type' => 'widget',
'widget' => 'widget_name',
'attributes' => array('class' => 'class-name')
)
Redactor Backward Compatible Rules
menuClass
The menuClass
rule is used in Redactor custom styles for adding a preview of the style in the Custom Styles drop-down. This rule is optional in Redactor and unused in CKEditor. If not used it can be omitted.
spanClass
The spanClass
rule is used in Redactor custom styles for setting a custom class, or classes, to selected text or text blocks. This rule is required for making custom styles backwards compatible with Redactor. When used with CKEditor, the spanClass
rule is converted to an attributes
class rule.
forceBlock
The forceBlock
rule is used in Redactor custom styles for setting the style type to block-level or inline. This rule is converted into a form CKEditor can use.
- A
forceBlock
rule set to1
will target block-level elements:h1
,h2
,h3
,h4
,h5
,h6
, andp
. - A
forceBlock
set to-1
will wrap the selected text in aspan
tag. - If no
element
is set andforceBlock
is not set, the CKEditor style will be an inline style using aspan
element.
When making themes that are for v8 only or using styles that are just for CKEditor, menuClass
, spanClass
, and forceBlock
are not necessary and can be omitted.
Use Redactor or CKEditor Styles Based on Concrete Version
By checking the Concrete version before defining a theme's editor styles in getThemeEditorClasses()
, you can conditionally load Redactor or CKEditor styles. If the version is 5.7, then Redactor styles will be used, and if version 8, CKEditor styles will be used. This allows you to offer basic Redactor styles and more flexible, powerful CKEditor styles in the same theme without worrying about backwards compatibility and editor specific style rules.
This code allows you to use custom editor styles based on Concrete version:
public function getThemeEditorClasses()
{
// check to see if the compat_is_version_8() function exists
if (!function_exists('compat_is_version_8')) {
// if not, create the compat_is_version_8() function
function compat_is_version_8() {
// return a boolean, true or false, if the interface Concrete\Core\Export\ExportableInterface exists
// - Concrete 5.7 does not have this interface, so it will be false
// - Concrete v8 does have this interface, so it will be true
return interface_exists('\Concrete\Core\Export\ExportableInterface');
}
}
// check the Concrete version
if (compat_is_version_8()) {
// concrete5 v8
// - custom styles for CKEditor
} else {
// concrete5 5.7
// - custom styles for Redactor
}
}
This is an example of loading your custom styles based on the Concrete version and what the default Elemental theme custom styles look like written as CKEditor styles:
public function getThemeEditorClasses()
{
// check to see if the compat_is_version_8() function exists
if (!function_exists('compat_is_version_8')) {
// if not, create the compat_is_version_8() function
function compat_is_version_8() {
// return a boolean, true or false, if the interface Concrete\Core\Export\ExportableInterface exists
// - Concrete 5.7 does not have this interface, so it will be false
// - Concrete v8 does have this interface, so it will be true
return interface_exists('\Concrete\Core\Export\ExportableInterface');
}
}
// check the Concrete version
if (compat_is_version_8()) {
// concrete5 v8
// - custom styles for CKEditor
return array(
array(
'title' => t('Title Thin'),
'element' => array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'),
'attributes' => array('class' => 'title-thin')
),
array(
'title' => t('Title Caps Bold'),
'element' => array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'),
'attributes' => array('class' => 'title-caps-bold')
),
array(
'title' => t('Title Caps'),
'element' => array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'),
'attributes' => array('class' => 'title-caps')
),
array(
'title' => t('Image Caption'),
'element' => 'span',
'attributes' => array('class' => 'image-caption')
),
array(
'title' => t('Standard Button'),
'element' => 'span',
'attributes' => array('class' => 'btn btn-default')
),
array(
'title' => t('Success Button'),
'element' => 'span',
'attributes' => array('class' => 'btn btn-success')
),
array(
'title' => t('Primary Button'),
'element' => 'span',
'attributes' => array('class' => 'btn btn-primary')
),
);
} else {
// concrete5 5.7
// - custom styles for Redactor
return array(
array('title' => t('Title Thin'), 'menuClass' => 'title-thin', 'spanClass' => 'title-thin', 'forceBlock' => 1),
array('title' => t('Title Caps Bold'), 'menuClass' => 'title-caps-bold', 'spanClass' => 'title-caps-bold', 'forceBlock' => 1),
array('title' => t('Title Caps'), 'menuClass' => 'title-caps', 'spanClass' => 'title-caps', 'forceBlock' => 1),
array('title' => t('Image Caption'), 'menuClass' => 'image-caption', 'spanClass' => 'image-caption', 'forceBlock' => '-1'),
array('title' => t('Standard Button'), 'menuClass' => '', 'spanClass' => 'btn btn-default', 'forceBlock' => '-1'),
array('title' => t('Success Button'), 'menuClass' => '', 'spanClass' => 'btn btn-success', 'forceBlock' => '-1'),
array('title' => t('Primary Button'), 'menuClass' => '', 'spanClass' => 'btn btn-primary', 'forceBlock' => '-1')
);
}
}
Style Formatting
When dealing with long custom styles that have nested arrays (arrays within arrays) it is helpful to write the style across multiple lines. This makes the style much easier to read and understand.
Example: default formatting
array('title' => t('Complex Style'), 'element' => array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'), 'styles' => array('font-weight' => 'bold', 'background' => 'lightgreen'), 'attributes' => array('class' => 'my-class', 'data-special' => 'my value'))
Example: multiple line formatting
array(
'title' => t('Complex Style'),
'element' => array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'),
'styles' => array(
'font-weight' => 'bold',
'background' => 'lightgreen'
),
'attributes' => array(
'class' => 'my-class',
'data-special' => 'my value'
)
),
If your theme requires PHP 5.4 or above, then you can write your styles using short array syntax.
Example: array() syntax
array('title' => t('Yellow Highlight'), 'element' => 'span', 'attributes' => array('class' => 'yellow-highlight'))
Example: short array syntax
['title' => t('Yellow Highlight'), 'element' => 'span', 'attributes' => ['class' => 'yellow-highlight']]
Mistakes to Avoid
Whenever possible, and with few exceptions, avoid using inline CSS to style content. Inline CSS has a very high selector specificity and is very hard to override and manage. When you switch themes, attempt to update your theme CSS, or migrate your content - that inline CSS will come along with all your content. Use CSS selectors instead.
An example might be that you use inline CSS to make the background of external link tags yellow because the color stands out and goes well with the rest of your theme colors. In the future you might refresh your site design and use completely different colors, but all your links will still have a yellow background, and now they don't match the rest of your theme. If you had used CSS selectors like class, element, and attribute selectors for styling, you could easily update your link styling across your entire site, instead you are stuck with links with yellow backgrounds forever unless you manually update all your content, write highly specific CSS styles using !important, or write a script to search through your content to remove the inline CSS.