Object Oriented Programming in ARIS Script (Part 3)
The previous two installments of this series demonstrated how you can de-couple data from presentation by using Javascript Classes. By using a simple design pattern that contains three elements: Data, Presentation, and Control, you can quickly create rich reporting solutions using ARIS Script.
We covered creation of Presentation elements Object Oriented Programming in ARIS and creation of Data elements in Object Oriented Programming in ARIS Script (Part 2). In Part 3 we will bring it all together by creating the Controller element.
For the Controller, we could create a Javascript Class and initialize that in a Report. I have considered doing this, but the Controller is usually very specific and re-use is unlikely. However, if you do find that you begin using the same functions over and over in your reports, you can easily move those particular functions into one of your Javascript classes.
Setting up the ARIS Report
This Report will be used to generate a spreadsheet showing the hierarchy of our objects based on the business rules for our particular structure. This will provide at-a-glance view of relationships between processes at all levels.
I have created a blank ARIS Template that I use whenever creating a new report. This template includes some header information, but most importantly it is pre-loaded with my Javascript imports. If I will not be using a particular import, I will remove it from the imports. <pic of imports>
It's good practice to include a header in our Report code so that future developers will at least have some idea what thinking was behind the development of this script.
Initializing Javascript Classes
While you can initialize classes at any point in your code, it's good to initialze any of the classes that are used throughout at the top of your code:
var oLocale = Context.getSelectedLanguage(); var oDatabase = ArisData.getActiveDatabase(); var oMyData = new MY_DATA(); var oMyArisData = new MY_ARIS_DATA(oDatabase, oLocale); var oMyExcel = new MY_EXCEL("W3 Hierarchy Report"); var oMyAttributes = new MY_ARIS_ATTRIBUTES(); var oFormat = Context.getSelectedFormat(); var oSelectedGroup = ArisData.getSelectedGroups()[0];
Body
For our purposes, we wanted the ability to manage output based on which kind of output is selected when the user runs the report.
A simple 'switch' statement provides direction for the type of report selected. Since we are only talkinag about two different types, we could have easily used an if statment, but I prefer to not nest 'if' statements whenever possible.
var oFormat = Context.getSelectedFormat(); switch(oFormat) { case Constants.OUTEXCEL: getHierarchy(oSelectedGroup); oMyExcel.WorkBook.write(); delete oMyExcel; break; case Constants.OUTTEXT: oLog.Start(oSelectedGroup); getHierarchy(); oLog.Log.WriteReport(); delete oLog; break; }
There are two functions used in the report to traverse and capture the hierarchy. The data it extracts along the way is stored in the MY_DATA object we defined earlier. Note that this data capture is completely independent of the output (whether excel, txt or other). This will make it easy in the future if we want to add another output type such as XML or HTML.
The 'getHierarchy' function
The getHierarchy function initializes two tables. One for L3 EPC and the other for L4 EPC. The columns are defined using the 'Columns' property of the table object.
var oReportDataset = new oMyData.DataSet(); var oL3Table = new oMyData.DataTable("L3 Hierarchy"); var oL4Table = new oMyData.DataTable("L4 Hierarchy"); oL3Table.Columns = [ {key:"majorProcess", value:"Major Process",index:1}, {key:"process", value:"Process",index:2}, {key:"subProcessL3", value:"Sub-Process", index:3}, {key:"description", value:"Description", index:4}, {key:"group", value:"Group", index:5} ]; oL4Table.Columns = [ {key:"majorProcess", value:"Major Process",index:1}, {key:"process", value:"Process",index:2}, {key:"subProcessL3", value:"L3 Sub-Process", index:3}, {key:"subProcessL4", value:"L4 Sub-Process", index:4}, {key:"description", value:"Description", index:5}, {key:"keyword", value:"Owning Team", index:6}, {key:"group", value:"Group", index:7} ]; var oL3Models = oSelectedGroup.ModelList(true,196621); var oL4Models = oSelectedGroup.ModelList(true,Constants.MT_EEPC);
JSLINQ
Next you will see a nifty function called JSLINQ. This is a basic 'LinQ' like library that allows you to create queries on objects to extract information. Without JSLINQ, you have to create elaborate 'for' loops with extensive conditional logic to get what you need. If you're curious about JSLINQ, send me a note and I will fill you in.
var oL4TypeModels = JSLINQ(oL4Models).Where(function(eepc){return eepc.TypeNum() == Constants.MT_EEPC;}).ToArray();
Populating the tables
next, the 'parsePath' function is called. The models collected above are passed to the function as well as the appropriate table and the list type (L3 EPC or L4 EPC). The 'parsePath' function splits up the group path. Because we use a standardized path structure, we can traverse the path and use each index for a particular piece of data.
parsePath(oL3Models, oL3Table, 196621); parsePath(oL4TypeModels, oL4Table, Constants.MT_EEPC);
Notice that no value is returned from this function. In Javascript, all values are passed by reference so the actual table population takes place in the 'parsePath' function.
The 'parsePath' function
The parsePath function receives a collection of models, loops through the collection and populates the table based on the structure that was created in the getHierarchy function. Notice that the 'key' value here matches the key values defined in the columns for oL4Table.
var oData = [ {key:"majorProcess", value:oGroups[3]}, {key:"process", value:oGroups[4]}, {key:"subProcessL3", value:oGroups[5]}, {key:"description", value:oDescription}, {key:"group", value:oGroups[2]}, {key:"subProcessL4", value:oModel.Name(oLocale)}, {key:"keyword", value:oKeyword} ];
Also note that the sort of the keys is not the same. That's not a problem as declaring key/value pairs in this method does not rely on order like, say, indexed conllections would. The actual order of the columns as they are laid out across the top is handled by the 'index' property defined in the columns initially.
In the case of the parsePath function, I used an 'if' conditional. A better approach would be to use Switch as I did above. For now, we are only creating Hierarchy for 2 sepearte types of EPC.
Adding rows to the table
As we loop through the models and capture data, the rows for the table are created and added to the table on-the-fly.
oMyData.AddRow(oData,oTable);
The 'AddRow' function, as you can see, is a part of oMyData which is the initialized MY_DATA_CLASS.
function parsePath(oModels, oTable, oType){ for (oMod in oModels){ var oModel = oModels[oMod]; var oPath = oModel.Group().Path(oLocale); //oPath = oPath.replace("\\", "!"); var oGroups = oPath.split("\\\\"); var oDescription = oModel.Attribute(Constants.AT_DESC, oLocale).getValue(); if (oDescription == "" || oDescription == null){ oDescription = ""; } //0 = Architecture //1 = Business //2 = Deployment //3 = Major Process //4 = Process //5 = L3 //6 = L4 var oData = []; if (oType == Constants.MT_EEPC){ var oObj = oModel.getSuperiorObjDefs()[0]; var oKeyword = oMyAttributes.GetKeyword(oObj); var oData = [ {key:"majorProcess", value:oGroups[3]}, {key:"process", value:oGroups[4]}, {key:"subProcessL3", value:oGroups[5]}, {key:"description", value:oDescription}, {key:"group", value:oGroups[2]}, {key:"subProcessL4", value:oModel.Name(oLocale)}, {key:"keyword", value:oKeyword} ]; } else { var oData = [ {key:"majorProcess", value:oGroups[3]}, {key:"process", value:oGroups[4]}, {key:"subProcessL3", value:oModel.Name(oLocale)}, {key:"description", value:oDescription}, {key:"group", value:oGroups[2]} ]; } oMyData.AddRow(oData,oTable); } }
Creating the output
Once we return from the parsePath function, the getHierarchy code resumes. The two tables that are created are pushed into our DataSet property. By having these in a DataSet, we have greater control over our output, particularly as it relates to Excel. We loop through the Dataset and create a new Excel Report on each loop. We also have control over which columns to sort on (at the moment sorting is all ASCending on each column). Inside the loop, we initialize a new Excel Report (Workbook)
for(oReportTable in oReportDataset.DataTables.sort(oCargillData.DataSortASC)) { var oDataReport = oReportDataset.DataTables[oReportTable]; var oReport = new oMyExcel.Report(oDataReport.TableName); oReport.Table = oDataReport; oReport.Sort = ["majorProcess", "process", "subProcessL3"]; oReport.Write(oReport.Sort) }
Once that looping is finished, our Excel document is created in memory. Notice in our switch statement above, we call the oMy.Workbook.write(); This handles the output of the Excel document created in memory and results in a completed excel doc with two tabs.
Below is the entire Report. Notice how simplified it is when all the data and presentation logic is moved out into respective classes.
If you have any comments or questions or if something just doesn't look right, let me know. Enjoy!
Rick Beddoe
Cargill Aris Technical Analyst
Minneapolis, MN, USA
Hi Ciska,
Here's the link to JSLINQ - http://jslinq.codeplex.com/. Be sure to use this an NOT LINQJS. LINQJS is more robust, but I think it's overly complex for what we're doing in ARIS.
JSLINQ is compatible with ARIS and will work in Architect and Publisher.
You need to put JSLINQ in your 'Common files' folder (you don't need JSLINQ-vsdoc.js) then make sure you include it in your Imported files list
Rick Beddoe
Cargill Aris Technical Analyst
Minneapolis, MN, USA
Yes, I think there was some modifications that needed to be done. You can use the JSLINQ I included with the script or modify it yourself. Out of the box, it's designed to work within a webpage. Hence the 'window' reference.
The concepts in JSLINQ are the same as they are in LINQ, so if you read up about the basics of that in VB.NET or C#.NET, it will give you a pretty good idea of its intent.
Rick Beddoe
Cargill Aris Technical Analyst
Minneapolis, MN, USA