Tag: JavaScript Extension

Spell Checking in Interview Controls

The other day, I came across a question regarding Spell Check in Interview Controls. You know the sort of thing – this would be useful for Inputs, but especially for Text Area Controls where there may be a considerable amount of text that will need spell checking. There are today several approaches but one that stands out.

The first option is to implement something in JavaScript – this has downsides for a couple of reasons. The dictionary for a given language, even compressed and specially structured, is going to be larger than average – the size of the file will make for slower processing. And the JavaScript will need to search this file. Thus, the performance might not be great. There are a couple of good toolkits that find innovative solutions, for example this one, but it will need work to build the apporopriate user interface.

The next option is to hand off some of the processing to the server-side. There are a number of spell check engines out there in open source that use PHP or ASP.NET for example. Some others use third-party servers or Google APIs that need payment. So for a simple spell check in Intelligent Advisor that sounds like a lot of effort.

The final spell check option is to leverage the browser itself. Most modern browsers support the spellcheck=”true” attribute value for inputs and text areas and lots of other text-content tags like <p> and <div>. So the simplest option is to switch on that attribute on any items you want to spell check. For example, add an invisible label extension to your Screen and use it scan all the relevant items on the page and add the spell check capability. For speed, the example below uses jQuery to select the relevant items for spell checking but you don’t need it of course, you can select using standard JavaScript too.

/*  Generated by the OPA Hub Website Code Generator 19/06/2020 18:27
Educational Example of Custom Label Extension for Oracle Policy Automation
I will remember this is for demonstration purposes only.
 */
OraclePolicyAutomation.AddExtension({
	customLabel: function (control, interview) {
		if (control.getProperty("name") == "xSpellCheckEnable") {
			return {
				mount: function (el) {
					console.log("Starting name:xSpellCheckEnable customLabel Mount");
					$(document).ready(function () {
						$("input[type='text'], textarea").attr('spellcheck', true);
					});
					console.log("Ending name:xSpellCheckEnable customLabel Mount");
				},
				update: function (el) {},
				unmount: function (el) {
					if (control.getProperty("name") == "xSpellCheckEnable") {
						console.log("Starting name:xSpellCheckEnable customLabel UnMount");
						console.log("Ending name:xSpellCheckEnable customLabel UnMount");
					}
				}
			}
		}
	}
})

With the above code in place, an input or text area will allow spell checking using the browser. Note the wavy line and the right-click:

Spell Check in IA

I’m not saying this is the perfect solution but there are some good and less good points:

  • No management of files needed
  • No user interface to create
  • Supported by the vast majority of browsers (see link above)
  • No server-side stuff to pay for
  • Applicable to pretty much any HTML tag

There are a few downsides

  • Usually defaults to the operating system / chosen language in the browser settings rather than the document language
  • Not the most exciting user interface
  • If you use multiple browsers you might end up with different word lists.

But sometimes, less is more. This example is free in the Shop. Happy Spell Checking!

Dynamic Input Mask JavaScript Extension

Dynamic Input Mask JavaScript Extension

This week I was lucky enough to be chatting to a reader when he shared a requirement for a dynamic mask. The exact concept was simple : when entering a telephone number, if the end user of the Interview types a number first, then the mask should default to the national format – for example (555) 1234-1234 – but if the end user types the international “+” prefix first then the mask should switch to a different one, for example +00 000 0000 0000 or whatever is appropriate. Outside of this example, there are frequently situations where more than one mask is needed, depending on some criteria or some fragment of data entry.

We should not forget of course that if all we want is a simple mask, then Intelligent Advisor has got it covered with the standard out of the box functionality. To implement a simple, static mask simply requires us to create an attribute of type Text and to select Masked Input from the Interview Ribbon before specifying the mask in the Input Mask dialog.

As an example, the following shows a Masked Input Control with a specific mask being implemented. In addition, you can see the Hint Text in the Interview tab shows the result in case you forget.

Clearly this is going to produce a good mask. But it cannot be changed dynamically, as it is part of the Control properties at runtime. So the possible solution is to use an Extension. In this case, a customInput. For the purposes of demonstration, we chose to use the jQuery Mask plugin. You can find more about it at the address : https://igorescobar.github.io/jQuery-Mask-Plugin/docs.html. The most interesting element is the fact that it supports dynamic masks. It uses the keypress event to enable the developer to check the character(s) and then change the mask. So this sounded like a great idea, and indeed proved quite straightforward. The code below shows that in fact there are several steps needed : define the two masks, and also handle the case when the area is blanked by the user.

Happily we found an old project where we had already used this dynamic input mask technique and adapted it for today.

Dynamic Mask

Here is the example code, of course you can get the Zip file from the Shop. There are some comments below.

/*  Generated by the OPA Hub Website Code Generator 30/11/2018 22:41
Educational Example of Custom Input Mask Extension for Oracle Policy Automation
I will remember this is for demonstration purposes only.
 */
OraclePolicyAutomation.AddExtension({
	customInput: function (control, interview) {
		if (control.getProperty("name") == "xMask") {
			return {
				mount: function (el) {
					console.log("Starting name:xMask customInput Mount");
					var div = document.createElement("input");
					div.id = "xMask";
					div.value = control.getValue();
					el.appendChild(div);
					var oldVal;
					$('#xMask').on('keypress', function () {
						var val = this.value;
						if ((val != oldVal) && (val.length == 1)) {
							oldVal = val;
							if (oldVal == '+') {
								$('#xMask').mask('+999-9999-9999');
								$('#xMask').val(oldVal)
							} else {
								$('#xMask').mask('(999)-999-99999');
								$('#xMask').val(oldVal)
							}
						} else if (val.length == 0) {
							$('#xMask').unmask();
						}
					});
					console.log("Ending name:xMask customInput Mount");
				},
				unmount: function (el) {
					if (control.getProperty("name") == "xMask") {
						console.log("Starting name:xMask customInput UnMount");
						var xMask = document.getElementById("xMask");
						xMask.parentNode.removeChild(xMask);
						console.log("Ending name:xMask customInput UnMount");
					}
				}
			}
		}
	}
})

As you can see in this dynamic input mask demo, there is only really the mount to worry about. We use the keypress event and switch the mask based on the previous character pressed on the keyboard. And if no characters exist in the control we remove the mask ready for the next try. Here is a short video demonstrating the effect, and comparing to a static mask. The input control extension has not had any CSS formatting applied so it appears a little smaller than the standard. This can easily be changed.

video

Have a nice day! (the documentation about standard input mask functionality can be found online here).

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.

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.

Temporal Reasoning #5 : Temporal Attributes in Interviews

Temporal Reasoning #5 : Temporal Attributes in Interviews

In the final part of this mini-series, the OPA Hub Website continues to look at Temporal Reasoning and in this particular chapter discusses the display of Temporal Attributes data in the Interview experience.

If you missed any of the previous parts of the series, they are here:

So now that the we have a bit of understanding of how these data elements work, what about displaying them in a good way to the end user in the Interview. Well, part of the answer is hidden in part four of this series. We cannot actually display temporal attributes for data entry in an Interview (so conversion comes in very handy).

Let’s be more specific. Using the project from the previous chapter, in 18C, when an attempt is made to display the temporal information on a Screen, various things happen that underline the some important concepts:

Temporal Attributes in Interviews Animation OPA 12Please excuse the retro-style animated GIF above. But the demonstration above illustrates the fact that Temporal attributes and their change-points are complex data types that cannot be handled in a “normal” way. The one exception to the above is if you have a Temporal attribute whose values are the Goal of the interview, then an Explanation will display the data in a coherent way:

OPA 12 - Viewing Temporal Attributes in Interviews

If your project is using RuleScript and your data is pre-seeded, then potentially you could access it by deriving a string value which is a concatenation of the different values and displaying that attribute using a label control.

// RuleScript(myString) &lt;- (temporalBankBalance)
function FindHighest(global) {
	console.log("Starting RuleScript");
	var v = global.temporalBankBalance;
 
		console.log("Base Value of v " + v._baseValue);
		var idx,
		maxVal = null;
		var myString = "";
		console.log("Length of Temporal Array " + v._changeValues.length);
 
	if (v._changeValues.length &lt;= 0) {
		global.myString = "No Temporal Values";
	} else {
		console.log("Getting Temporal Values");
		for (idx = 0; idx &lt; v._changeValues.length; idx++) { var changeValue = v._changeValues.valueAt(idx); if (changeValue != null &amp;&amp; (maxVal == null || changeValue &gt; maxVal))
				maxVal = changeValue;
		}
		global.myString = global.myString + changeValue + ";";
	}
 
}

But given that RuleScript is experimental, that it’s use cases are highly controlled this would be a very bad idea. Plus it would only work if the temporal values were pre-seeded into the Interview, which renders it all very complex.

So what is plan B? It might be to use something like the Entity Container Extension that we have previously looked at (which would imply using the conversion technique mentioned in part four).

If you are looking for a quick and dirty way to get at the Temporal values, even though the JavaScript Extensions do not actually support them today, the values are still accessible. They can be accessed using something like this. I have not published this as a download since the snippet below is very primitive.

 

/**
 * Richard Napier The OPA Hub Website August 2018
 * Educational Example of Custom Container with a Timeline inside
 * I will remember this is for demonstration and educational purposes only
 */
OraclePolicyAutomation.AddExtension({
	customLabel: function (control, interview) {
		if (control.getProperty("name") === "xTimeline") {
 
			return {
				mount: function (el) {
					var script_tag_addition = document.createElement('script');
					script_tag_addition.setAttribute('src', '${resources-root}/js/timeline.min.js');
					script_tag_addition.setAttribute('type', 'text/javascript');
					document.getElementsByTagName('head')[0].appendChild(script_tag_addition);
					var stylesheet_tag_addition = document.createElement('link');
					stylesheet_tag_addition.setAttribute('href', '${resources-root}/css/timeline.min.css');
					stylesheet_tag_addition.setAttribute('rel', 'stylesheet');
					stylesheet_tag_addition.setAttribute('type', 'text/css');
					document.getElementsByTagName('head')[0].appendChild(stylesheet_tag_addition);
 
					var myDiv = document.createElement("div");
					myDiv.setAttribute("class", "timeline");
										myDiv.setAttribute("data-mode", "horizontal");
					el.appendChild(myDiv);
 
 
					var myDivWrap = document.createElement("div");
					myDivWrap.setAttribute("class", "timeline__wrap");
					$(".timeline").append(myDivWrap);
 
					var myDivChild = document.createElement("div");
					myDivChild.setAttribute("class", "timeline__items");
					$(".timeline__wrap").append(myDivChild);
 
					// Don't do this, it relies upon the temporal attribute
					// being the only one in Global exposed on the page as a %label%
					var myTemporal = []
					myTemporal = control._source.screen.config.data[0].instances[0].attributes[0].value.changePoints
						for (var i in myTemporal) {
							var iterateDIV = document.createElement("div");
							iterateDIV.setAttribute("class", "timeline__item");
							iterateDIV.setAttribute("id", i);
							$(".timeline__items").append(iterateDIV)
							var iterateDIVContent = document.createElement("div");
							iterateDIVContent.setAttribute("class", "timeline__content");
							$('.timeline__item[id="' + i + '"]').append( "GBP "  + myTemporal[i].value + " on " + myTemporal[i].date)
						}
				},
				update: function (el) {},
				unmount: function (el) {
					var myDiv = $(".timeline");
					myDiv.remove();
 
				}
 
			}
		}
	}
});

The above snippet relies upon a jQuery plugin to display the data. This gives something like this, a visual timeline on the left of this page:

Temporal Attributes in Interviews JavaScript Extension

Of course there are hundreds of Timeline plugins, each more sophisticated than the other, on the Internet.

We hope that this series has brought to light some of the key features of temporal reasoning, and thanks to Orlando who suggested this series in the first place. Have a good day and see you soon!

 

Oracle Policy Automation Embed Website in Interview

Oracle Policy Automation Embed Website in Interview

This request comes up quite often, at least often enough that I feel the need to mention it today. The example we are going to use is to embed the OPA Hub Website in an Oracle Policy Automation Interview. In addition we are going to pass an attribute to the website so that it performs a search for us. So, let us start our tutorial “Oracle Policy Automation Embed Website in Interview”.

Now, I am sure many of you are old enough to have spent years trying to avoid IFRAME integrations in applications : Siebel, SAP, they all do it or have done it at some point in time, and they are awful for the most part – whether it be from an accessibility, SEO, browser restriction or other perspective. So here are our goals for this mission:

  • Don’t use an IFRAME
  • Add the Website in a way that does not destroy the look and feel of the Interview
  • The Website must actually function properly

The steps to create this Project are shown below. You should be aware (and not be surprised) that this will not work well in the built-in embedded Browser in Debug Mode, so run Debug mode using Ctrl+Debug or deploy the Project to see the final results.

Oracle Policy Automation Embed Website in Interview Pre-requisites:

A New Project called “Oracle Policy Automation Embed Website in Interview” or something shorter.

Create a global attribute with the text the subject with a name of subject.

Create a new Screen to ask what is the subject. I suggest a Drop-down list with the Values “Siebel Integration, JavaScript Extensions,Service Cloud”.

Create a second Screen to display the Website.

  1. Create a label, using the name  of  the subject to display the chosen subject.
  2. Place this inside a Container
  3. Make sure you add a Property for the Container, naming the Object.

By now the Screen should look like this:

Oracle Policy Automation Embed Website in Interview Design

Now we come to the code. This is quite simple,although we will also need some CSS to make it look right. Add a JavaScript file and a CSS file to your resources folder for this Project. The explanation is after this code, which is as always provided for educational and investigative purposes only:

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
/**
 * Richard Napier The OPA Hub Website April 2018
 * Educational Example of Custom Container with a Website inside
 * I will remember this is for demonstration and educational purposes only
 */
 
OraclePolicyAutomation.AddExtension({
	customContainer: function (control,interview) {
		if (control.getProperty("name") === "xWebsite") {
 
			return {
				mount: function (el) {
					var myDiv = document.createElement("div");
					myDiv.setAttribute("id", "mySpecialDIV");
					document.body.appendChild(myDiv);
					$("#mySpecialDIV").width(900);
					$("#mySpecialDIV").height(600);
					var mySubject = interview.getValue("subject");
					$("#mySpecialDIV").html('XXXXXX'+ mySubject + '"&gt;');
				},
				update: function (el) {},
				unmount: function (el) {
					var myDiv = $("#mySpecialDIV");
					myDiv.remove();
 
				}
 
			}
		}
	}
});

Oracle Policy Automation Embed Website in Interview Code

In the code above, here are the salient points.

Lines 13 to 17 create a DIV and insert it into DOM, appending it to the body of the document.

Line 18 retrieves the value selected by the user on the previous page that is present in the Container as a label.

Line 19 is the most important one. Notice the “XXXXX”. Replace this with the website and any URL construct you need. For example, replace it with the following:

<object data="https://theopahub.com/main/?s=

The code viewer didn’t correctly display that part. Insert it exactly as shown, so that you are concatenating the URL with the user selected subject. The key point here is the use of the object tag rather than an nasty IFRAME. Thanks to Stackoverflow!

The rest of the code just tidies up when the unmount happens.

Oracle Policy Automation Embed Website in Interview CSS

The CSS is quite important here, to ensure that the embedding is seamless. Add this CSS code to your CSS file.

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
object {
    height: 100%;
    width: 100%;
}
#mySpecialDIV {
	float: left;
    margin: 0px;
	-webkit-border-radius: 10px;
	-moz-border-radius: 10px;
	border-radius: 10px;
	background-color: #404040;
}
html
{
    border: 0px;
    margin: 0px;
    overflow: hidden;
    padding: 0px;
}
body
{
    border: 0px;
    margin: 0px;
    overflow: hidden;
    padding: 0px;
}

Let’s take a look at the CSS. We use styling to position the DIV, as well as styling to ensure the object tag uses all of the available DIV. Finally we use some tricks to eliminate overflowing content and remove the horizontal scrollbar.

Oracle Policy Automation Embed Website in Interview Debugging

As mentioned earlier, this may be best tested in the Browser, not in the Debug Embedded Browser, so make sure you start with Ctrl+Debug.

The first screen will be straightforward:

Oracle Policy Automation Embed Website in Interview 1

The second screen will display and if your Internet connection is slow, you may have time to witness the two stages of display:

Stage 1 : Show the styled DIV that has been added to the Screen. Of course you don’t have to use this colour, I just wanted to use it for positioning and effect.

Oracle Policy Automation Embed Website in Interview DIV

Stage 2 : The embedded Website is displayed. The embedding is seamless (nice colour scheme!).

Oracle Policy Automation Embed Website in Interview End

  1. Notice that the attribute value has been passed to the OPA Hub Website and a search has been performed for you. The site is fully functional and can be accessed from the Interview Window.

Have a nice day! (The PDF is in the OPA Hub Shop).

JavaScript Extensions with a Live Entity-based Chart in Oracle Policy Modelling

JavaScript Extensions with a Live Entity-based Chart in Oracle Policy Modelling

[NB : An updated post has been written in February 2019]

One of the most interesting questions I have been asked during my current assignment in beautiful Madrid has been the following :

“How can I make a Chart”

Well, that’s easy I said : pick your Charting platform (for example, you might choose D3 as the charting library as it is well-known and incredibly powerful. Or alternatively you could manipulate SVG arcs and lines yourself if you want to, to achieve the same thing.

“How can I make a Chart based on Oracle Policy Automation data”

Well, that’s easy I said : you even have an example in the Oracle Policy Automation Example Projects, called the Loan Advisor. You can see a screenshot from that project, right here:

JavaScript Extensions with a Live Entity-based Chart : Intro

Digging deeper into that Project, we discover that the attributes that are used to create the chart are global :

JavaScript Extensions with a Live Entity-based Chart : Intro 2

“How can I make a Chart that uses Entity data”

Well that’s easy I said, you could hook up the D3 library to the entity, and use a similar system to our custom Entity Container example to retrieve the data. After all, the Pie Chart in D3  accepts any array of data organized into labels and values. You may remember in that example, we sought the name of the entity using EntityId and then iterated through the instances in the Entity to display them.

But upon investigation this is not really satisfactory. Adding an instance to the entity container does not refresh the Chart. In fact, you have to navigate forward and then backwards to get the Chart to refresh. That’s because, of course, your new “instance” does not actually exist yet. It does not get added into the entity.instances[i] list until you leave the current screen.

“How Can I make JavaScript Extensions with a Live Entity-based Chart in Oracle Policy Modelling”

You will have guessed by now that my entourage here is pretty demanding! So here goes. Firstly, the scenario. I have created an Entity called the payment. It has two attributes, the identifier, a text attribute, and the payment amount, a currency or number as you wish.

The goal : on ONE Screen, enter data and view a live chart that updates live, without changing Screens, as data is added or removed.

To experiment further with this, I also decided to create another Entity called the shadow payment. This is a copy of the original, created by the simple rule in Word that follows:

JavaScript Extensions with a Live Entity-based Chart Intro 3

The idea was to test if it would work both on inferred and normal entity instances. It seemed to, so you could probably use the code with either. To enable my shadow entities, I set up the relationship for the above Word rules to function properly.

JavaScript Extensions with a Live Entity-based Chart in Oracle Policy Modelling Intro 5

The Code Concept

Now we come to the code itself. This is, as I always state clearly, just code that I have hacked together to see if a concept had the ability to be taken further. It is in no way production or even unit test ready. That’s your problem. But I hope you find it interesting and inspiring.

Another note, I have seen that this “code view” messes with some characters. The OPA Hub Shop has a PDF version which you can use to compare and correct anything that looks wrong, notably the > and < characters don’t show up correctly here.

The code uses D3 for the pie chart, and the explanation follows after the code. Pop D3 in a JS file in the resources folder, as well as the D3 library.

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
/**
 * Richard Napier The OPA Hub Website April 2018
 * Educational Example of Custom D3 Chart Extension with live refresh on a screen
 * I will remember this is for demonstration and educational purposes only
 */
 
OraclePolicyAutomation.AddExtension({
	customContainer: function (control) {
		if (control.getProperty("name") === "xChart") {
 
			return {
				mount: function (el) {
 
				},
				update: function (el) {
 
					if (document.readyState == "complete") {
 
						var entity = control._source.screen.serverState.shadowpayment;
						var myFlatList = [];
						var myObject;
 
						var width = 300, //width
						height = 300, //height
						radius = 150, //radius
 
						color = d3.scaleOrdinal(d3.schemeCategory10);
						var size = Object.keys(entity).length;
 
						for (i = 1; i &lt; size+1; i++) {
							myObject = new Object();
							myObject.label =control._source.screen.serverState.shadowpayment["@_@opa-rel-target={payment[the payment" + i + "]}/global[global]"].shadow_payment;
							myObject.value = control._source.screen.serverState.shadowpayment["@_@opa-rel-target={payment[the payment" + i + "]}/global[global]"].shadow_amount;
							myFlatList.push(myObject);
							//console.log(" Flattened the list - item " + i);
						}
						data = myFlatList;
						var vis = d3.select(el);
						vis.select("svg").remove();
						var vis = d3.select(el)
						.append("svg")
							.data([data])
							.attr("width", width)
							.attr("height", height)
							.append("g")
							.attr('transform', 'translate(' + (width / 2) +
								',' + (height / 2) + ')'); 
						var arc = d3.arc().outerRadius(radius)
							.innerRadius(0); ;
						var pie = d3.pie()
							.value(function (d) {
								return d.value;
							});
						var arcs = vis.selectAll("g.slice")
							.data(pie)
							.enter()
							.append("svg:g")
							.attr("class", "slice");
						arcs.append("svg:path")
						.attr("fill", function (d, i) {
							return color(i);
						})
						.attr("d", arc);
						arcs.append("svg:text")
						.attr("transform", function (d) {
							d.innerRadius = 0;
							d.outerRadius = radius;
							return "translate(" + arc.centroid(d) + ")";
						})
						.attr("text-anchor", "middle")
						.text(function (d, i) {
							return data[i].label;
						});
					}
				}
			}
		}
	}
});

Review of JavaScript Extensions with a Live Entity-based Chart

Line 8 : This is a Custom Container. So make sure that you drop a container into your Screen, and in the container make sure you add your Entity. In my example, I displayed both the original the payment entity, for the user to add or delete records, as well as the shadow payment which I leveraged to draw the pie chart.

Line 19 ; This example uses control._source.screen.serverState.shadowpayment. This JavaScript object contains the instances of your entity, and is updated as new instances are added, before you leave the page. Of course the name is the technical name  of your entity so don’t forget to add that to your Data tab.

Line 27 : Selecting a standard set of colors from the D3 color categories

Line 28: Finding how many objects are inside the control._source.screen.serverState.shadowpayment object. Each instance is a child object.

Lines 32 and 33 : Creating dynamically the identifiers of each row to select the label and the value using the names of the attributes in your entity, and copying them into a flat object with values and labels

Line 37 : Set the flat list as the data source for the Pie Chart

Line 40 : Add the Chart into the Container

Line 41 : Add the pie chart, setting the origin to the center of the Container, and hooking up to the data we created

Line 50 and beyond : Using D3, draw the arcs and fills that make the Pie Chart by going through the total set of data and dividing the pie into the right number of pieces.

This is the result, in full Hollywood glory, of JavaScript Extensions with a Live Entity-based Chart in Oracle Policy Modelling.

The PDF version is on the OPA Hub Shop, just search for the Pie Chart example.

We will of course be revisiting this to investigate making it more robust, but it’s a good starting point. Have fun and see you soon!

Custom Entity Container with JavaScript Extensions Revisited

Custom Entity Container with JavaScript Extensions Revisited

Assiduous readers will recall that we followed a series of adventures in Entity Container extension some time ago, from a basic tool that worked only in Debug Mode to a more interesting and robust concept that worked once deployed. For reference those Custom Entity Container with JavaScript Extensions articles can be found in the following links

So why come back to this example? For several reasons it seems appropriate to talk again about Custom Entity Container with JavaScript Extensions. Firstly, it is something that is often coming up in classes or on customer sites. So, subjectively I want to talk about it. Secondly, it is a great way of learning the ins and outs of the JavaScript extensions in general.

Yesterday, I was mad

I noticed that the PDF generator I had used for the third (and most interesting and useful example) had pretty much destroyed part of the file : specifically a couple of lines were duplicated and others were truncated. So it is time to revisit this, if only to correct the errors (I have uploaded a more up-to-date file, so that some of the errors have gone).

So let’s set the scene first. We want to display some entity instances. These are generated in my case by an Excel Spreadsheet. They contain one entity, the insult and this entity has three attributes : an Id number, the text of the insult and an insult level – a numeric categorisation of the insult. The higher the number, the more severe the insult. The insults themselves come from Tintin, or more precisely Captain Haddock.

There are no conditions in this Excel file, so the instances are created. There are 240, so we need a good display of our instances. The default display is too long, with no useful scroll bar. We want to replace this with jsGrid, a lightweight jQuery grid. We want something that replaces the style on the left with the style on the right:

Custom Entity Container with JavaScript Extensions Revisited

We would like

  • A grid format using little space
  • A scroll bar
  • A pagination control

The visual elements will be provided by jsGrid, a lightweight JavaScript control. We are also going to set the bar a little higher than last time. We want to have a dynamic filter of the grid, so that the user can view what they want (and not always have the 240 instances on the grid).

Custom Entity Container with JavaScript Extensions Revisited 2

Note: we must tread very carefully here. We must not change the business logic in any way. We must separate the concerns and provide purely UX elements in our JavaScript extension. But given this is inferred data, I think a little filtering is fine, as long as the underlying relationship is not tampered with.

The code would be based on the standard template, so I will simply put it here, in all of it’s quickly-strung together glory, so that you can read it, learn about it, clean it and make it industrial. As I always like to make clear, anything I post here is strictly not-ready, big-picture, here’s-an-idea for you to look at and make your own. This Custom Entity Container with JavaScript Extensions example is available on the OPA Hub Shop for download, as usual. It is listed as example #3 of Custom Entity Container.

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
/**
* Richard Napier The OPA Hub Website April 2018
* Educational Example of Custom EntityContainer Extension
* I will remember this is for demonstration and educational purposes only
*/
OraclePolicyAutomation.AddExtension({
customEntityContainer: function (control, interview) {
//console.log("Get Array Reference");
if (control.getProperty("name") == "xEntity") {
var entities = interview._session.config.data;
var entityId = "entitypublicname";
var entity;
for (= 0; i &lt; entities.length; i++) {
entity = entities[i];
if (entity.entityId === entityId) {
break;
}
}
return {
mount: function (el) {
//console.log("Beginning customEntityContainer jsGrid");
var myDiv = document.createElement("div");
myDiv.setAttribute("id", "mySpecialDIV");
//console.log("Styled customEntityContainer");
el.appendChild(myDiv);
var myFlatList = [];
var myObject;
for (= 0; i &lt; entity.instances.length; i++) {
myObject = new Object();
myObject.insult = entity.instances[i].attributes[0].value.toString();
myObject.insult_text = entity.instances[i].attributes[1].value.toString();
myObject.insult_score = entity.instances[i].attributes[2].value.toString();
myFlatList.push(myObject);
//console.log(" Flattened the list - item " + i);
}
$("#mySpecialDIV").jsGrid({
width: "80%",
height: "400px",
sorting: true,
paging: true,
pagelndex: 1,
pageSize: 10,
pageButtonCount: 10,
data: myFlatList,
fields: [{
name: "insult",
type: "text",
width: 20,
title: "id"
}, {
name: "insult_text",
type: "text",
width: 150,
title: "text"
}, {
name: "insult_score",
type: "number",
width: 20,
title: "score"
}
],
controller: {
loadData: function (filter) {
return $.grep(myFlatList, function (item) {
return item.insult_score === filter.insult_score
})
}
}
});
//console.log("Finished customEntityContainer");
},
update: function (el) {
var myslidervalue = $("[role*='slider']").attr("aria-valuetext");
$("#mySpecialDIV").jsGrid("search", {
insult_score: myslidervalue
}).done(function () {
//console.log("filtering completed with slider value " + myslidervalue);
});
},
unmount: function (el) {
var myDiv = $("#mySpecialDIV");
myDiv.remove();
//console.log(" Removed the customEntityContainer ");
}
}
}
}
});

So now let’s look at the key elements (don’t forget to download and place jQuery and jsGrid files into your resources folder) :

Line 11 – this should be replaced with the name of your entity (not the text, but the name or XML tag as some call it). We are going to search amongst the entities until we find yours.

Lines 30 to 35 –  the code extracts your entity and pulls out three attributes from the entity. Note of course that these three attributes need to be placed in your Interview Screen, inside the Entity Container, for this data to be available. Essentially the extracted information is made into a JavaScript object, and the object added to an array.

Line 38 – this is the start of the jsGrid code.

Line 47 – this is the definition of the three columns of data in the table and how to display them.

Line 66 – this is the custom filter function which will hide any instances that do not have the selected score.

Line 77 – this is where we obtain the value of the slider and we refresh the table to only show those records using the filter function.

Thanks to the Madrid crew for their suggestions. In the next few days we will look at another Custom Entity Container with JavaScript Extensions example, this time with a dynamic chart using the same principle. Please note as usual that for best results when debugging, use Ctrl+F5 to debug in a decent browser.

Worldwide
Logo by Southpaw Projects LLC