Tuesday, December 6, 2016

Using JSON and Javascript to show or hide list menu entries

Using JSON and Javascript to show or hide list menu entries 

Here's a post that is the result of great team work. Proving once again a point that I believe in very strongly: so few great ideas come from the hand of just one person!

A colleague and I started brainstorming on this for a very specific need for a very specific customer. But our solution is really very flexible and re-usable for many other things! Read on, believe me, it get's interesting! ;-)

The technology

We're gonna use PL/SQL (of course), Javascript, APEX API's and a JSON structure.

What's our goal

We were looking for a flexible way to show or hide menu list entries. 

You see we have a dashboard alike application that does nothing else than kicking off other APEX applications. The complete set of applications is kind of a "Suite".
Now depending on which role you have in the organization you can see different list entries.
All very much feasible with the normal conditions of course.

But this "Suite" is growing and with the team working hard, a lot of applications are being build as others are ready to go in the Test or Acceptance cycle, others are ready for Production. But others are still "under construction" and aren't allowed to be installed in other environments.

The solution in words

So we wanted to have this double validation in a flexible way. The information - or setup if you want - needed to stay in the APEX application. We wanted to avoid storing it in a custom table, because then we would have the extra difficulty to maintain this "setup table" on al the different environments.

So we had a good close look at the application.
We also had a good close look at the List Entry screen in APEX and finally we browsed through the APEX API's.

The solution in technology

Administrator stuff

All the following points are to be done once, by an administrator. So of course, that part of the APEX application Suite is protected and only administrators can do it!


We're using a table to store the environment information. This will be a pretty stable situation.
In my neck of the woods, we often talk about the "OTAP" street. In the English speaking world, I think only the "O" needs explanation. 
The "O" stands for "Ontwikkeling", which is Dutch for "Developtment".
"T" = "Test";
"A" = "Acceptance";
"P" = "Production".

Our table looks like:



And a really simple APEX page to handle this setup information:




Then it gets a little more complicated. Or is that the wrong word? It should probably sound nicer when I say "interesting" ...

This overview page shows all the lists that we have identified:



And this information is fetched from the APEX repository. Have a look at the query behind the scenes.

We're gathering all the lists on the pages we've identified in the different applications.



And here it's important to picture that each and every menu groups some (Self Service) tasks to which you either have access to or you don't ...

Let's take a closer look at one single Menu, the one for "My Employees".
This is typically a menu that only is visible for managers ....




And now we land in a page that needs some explanation, I believe:




It's probably (hopefully) still clear that this is an overview of all the list entries of that List called "mijn_emp_en".
So we show the list entry name. Followed by the check icon if there is an APEX condition entered for that entry. In our example almost all entries have a condition.

Next are the four environments: O - T - A - P. It's simple enough: 'Yes' means that the list entry is to be displayed. Or in other words: this functionality is installed in that environment.
Because it's not sure yet it can be displayed according to the rules of role management!
That's the last column: "Enabled for these role codes".

Our roles are coded in a table as well. E.g. 'ADM' for 'Administrator' and 'DIR' for 'Director' ....

If you have a look at the screenshot above, you see e.g. that the functionality 'Leave cards' is only available on Development ('O'). And (e.g.) the 'Team occupation overview' is also available on 'T'. Nothing has been installed on 'A' or 'P' yet.
And all entries are allowed for that set of roles identiefied in the last column.


Now it's time to zoom in on one entry. And in the screenshot I changed the list of roles, to make it more clear:




First the condition in APEX is shown. Because this condition will also be checked upon. But in our exercise it was the idea to replace those conditions by this flexible mechanism.
We then chose the environments where the application can be used (remember - almost - every list entry navigates to another APEX application).

The functionality may not be shown for the roles 'CTRL' and 'EMP'. All other roles will be able to access it.


Behind the scenes, beyong the obvious

I told you, we're not storing the setup in a table, but directly in the APEX application.
The Apply changes button kicks off the process 'Save values for Envs and Roles'.
Here the two types of validations are seperated. Because we store them in the attribute values of the entry itself. 

This code was a bit of work. Figuring it all out. Therefor step by step:
  1. Gather all current values.
    A very important step, because we learned the API overwrites everything with null values if you pass nothing.
  2. A JSON structure is used.
    Simple: we needed "a" structure and we believe that JSON was the best way.
    One JSON for ENTRY_ATTRIBUTE_09 to store the environment Yes/No information.
    Another JSON for ENTRY_ATTRIBUTE10 to store the role code information.
    Here we used the apex_json package.
  3. Now we need to set the API mode to 'REPLACE'.
    To make sure we can put the mode back to the original value, first the original value is stored:
    l_old_api_mode := wwv_flow_api.g_mode;
    wwv_flow_api.g_mode := 'REPLACE';
  4. Next the API. It's is called: wwv_flow_api.create_list_entry
    See screenshot below, where most important parameters are highlighted.
  5. Set API mode back to it's original, which is reversing what we did in step 3.


Results in the List in APEX

To see the results, have a look at the Shared Component List


And we pick the "Team occupation overview"



And just to show the condition, always returning "false", obviously a test case ...




But the real interesting stuff is in the attributes:



Now this information is in this application's definition. And it can be moved to any other environment. It's not important that the functionality behind this list entry is in fact installed on that environment. Because it will only come into play when the environment specific setting is set to 'Yes'.


And at run time

Now we have all the setup available. We have all the information in place to decide what to display for whom and what not to display at all.

So let me first show two screenshots of the same application.
First one is the application seen by the eyes of an administrator (remember the 'ADM' as role).
Second one is by the eyes of a supervisor (remember the 'SV' as role).

Administrator









Supervisor



To make this happen, we needed a Post-Rendering process on the page showing all the List regions:




It looks like a very simple process. But the complexity is hidden in the PL/SQL:




The PL/SQL code is too large to display here, but upon request (I guess) we're willing to share also that. However in short, this is what it does:


  1. Initialise some code:
  2. Check the DB we're living on:
  3. Loop through all regions, as some lists are defined in different applications, the query got quite large, but we're joining apex_application_page_regions and apex_application_list_entries
  4. Read both JSONs and look for the corresponding Y/N values
  5. We then check if we're at the end of a list, because if a list is completely empty, we also remove the region so you don't get a list region with no list entries in it!
  6. Check on the environment, because when the environment says no, we don't need to check the role condition, do we?
  7. Same check for the role
  8. Construct the javascript to remove the disabled list entries

Now, all this is executed as a JAVA script after having rendered the page. This means that an APEX condition is on a higher level than this validation. Meaning that if the APEX condition e.g. says 'Never' ... it really will be 'Never' and it will not display, no matter what comes out this Post-Rendering process.


Here we are ... a long explanation, I know. I tried several times to make it shorter, but then I believe the it was not possible to follow the route anymore. Well, I hope now it was possible to follow what we did. If it wasn't and you want to know more, I'm sure you'll find a way to get in thouch with me.

Also I want to thank again my colleague and partner in crime, Joost, for making this all possible!

Remarkable final thouch: we have some pretty good DJ's in Belgium: thanks Regi and Dimitri Vegas & Like Mike for the inspiring music while writing this all down .....



Happy to share ...
















Wednesday, November 30, 2016

My advice is to use the Advisor

I have noticed (too) often when talking to other APEX developers, that the Advisor functionality is not well known.

Although this is also described in a lot of Best practice  guides, the functionality is in my humble opinion highly underestimated!

The APEX Advisor

I actually don't know how long it's been there, but it is quite some time.
And it's all standard and included in the APEX Application Builder.
It is to be found under Utilities.




It kicks off following popup window



I recommend to have a look at the section Checks to perform:



It may well be possible that you have opted for whatever reason to not follow one of the pre-defined rules of the APEX Development team. You can chose not to check on those rules.

Usually I advice to select all checks and read the advice of the Advisor. You can always opt of course not to do anything with one of the remarks. Because one thing must be clear: your app can run very fine with several Advisor errors or warnings in it. You do not need to fix them all. But it is advised to solve as many as possible and you may learn something from the best practises according to the APEX Development Team. They're smart people ... believe me ;-)

Depending how large your app is - of course - running the Advisor may take some time.
In the end it provides you with a clear overview and with the option to navigate directly to where you need to be to fix the problem.

The result filter


The filter comes in very handy to keep an overview or to focus on one specific type of error (or warning).

An advisor error



I'm not walking you through every possible error an Advisor can identify. 
For each error/warning the Advisor will give you an overview of what he thinks is wrong or can be done better.
With the View button you navigate directly to the place in your application that concerns this hit.

He doesn't know everything

An example of errors an Advisor run is always indicating is the following:


What the Advisor cannot see here, is that this "EVENT_LINK" is part of a specific template. It's not necessarily a real error. So these errors can be ignored.




So okay, here you go. Hope to have convinced some developers to start using it. It's really a good practice to run it at the end (or beginning if you prefer) of every working day!


Happy to share ....

Tuesday, November 22, 2016

Interaction between two date fields made visible upon data entry

Interaction between two date fields

Have you ever been in a situation where you have two date fields and the second one is dependant on the first one? Sure you have!
Every time you do something with a start- and end-date ...

It's so very logic that the end-date must be later than the start-date. As long as we can't travel in time, this is a certainty.

Example

As often, my examples originate from the HR field.
This example is a page in an APEX app that gathers information for a candidate employee.



The candidate category was identified as 'Fixed Term', which implies you have to provide a start-date and an end-date for the contract.
We're gonna focus now on the Date start and on the Date end.

In the example used, the Date start must be a date in the future. The Date end must be later then the Date start.

When you do nothing

It's obvious, when you do nothing, you allow input of all dates.

I'm writing this blog post on November 21st. You can see I'm allowed to enter a date in the past for a Date start:



Same thing for the Date end. Even worse: I'm allowed to enter (pick) a date before the Date start:



Which results possibly in something like this:


What about validations?

Yes, of course, you can use validations to force the user in entering the correct dates. But those validations are executed on page submit. That's a bit too late, don't you think? So read on, it gets interesting now!


Known and standard features of APEX


To start of, we're gonna use a standard APEX feature to make sure the Date start is a future date.
A small notice here: a "future date" in a data entry application may not be clear enough as a spec. Because when reading "future", it will be checked against the system date. So when entering a date today, save the information and update it tomorrow, this system date has obviously changed. So make sure you get the intention of your customer clear! In our example we simplify and acknowledge that we're validating purely against the system date.
First identify the Date start:



Go to the property panel and have a look at the Settings section:


Three options here: Highlighted date, Minimum date and Maximum date. They all operate similarly. Actually the APEX Help explains it quite clearly:


We're now making sure that we highlight the current date (sysdate +0 days).
Same for the minimum date. It will make sure that you cannot pick a date in the past!
Yes!! One down.

We're now at this stage:



You're unable to pick a date in the past!

Also note some other interesting properties for datepicker fields, like e.g. the Show other months (Y/N) property. I suggest to try them out, but it's not the subject of this post ...
You can now do the same for the Date end. However, it will only enforce the user to pick a date in the future. What if the Date start is entered for December 1st (remember, the system date right now is November 21st)? You can still enter November 22nd for the Date end. Not what we want!

Full solution step by step

We're gonna use some javascript coding to make the relation between the Date start and the Date end
However it's been coded in a way that it can be any two date fields on the same page.

Page level functions

In the page properties, navigate to the JavaScript section.



Two functions need to be included here:

function stringToDate(_date,_format,_delimiter) {
    var formatLowerCase=_format.toLowerCase();
    var formatItems=formatLowerCase.split(_delimiter);
    var dateItems=_date.split(_delimiter);
    var dayIndex=formatItems.indexOf("dd");
    var monthIndex=formatItems.indexOf("mm");
    var yearIndex=formatItems.indexOf("yyyy");
    var month=parseInt(dateItems[monthIndex]);
    month-=1;
    var formatedDate = new Date(dateItems[yearIndex],month,dateItems[dayIndex]);
    return formatedDate;
}

function setMinAndMaxDates( fieldnameStartDate, fieldnameEndDate, maximumLengthInYears) {
    var minDateAsString = $x(fieldnameStartDate).value;
    var selectorEndDate = apex.jQuery("#" + fieldnameEndDate);

    if (minDateAsString == null || minDateAsString == '') {
        selectorEndDate.datepicker("option", "disabled", true);
    }
    else {
        var minDate = stringToDate( minDateAsString, "dd-mm-yyyy", "-");
        
        // Determine maxDate by adding given amount of years to minDate:
        var maxDate = new Date(minDate);
        maxDate.setFullYear(maxDate.getFullYear() + maximumLengthInYears);

        selectorEndDate.datepicker("option", "minDate", minDate);
        selectorEndDate.datepicker("option", "maxDate", maxDate);
        selectorEndDate.datepicker("option", "disabled", false);
    }
}


The first function (stringToDate) will need to hold the date format that you use. Generally accepted in my neck of the woods is the format "dd-mm-yyyy". So you will need to adapt this to your own setting. Or when you allow the user to pick the date format in a preference page, you will need to make this function more flexible as well!

The second function (setMinAndMaxDates) will actually add the needed attributes to the page items on the fly.
As you can see, the function accepts 3 parameters. The fieldnameStartDate and fieldnameEndDate refer to the page items you want to refer to. This makes the use of these functions very flexible. And you can just copy and past it over to any other page in your app.

Dynamic action doing the magic

As so often is the case, here again the dynamic action will do the magic!
In the use case described here, "something" will need to happen to the Date end when a date is picked in the Date start.
Logically the On change dynamic action is used on the Date start.


Following properties are set on Dynamic action level:



And because the functions are coded generically, the javascript in this TRUE action remains extremely simple:


setMinAndMaxDates("P3_DATE_START", "P3_DATE_END", 10);


Results

This results in following behaviour:

Date start


As soon as you pick a date in the Date start datepicker, the Date end gets defaulted to the same date.

Date end


No dates can be picked before the Date start.
Here you go .... all done !!!

Oh no ... there is a "but" 

Yes, the user gets guided perfectly when using the datepicker. But he can still pick a date and then change it manually!


My conclusion

So it doesn't take away all your pains. But it does support your user in the best possible way. And because that user can still manage to make things wrong, you should also provide the validations on page submit.
But I hope you agree with me that it is a nice way to help the user with the data entry.

Credits

For this blogpost I must specifically thank my colleague Joost Hoevers, as he has coded the nice solution and made it perfectly re-usable! 

Thank you, 





Happe to share!





Tuesday, November 15, 2016

Did you know? Apps aliases must be unique cross workspace

... and Oracle isn't checking it for you!


Hi,

this is just a quick note. Seems logical, but YOU have to take care of it!

Recently I uploaded an APEX export file from an app that my colleague had created.
I just wanted to check something he had done, so I uploaded the application in my own workspace. That's a workspace I use to quickly check something or do some research on.
It is however on the same database that my colleague is using to develop this particular application. And his customer is actually looking at that environment as well during the development phase.

So I uploaded the the export file and installed the app, accepting all the defaults there are when you import and install an APEX app.
But what I didn't know, is that APEX is NOT changing the application alias. Something that is done when you import an app that got exported from the same workspace. I believe it adds the new application ID to the alias.

But since this app came from a different workspace, it nicely installed it with the existing alias, causing the original app to fail.

So I just wanted you to know ... I will never forget this. And you may learn from my mista ... well ... from my experience! ;-)

Happy to share!

Wednesday, April 6, 2016

Apex: Save interactive report as default when integrated with EBS

This is something I shared quite some time ago (back then it was APEX 4.x, but it's still valid) on another forum. But I got the question back lately. So, here I am back with the same message ....


An integrated Apex App with Oracle E-Business Suite (EBS) has an unfortunate side effect concerning Interactive Reports.

Some of the settings of such a report is easiest done when running that report as a developer and then save it as Default and Primary Report Setting. So everybody who runs the report gets to see it the way you - as developer - has saved it.

When you execute the Apex app from within the EBS, the user you're using to connect to Apex is inherrited from EBS and as such not an Apex Developer user. Consequence: you are not allowed to save your IR as Default setting!

The trick around this to logon to Apex builder (as developer obviously) and to the EBS with the same browser session.  Then launch your app via EBS.  So the app opens in the same browser as the one used by your Apex builder.

In your app you have now the oportunity to use the edit-bar (at the bottum: "Home" / "Application" / "Edit page" / .....) and ... most of all, you can in this session save your reports as default (and primary) setting.  Which is the solution of the problem.

Happy to share ...

Tuesday, February 23, 2016

Duplicate page submits cause double database entries

No more double entries, please


In many applications the result of a page submit is the creation of one or more lines in some database table(s).
When these processes become heavy and have to take a lot of business rules into account and that perhaps in combination with sometimes not optimal network trafic, it may happen that the user doesn't see that APEX is processing his stuff.
As a result he or she may become impatiant and start hitting that Save button over and over again ...

When this results in multiple occurance of the record in the database, this creates data inconsistency. Something that every developer is - quite rightfully - scared as hell for.

There are some ways to prevent this behaviour from happening.

This blogpost is about preventing those double entries. But while doing my research and in fact the winning tip for the solution was coming from my good friend and APEX mentor Dimitri, I found a very nice extra. 
Did you, just like me, wonder how the APEX Builder has this spinning wheel showing up when saving your changes? And could you, just like me, not figure out how that was done? Well ... just read on!

So here's my business case.
A reasonably complex APEX page has a couple of buttons:
  1. Cancel - needs no explanation
  2. Save - simply writes the entered date to several tables in the database
  3. Finalize - does the same things as "Save", but also starts an approval workflow process

In our case, this Finalize was the issue.
Kicking off that workflow requires some business logic, some validations, well, it's a process that wasn't done in milliseconds.
Nothing was done on the APEX page to prevent the page from submitting multiple times. And nothing was done to show the end user that a process was running.
The only thing: when the process was done, then a message was shown, something like "Well done, your request has been sent to your manager".

The first week after go live of the system, we noticed that our end users were much more impatient then our test users. Because they didn't immediately saw the result of the 'press on the button', they started hitting that button multiple times. And so submitting the page multiple times. And so creating the same record multiple times.

So let me show you in this very much simplified sample what was the first setup of the APEX application.

The old way

Nothing was done to prevent double entries at all.
Base table used is hr_employee.  And it contains some very basic personnel information.

Only 1 record in there before the exercise:


On this table a form with report was created.



So I start hitting this Apply Changes button like crazy and it's almost impossible to mimic the customer's case with this simplified example.
But believe me, it happened and let say Mr. McEnroe is created twice in the db.




An important setting to keep in mind in this example is on page level


This setting can allow or prevent the page to be resubmitted multiple times. This feature was called into place to prevent a page to be resubmitted e.g. after a page reload in the browser. Or when hitting the back and forward button of your browser.
So ... there is our first clue!

The button (Save and Apply Changes) are both performing a Page Submit.


One step up and prevent page from being reposted

So I could redo all the steps as described above, with this option set to "No - Prevent this page from being re-posted". But I'm not gonna, because you will not see the difference, except for this one single setting:

In most situations this setting will save you. Although, if you remember the customer case, this setting was already set to prevent the re-posting.
So our example wasn't about being reposted or not. It really was due to multiple submissions. It was like the process was launched a second time, before the first time had come to an end.
There are other reasons why we could assume this, because at some point in the process it was checked if a request could be approved and then some status was changed so the same person could never approve twice the same request.  Because this little piece of functionality was in there and it had never occurred during test cycles, we were pretty confident that we had built a safe system.

Until ... one week after go live the customer came back to us and said: "multiple occurrences of the same record exist, created within less than a second from one and another. Please help!!!"
And then, the main test user had a brilliant idea. She sat down behind her computer, opened the specific page, entered some data and pointed the button and started clicking it like mad.
And there it was ... not two or three, but at least nine or ten records were saved to the db. 

So we needed another solution. And when I ran out of ideas, I called a help line.

Disable the button after having pressed it

The solution is as easy as it is simple. Prevent the the button from being pressed more then once.
In example one and two, the process was launched upon page submit. The button basically submitting the page.

Compared to both examples above, the change is with the button definition. No longer use the Page Submit, but set its behaviour to Defined by Dynamic Action.


Note: in this example we need the Database Action. In many cases you will not need that, but you will launch your actions via a page process.

 Now create the dynamic action:



First DA True Action:
Disable the button, making sure it is NOT fired on Page Load (by default it will fire on Page Load).

Finally add a second DA True Action:
To stick to the original behaviour, it's quite simple: just perform a Page Submit and set the Request to the Button name (Create). This is important, otherwise the defined page process that was previously directly linked to the button, will not be fired.

For the Page Submit obviously the default is set that the action should not fire on Page Load.
And look what came with this solution for free!




Yup ... the spinning wheel. There is that very nice extra that I was talking about in the introduction.
From now on - until further notice - I believe this will be my standard way of building an APEX page that submits something to the DB. 
I will no longer submit the page directly from the button, but will have a dynamic action do this, after I have disabled the button.

Also when you stay on the same page after the process is done, you do not need to enable the button again. Your Page Load will do this for you.


For me this really was an eye opener. For so many years I was used to write as much as possible in page processes that got fired on Page Submit.
I even made a habit out of it to - when possible of course - only write something to the DB after a page submit.
And it never occurred to me to do this in another way than via the button behaviour "Submit Page".

As simple as it may seem and as logic as it in fact is ... I only discovered this recently. I don't know if the APEX Builder is using the same method, but it looks the same. And that's good enough for me ;-)


Happy to share ...