src/nmsp.js
/*! github.com/ryanfitzer/nmsp/blob/master/LICENSE */
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(factory);
}
else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS
module.exports = factory();
}
else {
// Browser globals
root.nmsp = factory();
}
}(this, function ( exports ) {
var toString = Object.prototype.toString;
/**
* Test if a value is an Object.
*
* @example
* if ( !isObject( someValue ) ) return;
*
* @param value {*} The value to test.
* @returns {Boolean}
*/
function isObject( value ) {
return toString.call( value ) === toString.call( {} );
}
/**
* Assign (recursively) the properties of a source object to
* a destination object. If a `context` object is provided,
* the returned function will use it as the destination to extend.
*
* @example
* var extendWithContext = extend( myAwesomeContext );
* extendWithContext( myAwesomeSource );
* extendWithContext( anotherAwesomeSource );
* // or
* var extender = extend();
* var totesExtended = extender( myTotesDest, myTotesSrc );
*
* @param [context] {Object} The object to use as the returned function's destination
* @returns {Function} The function accepts a `dest` and `src` object as arguments.
*/
function extend( context ) {
return function ( dest, src ) {
if ( context ) {
src = dest;
dest = context;
}
Object.keys( src ).forEach( function ( key ) {
if ( isObject( src[ key ] ) ) {
dest[ key ] = isObject( dest[ key ] ) ? dest[ key ] : {};
dest[ key ] = dest[ key ] || {};
extend( dest[ key ] )( src[ key ] );
}
else {
dest[ key ] = src[ key ];
}
});
return dest;
};
}
/**
* Create an array of elements from a path.
*
* @example
* var myPath = toPath( 'a.b.c.d' );
* // [ 'a', 'b', 'c', 'd' ]
* var myOtherPath = toPath( [ 'a', 'b', 'c', 'd' ] );
* // [ 'a', 'b', 'c', 'd' ]
*
* @private
*
* @param path {String|Array} The path.
* @returns {Array} The path's elements.
*/
function toPath( path ) {
return Array.isArray( path ) ? path : path.split( '.' );
}
/**
* Get the value at a path in a source object. If a `context` object is provided,
* the returned function will use it as the source.
*
* @example
* var atPathWithContext = atPath( myAwesomeContext );
* var myAwesomeValue = atPathWithContext( 'my.awesome.prop' );
* var myEvenMoreAwesomeValue = atPathWithContext( 'my.even.more.awesome.prop' );
* // or
* var getAtDatPath = atPath();
* var gimmeSomeDatValue = getAtThatPath( 'dat.prop.tho', datSrcTho );
*
* @param [context] {Object} The object to use as the returned function's `src` argument.
* @param path {String} The path to search in the object.
* @param src {Object} The object to search.
* @returns {Function} The function accepts a `path` and `src` object as arguments and returns the value found at `path`.
*/
function atPath( context ) {
return function ( path, src ) {
src = context || src;
return toPath( path ).reduce( function ( accum, key ) {
return accum && accum[ key ];
}, src );
};
}
/**
* Create a nested object based on the provided path.
*
* @example
* var objFromPath = fromPath( 'one.two.three` );
* // { one: { two: { three: {} } } }
*
* @param path {String} The path to model the object.
* @returns {Object}
*/
function fromPath( path ) {
var accumulator = {};
toPath( path ).reduce( function ( accum, key ) {
accum[ key ] = {};
return accum[ key ];
}, accumulator );
return accumulator;
}
/**
* Create a plain object that consists of only the enumerable own properties of a source object.
*
* @example
* var myNameSpace = nmsp( { some: { data: {} } } );
* myNameSpace.extend( 'some.other.data', { the: 'data' } );
* var plainObj = myNameSpace.plain();
*
* @param store {Object} The object to return as a plain object.
* @returns {Object}
*/
function plain( store ) {
return function( src ) {
src = store || src;
// @support: IE and Nashorn do not not support Object.assign();
return Object.keys( src ).reduce( function ( accum, key ) {
accum[ key ] = src[ key ];
return accum;
}, {} );
};
}
/**
* Create an object with an API that enables easy extension.
*
* @example
* var myNameSpace = nmsp();
* myNameSpace.extend( { some: { data: {} } } );
* myNameSpace.extend( 'some.other.data', { the: 'data' } );
* // or
* var myNameSpace = nmsp( { some: { data: {} } } );
* myNameSpace.extend( 'some.other.data', { the: 'data' } );
*
* // {
* // some: {
* // data: {},
* // other: {
* // data: {
* // the: 'data'
* // }
* // }
* // }
* // }
*
* @param [initialValue] {Object|String} Create a namespace with an existing object. A path string (ex: `'a.b.c'` ) can be passed as the model for the namespace.
* @returns `{Object}` An object extended with the `nmsp` API.
*/
function nmsp( initialValue ) {
var extendStore
, store = Object.create( {} )
;
if ( isObject( initialValue ) ) {
store = initialValue;
}
else if ( initialValue !== undefined ) {
store = fromPath( initialValue );
}
extendStore = extend( store );
return Object.defineProperties( store, {
nmsp: {
value: true
},
extend: {
value: function ( path, src ) {
// Extend the store at the top level using
// the path value as src.
if ( !src ) {
extendStore( path );
}
// Extend the store at a specific path.
else {
// Get the value at the path.
var pathValue = atPath( store )( path );
if ( Array.isArray( pathValue ) ) {
// Concatenate the path array with the src value.
extend( pathValue )( pathValue.concat( src ) );
}
else {
// Create the required path, if not already present.
extendStore( fromPath( path ) );
// Set the path value as context and
// extend it with the src value.
extend( atPath( store )( path ) )( src );
}
}
}
},
atPath: {
value: atPath( store )
},
plain: {
value: plain( store )
}
});
}
exports = nmsp;
exports.extend = extend();
exports.atPath = atPath();
exports.plain = plain();
exports.fromPath = fromPath;
return exports;
}));