/** * Step abstraction * * Copyright (C) 2015 LoVullo Associates, Inc. * * This file is part of liza. * * liza is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * @needsLove * - References to "quote" should be replaced with generic terminology * representing a document. * - Sorting logic must be extracted, and MultiSort decoupled. * @end needsLove */ var Class = require( 'easejs' ).Class, EventEmitter = require( 'events' ).EventEmitter, // XXX: tightly coupled MultiSort = require( '../sort/MultiSort' ); /** * Represents a single step to be displayed in the UI */ module.exports = Class( 'Step' ) .extend( EventEmitter, { /** * Called when quote is changed * @type {string} */ 'const EVENT_QUOTE_UPDATE': 'updateQuote', /** * Step identifier * @type {number} */ 'private _id': 0, /** * Data bucket to store the raw data for submission * @type {StepDataBucket} */ 'private _bucket': null, /** * Fields contained exclusively on the step (no linked) * @type {Object} */ 'private _exclusiveFields': {}, /** * Fields that must contain a value * @type {Object} */ 'private _requiredFields': {}, /** * Whether all fields on the step contain valid data * @type {boolean} */ 'private _valid': true, /** * Explanation of what made the step valid/invalid, if applicable * * This is useful for error messages * * @type {string} */ 'private _validCause': '', /** * Sorted group sets * @type {Object} */ 'private _sortedGroups': {}, /** * Initializes step * * @param {number} id step identifier * @param {ClientQuote} quote quote to contain step data * * @return {undefined} */ 'public __construct': function( id, quote ) { var _self = this; this._id = +id; // TODO: this is temporary; do not pass bucket, pass quote quote.visitData( function( bucket ) { _self._bucket = bucket; } ); }, /** * Returns the numeric step identifier * * @return Integer step identifier */ 'public getId': function() { return this._id; }, /** * Return the bucket associated with this step * * XXX: Remove me; breaks encapsulation. * * @return {Bucket} bucket associated with step */ 'public getBucket': function() { return this._bucket; }, /** * Set whether or not the data on the step is valid * * @param {boolean} valid whether the step contains only valid data * * @return {Step} self */ 'public setValid': function( valid, cause ) { this._valid = !!valid; this._validCause = cause; return this; }, /** * Returns whether all the elements in the step contain valid data * * @return Boolean true if all elements are valid, otherwise false */ 'public isValid': function( cmatch ) { if ( !cmatch ) { throw Error( 'Missing cmatch data' ); } return this._valid && ( this.getNextRequired( cmatch ) === null ); }, 'public getValidCause': function() { return this._validCause; }, /** * Retrieve the next required value that is empty * * Aborts on first missing required field with its name and index. * * @param {Object} cmatch cmatch data * * @return {!Array.} first missing required field */ 'public getNextRequired': function( cmatch ) { cmatch = cmatch || {}; // check to ensure that each required field has a value in the bucket for ( var name in this._requiredFields ) { var data = this._bucket.getDataByName( name ), cdata = cmatch[ name ]; // a non-empty string indicates that the data is missing (absense of // an index has no significance) for ( var i in data ) { // any falsy value will be considered empty (note that !"0" === // false, so this will work) if ( !data[ i ] && ( data[ i ] !== 0 ) ) { if ( !cdata || ( cdata && cdata.indexes[ i ] ) ) { return [ name, i ]; } } } } // all required fields have values return null; }, /** * Sets a new bucket to be used for data storage and retrieval * * @param {QuoteDataBucket} bucket new bucket * * @return {Step} self */ 'public updateQuote': function( quote ) { // todo: Temporary var _self = this, bucket = null; quote.visitData( function( quote_bucket ) { bucket = quote_bucket; } ); _self._bucket = bucket; _self.emit( this.__self.$('EVENT_QUOTE_UPDATE') ); return this; }, /** * Adds field names exclusively contained on this step (no linked) * * @param {Array.} fields field names * * @return {StepUi} self */ 'public addExclusiveFieldNames': function( fields ) { var i = fields.length; while ( i-- ) { this._exclusiveFields[ fields[ i ] ] = true; } return this; }, /** * Retrieve list of field names (no linked) * * @return {Object.} field names */ 'public getExclusiveFieldNames': function() { return this._exclusiveFields; }, /** * Set names of fields that must contain a value * * @param {Object} required required field names * * @return {StepUi} self */ 'public setRequiredFieldNames': function( required ) { this._requiredFields = required; return this; }, 'public setSortedGroupSets': function( sets ) { this._sortedGroups = sets; return this; }, 'public eachSortedGroupSet': function( c ) { var sets = {}; var data = []; for ( var id in this._sortedGroups ) { // call continuation with each sorted set containing the group ids c( this._processSortedGroup( this._sortedGroups[ id ] ) ); } }, 'private _processSortedGroup': function( group_data ) { var data = []; for ( var i in group_data ) { var cur = group_data[ i ], name = cur[ 0 ], fields = cur[ 1 ]; // get data for each of the fields var fdata = []; for ( var i in fields ) { fdata.push( this._bucket.getDataByName( fields[ i ] ) ); } data.push( [ name, fdata ] ); } var toint = [ 0, 0, 1 ]; function pred( i, a, b ) { var vala = a[ 1 ][ i ][ 0 ], valb = b[ 1 ][ i ][ 0 ]; // convert to numeric if it makes sense to do so (otherwise, we may // be comparing them as strings, which does not quite give us the // ordering we desire) if ( toint[ i ] ) { vala = +vala; valb = +valb; } if ( vala > valb ) { return 1; } else if ( vala < valb ) { return -1; } return 0; } // generate predicates var preds = []; for ( var i in group_data[ 0 ][ 1 ] ) { ( function( i ) { preds.push( function( a, b ) { return pred( i, a, b ); } ); } )( i ); } // sort the data var sorted = MultiSort().sort( data, preds ); // return the group names var ret = []; for ( var i in sorted ) { // add name ret.push( sorted[ i ][ 0 ] ); } return ret; } } );