X

Technical Articles relating to Oracle Development Tools and Frameworks

  • October 11, 2019

Ensuring Unique User Input in Oracle Visual Builder

Duncan Mills
Architect

A common requirement in most systems is the ability to ensure uniqueness of some attributes of a data record. Most of the time this will apply to record keys that perhaps managed behind the scenes either by your REST backend or by the built-in Business Objects layer in Visual Builder.  However, on some occasions we may want to apply similar uniqueness constraints to user-entered information as well.  In this article I'll be looking at both the declarative way your can do that when using Visual Builder Business Objects and then a more advanced technique can can be applied no matter what the backend is.

Uniqueness Checking for Free

When using Business Objects in Visual Builder, one of the basic declarative features of the field definitions is the ability to apply a uniqueness check at the database level. You simply do this by checking the Unique Constraint when defining the field. 

Screen shot of the Visual Builder field definition panel showing the highlighted unique option

Job done, the power of the Oracle Database kicks in and no more duplicates! If you now create a default UI to create a record in that Business Object you'll simply not be able to create a duplicate. However, there's a catch (of course or this would be a short article). In order for the user to see that there was a problem with their data they have to send the record to the server at which point that whole process will fail and the user will have to amend the data and try again. 

 What I'l like to do is have a validator that tells me there's a problem with the value that I've entered, before I try and save the record. And that's what this article is really all about. 

Asynchronous Validation to the Rescue

I've used the word asynchronous here and that's important.  By definition, in order to check that a value is truly unique we will need to go back and ask the server, and a trip back to the server is always going to be asynchronous. The Oracle JET toolkit, on which Visual Builder is based, just so happens to support asynchronous validation, so let's see what that looks like and how it can be applied in this situation. 

Here's my starting point, I have a simple UI for registering band members and I want to ensure that whatever name is entered for the player is not already used by someone else in the band - just to prevent confusion in the fan-base. 

The Player Name is an <oj-input-text> with the required attribute already set.  To add the validation I'm also going to set the async-validators attribute of the tag.  In Visual Builder you will find this on the All tab of the property inspector.  This property of the component takes an array of zero or more validators that are crafted in a slightly special way using JavaScript promises in order to produce the asynchronous background checking.  Unlike the normal validators array on input-text we can't define everything in-line in the property, just because we're going to write our own validator here, rather than using one of the pre canned validations. So, before we set that async-validators attribute on the tag, we'll need to write some code in the page module to create the validator in the first place. 

About Asynchronous Validators

Asynchronous validators in Oracle JET have pretty much the same API as normal synchronous validators except that the the API calls have to return JavaScript Promises which, when resolved (or rejected) will determine the validation state.  The API consists of one function and an optional property:

validate method - this is called with the value that needs to be checked. If the promise that it returns is resolved then the validation has passed, if it rejects then the vaidation has failed and the payload of the reject call is the error message to display to the user. 

hint property (Optional) - this hint is added as help to the UI to inform the user about the validation in question. Again this requires a promise to be returned and the resolved payload  must will be the hint text.  Because this hint message is also asynchronous you can use calls to the server to generate that as well (for example to tell the user up-front that certain options will not be available). 

A Basic Asynchronous Validator

So let's start with the most basic implementation of such a validator that we can use as a testbed - I'll evolve this later on to actually talk to the server to do a real check. 

In the pageModule we need to create a function which will return an array of these Asynchronous Validators. For clarity I've split this into two functions, one (ayncValidatorsForPlayerName) to create the array to pass to the <oj-input-text> and a second (createNameCheckValidator) to create the actual validator.  In principle we can attach multiple asynchronous validators by adding more entries to the array returned by ayncValidatorsForPlayerName.


define([], function() {
 'use strict';
 var PageModule = function PageModule() {};
  
 PageModule.prototype.ayncValidatorsForPlayerName = function(){
  var validatorArray = [];
  validatorArray.push(this.createNameCheckValidator());
  return validatorArray;
 }
  
  
 PageModule.prototype.createNameCheckValidator = function(){
  return  {
   validate: function(value){
    return new Promise(function(resolve, reject){
     setTimeout(function() {
      if (value === 'Duncan'){
       resolve();
      }
      else {
       reject({detail:"The fans won't like "+ value});
      }
     }, 1000);
    }
   );
  },
  hint: Promise.resolve('Choose a name that the fans can identify with')               
 };
};
return PageModule;
});

This first example only has a "fake" validation using a time delay of 1 second to prove the principle that it a) works and b) is asynchronous. To break down the code a little let's look at the createNameCheckValidator function.  This just returns an object with two attributes; The validate function itself  which as you can see checks that the entered name just matches a particular value and if so calls resolve(), if not it calls reject() whilst passing an object containing a detail attribute with the required error message. The second (hint) attribute does not need to be asynchronous in this case although it still needs to be a promise, so we use the shortcut of just create a pre-resolved promise with the message we want to pass as the hint text.

So let's wire that up to the input field:


<oj-input-text async-validators="[[ $page.functions.ayncValidatorsForPlayerName() ]]" 
               id="oj-input-text-2091532825-1" 
               label-hint="Player Name" 
               required="true" 
               value="{{ $page.variables.player.playerName }}">
</oj-input-text>

So you can see here that I'm calling the ayncValidatorsForPlayerName function directly to grab the array of asynchronous validators. You have to type this expression manually into the PI field or code view as the property inspector does not provide a picker for functions -  so be careful and remember to include the parentheses to ensure that the function is actually executed as opposed to passed as a reference.

Once wired up, we can test it. Here I've entered an "Invalid" value and we can also see the effect of the hint:

Screenshot of the validator showing the error message and hint

Calling a REST Function from Your Asynchronous Validator

Now that we have wired up and tested the basic principle let's move on to doing something realistic that involves calling a RESTful backend to check the proposed value against the list of names that have been registered already.  This uses a technique that I've discussed before in a different context: the REST Helper API. This API is the same one that is used in Action Chains to make REST calls and we can use it directly from our validator code.  Here's the amended version of the Page Module which shows this. 


define(['vb/helpers/rest'], function(Rest) {
  'use strict';

  var PageModule = function PageModule() {};
  
  PageModule.prototype.ayncValidatorsForPlayerName = function(){
    var validatorArray = [];
    validatorArray.push(this.createNameCheckValidator());
    return validatorArray;
  }
  
  
  PageModule.prototype.createNameCheckValidator = function(){
   return  {
    validate: function(value){
     return new Promise(function(resolve, reject){
      var endpoint = Rest.get('businessObjects/getall_Player');
       endpoint.requestTransformationOptions({
        filter: {criteria: [{
         attribute: 'playerName',
         op: '$eq',
         value: value}]}});
       var resultCallback = endpoint.fetch();
       resultCallback.then(function(payload){
        if (payload.response.status === 200){
         if (payload.body.count > 0){
          reject({summary:'Sorry cannot use ' + value,
                  detail:'This name is already in use, try another'});
         }
         else {
          resolve();
         }
        }
       });
      }
     );
    },
    hint: Promise.resolve('Choose a name that the fans can identify with')               
   };
  };

  return PageModule;
});

To examine the differences:

  1. The define() block now imports the vb/helpers/rest class under the alias of Rest.  This is what allows us to make calls to defined service endpoints
  2. Inside the Promise creates for the validate function we use Rest.get() to get a reference to the endpoint we want to query (this must already be mapped as a service or Business Object in Visual Builder, you can't use a URL here directly).  This approach looks up the correct endpoint information for you and takes care of applying any authentication if required.
  3. I setup the filter that I want on the endpoint.  How you do this will vary depending on the service, but in my case I'm using a Business Object so I can express this in exactly the same way as I would in an action chain or and Service Data Provider definition. In this example, to check for uniqueness I'm asking for any rows that match the requested value already. 
  4. Next we call the fetch() function on the endpoint which makes the asynchronous call and provides ne with a promise to wait on for the result.
  5. In the then() function of the callback promise we can check the actual results. In this case, if there is more than zero instances of the value then that shows that this would be a duplicate and so we call reject
  6. Notice that the reject call in this case is passing a summary as well as a detail value to provide an improved error message.

With this new version, here's the result: 

Hopefully you'll find this pattern to be useful.  If you are using Business Objects as your backend then I'd still recommend switching on the field level unique flag to ensure data consistency in cases where multiple users make simultaneous changes.

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.