Markus Kraus
Kilo Sage

I think pretty much everyone who ever worked with MRVS wished, that we had a way on the "parent" (the catalog item) to detect a change of a MRVS. All solutions posted (and some even got accepted as a "solution") have flaws:
They either don't work in the classic UI and/or they are built in a way that the does the change-detection every angular-cycle. So I did some tinkering and came up with a solution which works in the Classic UI as well as Service Portal.

Create a new variable of type custom, and make it Hidden=true.
In the 'Type Specification' Section of the new custom variable, use a new widget with the following Client Controller:

api.controller=function($scope, $rootScope, $timeout) {
  var handlers = [];
  var lastValues = {};

  var g_form = $scope.page.g_form;
  g_form.getFieldNames().forEach(function (fieldName) { 
		var field = g_form.getField(fieldName);
		if (field.type != 'sc_multi_row') {
			return;
		}

		var unregister = $rootScope.$on('field.change.' + fieldName, function ($event, data) {			
			// Note: data.oldValue is the *original* value (upon form load) ...
			var operation = (function (oldData, newData) {
				if (newData.length > oldData.length) {
					return 'create';
				} else if (newData.length < oldData.length) {
					return 'delete';
				} else {
					return 'update';
				}
			})(JSON.parse(lastValues[fieldName] || '[]'), JSON.parse(data.newValue || '[]'));

			// ... because of this, we simply preserve a copy of the previous value
			lastValues[fieldName] = data.newValue;

			CustomEvent.fire('scp_mrvs_changed', fieldName, operation);
		});
		
		handlers.push(unregister);
	});

	$scope.$on('$destroy', function () {
		handlers.forEach(function (fn) { fn(); });
	});
};

You also need to reference a UI Macro in the Custom Variable with the following code:

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
<script>
(function () {
	if (window.TableVariable) {
		window.TableVariable = scPlusTableVariable(window.TableVariable);
	} else {
		Object.defineProperty(window, 'TableVariable', {
			configurable: true,
			set: function (v) {
				Object.defineProperty(window, 'TableVariable', {
					configurable: true, enumerable: true, writable: true, value: scPlusTableVariable(v)
				});
			}
		});
	}

	function scPlusTableVariable(ootbTableVariable) {
		const SCPlusTableVariable = {
			notifyOperation: function (operation) {
				var res = ootbTableVariable.prototype.notifyOperation.apply(this, arguments);
				CustomEvent.fire('scp_mrvs_changed', this.internal_name, operation);
				return res;
			},

			type: 'SC+TableVariable'
		};

		return window.Class.create(ootbTableVariable, SCPlusTableVariable);
	}
})();
</script>
</j:jelly>

 

Now you can use the following onLoad - Catalog Client Script on the Catalog Item itself:
UI Type: All
Script:

function onLoad() {
	updateVariableState();
	CustomEvent.on('scp_mrvs_changed', function (mrvsName, operation) {
		if (mrvsName == 'report_name_and_email') {
			updateVariableState();
		}
	});

	function updateVariableState() {
		var mrvs = JSON.parse(g_form.getValue('report_name_and_email') || '[]');
		if (mrvs.length > 0) {
			g_form.setReadOnly('add_or_remove', true);
		} else {
			g_form.setReadOnly('add_or_remove', false);
		}
	}
}