Source: br-presenter/src/br/presenter/util/ErrorMonitor.js

'use strict';

var ToolTipField = require('br/presenter/node/ToolTipField');
var PropertyHelper = require('br/presenter/property/PropertyHelper');
var EditableProperty = require('br/presenter/property/EditableProperty');
var Errors = require('br/Errors');
var ToolTipNode = require('br/presenter/node/ToolTipNode');

/**
 * @module br/presenter/util/ErrorMonitor
 */

/**
 * @class
 * @alias module:br/presenter/util/ErrorMonitor
 * 
 * @classdesc
 * ErrorMonitor is responsible for monitoring the status of fields that contain
 * an error and failure message property. It is used to set the tool tip class that
 * the tooltip control will scan in order to create the tool tip box.
 *
 * @param {module:br/presenter/node/ToolTipNode} Node representing the tool tip model.
 */
function ErrorMonitor(oTooltipNode) {
	if (!oTooltipNode || !(oTooltipNode instanceof ToolTipNode)) {
		throw new Errors.CustomError(Errors.INVALID_PARAMETERS, 'The ErrorMonitor has to be constructed with an instance of a br.presenter.node.ToolTipNode');
	}

	this.m_oPropertyHelper = new PropertyHelper();

	this.oTooltipNode = oTooltipNode;

	this.m_pErrorStack = [];

	this.hasError = new EditableProperty(false);
}

/**
 *
 * Filters all nodes of type {@see br.presenter.node.ToolTipField} and monitors them using
 * {@see br.presenter.util.ErrorMonitor#monitorField}
 *
 * @type {br.presenter.node.PresentationNode[]}
 */
ErrorMonitor.prototype.addErrorListeners = function(pGroups) {
	var node;
	for (var i = 0; i < pGroups.length; i++) {
		node = pGroups[i];
		if (node instanceof ToolTipField) {
			this.monitorField(node);
		}
	}
};

/**
 * removes all error subscriptions for the given fields
 * @param {Array} pGroups fields we want to forget
 */
ErrorMonitor.prototype.removeErrorListeners = function(pGroups) {
	for (var i = 0; i < pGroups.length; i++) {
		if (pGroups[i].hasError && pGroups[i].failureMessage) {
			this.forgetField(pGroups[i]);
		}
	}
};

ErrorMonitor.prototype.replaceErrorListeners = function(pGroups) {
	this.removeAllErrors();
	this.addErrorListeners(pGroups);
};

/**
 * Monitors a Field to automatically add it as an error when it enters an error state and remove it once
 * that error is resolved.
 *
 * @param {module:br/presenter/node/ToolTipField} oField
 */
ErrorMonitor.prototype.monitorField = function(oField) {
	if (!(oField instanceof ToolTipField)) {
		throw new Errors.CustomError(Errors.INVALID_PARAMETERS, 'The field to monitor has to be an instance of br.presenter.node.ToolTipField');
	}

	var oTicketErrorProperty = this;
	var fValidationSuccessHandler = function() {
		if (this.hasError.getValue() === true && oField.failureMessage.getValue() !== '') {
			oTicketErrorProperty._addError(oField);
		} else {
			oTicketErrorProperty._removeError(this);
		}
	};

	this.m_oPropertyHelper.addChangeListener(oField.hasError, oField, fValidationSuccessHandler);
	this.m_oPropertyHelper.addValidationErrorListener(oField.value, oField, this._addError.bind(this, oField));

	// We don't put a notify immediately on the add listeners as it triggers a force revalidation which is not needed
	// for async validation
	if (oField.hasError.getValue() && oField.failureMessage.getValue()) {
		this._addError(oField);
	}
};

/**
 * Removes the automatic monitoring of the supplied Field, and removes it from the list of current errors
 * if the field is currently in an error state.
 *
 * @param {module:br/presenter/node/Field} oField
 */
ErrorMonitor.prototype.forgetField = function(oField) {
	this._removeError(oField);
	this.m_oPropertyHelper.clearProperty(oField.hasError);
	this.m_oPropertyHelper.clearProperty(oField.value);
};

ErrorMonitor.prototype.removeAllErrors = function() {
	this.oTooltipNode.move(false);
	this.m_pErrorStack = [];
	this.m_oPropertyHelper.removeAllListeners();
	this._removeLastError();
};

ErrorMonitor.prototype._addError = function(oField) {
	this._removeFromStack(oField);
	this.hasError.setValue(true);

	var sFailureMessage = oField.failureMessage.getValue();
	this.m_pErrorStack.push({
		field: oField,
		failureMessage: sFailureMessage
	});

	var nTopOfStack = this.m_pErrorStack.length;
	if (nTopOfStack === 1) {
		this._addTooltipToTopOfStack();
	} else // if (this.m_pErrorStack.length > 1)
	{
		var oFieldWithTooltipToRemove = this.m_pErrorStack[nTopOfStack - 2].field;
		this._moveTooltip(oFieldWithTooltipToRemove);
	}

	this.oTooltipNode.setMessage(sFailureMessage);
	this._notifyObserversOfErrorChange();
};

ErrorMonitor.prototype._removeError = function(oField) {
	var mFieldAndMessage = this._removeFromStack(oField);
	if (mFieldAndMessage) {
		var oFieldNoLongerInError = mFieldAndMessage.field;

		if (this.m_pErrorStack.length > 0) {
			this._updateTooltipOnRemove(oFieldNoLongerInError);
			this._updateErrorMessage();
		} else {
			this._removeTooltipFrom(oFieldNoLongerInError);
			this._removeLastError();
		}

		this._notifyObserversOfErrorChange();
	}
};

/**
 *
 * @param oField
 * @returns {*}
 * @private
 */
ErrorMonitor.prototype._removeFromStack = function(oField) {
	var nErrorStackLength = this.m_pErrorStack.length;
	for (var i = 0; i < nErrorStackLength; ++i) {
		if (this.m_pErrorStack[i].field == oField) {
			return this.m_pErrorStack.splice(i, 1)[0];
		}
	}
};

/**
 *
 * @private
 */
ErrorMonitor.prototype._notifyObserversOfErrorChange = function() {
	this.oTooltipNode.move(false);
	this.oTooltipNode.move(this.m_pErrorStack.length !== 0);
};

/**
 *
 * @private
 */
ErrorMonitor.prototype._removeLastError = function() {
	this.oTooltipNode.setMessage('');
	this.hasError.setValue(false);
};

/**
 *
 * @private
 */
ErrorMonitor.prototype._updateErrorMessage = function() {
	var oErrorMessage = this._getNextErrorMessage();
	if (oErrorMessage) {
		this.oTooltipNode.setMessage(oErrorMessage);
	}
};

/**
 * @returns {*}
 * @private
 */
ErrorMonitor.prototype._getNextErrorMessage = function() {
	var nErrorIndex = this.m_pErrorStack.length - 1;
	return this.m_pErrorStack[nErrorIndex].failureMessage;
};

/**
 *
 * @param oField
 * @private
 */
ErrorMonitor.prototype._updateTooltipOnRemove = function(oField) {
	if (oField.tooltipClassName.getValue() !== '') {
		this._moveTooltip(oField);
	}
};

/**
 *
 * @param oField
 * @private
 */
ErrorMonitor.prototype._moveTooltip = function(oField) {
	this._removeTooltipFrom(oField);
	this._addTooltipToTopOfStack();
};

/**
 *
 * @private
 */
ErrorMonitor.prototype._addTooltipToTopOfStack = function() {
	var nTopOfStack = this.m_pErrorStack.length - 1;
	var mTopOfStack = this.m_pErrorStack[nTopOfStack];
	var oFieldAtTopOfStack = mTopOfStack.field;
	this._addTooltipTo(oFieldAtTopOfStack);
};

/**
 *
 * @param oField
 * @private
 */
ErrorMonitor.prototype._addTooltipTo = function(oField) {
	if (oField.failureMessage.getValue() !== '') {
		oField.tooltipClassName.setValue(this.oTooltipNode.getTooltipClassName());
	}
};

/**
 *
 * @param oField
 * @private
 */
ErrorMonitor.prototype._removeTooltipFrom = function(oField) {
	oField.tooltipClassName.setValue('');
};

module.exports = ErrorMonitor;