diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php
index 441f613dc..1b652befc 100644
--- a/admin/code/LeftAndMain.php
+++ b/admin/code/LeftAndMain.php
@@ -339,8 +339,6 @@ class LeftAndMain extends Controller implements PermissionProvider {
if (Director::isDev()) Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/leaktools.js');
- HTMLEditorField::include_js();
-
$leftAndMainIncludes = array_unique(array_merge(
array(
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Layout.js',
diff --git a/docs/en/02_Developer_Guides/03_Forms/Field_types/03_HTMLEditorField.md b/docs/en/02_Developer_Guides/03_Forms/Field_types/03_HTMLEditorField.md
index 3ef4c9421..13b276dde 100644
--- a/docs/en/02_Developer_Guides/03_Forms/Field_types/03_HTMLEditorField.md
+++ b/docs/en/02_Developer_Guides/03_Forms/Field_types/03_HTMLEditorField.md
@@ -31,6 +31,34 @@ functionality. It is usually added through the `[api:DataObject->getCMSFields()]
}
}
+### Specify which configuration to use
+
+By default, a config named 'cms' is used in any new `[api:HTMLEditorField]`.
+
+If you have created your own `[api:HtmlEditorConfig]` and would like to use it,
+you can call `HtmlEditorConfig::set_active('myConfig')` and all subsequently created `[api:HTMLEditorField]`
+will use the configuration with the name 'myConfig'.
+
+You can also specify which `[api:HtmlEditorConfig]` to use on a per field basis via the construct argument.
+This is particularly useful if you need different configurations for multiple `[api:HTMLEditorField]` on the same page or form.
+
+ :::php
+ class MyObject extends DataObject {
+ private static $db = array(
+ 'Content' => 'HTMLText',
+ 'OtherContent' => 'HTMLText'
+ );
+
+ public function getCMSFields() {
+ return new FieldList(array(
+ new HTMLEditorField('Content'),
+ new HTMLEditorField('OtherContent', 'Other content', $this->OtherContent, 'myConfig')
+ ));
+ }
+ }
+
+In the above example, the 'Content' field will use the default 'cms' config while 'OtherContent' will be using 'myConfig'.
+
## Configuration
To keep the JavaScript editor configuration manageable and extensible, we've wrapped it in a PHP class called
@@ -41,7 +69,6 @@ There can be multiple configs, which should always be created / accessed using `
then set the currently active config using `set_active()`.
-By default, a config named 'cms' is used in any field created throughout the CMS interface.
diff --git a/docs/en/04_Changelogs/3.2.0.md b/docs/en/04_Changelogs/3.2.0.md
index a03bb09b0..bb21b2306 100644
--- a/docs/en/04_Changelogs/3.2.0.md
+++ b/docs/en/04_Changelogs/3.2.0.md
@@ -19,6 +19,7 @@
* `FormField::validate` now requires an instance of `Validator`
* Implementation of new "Archive" concept for page removal, which supercedes "delete". Where deletion removed
pages only from draft, archiving removes from both draft and live simultaneously.
+ * Support for multiple HtmlEditorConfigs on the same page.
#### Deprecated classes/methods removed
diff --git a/forms/HtmlEditorConfig.php b/forms/HtmlEditorConfig.php
index 58cb41b08..c0cb7bea3 100644
--- a/forms/HtmlEditorConfig.php
+++ b/forms/HtmlEditorConfig.php
@@ -39,12 +39,20 @@ class HtmlEditorConfig {
self::$current = $identifier;
}
+ /**
+ * Get the currently active configuration identifier
+ * @return String - the active configuration identifier
+ */
+ public static function get_active_identifier() {
+ $identifier = self::$current ? self::$current : 'default';
+ return $identifier;
+ }
/**
* Get the currently active configuration object
* @return HtmlEditorConfig - the active configuration object
*/
public static function get_active() {
- $identifier = self::$current ? self::$current : 'default';
+ $identifier = self::get_active_identifier();
return self::get($identifier);
}
@@ -291,38 +299,81 @@ class HtmlEditorConfig {
}
/**
- * Generate the javascript that will set tinyMCE's configuration to that of the current settings of this object
- * @return string - the javascript
+ * Generate the JavaScript that will set TinyMCE's configuration:
+ * - Parse all configurations into JSON objects to be used in JavaScript
+ * - Includes TinyMCE and configurations using the {@link Requirements} system
*/
- public function generateJS() {
- $config = $this->settings;
+ public static function require_js() {
+ require_once 'tinymce/tiny_mce_gzip.php';
+ $useGzip = Config::inst()->get('HtmlEditorField', 'use_gzip');
- // plugins
+ $configs = array();
+ $externalPlugins = array();
$internalPlugins = array();
- $externalPluginsJS = '';
- foreach($this->plugins as $plugin => $path) {
- if(!$path) {
- $internalPlugins[] = $plugin;
- } else {
- $internalPlugins[] = '-' . $plugin;
- $externalPluginsJS .= sprintf(
- 'tinymce.PluginManager.load("%s", "%s");' . "\n",
- $plugin,
- $path
- );
+ $languages = array();
+
+ foreach (self::$configs as $configID => $config) {
+ $settings = $config->settings;
+ // parse plugins
+ $configPlugins = array();
+ foreach($config->plugins as $plugin => $path) {
+ if(!$path) {
+ $configPlugins[] = $plugin;
+ $internalPlugins[] = $plugin;
+ } else {
+ $configPlugins[] = '-' . $plugin;
+ if ( !array_key_exists($plugin, $externalPlugins) )
+ {
+ $externalPlugins[$plugin] = sprintf(
+ 'tinymce.PluginManager.load("%s", "%s");',
+ $plugin,
+ $path
+ );
+ }
+ }
}
- }
- $config['plugins'] = implode(',', $internalPlugins);
- foreach ($this->buttons as $i=>$buttons) {
- $config['theme_advanced_buttons'.$i] = implode(',', $buttons);
+ // save config plugins settings
+ $settings['plugins'] = implode(',', $configPlugins);
+
+ // buttons
+ foreach ($config->buttons as $i=>$buttons) {
+ $settings['theme_advanced_buttons'.$i] = implode(',', $buttons);
+ }
+
+ // languages
+ $languages[] = $config->getOption('language');
+
+ // save this config settings
+ $configs[$configID] = $settings;
}
- return "
+ // tinyMCE JS requirement
+ if ( $useGzip )
+ {
+ $tag = TinyMCE_Compressor::renderTag(array(
+ 'url' => THIRDPARTY_DIR . '/tinymce/tiny_mce_gzip.php',
+ 'plugins' => implode(',', $internalPlugins),
+ 'themes' => 'advanced',
+ 'languages' => implode(",", array_filter($languages))
+ ), true);
+ preg_match('/src="([^"]*)"/', $tag, $matches);
+ Requirements::javascript(html_entity_decode($matches[1]));
+ }
+ else{
+ Requirements::javascript(MCE_ROOT . 'tiny_mce_src.js');
+ }
+
+ // prepare external plugins js string
+ $externalPlugins = array_values($externalPlugins);
+ $externalPlugins = implode("\n ", $externalPlugins);
+
+ // tinyMCE config object and external plugings
+ $configsJS = "
if((typeof tinyMCE != 'undefined')) {
- $externalPluginsJS
- var ssTinyMceConfig = " . Convert::raw2json($config) . ";
-}
-";
+ $externalPlugins
+ var ssTinyMceConfig = " . Convert::raw2json($configs) . ";
+}";
+ Requirements::customScript($configsJS, 'htmlEditorConfig');
}
}
diff --git a/forms/HtmlEditorField.php b/forms/HtmlEditorField.php
index 7cda6f3a9..11863fed3 100644
--- a/forms/HtmlEditorField.php
+++ b/forms/HtmlEditorField.php
@@ -27,41 +27,31 @@ class HtmlEditorField extends TextareaField {
private static $sanitise_server_side = false;
protected $rows = 30;
-
+
/**
- * Includes the JavaScript neccesary for this field to work using the {@link Requirements} system.
+ * @deprecated since version 3.2
*/
public static function include_js() {
- require_once 'tinymce/tiny_mce_gzip.php';
-
- $configObj = HtmlEditorConfig::get_active();
-
- if(Config::inst()->get('HtmlEditorField', 'use_gzip')) {
- $internalPlugins = array();
- foreach($configObj->getPlugins() as $plugin => $path) if(!$path) $internalPlugins[] = $plugin;
- $tag = TinyMCE_Compressor::renderTag(array(
- 'url' => THIRDPARTY_DIR . '/tinymce/tiny_mce_gzip.php',
- 'plugins' => implode(',', $internalPlugins),
- 'themes' => 'advanced',
- 'languages' => $configObj->getOption('language')
- ), true);
- preg_match('/src="([^"]*)"/', $tag, $matches);
- Requirements::javascript(html_entity_decode($matches[1]));
-
- } else {
- Requirements::javascript(MCE_ROOT . 'tiny_mce_src.js');
- }
-
- Requirements::customScript($configObj->generateJS(), 'htmlEditorConfig');
+ Deprecation::notice('4.0', 'Use HtmlEditorConfig::require_js() instead');
+ HtmlEditorConfig::require_js();
}
+
+ protected $editorConfig = null;
+
/**
+ * Creates a new HTMLEditorField.
* @see TextareaField::__construct()
- */
- public function __construct($name, $title = null, $value = '') {
+ *
+ * @param string $name The internal field name, passed to forms.
+ * @param string $title The human-readable field label.
+ * @param mixed $value The value of the field.
+ * @param string $config HTMLEditorConfig identifier to be used. Default to the active one.
+ */
+ public function __construct($name, $title = null, $value = '', $config = null) {
parent::__construct($name, $title, $value);
- self::include_js();
+ $this->editorConfig = $config ? $config : HtmlEditorConfig::get_active_identifier();
}
public function getAttributes() {
@@ -71,6 +61,7 @@ class HtmlEditorField extends TextareaField {
'tinymce' => 'true',
'style' => 'width: 97%; height: ' . ($this->rows * 16) . 'px', // prevents horizontal scrollbars
'value' => null,
+ 'data-config' => $this->editorConfig
)
);
}
@@ -185,6 +176,8 @@ class HtmlEditorField_Toolbar extends RequestHandler {
Requirements::javascript(THIRDPARTY_DIR . '/jquery-ui/jquery-ui.js');
Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js');
Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/ssui.core.js');
+
+ HtmlEditorConfig::require_js();
Requirements::javascript(FRAMEWORK_DIR ."/javascript/HtmlEditorField.js");
Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css');
diff --git a/javascript/HtmlEditorField.js b/javascript/HtmlEditorField.js
index d72948265..9a0a3fe5f 100644
--- a/javascript/HtmlEditorField.js
+++ b/javascript/HtmlEditorField.js
@@ -332,8 +332,8 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
},
redraw: function() {
- // Using a global config (generated through HTMLEditorConfig PHP logic)
- var config = ssTinyMceConfig, self = this, ed = this.getEditor();
+ // Using textarea config ID from global config object (generated through HTMLEditorConfig PHP logic)
+ var config = ssTinyMceConfig[this.data('config')], self = this, ed = this.getEditor();
ed.init(config);
diff --git a/tests/forms/HtmlEditorConfigTest.php b/tests/forms/HtmlEditorConfigTest.php
index 0c2830798..0c7a1754a 100644
--- a/tests/forms/HtmlEditorConfigTest.php
+++ b/tests/forms/HtmlEditorConfigTest.php
@@ -67,12 +67,26 @@ class HtmlEditorConfigTest extends SapphireTest {
$this->assertNotContains('plugin2', array_keys($plugins));
}
- public function testGenerateJSWritesPlugins() {
- $c = new HtmlEditorConfig();
- $c->enablePlugins(array('plugin1'));
+ public function testRequireJSIncludesAllExternalPlugins() {
+ $c = HtmlEditorConfig::get('config');
+ $c->enablePlugins(array('plugin1' => '/mypath/plugin1'));
$c->enablePlugins(array('plugin2' => '/mypath/plugin2'));
- $this->assertContains('plugin1', $c->generateJS());
- $this->assertContains('tinymce.PluginManager.load("plugin2", "/mypath/plugin2");', $c->generateJS());
+ HtmlEditorConfig::require_js();
+ $js = Requirements::get_custom_scripts();
+
+ $this->assertContains('tinymce.PluginManager.load("plugin1", "/mypath/plugin1");', $js);
+ $this->assertContains('tinymce.PluginManager.load("plugin2", "/mypath/plugin2");', $js);
+ }
+
+ public function testRequireJSIncludesAllConfigs() {
+ $c = HtmlEditorConfig::get('configA');
+ $c = HtmlEditorConfig::get('configB');
+
+ HtmlEditorConfig::require_js();
+ $js = Requirements::get_custom_scripts();
+
+ $this->assertContains('"configA":{', $js);
+ $this->assertContains('"configB":{', $js);
}
}