/**
* Key/value store
*
* Copyright (C) 2017 R-T Specialty, LLC.
*
* This file is part of liza.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
var Class = require( 'easejs' ).Class,
Bucket = require( './Bucket' ),
EventEmitter = require( 'events' ).EventEmitter;
/**
* General key/value store for document
*
* The term "Quote" here is an artifact from the initial design of the
* system used for insurance quoting. It will be renamed.
*
* @todo Rename to DocumentDataBucket
*/
module.exports = Class( 'QuoteDataBucket' )
.implement( Bucket )
.extend( EventEmitter,
{
/**
* Triggered when data in the bucket is updated, before it's committed
* @type {string}
*/
'const EVENT_UPDATE': 'update',
/**
* Raw key/value store
* @type {Object}
*/
'private _data': {},
/**
* Cleans a name for use in the bucket
*
* Removes trailing brackets, if present
*
* @return {string} cleaned name
*/
'private _cleanName': function( name )
{
name = ''+name || '';
var bracket = name.indexOf( '[' );
if ( bracket == -1 )
{
return name;
}
return name.substring( 0, bracket );
},
/**
* Explicitly sets the contents of the bucket
*
* @param {Object.} data associative array of the data
*
* @return {QuoteDataBucket} self to allow for method chaining
*/
'public setValues': function( data )
{
this._mergeData( data );
return this;
},
/**
* Alias of setValues
*
* @return {QuoteDataBucket} self to allow for method chaining
*/
'public setCommittedValues': function()
{
return this.setValues.apply( this, arguments );
},
/**
* Clears all data from the bucket
*
* @return {QuoteDataBucket} self
*/
'public clear': function()
{
this._data = {};
return this;
},
/**
* Merges updated data with the existing data
*
* @param Object data updated data
*
* @return undefined
*/
'private _mergeData': function( data )
{
var ignore = {};
// remove any data that has not been updated (the hooks do processing on
// this data, often updating the DOM, so it's faster to do this than to
// have a lot of unnecessary DOM work done)
for ( name in data )
{
var data_set = data[ name ],
pre_set = this._data[ name ],
changed = false;
// if there's no previous data for this key, or the lengths vary,
// then we want to keep it
if ( ( pre_set === undefined )
|| ( pre_set.length !== data_set.length )
)
{
continue;
}
for ( var i = 0, len = data_set.length; i < len; i++ )
{
if ( data_set[ i ] !== pre_set[ i ] )
{
changed = true;
break;
}
}
// data matches original---we do not want to delete it, since that
// would modify the provided object; instead, mark it to be ignored
if ( changed === false )
{
ignore[ name ] = true;
}
}
this.emit( this.__self.$('EVENT_UPDATE'), data );
for ( name in data )
{
if ( ignore[ name ] )
{
continue;
}
var data_set = data[ name ];
// initialize it if its undefined in the bucket
if ( this._data[name] === undefined )
{
this._data[name] = [];
}
// merge the indexes one by one to preserve existing data
var data_set_len = data_set.length;
for ( var i = 0; i < data_set_len; i++ )
{
// ignore undefined (since we're merging, if it's not set, then
// we don't want to remove the data that's already there)
if ( data_set[ i ] === undefined )
{
continue;
}
// ignore if set to null (implying the index was removed)
if ( data_set[i] === null )
{
// this marks the end of the array as far as we're concerned
this._data[ name ].length = i;
break;
}
this._data[name][i] = data_set[i];
}
}
},
/**
* Overwrites values in the original bucket
*
* For this buckeet, overwriteValues() is an alias for setValues() without
* index merging. However, other Bucket implementations may handle it
* differently.
*
* @param {Object.} data associative array of the data
*
* @return {Bucket} self
*/
'public overwriteValues': function( data )
{
this.setValues(
Object.keys( data ).reduce(
( vals, key ) => (
vals[ key ] = data[ key ].concat( [ null ] ),
vals
),
{}
)
);
return this;
},
/**
* Calls a function for each each of the values in the bucket
*
* Note: This format is intended to be consistent with Array.forEach()
*
* @param {function(string,string)} callback function to call for each
* value in the bucket
*
* @return {QuoteDataBucket} self to allow for method chaining
*/
'public each': function( callback )
{
var bucket = this;
for ( var name in this._data )
{
callback( this._data[ name ], name );
}
return this;
},
/**
* Calls a function for each each of the values in the bucket matching the
* given predicate
*
* @param {function(string)} pred predicate
* @param {function( Object, number )} c function to call for each
* value in the bucket
*
* @return {StagingBucket} self
*/
'public filter': function( pred, c )
{
this.each( function( data, name )
{
if ( pred( name ) )
{
c( data, name );
}
} );
},
/**
* Returns the data for the requested field
*
* @param {string} name name of the field (with or without trailing brackets)
*
* @return {Array} data for the field, or empty array if none
*/
'public getDataByName': function( name )
{
var data = this._data[ this._cleanName( name ) ];
if ( data === undefined )
{
return [];
}
if ( data === null )
{
return null;
}
// return a copy of the data
return ( typeof data === 'object' ) ? data.slice( 0 ) : data;
},
/**
* Returns the data as a JSON string
*
* @return {string} data represented as JSON
*/
'public getDataJson': function()
{
return JSON.stringify( this._data );
},
/**
* Return raw bucket data
*
* TODO: remove; breaks encapsulation
*
* @return {Object} raw bucket data
*/
'public getData': function()
{
return this._data;
}
} );