Aras Innovator Platform

Cookbook

This section is a Cookbook of recipes to help you solve common tasks while developing Methods. The examples are shown in JavaScript, C# and VB.NET when possible.

You need an Aras Innovator Object to return a newResult() or newError() Item.

There are basically two ways to create a new Innovator object; by getting the Innovator from the Item object or (only in JavaScript) calling the Class constructor.

var myInnovator = new Innovator();
var myInnovator = this.getInnovator();
Innovator myInnovator = this.getInnovator();

You need an Item Object to submit a query or to add an Item.

There are basically two ways to create a new Item Object; by calling the factory methods on the Item object or Innovator object or (only in JavaScript) calling the Class constructor.

var myItem = new Item();
var myItem = this.newItem(myType,myAction);
var myInnovator = this.getInnovator();
var myItem = myInnovator.newItem(myType,myAction);
var myResult = myInnovator.newResult(resultText);
var myError = myInnovator.newError(errorMessage);
Item myItem = this.newItem(myType,myAction);
Innovator myInnovator = this.getInnovator();
Item myItem = myInnovator.newItem(myType,myAction);
Item myResult = myInnovator.newResult(resultText);
Item myError = myInnovator.newError(errorMessage);

You want to query for an Item that you know by id and type.

There are a few ways to get an Item when you know its id and type, the simplest being the Innovator.getItemById() method. However, if you need to be granular about your request then building the query using the IOM is required. This provides the ability to include controls to limit the results and define the structure to be returned for the Items found.

var qryItem = this.newItem(myType,"get”);
qryItem.setID(myId);
var results = qryItem.apply();
var myInnovator = this.getInnovator();
var results = myInnovator.getItemById(myType, myId);
Item qryItem = this.newItem(myType,"get”);
qryItem.setID(myId);
Item results = qryItem.apply();
Innovator myInnovator = this.getInnovator();
Item results = myInnovator.getItemById(myType, myId);

You want to query for the Items that match some criteria and generate an HTML Table as the result.

There is no difference in setting up a query for a single Item or for many. Only the criteria define the set size returned. In this recipe you create an Item and populate the query criteria, apply it, and iterate over the Items returned producing an HTML <TABLE> fragment.

var qryItem = this.newItem(“Part”,"get”);
qryItem.setAttribute(“select”,"item_number,description,cost”);
qryItem.setProperty(“cost”, “100");
qryItem.setPropertyCondition(“cost”, “gt”);
var results = qryItem.apply();
var count = results.getItemCount();
var content = "<table>";
for (var i=0; i<count; ++i)
{
 var item = results.getItemByIndex(i);
 content += "" +
 "<tr>" +
 "<td>" + item.getProperty(“item_number”) + "</td>" +
 "<td>" + item.getProperty(“description”) + "</td>" +
 "<td>" + item.getProperty(“cost”) + "</td>" +
 "</tr>";
}
content += "</table>";
return content;
Item qryItem = this.newItem(“Part”,"get”);
qryItem.setAttribute(“select”,"item_number,description,cost”);
qryItem.setProperty(“cost”, “100");
qryItem.setPropertyCondition(“cost”, “gt”);
Item results = qryItem.apply();
int count = results.getItemCount();
int i;
string content = "<table>";
for (i=0; i<count; ++i)
{
 Item item = results.getItemByIndex(i);
 content += "" +
 "<tr>" +
 "<td>" + item.getProperty(“item_number”) + "</td>" +
 "<td>" + item.getProperty(“description”) + "</td>" +
 "<td>" + item.getProperty(“cost”) + "</td>" +
 "</tr>";
}
content += "</table>";
Innovator innovator = this.getInnovator();
return innovator.newResult(content);

You want to query for an Item and return its configuration in the results.

To query for an Item and retrieve its structure you build the query as the structure you want returned. Use the IOM methods to add the relationships you want and build the structure in the Item. The server returns the structure that follows the request structure.

This recipe illustrates several related concepts together, which are how to get a set of Items from an Item and how to iterate over the set, plus how to get the related Item from the relationship Item.

var innovator = this.getInnovator();
// Set up the query Item.
var qryItem = this.newItem(“Part”,"get”);
qryItem.setAttribute(“select”,"item_number,description,cost”);
qryItem.setID(myId);
// Add the BOM structure.
var bomItem = this.newItem(“Part BOM”,"get”);
bomItem.setAttribute(“select”,"quantity,related_id(item_number,description,cost)”);
qryItem.addRelationship(bomItem);
// Perform the query.
var results = qryItem.apply();
// Test for an error.
if (results.isError()) {
 top.aras.AlertError(“Item not found: " + results.getErrorDetail());
 return;
}
// Get a handle to the BOM Items.
var bomItems = results.getRelationships();
var count = bomItems.getItemCount();
// Create the results content.
var content = "<table border='1'>" +
 "<tr>" +
 "<td>Part Number</td>" +
 "<td>Description</td>" +
 "<td>Cost</td>" +
 "<td>Quantity</td>" +
 "</tr>";
// Iterate over the BOM Items.
for (var i=0; i<count; ++i)
{
// Get a handle to the relationship Item by index.
 var bom = bomItems.getItemByIndex(i);
// Get a handle to the related Item for this relationship Item.
 var bomPart = bom.getRelatedItem();
 content += "<tr>" +
 "<td>" + bomPart.getProperty(“item_number”) + "</td>" +
 "<td>" + bomPart.getProperty(“description”) + "</td>" +
 "<td>" + bomPart.getProperty(“cost”) + "</td>" +
 "<td>" + bom.getProperty(“quantity”) + "</td>" +
 "</tr>";
}
return content + "</table>";
Innovator innovator = this.getInnovator();
// Set up the query Item.
Item qryItem = this.newItem(“Part”,"get”);
qryItem.setAttribute(“select”,"item_number,description,cost”);
qryItem.setID(myId);
// Add the BOM structure.
Item bomItem = this.newItem(“Part BOM”,"get”);
bomItem.setAttribute(“select”,"quantity, related_id(item_number,description,cost)”);
qryItem.addRelationship(bomItem);
// Perform the query.
Item results = qryItem.apply();
// Test for an error.
if (results.isError()) {
 return innovator.newError(“Item not found: " + results.getErrorDetail());
}
// Get a handle to the BOM Items.
Item bomItems = results.getRelationships();
int count = bomItems.getItemCount();
int i;
// Create the results content.
string content = "<table border='1'>" +
 "<tr>" +
 "<td>Part Number</td>" +
 "<td>Description</td>" +
 "<td>Cost</td>" +
 "<td>Quantity</td>" +
 "</tr>";
// Iterate over the BOM Items.
for (i=0; i<count; ++i)
{
// Get a handle to the relationship Item by index.
 Item bom = bomItems.getItemByIndex(i);
// Get a handle to the related Item for this relationship Item.
 Item bomPart = bom.getRelatedItem();
 content += "" +
 "<tr>" +
 "<td>" + bomPart.getProperty(“item_number”) + "</td>" +
 "<td>" + bomPart.getProperty(“description”) + "</td>" +
 "<td>" + bomPart.getProperty(“cost”) + "</td>" +
 "<td>" + bom.getProperty(“quantity”) + "</td>" +
 "</tr>";
}
content += "</table>";
return innovator.newResult(content);

You want to perform a query using the AML to construct the query criteria.

Create an Item Object but use the Item.loadAML() method to populate the Item.

var innovator = new Innovator();
var qryItem = innovator.newItem();
qryItem.loadAML(
 "<Item type='Part’ action='get’ select='item_number,description,cost'>" +
 "<item_number condition='like'>1%</item_number>" +
 "<Relationships>" +
 "<Item type='Part BOM’ action='get’ select='quantity'>" +
 "<quantity condition='gt'>1</quantity>" +
 "</Item>" +
 "</Relationships>" +
 "</Item>"
);
var resultItem = qryItem.apply();
if (resultItem.isError()) {
 top.aras.AlertError(“Item not found: " + resultItem.getErrorDetail());
 return;
}
var count = resultItem.getItemCount();
for (i=0; i<count; ++i) {
 var item = resultItem.getItemByIndex(i);
} 

You want to get the next promotion states for an Item and use the states as the choices for a dropdown control.

Use the item action getItemNextStates to get the next state values. This recipe assumes there is a select input field on the form for us to populate with state values.

<select id="mySelect"></select>
var results = document.thisItem.apply(“getItemNextStates”);
var nextStates = results.getItemsByXPath('//Item[@type="Life Cycle State”]’);
var count = nextStates.getItemCount();
var oSelect = document.getElementById(‘mySelect’);
for (var i=0; i<count; ++i) {
 var item = nextStates.getItemByIndex(i);
 var opt = document.createElement(‘option’);
 opt.text = item.getProperty(‘name’);
 oSelect.add(opt);
}

You want to search for an Item using the relationship and related Items as search criteria. In this recipe, we get the User Item that matches the Identity name as an Alias relationship to the User Item.

The following are some key points to understand when constructing an AML query:

  1. Use the get action on the relationship Items to include it as search criteria.
    1. Without the get action the relationship Item is ignored as search criteria.
    2. The relationship Items are also returned. Currently there is no way to use relationships as search criteria and not return them in the results, as you can with the related Item described below.
  2. Include the related_id property name in the select attribute for the relationship Item if you want to return the related Item nested inside the related_id property in the results.
  3. <Item type="Part BOM” select="quantity,related_id"/>Use () to include the select attribute value for the related Item inside the select attribute for the relationship Item.<Item type="Part BOM” select="quantity,related_id(item_number,description)"/>

  4. The select attribute for the nested Item tag for the related_id property has higher precedence over the select value inside the () for the relationship’s select attribute.
  5. The get action is not required for the nested Item tag for the related_id property to include it as search criteria.

These two AML scripts are equivalent queries for selecting the name property for the related Item: <Item type="User” action="get” select="first_name,last_name,email"> <Relationships> <Item type="alias” action="get” select="related_id(name)"/> </Relationships> </Item> <Item type="User” action="get” select="first_name,last_name,email"> <Relationships> <Item type="alias” action="get” select="related_id"> <related_id> <Item type="Identity” action="get” select="name"/> </related_id> </Item> </Relationships> </Item> Clearly the first example is simpler and requires less coding (referring to the IOM logic that would construct the AML) and is the recommended style when all you require is specifying a select for the related Item for the query. But the second style opens the opportunity to now include additional search criteria for the related Item.

<Item type="User” action="get” select="first_name,last_name,email">
 <Relationships>
 <Item type="Alias” action="get” select="related_id">
<!--
This get will limit root Items to only those that match the relationship criteria.
The get action is required otherwise the criteria are ignored.
To include the nested Item tag for the related_id include the property name in the select attribute for the relationship Item.
Can include the select attribute value for the related Item inside ()
i.e. related_id(name)
-->
 <related_id>
 <Item type="Identity” action="get” select="keyed_name">
<!--
This get has no effect and the search will work with or without it.
It is recommended that you include it because the AML parser may be stricter in the future.
The select attribute over rules the parent relationships select.
-->
 <name>Larry Bird</name>
 </Item>
 </related_id>
 </Item>
 </Relationships>
</Item>
var innovator = new Innovator();
var qry = innovator.newItem(“User”,"get”);
qry.setAttribute(“select”,"first_name,last_name,email”);
var alias = new Item(“Alias”,"get”);
alias.setAttribute(“select”,"related_id”);
var identity = new Item(“Identity”,"get”);
identity.setAttribute(“select”,"name”);
identity.setProperty(“name”, “Larry Bird”);
alias.setRelatedItem(identity);
qry.addRelationship(alias) ;
var results = qry.apply();
if (results.isError()) {
 top.aras.AlertError(results.getErrorDetail());
 return;
}

You want to perform a recursive query on a related item. Technique Use the GetItemRepeatConfig action to perform the query. For more information, refer to the table in section 4.2 Built in Action Methods. AML

<Item type="Part” action="GetItemRepeatConfig” select="item_number,name,major_rev,has_change_pending,state” id="{@id}"> <Relationships> <Item type="Part BOM” select="sort_order,quantity,_kit_flag,related_id” repeatProp="related_id” repeatTimes="6"/> </Relationships> </Item>

You want to add an Item configuration like a BOM as one transaction. Adding an Item configuration is done by building the Item structure using the IOM methods.

var innovator = new Innovator();
var partItem = innovator.newItem(“Part”,"add”);
partItem.setProperty(“item_number”, “123-456");
partItem.setProperty(“description”, “Blah blah”);
var bomItem = new Item(“Part BOM”,"add”);
bomItem.setProperty(“quantity”, “10");
var relatedItem = new Item(“Part”,"get”);
relatedItem.setProperty(“item_number”, “555-555");
bomItem.setRelatedItem(relatedItem);
partItem.addRelationship(bomItem) ;
var resultItem = partItem.apply();
if (resultItem.isError()) {
 top.aras.AlertError(resultItem.getErrorDetail());
 return;
} 

The following is the same thing but uses the Item.loadAML() method to populate the Item Object with AML text.

var innovator = new Innovator();
var partItem = innovator.newItem();
partItem.loadAML(
 "<Item type='Part’ action='add’ >" +
 "<item_number>123-456</item_number>" +
 "<description>Blah blah</description>" +
 "<Relationships>" +
 "<Item type='Part BOM’ action='add'>" +
 "<quantity>10</quantity>" +
 "<related_id>" +
 "<Item type='Part’ action='get'>" +
 "<item_number>555-555</item_number>" +
 "</Item>" +
 "</related_id>" +
 "</Item>" +
 "</Relationships>" +
 "</Item>"
);
var resultItem = partItem.apply();
if (resultItem.isError()) {
 top.aras.AlertError (resultItem.getErrorDetail());
 return;
}

You want to add a new named Permission Item. Use the Item Class Extended Method set to add a new Named Permission Item.

var innovator = new Innovator();
var permItem = innovator.newItem(“Permission”,"add”);
permItem.setProperty(“name”, “AK Part Permissions”);
setIdentityAccess(permItem, “All Employees”, “get”, true);
setIdentityAccess(permItem, “CM”, “get”, true);
setIdentityAccess(permItem, “CM”, “update”, true);
setIdentityAccess(permItem, “CM”, “delete”, true);
resultItem = permItem.apply();
if (resultItem.isError()) {
 top.aras.AlertError(resultItem.getErrorDetail());
 return;
} 
function setIdentityAccess(item, identityName, permType, accessState)
{
 var identity = item.newItem();
 identity.setType(“Identity”);
 identity.setAction(“get”);
 identity.setProperty(“name”, identityName);
 var access = item.newItem(“Access”,"add”);
 access.setProperty(“can_"+permType, (accessState ? “1" : “0"));
 access.setRelatedItem(identity);
 item.addRelationship(access);
}

You want to set a new private Permission for an Item. Use the Item Class Extended Method set to set a new private Permission for an Item.

// Set up the Part query
var innovator = new Innovator;
var qryItem = innovator.newItem(“Part”, “get”);
qryItem.setAttribute(“select”, “id,permission_id”);
qryItem.setAttribute(“expand”, “1");
qryItem.setAttribute(“levels”, “1");
qryItem.setPropertyCondition(“item_number”, “like”);
qryItem.setProperty(“item_number”, “123%");
// Run the query and check for errors
var resultItem = qryItem.apply();
if (resultItem.isError()) {
 top.aras.AlertError(resultItem.getErrorDetail());
 return;
}
// Iterate over the Items returned and add the private permissions for each.
var count = resultItem.getItemCount();
for (i=0; i<count; ++i) {
 var item = resultItem.getItemByIndex(i);
 var permItem = item.getPropertyItem(“permission_id”);
 // Remove existing permissions first
 var accesses = permItem.getRelationships(“Access”);
 for (i=0; i<accesses.getItemCount(); i++) {
 var access = accesses.getItemByIndex(i);
 access.setAction(“delete”);
 }
 permItem.setProperty(“name”, permItem.getID());
 setIdentityAccess(permItem, “Component Engineering”, “get”, true);
 setIdentityAccess(permItem, “CM”, “get”, true);
 setIdentityAccess(permItem, “CM”, “update”, true);
 // Grant access to the current user’s alias identity
 var myAlias = innovator.newItem(“Alias”,"get”);
 myAlias.setProperty(“source_id”, inn.getUserID());
 myAlias = myAlias.apply();
 var aliasId = myAlias.getItemByIndex(0).getProperty(“related_id”);
 var aliasName = innovator.getItemById(“Identity”,aliasId).getProperty(“name”);
 setIdentityAccess(permItem, aliasName, “get”, true);
 item.setAction(“edit”);
 resultItem = item.apply();
 if (resultItem.isError()) { top.aras.AlertError(resultItem.getErrorDetail()); }
}
function setIdentityAccess(item, identityName, permType, accessState)
{
 var identity = item.newItem();
 identity.setType(“Identity”);
 identity.setAction(“get”);
 identity.setProperty(“name”, identityName);
 var access = item.newItem(“Access”,"add”);
 access.setProperty(“can_"+permType, (accessState ? “1" : “0"));
 access.setRelatedItem(identity);
 item.addRelationship(access);
}

You want to write Generic Methods that can be used as subroutines for other Methods. Use the Innovator.applyMethod() method to apply Generic Methods. The following examples assume a server-side method named “Reverse String” exists, and that it returns a result item containing the reversed contents of the <string> tag.

var inn = this.getInnovator();
var results = inn.applyMethod(“Reverse String”, "<string>abc</string>");
return results.getResult(); // returns “cba”
Innovator inn = this.getInnovator();
Item results = inn.applyMethod(“Reverse String”, "<string>abc</string>");
// Return a result item with “cba” as the contents of the Result tag
return inn.newResult(results.getResult()); 

You want to save text to a file. Use the File and StreamWriter namespaces to write to a text file.

Innovator myInnovator = this.getInnovator();
string path = CCO.Server.MapPath(“temp/yoyo.txt”);
try
{
 if (File.Exists(path)) File.Delete(path);
 StreamWriter sw = File.CreateText(path);
 sw.Write(this.dom.InnerXml);
 sw.Close();
}
catch (Exception e)
{
 return myInnovator.newError(e.Message);
}
return myInnovator.newResult(“ok”);

You want to send an Email message from either a server or client-side Method. Use the Aras.IOM.Item.email(mail_item, idnt_item) method to send email to a particular Innovator’s identity.

Note
Aras Innovator’s identity could be a group of people in which case the email is sent to all of them.

... 
// It’s assumed here that required identity (item of type ‘Identity’)
// has already obtained (see other examples on how to perform ‘get’
// requests to Innovator server using IOM). Same about item of type
// ‘User’ that represents the person who sends the email (‘fromUser’).
Item idnt = ...
Item fromUser = ...
// It’s assumed in the sample that this represents an item of
// type Part. ${Item/item_number} in the email message is a parameter
// that represents the XPath to the property item_number of 
// item of type Part; this parameter will be substituted on
// this.item_number before the email is sent. Note that both 
// subject and body of the email item could be parameterized. 
// This mechanism allows to created parameterized email templates
// (items of type “Email Message”) that could be saved
// in Innovator and used for sending emails with concrete content
// when required.
string subject = “Part promotion notification";
string body = @"The part ${Item/item_number} has been promoted";
// In this particular example instead of getting a ready template
// email from the server a new item of type “EMail Message” is created
Item email_msg = this.newItem(“EMail Message”);
email_msg.setProperty(“subject”, subject);
email_msg.setProperty(“body_plain”, body);
email_msg.setPropertyItem(“from_user”, fromUser);
// Finally send the email
if( this.email( email_msg, idnt ) == false )
{
 // Error handling
 ...
}

You want to call a client side Method when the Relationships Grid row is selected to deselect the row if it is not a new relationship row. Add the Grid Event relationship to a Method as the callback for the OnSelectRow event.

The Method gets three arguments: relationshipID, relatedID and gridApplet. The relationshipID is the ID for the relationship Item for the selected row. The relatedID is the ID for the related Item for the selected row. The gridApplet is a handle to the grid control object. The relationshipID is also the ID for the grid control row. This recipe calls the gridApplet Deselect() method if the relationship Item for the selected row has not been modified.

// The row ID is the same as the relationship ID
var rowId = gridApplet.getSelectedId();
// Find the relationship item and exit if it’s not found
var thisItem = parent.thisItem;
var xpath = “Relationships/Item[@id='" + rowId + "']";
var relItem = thisItem.getItemsByXPath(xpath);
if (relItem.getItemCount() == 1) { 
 relItem = relItem.getItemByIndex(0);
} else {
 return; 
}
// Check the isDirty attribute to see if the relationship has been modified
var isDirty = (relItem.getAttribute(“isDirty”) == “1");
if (!isDirty) { gridApplet.Deselect(); }

You want to call a client-side Method when the Relationships Grid cell is selected to blur the cell and prevent editing the cell value. Add the Event relationship to a Property as the callback for the OnEditCell event.

The Method gets five arguments: relationshipID, relatedID, propertyName, colNumber, gridApplet The propertyName is the name of the Property for the cell column selected, and colNumber is the column position number in the grid. Simple return false and this blurs the grid cell.

// Get the current value of the cell
var cellValue = gridApplet.GetCellValue(relationshipID,colNumber);
// If the cell already has a value, disallow editing
if (cellValue !== "") {
 return false;
}

You want to show the relationships for the context Item in a Grid control on the Item Form. Add an HTML Field (positioned at Point (300,10) in the code sample below) and insert an HTML code than defines the <div>tag to hold the dynamically populated grid and the JavaScript to get the populating grid relationships. HTML Field code

<div id="gridTD” style="width: 400px; height: 500px;"></div>
<script type="text/javascript">
 var myCount = 0;
 var gridControl;
 clientControlsFactory.createControl(“Aras.Client.Controls.Public.GridContainer”, {id: “grid”, connectId: “gridTD”, canEdit_Experimental: function () { return false; }}, function(control){
 gridControl = control;
 clientControlsFactory.on(gridControl, {
 “gridClick": function (rowID, column) {
 alert(“rowId:" + rowID + ", col:" + column);
 }
 });
 fillGrid();
 });
 function fillGrid() {
 var item = document.thisItem;
 // Get the relationships
 var qry = item.newItem(“Part BOM”, “get”);
 qry.setAttribute(“select”, “quantity,related_id(item_number,name,cost)”);
 qry.setProperty(“source_id”, item.getID());
 var results = qry.apply();
 if (results.getItemCount() < 0) {
 top.aras.AlertError(results.getErrorDetail());
 return;
 }
 // Populate the grid with the results.
 populateGrid(item, results);
 }
 function populateGrid(item, results) {
 var propNameArr = new Array(“item_number”, “name”, “cost”);
 var gridXml =
 "<table editable='false’ draw_grid='true'>" +
 "<columns>" +
 "<column width='30%' order='0' align='left’ />" +
 "<column width='40%' order='1' align='left’ />" +
 "<column width='15%' order='2' align='right’ />" +
 "<column width='15%' order='3' align='right’ />" +
 "</columns>" +
 "<thead>" +
 "<th>Part Number</th>" +
 "<th>Name</th>" +
 "<th>Cost</th>" +
 "<th>Quantity</th>" +
 "</thead>" +
 "</table>";
 var inn = item.getInnovator();
 var gridDom = inn.newXMLDocument();
 gridDom.loadXML(gridXml);
 var tableNd = gridDom.selectSingleNode("/table”);
 var c = results.getItemCount();
 for (var i=0; i<c; ++i) {
 var bom = results.getItemByIndex(i);
 var part = bom.getRelatedItem();
 var trNd = gridDom.createElement(“tr”);
 trNd.setAttribute(“id”, bom.getID());
 var tdNd;
 for (var j=0; j<propNameArr.length; j++) {
 tdNd = gridDom.createElement(“td”);
 tdNd.text = part.getProperty(propNameArr[j]);+
 trNd.appendChild(tdNd);
 }
 tdNd = gridDom.createElement(“td”);
 tdNd.text = bom.getProperty(“quantity”);
 trNd.appendChild(tdNd);
 tableNd.appendChild(trNd);
 }
 gridControl.InitXML(gridDom.xml);
 } </script>

You want to get the Identity IDs for the User. Use the classic Aras Innovator API top.aras.getIdentityList() method, which returns a comma delimited string of IDs.

Note
The classic API will eventually be eliminated, and this method will become available on the IOM Innovator object as Innovator.getIdentityList().

// This will get an array of identity IDs from the client cache
var myIdentityIDs = top.aras.getIdentityList().split(',’);

You want to enable a field on the form to be a sequence value or allow the user to enter a value. Use the classic Aras Innovator API top.aras.getNetSequence() method, which returns the next sequence value from the server.

Note
The classic API will eventually be eliminated, and this method will become available on the IOM Innovator object as Innovator.getNetSequence().

Using the Form Tool we add an HTML Field to the Form, which we use to insert the HTML and JavaScript code for to provide the button to get the next sequence value and update the Field value and client cache.

You want to save a CAD Document with an attached Native File. Technique You will need to use the setFileProperty call which handles creating a file and sets the associated value on the specified property. Client-side methods use selectFile()that gets a File object based on user selection: Javascript

 var vlt = top.aras.vault;
 vlt.selectFile().then(function (fileObject)
 {
 var d = aras.IomInnovator.newItem(“CAD”, “add”);
 d.setProperty(“item_number”, “007");
 d.setFileProperty(“native_file”, fileObject);
 d.apply();
 });

Server-side methods require a string for the physical path to the file. The file must be on the server machine. C#

 Item d = this.newItem(“CAD”, “add”);
 d.setProperty(“item_number”, “007");
 d.setFileProperty(“native_file”, @"C:\myFile.txt”);
 return d.apply();

You want to get an existing File from the Vault and attach it to a new Document This is similar to the last recipe, but uses the getItemByKeyedName() method to get an existing File Item and copyAsNew() to create it as a new File Item.

// Create the Document item
var docItem = this.newItem(“Document”,"add”);
docItem.setProperty(“item_number”,"456");
// Get the File item
var innovator = this.getInnovator();
var fileItem = innovator.getItemByKeyedName(“File”,"My Document.doc”);
if (fileItem.isError()) {
 top.aras.AlertError(fileItem.getErrorDetail());
 return;
}
// Duplicate File Item as files should be 1 to 1
var newFile = fileItem.apply(“copyAsNew”);
// Create the relationship between the Document and File
var relItem = this.newItem(“Document File”,"add”);
docItem.addRelationship(relItem);
relItem.setRelatedItem(newFile);
var results = docItem.apply();
if (results.isError()) {
 top.aras.AlertError(results.getErrorDetail());
} else {
 // Show the new Document
 top.aras.uiShowItemEx(results.getItemByIndex(0).node, ‘tab view’, true);
}

You want to reject an Item Promote if a value of Property is invalid. Use the Pre Server-Method on the Life Cycle Transition to call a server-side Method to validate the Item before it is promoted and if invalid rejects the Promote by returning an Error Item.

Innovator innovator = this.getInnovator();
if (Convert.ToDecimal(this.getProperty(“cost”)) > 500) {
 Item error = innovator.newError(“Error promoting: Item costs more than $500.00");
 return error;
}
return this;

You want to programmatically get/set multilingual string properties Use Item.setAttribute(“language”,"*") to get all language values and Item.setProperty(myProperty,value,lang) to set specific language values.

var inn = this.getInnovator();
// Add a new List with multilingual Value labels
var listItem = this.newItem(“List”,"add”);
listItem.setProperty(“name”,"Numbers”);
var valueItem = listItem.createRelationship(“Value”,"add”);
valueItem.setProperty(“value”,"1");
valueItem.setProperty(“label”,"One”,"en”);
valueItem.setProperty(“label”,"Ein”,"de”);
var valueItem2 = listItem.createRelationship(“Value”,"add”);
valueItem2.setProperty(“value”,"2");
valueItem2.setProperty(“label”,"Two”,"en”);
valueItem2.setProperty(“label”,"Zwei”,"de”);
var resultItem = listItem.apply();
if (resultItem.isError()) {
 return inn.newError(“Error adding List: " + resultItem.getErrorDetail());
}
// Retrieve the List with labels in both English and German
listItem = this.newItem(“List”,"get”);
listItem.setProperty(“name”,"Numbers”);
valueItem = listItem.createRelationship(“Value”,"get”);
valueItem.setAttribute(“language”,"en,de”);
resultItem = listItem.apply();
if (resultItem.isError()) {
 return inn.newError(“Error retrieving List: " + resultItem.getErrorDetail());
}

You want to programmatically get/set date properties Convert date values to “yyyy-MM-ddThh:mm:ss” format before setting the property

// Get yesterday’s date
var myDate = new Date();
myDate.setDate(myDate.getDate()-1);
// Find all methods edited in the past 24 hours
var myItem = this.newItem(“Method”,"get”);
myItem.setAttribute(“select”,"name”);
myItem.setProperty(“modified_on”,dateFormat(myDate));
myItem.setPropertyAttribute(“modified_on”,"condition”,"gt”);
myItem = myItem.apply();
// Loop through the returned methods and return the list
var methodList = new String("");
for (var i=0; i<myItem.getItemCount(); i++) {
 methodList += myItem.getItemByIndex(i).getProperty(“name”,"") + ", ";
}
top.aras.AlertError(“The following methods were modified in the past 24 hours: "+methodList.substr(0,methodList.length-2));
function dateFormat(d) {
 var dateString = d.getFullYear()+"-";
 dateString += pad(d.getMonth()+1)+"-";
 dateString += pad(d.getDate())+"T";
 dateString += pad(d.getHours())+":";
 dateString += pad(d.getMinutes())+":";
 dateString += pad(d.getSeconds());
 return dateString;
}
function pad(x) {
 return (x<10) ? “0"+x : ""+x;
}
Innovator inn = this.getInnovator();
// Get yesterday’s date
DateTime myDate = DateTime.Today.AddDays(-1);
// Find all methods edited in the past 24 hours
Item myItem = inn.newItem(“Method”,"get”);
myItem.setAttribute(“select”,"name”);
myItem.setProperty(“modified_on”,myDate.ToString(“yyyy-MM-ddThh:mm:ss”));
myItem.setPropertyAttribute(“modified_on”,"condition”,"gt”);
myItem = myItem.apply();
// Loop through the returned methods and return the list
string methodList = "";
int myItemCount = myItem.getItemCount();
for(int i = 0; i < myItemCount; i++){
 methodList += myItem.getItemByIndex(i).getProperty(“name”,"") + ", ";
}
return inn.newError(“The following methods were modified In the past 24 hours: " + methodList);

You want to pass some value from an onBefore (i.e., onBeforeUpdate) event to an onAfter (i.e., onAfterUpdate) event for data process handling. Use the built-in function to add a variable, read a variable and remove a variable from the RequestState. This sample determines if there was a change made to the “Name” property of Part. OnBeforeUpdate event

//Get Part Name before change
Innovator inn = this.getInnovator();
Item myPart = inn.newItem(“Part”,"get”);
myPart.setID(this.getID());
myPart.setAttribute(“select”,"name”);
myPart=myPart.apply();
string prevName = myPart.getProperty(“name”);
RequestState.Add(“prevName”, prevName); //Add value to SessionState
return this;
string prevName = (string)RequestState[“prevName”]; 
string curName = this.getProperty(“name”);
if (prevName != curName)
{
 //Do some logic here
}
//Perform cleanup of RequestState
RequestState.Remove(“prevName”); //removes single key
// to remove all keys use RequestState.Clear();
return this;

You want to reference a custom library from inside an Aras Innovator Server Method. Create a custom DLL that references the IOM. This DLL is then added to the BIN folder and referenced in the method-config.xml, allowing it to be called from a server-side method. To create the custom DLL, it is necessary to create a Visual Studio C# class library project using .NET 6.0. The project needs to reference the IOM of the version of Aras Innovator you are connecting to. Class Code

namespace CookBookCustomDLL
{
 public class CustomDLLFunct
 {
 public static string returnUser(Innovator inn)
 {
 string userID = inn.getUserID();
 return userID;
 }
 }
}

Once you have created the DLL and built the assembly, you need to use the following steps to add the DLL to your Aras Innovator code tree

  1. Copy the CookBookCustomDLL.dll and CookBookCustomDLL.pdb into the \Innovator\Server\bin folder.
  2. Open the \Innovator\Server\Method-Config.xml for editing and add the highlighted line....
  3. <ReferencedAssemblies><name>$(binpath)/Aras.Server.Core.dll</name><name>$(binpath)/Aras.TDF.Base.dll</name><name>$(binpath)/Aras.TDF.Base.Extensions.dll</name><name>$(binpath)/Conversion.Base.dll</name><name>$(binpath)/ConversionManager.dll</name><name>$(binpath)/FileExchangeService.dll</name><name>$(binpath)/IOM.dll</name><name>$(binpath)/Newtonsoft.Json.dll</name><name>$(binpath)/SixLabors.ImageSharp.dll</name><name>$(binpath)/Microsoft.Data.SqlClient.dll</name><name>Microsoft.Extensions.Logging.Abstractions.dll</name><name>$(binpath)/CookBookCustomDLL.dll</name>...

  4. Search for the <Template> tag for the language you are using and include the additional namespace you need by adding additional “using” lines.
  5. When adding lines, make sure you update the line_number_offset for accurate debugging messages.

     <Template name="CSharp” line_number_offset="37">
     <![CDATA[
    using System;
    using CookBookCustomDLL;
  6. Save the Method-Config.xml

Code snippet that can be used as a reference for how to call your assembly and function. Code Snippet

Innovator inn = this.getInnovator();
string userID = CustomDLLFunct.returnUser(inn);

There is the possibility of adding sub menus to the context menu in the search grid of Aras Innovator menu items. Aras.Client.Controls.ContextMenu control supports submenus. It provides two methods for adding menu items:

  1. add(id, label, parentMenuId, args);
  2. addRange(items, parentMenuId);

The following are the examples given for itemsGrid.html:

var menu = grid.getMenu();
menu.add(“item_0", “Item 0");
menu.add(“item_0.0", “Item 0.0", “item_0", { onClick: function () { alert(0); } });
menu.add(“item_0.1", “Item 0.1", “item_0", { onClick: function () { alert(1); } });
menu.add(“item_0.2", “Item 0.2", “item_0", { onClick: function () { alert(2); } });
var menu = grid.getMenu(),
 subMenus = [
 {
 id: “item_0.0",
 name: “Item 0.0",
 onClick: function () { alert(0); } },
 {
 id: “item_0.1",
 name: “Item 0.1",
 onClick: function () { alert(1); }
 },
 {
 id: “item_0.2",
 name: “Item 0.2",
 onClick: function () { alert(2); }
 }
 ];
menu.add(“item_0", “Item 0");
menu.addRange(subMenus, “item_0");

Use the following code to create and populate a dynamic list:

var listDropdown = getFieldComponentById("{ID}");
//var listDropdown = getFieldComponentByName("{NAME}");
//you can use either getFieldComponentByName or getFieldComponentById to find the list
function addOption(selectbox, txt, val){
 var options = selectbox.component.state.list
 options.push({label: txt, value: val})
 selectbox.component.setState({list: options});
}
function removeOption(selectbox, val) {
 var options = selectbox.component.state.list
 for (var i = 0; i < options.length; i++) {
 if (options[i].value == val) {
 options.splice(i, 1)
 break;
 }
 }
 selectbox.component.setState({
 list: options
 });
}

You can add and remove options using the following:

addOption(listDropdown, “option 1”, “opt-1”);removeOption(listDropdown, “opt-1”);

The purpose of the aras.downloadFile is to download a file from Aras Innovator to a client machine. This function is a Javascript function and can only be called from a client-side Method. The function takes in up to two properties:

  • fileNd – an XML Node of the File to be downloaded. It contains the filename, ID, and all pertinent properties.
  • preferredName – (Optional) a string to replace the file’s name during download.
let item = aras.newIOMItem(‘File’, ‘get’);
 ...
 item = item.apply();
 aras.downloadFile(item.node, ‘sample.txt’);

The ArasModules.xml.parseString function takes a string and converts it into an XmlDOMDocument. The function takes the following argument:

  • itemND – a string that conforms to a valid XML structure.
...
 let document = ArasModules.xml.parseString('<Item action="get” type="File"><filename>sample.txt</filename></Item>');
 ...

The ArasModules.xml.parseFile function loads the URL of an XML file into a local XmlDOMDocument object. The function takes the following argument:

  • url – a valid URL for an XML file.
...
let document = ArasModules.xml.parseFile(‘http://sampleserver/samplexml.xml’);
...

ArasModules.xml.selectNodes selects a list of nodes that match the query. It functions identically to the standard XmlDOMDocument.selectNodes(xPathString). The function takes the following arguments:

  • xmlDocument – a valid XML document.
  • nodeName – the name of a node in the xmlDocument.
...
let xmlNodes = ArasModules.xml.selectNodes(xmlDoc, ‘sample’);
...

ArasModules.xml.selectSingleNode selects the first XML node that matches the XPath expression. It functions identically to the standard XmlDOMDocument.selectSingleNode(xPathString). The function takes the following arguments:

  • xmlDocument – a valid xmlDocument.
  • nodeName – The name of a node in the xmlDocument.
...
let xmlSingleNode = ArasModules.xml.selectSingleNode(xmlDoc, “created_on”);
...

ArasModules.xml.transform processes the document node using the specified XSL stylesheet. It functions identically to the standard XmlDOMDocument.transformNode(objXSLTStylesheet). The function takes the following arguments:

  • xmlDocument – a valid XmlDOMDocument.
  • xsltStylesheet – a valid XSLT stylesheet that is applied to the xmlDocument.
...
let transformedXmlDocument = ArasModules.xml.transform(xmlDoc, xsltStylesheet);
...

ArasModules.xml.createNode creates a NodeType. It functions identically to the standard XmlDOMDocument.createNode(Type, name, namespaceURL). The function takes the following arguments:

  • xmlDocument – a valid XmlDOMDocument.
  • Type – The IXMLDOMNodeType that is being applied. For example, 1 is a NODE_ELEMENT.
  • Name – The name of the NodeType being created.
  • url – A string defining the namespace URL.
...
let newNode = ArasModules.xml.createNode(xmlDoc, 1, ‘sample’, '');
...

ArasModules.xml.getText retrieves the text from an XmlDOMDocument object node. The function takes the following argument:

  • xmlNode – A valid XmlDocument object’s node.
...
let nodeValue = ArasModules.xml.getText(xmlNode);
...

ArasModules.xml.setText sets the text for an XmlDOMDocument object node. The function takes the following arguments:

  • xmlNode – a validXmlDOMDocument object’s node.
  • nodeValue – The value to be inserted into the node.
...
ArasModules.xml.setText(xmlNode, ‘sample’);
...

ArasModules.xml.getXml converts an XmlDOMDocument object into a string. The function takes the following argument:

  • xmlDocument – a valid XmlDocument.
...
let xmlString = ArasModules.xml.getXml(xmlDoc);
...

ArasModules.xml.getError returns an error when given an XMLDOMDocument object. The function takes the following argument:

  • xmlDocument – A valid XmlDocument.
...
 var error = ArasModules.xml.getError(xmlDoc);
 if (error.errorCode !== 0) {
 return aras.getResource('', ‘tz.parse_update_fail_details’, error.reason);
 }
...

The following sample code demonstrates how to prevent a user from updating a property in the data store. Validate event methods have the following parameters available:

  • itemId – the GUID of the current context item
  • name – the name of the property being updated
  • value – the property value being validated. For item properties, this is an object with the selected item’s id, type, and keyedName.

Example 1

Use Case: We don’t want users to update prop_a anywhere in the client – on forms, relationship grids, or search grids. Solution: Create the following method and configure it as a Validate event on prop_a of an ItemType. ... if (name === ‘prop_a’) { return { valid: false, validationMessage: `${name} property can’t be changed` }; } ...

Example 2

Use Case: The owner and manager of an item cannot be the same identity. Solution: Create the following method and configure it as a Validate event on both the owned_by_id and managed_by_id properties of an ItemType. ... const currentItem = aras.itemsCache.getItem(itemId) || aras.itemsCache.getItemByXPath(`//Item[@id="${itemId}"]`); const owner = aras.getItemProperty(currentItem, ‘owned_by_id’, ''); const manager = aras.getItemProperty(currentItem, ‘managed_by_id’, ''); // one or both of the fields may be null if (value === null) { return { valid: true }; } if (owner === value.id || manager === value.id) { return { valid: false, validationMessage: `Cannot update ${name}. Owner and manager cannot be the same.` }; } return { valid: true }; ...

The following sample code demonstrates how to update a property’s value in the data store. Change event methods have the following parameters available:

  • itemId – the GUID of the current context item
  • name – the name of the property being updated
  • value – the property value being validated. For item properties, this is an object with the selected item’s id, type, and keyedName.

Use Case: All Parts with the “Software” classification should have a name prefixed with “SW – “. Solution: Create the following method and configure it as a Change event on the name property of the Part ItemType. ... const itemNode = aras.itemsCache.getItem(itemId) || aras.itemsCache.getItemByXPath(`//Item[@id="${itemId}"]`); if (aras.getItemProperty(itemNode, ‘classification’, '') !== ‘Software’) { return; } const prefix = ‘SW - '; if (value.substring(0,5) === prefix) { return; } aras.setItemProperty(itemNode, name, prefix + value); ...

Federated property is used for Item types to access data from an external system instead of the Aras Innovator database. It can be configured via turning the Federated flag on, but not all data types are supported. Currenty the following data types can be used for Federated properties: Integer. Text. Date. It is expected that the date value is set in Item according to the neutral format of Innovator yyyy-MM-ddTHH:mm:ss (or yyyy-MM-dd) in the current TZ in local or Corporate TimeZone (if Corporate TimeZone is set). Otherwise, the correct work of federated dates with other logic of Innovator (e.g., presentation in the Client) cannot be guaranteed. The customization developers should consider the potential differences in the date format and TimeZones of Innovator Server and a Federated System and implement corresponding adjustments before setting the value to Innovator Items (especially in cases when time part is important). As recommendation: custom approach to convert dates from the TZ of Federated System to UTC can be implemented and then call IOM method ConvertUtcDateTimeToNeutral which will produce the result in a proper format and TZ for Innovator.

And in the opposite direction when data should be sent from Innovator to Federated System, IOM method ConvertNeutralToUtcDateTime will provide the datetime in UTC and then the customization logic (if needed) will adjust it to the TZ of Federated System (see page “Data Flow-2a-FS-NoCorpTZ” in the attached excel file “Date - Pattern and TZ” for illustrations of data flows)

Propery setting Pattern (pattern) for date properties is used only in Innovator Client to present the date value for the end user with the order of display for month, day and year following internationalization and localization rules dependent on client settings. Float. IOM means i18NSessionContext.ConvertToNeutral can be used to convert strings with float values to neutral format to prepare float string in proper format if needed. Please do not specify Precision and Scale for federated Float properties to avoid inconsistent restrictions on the client side. String. For regular String properties there are two special settings in Innovator:

  • Length (stored_length) – required, the maximum length of the string property
  • Pattern (pattern) optional, is used to prepare a Regex (ignore-case, single line) which the property value should match

It’s up to the Administrator/Developer creating the federated String property to set it up in Innovator according to the specification and requirements of the Federated System. Innovator’s Client will provide built-in validation based on these settings for bi-directional data exchange. As for additional validation in Innovator Server, the Developers can implement it themselves in OnBeforeAdd/Update or OnAdd/Update event handlers before sending the data with the string property value to the Federated System.If a federated property is supposed just to show information from the Federated System and no input is expected on Innovator side, then set Readonly setting for the federated property to true.