|
@@ -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 {}
|
|
|