Correct concurrent show/hide of fields from cmatch (bugfix)
commit
aea22294d1
|
@ -38,7 +38,7 @@
|
||||||
"version": "3.2.0"
|
"version": "3.2.0"
|
||||||
},
|
},
|
||||||
"easejs": {
|
"easejs": {
|
||||||
"version": "0.2.8"
|
"version": "0.2.9"
|
||||||
},
|
},
|
||||||
"escape-string-regexp": {
|
"escape-string-regexp": {
|
||||||
"version": "1.0.5"
|
"version": "1.0.5"
|
||||||
|
|
|
@ -23,6 +23,7 @@ const Class = require( 'easejs' ).Class;
|
||||||
const EventEmitter = require( 'events' ).EventEmitter;
|
const EventEmitter = require( 'events' ).EventEmitter;
|
||||||
const DomFieldNotFoundError = require( '../ui/field/DomFieldNotFoundError' );
|
const DomFieldNotFoundError = require( '../ui/field/DomFieldNotFoundError' );
|
||||||
const UnknownEventError = require( './event/UnknownEventError' );
|
const UnknownEventError = require( './event/UnknownEventError' );
|
||||||
|
const system = require( '../system/client' );
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -198,30 +199,10 @@ module.exports = Class( 'Client' )
|
||||||
'private _validatorFormatter': null,
|
'private _validatorFormatter': null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains classification match data per field
|
* Classification match handler
|
||||||
*
|
* @type {Cmatch}
|
||||||
* TODO: Move this to somewhere more appropriate
|
|
||||||
*
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
*/
|
||||||
'private _cmatch': {},
|
'private _cmatch': null,
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs classification matching on fields
|
|
||||||
*
|
|
||||||
* A field will have a positive match for a given index if all of its
|
|
||||||
* classes match
|
|
||||||
*
|
|
||||||
* @type {FieldClassMatcher}
|
|
||||||
*/
|
|
||||||
'private _classMatcher': null,
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fields that were hidden (including indexes) since the last cmatch clear
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
'private _cmatchHidden': {},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatically discards staging bucket contents
|
* Automatically discards staging bucket contents
|
||||||
|
@ -347,9 +328,7 @@ module.exports = Class( 'Client' )
|
||||||
this, this._dataValidator, this.elementStyler, this.dataProxy, jQuery
|
this, this._dataValidator, this.elementStyler, this.dataProxy, jQuery
|
||||||
);
|
);
|
||||||
|
|
||||||
this._classMatcher = this._factory.createFieldClassMatcher(
|
this._cmatch = system.cmatch( this.program, this.__inst );
|
||||||
this.program.whens
|
|
||||||
);
|
|
||||||
|
|
||||||
this._validatorFormatter = this._factory.createValidatorFormatter(
|
this._validatorFormatter = this._factory.createValidatorFormatter(
|
||||||
this.program.meta.qtypes
|
this.program.meta.qtypes
|
||||||
|
@ -428,7 +407,7 @@ module.exports = Class( 'Client' )
|
||||||
{
|
{
|
||||||
client.ui.displayStep( step_id, function()
|
client.ui.displayStep( step_id, function()
|
||||||
{
|
{
|
||||||
client.forceCmatchAction();
|
client._cmatch.forceCmatchAction();
|
||||||
} );
|
} );
|
||||||
|
|
||||||
client._currentStepId = step_id;
|
client._currentStepId = step_id;
|
||||||
|
@ -503,7 +482,7 @@ module.exports = Class( 'Client' )
|
||||||
|
|
||||||
client._quote.setQuickSaveData( data.content.quicksave || {} );
|
client._quote.setQuickSaveData( data.content.quicksave || {} );
|
||||||
|
|
||||||
client._hookClassifier();
|
client._cmatch.hookClassifier( client._dataValidator );
|
||||||
|
|
||||||
// store internal status
|
// store internal status
|
||||||
client._isInternal = client.program.isInternal =
|
client._isInternal = client.program.isInternal =
|
||||||
|
@ -556,285 +535,6 @@ module.exports = Class( 'Client' )
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Force handling of the most recent cmatch data
|
|
||||||
*
|
|
||||||
* This can be used to refresh the UI to ensure that it is consistent with
|
|
||||||
* the cmatch data.
|
|
||||||
*
|
|
||||||
* @return {Client} self
|
|
||||||
*/
|
|
||||||
'public forceCmatchAction': function()
|
|
||||||
{
|
|
||||||
if ( !( this._cmatch ) )
|
|
||||||
{
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._handleClassMatch( this._cmatch, true );
|
|
||||||
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
'private _hookClassifier': function()
|
|
||||||
{
|
|
||||||
var _self = this,
|
|
||||||
program = this.program;
|
|
||||||
|
|
||||||
// clear/initialize cmatches
|
|
||||||
this._cmatch = {};
|
|
||||||
|
|
||||||
var cmatchprot = false;
|
|
||||||
|
|
||||||
// set classifier
|
|
||||||
this._quote
|
|
||||||
.setClassifier( program.getClassifierKnownFields(), function()
|
|
||||||
{
|
|
||||||
return program.classify.apply( program, arguments );
|
|
||||||
} )
|
|
||||||
.on( 'classify', function( classes )
|
|
||||||
{
|
|
||||||
if ( cmatchprot === true )
|
|
||||||
{
|
|
||||||
_self._handleError( Error( 'cmatch recursion' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
cmatchprot = true;
|
|
||||||
|
|
||||||
// handle field fixes
|
|
||||||
_self._dataValidator.validate( undefined, classes )
|
|
||||||
.catch( e => _self.handleError( e ) );
|
|
||||||
|
|
||||||
_self._classMatcher.match( classes, function( cmatch )
|
|
||||||
{
|
|
||||||
// it's important that we do this here so that everything
|
|
||||||
// that uses the cmatch data will consistently benefit
|
|
||||||
_self._postProcessCmatch( cmatch );
|
|
||||||
|
|
||||||
// if we're not on a current step, defer
|
|
||||||
if ( !( _self.ui.getCurrentStep() ) )
|
|
||||||
{
|
|
||||||
_self._cmatch = cmatch;
|
|
||||||
cmatchprot = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_self._handleClassMatch( cmatch );
|
|
||||||
cmatchprot = false;
|
|
||||||
} );
|
|
||||||
} );
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
'private _postProcessCmatch': function( cmatch )
|
|
||||||
{
|
|
||||||
// for any matches that are scalars (they will have no indexes), loop
|
|
||||||
// through each field and set the index to the value of 'all'
|
|
||||||
for ( var field in cmatch )
|
|
||||||
{
|
|
||||||
if ( field === '__classes' )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cfield = cmatch[ field ];
|
|
||||||
|
|
||||||
if ( cfield.indexes.length === 0 )
|
|
||||||
{
|
|
||||||
var data = this.getQuote().getDataByName( field ),
|
|
||||||
i = data.length;
|
|
||||||
|
|
||||||
// this will do nothing if there is no data found
|
|
||||||
while ( i-- )
|
|
||||||
{
|
|
||||||
cfield.indexes[ i ] = cfield.all;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmatch;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
// from UI
|
|
||||||
'private _cmatchVisFromUi': function( field, all )
|
|
||||||
{
|
|
||||||
var step = this.getUi().getCurrentStep();
|
|
||||||
|
|
||||||
if ( !step )
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
var group = step.getElementGroup( field );
|
|
||||||
if ( !group )
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
var i = group.getCurrentIndexCount(),
|
|
||||||
ret = [];
|
|
||||||
|
|
||||||
while ( i-- )
|
|
||||||
{
|
|
||||||
ret.push( all );
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
'private _handleClassMatch': function( cmatch, force )
|
|
||||||
{
|
|
||||||
force = !!force;
|
|
||||||
|
|
||||||
this.ui.setCmatch( cmatch );
|
|
||||||
|
|
||||||
var _self = this,
|
|
||||||
quote = this.getQuote(),
|
|
||||||
|
|
||||||
// oh dear god...(Demeter, specifically..)
|
|
||||||
fields = this.getUi().getCurrentStep().getStep()
|
|
||||||
.getExclusiveFieldNames();
|
|
||||||
|
|
||||||
|
|
||||||
var visq = [];
|
|
||||||
for ( var field in cmatch )
|
|
||||||
{
|
|
||||||
// ignore fields that are not on the current step
|
|
||||||
if ( !( fields[ field ] ) )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the match is still false, then we can rest assured
|
|
||||||
// that nothing has changed (and skip the overhead)
|
|
||||||
if ( !force
|
|
||||||
&& ( cmatch[ field ] === false )
|
|
||||||
&& ( _self._cmatch[ field ] === false )
|
|
||||||
)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var show = [],
|
|
||||||
hide = [],
|
|
||||||
|
|
||||||
cfield = cmatch[ field ],
|
|
||||||
|
|
||||||
vis = cfield.indexes,
|
|
||||||
cur = (
|
|
||||||
( _self._cmatch[ field ] || {} ).indexes
|
|
||||||
|| []
|
|
||||||
);
|
|
||||||
|
|
||||||
// the system was previously unable to determine the length, so
|
|
||||||
// let's attempt to get it from the UI
|
|
||||||
if ( vis.length === 0 )
|
|
||||||
{
|
|
||||||
vis = this._cmatchVisFromUi( field, cfield.all );
|
|
||||||
}
|
|
||||||
|
|
||||||
// consider the number of indexes in the bucket first;
|
|
||||||
// otherwise, we might try to operate on fields that don't
|
|
||||||
// exist (bucket/class indexes not the same). the check for
|
|
||||||
// undefined in the first index is a workaround for the explicit
|
|
||||||
// setting of the length property of the bucket value when
|
|
||||||
// indexes are removed
|
|
||||||
var curdata = quote.getDataByName( field ),
|
|
||||||
fieldn = ( curdata.length > 0 && ( curdata[ 0 ] !== undefined ) )
|
|
||||||
? curdata.length
|
|
||||||
: vis.length;
|
|
||||||
|
|
||||||
for ( var i = 0; i < fieldn; i++ )
|
|
||||||
{
|
|
||||||
// do not record unchanged indexes as changed
|
|
||||||
// (avoiding the event overhead)
|
|
||||||
if ( !force && ( vis[ i ] === cur[ i ] ) )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
( ( vis[ i ] ) ? show : hide ).push( i );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( show.length )
|
|
||||||
{
|
|
||||||
visq[ field ] = { event_id: 'show', name: field, indexes: show };
|
|
||||||
this._mergeCmatchHidden( field, show, false );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( hide.length )
|
|
||||||
{
|
|
||||||
visq[ field ] = { event_id: 'hide', name: field, indexes: hide };
|
|
||||||
this._mergeCmatchHidden( field, hide, true );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// it's important to do this before showing/hiding fields, since
|
|
||||||
// those might trigger events that check the current cmatches
|
|
||||||
this._cmatch = cmatch;
|
|
||||||
|
|
||||||
|
|
||||||
// allow DOM operations to complete before we trigger
|
|
||||||
// manipulations on it (TODO: this is a workaround for group
|
|
||||||
// show/hide issues; we need a better solution to guarantee
|
|
||||||
// order
|
|
||||||
setTimeout( () =>
|
|
||||||
{
|
|
||||||
Object.keys( visq ).forEach( field =>
|
|
||||||
{
|
|
||||||
const { event_id, name, indexes } = visq[ field ];
|
|
||||||
|
|
||||||
this.handleEvent( event_id, {
|
|
||||||
elementName: name,
|
|
||||||
indexes: indexes,
|
|
||||||
} );
|
|
||||||
|
|
||||||
this._dapiTrigger( name );
|
|
||||||
} );
|
|
||||||
}, 25 );
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
'private _mergeCmatchHidden': function( name, indexes, hidden )
|
|
||||||
{
|
|
||||||
if ( !( this._cmatchHidden[ name ] ) )
|
|
||||||
{
|
|
||||||
this._cmatchHidden[ name ] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
var cindexes = this._cmatchHidden[ name ];
|
|
||||||
|
|
||||||
for ( i in indexes )
|
|
||||||
{
|
|
||||||
if ( hidden )
|
|
||||||
{
|
|
||||||
cindexes[ indexes[ i ] ] = i;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
delete cindexes[ indexes[ i ] ];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var some = false;
|
|
||||||
for ( var i in cindexes )
|
|
||||||
{
|
|
||||||
some = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !some )
|
|
||||||
{
|
|
||||||
// v8 devs do not recomment delete as it progressively slows down
|
|
||||||
// property access on the object
|
|
||||||
this._cmatchHidden[ name ] = undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks quote for performing validations on data change
|
* Hooks quote for performing validations on data change
|
||||||
*
|
*
|
||||||
|
@ -862,7 +562,7 @@ module.exports = Class( 'Client' )
|
||||||
name,
|
name,
|
||||||
bucket,
|
bucket,
|
||||||
diff,
|
diff,
|
||||||
this._cmatch,
|
this._cmatch.getMatches(),
|
||||||
function()
|
function()
|
||||||
{
|
{
|
||||||
var args = arguments;
|
var args = arguments;
|
||||||
|
@ -937,7 +637,7 @@ module.exports = Class( 'Client' )
|
||||||
{
|
{
|
||||||
// N.B.: We pass {} as the diff because nothing has actually changed
|
// N.B.: We pass {} as the diff because nothing has actually changed
|
||||||
_self.ui.invalidateForm(
|
_self.ui.invalidateForm(
|
||||||
validate_callback( bucket, {}, _self._cmatch )
|
validate_callback( bucket, {}, _self._cmatch.getMatches() )
|
||||||
);
|
);
|
||||||
} );
|
} );
|
||||||
},
|
},
|
||||||
|
@ -1009,7 +709,7 @@ module.exports = Class( 'Client' )
|
||||||
// force UI cmatch update, since we may have fields that have been added
|
// force UI cmatch update, since we may have fields that have been added
|
||||||
// that need to be shown/hidden based on the current set of
|
// that need to be shown/hidden based on the current set of
|
||||||
// classifications
|
// classifications
|
||||||
this.forceCmatchAction();
|
this._cmatch.forceCmatchAction();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
@ -1049,7 +749,7 @@ module.exports = Class( 'Client' )
|
||||||
catch ( e )
|
catch ( e )
|
||||||
{
|
{
|
||||||
// todo: better suited for brokers
|
// todo: better suited for brokers
|
||||||
this._handleError( Error(
|
this.handleError( Error(
|
||||||
"Error loading program data: " + e.message
|
"Error loading program data: " + e.message
|
||||||
) );
|
) );
|
||||||
|
|
||||||
|
@ -1058,7 +758,7 @@ module.exports = Class( 'Client' )
|
||||||
|
|
||||||
program.on( 'error', function( e )
|
program.on( 'error', function( e )
|
||||||
{
|
{
|
||||||
_self._handleError( e );
|
_self.handleError( e );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
// handle field updates
|
// handle field updates
|
||||||
|
@ -1158,7 +858,7 @@ module.exports = Class( 'Client' )
|
||||||
} )
|
} )
|
||||||
.on( 'error', function( e )
|
.on( 'error', function( e )
|
||||||
{
|
{
|
||||||
_self._handleError( e );
|
_self.handleError( e );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
return program;
|
return program;
|
||||||
|
@ -1412,7 +1112,7 @@ module.exports = Class( 'Client' )
|
||||||
// handle context errors
|
// handle context errors
|
||||||
root_context.on( 'error', function( e )
|
root_context.on( 'error', function( e )
|
||||||
{
|
{
|
||||||
client._handleError( e );
|
client.handleError( e );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
// must init after the Ui obj is available
|
// must init after the Ui obj is available
|
||||||
|
@ -1472,7 +1172,7 @@ module.exports = Class( 'Client' )
|
||||||
} )
|
} )
|
||||||
.on( 'error', function( e )
|
.on( 'error', function( e )
|
||||||
{
|
{
|
||||||
client._handleError( e );
|
client.handleError( e );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
return ui.saveStep( function( stepui )
|
return ui.saveStep( function( stepui )
|
||||||
|
@ -1929,7 +1629,7 @@ module.exports = Class( 'Client' )
|
||||||
var client = this;
|
var client = this;
|
||||||
|
|
||||||
// if the step contains invalid data, they must correct it
|
// if the step contains invalid data, they must correct it
|
||||||
if ( !( stepui.isValid( this._cmatch ) ) )
|
if ( !( stepui.isValid( this._cmatch.getMatches() ) ) )
|
||||||
{
|
{
|
||||||
// well we didn't get very far
|
// well we didn't get very far
|
||||||
callback( false );
|
callback( false );
|
||||||
|
@ -1947,7 +1647,7 @@ module.exports = Class( 'Client' )
|
||||||
|
|
||||||
// we want to do this before save so that we don't re-mark the bucket as
|
// we want to do this before save so that we don't re-mark the bucket as
|
||||||
// dirty by populating it with uncommitted data
|
// dirty by populating it with uncommitted data
|
||||||
client._clearCmatchFields();
|
client._cmatch.clearCmatchFields();
|
||||||
|
|
||||||
// give devs the option to disable client-side submit events so we can
|
// give devs the option to disable client-side submit events so we can
|
||||||
// test server-side functionality
|
// test server-side functionality
|
||||||
|
@ -1959,7 +1659,7 @@ module.exports = Class( 'Client' )
|
||||||
// shouldn't occurr, we should still throw an exception if one is
|
// shouldn't occurr, we should still throw an exception if one is
|
||||||
// triggered
|
// triggered
|
||||||
var failures = this.program.submit(
|
var failures = this.program.submit(
|
||||||
step_id, bucket, this._cmatch
|
step_id, bucket, this._cmatch.getMatches()
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( failures !== null )
|
if ( failures !== null )
|
||||||
|
@ -2072,7 +1772,7 @@ module.exports = Class( 'Client' )
|
||||||
{
|
{
|
||||||
event.abort();
|
event.abort();
|
||||||
|
|
||||||
_self._handleError( Error(
|
_self.handleError( Error(
|
||||||
'Save timeout; please try again'
|
'Save timeout; please try again'
|
||||||
) );
|
) );
|
||||||
}, 15000 );
|
}, 15000 );
|
||||||
|
@ -2371,57 +2071,6 @@ module.exports = Class( 'Client' )
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
'private _clearCmatchFields': function()
|
|
||||||
{
|
|
||||||
var step = this.getUi().getCurrentStep(),
|
|
||||||
program = this.program;
|
|
||||||
|
|
||||||
// don't bother if we're not yet on a step
|
|
||||||
if ( !step )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var reset = {};
|
|
||||||
for ( var name in step.getStep().getExclusiveFieldNames() )
|
|
||||||
{
|
|
||||||
var data = this._cmatchHidden[ name ];
|
|
||||||
|
|
||||||
// if there is no data or we have been asked to retain this field's
|
|
||||||
// value, then do not clear
|
|
||||||
if ( !data || program.cretain[ name ] )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// what state is the current data in?
|
|
||||||
var cur = this.getQuote().getDataByName( name );
|
|
||||||
|
|
||||||
// we could have done Array.join(',').split(','), but we're trying
|
|
||||||
// to keep performance sane here
|
|
||||||
var indexes = [];
|
|
||||||
for ( var i in data )
|
|
||||||
{
|
|
||||||
// we do *not* want to reset fields that have been removed
|
|
||||||
if ( cur[ i ] === undefined )
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
indexes.push( i );
|
|
||||||
}
|
|
||||||
|
|
||||||
reset[ name ] = indexes;
|
|
||||||
}
|
|
||||||
|
|
||||||
// batch reset (limit the number of times events are kicked off)
|
|
||||||
this._resetFields( reset );
|
|
||||||
|
|
||||||
// we've done our deed; reset it for the next time around
|
|
||||||
this._cmatchHidden = {};
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform `forward' validations if needed
|
* Perform `forward' validations if needed
|
||||||
*
|
*
|
||||||
|
@ -2449,7 +2098,7 @@ module.exports = Class( 'Client' )
|
||||||
var failures = this.program.forward(
|
var failures = this.program.forward(
|
||||||
cur_step_id,
|
cur_step_id,
|
||||||
bucket,
|
bucket,
|
||||||
this._cmatch,
|
this._cmatch.getMatches(),
|
||||||
function( trigger_event, question_id, value )
|
function( trigger_event, question_id, value )
|
||||||
{
|
{
|
||||||
client.handleEvent( trigger_event, { stepId: +value } );
|
client.handleEvent( trigger_event, { stepId: +value } );
|
||||||
|
@ -2561,14 +2210,12 @@ module.exports = Class( 'Client' )
|
||||||
/**
|
/**
|
||||||
* Handles client-side events
|
* Handles client-side events
|
||||||
*
|
*
|
||||||
* @param String event_name name of the event
|
* @param {string} event_name name of the event
|
||||||
* @param Object data data to pass to event
|
* @param {Object} data data to pass to event
|
||||||
* @param Function callback function to call when event is done (if
|
* @param {function(Object)} callback function to call when event is done
|
||||||
* not asynchronous, it'll be called immediately)
|
* @param {function(Error)} error_callback function to call if event fails
|
||||||
*
|
*
|
||||||
* @param Function error_callback function to call if event fails
|
* @return {Client} self to allow for method chaining
|
||||||
*
|
|
||||||
* @return Client self to allow for method chaining
|
|
||||||
*/
|
*/
|
||||||
handleEvent: function( event_name, data, callback, error_callback )
|
handleEvent: function( event_name, data, callback, error_callback )
|
||||||
{
|
{
|
||||||
|
@ -2577,15 +2224,13 @@ module.exports = Class( 'Client' )
|
||||||
|
|
||||||
this.emit( this.__self.$('EVENT_TRIGGER'), event_name, data );
|
this.emit( this.__self.$('EVENT_TRIGGER'), event_name, data );
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this._eventHandler.handle(
|
this._eventHandler.handle(
|
||||||
event_name, function( err, data )
|
event_name, function( err, data )
|
||||||
{
|
{
|
||||||
if ( err )
|
if ( err )
|
||||||
{
|
{
|
||||||
error_callback( err );
|
error_callback && error_callback( err );
|
||||||
return;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: move me
|
// XXX: move me
|
||||||
|
@ -2598,102 +2243,10 @@ module.exports = Class( 'Client' )
|
||||||
}, data
|
}, data
|
||||||
);
|
);
|
||||||
|
|
||||||
// we had no problem handling this event; no need to continue with
|
|
||||||
// the old event handling system
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch ( e )
|
|
||||||
{
|
|
||||||
// segue into the old event handling system
|
|
||||||
if ( !( Class.isA( UnknownEventError, e ) ) )
|
|
||||||
{
|
|
||||||
// ruh roh
|
|
||||||
this._handleError( e );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// perform event (XXX: replace me; see above)
|
|
||||||
switch ( event_name )
|
|
||||||
{
|
|
||||||
case 'reset':
|
|
||||||
var reset = {};
|
|
||||||
reset[ data.elementName ] = data.indexes;
|
|
||||||
|
|
||||||
this._resetFields( reset );
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
this._handleError( Error(
|
|
||||||
'Unknown client-side event: ' + event_name
|
|
||||||
) );
|
|
||||||
}
|
|
||||||
|
|
||||||
// call the callback, if one was provided
|
|
||||||
if ( callback instanceof Function )
|
|
||||||
{
|
|
||||||
callback.call( this );
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trigger DataApi event for field FIELD
|
|
||||||
*
|
|
||||||
* @param {string} field field name
|
|
||||||
*
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
'private _dapiTrigger': function( field )
|
|
||||||
{
|
|
||||||
var _self = this;
|
|
||||||
|
|
||||||
this.getQuote().visitData( function( bucket )
|
|
||||||
{
|
|
||||||
_self.program.dapi(
|
|
||||||
_self._currentStepId,
|
|
||||||
field,
|
|
||||||
bucket,
|
|
||||||
{},
|
|
||||||
_self._cmatch,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
} );
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
'private _resetFields': function( fields )
|
|
||||||
{
|
|
||||||
var update = {};
|
|
||||||
|
|
||||||
for ( var field in fields )
|
|
||||||
{
|
|
||||||
var cur = fields[ field ],
|
|
||||||
cdata = this._quote.getDataByName( field ),
|
|
||||||
val = this.elementStyler.getDefault( field );
|
|
||||||
|
|
||||||
var data = [];
|
|
||||||
for ( var i in cur )
|
|
||||||
{
|
|
||||||
var index = cur[ i ];
|
|
||||||
|
|
||||||
if ( cdata[ index ] === val )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
data[ index ] = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
update[ field ] = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._quote.setData( update );
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current quote
|
* Returns the current quote
|
||||||
*
|
*
|
||||||
|
@ -2926,7 +2479,7 @@ module.exports = Class( 'Client' )
|
||||||
// proxy errors
|
// proxy errors
|
||||||
this._fieldMonitor.on( 'error', function( e )
|
this._fieldMonitor.on( 'error', function( e )
|
||||||
{
|
{
|
||||||
_self._handleError( e );
|
_self.handleError( e );
|
||||||
} );
|
} );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2960,7 +2513,7 @@ module.exports = Class( 'Client' )
|
||||||
*
|
*
|
||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
'private _handleError': function( e )
|
'public handleError': function( e )
|
||||||
{
|
{
|
||||||
if ( !e )
|
if ( !e )
|
||||||
{
|
{
|
||||||
|
@ -3024,10 +2577,4 @@ module.exports = Class( 'Client' )
|
||||||
{
|
{
|
||||||
return this.program.id;
|
return this.program.id;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
'public getCmatchData': function()
|
|
||||||
{
|
|
||||||
return this._cmatch;
|
|
||||||
}
|
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -82,7 +82,6 @@ var Step = require( '../step/Step' ),
|
||||||
NavStyler = require( '../ui/nav/NavStyler' ),
|
NavStyler = require( '../ui/nav/NavStyler' ),
|
||||||
Sidebar = require( '../ui/sidebar/Sidebar' ),
|
Sidebar = require( '../ui/sidebar/Sidebar' ),
|
||||||
|
|
||||||
FieldClassMatcher = require( '../field/FieldClassMatcher' ),
|
|
||||||
DataApiFactory = require( '../dapi/DataApiFactory' ),
|
DataApiFactory = require( '../dapi/DataApiFactory' ),
|
||||||
DataApiManager = require( '../dapi/DataApiManager' ),
|
DataApiManager = require( '../dapi/DataApiManager' ),
|
||||||
|
|
||||||
|
@ -345,8 +344,6 @@ module.exports = Class( 'ClientDependencyFactory',
|
||||||
|
|
||||||
createNotifyBar: UiNotifyBar,
|
createNotifyBar: UiNotifyBar,
|
||||||
|
|
||||||
createFieldClassMatcher: FieldClassMatcher,
|
|
||||||
|
|
||||||
|
|
||||||
createClientEventHandler: function(
|
createClientEventHandler: function(
|
||||||
client, data_validator, styler, data_proxy, jquery
|
client, data_validator, styler, data_proxy, jquery
|
||||||
|
@ -373,5 +370,5 @@ module.exports = Class( 'ClientDependencyFactory',
|
||||||
|
|
||||||
'action$cvv2Dialog': requireh( 'Cvv2DialogEventHandler' )( jquery )
|
'action$cvv2Dialog': requireh( 'Cvv2DialogEventHandler' )( jquery )
|
||||||
} );
|
} );
|
||||||
}
|
},
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -0,0 +1,515 @@
|
||||||
|
/**
|
||||||
|
* Liza classification match (cmatch) handling
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 R-T Specialty, LLC.
|
||||||
|
*
|
||||||
|
* This file is part of the Liza Data Collection Framework
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*
|
||||||
|
* TODO: This is code directly extracted from Client, modified to maintain
|
||||||
|
* references to necessary objects. It is coupled with far too many things,
|
||||||
|
* and the code is a mess. Getting this clean and well-tested is important,
|
||||||
|
* as this is one of the core systems and is both complicated and complex.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const { Class } = require( 'easejs' );
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = Class( 'Cmatch',
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Contains classification match data per field
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
'private _cmatch': {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fields that were hidden (including indexes) since the last cmatch
|
||||||
|
* clear
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
'private _cmatchHidden': {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs classification matching on fields
|
||||||
|
*
|
||||||
|
* A field will have a positive match for a given index if all of its
|
||||||
|
* classes match
|
||||||
|
*
|
||||||
|
* @type {FieldClassMatcher}
|
||||||
|
*/
|
||||||
|
'private _classMatcher': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Program client
|
||||||
|
* @type {Client}
|
||||||
|
*/
|
||||||
|
'private _client': null,
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize match handler
|
||||||
|
*
|
||||||
|
* This relies on too many objects; see header.
|
||||||
|
*
|
||||||
|
* @param {FieldClassMatcher} class_matcher class/field matcher
|
||||||
|
* @param {Program} program active program
|
||||||
|
* @param {Client} client active client
|
||||||
|
*/
|
||||||
|
constructor( class_matcher, program, client )
|
||||||
|
{
|
||||||
|
this._classMatcher = class_matcher;
|
||||||
|
this._program = program;
|
||||||
|
this._client = client;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
'private _cmatchVisFromUi': function( field, all )
|
||||||
|
{
|
||||||
|
var step = this._client.getUi().getCurrentStep();
|
||||||
|
|
||||||
|
if ( !step )
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var group = step.getElementGroup( field );
|
||||||
|
if ( !group )
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var i = group.getCurrentIndexCount(),
|
||||||
|
ret = [];
|
||||||
|
|
||||||
|
while ( i-- )
|
||||||
|
{
|
||||||
|
ret.push( all );
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
'public hookClassifier': function( data_validator )
|
||||||
|
{
|
||||||
|
var _self = this,
|
||||||
|
program = this._program;
|
||||||
|
|
||||||
|
// clear/initialize cmatches
|
||||||
|
this._cmatch = {};
|
||||||
|
|
||||||
|
var cmatchprot = false;
|
||||||
|
|
||||||
|
// set classifier
|
||||||
|
this._client.getQuote()
|
||||||
|
.setClassifier( program.getClassifierKnownFields(), function()
|
||||||
|
{
|
||||||
|
return program.classify.apply( program, arguments );
|
||||||
|
} )
|
||||||
|
.on( 'classify', function( classes )
|
||||||
|
{
|
||||||
|
if ( cmatchprot === true )
|
||||||
|
{
|
||||||
|
_self._client.handleError( Error( 'cmatch recursion' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
cmatchprot = true;
|
||||||
|
|
||||||
|
// handle field fixes
|
||||||
|
data_validator.validate( undefined, classes )
|
||||||
|
.catch( e => _self.client._handleError( e ) );
|
||||||
|
|
||||||
|
_self._classMatcher.match( classes, function( cmatch )
|
||||||
|
{
|
||||||
|
// it's important that we do this here so that everything
|
||||||
|
// that uses the cmatch data will consistently benefit
|
||||||
|
_self._postProcessCmatch( cmatch );
|
||||||
|
|
||||||
|
// if we're not on a current step, defer
|
||||||
|
if ( !( _self._client.getUi().getCurrentStep() ) )
|
||||||
|
{
|
||||||
|
_self._cmatch = cmatch;
|
||||||
|
cmatchprot = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_self._handleClassMatch( cmatch );
|
||||||
|
cmatchprot = false;
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
'private _postProcessCmatch': function( cmatch )
|
||||||
|
{
|
||||||
|
// for any matches that are scalars (they will have no indexes), loop
|
||||||
|
// through each field and set the index to the value of 'all'
|
||||||
|
for ( var field in cmatch )
|
||||||
|
{
|
||||||
|
if ( field === '__classes' )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfield = cmatch[ field ];
|
||||||
|
|
||||||
|
if ( cfield.indexes.length === 0 )
|
||||||
|
{
|
||||||
|
var data = this._client.getQuote().getDataByName( field ),
|
||||||
|
i = data.length;
|
||||||
|
|
||||||
|
// this will do nothing if there is no data found
|
||||||
|
while ( i-- )
|
||||||
|
{
|
||||||
|
cfield.indexes[ i ] = cfield.all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmatch;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
'private _mergeCmatchHidden': function( name, indexes, hidden )
|
||||||
|
{
|
||||||
|
if ( !( this._cmatchHidden[ name ] ) )
|
||||||
|
{
|
||||||
|
this._cmatchHidden[ name ] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
var cindexes = this._cmatchHidden[ name ];
|
||||||
|
|
||||||
|
for ( i in indexes )
|
||||||
|
{
|
||||||
|
if ( hidden )
|
||||||
|
{
|
||||||
|
cindexes[ indexes[ i ] ] = i;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delete cindexes[ indexes[ i ] ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var some = false;
|
||||||
|
for ( var i in cindexes )
|
||||||
|
{
|
||||||
|
some = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !some )
|
||||||
|
{
|
||||||
|
// v8 devs do not recomment delete as it progressively slows down
|
||||||
|
// property access on the object
|
||||||
|
this._cmatchHidden[ name ] = undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
'private _handleClassMatch': function( cmatch, force )
|
||||||
|
{
|
||||||
|
force = !!force;
|
||||||
|
|
||||||
|
this._client.getUi().setCmatch( cmatch );
|
||||||
|
|
||||||
|
var _self = this,
|
||||||
|
quote = this._client.getQuote(),
|
||||||
|
|
||||||
|
// oh dear god...(Demeter, specifically..)
|
||||||
|
fields = this._client.getUi().getCurrentStep().getStep()
|
||||||
|
.getExclusiveFieldNames();
|
||||||
|
|
||||||
|
|
||||||
|
var visq = {};
|
||||||
|
for ( var field in cmatch )
|
||||||
|
{
|
||||||
|
// ignore fields that are not on the current step
|
||||||
|
if ( !( fields[ field ] ) )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the match is still false, then we can rest assured
|
||||||
|
// that nothing has changed (and skip the overhead)
|
||||||
|
if ( !force
|
||||||
|
&& ( cmatch[ field ] === false )
|
||||||
|
&& ( _self._cmatch[ field ] === false )
|
||||||
|
)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var show = [],
|
||||||
|
hide = [],
|
||||||
|
|
||||||
|
cfield = cmatch[ field ],
|
||||||
|
|
||||||
|
vis = cfield.indexes,
|
||||||
|
cur = (
|
||||||
|
( _self._cmatch[ field ] || {} ).indexes
|
||||||
|
|| []
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: Figure out something better here. This is currently
|
||||||
|
// needed for hiding statics---they are registered as exclusive
|
||||||
|
// fields (`fields', above), but aren't actually fields (they're
|
||||||
|
// not in the bucket). But we must show/hide them appropriately.
|
||||||
|
if ( vis.length === 0 )
|
||||||
|
{
|
||||||
|
vis = this._cmatchVisFromUi( field, cfield.all );
|
||||||
|
}
|
||||||
|
|
||||||
|
// consider the number of indexes in the bucket first;
|
||||||
|
// otherwise, we might try to operate on fields that don't
|
||||||
|
// exist (bucket/class indexes not the same). the check for
|
||||||
|
// undefined in the first index is a workaround for the explicit
|
||||||
|
// setting of the length property of the bucket value when
|
||||||
|
// indexes are removed
|
||||||
|
var curdata = quote.getDataByName( field ),
|
||||||
|
fieldn = ( curdata.length > 0 && ( curdata[ 0 ] !== undefined ) )
|
||||||
|
? curdata.length
|
||||||
|
: vis.length;
|
||||||
|
|
||||||
|
for ( var i = 0; i < fieldn; i++ )
|
||||||
|
{
|
||||||
|
// do not record unchanged indexes as changed
|
||||||
|
// (avoiding the event overhead)
|
||||||
|
if ( !force && ( vis[ i ] === cur[ i ] ) )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
( ( vis[ i ] ) ? show : hide ).push( i );
|
||||||
|
}
|
||||||
|
|
||||||
|
this.markShowHide( field, visq, show, hide );
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's important to do this before showing/hiding fields, since
|
||||||
|
// those might trigger events that check the current cmatches
|
||||||
|
this._cmatch = cmatch;
|
||||||
|
|
||||||
|
|
||||||
|
// allow DOM operations to complete before we trigger
|
||||||
|
// manipulations on it (TODO: this is a workaround for group
|
||||||
|
// show/hide issues; we need a better solution to guarantee
|
||||||
|
// order
|
||||||
|
setTimeout( () =>
|
||||||
|
{
|
||||||
|
Object.keys( visq ).forEach( field =>
|
||||||
|
{
|
||||||
|
const field_vis = visq[ field ];
|
||||||
|
|
||||||
|
Object.keys( field_vis ).forEach( event_id =>
|
||||||
|
{
|
||||||
|
const indexes = field_vis[ event_id ];
|
||||||
|
|
||||||
|
this._client.handleEvent( event_id, {
|
||||||
|
elementName: field,
|
||||||
|
indexes: indexes,
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
this._dapiTrigger( name );
|
||||||
|
} );
|
||||||
|
}, 25 );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark fields to be shown/hidden
|
||||||
|
*
|
||||||
|
* This also updates the cached visibility of field FIELD.
|
||||||
|
*
|
||||||
|
* @param {string} field field name
|
||||||
|
* @param {Array} show indexes to show
|
||||||
|
* @param {Array} hide indexes to hide
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'virtual protected markShowHide'( field, visq, show, hide )
|
||||||
|
{
|
||||||
|
if ( !( show.length || hide.length ) )
|
||||||
|
{
|
||||||
|
return visq;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { [field]: result = {} } = visq;
|
||||||
|
|
||||||
|
if ( show.length )
|
||||||
|
{
|
||||||
|
this._mergeCmatchHidden( field, show, false );
|
||||||
|
result.show = show;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( hide.length )
|
||||||
|
{
|
||||||
|
this._mergeCmatchHidden( field, hide, true );
|
||||||
|
result.hide = hide;
|
||||||
|
}
|
||||||
|
|
||||||
|
visq[ field ] = result;
|
||||||
|
|
||||||
|
return visq;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger DataApi event for field FIELD
|
||||||
|
*
|
||||||
|
* @param {string} field field name
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _dapiTrigger': function( field )
|
||||||
|
{
|
||||||
|
const current_step_id = this._client.nav.getCurrentStepId();
|
||||||
|
|
||||||
|
this._client.getQuote().visitData( bucket =>
|
||||||
|
{
|
||||||
|
this._program.dapi(
|
||||||
|
current_step_id,
|
||||||
|
field,
|
||||||
|
bucket,
|
||||||
|
{},
|
||||||
|
this._cmatch,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
'public clearCmatchFields': function()
|
||||||
|
{
|
||||||
|
var step = this._client.getUi().getCurrentStep(),
|
||||||
|
program = this._program;
|
||||||
|
|
||||||
|
// don't bother if we're not yet on a step
|
||||||
|
if ( !step )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var reset = {};
|
||||||
|
for ( var name in step.getStep().getExclusiveFieldNames() )
|
||||||
|
{
|
||||||
|
var data = this._cmatchHidden[ name ];
|
||||||
|
|
||||||
|
// if there is no data or we have been asked to retain this field's
|
||||||
|
// value, then do not clear
|
||||||
|
if ( !data || program.cretain[ name ] )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// what state is the current data in?
|
||||||
|
var cur = this._client.getQuote().getDataByName( name );
|
||||||
|
|
||||||
|
// we could have done Array.join(',').split(','), but we're trying
|
||||||
|
// to keep performance sane here
|
||||||
|
var indexes = [];
|
||||||
|
for ( var i in data )
|
||||||
|
{
|
||||||
|
// we do *not* want to reset fields that have been removed
|
||||||
|
if ( cur[ i ] === undefined )
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
indexes.push( i );
|
||||||
|
}
|
||||||
|
|
||||||
|
reset[ name ] = indexes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// batch reset (limit the number of times events are kicked off)
|
||||||
|
this._resetFields( reset );
|
||||||
|
|
||||||
|
// we've done our deed; reset it for the next time around
|
||||||
|
this._cmatchHidden = {};
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
'private _resetFields': function( fields )
|
||||||
|
{
|
||||||
|
const quote = this._client.getQuote();
|
||||||
|
const update = {};
|
||||||
|
|
||||||
|
for ( var field in fields )
|
||||||
|
{
|
||||||
|
var cur = fields[ field ],
|
||||||
|
cdata = quote.getDataByName( field ),
|
||||||
|
val = this._client.elementStyler.getDefault( field );
|
||||||
|
|
||||||
|
var data = [];
|
||||||
|
for ( var i in cur )
|
||||||
|
{
|
||||||
|
var index = cur[ i ];
|
||||||
|
|
||||||
|
if ( cdata[ index ] === val )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
data[ index ] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
update[ field ] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
quote.setData( update );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force handling of the most recent cmatch data
|
||||||
|
*
|
||||||
|
* This can be used to refresh the UI to ensure that it is consistent with
|
||||||
|
* the cmatch data.
|
||||||
|
*
|
||||||
|
* @return {Client} self
|
||||||
|
*/
|
||||||
|
'public forceCmatchAction': function()
|
||||||
|
{
|
||||||
|
if ( !( this._cmatch ) )
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._handleClassMatch( this._cmatch, true );
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get matches from last classifier application
|
||||||
|
*
|
||||||
|
* TODO: Remove me; breaks encapsulation. Intended for transition from
|
||||||
|
* mammoth Client.
|
||||||
|
*
|
||||||
|
* @return {Object} classification matches
|
||||||
|
*/
|
||||||
|
'public getMatches'()
|
||||||
|
{
|
||||||
|
return this._cmatch;
|
||||||
|
},
|
||||||
|
} );
|
|
@ -21,6 +21,8 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
const Cmatch = require( '../client/Cmatch' );
|
||||||
|
const field = require( '../field' );
|
||||||
const store = require( '../store' );
|
const store = require( '../store' );
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,6 +35,12 @@ const store = require( '../store' );
|
||||||
* This is incomplete; it will be added to as code is ported to liza.
|
* This is incomplete; it will be added to as code is ported to liza.
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
cmatch: ( program, client ) => Cmatch(
|
||||||
|
field.FieldClassMatcher( program.whens ),
|
||||||
|
program,
|
||||||
|
client
|
||||||
|
),
|
||||||
|
|
||||||
data: {
|
data: {
|
||||||
/**
|
/**
|
||||||
* Create a store suitable for comparing diffs
|
* Create a store suitable for comparing diffs
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
/**
|
||||||
|
* Test case for Cmatch
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 R-T Specialty, LLC.
|
||||||
|
*
|
||||||
|
* This file is part of the Liza Data Collection Framework
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { event } = require( '../../' ).client;
|
||||||
|
const { expect } = require( 'chai' );
|
||||||
|
|
||||||
|
const Sut = require( '../../src/client/Cmatch' )
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
'override constructor'( _, __, ___ ) {},
|
||||||
|
|
||||||
|
// make public
|
||||||
|
'override public markShowHide'( field, visq, show, hide )
|
||||||
|
{
|
||||||
|
return this.__super( field, visq, show, hide );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
// these tests aren't terribly effective right now
|
||||||
|
describe( "Cmatch", () =>
|
||||||
|
{
|
||||||
|
it( "marks hidden fields on class change to show", () =>
|
||||||
|
{
|
||||||
|
expect(
|
||||||
|
Sut().markShowHide( 'foo', {}, [ 1, 2 ], [] )
|
||||||
|
).to.deep.equal( { foo: { show: [ 1, 2 ] } } );
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
it( "marks shown fields on class change to hide", () =>
|
||||||
|
{
|
||||||
|
expect(
|
||||||
|
Sut().markShowHide( 'foo', {}, [], [ 3, 4, 5 ] )
|
||||||
|
).to.deep.equal( { foo: { hide: [ 3, 4, 5 ] } } );
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
it( "marks combination show/hide on class change", () =>
|
||||||
|
{
|
||||||
|
expect(
|
||||||
|
Sut().markShowHide( 'foo', {}, [ 2, 3 ], [ 4, 5, 6 ] )
|
||||||
|
).to.deep.equal( {
|
||||||
|
foo: {
|
||||||
|
show: [ 2, 3 ],
|
||||||
|
hide: [ 4, 5, 6 ],
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
it( "marks no fields with no show or hide", () =>
|
||||||
|
{
|
||||||
|
expect(
|
||||||
|
Sut().markShowHide( 'foo', {}, [], [] )
|
||||||
|
).to.deep.equal( {} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
it( "does not affect marking of other fields", () =>
|
||||||
|
{
|
||||||
|
const barval = {};
|
||||||
|
const visq = { bar: barval };
|
||||||
|
|
||||||
|
Sut().markShowHide( 'foo', {}, [ 1 ], [ 0 ] );
|
||||||
|
|
||||||
|
expect( visq.bar ).to.equal( barval );
|
||||||
|
} );
|
||||||
|
} );
|
Loading…
Reference in New Issue