From a8ace7534194b5e1a636c96eca4607d08726dfeb Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Tue, 9 Jun 2015 12:01:21 +1200 Subject: [PATCH] API Support for multiple HTMLEditorConfig per page --- admin/code/LeftAndMain.php | 2 - .../Field_types/03_HTMLEditorField.md | 29 ++++- docs/en/04_Changelogs/3.2.0.md | 1 + forms/HtmlEditorConfig.php | 103 +++++++++++++----- forms/HtmlEditorField.php | 45 ++++---- javascript/HtmlEditorField.js | 4 +- tests/forms/HtmlEditorConfigTest.php | 24 +++- 7 files changed, 146 insertions(+), 62 deletions(-) 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); } }