diff --git a/src/event/Cvv2DialogEventHandler.js b/src/event/Cvv2DialogEventHandler.js
new file mode 100644
index 0000000..16aceba
--- /dev/null
+++ b/src/event/Cvv2DialogEventHandler.js
@@ -0,0 +1,77 @@
+/**
+ * Cvv2DialogEventHandler
+ *
+ * Copyright (C) 2017 LoVullo Associates, Inc.
+ *
+ * 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 .
+ *
+ * TODO: This can be generalized.
+ */
+
+var Class = require( 'easejs' ).Class,
+ EventHandler = require( './EventHandler' );
+
+
+/**
+ * Shows Cvv2 Dialog
+ */
+module.exports = Class( 'Cvv2DialogEventHandler' )
+ .implement( EventHandler )
+ .extend(
+{
+ /**
+ * jQuery instance
+ *
+ * @type {jQuery}
+ */
+ 'private _jquery': null,
+
+
+ /**
+ * Initializes with client that will delegate the event
+ *
+ * @param {jQuery} jquery jquery instance
+ */
+ __construct: function( jquery )
+ {
+ this._jquery = jquery;
+ },
+
+
+ /**
+ * Handles kick-back
+ *
+ * @param {string} type event id; ignored
+ *
+ * @param {function(*,Object)} continuation to invoke on completion
+ *
+ * @return {StatusEventHandler} self
+ */
+ 'public handle': function( type, c, data )
+ {
+ var $dialog = this._jquery( '#' + data.ref ).dialog( {
+ title: "CVV/CSV Verification Information",
+ width: "500px",
+ resizable: false,
+ buttons: {
+ 'Close': function()
+ {
+ $dialog.dialog( 'close' );
+ }
+ }
+ } );
+ }
+} );
diff --git a/src/event/DelegateEventHandler.js b/src/event/DelegateEventHandler.js
new file mode 100644
index 0000000..0ca9859
--- /dev/null
+++ b/src/event/DelegateEventHandler.js
@@ -0,0 +1,124 @@
+/**
+ * Event handler proxy
+ *
+ * Copyright (C) 2017 LoVullo Associates, Inc.
+ *
+ * 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 .
+ */
+
+var Class = require( 'easejs' ).Class,
+ EventHandler = require( './EventHandler' ),
+ UnknownEventError = require( './UnknownEventError' );
+
+
+/**
+ * Delegates events to an apropriate handler
+ *
+ * Handlers must be registered to recgonize an event.
+ */
+module.exports = Class( 'DelegateEventHandler' )
+ .implement( EventHandler )
+ .extend(
+{
+ /**
+ * Hash of event handlers to delegate to for various events
+ * @type {Object}
+ */
+ 'private _handlers': {},
+
+
+ /**
+ * Initialize delegate with handlers to delegate requests to for each
+ * supported event type
+ *
+ * @param {Object} handlers events as keys, handlers as values
+ */
+ __construct: function( handlers )
+ {
+ // register each provided handler
+ for ( var type in handlers )
+ {
+ this._addHandler( type, handlers[ type ] );
+ }
+ },
+
+
+ /**
+ * Determines if a handler has been registered for the given type
+ *
+ * @param {string} type event id
+ *
+ * @return {boolean} whether a handler exists for the given type
+ */
+ 'public hasHandler': function( type )
+ {
+ return ( this._handlers[ type ] !== undefined );
+ },
+
+
+ /**
+ * Handle an event of the given type
+ *
+ * An exception will be thrown if the event cannot be handled.
+ *
+ * The handler should always return itself; if a return value is needed to
+ * the caller, then a callback should be provided as an argument to the
+ * handler.
+ *
+ * Additional arguments will be passed to the appropriate handler.
+ *
+ * @param {string} type event id
+ *
+ * @return {EventHandler} self
+ */
+ 'public handle': function( type )
+ {
+ var handler = this._handlers[ type ];
+
+ // fail if we do not have a handler for this particular event
+ if ( !handler )
+ {
+ throw UnknownEventError( "Unsupported event type: " + type );
+ }
+
+ // delegate
+ handler.handle.apply( handler, arguments );
+
+ return this;
+ },
+
+
+ /**
+ * Add an EventHandler for a given event type
+ *
+ * Warning: this will overwrite existing handlers for this type.
+ *
+ * @param {string} type event id
+ * @param {EventHandler} handler handler for event
+ *
+ * @return {ClientEventHandler} self
+ */
+ 'private _addHandler': function( type, handler )
+ {
+ if ( !( Class.isA( EventHandler, handler ) ) )
+ {
+ throw TypeError( "Expected EventHandler for event type " + type );
+ }
+
+ this._handlers[ type ] = handler;
+ return this;
+ }
+} );
diff --git a/src/event/EventHandler.js b/src/event/EventHandler.js
new file mode 100644
index 0000000..41c8552
--- /dev/null
+++ b/src/event/EventHandler.js
@@ -0,0 +1,41 @@
+/**
+ * Event handling interface
+ *
+ * Copyright (C) 2017 LoVullo Associates, Inc.
+ *
+ * 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 .
+ */
+
+var Interface = require( 'easejs' ).Interface;
+
+
+module.exports = Interface( 'EventHandler',
+{
+ /**
+ * Handle an event of the given type
+ *
+ * An exception should be thrown if the event cannot be handled.
+ *
+ * The handler should always return itself; if a return value is needed to
+ * the caller, then a callback should be provided as an argument to the
+ * handler.
+ *
+ * @param {string} type event id
+ *
+ * @return {EventHandler} self
+ */
+ 'public handle': [ 'type' /*, ... */ ]
+} );
diff --git a/src/event/IndvRateEventHandler.js b/src/event/IndvRateEventHandler.js
new file mode 100644
index 0000000..cc95f8e
--- /dev/null
+++ b/src/event/IndvRateEventHandler.js
@@ -0,0 +1,53 @@
+/**
+ * Invidiual rate request event handler
+ *
+ * Copyright (C) 2017 LoVullo Associates, Inc.
+ *
+ * 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 .
+ */
+
+var Class = require( 'easejs' ).Class,
+ RateEventHandler = require( './RateEventHandler' );
+
+
+/**
+ * Performs rate requests
+ */
+module.exports = Class( 'IndvRateEventHandler' )
+ .extend( RateEventHandler,
+{
+ 'override protected postRate': function( err, data, client, quote )
+ {
+ if ( err )
+ {
+ var inelig = {};
+ inelig[ action.id + '_ineligible' ] = 'Error calculating premium.';
+
+ // uh oh. notify the user that there was a problem.
+ quote.setData( inelig ).save();
+ }
+
+ // we must force a screen update (TODO: make this unnecessary)
+ client.getUi().getCurrentStep().emptyBucket();
+ },
+
+
+ 'override protected queueProgressDialog': function( after_ms )
+ {
+ // no dialog.
+ return function() {};
+ }
+} );
diff --git a/src/event/KickbackEventHandler.js b/src/event/KickbackEventHandler.js
new file mode 100644
index 0000000..21b52d8
--- /dev/null
+++ b/src/event/KickbackEventHandler.js
@@ -0,0 +1,82 @@
+/**
+ * Kickback event handler
+ *
+ * Copyright (C) 2017 LoVullo Associates, Inc.
+ *
+ * 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 .
+ */
+
+var Class = require( 'easejs' ).Class,
+ EventHandler = require( './EventHandler' );
+
+
+/**
+ * Performs rate requests
+ */
+module.exports = Class( 'KickbackEventHandler' )
+ .implement( EventHandler )
+ .extend(
+{
+ /**
+ * Client that will perform requests on this handler
+ * @type {Client}
+ */
+ 'private _client': null,
+
+
+ /**
+ * Initializes with client that will delegate the event
+ *
+ * @param {Client} client client object
+ */
+ __construct: function( client )
+ {
+ this._client = client;
+ },
+
+
+ /**
+ * Handles kick-back
+ *
+ * @param {string} type event id; ignored
+ *
+ * @param {function(*,Object)} continuation to invoke on completion
+ *
+ * @return {KickbackEventHandler} self
+ */
+ 'public handle': function( type, c, data )
+ {
+ var step_id = +data.stepId,
+ quote = this._client.getQuote(),
+ nav = this._client.nav,
+ ui = this._client.getUi();
+
+ if ( quote.getTopVisitedStepId() > step_id )
+ {
+ quote.setTopVisitedStepId( step_id );
+ nav.setTopVisitedStepId( step_id );
+
+ if ( quote.getCurrentStepId() > step_id )
+ {
+ nav.navigateToStep( step_id );
+ }
+
+ ui.redrawNav();
+ }
+
+ c();
+ }
+} );
diff --git a/src/event/RateEventHandler.js b/src/event/RateEventHandler.js
new file mode 100644
index 0000000..ef233da
--- /dev/null
+++ b/src/event/RateEventHandler.js
@@ -0,0 +1,233 @@
+/**
+ * Rate event handler
+ *
+ * Copyright (C) 2017 LoVullo Associates, Inc.
+ *
+ * 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 .
+ */
+
+var Class = require( 'easejs' ).Class,
+ EventHandler = require( './EventHandler' );
+
+
+/**
+ * Performs rate requests
+ */
+module.exports = Class( 'RateEventHandler' )
+ .implement( EventHandler )
+ .extend(
+{
+ /**
+ * Number of milliseconds to delay rating progress dialog display
+ * @type {number}
+ */
+ 'private const _DIALOG_DELAY_MS': 500,
+
+ /**
+ * Client that will perform requests on this handler
+ * @type {Client}
+ */
+ 'private _client': null,
+
+ /**
+ * Data proxy used for requests
+ * @type {ClientDataProxy}
+ */
+ 'private _dataProxy': null,
+
+
+ /**
+ * Initializes event handler with a data proxy that may be used to
+ * communicate with a remote server for rate requests
+ *
+ * @param {Client} client client object
+ * @param {ClientDataProxy} data proxy used for rate requests
+ */
+ __construct: function( client, data_proxy )
+ {
+ this._client = client;
+ this._dataProxy = data_proxy;
+ },
+
+
+ /**
+ * Handle rating request and performs rating
+ *
+ * If the quote is locked, no rating request will be made and the
+ * continuation will be immediately invoked with no error and null rate
+ * data.
+ *
+ * If the client is in the process of saving, then rating will be deferred
+ * until all save operations have completed, after which rating will proceed
+ * as normal.
+ *
+ * After rating is complete, the coninuation will be invoked. If there is an
+ * error, it will be provided as the first argument and the data argument
+ * will be null. Otherwise, the error argument will be null and the data
+ * argument will contain the result of the rate call. Note that the quote
+ * will be automatically filled with the return data.
+ *
+ * @param {string} type event id; ignored
+ *
+ * @param {function(*,Object)} continuation to invoke on completion
+ *
+ * @return {RateEventHandler} self
+ */
+ 'public handle': function( type, c, data )
+ {
+ var _self = this,
+ quote = this._client.getQuote(),
+ qstep = quote.getCurrentStepId();
+
+ // do not perform rating if quote is locked; use existing rates, if
+ // available (stored in bucket)
+ if ( quote.isLocked()
+ || ( qstep <= quote.getExplicitLockStep() )
+ )
+ {
+ // no error, no data.
+ c( null, null );
+ return;
+ }
+
+ // if we're in the process of saving, then wait until all saves are
+ // complete before continuing
+ if ( this._client.isSaving() )
+ {
+ // defer rating until after saving is complete
+ this._client.once( 'postSaveAll', function()
+ {
+ dorate();
+ } );
+
+ return;
+ }
+
+ function dorate()
+ {
+ _self._performRating( quote, c, data.indv, data.stepId );
+ }
+
+ // perform rating immediately
+ dorate();
+
+ return this;
+ },
+
+
+ 'private _performRating': function( quote, c, indv, dest_step_id )
+ {
+ var _self = this;
+
+ // queue display of "rating in progress" dialog
+ var dialog_close = this.queueProgressDialog(
+ this.__self.$( '_DIALOG_DELAY_MS' )
+ );
+
+ // grab the rates from the server for the already posted quote data
+ this._dataProxy.get( this._genRateUrl( quote, indv ),
+ function( response, err )
+ {
+ var data = ( response.content.data || {} );
+
+ if ( err )
+ {
+ // error; do not provide rate data
+ c( err, null );
+ return;
+ }
+
+ // fill the bucket with the response data and save (client-side
+ // save only; no transport is specified)
+ quote.refreshData( data );
+
+ // let subtypes handle additional processing
+ _self.postRate( err, data, _self._client, quote );
+
+ // close rating dialog after rates are displayed
+ dialog_close();
+
+ // invalidate the step to force emptying of the bucket, ensuring
+ // that data is updated on the screen when it's re-rated (will
+ // be undefined if the step hasn't been loaded yet, in which
+ // case it doesn't need to be invalidated)
+ var stepui = _self._client.getUi().getStep( dest_step_id );
+ if ( stepui !== undefined )
+ {
+ stepui.invalidate();
+ }
+
+ c( null, response.content.data );
+ }
+ );
+ },
+
+
+ 'virtual protected postRate': function( err, data, client, quote )
+ {
+ // reserved for subtypes
+ },
+
+
+ /**
+ * Generate the rate request URL
+ *
+ * @param {Quote} quote to get premium for
+ * @param {string} indv optional individual supplier to request
+ *
+ * @return {string} request URL
+ */
+ 'private _genRateUrl': function( quote, indv )
+ {
+ return quote.getId() + '/rate' +
+ ( ( indv )
+ ? '/' + indv
+ : ''
+ );
+ },
+
+
+ /**
+ * Queue "rating in progress" dialog for display after a short period of
+ * time
+ *
+ * The dialog may be closed/cancelled by invoking the returned function.
+ *
+ * @param {number} after_ms display dialog after this number of millisecs
+ *
+ * @return {function()} function to close dialog
+ */
+ 'virtual protected queueProgressDialog': function( after_ms )
+ {
+ var _self = this,
+ $dialog = null;
+
+ // only display the dialog if the rating time exceeds 500ms
+ var timeout = setTimeout( function()
+ {
+ $dialog = _self._client.uiDialog.showRatingInProgressDialog();
+ }, after_ms );
+
+ // return a function that may be used to close the dialog
+ return function()
+ {
+ // prevent the dialog from being displayed and close it if it has
+ // already been displayed
+ clearTimeout( timeout );
+ $dialog && $dialog.close();
+ }
+ }
+} );
diff --git a/src/event/StatusEventHandler.js b/src/event/StatusEventHandler.js
new file mode 100644
index 0000000..95d3431
--- /dev/null
+++ b/src/event/StatusEventHandler.js
@@ -0,0 +1,81 @@
+/**
+ * Field status event handler
+ *
+ * Copyright (C) 2017 LoVullo Associates, Inc.
+ *
+ * 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 .
+ */
+
+var Class = require( 'easejs' ).Class,
+ EventHandler = require( './EventHandler' );
+
+
+/**
+ * Performs rate requests
+ */
+module.exports = Class( 'StatusEventHandler' )
+ .implement( EventHandler )
+ .extend(
+{
+ /**
+ * Styler used to manipulate the DOM
+ *
+ * TODO: deprecate
+ *
+ * @type {ElementStyler}
+ */
+ 'private _styler': null,
+
+
+ /**
+ * Initializes with client that will delegate the event
+ *
+ * @param {ElementStyler} styler element styler
+ */
+ __construct: function( styler )
+ {
+ this._styler = styler;
+ },
+
+
+ /**
+ * Handles kick-back
+ *
+ * @param {string} type event id; ignored
+ *
+ * @param {function(*,Object)} continuation to invoke on completion
+ *
+ * @return {StatusEventHandler} self
+ */
+ 'public handle': function( type, c, data )
+ {
+ var indexes = data.indexes || [],
+ value = data.value || '',
+ name = data.elementName;
+
+ for ( var i in indexes )
+ {
+ // string means a static value; otherwise, an array
+ // represents bucket values, of which we should take the
+ // associated index
+ var value = ( typeof value === 'string' )
+ ? value
+ : value[ i ];
+
+ this._styler.setStatus( name, indexes[ i ], value );
+ }
+ }
+} );
diff --git a/src/event/UnknownEventError.js b/src/event/UnknownEventError.js
new file mode 100644
index 0000000..da346c8
--- /dev/null
+++ b/src/event/UnknownEventError.js
@@ -0,0 +1,37 @@
+/**
+ * Contains UnknownEventError
+ *
+ * Copyright (C) 2017 LoVullo Associates, Inc.
+ *
+ * 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 .
+ */
+
+module.exports = function UnknownEventError( msg )
+{
+ // 'new' keyword optional
+ if ( !( this instanceof module.exports ) )
+ {
+ return new module.exports( msg );
+ }
+
+ Error.prototype.constructor.apply( this, arguments );
+};
+
+// extends TypeError
+module.exports.constructor = module.exports;
+module.exports.prototype = TypeError();
+module.exports.prototype.name = 'UnknownEventError';
+