Tag: JavaScript

JavaScript Custom Image Extension

One of the regular readers of this website was very excited to see that recently, the Custom Extensions were updated to include a Custom Image Extension. Whilst at first glance you might not think it would need much of an extension, or maybe you cannot think of a requirement, there have been many situations where the logic of dynamic images, dynamic sizing, and so forth could not be done with a styling extension or a bit of CSS.

So what is available in this type of extension? In a Custom Image Extension, you can leverage the following Control Methods to customize your image.

getWidth()The width of the control (not just for image controls)
getHeight()The height of the control
getImageSource()The URL representing the source of the image
getLinkUrl()The URL used if the image is clicked
openLinkInNewWindow()Will the link open in a new Window?
getHorizontalAlignment()The alignment of the image
getCaption()The text caption of the image

For a quick demonstration, the following project will create a dynamic QR code according to your chosen URL. The image can resized on the fly using the sliders, and you can set the URL and whether it will open in another window:

The Custom Image Extension will generate a QR code and the user can manipulate the size of the image to see it changing dynamically. You could even then put the dynamic image in your Forms. Or use it to send a link to someone for a GetCheckPoint. Lots of ideas!

Creating a QR Code

There are lots of applications where the ability work with a Custom Image Extension will maker life easier. There is a good example in the Example Projects – it’s called Health Diagnosis and check out the diagnosis page with the dynamic image. The example project contains the JavaScript for the Custom Image Extension in the usual folder.

You can get the example Zip file from the OPA Hub Shop.

Intro Tooltips for Guided Assistance

I’ve been working with my other software (Oracle Siebel CRM) recently. As many of you know I’m the co-Founder also of https://siebelhub.com. I don’t contribute much to it these days as The OPA Hub Website takes up a fair amount of time, but I work with Siebel on a regular basis. One of the big things at the Siebel Hub is my colleague Alex, who has really become the star of the Siebel blogging world. He is also a great JavaScript guy, and Siebel has a big jQuery layer of extensible code which configurators can use to squeeze new User Experience out of the CRM. It’s like Interview Extensions on steroids – we can do all sorts of things with it that we would not do in Oracle Intelligent Advisor. And I was looking at tooltips and guidance implemented with  a library called intro.js

So, why am I mentioning it? Because the other day, I happened to be working on Siebel with this JavaScript library and I thought it would look great implemented in Oracle Intelligent Advisor. What does it do? Basically it provides a set of easy to use methods to create Hints (which you can think of as Tooltips) and also to create guided Introduction sequences. You can think of them as kind of animated introductions to your Interview. They have lots of features, and these are only two of the most obvious. Before we get into the details, let’s see what they look like in a short video.

video

Two Styles of intro.js

Basically it tries to demonstrate two different approaches  to working with intro.js – the targeted approach where the tooltips and so on are called up by the user, on demand, and the second one where the contents autoloads. Both are valid approaches and the autoload demonstrates the use of a JSON object – in this case an external file is not used but that opens up interesting concepts like storing the tooltips in an Excel Sheet in the Project and using our ability to access that data in the Interview.

intro.js with tooltips for Intelligent Advisor

Anyway, I hope you find it useful and you can get it in the OPA Hub Shop as usual. If you are interested in learning more about IntroJS, this book is on our current reading list. Note that this link is an affiliate link, purchases made from this link give a small amount to the OPA Hub Website which is used to pay for hosting the website.

Input Extensions – DropDowns

Input Extensions – DropDowns

Many thanks to long-time reader and Oracle Policy Automation expert Aaron H for an interesting discussion just before the holiday season. The subject for this post is as you can tell, Input extensions that are dropdowns.

Our conversation was wide ranging but this article will focus on a couple of things

  1. How to ensure that the extension displays and handles the text and value elements of your list of values
  2. How to handle <uncertain>
  3. How to have a different label for <uncertain>

Along the way this will give us the opportunity to understand the usefulness of the Value() function in a rule. In later posts in this series we will look at other forms of Input, also beyond the basic INPUT or SELECT tag.

So let’s begin with a simple setup – the Zip Archive is available in the OPA Hub Shop by the way) – starting with just one attribute and a Value List:

The initial Value List looks like this – notice that in this case, there is no distinction between the displayed text and the actual value:

Input Extensions - DropDowns

In this simple scenario, one often sees extension code that focuses on the control.getOptions() method and then iterating through the various options from the returned object, something a little like this (for educational purposes only) :

var myChoices = control.getOptions();
for (key in myChoices) {
if(myChoices[key].value)
{
$('#xDropDown').append('<option value="' + myChoices[key]..text.toString() + '">' + myChoices[key].text.toString() + '</option>');
}
}

The code works fine, but makes a few assumptions that can come back and bite later when the rule designer has made changes.

  1. It does not take into account the possible change in the Value List to include display and values.
  2. It will error out if the dropdown becomes non-mandatory since “uncertain” will cause .toString() to fail

So when the developer of the rule changes the Value List to something like this:

Input Extensions - DropDowns 2

The code would no longer be useful since the [key].text and [key].value are two different values. And if we are updating the underlying control, we need to remember that we call control.setValue() so it is expecting a value, not the display value.

So maybe the code gets changed to :

 $('#xDropDown').append('<option value="' + myChoices[key].value + '">' + myChoices[key].text.toString() + '</option>');

Or something similar. All is well and the dropdown is correctly handling things :

Until the attribute is defined as non-mandatory in the Screen where it is displayed. At which point the code breaks again, as the uncertain value is not handled. Let’s take a step back and remind ourselves of the defaut behaviour in these situations, without an extension:

The standard control renders an empty option for the uncertain value. I’ve always considered it a bit of a bug that it does not respect the optional Display Value (in my case, “Not Sure” – see the screenshot a little further up) but always renders a blank, at least in all my browsers.

So in the case of an Input Extension that needs to render the uncertain value (because the control is not mandatory) and also wants to show the Display Value, the code will probably need to have another quick change:

Input Extensions DropDown Code Last One

Which will ensure that the dropdown is rendered with the correct text and value, if there is one. Also, the added bonus is that the display text of uncertain is rendered as we would expect:

Input Extension Showing Uncertain Vlaue

And finally, we have good use of the Value() function so that we can be sure that the correct value is stored in our Interview:

The function above, in a Word document for example, will return the value rather than the display value. So you can drop it onto the final Screen of your testing Project and compare the two to make sure you have gathered the correct one.

Hopefully that was fun and interesting, and you can practice yourself with your own project. As mentioned above the code is available in the OPA Hub Shop. The documentation is of course on the Oracle Intelligent Advisor website.

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

New JavaScript Extensions Book – Win!

New JavaScript Extensions Book – Win!

We’re pleased to say that our new Oracle Policy Automation JavaScript Extensions book is almost finished. Designed to be useful for anyone looking to understand how to extend their interviews with JavaScript Extensions. It comes with 50 worked examples and Zip Archives to download. It was written to help non-programming Oracle Policy Automation professionals understand what is possible. Professional Programmers can use the examples and extend them / adapt them according to their needs.

Win a Free Copy!

The new book is part of our program of providing useful resources and assistance to the community. Help the OPA Hub Website by filling in the survey below and enter to win a copy of the book and the free goodies we mentioned. We’re attempting to broaden our offerings to include various forms of training and assistance, and your feedback helps us a great deal. We want to serve the community in the best way possible and this is part of that process, learning about your needs.

If you enter the very short survey below and complete the last question, you will be entered into the draw – one person will be drawn at random and will win a copy of the book as soon as it is published (in the New Year 2020) and a bunch of other goodies – a baseball cap, a tee-shirt, a pen and a mug! So what are you waiting for? If for whatever reason, the embedded survey below does not work, you can also access it here.

Enter the Survey to win!

Create your own user feedback survey

Back to Basics : Extensions #2

Back to Basics : Extensions #2

Following on from the previous post, we delve more deeply into the JavaScript Extensions world.

Interview Execution in a Browser

So how does an Interview Extension work? Let’s begin with some basic information about how your Oracle Policy Automation Interview runs in your Browser. If you happened to be viewing an Interview right now, and you were to open the Console (F12 in Google Chrome, or Microsoft Edge. Check your Browser documentation for the equivalent key or menu option), you might be able to view something like the following screenshot. Check the steps under the image as you may need to refer to them in your own case.

Extensions

In this screenshot I have launched a Project using the Debugger. Remember that if you hold down F5 while clicking the Debug button, you will open the Interview in the Debugger and in your default Browser.

  1. Your web server may of course be a different address.
  2. The web-determinations folder will not have the same numeric suffix as in this screenshot, indeed will most likely not have a suffix at all. This is a feature of the Debugging session.
  3. The js file is most likely in the staticresource folder, however if you are in a more integrated environment it may be in a different subfolder, or a different folder altogether. But it will be present.
  4. The contents of the js file can be read more easily by selecting (in Google Chrome in this case) the option to pretty print the code.

Interviews.js

This file is the foundation of the Interview experience provided by Oracle Policy Automation. It contains all the code necessary to make the user experience function correctly. Inside this file, however, there is a built-in capacity to accept extensions that change the behaviour of the Interview.

In your Console, search in the file for the following text – “customLabel:” (without the quotation marks, but with the colon). You should find one instance of that text, as shown in the screenshot below.

Extensions

  1. Search for the text
  2. Find the text in the file.

Take a moment to perform a second search in the same file, for the text shown below. Use the screenshot as your guide.

  1. Ensure you are looking at interviews.js
  2. Search for this text
  3. View the style definition for textInputStyle.

Accepted Extension Types

Notice in the first example, that customLabel is only one of a series of items in the first list. These are the recognized types of Control extension that we, as Oracle Policy Automation Project workers, are permitted to develop.

In the second search you found that there was a style defined for controls called textInputs. Although not quite as obvious perhaps as the first example, an Oracle Policy Automation Project might want to override the Style(s) used in a Project, in order to comply with corporate guidelines for example: and this system will help us do just that. Style Extensions use keywords in the same way to indicate which elements you wish to style.

About Extensions

It is not important at this stage to understand how these extensions are created or used. It is, however fundamentally important to understand that you will be extending Oracle Policy Automation Interviews by adding one or more of these acceptable extensions, and that they will be run in the browser in the same way as the standard JavaScript is already. These interviews can then be better adapted to your IT environment. As an example, read how Styling Extensions enable integration visually with Oracle Content & Experience Cloud.

More on this subject, with some worked examples, shortly.

Business Process : Force PDF Download in OPA

Business Process : Force PDF Download in OPA

After last week’s business case put forward about Calendar Blackout dates, this week we can thank Arnaud D and Sylvie A from Switzerland for their excellent use case. This week we will be discussing the idea of letting a user progress to the end of an Interview, only if they have clicked to download a PDF file. So, can we Force PDF Download in OPA?

There is not really a technically feasible way to prove that a user actually read a PDF. But if we can force them to download it, at least we know they have gotten that far!

The business case is easily applied to many industries, for example where terms and conditions or a contract are generated and tacit approval needed before the user continues. Or, more simply, you need some way to encourage people to read the BI Publisher thing you spent all those weeks developing.

It is also a good opportunity for us to investigate the new custom Button extensions available in Oracle Policy Automation 19A. So this won’t work in 18D or earlier. The approach we are going to take will involve two different extensions. Let’s go!

Input Extension

The input extension is needed because we wish to capture the value of a Boolean attribute for our demonstration. Navigating forwards will only be allowed if this Boolean is true.

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
/*  Generated by the OPA Hub Website 29/03/2019 19:35
Educational Example of Custom Input Extension for Oracle Policy Automation
I will remember this is for demonstration purposes only.
 */
OraclePolicyAutomation.AddExtension({
	fullCustomInput: function (control, interview) {
		if (control.getProperty("name") == "xInput") {
			return {
				mount: function (el) {
					console.log("Starting customInput Mount");
					var div = document.createElement("input");
					div.id = "myInput";
					div.value = interview.getValue("my_flag");
div.setAttribute("style", "visibility: hidden");
el.appendChild(div);
console.log("Ending customInput Mount");
$(".opa-document").click(function(){
console.log("Clicked Link");
control.setValue(true);
});
},
update: function (el) {
console.log("Starting customInput Update");
 
console.log("Ending customInput Update");
},
validate: function (el) {
console.log("Starting customInput Validate");
 
console.log("Ending customInput Validate");
},
unmount: function (el) {
if (control.getProperty("name") == "xInput") {
console.log("Starting customInput UnMount");
 
interview.setInputValue("my_flag", $("#myInput").val());
var myInput = document.getElementById("myInput");
myInput.parentNode.removeChild(myInput);
console.log("Ending customInput UnMount");
}
}
}
}
}
})

So what’s going on here? Basically we are putting a click event on a PDF download. Notice line 17 : this adds a click event.  So as soon as the custom Input is loaded, an event is added to another control using jQuery. When the link is clicked, we update our flag to true. We use the style selector “.opa-document” to find the link. If you have more than one link, you will need a more precise method of finding it. Other than that, the custom input itself is invisible, and simply reads the current value of the flag into the control.

Button Extension

The other extension, for a nextButton, uses jQuery to display a dialog when you try and leave the screen before clicking on the link (the flag is false). Otherwise it lets you navigate without issues.

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
OraclePolicyAutomation.AddExtension({
	customNextButton: function (interview) {
		let button,
		caption;
 
		return {
			mount: function (el) {
				const screen = interview.currentScreen();
 
				button = document.createElement("div");
				button.setAttribute("title", "Print Your PDF First");
				button.setAttribute("class", "opa-back");
				button.setAttribute("role", "button");
				button.setAttribute("style", "	text-overflow: ellipsis; white-space: nowrap; line-height: 1; cursor: pointer; padding: 0.7em 1.2em; border: none; text-decoration: none; -webkit-appearance: none; flex: 0 0 auto; text-align: center; color: rgb(255, 255, 255); border-radius: 6px; background: rgb(20, 116, 191); font-size: 16px; font-style: normal; font-weight: normal; margin-left: 6px; visibility: visible; outline: none;");
 
				caption = document.createTextNode(screen.getNextCaption());
				button.appendChild(caption);
 
				var dialog = document.createElement("div");
				dialog.setAttribute("id", "dialog");
				dialog.setAttribute("title", "Warning");
 
				var dialogtext = "Please download your document first.";
				el.appendChild(dialog);
 
 
				button.onclick = function (evt) {
					evt.preventDefault();
					if (interview.getValue("my_flag") == true) {
						interview.submit();
					} else {
						$("#dialog").dialog();
						$("#dialog").text(dialogtext);
						console.log("Not Yet ");
						console.log("Flag Value is " + $("#myInput").val());
					}
				}
 
				el.appendChild(button);
			},
			update: function (el) {
				const screen = interview.currentScreen();
 
				caption.textContent = screen.getNextCaption();
 
				if (screen.hasNextButton())
					button.removeAttribute("disabled");
				else
					button.setAttribute("disabled", "disabled");
			},
			unmount: function (el) {}
		}
	}
});

So what’s happening? The early part of the mount is simply painting a nice button on the screen. We could of course not display the button at all, but the User Experience might be better if we display it always, just prohibit using it in certain circumstances. Starting around line 19 we create the dialog and make it ready for use.

Line 27 includes the button click routine which either displays the dialog, complete with a message, or just lets you navigate as normal.

The other lines in the unmount are just taken from the example in the online help, where if this is the last Screen, no next button is available. But our business case is based on it not being the last.

Force PDF Download : Animated GIF

In the animated GIF above, you can see what it looks like.

The Project Zip is in the shop. Have fun!

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.

Dynamic Charts in Container Controls

Dynamic Charts in Container Controls

As many of you know, we try to provide JavaScript Extension examples for you to experiment with in your own Projects. A while ago, we looked at an interesting question about using Dynamic Charts in Container Controls – basically to display a chart (which is the sort of thing it was designed for),  but to also include the capability to update the chart live as the data is edited on the same Screen. A quick animation will give you the idea:

The example is an interesting one, on account of it allowing us to talk about the difference between viewing data that exists already, versus using data that has just been created.

To keep it simple, we can think of server-side data and client-side data. Server-side data, for example used in Container Controls, represents the data that has been collected in a previous Screen. It’s already been seen by the determination server.

Client-side data is information that has been entered on the current Screen before the Next button has been clicked. So as far as the determination process is concerned it is “not quite there yet”.

The same concepts exist in the world of JavaScript extensions. For example, consider the difference between these two keys : mount and update from the updated  Container Control example that is available here.

 return {
                mount: function(el) {
                    console.log("Starting customContainer Mount");
                    if (document.readyState == 'complete') {
                        var rows = [];
                        for (i = 0; i &lt; entities.length; i++) {
                            entity = entities[i];
                            if (entity.entityId === "payment") {
                                break;
                            }
                        }
                        rows = entity.instances;
                        var myFlatList = [];
                        var myObject;
                        var width = 300,
                            height = 300,
                            radius = 150,
                            color = d3.scaleOrdinal(d3.schemeCategory10);
                        var size = rows.length;
                        for (i = 1; i &lt; size + 1; i++) {
                            myObject = new Object();
                            myObject.label = control._source.screen.serverState.payment['the payment' + i].payment;
                            myObject.value = control._source.screen.serverState.payment['the payment' + i].amount;
                            myFlatList.push(myObject);
                        }
                        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;
                            });
                        console.log("Ending customContainer Update");
                    }
                    console.log("Ending customContainer Mount");
                },
                update: function(el) {
                    console.log("Starting customContainer Update");
                    var myFlatList = [];
                    var myObject;
                    entity = control._source.publicInterface._source.screen.clientState.payment;
                    var rows = [];
                    rows = entity;
                    for (var key in entity) {
                        if (!rows.hasOwnProperty(key)) continue;
                        var obj = rows[key];
                        for (var prop in obj) {
                            if (!obj.hasOwnProperty(prop)) continue;
                            myObject = new Object();
                            myObject.label = obj.payment;
                            myObject.value = obj.amount;
                        }
                        if (myObject.label != "") {
                            myFlatList.push(myObject);
                        }
                    }
                    var width = 300,
                        height = 300,
                        radius = 150,
                        color = d3.scaleOrdinal(d3.schemeCategory10);
                    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;
                        });
                    console.log("Ending customContainer Update");
                },

Notice that the mount key uses the server-side data : when we draw the Control, we can only draw what is already available to us. But in the update, we want to take into account the new data entered by the user, so we look for the client-side data. So now we can be more specific : you can access clientstate and serverstate information which can help you work with previously entered or just-entered dynamic information.

Hope you have fun building your Dynamic Charts in Container Controls! We’ve updated the Shop example to include the complete Zip File.

Dynamic Charts in Container Controls Example Image

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.

Worldwide
Logo by Southpaw Projects LLC