importPackage(Packages.de.elo.ix.client); //@include lib_Class.js //@include lib_sol.common.Template.js //@include lib_sol.common.WfUtils.js //@include lib_sol.common.JsonUtils.js //@include lib_sol.common.RepoUtils.js //@include lib_sol.common.SordUtils.js //@include lib_sol.common.DateUtils.js //@include lib_sol.common.ObjectUtils.js //@include lib_sol.common.ObjectSortUtils.js //@include lib_sol.common.IxUtils.js //@include lib_sol.common.ix.RfUtils.js //@include lib_sol.common.ix.FunctionBase.js //@include lib_sol.common.Injection.js /** * Iterates over a sord's MapTable(s) or elements provided via parameter or a service call and calls a registered function for each element. * * Each row/element is passed to the function as parameter. The parameter name must be defined * as options.elementArg and could be called "sordMetadata". * * Scroll down for an example using a service as data source instead of a MAPTABLE. * * ### Example: Short Intro * * Define the table structure in `columns`. Omit the "1" from the end of the field name which you * defined in the form-editor. * * If this function is running in a workflow, it will have access to objId and flowId automatically. * Otherwise, define both as a parameter. * * { * "objId": "5203", // optional, when in workflow * "flowId": 59, // optional, when in workflow * "columns": { * "wfMap": [ * "COURSE_ENROLLMENT_USER", * "COURSE_ENROLLMENT_STATUS" * ], * "map": [] * }, * "options": { * "elementArg": "sordMetadata" * }, * "callback": { * "name": "RF_sol_learning_function_CreateEnrollmentHeadless", * "args": { * "myadditionalargument": "{{translate 'my.locale.string'}}" * } * } * } * * The function would now be called with a parameter called `sordMetadata` for every table row. * The parameter would look like this * * { * "sordMetadata": { * "COURSE_ENROLLMENT_USER": "Felix Unger", * "COURSE_ENROLLMENT_STATUS": "ENROLLED" * }, * "myadditionalargument": "This is a localized text" * } * * To view these parameters, define `options.dryRun:true`. The callback will not be executed then. * Instead you can see all values with with the function will be called. (One array entry is one call). * * The object displayed in "result" will be passed to the function defined in callback.name. * * ### Example: TemplateSord * * Your target function might require a TemplateSord instead of a flat object. Simply define * * "options": { * "elementArg": "sordMetadata", * "elementAsTemplateSord": true * }, * * The result would look like this: * * { * "sordMetadata": { * "wfMapKeys": { * "COURSE_ENROLLMENT_USER": "Felix Unger", * "COURSE_ENROLLMENT_STATUS": "ENROLLED" * } * }, * "myadditionalargument": "This is a localized text" * } * * ### Example: Mapping field values * * Most of the time, the target function will probably require different field names from the original * sord's field names. * * You can use a mapping to move fields from one property to another: Please note, that the property key * is relative to sordMetadata (or whatever is defined as `options.elementArg`), while the property value * is not. This enables moving values from e.g. a templateSord to an outer scope * * "options": { * "elementArg": "sordMetadata", * "elementAsTemplateSord": true, * "moveValues": { * "wfMapKeys.COURSE_ENROLLMENT_STATUS": "sordMetadata.objKeys.COURSE_ENROLLMENT_STATUS", * "wfMapKeys.COURSE_ENROLLMENT_USER": "thisis.something.else", * } * } * * Of course the mapping source would not include "wfMapKeys", if the "elementAsTemplateSord" option * was set to false. The mapping target path can be as deeply nested as you wish. * * { * "sordMetadata": { * "wfMapKeys": {}, * "objKeys": { * "COURSE_ENROLLMENT_STATUS": "ENROLLED" * } * }, * "thisis": { * "something": { * "else": "ENROLLED" * } * }, * "myadditionalargument": "This is a localized text" * } * * Example: Adding additional parameters * * Most functions need additional parameters. And you will also want to have access to fields of the * (workflow) sord, which contains the maptable. * * E.g. imagine an invoice sord containing a maptable of items. Each item will have an item number * and item description. However, you want to call a createInvoiceItem function which also requires * a field called "INVOICE_NO" which is not contained in the maptable but on the base sord. * You can now add this field using a handlebars helper in the additional `callback.args` object. * In handlebars you have access to the (invoice) sord. * * If additional "callback.args" are defined, they are deep-merged into the prepared parameter * which contains the sordMetadata element: * * element: { sordMetadata: { mapKeys: { ... } } } * args: { test: true, sordMetadata: { testInner: true } } * result: { test: true, sordMetadata: { testInner: true, mapKeys: { ... } } } * * A complete example could look like this: * * { * "objId": "5203", // optional, when in workflow * "flowId": 59, // optional, when in workflow * "columns": { * "wfMap": [ * "COURSE_ENROLLMENT_USER", * "COURSE_ENROLLMENT_STATUS" * ], * "map": [] * }, * "options": { * "elementArg": "sordMetadata", * "elementAsTemplateSord": true, * "moveValues": { * "wfMapKeys.COURSE_ENROLLMENT_STATUS": "objKeys.COURSE_ENROLLMENT_STATUS" * } * }, * "callback": { * "name": "RF_sol_learning_function_CreateEnrollmentHeadless", * "args": { * "myadditionalargument": "{{translate 'my.locale.string'}}", * "sordMetadata": { * "objId": "{{sord.id}}", * "flowId": "{{flowId}}", * "objKeys": { * "COURSE_REFERENCE": "{{{sord.objKeys.COURSE_REFERENCE}}}", * "COURSE_NAME": "{{{sord.objKeys.COURSE_NAME}}}" * } * } * } * } * } * * Which results in * * { * "sordMetadata": { * "mapKeys": {}, * "wfMapKeys": { * "COURSE_ENROLLMENT_USER": "Administrator" * }, * "objKeys": { * "COURSE_ENROLLMENT_STATUS": "ENROLLED", * "COURSE_REFERENCE": "0007", * "COURSE_NAME": "Test" * }, * "objId": "5203", * "flowId": "59" * }, * "myadditionalargument": "my.locale.string" * } * * ### Filtering * If you want that only specific rows would be passed to the callback, you can define filter rules in * the options. For each row, all of the given filter will be applied and only matching objects will * be kept and passed to the callback function. It doesn't matter if you're using map table or wfMap table. * The crucial point ist, that your filter prop path is matching a object path of the result object. * The output format is depending on elementAsTemplateSord option or your moveValues operations. * * The filter will be applied after the moveValue operation. * * * #### Applying a filter with single filter value * * { * "options": { * "filter": [ * { "prop" : "sordMetadata.mapKeys.SOLUTION_FIELD", value: "A -*"} * ] * } * } * * #### Applying a filter with multiple filter values * * { * "options": { * "filter": [ * { "prop" : "sordMetadata.mapKeys.SOLUTION_FIELD", value: ["A -*", "B -*"]} * ] * } * } * * #### Returns * * An array of all return values of the function calls or all prepared parameters which * would have been sent during each function call if `options.dryRun` is set to true. * * { data: [{ objId: "12345", flowId: "33" }] } * * ### Sorting * * It is possible to specify certain search criteria after which the result is sorted. * * Important: Only the result on the service side is sorted, not on the database level. * This can lead to incorrect sorting when using pagination. * * Several sorting criteria can also be specified. The first element is always * the most important criterion. In the case of identical values, the following sorting criteria * are applied. * * Via `type` a sorting algorithm from {@see sol.common.ObjectSortUtils} can be passed as a string. * The name must be identical to the functions offered there. Otherwise, * the default is always selected as the standard. * * Possible types at the moment: `default`, `date`. * * The type `date` enables sorting with the help of date objects. * * { * "options": { * "elementArg" : "data" * "sort": [ * { "prop": "data.startDate", "type": "date" }, * { "prop": "data.reference"} * ] * } * } * * ### Complete Example: Use service results (e.g. sords) as data source * * If your data is not contained in a MAPTABLE, you can also use an external service to provide e.g. sords. * * In this example, we want to find all courses in the archive using the RF_sol_common_service_SordProvider as `elementService`. * Then, we want to prepend "POPULAR" to each course's name, when the course has a 5 star rating, using the RF_sol_function_Set function. * * To enable access to the element's data when the callback is prepared for execution, we define `renderArgsWithElement": true`. * This enables the use of the element's data in the `callback.args` object's strings. * * { * "elementService": { * "name": "RF_sol_common_service_SordProvider", * "args": { * "masks": ["Course"], * "search": [ * { "key": "SOL_TYPE", "value": ["COURSE"] } * { "key": "REFERENCE", "value": "{{sord.objKeys.REFERENCE}}" } * ], * "output": [ * { "source": { "type": "SORD", "key": "id" }, "target": { "prop": "id" } }, * { "source": { "type": "GRP", "key": "COURSE_NAME" }, "target": { "prop": "name" } }, * { "source": { "type": "MAP", "key": "COURSE_STAR_SCORE" }, "target": { "prop": "stars" } } * ] * }, * options: { * forTemplating: true * } * }, * "options": { * "elementArg": "data", * "moveValues": { "id": "objId" }, // the SET-function we use in the callback needs an objId. * "renderArgsWithElement": true, * "filter": [ { "prop": "data.stars", "value": "5" } ], // since SordProvider supports filtering too, we could have filtered above instead of here * "dryRun": true // set this to false to actually execute the SET function in the end * }, * "callback": { * "name": "RF_sol_function_Set", * "args": { * "entries": [ { "type": "GRP", "key": "COURSE_NAME", "value": "POPULAR: {{{element.data.name}}}" }] * } * } * } * * Info: instead of moving the `id` to `objId` using the `moveValues` option, we also could have defined `objId: "{{element.data.id}}"` * in the `callback.args` object. * * If you want to convert each item to an mapTable entry you can use the variable $mapIndex from your elementArg variable (e.g. data) * With Handlebars you can append each trailing number to your configuration result * * { "type": "MAP", "key": "COURSE_NAME{{element.data.$mapIndex}}", "value": "{{{element.data.course}}}" } * * In this case you should avoid to use filter from the foreach function because we can't ensure that the index is correct. * For example the filter function will delete the second row, so your result is that $mapIndex = 2 is missing. * * { * "args": [ * { * "entries": [ { "type": "GRP", "key": "COURSE_NAME1", "value": "POPULAR: BS ELO Contract" } ] * }, * { * "entries": [ { "type": "GRP", "key": "COURSE_NAME3", "value": "POPULAR: BS ELO Learning" } ] * } * ] * } * * Instead use sordprovider filter function * * { * "elementService": { * "name": "RF_sol_common_service_SordProvider", * "args": { * "masks": ["Course"], * "search": [{ "key": "SOL_TYPE", "value": ["COURSE"] }], * "filter": [ { "prop": "stars", "value": "5" } ] * "output": [ * { "source": { "type": "SORD", "key": "id" }, "target": { "prop": "id" } }, * { "source": { "type": "GRP", "key": "COURSE_NAME" }, "target": { "prop": "name" } }, * { "source": { "type": "MAP", "key": "COURSE_STAR_SCORE" }, "target": { "prop": "stars" } } * ] * } * ... * } * * Attention: Only services which return an array of objects as the `sords` or `elements` property can be used with the ForEach function. * * #### Result * * { * "args": [ * { * "data": { "name": "BS ELO Contract", "stars": "5" } } * "objId": "5204", * "entries": [ { "type": "GRP", "key": "COURSE_NAME", "value": "POPULAR: BS ELO Contract" } ] * }, * { * "data": { "name": "BS ELO Learning", "stars": "5" } } * "objId": "5442", * "entries": [ { "type": "GRP", "key": "COURSE_NAME", "value": "POPULAR: BS ELO Learning" } ] * } * ], * "excluded": 7 // number of elements which did not match the filter criteria * } * * If `dryRun` is set to false, the SET-function will be called with each of the two objects in the `args` array. * * * @author ESt, ELO Digital Office GmbH * @version 1.0 * * @eloix * * @requires sol.common.Template * @requires sol.common.WfUtils * @requires sol.common.JsonUtils * @requires sol.common.RepoUtils * @requires sol.common.SordUtils * @requires sol.common.IxUtils * @requires sol.common.ObjectUtils * @requires sol.common.ix.RfUtils * @requires sol.common.ix.FunctionBase * */ sol.define("sol.common.ix.functions.ForEach", { extend: "sol.common.ix.FunctionBase", mixins: [ "sol.common.mixins.Inject", "sol.common.mixins.ObjectFilter", "sol.common.mixins.ObjectSort" ], requiredConfig: ["callback"], inject: { sord: { sordIdFromProp: "objId", flowIdFromProp: "flowId", optional: true }, originalConfig: { jsonFromProp: "configStr", forTemplating: false, template: false }, config: { jsonFromProp: "configStr", forTemplating: false, template: true }, flowId: { prop: "flowId", forTemplating: true } }, /** * @cfg {Object} columns min. one of wfMap or map column names must be defined * @cfg {String[]} columns.wfMap (optional) column names of fields which are a WFMAPTABLE column (see docs) * @cfg {String[]} columns.map (optional) column names of fields which are a MAPTABLE column (see docs) */ /** * @cfg {Object} options * @cfg {Boolean} options.elementArg property name, at which the maptable row object will be stored in the parameter object * @cfg {Boolean} [options.elementAsTemplateSord = false] (optional) formats the maptable row object as a templatesord before adding it to the parameter object * @cfg {Boolean} [options.dryRun = false] (optional) callback will not be executed, only prepared parameters will be returned. (good for debugging) * @cfg {Object} options.moveValues.Object * @cfg {Boolean}[options.deleteAfterUse = false] (optional) if set it to true, all applied rows to the callback function will be deleted. If dryRun = true, deleteInstructions will only be returned. * @cfg {Object[]} options.filter filter rules which will be applied to each row * @cfg {String} options.protectedFields.Object.* property names are the source paths, property values are the target paths for moving values in the parame * @cfg {Object} elementService * @cfg {String} elementService.name name of RF service (e.g. "RF_sol_common_service_SordProvider") * @cfg {Object} elementService.args additional arguments for the service */ /** * @cfg {Object} callback * @cfg {String} callback.name name of registered function (e.g. "RF_sol_function_Set") * @cfg {Object} callback.args additional arguments for the callback */ /** * External dependencies */ executeCallback: function (callback, args) { var me = this; me.logger.debug(["executeCallback rf={0}, args={1}", callback, JSON.stringify(args)]); return sol.common.IxUtils.execute(callback, args); }, isObj: function (o) { return sol.common.ObjectUtils.type(o, "object"); }, validStr: function (s) { return sol.common.ObjectUtils.type(s, "string") && String(s) && s.trim(); }, moveVal: function (srcObj, tgtObj, source, target) { var ou = sol.common.ObjectUtils; ou.setProp(tgtObj, target, ou.getProp(srcObj, source)); ou.setProp(srcObj, source, undefined, true); }, /** * Helpers */ buildTable: function (columns, sord) { var me = this, mapTypes = Object.keys(columns); function createEmptyTable() { return Object.keys(columns).reduce(function (table, mapType) { return columns[mapType].forEach(function (column) { table[column] = []; }) || table; }, {}); } function addMapDataToTable(table, mapType) { var sordMapData = sord[mapType + "Keys"] || {}, cols = columns[mapType]; function addKeyToTable(key) { var column, rowNo; function keyIsColumn() { return cols.some(function (col) { return column = ( key.indexOf(col) == 0 && ((rowNo = +(key.slice(col.length))) === rowNo) ) && col; }); } keyIsColumn() // -1 below, because MAPTABLES index has a +1 index offset && (table[column][rowNo - 1] = (sordMapData[key])); } return Object.keys(sordMapData).forEach(addKeyToTable) || table; } function addColumnDefinitions(info, mapType) { me.logger.info("table build successfully. adding table info"); return columns[mapType].forEach(function (col) { info[col] = mapType + "Keys"; }) || info; } me.logger.info("building table in memory"); return { data: mapTypes.reduce(addMapDataToTable, createEmptyTable()), info: mapTypes.reduce(addColumnDefinitions, {}) }; }, convertToElements: function (table, opt) { var me = this, elementArg = opt.elementArg, asTemplateSord = opt.elementAsTemplateSord, data = table.data, info = table.info, columns = Object.keys(data), elements; function createBlankElementsArray() { function arrayOfInt(int) { return Array.apply(null, Array(int)); } return arrayOfInt(sizeOfLargestColumn()); } function sizeOfLargestColumn() { return columns.reduce(function (max, col) { return (data[col].length >= max) ? data[col].length : max; }, 0); } function rowToObject(_, rowNo) { var element = {}, notEmpty = false, tmp = columns.reduce(function (obj, col) { var val = data[col][rowNo]; (val !== "") && (notEmpty = true) && (obj[col] = val); return obj; }, {}); element[elementArg] = tmp; element.$rowIndex = rowNo; // we need the index later for the deleteInstructions return notEmpty && element; } function rowToTemplateSord(_, rowNo) { var element = {}, notEmpty = false, tmp = columns.reduce(function (ts, col) { var val = data[col][rowNo]; (val !== "") && (notEmpty = true) && (ts[info[col]][col] = val); return ts; }, { mapKeys: {}, wfMapKeys: {} }); element[elementArg] = tmp; element.$rowIndex = rowNo; // we need the index later for the deleteInstructions return notEmpty && element; } function truthy(o) { return o[elementArg]; } me.logger.info("Converting table to elements. Columns: " + columns); elements = createBlankElementsArray() .map(asTemplateSord ? rowToTemplateSord : rowToObject) .filter(truthy); me.logger.info("Extracted " + elements.length + " elements from table"); return elements; }, forEachElement: function (elements, opts, cbOpts) { var me = this, preparedArgs, args = cbOpts.args; function prepareCallbackArg(element) { var freshArgs = args; function deepMerge(a, b) { return Object.keys(b).forEach(function (p) { a[p] = (me.isObj(a[p]) && me.isObj(b[p])) ? deepMerge(a[p], b[p]) : b[p]; }) || a; } if (opts.renderArgsWithElement) { me.$templatingData.element = element; try { freshArgs = JSON.parse(sol.common.TemplateUtils.render(freshArgs, me.$templatingData)); } catch (error) { freshArgs = sol.common.TemplateUtils.render(JSON.parse(freshArgs), me.$templatingData); } } return deepMerge(element, freshArgs); } function executeCb(params) { var rfName = cbOpts.name; return me.executeCallback(rfName, params); } function addMapIndex(element, index) { if (element.data && element.data.$mapIndex) { me.logger.warn("Don't use $mapIndex as target prop - The prop is reserved to pass MapIndex to callback and will override your definition"); } // map index always begin at 1 element.data && (element.data.$mapIndex = index + 1); return element; } me.logger.info("Preparing arguments for callbacks"); // MapIndex must be added first so we can access $mapIndex in handlebars preparedArgs = elements .map(addMapIndex) .map(prepareCallbackArg) .filter(me.matchObject.bind(null, opts.filter)); me.logger.info("Callbacks will be executed for " + preparedArgs.length + " elements"); return opts.dryRun ? (me.logger.info("Dry run: No callback executed. Returning args"), { args: preparedArgs }) : { args: preparedArgs, results: preparedArgs.map(executeCb) }; }, sanitizeConfig: function (cfg, sordsProvided) { var me = this, result = {}; function filterArrayStrings(arr) { return arr.map(me.validStr).filter(me.validStr); } function columns(opt) { if (!(Array.isArray(opt.map) || Array.isArray(opt.wfMap))) { throw "`columns` must contain an array called `map` or `wfMap` or both arrays"; } opt = Object.keys(opt).reduce(function (acc, key) { acc[key] = filterArrayStrings(opt[key]); return acc; }, {}); if (!((opt.map || []).length || (opt.wfMap || []).length)) { throw "no valid column names found in `columns` arrays `map` or `wfMap`"; } me.logger.info("columns config ok"); return opt; } function callback(opt) { var name = opt.name, args = opt.args || {}; if (!((name = me.validStr(name)) && name.indexOf("RF_") === 0)) { throw "`callback.name` must be a string starting with 'RF_' (a valid registered function)`: " + name; } if (args && !me.isObj(args)) { throw "`callback.args` must be an object if it is defined. current type: " + typeof args; } me.logger.info("callback config ok"); return { name: name, args: args }; } function elementService(opt) { var name = opt.name, args = opt.args || {}, elementServiceOptions = opt.options || {}; if (!((name = me.validStr(name)) && name.indexOf("RF_") === 0)) { throw "`elementService.name` must be a string starting with 'RF_' (a valid registered service)`: " + name; } if (args && !me.isObj(args)) { throw "`elementService.args` must be an object if it is defined. current type: " + typeof args; } if (elementServiceOptions && !me.isObj(elementServiceOptions)) { throw "`elementService.options` must be an object if it is defined. current type: " + typeof elementServiceOptions; } me.logger.info("elementService config ok"); return { name: name, args: args, options: elementServiceOptions }; } function options(opt) { var elArg = opt.elementArg, mvVals = opt.moveValues, filters, mvKeys = [], sorts; if (!(elArg = me.validStr(elArg))) { throw "`options.elementArg` must be a string containing min. 1 character: " + elArg; } if (mvVals && !(me.isObj(mvVals) && (mvKeys = filterArrayStrings(Object.keys(mvVals))).length)) { throw "`options.moveValues` must be an object containing min. 1 property with a string value if it is defined. current type: " + typeof mvVals; } mvKeys.length && (mvVals = (mvKeys || []).reduce(function (acc, key) { var val = me.validStr(mvVals[key]); if (!val) { throw "`options.moveValues` property values must be non-empty strings! current type: " + typeof mvVals[key] + " value: " + val; } acc[key] = val; return acc; }, {})); // from mixin sol.common.ObjectUtils.ObjectFilter filters = me.generateFilter(me.options.filter || []); // from mixin sol.common.ObjectSortUtils.ObjectSort sorts = me.generateSort(me.options.sort || []); me.logger.info("options ok"); return { elementArg: elArg, elementAsTemplateSord: opt.elementAsTemplateSord, deleteAfterUse: opt.deleteAfterUse, renderArgsWithElement: opt.renderArgsWithElement, moveValues: mvVals, filter: filters, sort: sorts, dryRun: opt.dryRun }; } me.logger.info("sanitizing config"); me.isObj(cfg.elementService) && (result.elementService = elementService(cfg.elementService)); if ((sordsProvided || (sordsProvided = !!result.elementService)) && cfg.options.deleteAfterUse) { throw "`An `elementService` or `sords` were defined. `deleteAfterUse` option can only be used on MAPTABLES`."; } result.sordsProvided = sordsProvided; !sordsProvided && (result.columns = columns(cfg.columns)); result.options = options(cfg.options); result.callback = callback(cfg.callback); if (result.options.renderArgsWithElement) { result.callback.args = sol.common.JsonUtils.stringifyQuick(me.originalConfig.callback.args); } return result; }, executeDeleteInstructions: function (deleteInstructions) { var me = this; if (deleteInstructions.length) { me.logger.info("used table rows will be unset"); me.executeCallback("RF_sol_function_Set", { objId: me.objId, flowId: me.flowId, entries: deleteInstructions }); } else { me.logger.info("nothing to delete. DeleteInstructions are empty"); } }, generateDeleteInstructions: function (elements, table) { var type, typeMapping = { mapKeys: "MAP", wfMapKeys: "WFMAP" }, deleteInstructions = [], appliedRowIndices = []; elements.forEach(function (el) { // build lookup array: rows for which the callback was executed appliedRowIndices[el.$rowIndex] = true; }); Object.keys(table.data).forEach(function (columnName) { type = typeMapping[table.info[columnName]]; // {table: {info: SOLUTION_FIELD: "mapKeys"}} => "MAP" table.data[columnName].forEach(function (_, curIndex) { appliedRowIndices[curIndex] && deleteInstructions.push({ type: type, key: columnName + (curIndex + 1), value: "" }); }); }); return deleteInstructions; }, mv: function (opts, element) { var me = this, elementArg = opts.elementArg, moveDef = opts.moveValues, mvKeys = Object.keys(moveDef); return mvKeys.forEach(function (source) { me.moveVal(element[elementArg], element, source, moveDef[source]); }) || element; }, sordsToElements: function (sords, opt) { var me = this, elementArg = opt.elementArg, elements; function toElement(sord) { var element = {}; element[elementArg] = sord; return element; } elements = sords.map(toElement); me.logger.info("Created " + elements.length + " elements from sords"); return elements; }, executeElementService: function (cfg) { var me = this, elements, args = cfg.args; if (cfg.options && cfg.options.forTemplating === true) { if (!me.sord) { throw Error("Templating only works if an `objId` was passed for templating"); } args = sol.common.TemplateUtils.render(cfg.args, { sord: me.sord }); } elements = (me.executeCallback(cfg.name, args || {}) || {}); elements = elements.sords || elements.elements; if (!Array.isArray(elements)) { throw "The RF defined as `elementService` must return an object containing a property `sords` or `elements` which contains an array (of objects)"; } return elements; }, process: function () { var me = this, config, table, elements, data, deleteInstructions, sords = me.sords || me.elements, result = {}; config = me.sanitizeConfig(me.config, Array.isArray(sords)); if (config.sordsProvided) { config.elementService && (sords = me.executeElementService(config.elementService)); elements = me.sordsToElements(sords, config.options); } else { table = me.buildTable(config.columns, me.sord); elements = me.convertToElements(table, config.options); } config.options.moveValues && (elements = elements.map(me.mv.bind(me, config.options))); if (config.options.sort && config.options.sort.length > 0) { me.logger.debug(["element will be sorted by {0}", JSON.stringify(config.options.sort)]); elements = me.sortArray(elements, config.options.sort); } data = me.forEachElement(elements, config.options, config.callback); if (config.options.deleteAfterUse) { deleteInstructions = me.generateDeleteInstructions(data.args, table); if (config.options.dryRun) { result.deleteInstructions = deleteInstructions; } else { me.executeDeleteInstructions(deleteInstructions); } } if (config.options.dryRun) { result.args = data.args; result.excluded = elements.length - data.args.length; } result.data = data.results; return result; } }); /** * @member sol.common.ix.functions.ForEach * @static * @inheritdoc sol.common.ix.FunctionBase#onEnterNode */ function onEnterNode(_clInfo, _userId, wfDiagram, nodeId) { var params = sol.common.WfUtils.parseAndCheckParams(wfDiagram, nodeId); params.objId = wfDiagram.objId; params.flowId = wfDiagram.id; params.configStr = sol.common.JsonUtils.stringifyQuick(params); sol.create("sol.common.ix.functions.ForEach", params).process(); } /** * @member sol.common.ix.functions.ForEach * @static * @inheritdoc sol.common.ix.FunctionBase#onExitNode */ function onExitNode(_clInfo, _userId, wfDiagram, nodeId) { var params = sol.common.WfUtils.parseAndCheckParams(wfDiagram, nodeId); params.objId = wfDiagram.objId; params.flowId = wfDiagram.id; params.configStr = sol.common.JsonUtils.stringifyQuick(params); sol.create("sol.common.ix.functions.ForEach", params).process(); } /** * @member sol.common.ix.functions.ForEach * @method RF_sol_common_function_ForEach * @static * @inheritdoc sol.common.ix.FunctionBase#RF_FunctionName * @return {Object} */ function RF_sol_common_function_ForEach(iXSEContext, args) { var logger = sol.create("sol.Logger", { scope: "sol.common.ix.functions.ForEach" }), rfParams = sol.common.ix.RfUtils.parseAndCheckParams(iXSEContext, arguments.callee.name, args), result; logger.enter("RF_sol_common_function_ForEach"); rfParams.configStr = sol.common.JsonUtils.stringifyQuick(rfParams); result = sol.common.JsonUtils.stringifyQuick( sol.create("sol.common.ix.functions.ForEach", rfParams).process() ); logger.exit("RF_sol_common_function_ForEach"); return result; }