Welcome to the OPA Hub!


Category Archives: JavaScript Extensions

Most Popular Downloads on this Site

Most Popular Downloads on this Site

We used to say that anything with “Google Maps” in the title was a sure-fire winner when it came to people downloading examples – after all Google Maps used to be so easy to get into, and so many people just “get” why a map is useful, that it was absolutely the go-to example. But is it still the case? Google APIs now need a Billing Account, and an API key, which in turn needs to have domain restrictions, and other administrative elements. It’s not quite as easy as it used to be.

So we set out to look at the different examples that have been downloaded on this site. And here are the results. Interesting. Under the graph, some comments and remarks which might explain some of the data.

Most Popular Downloads

Custom Google Maps JavaScript Label Extension Example 21

Custom Dynamic Options JavaScript Extension Example 21

Didn’t expect that one to be in there at all!

Custom JSON Search JavaScript Extension Example 19

Same for this one. Maybe Search is the new Map?

Custom Entity Container Extension Example  17

Google Maps as Custom Input Control Extension 15

Ah, see, the combined score is going to be huge.

Custom JavaScript Extensions Map with Google Places and Custom Options List  12

Told you. Google Maps reigns supreme!

Custom Header as a Timer JavaScript Extension 12

Custom Year Picker JavaScript Input Extension Example 9

JavaScript Extension Entity Collect Control 7

JavaScript Entity Collect Extension Excel Data Load 6

Custom Signature Extension Example 6

Wow. This one isn’t even really an extension!

JavaScript Extension Search Examples – Airlines, Great Circle Mapping, Airports 6

Everyone loves planes. I get that.

Embed Website in Interview Label Extension 5

JavaScript Extensions with a Live Entity-based Chart with D3 5

Everyone hates D3. I get that.

JavaScript Extension Search Example – Railways 5

Not as much fun as airplanes.

Loader Image in Entity Instances Search Extension 4

JavaScript Extension fullCustomInput Example 4

JavaScript Extension Custom Label Detail Pop-up 4

Calendar Input Extension Blackout Dates 3

JavaScript Extension Search Example – Siebel REST API 3

JavaScript Extension Relationship Control 2

JavaScript Label Extension Leaflet JS Map 2

Wow. This is the free alternative to Google Maps. I would have imagined this to be more popular.

Force PDF Click Button Extension and Label  1

As you can see from the Chart, Google Maps is hanging on – but only just! It’s ex aequo with the Search Extension. However if you pool together all the Google Maps stuff, it completely wipes the floor with the competition. Definitely the most popular downloads.

Want even more downloads?

If you are interested in even more examples (including a complete Entity Collect, a Carousel, Breadcrumbs, lots of Inputs and more, then you will be interested in our new book, JavaScript Extensions for Oracle Policy Modeling.

JavaScript Extensions for Oracle Policy Modeling

JavaScript Extensions for Oracle Policy Modeling

It’s almost ready. The perfect Christmas gift (yeah, right, in bizarro-world) or New Year workout kit. The new book is due out in a few weeks, and you have the chance to win a free copy ahead of everyone else by entering our prize Survey (30 seconds) right now.

The book comes with 50 examples. You get them as Zip Files as well when you buy the book. The book covers all the different types of JavaScript Extensions that are currently available, with examples of each of them (sometimes many different examples) using easily-understandable business scenarios as the starting point, and using example projects from Oracle Policy Modeling – so you don’t need to install a Hub, or do anything special – which let’s you get started straight away.

  • Label Extensions
  • Input Extensions
  • Search Extensions
  • Options Extensions
  • Entity Collect Extensions
  • Entity Container Extensions
  • Button Extensions
  • Event Extensions
  • Navigation, Header and Footer Extensions

The OPA Hub Website Community has also stepped up and I am delighted to say that 8 examples (with credits) came from readers of this website. I am very grateful to them and encourage all of you to submit your ideas and code snippets for the next version.

Hopefully this book will appeal to non-technical people too – I wrote it in the same style as the others (Getting Started with OPA, Getting Started with OSvC) and tried to make it as accessible as possible to everyone.

Here, in avant-premiere, is the cover. And no, I’m not sure what those things are either. But they sure look exotic and tasty. I wanted a parsnip on the cover but these were available and they look good. These are important questions that you worry about late at night. The publisher’s website has been updated, watch this space for the launch date!

 

JavaScript Extensions

JavaScript Extension : Geomapping with Leaflet JS

JavaScript Extension : Geomapping with Leaflet JS

Ever since the dawn of time (or at least since the arrival of JavaScript Extensions in Oracle Policy Automation), for whatever reason, the subject of Google Maps (geocoding, reverse geocoding, etc) has been one of the most read and downloaded on this site. But now, given that Google Maps (like Google APIs in general) requires registrations, license keys, and is subject to all sorts of restrictions  and throttling, developers have needed something else to impress their customers and colleagues with eye candy.

Geomapping with Leaflet JS and MapBox

So it’s appropriate therefore that this little post is all about Leaflet JS, “an open-source JavaScript library
for mobile-friendly interactive maps”. The Leaflet JS website is stuffed full of interesting examples and tutorials, so here goes for a short example of using this library in Oracle Policy Automation.

The setup is slightly different than working with Google Maps, but everything starts with the obvious step : download a library! In my case, I downloaded the library and CSS styles and dropped them into the resources folder. The files are tiny.

For the demonstration, my Oracle Policy Automation project contains only a handful of attributes, specifically enough to be able to create an address (street, town, zipcode, country) and a customer name (for a marker on the map).

The second element to handle in your code, is the image tile provider for your map. Working with Leaflet requires two things : Leaflet and a tile provider – in my case mapbox proved to be a good choice. It looks lovely. It requires a registration and access token, but it is very easy to obtain and you can try for free. Leaflet is entirely agnostic however, so if you want to use another tiling source, you can.

Apart from these steps, most of the code looks familiar : get the attribute values, pass them to the geocoding engine and then extract the latitude and longitude from the returned JSON object. Once you have that information,we use onreadystatechange to check that the page is loaded, and then draw the map, using latitude and longitude, and drop a custom marker at the location with a shadow and a popup window associated with the marker.

Because this is based on OpenStreetMap, we need to be good citizens and ensure we give credit where credit is due. Let’s look at a simple example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/**
 * Richard Napier The OPA Hub Website June 2019
 * Educational Example of Custom Label with Leaflet JS and MapBox
 * I will remember this is for demonstration purposes only
 */
OraclePolicyAutomation.AddExtension({
	customLabel: function (control, interview) {
 
		if (control.getProperty("name") == "xMap") {
			return {
				mount: function (el) {
 
					// Add CSS First
					var head = document.getElementsByTagName('head')[0];
					var linkcss = document.createElement('link');
					linkcss.id = "leafletcss";
					linkcss.rel = 'stylesheet';
					linkcss.type = 'text/css';
					linkcss.href = '${resources-root}/leaflet.css';
					linkcss.setAttribute('crossorigin', "anonymous");
					head.appendChild(linkcss);
					var div = document.createElement("div");
					div.id = "lblmap";
					div.style.visibility = 'visible';
					div.style.height = '370px';
					div.style.width = '400px';
					el.appendChild(div);
 
					var street = interview.getValue("street");
					var city = interview.getValue("city");
					var zip = interview.getValue("zip");
					var country = interview.getValue("country");
					var customer = interview.getValue("customer");
					var latitude;
					var longitude;
					var uri = street + "," + city + "," + zip + "," + country;
					var res = encodeURI(uri);
					var root = "https://api.mapbox.com/geocoding/v5/mapbox.places/" + res + ".json?access_token=pXXX";
 
					$.ajax({
						url: root,
						method: "GET",
						async: false,
						dataType: 'json'
					}).then(function (data) {
						//console.log("Cleaning Up Response");
						//for (var key in data.features) {
						if (data.features[0]) {
							var val = data.features[0];
							longitude = val.center[0];
							latitude = val.center[1];
							//console.log(" Returned " + latitude + " " + longitude);
							$.getScript("${resources-root}/leaflet.js", function () {
 
								//console.log("Script loaded");
								window.onerror = function (errorMsg, url, lineNumber, column, errorObj) {
									console.log('Error: ' + errorMsg + ' Script: ' + url + ' Line: ' + lineNumber
										 + ' Column: ' + column + ' StackTrace: ' + errorObj);
								}
 
								//console.log(document.readyState);
								//console.log(latitude + " " + longitude);
								//	document.onreadystatechange  = function () {
								//console.log("ReadyState Change Event");
								if (document.readyState === "complete") {
									//console.log("ReadyState is Complete");
									var siteIcon = L.icon({
											iconUrl: '${resources-root}/images/Logo_Marker.png',
											shadowUrl: '${resources-root}/images/Logo_Marker_Shadow.png',
											iconSize: [30, 40],
											shadowSize: [30, 40],
											iconAnchor: [0, 40],
											shadowAnchor: [0, 38],
											popupAnchor: [15, -20]
										});
									var myLatLng = new L.LatLng(latitude, longitude);
									var mymap = L.map('lblmap').setView(myLatLng, 13);
									L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}', {
										attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
										maxZoom: 18,
										id: 'mapbox.streets',
										accessToken: 'pk.eyJ1IjoidGhlb3BhaHViIiwiYSI6ImNqdDJ3Y2liNDFvMjE0M3BpampzOG16cDYifQ.0ppcKabM2WhuaPT-kCGwYw'
									}).addTo(mymap);
 
									var marker = L.marker(new L.LatLng(latitude, longitude), {
											icon: siteIcon
										}).addTo(mymap);
									marker.bindPopup(customer + " is based here.");
								};
 
 
 
							});
						}
 
 
					});
 
					// Assumes JS is loaded
 
				},
				unmount: function (el) {
					if (control.getProperty("name") == "xMap") {
 
						 var myMap = document.getElementById("lblmap");
                        myMap.parentNode.removeChild(myMap);
 
 
					}
 
				}
 
			}
 
		}
 
	}
})

Lines 0-40 : Pretty standard stuff, setting up the CSS file and the encoded URL for the REST call which will get us the latitude and longitude.
Lines 41- 52 : Used to call the service and get / parse the response, giving us the latitude and longitude
Lines 53 – 64 : We need to be sure that we wait until the leaflet JavaScript file is loaded before doing anything else
Lines 67 – 75 : Set up a Custom Marker
Lines 77 – 83 : Add a Map Layer and Credits
Lines 84 – 87 : Add a Marker using a custom image and shadow
Lines 85 – 88 : Add a Popup to the Marker click, and use some Oracle Policy Automation data in the popup.

The rest is just clean-up and go home (the unmount).

Geomapping with Leaflet JS : The Result

The end result is a nicely presented map, with a marker at the correct address, and a cute popup window when the marker is clicked.

Geomapping with Leaflet JS MapBox Example

We’ll see what else we can do with this nice alternative library in the coming weeks. In the meantime, you will find the sample Project in the OPA Hub Shop.

Importing Data into an Interview : Excel Example

Importing Data into an Interview : Excel Example

Readers will remember a while ago I explained briefly how to use Microsoft Excel to act as a Connection Datasource – in this overview article, followed by this one in a little more detail. Now we look at another challenge : Importing Data into an Interview.

Well, here comes another example of the ubiquitous nature of Microsoft Excel. The customer requirement was as follows:

Using a simple mechanism, let the user upload an existing Excel spreadsheet into the Interview. Parse the spreadsheet, read the data in it, create corresponding rows in an Entity. Let the user review the data but do not require any new data entry. There may be up to 250 rows of data to import. So how do you go about about Importing Data into an Interview?

So how can we face up to a challenge like that? We need:

  • An upload that isn’t a standard File Upload Group
  • A parsing mechanism to read Excel and extract the data in a given tab, or wherever
  • A custom Entity Collect to handle the data import / create the rows in a Screen

The shopping list above isn’t that long.

The File Upload is essentially an HTML 5 component to let the user select a file on their computer. We cannot access an arbitrary local path from JavaScript, so we need the user to point to the file they want to upload.

There are a number of JavaScript-based Excel parsers, including the excellent SheetJS js-xlsx which we used. It is capable of converting to and from Excel, which is no easy task when you consider that an Excel file is basically a Zip file with a bunch of complicated stuff inside it. The library can convert to HTML, CSV and magically (for our requirement) JSON. Awesome!

Plus, in a previous post we’ve also looked at the (large amount of) work required to build a Custom Entity Collect Extension. In fact when I was writing that article I was thinking, for goodness sake Richard, when do you think you will actually need to go to the trouble of building that Entity Collect Extension? Well, I’ve finally found a use for it – Importing Data into an Interview!

We need an Entity Collect Extension since we need some way of getting the Excel data into the Entity Collect, which ultimately means we need to do some work behind the scenes between the import of the data and the display of the Entity Collect. We need to rewire the Entity Collect temporarily so that it sucks our Excel data up, before we show it to the user so they can examine the results.

For the purposes of a raw demo, I unplugged all the other functionality (delete buttons, add buttons, etc.) and just concentrated on getting the data into the Entity Collect. There are how ever a few caveats. Once you get into the larger imports, at least in the Debugger, you can expect to see “concurrent record editing” errors. I’m trying to find out what the limit is exactly. But up to a few hundred I think it’s OK.

So let’s look at the items in turn.

File Upload and Data Load

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{
					//console.log("Starting customInput Mount");
					var div = document.createElement("input");
					div.id = "myFile";
					div.type = "File";
					//div.value = control.getValue();
					el.appendChild(div);
 
					function handleFile(e) {
						var files = e.target.files,
						f = files[0];
						var reader = new FileReader();
						reader.onload = function (e) {
							var data = new Uint8Array(e.target.result);
							//console.log("In Change");
							var workbook = XLSX.read(data, {
									type: 'array'
								});
 
							worksheet = workbook.Sheets["YOURWORKSHEET"];
							jsonoutput = XLSX.utils.sheet_to_json(worksheet, {
						raw: true, header : 1
							});
						//console.log("Read " + jsonoutput );
						};
 
						reader.readAsArrayBuffer(f);	
						var completepath = $(':file').val();
							//console.log(completepath);
						interview.setInputValue("rest_filenameandpath", completepath);
					}
 
 
				var filedialog = document.getElementById("myFile");
					filedialog.addEventListener('change', handleFile, false);
 
					var completepath = $(':file').val();
						control.setValue(completepath);
						//console.log("Hello " + completepath);
						//console.log("Ending customInput Mount");
				}

Assuming you have a custom Input framework as your starting point, the above code will be in the mount. This will build an HTML5 file upload control, and attach an event handler. The code regarding Excel depends upon xlsx.full.min.js being in the resources directory. But that’s it. You’ve loaded the Excel file into a JSON object.

Entity Collect

The next step is to include a Custom Entity Collect in your project, and use the jsonoutput object (which you just created from the imported file above) in the mount of the Entity Collect to load the JSON into the Entity Collect. The following is an extract from the mount code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var numEntities = Object.size(jsonoutput);
					//console.log(numEntities);
					// Remove header row if the file has one
					jsonoutput.shift();
					// load records into the Entity Collect
 
					if (control.getRows() == 0) {
						for (j = 0; j &lt; numEntities - 1; j++) {
							control.addNewRow();
							var mycurrentrecords = control.getRows();
							mycurrentrecords[j][0].setValue(jsonoutput[j][0]);
							mycurrentrecords[j][1].setValue(jsonoutput[j][1]);
							mycurrentrecords[j][2].setValue(jsonoutput[j][2]);
							mycurrentrecords[j][3].setValue(jsonoutput[j][3])
drawrows();

The end result is something like this:

Importing Data into an Interview

The File is loaded into the Entity Collect, and the contents displayed to the user. In my case I unhooked all the code related to modification (the onchange stuff from the original idea) and removed the add / delete buttons, since it was designed to just allow the user to see the loaded result, not modify it.

Importing Data into an Interview

If you want to have a look at the project, just download the very basic example here.

Calendar Black Out Dates with Control Extensions

Calendar Black Out Dates with Control Extensions

The OPA Hub Website is always happy to hear from readers and learn about the things they are doing and trying to do. In this case, this article was inspired by our reader AF, from Adelaide. The question was; how can we implement a calendar control, to allow the users to select a date – but the calendar control must be able to black-out certain dates (public holidays, non-work days). It’s the sort of thing we probably can all relate to.

The Calendar control that Oracle Policy Automation uses is a very standard JavaScript dropdown, but there is little in the way of configuration in respect of the dates shown. We can style the calendar and we can offer two different ways to enter dates (either as the traditional calendar or the three-fields-in-a-row style that some applications use).

So it comes down to what can be done with an Extension. Regular reader will remember that we have spoken about calendar controls before, on the subject of Year-Only Selection. So that Extension will be the basis of this article.

Firstly, what are the tools we might need?

  • jQuery
  • JQuery UI, especially datepicker

The datepicker widget from jQuery supports a variety of user-related events, including one called BeforeShowDay, which is where we can come in a specify which days should not be clickable. They remain in the calendar display of course.

The basic concept therefore, for this demonstration is:

  1. The user can select a date from the control. Certain days are not available.
  2. The control must handle both adding a date when the date is not currently entered, as well as when the date is already entered and the user wants to correct it (for example, going back to a previous screen in the interview.
  3. The date must of course be saved to our chosen attribute.

As always this is without any warranty or guarantee of fitness for purpose. It’s a quick demonstration that you can then add to and correct yourselves.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/**
 * Richard Napier The OPA Hub Website March 2019
 * Educational Example of Custom Date Control Extension
 * I will remember this is for demonstration purposes only
 */
OraclePolicyAutomation.AddExtension({
	customInput: function (control, interview) {
		if (control.getProperty("name") == "xDate") {
			return {
				mount: function (el) {
 
					var myDatePicker = document.createElement('input');
					myDatePicker.setAttribute("id", "dateselect");
					var mySessionDate = interview.getValue("dt_session");
 
 
					el.appendChild(myDatePicker);
 
 
					$('#dateselect').datepicker({
						dateFormat: "yy-mm-dd",
						onSelect: function (dateText) {
							var RecoveredDate = dateText;
 
							interview.setInputValue("dt_circ", RecoveredDate);
 
						},
						beforeShowDay: function (date) {
							var array = ["2019-03-14", "2019-03-15", "2019-03-16"]
							var string = jQuery.datepicker.formatDate('yy-mm-dd', date);
 
							return [array.indexOf(string) == -1]
						}
					});
 
					var mySelectedDate = "";
 
					if (interview.getValue("dt_circ") == null) {
 
						$('#dateselect').datepicker("setDate", mySessionDate);
						var RecoveredDate = mySessionDate;
 
						interview.setInputValue("dt_circ", RecoveredDate);
 
 
					} else {
 
						var myPreviouslySelectedDate = new
							Date(interview.getValue("dt_circ"));
 
 
						var myPreviouslySelectedDateAsDate = new
							Date(myPreviouslySelectedDate);
 
						var myDayPrefix = "";
						var myMonthPrefix = "";
						if (myPreviouslySelectedDateAsDate.getDate() &lt; 10) {
							myDayPrefix = "0"
						}
						if (myPreviouslySelectedDateAsDate.getMonth() &lt; 10) {
							myMonthPrefix = "0"
						}
						var myConvertedYear = myPreviouslySelectedDateAsDate.getFullYear();
						var myConvertedMonth = myPreviouslySelectedDateAsDate.getMonth() + 1;
						var myConvertedDay = myPreviouslySelectedDateAsDate.getDate();
						var myPreviouslySelectedDateOnly =
							myConvertedYear + "-" + myMonthPrefix + (myConvertedMonth) + "-" + myDayPrefix + myConvertedDay;
 
 
						mySelectedDate = myPreviouslySelectedDateOnly;
 
 
						$('#dateselect').datepicker("setDate", mySelectedDate);
 
 
						var RecoveredDate = $('#dateselect').datepicker("getDate");
 
						interview.setInputValue("dt_circ", new Date(RecoveredDate));
					}
				},
				update: function (el) {},
				unmount: function (el) {
					var RecoveredDate = $('#dateselect').datepicker("getDate");
 
					interview.setInputValue("dt_circ", new Date(RecoveredDate));
 
					var myPicker = $('#dateselect');
					myPicker.remove();
 
 
				}
			}
		}
	}
});

Calendar Black Out Dates with Control Extensions – About the Code

The first part of the Mount is basically setting up the jQuery datepicker to be able to hide some days. We also set the date format to the international YYYY-MM-DD that we all know. Of course a more sophisticated approach would check the region and apply the correct format.

Line 22 Sets up the Select Event and Line 29 the BeforeShowDay  Event.

We attempt to grab the value of our user-entered date (dt_circ) and place it in the date control. If that is unknown or null, then we will default to the current date.

When the user selects a valid date, we will of course copy it into the attribute again.
Finally, when the Unmount fires, we will clean up.

The End Result

 

In a later post we will look at using an entity to store the blackout dates. Stay tuned! For the time being the Project is in the OPA Hub Shop.

Custom Options : Hierarchical Lists

Custom Options : Hierarchical Lists

Following on from the previous post, where we looked at how you might use an Entity and it’s instances to provide the dynamic input for a List of Values at run-time, this post will investigate how we might go one step further and implement a hierarchical lists with two dynamic lists based on instances.

Going back to something I mentioned in the previous post, I’ve generally been mystified why Hierarchical Lists have always been a struggle in Oracle Policy Modeling. I don’t think that exporting a List, modifying the XML file and then re-importing it is exactly business friendly. I know and appreciate that Stephen Estes created a cool Excel tool to make it more friendly by hiding all the XML manipulation from the naked eye, but the overall process just seems, well, lame to me.

In the previous post, we looked at building a Custom Options extension to pull values from an Entity. That at least means that instances become the run-time source of the values. But what about having two value lists in a hierarchy, and having both of them based on Entity Instances? Basically, create an Excel spreadsheet for your dynamic values using inferred instances, and read that into the hierarchy dynamically?

As soon as you sit down and look at this, some thorny problems jump out. Most obviously the following:

  1. The Custom Options extension does not provide any keys for our code like the ones you see in Inputs and elsewhere : updatevalidatemount or unmount. So the concept of “update the child because the parent changed” becomes harder.
  2. The Entities (assuming for the moment that there are two) will be arranged in a hierarchy, which means a double iteration – find the parents, then find the children for each parent. This would be time consuming.

From a component perspective, we will have two Controls, both Custom Options. Two JavaScript files. They will need to have a few Custom Properties on the Controls, to support the concept (such as the Property “EntityName” which we will pass into the JavaScript. But the child list will need to be able to access the Parent value.

In the end, for the purposes of demonstration, we are going to do this with three files. Two Custom Options and a Custom Input. The Custom Input does not do much, except provide us with a hook to hang a line of code, so that when the parent value is updated we can refresh the child values.

Our basic spreadsheet looks like this:

OPA 12 - Custom Options Hierarchy Data Source

Regular readers will recognize this as a data set we have used previously in posts about RuleScript. This time however we will just use it as source data for our Custom Options hierarchy. There are two Entities, the station and the child station. Each parent has multiple children. You get the idea. Pick a station, then view and pick a child station. The entities and attributes have been set up with names to be able to reference them in our JavaScript.

  1. These attributes have nothing to do with the example, they are just to fill out the page
  2. The parent and child entities are added with Entity Containers. There is a Boolean attribute to ensure that they are hidden, but they need to be present for the data to be available to JavaScript
  3. These are the attributes involved in the Hierarchy
  4. This Control has a Custom Option extension. It is the parent list.
  5. This Control has a Custom Input extension. It is never entered manually, and simply shows the user what they selected. A decent CSS would make it either invisible or at least a bit nicer to look at.
  6. This Control has a Custom Option extension. It is the child list.

First the parent list, which is identical to the example shown in the previous post. It simply reads the instances into the Options list.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
OraclePolicyAutomation.AddExtension({
	customOptions: function (control, interview) {
		if (control.getProperty("name") == "xOptionsParent") {
			console.log("Custom Options Extension found " + control.getProperty("name"));
				var entityId = control.getProperty("entityname");
				var entities = interview._session.config.data;
				var entityinstance;
				var entity;
				var myStations = []
				for (i = 0; i &lt; entities.length; i++) {
					entity = entities[i];
					if (entity.entityId === entityId) {
						break;
					}
				}
				var Stations = entity.instances;
				for (i = 0; i &lt; Stations.length; i++) {
 
					myObject = new Object();
					for (j = 0; j &lt; Stations[i].attributes.length; j++) {
						entityinstance = Stations[i].attributes[j];
 
						if (entityinstance.attributeId === "station") {
							textofentry = entityinstance.value
						}
 
					}
 
					myObject.text = textofentry.toString();
					myObject.value = textofentry.toString();
					myStations.push(myObject);
				}
 
				console.log("List of stations now ready" + JSON.stringify(myStations));
 
				return {
					options: myStations,
					controlType: "Dropdown"
				}
 
		}
	}
});

The child list Custom Options file is very similar:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
 * Richard Napier The OPA Hub Website January 2019
 * Educational Example of Custom Options List as Hierarchical LOVs
 * I will remember this is for demonstration purposes only
 */
OraclePolicyAutomation.AddExtension({
	customOptions: function (control, interview) {
		if (control.getProperty("name") == "xOptionsChild") {
			//console.log("Custom Options Extension found " + control.getProperty("name"));
			var myStation = interview.getValue("parentstation");
				var entityId = control.getProperty("childentityname");
				var entities = interview._session.config.data;
				var entityinstance;
				var entity;
				var myChildStations = []
				for (i = 0; i &lt; entities.length; i++) {
					entity = entities[i];
					if (entity.entityId === entityId) {
						break;
					}
				}
				var Stations = entity.instances;
				var shortList = Stations.filter(function (el) {
						return el.parentId.attrVal == myStation;
					});
				for (i = 0; i &lt; shortList.length; i++) {
 
					myObject = new Object();
					for (j = 0; j &lt; shortList[i].attributes.length; j++) {
						entityinstance = shortList[i].attributes[j];
 
						if (entityinstance.attributeId === "child_station") {
							textofentry = entityinstance.value
						}
 
					}
 
					myObject.text = textofentry.toString();
					myObject.value = textofentry.toString();
					myChildStations.push(myObject);
				}
 
			//	console.log("List of child stations now ready" + JSON.stringify(myChildStations));
				return {
					options: myChildStations,
					controlType: "Dropdown"
				}
		}
	}
});

Lines 22 -25 try and optimize the process of grabbing the child stations by using a direct access to the child stations in JavaScript and then filtering the array to only get those whose parent id is the correct one. This seemed faster than iterating over all the children first.

So far so good. The two options will work fine, if you place them on different screens, with the Parent before the child. But we need them to be on the same Screen. The third JavaScript file, the Custom Input, looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/*  Generated by the OPA Hub Website 23/01/2019 15:22
Educational Example of Custom Input Extension for Oracle Policy Automation
 I will remember this is for demonstration purposes only. 
*/
OraclePolicyAutomation.AddExtension({
    customInput: function(control, interview) {
        if (control.getProperty("name") == "xInput") {
            return {
                mount: function(el) {
                    //console.log("Starting customInput Mount " + control.getProperty("name"));
					var div1 = document.createElement("divinput")
                    var div2 = document.createElement("input");
                    div1.id = "myContainer";
					div2.id = "myInput";
                    div2.value = control.getValue();
					div1.appendChild(div2);
                    el.appendChild(div1);
                    //console.log("Ending customInput Mount");
                },
                update: function(el) {
					var now = new Date();
					//console.log("Starting customInput Update " + control.getProperty("name") + " " + now);
                    //control.setValue(interview.getValue("parentstation"));
                    //console.log("Ending customInput Update " + now + " " + interview.getValue("parentstation"));
					//console.log("Control " + control.getValue());
					if(interview.getValue("parentstation") != null &amp;&amp; interview.getValue("parentstation") != "" &amp;&amp; interview.getValue("parentstation") != control.getValue()){
						control.setValue(interview.getValue("parentstation"));
						//console.log("Updating Now");
					interview.saveData();
					}
                },
                validate: function(el) {
                    console.log("Starting customInput Validate");
                        control.setValue(document.getElementById("myInput").value);
						//interview.setInputValue("parentstation", document.getElementById("myContainer").value)
						//console.log(document.getElementById("myInput").value);
						//interview.update();
		                return true;
                    console.log("Ending customInput Validate");
                },
                unmount: function(el) {
                    if (control.getProperty("name") == "xInput") {
                       // console.log("Starting customInput UnMount");
                        var myContainer = document.getElementById("myInput");
                        myContainer.parentNode.removeChild(myContainer);
                       // console.log("Ending customInput UnMount");
                    }
                }
            }
        }
    }
})

The only lines of code that matter are the update. Basically whenever the parent station is not null, and has a different value to this custom input, then save the interview. This triggers a refresh, and the parent and child lists are redrawn, and the new values are available to the user.

It’s not pretty, but short of hacking some real DOM events we couldn’t find a way to update the list since (as we mentioned earlier) it does not have any keys like update or validate that we might use.

In the end the user experience is reasonably cool, with the lists being refreshed:

Hierarchical Lists Animation

Hierarchical Lists Animation
If you are interested in the finished Project just drop a comment and I will happily share it, with absolutely no warranty.

Custom Options : Dynamic List of Values

Custom Options : Dynamic List of Values

One of the things that I always found strange about early Oracle Policy Modeling 12 versions was the inability to use Excel (or Word) as the basis for a List of Values. You either dropped them into the Screen Control as Values, or you built a Value List in the Project tab.

Neither of those seemed to exactly be the “business friendly” sort of thing that was everywhere else in Oracle Policy Modeling. Then along came the “Use Values From Rules” button that showed up a few versions ago:

Custom Options - Use Values from rules

And I thought, Great! Now I can have dynamic values read from an Excel or Word document. But actually, that’s not what it means. It imports the values from the source document, but the link is not dynamic. Change the values, and you would have to click the button again. To me, that just doesn’t really give me what I want.

Now, of course, I’m not about to suggest that every list of values needs to be dynamically loaded at run-time, nor do I want to have to do that. But consider a very simple idea. I have an Entity, with a bunch of instances. And I want to use those instances to be the dynamic content of my list of values. So if the user enters instances on Screen 1, I want to show a drop-down on Screen 12 with the instances as the source of my list of values. I don’t want to create a relationship control : this is not about relationships. This is just about using dynamic values for my list. It seems so obvious to me. But given I may not be thinking like everyone else is, that’s fine too.

One final point : with my luck, the Oracle Policy Automation team will deliver this functionality in the next version!

So here is what we want to do:

  1. Build an Excel sheet with some data
  2. Use that to display a dynamic list of values

This will be a Custom Options extension, since that allows us to load data from an Object in JavaScript. Let’s take the example of a list of train stations (from a previous project we’ve used on the OPA Hub Website).

Custom Options Data Source

Now we need to build a Custom Options JavaScript extension, using the standard template.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
OraclePolicyAutomation.AddExtension({
	customOptions: function (control, interview) {
		if (control.getProperty("name") == "xOptionsParent") {
			console.log("Custom Options Extension found " + control.getProperty("name"));
				var entityId = control.getProperty("entityname");
				var entities = interview._session.config.data;
				var entityinstance;
				var entity;
				var myStations = []
				for (i = 0; i &lt; entities.length; i++) {
					entity = entities[i];
					if (entity.entityId === entityId) {
						break;
					}
				}
				var Stations = entity.instances;
				for (i = 0; i &lt; Stations.length; i++) {
 
					myObject = new Object();
					for (j = 0; j &lt; Stations[i].attributes.length; j++) {
						entityinstance = Stations[i].attributes[j];
 
						if (entityinstance.attributeId === "station") {
							textofentry = entityinstance.value
						}
 
					}
 
					myObject.text = textofentry.toString();
					myObject.value = textofentry.toString();
					myStations.push(myObject);
				}
 
				console.log("List of stations now ready" + JSON.stringify(myStations));
 
				return {
					options: myStations,
					controlType: "Dropdown"
				}
 
		}
	}
});

Let’s look through the code:
Lines 6-10 : Using a Custom Property, cycle through the entities on the Screen and find our Source entity.
Lines 16-30 : iterate through all the instances of that entity, pulling out one attribute. Push that attribute into an Object, twice : once for the value, once for the display value. Of course this is where you could add a Filter and do something clever like only show values that meet some criteria or other.
Lines 35-38 : return the Object, and request a Drop-down (you can also specify other choices like “Radiobutton” or “text-button-group”). The value is case sensitive.

As always, this code is just for educational and entertainment purposes, and I definitely do not imply any warranty or fitness for purpose. Quite the opposite.

To implement it, place the following on a Screen

  1. Your attribute that displays the data. I’m using a Global attribute.
  2. Remember that all the things you want to reference in JavaScript need an attribute name, or an entity name.
  3. An entity container for your Source entity. I chose to hide mine using a Boolean which I hard coded in a Word document. Remember that currently, you need the information source to be present on the Screen to be able to access it in JavaScript.
  4. Add two Custom  Properties to the Control : one for the name and one for the entityname.

It might look like this :

  1. At a minimum add an Entity Container for your Source.
  2. Add the attribute that will be the Custom Options list.
  3. Configure the attribute to have the Custom Properties

At run-time, your drop-down will appear:

Now don’t go loading 500 values into your drop-down, since this is all handled at run-time. But of course, moderation in all things will mean you get what you want.

I’ve added this to the OPA Hub Shop – in the next part of this article, we will take Custom Options to the next level and find a way to make a hierarchy of dynamic values, without having to mess with XML formats, or import anything, or hook up a Connection.

Template Generator Update – JavaScript for Control Extensions

Template Generator – JavaScript for Control Extensions

Another update, quite a big one, for the template generator. We’ve added lots more information to explain how to use the generator, and hopefully how to work on the template once you have created it. In addition, we have added the Entity Container as a new extension type, so you can create scrollbar-friendly lists of instances for your Entities. Read on for more information. If you just want to download the new version leave a comment and I will send it to you. In the not too distant future we will be releasing it to the OPA Hub Shop, but for now just ask and we can pass it to you.

Control Extension Template Generator : New Form Available

If you don’t intend to use the template code straight away, you can now just print the Form and keep it for later. At the moment the form just dumps the code in plain text, it does not pretty-print it like the screen output, but the code is still ready to copy-and-paste.

Control Extension Template Generator : Information Added

In the Form, we have included some hopefully useful background information about using the code, what to look out for and what to know in terms of where you might want to customize the code. We’ll keep adding to this Form as time goes on, with anything we think might be useful.

Control Extension Template Generator : New Extension

You can now generate Entity Container Extensions. This template requires an extra Property and uses jsGrid to provide a scrolling layout. Again, more information is given in your Form about how to use it and how to customize it to go even further. As always, the official documentation is your friend.

Control Extension Template Generator : Example Container

New Video

And so, to close, you will find below a short video explaining the new Extension Template for Entity Containers, and giving you a bit more background about the different aspects of this Extension type. Nothing beats having a go yourself. Of course, as we always say, the code is entirely for entertainment purposes, never for production, and it’s your own fault if you don’t get a professional to rewrite it. But at least you will know that it can be done, and as a consultant that is often one of the most important things to know.

The video can be viewed here:

Entity Collect Extension

Entity Collect Extension

Some time ago I wrote about Entity Collect Extension and the challenges of writing a demonstration piece of code. The challenges of Entity Collect Extension are pretty well known, but here is a run down of the most important one:

  • You must build the container and the contents,  and manage them

That means, that if you build an Entity Collect Extension, you cannot use other control extensions inside it :- you must code the entire contents yourself, so you would be looking at

  • Handling all different data types of attribute
  • Handling all the basic events of these attributes
  • Managing instance creation and deletion
  • Handling styling options and look and feel

So when you look at this shopping list, it can be quite daunting. Even so, I said to myself, if Oracle provides such a functionality, then somebody somewhere is going to use it. And so the idea came to create a demonstration Entity Collection Extension. When I started working on it I really did not have much time to look at it, so I wrote a couple of articles about my experiences and left it at that.

Recently I have been revisiting the concept again, and have found  time to work on it a little bit more. I added it to the OPA Hub Shop (on the strict understanding, as usual, that it is completely for educational and amusement purposes only).

That statement is particularly important in this case. I only decided to do this because I am fascinated by other people’s JavaScript frameworks. I find it useful to know what goes on underneath the glossy Oracle Policy Modeling HTML output. And my example is basic to the point of being little more than a sandbox (oh, and by the way, it still has a lot of issues – especially when you are running it in the Debugger rather than the Browser).

Entity Collect Extension in JavaScript

This version tries to cover off the 4 shopping list items shown above, and adds a couple of interesting ideas

  • Dynamic Delete Button (remove the button based on an attribute value). This is one request I have seen many times – the ability to forbid deleting in certain circumstances.
  • Handling Date Time and Value Lists for display and data entry.

I still have to find time to investigate :

  • Handling Inferred attributes that need to be refreshed as you are entering instance attributes.

I’ve just about had enough of this now so I am going to leave it for a while.  It has started to make my head spin.  Nonetheless it is an interesting exercise and helped make clear how cool the out of the box JavaScript really is.

The silent movie below shows where I’m at. All that work, for this rather dull looking thing. Sometimes there is not much to show for efforts. Of course, if I was a real programmer, I would be doing all of this in a much more efficient way (avoiding complete page refreshes at every change and so on) but I found this exercise very interesting. Certainly it gives me a better understanding of the scale of such a task.

Have a nice day!

Showing a Loading Image During Entity Creation

Showing a Loading Image During Entity Creation

When a user is entering some information into Entity attributes, it is entirely possible that one of those entity attributes may take its information from a Search extension. For example, you are entering instances of the Person entity and each Person has a location, so you want to select the location using a Search extension.

The Search, given that it is perhaps an Ajax call, could take some time. So you want to signal to the user that there is nothing to worry about, but they need to wait. Typically this is done through some sort of icon or image being displayed, much in the style of the Windows egg-timer or similar. This probably will also need a CSS style rule or two, in order to make it a bit funky.

We want to make sure that this is displayed in the right place, even if the user is creating several instances of the same entity. I mean that the icon should be displayed in the correct area of the screen, especially if you have instances whose screen layout takes up some space.

Anyway as always a picture is worth a thousand words. Here is the instance collection form:

Showing a Loading Image During Entity CreationWhen you have several on the screen, it might look like this:

Showing a Loading Image During Entity Creation

The Destination attribute is a Search extension that helps the user search for a Train Station in the United Kingdom. It take a few seconds for the search to happen.

So our timer needs to be shown in the right place whenever the user is searching. It needs to be instance A or B for example, depending on the instance the user is working on.

Showing a Loading Image During Entity Creation

In the example above the user has typed the Search criteria. The loader is shown in the centre of the instance while the search is happening. So we are Showing a Loading Image During Entity Creation.

Showing a Loading Image During Entity Creation

When the search data is returned, as in the example above, the user should no longer see the loader and the operation can continue as normal.

If the user moves to another instance, then the process should start again but the loader should be instance-aware and show in the correct place so as not to confuse.

Showing a Loading Image During Entity CreationTo do this we can use the Search extension, and add a little bit of extra code to

  • Check to see if we have already displayed the special icon
  •  If we have not, create it, center it on the instance we are working on, show it and make the Search
  • If the icon already exists, move it to the correct instance and show it then make the Search
  • When the search data is returned, hide the icon until the next time.

This example will work with non-tabular forms. I’ll be back with a second post investigating them in a couple of days.

You can find this simple example (with all the usual caveats and reminders that this is just for fun) in the OPA Hub Shop.  The official documentation is here, as always. Thanks to Shankar for the great example of Showing a Loading Image During Entity Creation!