-
Notifications
You must be signed in to change notification settings - Fork 40
t/16: Introduced the configuration API #19
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
/** | ||
* @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
*/ | ||
|
||
'use strict'; | ||
|
||
/** | ||
* Handles a configuration dictionary. | ||
* | ||
* @class Config | ||
* @extends Model | ||
*/ | ||
|
||
CKEDITOR.define( [ 'mvc/model', 'utils' ], function( Model, utils ) { | ||
var Config = Model.extend( { | ||
/** | ||
* Creates an instance of the {@link Config} class. | ||
* | ||
* @param {Object} [configurations] The initial configurations to be set. | ||
* @constructor | ||
*/ | ||
constructor: function Config( configurations ) { | ||
// Call super-constructor. | ||
Model.apply( this ); | ||
|
||
if ( configurations ) { | ||
this.set( configurations ); | ||
} | ||
}, | ||
|
||
/** | ||
* Set configuration values. | ||
* | ||
* It accepts both a name/value pair or an object, which properties and values will be used to set | ||
* configurations. | ||
* | ||
* It also accepts setting a "deep configuration" by using dots in the name. For example, `'resize.width'` sets | ||
* the value for the `width` configuration in the `resize` subset. | ||
* | ||
* config.set( 'width', 500 ); | ||
* config.set( 'toolbar.collapsed', true ); | ||
* | ||
* // Equivalent to: | ||
* config.set( { | ||
* width: 500 | ||
* toolbar: { | ||
* collapsed: true | ||
* } | ||
* } ); | ||
* | ||
* Passing an object as the value will amend the configuration, not replace it. | ||
* | ||
* config.set( 'toolbar', { | ||
* collapsed: true, | ||
* } ); | ||
* | ||
* config.set( 'toolbar', { | ||
* color: 'red', | ||
* } ); | ||
* | ||
* config.toolbar.collapsed; // true | ||
* config.toolbar.color; // 'red' | ||
* | ||
* @param {String|Object} nameOrConfigurations The configuration name or an object from which take properties as | ||
* configuration entries. Configuration names are case-insensitive. | ||
* @param {*} [value=null] The configuration value. Used if a name is passed to nameOrConfigurations. | ||
*/ | ||
set: function( name, value ) { | ||
// Just pass the call to the original set() in case of an object. It'll deal with recursing through the | ||
// object and calling set( name, value ) again for each property. | ||
if ( utils.isObject( name ) ) { | ||
Model.prototype.set.apply( this, arguments ); | ||
|
||
return; | ||
} | ||
|
||
// The target for this configuration is, for now, this object. | ||
//jscs:disable safeContextKeyword | ||
var target = this; | ||
//jscs:enable | ||
|
||
// The configuration name should be split into parts if it has dots. E.g: `resize.width`. | ||
var parts = name.toLowerCase().split( '.' ); | ||
|
||
// Take the name of the configuration out of the parts. E.g. `resize.width` -> `width` | ||
name = parts.pop(); | ||
|
||
// Retrieves the final target for this configuration recursively. | ||
for ( var i = 0; i < parts.length; i++ ) { | ||
// The target will always be an instance of Config. | ||
if ( !( target[ parts[ i ] ] instanceof Config ) ) { | ||
target.set( parts[ i ], new Config() ); | ||
} | ||
|
||
target = target[ parts[ i ] ]; | ||
} | ||
|
||
// Values set as pure objects will be treated as Config subsets. | ||
if ( utils.isPlainObject( value ) ) { | ||
// If the target is an instance of Config (a deep config subset). | ||
if ( target[ name ] instanceof Config ) { | ||
// Amend the target with the value, instead of replacing it. | ||
target[ name ].set( value ); | ||
|
||
return; | ||
} | ||
|
||
value = new Config( value ); | ||
} | ||
|
||
// Values will never be undefined. | ||
if ( typeof value == 'undefined' ) { | ||
value = null; | ||
} | ||
|
||
// Call the original set() on the target. | ||
Model.prototype.set.call( target, name, value ); | ||
}, | ||
|
||
/** | ||
* Gets the value for a configuration entry. | ||
* | ||
* config.get( 'name' ); | ||
* | ||
* Deep configurations can be retrieved by separating each part with a dot. | ||
* | ||
* config.get( 'toolbar.collapsed' ); | ||
* | ||
* @param {String} name The configuration name. Configuration names are case-insensitive. | ||
* @returns {*} The configuration value or `undefined` if the configuration entry was not found. | ||
*/ | ||
get: function( name ) { | ||
// The target for this configuration is, for now, this object. | ||
//jscs:disable safeContextKeyword | ||
var source = this; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why we need this variable? And why it's sometimes called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
You should read the rest of the code... here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. y, it make sense - good naming. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh I see now, my bad. |
||
//jscs:enable | ||
|
||
// The configuration name should be split into parts if it has dots. E.g. `resize.width` -> [`resize`, `width`] | ||
var parts = name.toLowerCase().split( '.' ); | ||
|
||
// Take the name of the configuration from the parts. E.g. `resize.width` -> `width` | ||
name = parts.pop(); | ||
|
||
// Retrieves the source for this configuration recursively. | ||
for ( var i = 0; i < parts.length; i++ ) { | ||
// The target will always be an instance of Config. | ||
if ( !( source[ parts[ i ] ] instanceof Config ) ) { | ||
source = null; | ||
break; | ||
} | ||
|
||
source = source[ parts[ i ] ]; | ||
} | ||
|
||
// Try to retrieve it from the source object. | ||
if ( source && ( typeof source[ name ] != 'undefined' ) ) { | ||
return source[ name ]; | ||
} | ||
|
||
// If not found, take it from the definition. | ||
if ( this.definition ) { | ||
return this.definition[ name ]; | ||
} | ||
|
||
return undefined; | ||
}, | ||
|
||
/** | ||
* Defines the name and default value for configurations. It accepts the same parameters as the | ||
* {@link Config#set set()} method. | ||
* | ||
* On first call, the {@link Config#definition definition} property is created to hold all defined | ||
* configurations. | ||
* | ||
* This method is supposed to be called by plugin developers to setup plugin's configurations. It would be | ||
* rarely used for other needs. | ||
* | ||
* @param {String|Object} nameOrConfigurations The configuration name or an object from which take properties as | ||
* configuration entries. | ||
* @param {*} [value] The configuration value. Used if a name is passed to nameOrConfigurations. If undefined, | ||
* the configuration is set to `null`. | ||
*/ | ||
define: function( name, value ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not really convinced to the name of this method... Wouldn't it make more sense to name it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm out of context right now so ignore my message if it misses the point. But I remember that defining config properties was aimed at not only defining defaults, but also what configuration options are out there (possible validation). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, that makes sense. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Exactly... that's why it's named "define". |
||
if ( !this.definition ) { | ||
/** | ||
* | ||
* | ||
* @property | ||
* @type {Config} | ||
*/ | ||
this.definition = new Config(); | ||
} | ||
|
||
this.definition.set( name, value ); | ||
} | ||
} ); | ||
|
||
return Config; | ||
} ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/** | ||
* @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
*/ | ||
|
||
'use strict'; | ||
|
||
/** | ||
* Handles a configuration dictionary for an editor instance. | ||
* | ||
* The basic difference between {@link EditorConfig} and {@link Config} is that {@link EditorConfig#get} retrieves | ||
* configurations from {@link CKEDITOR#config} if they are not found. | ||
* | ||
* @class EditorConfig | ||
* @extends Config | ||
*/ | ||
|
||
CKEDITOR.define( [ 'ckeditor', 'config' ], function( CKE, Config ) { | ||
var EditorConfig = Config.extend( { | ||
/** | ||
* Creates an instance of the {@link EditorConfig} class. | ||
* | ||
* @param {Object} [configurations] The initial configurations to be set. | ||
* @constructor | ||
*/ | ||
constructor: function EditorConfig() { | ||
// Call super-constructor. | ||
Config.apply( this, arguments ); | ||
}, | ||
|
||
/** | ||
* @inheritdoc Config#get | ||
*/ | ||
get: function() { | ||
// Try to take it from this editor instance. | ||
var value = Config.prototype.get.apply( this, arguments ); | ||
|
||
// If the configuration is not defined in the instance, try to take it from CKEDITOR.config. | ||
if ( typeof value == 'undefined' ) { | ||
// There is a circular dependency issue here: CKEDITOR -> Editor -> EditorConfig -> CKEDITOR. | ||
// Therefore we need to require() it again here. That's why the parameter was named CKE. | ||
// | ||
// Note additionally that we still keep 'ckeditor' in the dependency list for correctness, to ensure | ||
// that the module is loaded. | ||
|
||
CKE = CKE || CKEDITOR.require( 'ckeditor' ); | ||
value = CKE.config.get.apply( CKE.config, arguments ); | ||
} | ||
|
||
return value; | ||
} | ||
} ); | ||
|
||
return EditorConfig; | ||
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to stress it across API docs that keys are automatically lowercased. I can't see it anywhere in docs ATM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docs are pointing to
get()
to retrieve configurations, which is also case-insensitive so it doesn't make much difference.Ofc it is in any case worth to at least make it notice that the config keys are case-insensitive.
Btw, this is for now documented here:
https://github.com/ckeditor/ckeditor5-design/wiki/Configuration