Merge pull request #4259 from tractorcow/pulls/3/multi-htmleditor-config

API Support for multiple HTMLEditorConfig per page
This commit is contained in:
Daniel Hensby 2015-06-12 00:53:44 +01:00
commit ad04eeb5c4
7 changed files with 146 additions and 62 deletions

View File

@ -339,8 +339,6 @@ class LeftAndMain extends Controller implements PermissionProvider {
if (Director::isDev()) Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/leaktools.js'); if (Director::isDev()) Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/leaktools.js');
HTMLEditorField::include_js();
$leftAndMainIncludes = array_unique(array_merge( $leftAndMainIncludes = array_unique(array_merge(
array( array(
FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Layout.js', FRAMEWORK_ADMIN_DIR . '/javascript/LeftAndMain.Layout.js',

View File

@ -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 ## Configuration
To keep the JavaScript editor configuration manageable and extensible, we've wrapped it in a PHP class called 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()`. then set the currently active config using `set_active()`.
<div class="info" markdown="1"> <div class="info" markdown="1">
By default, a config named 'cms' is used in any field created throughout the CMS interface.
</div> </div>
<div class="notice" markdown='1'> <div class="notice" markdown='1'>

View File

@ -19,6 +19,7 @@
* `FormField::validate` now requires an instance of `Validator` * `FormField::validate` now requires an instance of `Validator`
* Implementation of new "Archive" concept for page removal, which supercedes "delete". Where deletion removed * 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. pages only from draft, archiving removes from both draft and live simultaneously.
* Support for multiple HtmlEditorConfigs on the same page.
#### Deprecated classes/methods removed #### Deprecated classes/methods removed

View File

@ -39,12 +39,20 @@ class HtmlEditorConfig {
self::$current = $identifier; 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 * Get the currently active configuration object
* @return HtmlEditorConfig - the active configuration object * @return HtmlEditorConfig - the active configuration object
*/ */
public static function get_active() { public static function get_active() {
$identifier = self::$current ? self::$current : 'default'; $identifier = self::get_active_identifier();
return self::get($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 * Generate the JavaScript that will set TinyMCE's configuration:
* @return string - the javascript * - Parse all configurations into JSON objects to be used in JavaScript
* - Includes TinyMCE and configurations using the {@link Requirements} system
*/ */
public function generateJS() { public static function require_js() {
$config = $this->settings; require_once 'tinymce/tiny_mce_gzip.php';
$useGzip = Config::inst()->get('HtmlEditorField', 'use_gzip');
// plugins $configs = array();
$externalPlugins = array();
$internalPlugins = array(); $internalPlugins = array();
$externalPluginsJS = ''; $languages = array();
foreach($this->plugins as $plugin => $path) {
if(!$path) { foreach (self::$configs as $configID => $config) {
$internalPlugins[] = $plugin; $settings = $config->settings;
} else { // parse plugins
$internalPlugins[] = '-' . $plugin; $configPlugins = array();
$externalPluginsJS .= sprintf( foreach($config->plugins as $plugin => $path) {
'tinymce.PluginManager.load("%s", "%s");' . "\n", if(!$path) {
$plugin, $configPlugins[] = $plugin;
$path $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) { // save config plugins settings
$config['theme_advanced_buttons'.$i] = implode(',', $buttons); $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')) { if((typeof tinyMCE != 'undefined')) {
$externalPluginsJS $externalPlugins
var ssTinyMceConfig = " . Convert::raw2json($config) . "; var ssTinyMceConfig = " . Convert::raw2json($configs) . ";
} }";
"; Requirements::customScript($configsJS, 'htmlEditorConfig');
} }
} }

View File

@ -27,41 +27,31 @@ class HtmlEditorField extends TextareaField {
private static $sanitise_server_side = false; private static $sanitise_server_side = false;
protected $rows = 30; 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() { public static function include_js() {
require_once 'tinymce/tiny_mce_gzip.php'; Deprecation::notice('4.0', 'Use HtmlEditorConfig::require_js() instead');
HtmlEditorConfig::require_js();
$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');
} }
protected $editorConfig = null;
/** /**
* Creates a new HTMLEditorField.
* @see TextareaField::__construct() * @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); parent::__construct($name, $title, $value);
self::include_js(); $this->editorConfig = $config ? $config : HtmlEditorConfig::get_active_identifier();
} }
public function getAttributes() { public function getAttributes() {
@ -71,6 +61,7 @@ class HtmlEditorField extends TextareaField {
'tinymce' => 'true', 'tinymce' => 'true',
'style' => 'width: 97%; height: ' . ($this->rows * 16) . 'px', // prevents horizontal scrollbars 'style' => 'width: 97%; height: ' . ($this->rows * 16) . 'px', // prevents horizontal scrollbars
'value' => null, '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-ui/jquery-ui.js');
Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js'); Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js');
Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/ssui.core.js'); Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/ssui.core.js');
HtmlEditorConfig::require_js();
Requirements::javascript(FRAMEWORK_DIR ."/javascript/HtmlEditorField.js"); Requirements::javascript(FRAMEWORK_DIR ."/javascript/HtmlEditorField.js");
Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css'); Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css');

View File

@ -332,8 +332,8 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
}, },
redraw: function() { redraw: function() {
// Using a global config (generated through HTMLEditorConfig PHP logic) // Using textarea config ID from global config object (generated through HTMLEditorConfig PHP logic)
var config = ssTinyMceConfig, self = this, ed = this.getEditor(); var config = ssTinyMceConfig[this.data('config')], self = this, ed = this.getEditor();
ed.init(config); ed.init(config);

View File

@ -67,12 +67,26 @@ class HtmlEditorConfigTest extends SapphireTest {
$this->assertNotContains('plugin2', array_keys($plugins)); $this->assertNotContains('plugin2', array_keys($plugins));
} }
public function testGenerateJSWritesPlugins() { public function testRequireJSIncludesAllExternalPlugins() {
$c = new HtmlEditorConfig(); $c = HtmlEditorConfig::get('config');
$c->enablePlugins(array('plugin1')); $c->enablePlugins(array('plugin1' => '/mypath/plugin1'));
$c->enablePlugins(array('plugin2' => '/mypath/plugin2')); $c->enablePlugins(array('plugin2' => '/mypath/plugin2'));
$this->assertContains('plugin1', $c->generateJS()); HtmlEditorConfig::require_js();
$this->assertContains('tinymce.PluginManager.load("plugin2", "/mypath/plugin2");', $c->generateJS()); $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);
} }
} }