Using Oracle JET Converters

The Oracle JET date-time and number converters, oj.IntlDateTimeConverter and oj.IntlNumberConverter, extend the oj.Converter object which defines a basic contract for converter implementations.

The converter API is based on the ECMAScript Internationalization API specification (ECMA-402 Edition 1.0) and uses the Unicode Common Locale Data Repository (CLDR) for its locale data. Both converters are initialized through their constructors, which accept options defined by the API specification. For additional information about the ECMA-402 API specification, see http://www.ecma-international.org/ecma-402/1.0. For information about the Unicode CLDR, see http://cldr.unicode.org.

The Oracle JET implementation extends the ECMA-402 specification by introducing additional options, including an option for user-defined patterns. For the list of additional options, see the oj.IntlDateTimeConverter and oj.IntlNumberConverter API documentation.

For examples that illustrate the converters in action, see the Converters section in the Oracle JET Cookbook.

Note:

The bundles that hold the locale symbols and data used by the Oracle JET converters are downloaded automatically based on the locale set on the page when using RequireJS and the ojs/ojvalidation module. If your application does not use RequireJS, the locale data will not be downloaded automatically.

You can use the converters with an Oracle JET component or instantiate and use them directly on the page.

Topics:

Using Oracle JET Converters with Oracle JET Components

Oracle JET components that accept user input, such as ojInputDate, already include an implicit converter that is used when parsing user input. However, you can also specify an explicit converter on the component which will be used instead when converting data from the model for display on the page and vice versa.

For example, the following code sample shows a portion of a form containing an ojInputDate component that uses the default converter supplied by the component implicitly. The highlighted code shows the binding for the ojInputDate component.

<div id="builtin-example">
  ... contents omitted
  <div class="oj-row">
    <div class="oj-col">
      <label for="date1">default</label>
    </div>
    <div class="oj-col">
      <input id="date1" type="text" name="date1"
        title="Enter a date in your preferred format, and we will attempt to figure it out."
        data-bind="ojComponent:{component: 'ojInputDate' value: date,
          datePicker: {changeMonth: 'none', changeYear: 'none'}}"/>
    </div>
  </div>
  ... contents omitted
</div>

The script to create the view model for this example is shown below.

require(['ojs/ojcore', 'knockout', 'jquery', 'ojs/ojknockout', 'ojs/ojdatetimepicker'],
  function(oj, ko, $)
    {
        function MemberViewModel()
        {
            var self = this;
            self.date = ko.observable();
        };

    $(document).ready(
        function()
        {
            ko.applyBindings(new MemberViewModel(), document.getElementById('builtin-example'));
        }
    );
});

When the user runs the page, the ojInputDate component displays an input field with the expected date format. In this example, the component also displays a hint when the user clicks in the input field. If the user inputs data that is not in the expected format, the built-in converter displays an error message with the expected format.

The image is described in the surrounding text.

The error that the converter throws when there are errors during parsing or formatting operations is represented by the oj.ConverterError object, and the error message is represented by the oj.Message object. The messages that Oracle JET converters use are resources that are defined in the translation bundle included with Oracle JET. For more information about messaging in Oracle JET, see Working with User Assistance.

You can also specify the converter directly on the component's converter property, if it exists. The code excerpt below defines another ojInputDate component on the sample form and specifies the oj.IntlDateTimeConverter converter with options that will convert the user's input to a numeric year, long month, and numeric day according to the conventions of the locale set on the page. The options parameter is an object literal that contains the ECMA-402 options as name-value pairs.

<div class="oj-col">
  <input id="date2" type="text" name="date2" 
    title="Enter a date in your preferred format, and we will attempt to figure it out"
    data-bind="ojComponent:{component:'ojInputDate', value: date,
      datePicker: {changeMonth: 'none', changeYear: 'none'},
      converter: {type:'datetime', options: {year: 'numeric', month: 'long', day: 'numeric'}}}"/>
</div>

When the user runs the page in the en-us locale, the ojInputDate component displays an input field that expects the user's input date to be in the mmmm d, yyyy format. The converter will accept alternate input if it makes sense, such as 12/15/22 (MM/dd/yy), and perform the conversion, but will throw an error if it cannot parse the input. For details about Oracle JET converters and lenient parsing support, see Understanding Oracle JET Converters Lenient Parsing.

The image is described in the surrounding text.

Parsing of narrow era, weekday, or month name is not supported because of ambiguity in choosing the right value. For example, if you initialize the date time converter with options {weekday: 'narrow', month: 'narrow', day: 'numeric', year: 'numeric'}, then for the en-US locale, the converter will format the date representing May 06, 2014 as T, M 6, 2014, where T represents Tuesday. If the user inputs T, M 6, 2014, the converter can't determine whether the user meant Thursday, March 6, 2014 or Tuesday, May 6, 2014. Therefore, Oracle JET expects that user inputs be provided in either their short or long forms, such as Sat, March 02, 2013.

For additional detail about the oj.IntlDateTimeConverter and oj.IntlNumberConverter component options, see oj.IntlDateTimeConverter and oj.IntlNumberConverter.

Understanding Oracle JET Converters Lenient Parsing

The Oracle JET converters support lenient number and date parsing when the user input does not exactly match the expected pattern. The parser does the lenient parsing based on the leniency rules for the specific converter.

oj.IntlDateTimeConverter provides parser leniency when converting user input to a date and enables the user to:

  • Input any character as a separator irrespective of the separator specified in the associated pattern. For example, if the expected date pattern is set to y-M-d, the date converter will accept the following values as valid: 2013-11-16, 2013/11-16, and 2013aaa11xxx16. Similarly, if the expected time pattern is set to mm:ss:SS:, the converter will accept the following values as valid: 11.24.376.

  • Specify a 4-digit year in any position relative to day and month. For example, both 11-2013-16 and 16-11-2013 are valid input values.

  • Swap month and day positions, as long as the date value is greater than 12 when working with the Gregorian calendar. For example, if the user enters 2013-16-11 when y-M-d is expected, the converter will autocorrect the date to 2013-11-16. However, if both date and month are less or equal to 12, no assumptions are made about the day or month, and the converter parses the value against the exact pattern.

  • Enter weekday and month names or mix short and long names anywhere in the string. For example, if the expected pattern is E, MMM, d, y, the user can enter any of the following dates:

    Tue, Nov 26 2013
    Nov, Tue 2013 26
    2013 Tue 26 Nov
    
  • Omit weekdays. For example, if the expected pattern is E, MMM d, y, then the user can enter Nov 26, 2013, and the converter autocorrects the date to Tuesday, Nov 26, 2013. Invalid weekdays are not supported. For instance, the converter will throw an exception if the user enters Wednesday, Nov 26, 2013.

oj.IntlNumberConverter supports parser leniency as follows:

  • If the input does not match the expected pattern, Oracle JET attempts to locate a number pattern within the input string. For instance, if the pattern is #,##0.0, then the input string abc-123.45de will be parsed as -123.45.

  • For the currency style, the currency symbol can be omitted. Also, the negative sign can be used instead of a negative prefix and suffix. As an example, if the pattern option is specified as "\u00a4#,##0.00;(\u00a4#,##0.00)", then ($123), (123), and -123 will be parsed as -123.

  • When the style is percent, the percent sign can be omitted. For example, 5% and 5 will both be parsed as 0.05.

Using Custom Converters in Oracle JET

You can create custom converters in Oracle JET by extending oj.Converter or by duck typing it. You can also create a custom converter factory to register the converter with Oracle JET and make it easier to instantiate the converter.

Custom converters can be used with Oracle JET components, provided they don't violate the integrity of the component. As with the built-in Oracle JET converters, you can also use them directly on the page.

The figure below shows an example of a custom converter used to convert the current date to a relative term. The Schedule For column uses a RelativeDateTimeConverter to convert the date that the page is run in the en-US locale to display Today, Tomorrow, and the following date.

The image is described in the surrounding text.

To create and use a custom converter in Oracle JET:

  1. Define the custom converter.

    The code sample below shows a portion of the RelativeDateTimeConverter definition. The converter wraps the Oracle JET IntlDateTimeConverter by providing a specialized format() method that turns dates close to the current date into relative terms for display. For example, in the en-US locale, the relative terms will display Today, Yesterday, and Tomorrow. If a relative notation for the date value does not exist, then the date is formatted using the regular Oracle JET format() method supported by the Oracle JET IntlDateTimeConverter.

    function RelativeDateTimeConverter(options) {
      this.Init(options);
    };
    
    RelativeDateTimeConverter._DEFAULT_RELATIVE_DATETIME_CONVERTER_OPTIONS =
    {
      'formatUsing' : "displayName",
      'dateField' : "relativeField"
    };
    
    // Subclass from oj.Object
    oj.Object.createSubclass(RelativeDateTimeConverter, oj.Converter, "RelativeDateTimeConverter");
    
    // Initializes converter instance with the set options
    RelativeDateTimeConverter.prototype.Init = function(options)
    {
      RelativeDateTimeConverter.superclass.Init.call(this);
      this._options = options;
      var cf = oj.Validation.converterFactory(oj.ConverterFactory.CONVERTER_TYPE_DATETIME);
      this._wrappedConverter = cf.createConverter(options);
    };
    
    // Returns the options set on the converter instanceRelativeDateTimeConverter.prototype.getOptions = function()
    {
      return this._options;
    };
    
    // Does not support parsing
    RelativeDateTimeConverter.prototype.parse = function(value)
    {
      return null;
    };
    
    // Formats a value using the relative format options. Returns the formatted
    // value and a title that is the actual date, if formatted value is a relative date.
    RelativeDateTimeConverter.prototype.format = function(value)
    {  var formatted, base;
      formatted = this._getWrapped().formatRelative(value, this._getRelativeOptions());
      base = this.formatRegular(value);
      if (formatted &amp;&amp; typeof formatted === "string")
      {
        formatted = formatted.replace(/(\w)(\w*)/g, function (_, i, r) {
                      return i.toUpperCase() + (r !== null ? r : "");
                    });
      }
      return {value: formatted || base, title: formatted ? base : ""}
    };
    // formats using the base datetime converterRelativeDateTimeConverter.prototype.formatRegular = function(value)
    {
      // value does not have a relative display; use regular date
      return this._getWrapped().format(value);
    };
    
    // Returns a hint
    RelativeDateTimeConverter.prototype.getHint = function ()
    {
      return "";
    };
    
    RelativeDateTimeConverter.prototype._getWrapped = function ()
    {
      return this._wrappedConverter;
    };
    
    RelativeDateTimeConverter.prototype._getRelativeOptions = function ()
    {  var i, options = this._options, relativeOptions = {};
      if (options &amp;&amp; options["relativeField"])
      {
        relativeOptions['formatUsing'] = "displayName";
        relativeOptions['dateField'] = options["relativeField"];
      }
    
      else
      {    relativeOptions = RelativeDateTimeConverter._DEFAULT_RELATIVE_DATETIME_CONVERTER_OPTIONS;
      }
    
      return relativeOptions;
    };
    

    The custom converter relies on the IntlDateTimeConverter converter's formatRelative() method. For additional detail about the IntlDateTimeConverter converter's supported methods, see the oj.IntlDateTimeConverter API documentation.

  2. Optionally, create a converter factory for the custom converter that supports a createConverter() method, meeting the contract defined by oj.ConverterFactory.

    The following code sample shows a simple converter factory for the RelativeDateTimeConverter. The code also registers the factory with Oracle JET as a new relativeDate type using the oj.Validation module.

    /**
     * A converter factory for "relativeDate" that supports custom 
     * formatting of normal and relative date times. 
     */
    RelativeDateTimeConverterFactory = (function () {
      /**
      * Private function that takes regular and relative options.
      *
      * @param {Object} options
      * @return {Object}
      * @private
      */
      function _createRelativeDateTimeConverter(options)
      {
        return new RelativeDateTimeConverter(options);
      }
     
      return {
        'createConverter': function (options) {
          return _createRelativeDateTimeConverter(options);
        }
      };
    }());
     
    /** Register the  factory with JET */
    oj.Validation.converterFactory("relativeDate", // factory name
                                    RelativeDateTimeConverterFactory);
    
  3. Add code to your application that uses the custom converter.

    The code sample below shows how you could add code to your script to use the custom converter.

    var rdtcf = oj.Validation.converterFactory("relativeDate");
    var rdOptions = {relativeField: 'day', year: "numeric", month: "numeric", day: "numeric"};
    var rdConverter = rdtcf.createConverter(rdOptions);
    
  4. Add the Oracle JET component or components that will use the custom converter to your page.

    For the code sample that creates the ojTable component and displays the Schedule For column in relative dates, see Converters (Custom).

Using Oracle JET Converters Without Oracle JET Components

If you want to use a converter without binding it to an Oracle JET component, create the converter using oj.Validation.converterFactory.createConverter().

The Oracle JET Cookbook includes a complete example that shows how you can use the built-in number and date time converters directly in your pages without binding them to an Oracle JET component at Converters (Built-in Examples). In the image below, the salary is a number formatted as currency, and the start date is an ISO string formatted as a date.

The image is described in the surrounding text.

The sample code below shows a portion of the viewModel that defines a salaryConverter to format a number as currency and a dateConverter that formats the start date using the date format style and medium date format.

// for salary fields
var salOptions = {style: 'currency', currency: 'USD'}; 
var salaryConverter =
  oj.Validation.converterFactory("number").createConverter(salOptions);
self.amySalary = ko.observable(salaryConverter.format(125475.00));
self.garySalary = ko.observable(salaryConverter.format(110325.25));

// for date fields
var dateOptions = {formatStyle: 'date', dateFormat: 'medium'};
var dateConverter =
  oj.Validation.converterFactory("datetime").createConverter(dateOptions);
self.amyStartDate = ko.observable(dateConverter.format("2014-01-02"));
self.garyStartDate = ko.observable(dateConverter.format("2009-07-25"));

The code sample below shows the portion of the markup that sets the display output to the formatted values contained in amySalary, amyStartDate, garySalary, garyStartDate.

<td>
  <div class="oj-panel oj-panel-alt4 demo-panel-customizations"> 
    <h3 class="oj-header-border">Amy Flanagan</h3>
    <img src="images/Amy.png" alt="Amy">
    <p>Product Manager</p>
    <span style="white-space:nowrap;"><b>Salary</b>:
      <span data-bind="text: amySalary"></span></span>
    <br/>
    <span style="white-space:nowrap;"><b>Joined</b>:
      <span data-bind="text: amyStartDate"></span></span>
    <br/>
  </div>
</td>
<td>
  <div class="oj-panel oj-panel-alt2 demo-panel-customizations"> 
    <h3 class="oj-header-border">Gary Fontaine</h3>
    <img src="images/Gary.png" alt="Gary">
    <p>Sales Associate</p>
    <span style="white-space:nowrap;"><b>Salary</b>:
      <span data-bind="text: garySalary"></span></span>
    <br/>
    <span style="white-space:nowrap;"><b>Joined</b>:
        <span data-bind="text: garyStartDate"></span></span>
    <br/>
  </div>
</td>