Update on 09-09-2013: shortly after I wrote this article, I created the APEX Live Validation plugin.

Something that I really miss in APEX development is out-of-the-box support for client-side form validation. Most web applications these days make use of live validations written in JavaScript to instantly inform the end user on incorrect input. This greatly improves the usability and performance of your system since you avoid the user from submitting obvious erroneous data to the server. Don’t get me wrong; client-side validation is not a substitute for server-side validation. It’s simply too easy for an end user to bypass the JavaScript-driven validations.

Creating server-side validations in Oracle APEX is simple and fast. Common validations (e.g. not null, is numeric, is a valid date, etc.) are implemented in a declarative way, while other more complex and custom validation logic can be written in SQL or PL/SQL code. Declarative support for client-side validation in APEX is nonexistent. So we’ll have to write all logic in JavaScript/jQuery code ourselves. Sounds like a lot of work, right? Well, not really. Read on and allow me to explain you how to build a structured and reusable client-side validation component.

Here’s how my end result looks like: demo.

1. Create a form on a table (or view)

In my example, I created a form based on the EMP table. Make sure to assign a static ID to the region that contains the page items and the region buttons that submit the page:

  • I gave the region an ID of empForm.
  • The button that updates an employee record has an ID of saveEmpBtn.

2. Determine what page items need validation

Go through every page item and think of what validation(s) should be performed. Meanwhile, properly specify all attributes per page item. For example, set Value Required to Yes for items that are required to fill in, or set the Display As attribute for numeric fields to Number Field. It’s important to do this because we’ll rely on these settings later on.

I decided to perform the following validations:

  • Name: text field – required
  • Department: select list – required
  • Hiredate: date picker – required and valid date
  • Salary: number field – required and positive non-decimal number
  • Commission: number field – valid number

3. Write your validation logic

Create a reusable routine for each type of validation that you have to perform. Such a routine typically accepts a single parameter and returns a boolean. We’ll use these functions to check whether or not an input value is valid.

function isEmpty(pValue) {
  var isEmpty = false;

  if ($.trim(pValue) === "") {
    isEmpty = true;
  }

  return isEmpty;
}

function isPositiveInteger(pValue) {
  // an integer is a number that can be written without a fractional or decimal component
  var isPositiveInteger = false;
  var positiveIntegerRegex = /^\d+$/;

  if (pValue.match(positiveIntegerRegex)) {
    isPositiveInteger = true;
  }

  return isPositiveInteger;
}

function isValidDate(pValue) {
  var isValidDate = false;
  // date format is DD/MM/YYYY
  var dateFormatRegex = new RegExp("^(3[01]|[12][0-9]|0?[1-9])/(1[0-2]|0?[1-9])/(?:[0-9]{2})?[0-9]{2}$");

  if (pValue.match(dateFormatRegex)) {
    // seems that the date format is correct, but can we parse the date to a date object?
    var dateArray = pValue.split("/");
    var year = parseInt(dateArray[2]);
    var month = parseInt(dateArray[1], 10);
    var day = parseInt(dateArray[0], 10);
    var date = new Date(year, month - 1, day);

    if (((date.getMonth() + 1) === month) && (date.getDate() === day) && (date.getFullYear() === year)) {
      isValidDate = true;
    }
  }

  return isValidDate;
}

Tip: There is plenty of code available online when it comes to validating forms with JavaScript. So take a peek at Google when you find it difficult to create such functions.

4. Bind validations on page load

We’ll use the onblur event to immediately perform one or more validations from the moment a user leaves an input field. The binding takes place after page load. A jQuery selector is used to accurately target the page items that need some sort of validation. The easiest way to come up with these jQuery selectors is by studying the generated HTML. Let me explain the selectors that I have used:

  • $(‘[required]’) : Retrieves all page items that are required to fill in. This jQuery selector fetches all elements that have the required attribute. The required attribute is added to all elements for which the Value Required setting is set to Yes. This feature was introduced in APEX 4.2, so older versions need a different jQuery selector based on a custom CSS class. I’ll explain the concept of custom CSS classes straight away.
  • $(‘input.number_field’) : Fetches all input fields with a CSS class of number_field. APEX automatically assigns this class to page items that are number fields.
  • $(‘input.valPositiveInteger’) : This selector relies on a custom CSS class, valPositiveInteger. The difference between the previous CSS class is that the number_field class is added automatically by APEX for all number fields. The valPositiveInteger class however must be assigned manually in the HTML Form Element CSS Classes page item setting.
  • $(‘input.datepicker’) : Selects all items of type Date Picker. The datepicker class is added automatically by APEX, so there is no custom CSS class required here.
// global variables
var errorClass = "valError";

// bind validations on page load
$(function() {

  $('[required]').blur(function(event) {
    if (isEmpty(this.value)) {
      showErrorField(this, "required field");
      event.stopImmediatePropagation();
    } else {
      hideErrorField(this);
    }
  });

  $('input.number_field').blur(function(event) {
    if (!isEmpty(this.value) && isNaN(this.value)) {
      showErrorField(this, "not a numeric value");
      event.stopImmediatePropagation();
    } else {
      hideErrorField(this);
    }
  });

  $('input.valPositiveInteger').blur(function(event) {
    if (!isEmpty(this.value) && !isPositiveInteger(this.value)) {
      showErrorField(this, "not a positive integer value");
      event.stopImmediatePropagation();
    } else {
      hideErrorField(this);
    }
  });

  $('input.datepicker').blur(function(event) {
    if (!isEmpty(this.value) && !isValidDate(this.value)) {
      showErrorField(this, "not a valid date (DD/MM/YYYY)");
      event.stopImmediatePropagation();
    } else {
      hideErrorField(this);
    }
  });

});

5. Error message manipulation

You probably noticed the showErrorField and hideErrorField functions in the previous code snippet. The logic is pretty simple:

  • showErrorField inserts a span element accompanied by an error message behind a given input field. I also add an error class to the input field which makes it easy to apply certain CSS styles to it.
  • hideErrorField removes the span element and error class, if present.
function showErrorField(pInputElement, pMessage) {
  var inputElement = $(pInputElement);

  if (!inputElement.hasClass(errorClass)) {
    inputElement.addClass(errorClass);
    inputElement.after("<span class=\"" + errorClass + "\">" + pMessage + "</span>");
  } else {
    inputElement.next().text(pMessage);
  }
}

function hideErrorField(pInputElement) {
  var inputElement = $(pInputElement);

  if (inputElement.hasClass(errorClass)) {
    inputElement.removeClass(errorClass);
    inputElement.next().remove();
  }
}

6. CSS

Some CSS code to visually mark an input field as erroneous.

input.valError,
textarea.valError {
  background-color: #FAE7E7;
}

span.valError {
  margin-left: 5px;
  color: #c60f13;
}

7. Before page submit

The live validations on our form are now operational. Filling out an input field with an invalid value results in signaling the end user by displaying an appropriate error message. This certainly is an improvement in terms of user-friendliness. It is, however, still possible for the user to submit the form to the server while it contains obvious errors. We’re going to avoid this behaviour by (re-)executing all form validations on the moment our user hits the submit button. The form won’t get submitted if one or more errors exist. The formHasErrors function checks if a form contains errors.

function formHasErrors(pForm) {
  var errorsExist = false;
  var formElement = $(pForm);

  formElement.find('input, textarea, select').trigger('blur');

  if (formElement.find('.' + errorClass).length > 0) {
    errorsExist = true;
  }

  return errorsExist;
}

Next, we have to intercept the onclick event on the button that submits the form. To do so, we first save the original onclick event of the button (by using jQuery.data) and we then replace the onclick event with a function that evaluates the formHasErrors function:

  • if TRUE; alert the user that the form contains errors and certainly do not submit the page
  • if FALSE; execute the original onclick event
function validateFormBeforeSubmit(pForm, pFiringElement) {
  var firingElement = $(pFiringElement);
  var originalOnclickEvent = firingElement.attr('onclick');

  firingElement.data('origOnclickEvent', originalOnclickEvent);
  firingElement.removeAttr('onclick');

  firingElement.on('click', function() {
    if (!formHasErrors(pForm)) {
      eval(firingElement.data('origOnclickEvent'));
    } else {
      alert("Please fix all errors before continuing");
    }
  });
}

The validateFormBeforeSubmit function accepts two parameters with the purpose to identify the button(s) that are responsible for form submission. Creating this connection allows us to revalidate the coupled form before page submit. Both parameters represent a jQuery selector (remember the static ID’s in step 1). Place the code below in the Execute when Page Loads section on page settings.

validateFormBeforeSubmit('#empForm', '#saveEmpBtn');

8. Wrap it up

Put all JavaScript code, except the last snippet, in one file and include it on all pages throughout your application. The best way to do this is by referencing the JavaScript file in your page templates. You now have a highly reusable client-side validation library at your disposal which can be easily extended. Check out the end result here.