Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Gerwitz <mike.gerwitz@rtspecialty.com>2018-02-09 10:34:34 -0500
committerMike Gerwitz <mike.gerwitz@rtspecialty.com>2018-02-09 11:20:45 -0500
commit4b114d01379f0618a210388875a3d062f36771c4 (patch)
tree1a58792c29ea9805d29a32daaeaef666db028d74
parentcc7370711da9f06d899a27f887d6791f9c76c3c0 (diff)
downloadliza-4b114d01379f0618a210388875a3d062f36771c4.tar.gz
liza-4b114d01379f0618a210388875a3d062f36771c4.tar.bz2
liza-4b114d01379f0618a210388875a3d062f36771c4.zip
Client: Extract cmatch methods
This makes the minimal number of changes necessary to ensure that all object references remain available. It is a mess. And despite moving all of this, Client is still a massive clusterfuck. * src/client/Client.js (system): Add import. (_cmatch): Now references new Cmatch class instance. (_cmatchHidden, _classMatcher): Remove fields. (_forceCmatchAction): Rename to `forceCmatchAction'. (_hookClassifier, _postProcessCmatch, _cmatchVisFromUi, _handleClassMatch, _mergeCmatchHidden, _clearCmatchFields): Extract methods. Update references as necessary. (getCmatchData): Remove unused method. (_handleError): Rename to `handleError' to make accessible to Cmatch. Update references. * src/client/ClientDependencyFactory.js (FieldClassMatcher): Remove import. (createFieldClassMatcher): Remove method. See `system/client'. * src/client/Cmatch.js: New class. * src/system/client.js (Cmatch, field): Add imports. (cmatch): Add export.
-rw-r--r--src/client/Client.js401
-rw-r--r--src/client/ClientDependencyFactory.js11
-rw-r--r--src/client/Cmatch.js482
-rw-r--r--src/system/client.js10
4 files changed, 517 insertions, 387 deletions
diff --git a/src/client/Client.js b/src/client/Client.js
index 29fcf56..2ed3748 100644
--- a/src/client/Client.js
+++ b/src/client/Client.js
@@ -23,6 +23,7 @@ const Class = require( 'easejs' ).Class;
const EventEmitter = require( 'events' ).EventEmitter;
const DomFieldNotFoundError = require( '../ui/field/DomFieldNotFoundError' );
const UnknownEventError = require( './event/UnknownEventError' );
+const system = require( '../system/client' );
/**
@@ -198,30 +199,10 @@ module.exports = Class( 'Client' )
'private _validatorFormatter': null,
/**
- * Contains classification match data per field
- *
- * TODO: Move this to somewhere more appropriate
- *
- * @type {Object}
- */
- 'private _cmatch': {},
-
- /**
- * Performs classification matching on fields
- *
- * A field will have a positive match for a given index if all of its
- * classes match
- *
- * @type {FieldClassMatcher}
+ * Classification match handler
+ * @type {Cmatch}
*/
- 'private _classMatcher': null,
-
-
- /**
- * Fields that were hidden (including indexes) since the last cmatch clear
- * @type {Object}
- */
- 'private _cmatchHidden': {},
+ 'private _cmatch': null,
/**
* Automatically discards staging bucket contents
@@ -347,9 +328,7 @@ module.exports = Class( 'Client' )
this, this._dataValidator, this.elementStyler, this.dataProxy, jQuery
);
- this._classMatcher = this._factory.createFieldClassMatcher(
- this.program.whens
- );
+ this._cmatch = system.cmatch( this.program, this.__inst );
this._validatorFormatter = this._factory.createValidatorFormatter(
this.program.meta.qtypes
@@ -428,7 +407,7 @@ module.exports = Class( 'Client' )
{
client.ui.displayStep( step_id, function()
{
- client.forceCmatchAction();
+ client._cmatch.forceCmatchAction();
} );
client._currentStepId = step_id;
@@ -503,7 +482,7 @@ module.exports = Class( 'Client' )
client._quote.setQuickSaveData( data.content.quicksave || {} );
- client._hookClassifier();
+ client._cmatch.hookClassifier( client._dataValidator );
// store internal status
client._isInternal = client.program.isInternal =
@@ -557,285 +536,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
*
* @return {undefined}
@@ -862,7 +562,7 @@ module.exports = Class( 'Client' )
name,
bucket,
diff,
- this._cmatch,
+ this._cmatch.getMatches(),
function()
{
var args = arguments;
@@ -937,7 +637,7 @@ module.exports = Class( 'Client' )
{
// N.B.: We pass {} as the diff because nothing has actually changed
_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
// that need to be shown/hidden based on the current set of
// classifications
- this.forceCmatchAction();
+ this._cmatch.forceCmatchAction();
},
@@ -1049,7 +749,7 @@ module.exports = Class( 'Client' )
catch ( e )
{
// todo: better suited for brokers
- this._handleError( Error(
+ this.handleError( Error(
"Error loading program data: " + e.message
) );
@@ -1058,7 +758,7 @@ module.exports = Class( 'Client' )
program.on( 'error', function( e )
{
- _self._handleError( e );
+ _self.handleError( e );
} );
// handle field updates
@@ -1158,7 +858,7 @@ module.exports = Class( 'Client' )
} )
.on( 'error', function( e )
{
- _self._handleError( e );
+ _self.handleError( e );
} );
return program;
@@ -1412,7 +1112,7 @@ module.exports = Class( 'Client' )
// handle context errors
root_context.on( 'error', function( e )
{
- client._handleError( e );
+ client.handleError( e );
} );
// must init after the Ui obj is available
@@ -1472,7 +1172,7 @@ module.exports = Class( 'Client' )
} )
.on( 'error', function( e )
{
- client._handleError( e );
+ client.handleError( e );
} );
return ui.saveStep( function( stepui )
@@ -1929,7 +1629,7 @@ module.exports = Class( 'Client' )
var client = this;
// 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
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
// 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
// test server-side functionality
@@ -1959,7 +1659,7 @@ module.exports = Class( 'Client' )
// shouldn't occurr, we should still throw an exception if one is
// triggered
var failures = this.program.submit(
- step_id, bucket, this._cmatch
+ step_id, bucket, this._cmatch.getMatches()
);
if ( failures !== null )
@@ -2072,7 +1772,7 @@ module.exports = Class( 'Client' )
{
event.abort();
- _self._handleError( Error(
+ _self.handleError( Error(
'Save timeout; please try again'
) );
}, 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
*
@@ -2449,7 +2098,7 @@ module.exports = Class( 'Client' )
var failures = this.program.forward(
cur_step_id,
bucket,
- this._cmatch,
+ this._cmatch.getMatches(),
function( trigger_event, question_id, value )
{
client.handleEvent( trigger_event, { stepId: +value } );
@@ -2830,7 +2479,7 @@ module.exports = Class( 'Client' )
// proxy errors
this._fieldMonitor.on( 'error', function( e )
{
- _self._handleError( e );
+ _self.handleError( e );
} );
},
@@ -2864,7 +2513,7 @@ module.exports = Class( 'Client' )
*
* @return {undefined}
*/
- 'private _handleError': function( e )
+ 'public handleError': function( e )
{
if ( !e )
{
@@ -2928,10 +2577,4 @@ module.exports = Class( 'Client' )
{
return this.program.id;
},
-
-
- 'public getCmatchData': function()
- {
- return this._cmatch;
- }
} );
diff --git a/src/client/ClientDependencyFactory.js b/src/client/ClientDependencyFactory.js
index 03b2682..2ca86eb 100644
--- a/src/client/ClientDependencyFactory.js
+++ b/src/client/ClientDependencyFactory.js
@@ -82,9 +82,8 @@ var Step = require( '../step/Step' ),
NavStyler = require( '../ui/nav/NavStyler' ),
Sidebar = require( '../ui/sidebar/Sidebar' ),
- FieldClassMatcher = require( '../field/FieldClassMatcher' ),
- DataApiFactory = require( '../dapi/DataApiFactory' ),
- DataApiManager = require( '../dapi/DataApiManager' ),
+ DataApiFactory = require( '../dapi/DataApiFactory' ),
+ DataApiManager = require( '../dapi/DataApiManager' ),
RootDomContext = require( '../ui/context/RootDomContext' ),
DomFieldFactory = require( '../ui/field/DomFieldFactory' ),
@@ -345,8 +344,6 @@ module.exports = Class( 'ClientDependencyFactory',
createNotifyBar: UiNotifyBar,
- createFieldClassMatcher: FieldClassMatcher,
-
createClientEventHandler: function(
client, data_validator, styler, data_proxy, jquery
@@ -373,5 +370,5 @@ module.exports = Class( 'ClientDependencyFactory',
'action$cvv2Dialog': requireh( 'Cvv2DialogEventHandler' )( jquery )
} );
- }
-});
+ },
+} );
diff --git a/src/client/Cmatch.js b/src/client/Cmatch.js
new file mode 100644
index 0000000..5766da3
--- /dev/null
+++ b/src/client/Cmatch.js
@@ -0,0 +1,482 @@
+/**
+ * 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 );
+ }
+
+ 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._client.handleEvent( event_id, {
+ elementName: name,
+ indexes: indexes,
+ } );
+
+ this._dapiTrigger( name );
+ } );
+ }, 25 );
+ },
+
+
+ /**
+ * 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;
+ },
+} );
diff --git a/src/system/client.js b/src/system/client.js
index 18652b5..a1e109c 100644
--- a/src/system/client.js
+++ b/src/system/client.js
@@ -21,7 +21,9 @@
"use strict";
-const store = require( '../store' );
+const Cmatch = require( '../client/Cmatch' );
+const field = require( '../field' );
+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.
*/
module.exports = {
+ cmatch: ( program, client ) => Cmatch(
+ field.FieldClassMatcher( program.whens ),
+ program,
+ client
+ ),
+
data: {
/**
* Create a store suitable for comparing diffs