/**
* ArrayActions - Actions related to array functionallity
*
* This code is licensed under the MIT License (MIT).
*
* Copyright 2020, 2021, 2022 Rolf Bagge, Janus B. Kristensen, CAVI,
* Center for Advanced Visualization and Interaction, Aarhus University
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the “Software”), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
/**
* Actions that deal with arrays
* @namespace ArrayActions
*/
/**
* Action "length" that can put the length of an array or string into a variable
* @memberOf ArrayActions
* @example
* // Find the length of a property (array or string)
* {
* "length": {
* "of": {"property": "myArrayOrStringProperty"},
* "as": "myResultVariableName"
* }
* }
*
* @example
* // Find the length of a property (array or string), on a non selected property
* {
* "length": {
* "of": {"property": "myConcept.myArrayOrStringProperty"},
* "as": "myResultVariableName"
* }
* }
*
* @example
* // Find the length of a variable (array or string)
* {
* "length": {
* "of": {"variable": "myArrayOrStringVariable"},
* "as": "myResultVariableName"
* }
* }
*
* @example
* // Shorthand, set the variable "length" to the length of the property "myProperty"
* {
* "length": "myProperty"
* }
*
* @example
* // Shorthand, set the variable "length" to the length of the variable "myVariable"
* {
* "length": "$myVariable"
* }
*/
class LengthAction extends Action {
static options() {
return {
"of": "enumValue[property,variable]",
"as": "@string"
}
}
constructor(name, options, concept) {
//Shorthand
if(typeof options === "string") {
if(options.trim().startsWith("$")) {
//Shorthand lookup variable
options = {
of: {
variable: options.trim().substring(1)
}
}
} else {
options = {
of: {
property: options
}
}
}
}
super(name, options, concept);
if(this.options.of == null) {
if(this.options.property != null) {
this.options.of = {
property: this.options.property
}
delete this.options.property;
} else if(this.options.variable != null) {
this.options.of = {
variable: this.options.variable
}
delete this.options.variable;
}
}
}
async apply(contexts, actionArguments) {
const self = this;
if(this.options.of == null) {
throw new Error("Missing option 'of' on length action");
}
if(this.options.of.property == null && this.options.of.variable == null) {
throw new Error("Missing option 'of.property' or 'of.variable' on length action");
}
return this.forEachContext(contexts, actionArguments, async (context, options)=>{
let variableName = Action.defaultVariableName(self);
if(options.as != null) {
variableName = options.as;
}
if(options.of.property != null) {
// Length of property
const lookup = await VarvEngine.lookupProperty(context.target, self.concept, options.of.property);
if(lookup == null) {
throw new Error("No property ["+options.of.property+"] found");
}
const concept = lookup.concept;
const property = lookup.property;
const target = lookup.target;
if(property.type !== "array" && property.type !== "string") {
throw new Error("Unable to get length of non array|string type property ["+property.name+"] on ["+concept.name+"]");
}
let value = await property.getValue(target);
Action.setVariable(context, variableName, value.length);
} else if(options.of.variable != null) {
// Length of variable
const variableValue = Action.getVariable(context, options.of.variable);
if(!Array.isArray(variableValue) && typeof variableValue !== "string") {
throw new Error("Variable ["+options.of.variable+"] was not of type array or string!");
}
Action.setVariable(context, variableName, variableValue.length);
}
return context;
});
}
}
Action.registerPrimitiveAction("length", LengthAction);
window.LengthAction = LengthAction;
class AppendPrependAction extends Action {
constructor(name, options, concept, prepend = false) {
super(name, options, concept);
if(this.options.to == null) {
if(this.options.property != null) {
this.options.to = {
property: this.options.property
}
delete this.options.property;
} else if(this.options.variable != null) {
this.options.to = {
variable: this.options.variable
}
delete this.options.variable;
}
}
this.prepend = prepend;
}
async apply(contexts, actionArguments) {
const self = this;
if(this.options.to == null) {
throw new Error("Missing option 'to' on "+(this.prepend?"prepend":"append")+" action");
}
if(this.options.to.property == null && this.options.to.variable == null) {
throw new Error("Missing option 'to.property' or 'to.variable' on "+(this.prepend?"prepend":"append")+" action");
}
return this.forEachContext(contexts, actionArguments, async (context, options) =>{
let items = [];
if(options.item != null) {
items.push(options.item);
} else if(options.items != null) {
items.push(...options.items);
} else {
throw new Error("Missing 'item' or 'items' option on Append/Prepend action.");
}
if(options.to.property != null) {
// Append to property array
const lookup = await VarvEngine.lookupProperty(context.target, self.concept, options.to.property);
if(lookup == null) {
throw new Error("No property ["+options.of.property+"] found");
}
const concept = lookup.concept;
const property = lookup.property;
const target = lookup.target;
if(property.type !== "array") {
throw new Error("Unable to "+(this.prepend?"prepend":"append")+" to non array type property ["+property.name+"] on ["+concept.name+"]");
}
let value = await property.getValue(target);
if(self.prepend) {
items.reverse().forEach((item)=>{
value.unshift(item);
});
} else {
items.forEach((item)=>{
value.push(item);
});
}
await property.setValue(target, value);
} else if(options.to.variable != null) {
// Append to variable array
let array = Action.getVariable(context, options.to.variable);
if(self.prepend) {
items.reverse().forEach((item)=>{
array.unshift(item);
});
} else {
items.forEach((item)=>{
array.push(item);
});
}
}
return context;
});
}
}
/**
* An action "append" that can append an 'item' or a number of 'items' to an array, the array can be from either a property or a variable
* @memberOf ArrayActions
* @example
* // Append a string item to an array inside a property
* {
* "append": {
* "to": {"property": "myStringArrayProperty"},
* "item": "myStringItem"
* }
* }
*
* @example
* // Append a string item to an array inside a property, on a non selected concept
* {
* "append": {
* "to": {"property": "myConcept.myStringArrayProperty"},
* "item": "myStringItem"
* }
* }
*
* @example
* // Append a string item to an array inside a variable
* {
* "append": {
* "to": {"variable": "myStringArrayVariable"},
* "item": "myStringItem"
* }
* }
*
* @example
* // Append a number of string items to an array inside a variable
* {
* "append": {
* "to": {"variable": "myStringArrayVariable"},
* "items": ["myStringItem1", "myStringItem2"]
* }
* }
*/
class AppendAction extends AppendPrependAction {
static options() {
return {
"to": "enumValue[property,variable]",
"item": "raw"
}
}
constructor(name, options, concept) {
super(name, options, concept, false);
}
}
Action.registerPrimitiveAction("append", AppendAction)
window.AppendAction = AppendAction;
/**
* An action "prepend" that can prepend an 'item' or a number of 'items' to an array, the array can be from either a property or a variable
* @memberOf ArrayActions
* @example
* // Prepend a string item to an array inside a property
* {
* "prepend": {
* "to": {"property": "myStringArrayProperty"},
* "item": "myStringItem"
* }
* }
*
* @example
* // Prepend a string item to an array inside a property, on a non selected concept
* {
* "prepend": {
* "to": {"property": "myConcept.myStringArrayProperty"},
* "item": "myStringItem"
* }
* }
*
* @example
* // Prepend a string item to an array inside a variable
* {
* "prepend": {
* "to": {"variable": "myStringArrayVariable"},
* "item": "myStringItem"
* }
* }
*
* @example
* // Prepend a number of string items to an array inside a variable
* {
* "prepend": {
* "to": {"variable": "myStringArrayVariable"},
* "items": ["myStringItem1", "myStringItem2"]
* }
* }
*/
class PrependAction extends AppendPrependAction {
static options() {
return {
"to": "enumValue[property,variable]",
"item": "raw"
}
}
constructor(name, options, concept) {
super(name, options, concept, true);
}
}
Action.registerPrimitiveAction("prepend", PrependAction)
window.PrependAction = PrependAction;
/**
* An action "insert" that inserts an 'item' or 'items' into an array property or variable, at a given index.
* @memberOf ArrayActions
* @example
* {
* /Insert "myNewItem" at index 2, in the array at myArrayProperty
* "insert": {
* "to": {"property": "myArrayProperty"},
* "index": 2,
* "item": "myNewItem"
* }
* }
*
* @example
* {
* /Insert "myNewItem" at index 2, in the array at myVariableArray
* "insert": {
* "to": {"variable": "myVariableArray"},
* "index": 2,
* "item": "myNewItem"
* }
* }
*
* @example
* {
* /Insert multiple items at index 3, in the array at myVariableArray
* "insert": {
* "to": {"variable": "myVariableArray"},
* "index": 3,
* "items": ["myNewItem", "mySecondNewItem"]
* }
* }
*/
class InsertAction extends Action {
constructor(name, options, concept) {
super(name, options, concept);
if(this.options.to == null) {
if(this.options.property != null) {
this.options.to = {
property: this.options.property
}
delete this.options.property;
} else if(this.options.variable != null) {
this.options.to = {
variable: this.options.variable
}
delete this.options.variable;
}
}
}
async apply(contexts, actionArguments = {}) {
if(this.options.to == null) {
throw new Error("Missing option 'to' on "+(this.prepend?"prepend":"append")+" action");
}
if(this.options.index == null) {
throw new Error("Missing option 'index' on insert action");
}
if(this.options.to.property == null && this.options.to.variable == null) {
throw new Error("Missing option 'to.property' or 'to.variable' on "+(this.prepend?"prepend":"append")+" action");
}
return this.forEachContext(contexts, actionArguments, async (context, options)=>{
if(options.index < 0) {
throw new Error("Option 'index' can not be less than 0: "+options.index);
}
let array = null;
let arrayPropertyTarget = null;
let arrayProperty = null;
if(options.to.property != null) {
// Append to property array
const lookup = await VarvEngine.lookupProperty(context.target, self.concept, options.to.property);
if (lookup == null) {
throw new Error("No property [" + options.of.property + "] found");
}
const concept = lookup.concept;
const property = lookup.property;
const target = lookup.target;
if (property.type !== "array") {
throw new Error("Unable to " + (this.prepend ? "prepend" : "append") + " to non array type property [" + property.name + "] on [" + concept.name + "]");
}
arrayPropertyTarget = target;
arrayProperty = property;
array = await property.getValue(target);
} else if(options.to.variable != null) {
array = Action.getVariable(context, options.to.variable);
}
if(array == null) {
throw new Error("Unable to find array: "+JSON.stringify(options, null, 2)+" in 'insert'");
}
if(options.index > array.length) {
throw new Error("Option 'index' can not be more than array length: "+array.length);
}
let items = [];
if(options.item != null) {
items.push(options.item);
} else if(options.items != null) {
items.push(...options.items);
} else {
throw new Error("Missing option either 'item' or 'items' on insert action");
}
items.reverse().forEach((item)=>{
array.splice(options.index, 0, item);
});
if(arrayProperty != null) {
//If property, remember to set it
await arrayProperty.setValue(arrayPropertyTarget, array);
}
return context;
});
}
}
Action.registerPrimitiveAction("insert", InsertAction)
window.InsertAction = InsertAction;
class RemoveFirstLastAction extends Action {
constructor(name, options, concept, removeFirst = false) {
//Shorthand { "remove-first": "propertyName" }
if(typeof options === "string") {
if(options.trim().startsWith("$")) {
options = {
"of": {
"variable": options.trim().substring(1)
}
}
} else {
options = {
"of": {
"property": options
}
}
}
}
super(name, options, concept);
if(this.options.of == null) {
if(this.options.property != null) {
this.options.of = {
property: this.options.property
}
delete this.options.property;
} else if(this.options.variable != null) {
this.options.of = {
variable: this.options.variable
}
delete this.options.variable;
}
}
this.removeFirst = removeFirst;
}
async apply(contexts, actionArguments) {
const self = this;
if(this.options.of == null) {
throw new Error("Missing option 'of' on "+(this.removeFirst?"remove-first":"remove-last")+" action");
}
if(this.options.of.property == null && this.options.of.variable == null) {
throw new Error("Missing option 'of.property' or 'of.variable' on "+(this.removeFirst?"remove-first":"remove-last")+" action");
}
return this.forEachContext(contexts, actionArguments, async (context, options)=>{
let variableName = Action.defaultVariableName(self);
if(options.as != null) {
variableName = options.as;
}
if(options.of.property != null) {
// remove of property
const lookup = await VarvEngine.lookupProperty(context.target, self.concept, options.of.property);
if(lookup == null) {
throw new Error("No property ["+options.of.property+"] found");
}
const concept = lookup.concept;
const property = lookup.property;
const target = lookup.target;
if(property.type !== "array") {
throw new Error("Unable to "+(this.removeFirst?"remove-first":"remove-last")+" of non array type property ["+property.name+"] on ["+concept.name+"]");
}
let value = await property.getValue(target);
let result = null;
if(self.removeFirst) {
result = value.shift();
} else {
result = value.pop();
}
// Set back the changed array
await property.setValue(target, value);
// Set result variable
Action.setVariable(context, variableName, result);
} else if(options.of.variable != null) {
// remove of variable
const variableValue = Action.getVariable(context, options.of.variable);
if(!Array.isArray(variableValue)) {
throw new Error("Variable ["+options.of.variable+"] was not of type array!");
}
let result = null;
if(self.removeFirst) {
result = variableValue.shift();
} else {
result = variableValue.pop();
}
// Set back the changed array
Action.setVariable(context, options.of.variable, variableValue);
// Set the result variable
Action.setVariable(context, variableName, result);
}
return context;
});
}
}
/**
* An action "removeFirst" that can remove the first item of an array, and set a variable to the removed item
* @memberOf ArrayActions
* @example
* // Remove the first item from an array property
* {
* "removeFirst": {
* "of": {"property": "myArrayProperty"},
* "as": "myResultVariableName"
* }
* }
*
* @example
* // Remove the first item from an array property, on a non selected concept
* {
* "removeFirst": {
* "of": {"property": "myConcept.myArrayProperty"},
* "as": "myResultVariableName"
* }
* }
*
* @example
* // Remove the first item from an array property, shorthand notation, result will be in variable named "removeFirst"
* {
* "removeFirst": "myArrayProperty"
* }
*
* @example
* // Remove the first item from an array variable
* {
* "removeFirst": {
* "of": {"variable": "myArrayVariable"},
* "as": "myResultVariableName"
* }
* }
*/
class RemoveFirstAction extends RemoveFirstLastAction {
static options() {
return {
"of": "enumValue[property,variable]",
"as": "@string"
}
}
constructor(name, options, concept) {
super(name, options, concept, true);
}
}
Action.registerPrimitiveAction("removeFirst", RemoveFirstAction);
window.RemoveFirstAction = RemoveFirstAction;
/**
* An action "removeLast" that can remove the last item of an array, and set a variable to the removed item
* @memberOf ArrayActions
* @example
* // Remove the last item from an array property
* {
* "removeLast": {
* "of": {"property": "myArrayProperty"},
* "as": "myResultVariableName"
* }
* }
*
* @example
* // Remove the last item from an array property, on a non selected concept
* {
* "removeLast": {
* "of": {"property": "myConcept.myArrayProperty"},
* "as": "myResultVariableName"
* }
* }
*
* @example
* // Remove the last item from an array property, shorthand notation, result will be in variable named "removeLast"
* {
* "removeLast": "myArrayProperty"
* }
*
* @example
* // Remove the last item from an array variable
* {
* "removeLast": {
* "of": {"variable": "myArrayVariable"},
* "as": "myResultVariableName"
* }
* }
*/
class RemoveLastAction extends RemoveFirstLastAction {
static options() {
return {
"of": "enumValue[property,variable]",
"as": "@string"
}
}
constructor(name, options, concept) {
super(name, options, concept, false);
}
}
Action.registerPrimitiveAction("removeLast", RemoveLastAction);
window.RemoveLastAction = RemoveLastAction;
/**
* An action "removeItem" that can remove 1 or X items from a given index in an array, the array can be in either a property or a variable
*
* If removeCount is 1, then the result will be just the item removed, if its > 1, then the result will be an array of the removed items
* @memberOf ArrayActions
* @example
* //Remove 1 items starting from index 1 of an array property
* {
* "removeItem": {
* "of": { "property": "myArrayProperty" },
* "index": 1,
* "as": "myResultVariableName"
* }
* }
*
* @example
* //Remove 2 items starting from index 1 of an array property
* {
* "removeItem": {
* "of": { "property": "myArrayProperty" },
* "index": 1,
* "removeCount": 2,
* "as": "myResultVariableName"
* }
* }
*
* @example
* //Remove 2 items starting from index 1 af an array variable
* {
* "removeItem": {
* "of": { "variable": "myArrayVariable" },
* "index": 1,
* "removeCount": 2,
* "as": "myResultVariableName"
* }
* }
*/
class RemoveItemAction extends Action {
static options() {
return {
"of": "enumValue[property,variable]",
"index": "number",
"removeCount": "@number",
"as": "@string"
}
}
constructor(name, options, concept) {
super(name, options, concept);
if(this.options.of == null) {
if(this.options.property != null) {
this.options.of = {
property: this.options.property
}
delete this.options.property;
} else if(this.options.variable != null) {
this.options.of = {
variable: this.options.variable
}
delete this.options.variable;
}
}
}
async apply(contexts, actionArguments) {
const self = this;
if(this.options.of == null) {
throw new Error("Missing option 'of' on removeItem action");
}
if(this.options.of.property == null && this.options.of.variable == null) {
throw new Error("Missing option 'of.property' or 'of.variable' on removeItem action");
}
if(this.options.index == null && this.options.item == null) {
throw new Error("Missing option either 'index' or 'item' on removeItem action");
}
return this.forEachContext(contexts, actionArguments, async (context, options)=>{
let variableName = Action.defaultVariableName(self);
if(options.as != null) {
variableName = options.as;
}
let removeCount = 1;
if(options.removeCount != null) {
removeCount = options.removeCount;
}
if(options.of.property != null) {
const lookup = await VarvEngine.lookupProperty(context.target, self.concept, options.of.property);
if(lookup == null) {
throw new Error("No property ["+options.of.property+"] found");
}
const concept = lookup.concept;
const property = lookup.property;
const target = lookup.target;
if(property.type !== "array") {
throw new Error("Unable to removeItem of non array type property ["+property.name+"] on ["+concept.name+"]");
}
let value = await property.getValue(target);
let index = 0;
if(options.index != null) {
index = options.index;
} else if(options.item != null) {
index = value.indexOf(options.item);
}
let result = value.splice(index, removeCount);
if(result.length === 1) {
result = result[0];
}
await property.setValue(target, value);
Action.setVariable(context, variableName, result);
} else if(options.of.variable != null) {
let value = Action.getVariable(context, options.of.variable);
let index = 0;
if(options.index != null) {
index = options.index;
} else if(options.item != null) {
index = value.indexOf(options.item);
}
let result = value.splice(index, removeCount);
if(result.length === 1) {
result = result[0];
}
Action.setVariable(context, variableName, result);
}
return context;
});
}
}
Action.registerPrimitiveAction("removeItem", RemoveItemAction);
window.RemoveItemAction = RemoveItemAction;
/**
* An action 'items' that extracts an array property or variable into another variable, optionally applying filtering
* @memberOf ArrayActions
* @example
* //Shorthand, returns result into variable 'items' and applies no filtering
* {
* "items": "myArrayProperty"
* }
*
* @example
* //Shorthand, returns result into variable 'items' and applies no filtering
* {
* "items": "$myArrayVariable"
* }
*
* @example
* //Property example
* {
* "items:" {
* "property": "myArrayProperty",
* "as": "myResultVariableName",
* "where": {
* "equals": "my-specific-value"
* }
* }
* }
*
* //Property example, on non selected concept
* {
* "items:" {
* "property": "myConcept.myArrayProperty",
* "as": "myResultVariableName",
* "where": {
* "equals": "my-specific-value"
* }
* }
* }
*
* @example
* //Variable example
* {
* "items:" {
* "variable": "myArrayVariableName",
* "as": "myResultVariableName",
* "where": {
* "equals": "my-specific-value"
* }
* }
* }
*/
class ItemsAction extends Action {
static options() {
return {
"$items": "enumValue[property,variable]",
"as": "@string",
"where": "filter"
}
}
constructor(name, options, concept) {
//Shorthand
if(typeof options === "string") {
if(options.trim().startsWith("$")) {
options = {
variable: options.trim().substring(1)
}
} else {
options = {
property: options
}
}
}
super(name, options, concept);
}
async apply(contexts, actionArguments) {
const self = this;
return this.forEachContext(contexts, actionArguments, async (context, options)=>{
if(options.property == null && options.variable == null) {
throw new Error("Action 'items' must have either option 'property' or 'variable'");
}
let resultName = Action.defaultVariableName(self);
if(options.as != null) {
resultName = options.as;
}
let result = null;
if(options.property != null) {
const lookup = await VarvEngine.lookupProperty(context.target, self.concept, options.property);
if(lookup == null) {
throw new Error("No property ["+options.of.property+"] found");
}
const concept = lookup.concept;
const property = lookup.property;
const target = lookup.target;
if(property.type !== "array") {
throw new Error("Property ["+options.property+"] of ["+concept.name+"] is not an array");
}
result = await property.getValue(target);
} else if(options.variable != null) {
result = Action.getVariable(context, options.variable);
if(!Array.isArray(result)) {
throw new Error("Variable ["+options.variable+"] did not contain an array!");
}
}
if(options.where != null) {
let filteredResult = [];
let filter = FilterAction.constructFilter(options.where, true);
for(let v of result) {
//TODO: Might backfire if an array of strings contains the string equal to a concept?
let concept = await VarvEngine.getConceptFromUUID(v);
if(concept != null) {
v = {
target: v
}
}
if(await filter.filter(v)) {
if(v.target != null) {
v = v.target;
}
filteredResult.push(v);
}
}
result = filteredResult;
}
Action.setVariable(context, resultName, result);
return context;
});
}
}
Action.registerPrimitiveAction("items", ItemsAction);
window.ItemsAction = ItemsAction;
/**
* An action 'join' that joins an array into a string and saves it in a variable, default separator: ","
* @memberOf ArrayActions
* @example
* {
* "join": {
* "property": "myProperty",
* "separator": ","
* }
* }
*
* @example
* {
* "join": {
* "variable": "myProperty",
* "separator": ","
* }
* }
*
* @example
* //Shorthand, joins the array in property "myArrayProperty" into a string
* {
* "join": "myArrayProperty"
* }
*
* @example
* //Shorthand, joins the array in variable "myArrayVariable" into a string
* {
* "join": "$myArrayVariable"
* }
*/
class JoinAction extends Action {
constructor(name, options, concept) {
if(typeof options === "string") {
if(options.trim().startsWith("$")) {
options = {
of: {
variable: options.trim().substring(1)
}
}
} else {
options = {
of: {
property: options
}
}
}
}
if(options.property != null) {
options.of = {
property: options.property
}
delete options.property;
}
if(options.variable != null) {
options.of = {
variable: options.variable
}
delete options.variable;
}
const defaultOptions = {
"separator": ","
}
super(name, Object.assign({}, defaultOptions, options), concept);
}
async apply(contexts, actionArguments = {}) {
const self = this;
return this.forEachContext(contexts, actionArguments, async (context, options)=>{
let result = null;
let inputArray = null;
if(options?.of.property != null) {
let lookup = VarvEngine.lookupProperty(context.target, self.concept, options.of.property);
const concept = lookup.concept;
const property = lookup.property;
const target = lookup.target;
if(property.type !== "array") {
throw new Error("Property ["+options.of.property+"] of ["+concept.name+"] is not an array");
}
inputArray = property.getValue(target);
} else if(options?.of.variable != null) {
inputArray = Action.getVariable(context, options.of.variable);
} else {
throw new Error("'join' requires either option 'of.property' or option 'of.variable' to be present:"+JSON.stringify(options));
}
if(!Array.isArray(inputArray)) {
throw new Error("Targeted variable or property, did not result in an array: "+JSON.stringify(options));
}
if(inputArray != null) {
result = inputArray.join(options.separator);
}
let variableName = Action.defaultVariableName(self);
if(options.as != null) {
variableName = options.as;
}
Action.setVariable(context, variableName, result);
return context;
});
}
}
window.JoinAction = JoinAction;
Action.registerPrimitiveAction("join", JoinAction);
/**
* An action 'slice' that slices a string or an array.
*
* @memberOf ArrayActions
*
* @example
* //Slices mySliceableProperty starting from 0, ending with 10, saving the result in "myResultVariable"
* {
* "slice": {
* "of": {
* "property": "mySliceableProperty"
* },
* "start": 0,
* "end": 10,
* "as": "myResultVariable"
* }
* }
*
* @example
* //Shorthand: Slices the variable "mySliceableVariable", from 0 to end, which in practice, means its a copy of the whole thing
* {
* "slice: "mySliceableVariable"
* }
*/
class SliceAction extends Action {
constructor(name, options, concept) {
if(typeof options === "string") {
if(options.startsWith("$")) {
options = {
"of": {
"variable": options.trim().substring(1)
}
}
} else {
options = {
"of": {
"property": options
}
}
}
}
if(options.property != null) {
options.of = {
property: options.property
}
delete options.property;
}
if(options.variable != null) {
options.of = {
variable: options.variable
}
delete options.variable;
}
super(name, options, concept);
}
async apply(contexts, actionArguments = {}) {
const self = this;
return this.forEachContext(contexts, actionArguments, async (context, options)=> {
let theThingToSlice = null;
if(options.of == null) {
throw new Error("'slice' requires option 'of' to be present:"+JSON.stringify(options));
}
if(options.of.variable != null) {
//Handle variable
theThingToSlice = Action.getVariable(context, options.of.variable);
} else if(options.of.property != null) {
//Handle property
let lookup = await VarvEngine.lookupProperty(context.target, self.concept, options.of.property);
const concept = lookup.concept;
const property = lookup.property;
const target = lookup.target;
if(property.type !== "array") {
throw new Error("Property ["+options.property+"] of ["+concept.name+"] is not an array");
}
theThingToSlice = await property.getValue(target);
} else {
throw new Error("'slice' requires option 'of.variable' or 'of.property' to be present:"+JSON.stringify(options));
}
if(theThingToSlice == null) {
throw new Error("'slice' the chosen array was null: "+JSON.stringify(options));
}
if(theThingToSlice.slice == null) {
throw new Error("'slice' unable to slice on type: "+(typeof theThingToSlice));
}
let start = 0;
let end = theThingToSlice.length;
if(options.start != null) {
start = options.start;
}
if(options.end != null) {
end = options.end;
}
let result = theThingToSlice.slice(start, end);
let variableName = Action.defaultVariableName(self);
if(options.as != null) {
variableName = options.as;
}
Action.setVariable(context, variableName, result);
return context;
});
}
}
window.SliceAction = SliceAction;
Action.registerPrimitiveAction("slice", SliceAction);
/**
* An action 'index' that finds the index of a given item in an array
*
* @example
* //Lookup the item "myLookupItem" inside the array property "myArrayProperty", and save the resulting index in the variable "myResultVariableName"
* {"index": {
* "of": {
* "property": "myArrayProperty"
* },
* "item": "myLookupItem",
* "as": "myResultVariableName"
* }}
*
* @example
* //Shorthand, lookup the "myLookupItem" inside the property array "myArrayProperty", and saves the index in the variable "index"
* {"index": {"myArrayProperty": "myLookupItem"}}
*/
class IndexAction extends Action {
constructor(name, options, concept) {
//Handle {"index": {"$myVariable": "myItem"}} shorthand
//Handle {"index": {"myProperty": "myItem"}} shorthand
if(Object.keys(options).length === 1) {
let key = Object.keys(options)[0];
let value = options[key];
if(key.startsWith("$")) {
options = {
"of": {
"variable": key.substring(1)
},
"item": value
}
} else {
options = {
"of": {
"property": key
},
"item": value
}
}
}
if(options.property != null) {
options.of = {
property: options.property
}
delete options.property;
}
if(options.variable != null) {
options.of = {
variable: options.variable
}
delete options.variable;
}
super(name, options, concept);
}
async apply(contexts, actionArguments = {}) {
const self = this;
return this.forEachContext(contexts, actionArguments, async (context, options) => {
let theArray = null;
if(options.of == null) {
throw new Error("'index' requires option 'of' to be present:"+JSON.stringify(options));
}
if(options.item == null) {
}
if(options.of.variable != null) {
//Handle variable
theArray = Action.getVariable(context, options.of.variable);
} else if(options.of.property != null) {
//Handle property
let lookup = await VarvEngine.lookupProperty(context.target, self.concept, options.of.property);
const concept = lookup.concept;
const property = lookup.property;
const target = lookup.target;
if(property.type !== "array") {
throw new Error("Property ["+options.property+"] of ["+concept.name+"] is not an array");
}
theArray = await property.getValue(target);
} else {
throw new Error("'index' requires option 'of.variable' or 'of.property' to be present:"+JSON.stringify(options));
}
if(theArray == null) {
throw new Error("'index' the chosen array was null: "+JSON.stringify(options));
}
if(theArray.indexOf == null) {
throw new Error("'index' unable to find index on type: "+(typeof theArray));
}
let index = theArray.indexOf(options.item);
let variableName = Action.defaultVariableName(self);
if(options.as != null) {
variableName = options.as;
}
Action.setVariable(context, variableName, index);
return context;
});
}
}
Action.registerPrimitiveAction("index", IndexAction);
window.IndexAction = IndexAction;