With the SysOperation framework, I usually create a minumum of three classes, the data contract, controller and the business logic or service class. There is also a helper class which is used for the multi selection and joining with the query.
Data contract
[DataContractAttribute, SysOperationAlwaysInitializeAttribute]
public final class AVSysOpMultiSelectTemplateContract implements SysOperationInitializable
{
private str packedQuery, packedSelectedRecIds;
public void initialize() //always called because of SysOperationAlwaysInitializeAttribute
{
}
public Query getQuery()
{
return new Query(SysOperationHelper::base64Decode(packedQuery));
}
public void setQuery(Query _query)
{
packedQuery = SysOperationHelper::base64Encode(_query.pack());
}
//the query specified here is the query shown in the SysOp dialog and also the
//query used in the SysOp service class.
[DataMemberAttribute, AifQueryTypeAttribute('_packedQuery', queryStr(AVCustTable))]
public str parmQuery(str _packedQuery = packedQuery)
{
packedQuery = _packedQuery;
return packedQuery;
}
public Set getSelectedRecIdSet()
{
if (!packedSelectedRecIds)
{
throw error("You have to set the selected RecIds first.");
}
return Set::create(SysOperationHelper::base64Decode(packedSelectedRecIds));
}
public void setSelectedRecIdSet(Set _selectedRecIdSet)
{
if (_selectedRecIdSet == null)
{
throw error("Argument %1 cannot be null.");
}
packedSelectedRecIds = SysOperationHelper::base64Encode(_selectedRecIdSet.pack());
}
public boolean isMultiSelected()
{
return packedSelectedRecIds ? true : false;
}
}
Controller
[SysOperationJournaledParametersAttribute(true)]
public final class AVSysOpMultiSelectTemplateController extends SysOperationServiceController
{
public static void main(Args _args)
{
AVSysOpMultiSelectTemplateController operation = AVSysOpMultiSelectTemplateController::construct();
operation.parmLoadFromSysLastValue(false); //called from the UI so do not call getLast, we don't want previous selections etc.
//operation.initializeFromArgs(_args); //can call initializeFromArgs() if menu item contains SysOp execution info. if not, it removes SysOp execution info.
operation.parmShowDialog(true);
AVSysOpMultiSelectTemplateContract contract = operation.getDataContractObject();
Debug::assert(contract != null);
if (AVMultiSelectionHelper::isMultiSelected(_args))
{
contract.setSelectedRecIdSet(AVMultiSelectionHelper::getSelectedRecIDSet(_args));
}
else
{
operation.AVModifyQueryFromArgs(_args);
}
if (operation.startOperation() == SysOperationStartResult::Started) //will return result because we are running synchronous.
{
Counter cntr;
[cntr] = operation.operationReturnValue();
Info(strFmt("Processed %1 selected records.", cntr));
//refresh caller here
}
}
public void new(IdentifierName _className = '', IdentifierName _methodName = '', SysOperationExecutionMode _executionMode = SysOperationExecutionMode::Asynchronous)
{
super(_className, _methodName, _executionMode);
this.parmClassName(classStr(AVSysOpMultiSelectTemplateService));
this.parmMethodName(methodStr(AVSysOpMultiSelectTemplateService, runQuery));
this.parmExecutionMode(SysOperationExecutionMode::Synchronous); //ScheduledBatch: batch job remains visible. ReliableAsynchronous: job is deleted.
this.parmDialogCaption("AV multi select"); //set dialog caption.
}
public static AVSysOpMultiSelectTemplateController construct()
{
return new AVSysOpMultiSelectTemplateController();
}
public void AVModifyQueryFromArgs(Args _args)
{
if (!_args)
{
throw error(strFmt("Argument %1 cannot be null.", identifierStr(_args)));
}
if (_args.dataset() != tableNum(CustTable))
{
throw error(strFmt("Caller buffer must be %1.", tableStr(CustTable)));
}
CustTable custTable = _args.record();
if (custTable.RecId != 0)
{
AVSysOpMultiSelectTemplateContract contract = this.getDataContractObject();
Debug::assert(contract != null);
if (contract != null)
{
Query query = contract.getQuery();
Debug::assert(query != null);
if (query != null)
{
QueryBuildDataSource custTable_ds = query.dataSourceTable(tableNum(CustTable));
SysQuery::findOrCreateRange(custTable_ds, fieldNum(CustTable, AccountNum)).value(queryValue(custTable.AccountNum));
contract.setQuery(query);
}
}
}
}
public boolean mustGoBatch()
{
boolean ret;
ret = super();
return ret;
}
public boolean showQuerySelectButton(str parameterName)
{
boolean ret;
ret = super(parameterName);
AVSysOpMultiSelectTemplateContract contract = this.getDataContractObject();
Debug::assert(contract != null);
if (contract.isMultiSelected())
{
ret = false;
}
return ret;
}
public boolean showQueryValues(str parameterName)
{
boolean ret;
ret = super(parameterName);
AVSysOpMultiSelectTemplateContract contract = this.getDataContractObject();
Debug::assert(contract != null);
if (contract.isMultiSelected())
{
ret = false;
}
return ret;
}
}
Business logic
public final class AVSysOpMultiSelectTemplateService
{
//the service method is a class method with a contract as argument. the contract contains the parameters and query (if specified) for the operation.
//the contract specified here as argument is the contract for our SysOp controller and service classes.
//this method does not have to return anything but it does return a container to illustrate how to return 'something' from the service method.
public container runQuery(AVSysOpMultiSelectTemplateContract data)
{
Counter cntr;
AVTmpRecId tmpRecId; //table containing RecIds of the selected records.
Set tmpRecIdSet; //set containing the RecIds of the selected records.
Query query = data.getQuery();
if (data.isMultiSelected())
{
tmpRecIdSet = data.getSelectedRecIdSet(); //get selected RecIds as a Set.
AVMultiSelectionHelper::populateTmpRecIdFromSet(tmpRecIdSet, tmpRecId); //populate our temp table from the set that contains the RecIds.
AVMultiSelectionHelper::modifyQueryAddJoinToTmpTable(query); //modify the query to join with our temp table.
}
QueryRun queryRun = new QueryRun(query);
if (data.isMultiSelected())
{
queryRun.setRecord(tmpRecId); //if joined with a temp table, we must set the record on the queryRun object.
}
while (queryRun.next())
{
CustTable custTable = queryRun.get(tableNum(CustTable));
info(custTable.AccountNum);
cntr++;
}
return [cntr];
}
}
Helper class
public final class AVMultiSelectionHelper
{
public static Set getSelectedRecIDSet(Args _args)
{
Set selectedRecIdSet;
if (!_args)
{
throw error(strFmt("Argument %1 cannot be null.", identifierStr(_args)));
}
if (!_args.dataset())
{
throw error("Must be called with a caller buffer.");
}
if (!_args.record().isFormDataSource())
{
throw error("The caller buffer must be form datasource.");
}
selectedRecIdSet = new Set(Types::Int64);
MultiSelectionHelper multiSelectionHelper = MultiSelectionHelper::construct();
multiSelectionHelper.parmDatasource(_args.record().dataSource());
Common buffer = multiSelectionHelper.getFirst();
while (buffer.RecId != 0)
{
selectedRecIdSet.add(buffer.RecId);
buffer = multiSelectionHelper.getNext();
}
return selectedRecIdSet;
}
public static boolean isMultiSelected(Args _args)
{
Set selectedRecIdSet = AVMultiSelectionHelper::getSelectedRecIDSet(_args); //arg checks are done in getSelectedRecIDSet().
return selectedRecIdSet.elements() > 1 ? true : false;
}
public static void modifyQueryAddJoinToTmpTable(Query _query)
{
QueryBuildDataSource qbds;
SysDictTable dictTable;
QueryBuildDataSource tmpRecId_ds;
if (!_query)
{
throw error(strFmt("Argument %1 cannot be null.", identifierStr(_query)));
}
qbds = _query.dataSourceNo(1); //join on first datasource in query.
dictTable = SysDictTable::newTableId(qbds.table());
Debug::assert(dictTable != null);
tmpRecId_ds = qbds.addDataSource(tableNum(AVTmpRecId), tableStr(AVTmpRecId));
tmpRecId_ds.addLink(dictTable.fieldName2Id(identifierStr(RecId)), fieldNum(AVTmpRecId, RefRecId));
qbds.clearRanges(); //joined so clear all existing ranges.
SysQuery::findOrCreateRange(tmpRecId_ds, fieldNum(AVTmpRecId, CreatedBy)).value(curUserId());
SysQuery::findOrCreateRange(tmpRecId_ds, fieldNum(AVTmpRecId, CreatedTransactionId)).value(queryValue(appl.curTransactionId()));
}
public static void populateTmpRecIdFromSet(Set _recIdSet, AVTmpRecId _tmpRecId)
{
if (!_recIdSet)
{
throw error(strFmt("Argument %1 cannot be null.", identifierStr(_recIdSet)));
}
SetEnumerator enum = _recIdSet.getEnumerator();
while (enum.moveNext())
{
_tmpRecId.RefRecId = enum.current();
_tmpRecId.insert();
}
}
}