8
0
Просмотр исходного кода

Simplified code of configuration class.

Oskar Wrobel 9 лет назад
Родитель
Сommit
66effffeac
2 измененных файлов с 101 добавлено и 132 удалено
  1. 85 98
      packages/ckeditor5-utils/src/config.js
  2. 16 34
      packages/ckeditor5-utils/tests/config.js

+ 85 - 98
packages/ckeditor5-utils/src/config.js

@@ -5,7 +5,6 @@
 
 
 'use strict';
 'use strict';
 
 
-import isObject from './lib/lodash/isObject.js';
 import isPlainObject from './lib/lodash/isPlainObject.js';
 import isPlainObject from './lib/lodash/isPlainObject.js';
 
 
 /**
 /**
@@ -21,17 +20,20 @@ export default class Config {
 	 * @param {Object} [configurations] The initial configurations to be set.
 	 * @param {Object} [configurations] The initial configurations to be set.
 	 */
 	 */
 	constructor( configurations ) {
 	constructor( configurations ) {
-		if ( configurations ) {
-			this._set( configurations );
-		}
-
 		/**
 		/**
-		 * Store for whole configuration. Configuration is hide it private property to be not accessible
-		 * directly from Config instance to keep consistent API.
+		 * Store for whole configuration.
 		 *
 		 *
 		 * @private
 		 * @private
-		 * @member {ConfigSubset} utils.config#_config
+		 * @member {Object} utils.config#_config
 		 */
 		 */
+		this._config = {};
+
+		/**
+		 * Set initial configuration.
+		 */
+		if ( configurations ) {
+			this._setObjectToTarget( this._config, configurations );
+		}
 	}
 	}
 
 
 	/**
 	/**
@@ -64,27 +66,32 @@ export default class Config {
 	 *			color: 'red',
 	 *			color: 'red',
 	 *		} );
 	 *		} );
 	 *
 	 *
-	 *		config.toolbar.collapsed; // true
-	 *		config.toolbar.color; // 'red'
+	 *		config.get( 'toolbar.collapsed' ); // true
+	 *		config.get( 'toolbar.color' ); // 'red'
 	 *
 	 *
 	 * @param {String|Object} name The configuration name or an object from which take properties as
 	 * @param {String|Object} name The configuration name or an object from which take properties as
 	 * configuration entries. Configuration names are case-insensitive.
 	 * configuration entries. Configuration names are case-insensitive.
-	 * @param {*} [value=null] The configuration value. Used if a name is passed to nameOrConfigurations.
+	 * @param {*} value The configuration value. Used if a name is passed.
 	 */
 	 */
 	set( name, value ) {
 	set( name, value ) {
-		this._set( name, value );
+		this._setToTarget( this._config, name, value );
 	}
 	}
 
 
 	/**
 	/**
 	 * Does exactly the same as {@link utils.Config#set} with one exception - passed configuration extends
 	 * Does exactly the same as {@link utils.Config#set} with one exception - passed configuration extends
 	 * existing one, but does not overwrite already defined values.
 	 * existing one, but does not overwrite already defined values.
 	 *
 	 *
+	 * 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} name The configuration name or an object from which take properties as
 	 * @param {String|Object} name The configuration name or an object from which take properties as
 	 * configuration entries. Configuration names are case-insensitive.
 	 * configuration entries. Configuration names are case-insensitive.
-	 * @param {*} [value=null] The configuration value. Used if a name is passed to nameOrConfigurations.
+	 * @param {*} value The configuration value. Used if a name is passed.
 	 */
 	 */
 	define( name, value ) {
 	define( name, value ) {
-		this._set( name, value, true );
+		const isDefine = true;
+
+		this._setToTarget( this._config, name, value, isDefine );
 	}
 	}
 
 
 	/**
 	/**
@@ -100,128 +107,108 @@ export default class Config {
 	 * @returns {*} The configuration value or `undefined` if the configuration entry was not found.
 	 * @returns {*} The configuration value or `undefined` if the configuration entry was not found.
 	 */
 	 */
 	get( name ) {
 	get( name ) {
-		// The target for this configuration is, for now, this object.
-		let source = this;
-
-		// Whole configuration is stored in private property.
-		if ( !( source instanceof ConfigSubset ) ) {
-			// Configuration is empty.
-			if ( !source._config ) {
-				return;
-			}
-
-			source = source._config;
-		}
-
-		// The configuration name should be split into parts if it has dots. E.g. `resize.width` -> [`resize`, `width`].
-		const parts = name.toLowerCase().split( '.' );
-
-		// Take the name of the configuration out of the parts. E.g. `resize.width` -> `width`.
-		name = parts.pop();
-
-		// Retrieves the source for this configuration recursively.
-		for ( let i = 0; i < parts.length; i++ ) {
-			// The target will always be an instance of Config.
-			if ( !( source[ parts[ i ] ] instanceof ConfigSubset ) ) {
-				source = null;
-				break;
-			}
-
-			source = source[ parts[ i ] ];
-		}
-
-		// Always returns undefined for non existing configuration
-		return source ? source[ name ] : undefined;
+		return this._getFromSource( this._config, name );
 	}
 	}
 
 
 	/**
 	/**
-	 * Converts and saves passed configuration.
+	 * Saves passed configuration to the specified target (nested object).
 	 *
 	 *
-	 * @private
+	 * @param {Object} target level of nested object.
 	 * @param {String|Object} name The configuration name or an object from which take properties as
 	 * @param {String|Object} name The configuration name or an object from which take properties as
 	 * configuration entries. Configuration names are case-insensitive.
 	 * configuration entries. Configuration names are case-insensitive.
-	 * @param {*} [value=null] The configuration value. Used if a name is passed to nameOrConfigurations.
+	 * @param {*} value The configuration value. Used if a name is passed.
 	 * @param {Boolean} [isDefine=false] Define if passed configuration should overwrite existing one.
 	 * @param {Boolean} [isDefine=false] Define if passed configuration should overwrite existing one.
+	 * @private
 	 */
 	 */
-	_set( name, value, isDefine ) {
-		// In case of an object, iterate through it and call set( name, value ) again for each property.
-		if ( isObject( name ) ) {
-			this._setObject( name, isDefine );
+	_setToTarget( target, name, value, isDefine = false ) {
+		// In case of an object, iterate through it and call `_setToTarget` again for each property.
+		if ( isPlainObject( name ) ) {
+			this._setObjectToTarget( target, name, isDefine );
 
 
 			return;
 			return;
 		}
 		}
 
 
-		// The target for this configuration is, for now, this object.
-		let target = this;
-
-		// If we are at the top of the configuration tree, hide configuration in private property
-		// to prevent of getting properties directly from config, to keep consistent API.
-		if ( !( target instanceof ConfigSubset ) ) {
-			if ( !target._config ) {
-				target._config = new ConfigSubset();
-			}
-
-			target = target._config;
-		}
-
-		// The configuration name should be split into parts if it has dots. E.g: `resize.width`.
+		// The configuration name should be split into parts if it has dots. E.g. `resize.width` -> [`resize`, `width`].
 		const parts = name.toLowerCase().split( '.' );
 		const parts = name.toLowerCase().split( '.' );
 
 
-		// Take the name of the configuration out of the parts. E.g. `resize.width` -> `width`
+		// Take the name of the configuration out of the parts. E.g. `resize.width` -> `width`.
 		name = parts.pop();
 		name = parts.pop();
 
 
-		// Retrieves the final target for this configuration recursively.
-		for ( let i = 0; i < parts.length; i++ ) {
-			// The target will always be an instance of Config.
-			if ( !( target[ parts[ i ] ] instanceof ConfigSubset ) ) {
-				target.set( parts[ i ], new ConfigSubset() );
+		// Iterate over parts to check if currently stored configuration has proper structure.
+		for ( let part of parts ) {
+			// If there is no object for specified part then create one.
+			if ( !isPlainObject( target[ part ] ) ) {
+				target[ part ] = {};
 			}
 			}
 
 
-			target = target[ parts[ i ] ];
+			// Nested object becomes a target.
+			target = target[ part ];
 		}
 		}
 
 
-		// Values set as pure objects will be treated as Config subsets.
+		// In case of value is an object.
 		if ( isPlainObject( value ) ) {
 		if ( isPlainObject( value ) ) {
-			// If the target is an instance of Config (a deep config subset).
-			if ( target[ name ] instanceof ConfigSubset ) {
-				// Amend the target with the value, instead of replacing it.
-				target[ name ]._setObject( value, isDefine );
-
-				return;
+			// We take care of proper config structure.
+			if ( !isPlainObject( target[ name ] ) ) {
+				target[ name ] = {};
 			}
 			}
 
 
-			value = new ConfigSubset( value );
+			target = target[ name ];
+
+			// And iterate through this object calling `_setToTarget` again for each property.
+			this._setObjectToTarget( target, value, isDefine );
+
+			return;
 		}
 		}
 
 
-		// Do nothing if there is already defined configuration for this name
-		// and configuration is set as default.
+		// Do nothing if we are defining configuration for non empty name.
 		if ( isDefine && typeof target[ name ] != 'undefined' ) {
 		if ( isDefine && typeof target[ name ] != 'undefined' ) {
 			return;
 			return;
 		}
 		}
 
 
-		// Values will never be undefined.
-		if ( typeof value == 'undefined' ) {
-			value = null;
+		target[ name ] = value;
+	}
+
+	/**
+	 * Get specified configuration from specified source (nested object).
+	 *
+	 * @param {Object} source level of nested object.
+	 * @param {String} name The configuration name. Configuration names are case-insensitive.
+	 * @returns {*} The configuration value or `undefined` if the configuration entry was not found.
+     * @private
+     */
+	_getFromSource( source, name ) {
+		// The configuration name should be split into parts if it has dots. E.g. `resize.width` -> [`resize`, `width`].
+		const parts = name.toLowerCase().split( '.' );
+
+		// Take the name of the configuration out of the parts. E.g. `resize.width` -> `width`.
+		name = parts.pop();
+
+		// Iterate over parts to check if currently stored configuration has proper structure.
+		for ( let part of parts ) {
+			if ( !isPlainObject( source[ part ] ) ) {
+				source = null;
+				break;
+			}
+
+			// Nested object becomes a source.
+			source = source[ part ];
 		}
 		}
 
 
-		target[ name ] = value;
+		// Always returns undefined for non existing configuration
+		return source ? source[ name ] : undefined;
 	}
 	}
 
 
 	/**
 	/**
-	 * Iterate through passed object and call set method with object key and value for each property.
+	 * Iterate through passed object and call `_setToTarget` method with object key and value for each property.
 	 *
 	 *
-	 * @private
+	 * @param {Object} target level of nested object.
 	 * @param {Object} configuration Configuration data set
 	 * @param {Object} configuration Configuration data set
-	 * @param {Boolean} isDefine Defines if passed configuration is default configuration or not
+	 * @param {Boolean} [isDefine] Defines if passed configuration is default configuration or not
+	 * @private
 	 */
 	 */
-	_setObject( configuration, isDefine ) {
+	_setObjectToTarget( target, configuration, isDefine ) {
 		Object.keys( configuration ).forEach( ( key ) => {
 		Object.keys( configuration ).forEach( ( key ) => {
-			this._set( key, configuration[ key ], isDefine );
+			this._setToTarget( target, key, configuration[ key ], isDefine );
 		}, this );
 		}, this );
 	}
 	}
 }
 }
-
-/**
- * Helper class to recognize if current configuration is nested or the top.
- */
-class ConfigSubset extends Config {}

+ 16 - 34
packages/ckeditor5-utils/tests/config.js

@@ -28,7 +28,7 @@ describe( 'constructor', () => {
 	it( 'should set configurations', () => {
 	it( 'should set configurations', () => {
 		expect( config.get( 'creator' ) ).to.equal( 'inline' );
 		expect( config.get( 'creator' ) ).to.equal( 'inline' );
 		expect( config.get( 'language' ) ).to.equal( 'pl' );
 		expect( config.get( 'language' ) ).to.equal( 'pl' );
-		expect( config.get( 'resize' ) ).to.eql( {
+		expect( config.get( 'resize' ) ).to.deep.equal( {
 			minheight: 300,
 			minheight: 300,
 			maxheight: 800,
 			maxheight: 800,
 			icon: {
 			icon: {
@@ -45,11 +45,6 @@ describe( 'constructor', () => {
 } );
 } );
 
 
 describe( 'set', () => {
 describe( 'set', () => {
-	it( 'should create Config instances for objects', () => {
-		expect( config.get( 'resize' ) ).to.be.an.instanceof( Config );
-		expect( config.get( 'resize.icon' ) ).to.be.an.instanceof( Config );
-	} );
-
 	it( 'should set configurations when passing objects', () => {
 	it( 'should set configurations when passing objects', () => {
 		config.set( {
 		config.set( {
 			option1: 1,
 			option1: 1,
@@ -82,7 +77,7 @@ describe( 'set', () => {
 			prop: 1
 			prop: 1
 		} );
 		} );
 
 
-		expect( config.get( 'test' ) ).to.be.an.instanceof( Config );
+		expect( config.get( 'test' ) ).to.be.an( 'object' );
 		expect( config.get( 'test.prop' ) ).to.equal( 1 );
 		expect( config.get( 'test.prop' ) ).to.equal( 1 );
 	} );
 	} );
 
 
@@ -94,7 +89,7 @@ describe( 'set', () => {
 			}
 			}
 		} );
 		} );
 
 
-		expect( config.get( 'test' ) ).to.be.an.instanceof( Config );
+		expect( config.get( 'test' ) ).to.be.an( 'object' );
 		expect( config.get( 'test.prop' ) ).to.equal( 1 );
 		expect( config.get( 'test.prop' ) ).to.equal( 1 );
 	} );
 	} );
 
 
@@ -102,8 +97,8 @@ describe( 'set', () => {
 		config.set( 'test.prop', 1 );
 		config.set( 'test.prop', 1 );
 		config.set( 'test.prop.value', 1 );
 		config.set( 'test.prop.value', 1 );
 
 
-		expect( config.get( 'test' ) ).to.be.an.instanceof( Config );
-		expect( config.get( 'test.prop' ) ).to.be.an.instanceof( Config );
+		expect( config.get( 'test' ) ).to.be.an( 'object' );
+		expect( config.get( 'test.prop' ) ).to.be.an( 'object' );
 		expect( config.get( 'test.prop.value' ) ).to.equal( 1 );
 		expect( config.get( 'test.prop.value' ) ).to.equal( 1 );
 	} );
 	} );
 
 
@@ -119,7 +114,7 @@ describe( 'set', () => {
 			}
 			}
 		} );
 		} );
 
 
-		expect( config.get( 'resize' ) ).to.be.eql( {
+		expect( config.get( 'resize' ) ).to.be.deep.equal( {
 			minheight: 400,		// Overridden
 			minheight: 400,		// Overridden
 			maxheight: 800,		// The same
 			maxheight: 800,		// The same
 			hidden: true,		// Expanded
 			hidden: true,		// Expanded
@@ -140,7 +135,7 @@ describe( 'set', () => {
 			}
 			}
 		} );
 		} );
 
 
-		expect( config.get( 'resize' ) ).to.be.eql( {
+		expect( config.get( 'resize' ) ).to.deep.equal( {
 			minheight: 400,		// Overridden
 			minheight: 400,		// Overridden
 			maxheight: 800,		// The same
 			maxheight: 800,		// The same
 			hidden: true,		// Expanded
 			hidden: true,		// Expanded
@@ -162,12 +157,6 @@ describe( 'set', () => {
 		expect( config.get( 'date' ) ).to.be.an.instanceof( Date );
 		expect( config.get( 'date' ) ).to.be.an.instanceof( Date );
 		expect( config.get( 'instance' ) ).to.be.an.instanceof( SomeClass );
 		expect( config.get( 'instance' ) ).to.be.an.instanceof( SomeClass );
 	} );
 	} );
-
-	it( 'should set `null` for undefined value', () => {
-		config.set( 'test' );
-
-		expect( config.get( 'test' ) ).to.be.null();
-	} );
 } );
 } );
 
 
 describe( 'define', () => {
 describe( 'define', () => {
@@ -203,7 +192,7 @@ describe( 'define', () => {
 		config.define( 'resize.icon', 'some value' );
 		config.define( 'resize.icon', 'some value' );
 
 
 		expect( config.get( 'language' ) ).to.equal( 'pl' );
 		expect( config.get( 'language' ) ).to.equal( 'pl' );
-		expect( config.get( 'resize.icon' ) ).to.be.instanceof( Config );
+		expect( config.get( 'resize.icon' ) ).to.be.an( 'object' );
 		expect( config.get( 'resize.minheight' ) ).to.equal( 300 );
 		expect( config.get( 'resize.minheight' ) ).to.equal( 300 );
 	} );
 	} );
 
 
@@ -219,7 +208,7 @@ describe( 'define', () => {
 			}
 			}
 		} );
 		} );
 
 
-		expect( config.get( 'resize' ) ).to.be.eql( {
+		expect( config.get( 'resize' ) ).to.be.deep.equal( {
 			minheight: 300,		// The same
 			minheight: 300,		// The same
 			maxheight: 800,		// The same
 			maxheight: 800,		// The same
 			hidden: true,		// Expanded
 			hidden: true,		// Expanded
@@ -240,7 +229,7 @@ describe( 'define', () => {
 			}
 			}
 		} );
 		} );
 
 
-		expect( config.get( 'resize' ) ).to.be.eql( {
+		expect( config.get( 'resize' ) ).to.be.deep.equal( {
 			minheight: 300,		// The same
 			minheight: 300,		// The same
 			maxheight: 800,		// The same
 			maxheight: 800,		// The same
 			hidden: true,		// Expanded
 			hidden: true,		// Expanded
@@ -262,12 +251,6 @@ describe( 'define', () => {
 		expect( config.get( 'date' ) ).to.be.an.instanceof( Date );
 		expect( config.get( 'date' ) ).to.be.an.instanceof( Date );
 		expect( config.get( 'instance' ) ).to.be.an.instanceof( SomeClass );
 		expect( config.get( 'instance' ) ).to.be.an.instanceof( SomeClass );
 	} );
 	} );
-
-	it( 'should set `null` for undefined value', () => {
-		config.define( 'test' );
-
-		expect( config.get( 'test' ) ).to.be.null();
-	} );
 } );
 } );
 
 
 describe( 'get', () => {
 describe( 'get', () => {
@@ -280,16 +263,15 @@ describe( 'get', () => {
 		expect( config.get( 'resize.icon.path' ) ).to.equal( 'xyz' );
 		expect( config.get( 'resize.icon.path' ) ).to.equal( 'xyz' );
 	} );
 	} );
 
 
-	it( 'should retrieve a subset of the configuration', () => {
+	it( 'should retrieve a object of the configuration', () => {
 		let resizeConfig = config.get( 'resize' );
 		let resizeConfig = config.get( 'resize' );
 
 
-		expect( resizeConfig.get( 'minheight' ) ).equal( 300 );
-		expect( resizeConfig.get( 'maxheight' ) ).to.equal( 800 );
-		expect( resizeConfig.get( 'icon' ) ).to.be.instanceof( Config );
-
-		let iconConfig = resizeConfig.get( 'icon' );
+		expect( resizeConfig ).to.be.an( 'object' );
+		expect( resizeConfig.minheight ).equal( 300 );
+		expect( resizeConfig.maxheight ).to.equal( 800 );
+		expect( resizeConfig.icon ).to.be.an( 'object' );
 
 
-		expect( iconConfig.get( 'path' ) ).to.equal( 'xyz' );
+		expect( resizeConfig.icon ).to.be.an( 'object' );
 	} );
 	} );
 
 
 	it( 'should retrieve values case-insensitively', () => {
 	it( 'should retrieve values case-insensitively', () => {