2017-02-17 15:14:17 -05:00
|
|
|
/**
|
|
|
|
* Key/value store
|
|
|
|
*
|
2017-06-08 14:46:51 -04:00
|
|
|
* Copyright (C) 2017 R-T Specialty, LLC.
|
2017-02-17 15:14:17 -05:00
|
|
|
*
|
|
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
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.<string,Array>} data associative array of the data
|
|
|
|
*
|
|
|
|
* @param {boolean} merge_index whether to merge indexes individually
|
|
|
|
* @param {boolean} merge_null whether to merge undefined values (vs
|
|
|
|
* ignore)
|
|
|
|
*
|
|
|
|
* @return {QuoteDataBucket} self to allow for method chaining
|
|
|
|
*/
|
|
|
|
'public setValues': function( data, merge_index, merge_null )
|
|
|
|
{
|
|
|
|
this._mergeData( data, merge_index, merge_null );
|
|
|
|
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, merge_index, merge_null )
|
|
|
|
{
|
|
|
|
merge_index = !!merge_index; // default false
|
|
|
|
merge_null = !!merge_null; // default false
|
|
|
|
|
|
|
|
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 ];
|
|
|
|
|
|
|
|
// if we're not supposed to merge the indexes one by one, just set
|
|
|
|
// it
|
|
|
|
if ( merge_index === false )
|
|
|
|
{
|
|
|
|
this._data[name] = data_set;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 ( !merge_null && 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.<string,Array>} data associative array of the data
|
|
|
|
*
|
|
|
|
* @return {Bucket} self
|
|
|
|
*/
|
|
|
|
'public overwriteValues': function( data )
|
|
|
|
{
|
|
|
|
this.setValues( data, false );
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
} );
|