Using Oracle JET Converters

The Oracle JET color, date-time, and number converters, oj.ColorConverter, 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.ColorConverter, oj.IntlDateTimeConverter, and oj.IntlNumberConverter API documentation.

For examples that illustrate the date-time and number converters in action, see the Converters section in the Oracle JET Cookbook. For examples using the color converter, see the Color Palette and Color Spectrum Cookbook samples.

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-base, ojs/ojvalidation-datetime, or ojs/ojvalidation-number 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. An explicit converter is required if you want to include time zone data.

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="datetime-converter-example">
  ... contents omitted
  <div class="oj-flex">
    <div class="oj-flex-item">
      <label for="date1">input date with no converter</label>
    </div>
    <div class="oj-flex-item">
      <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>
</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();
            self.datetime = ko.observable();
            self.time = ko.observable();
        };

    $(
        function()
        {
            ko.applyBindings(new MemberViewModel(), document.getElementById('datetime-converter-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 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-flex">
  <div class="oj-flex-item">
    <label for="date2">input date</label>
  </div>  
  <div class="oj-flex-item">
  <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.

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 details 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.

Understanding Time Zone Support in Oracle JET

By default, the ojInputDateTime and ojInputTime components and oj.IntlDateTimeConverter support only local time zone input. You can add time zone support by including the ojs/ojtimezonedata RequireJS module and creating a converter with the desired pattern.

The Oracle JET framework supports time zone conversion and formatting using the following patterns:

Token Description Example

z, zz, zzz

Abbreviated time zone name, format support only

PDT, PST

zzzz

Full time zone name, format support only

Pacific Standard Time, Pacific Daylight Time

Z, ZZ, ZZZ

Sign hour minutes

-0800

X

Sign hours

-08

XX

Sign hours minutes

-0800

XXX

Sign hours:minutes

-08:00

VV

Time Zone ID

America/Los Angeles

The image below shows the basic ojInputDateTime component configured for time zone support. In this example, the component is converted using the Z pattern.

The ojInputDateTime component is initialized with its converter option, in this case a method named dateTimeConverter.

<label for="dateTime">Default</label>
  <input id="dateTime" data-bind="ojComponent: {component: 'ojInputDateTime',
                                                converter: dateTimeConverter,
                                                value: value}"/>
  <br/><br/>
  <span class="oj-label">Current component value is:</span>
  <span data-bind="text: value"></span>

The ViewModel contains the dateTimeConverter() definition. Note that you must also add the ojs/timezonedata to your RequireJS definition to access the time zone data files.

define(['ojs/ojcore' ,'knockout', 'jquery', 'ojs/ojknockout', 'ojs/ojdatetimepicker', 'ojs/ojtimezonedata'],
  function(oj, ko) {
   /**
    * The view model for the main content view template
    */
  function mainContentViewModel() {
    var self = this;
    this.value = ko.observable("2013-12-01T20:00:00-08:00"); // Los Angeles
    this.dateTimeConverter = ko.observable(oj.Validation.converterFactory(oj.ConverterFactory.CONVERTER_TYPE_DATETIME).
        createConverter(
        {
          pattern : 'MM/dd/yy hh:mm:ss a Z',
          isoStrFormat: 'offset',
          timeZone:'Etc/GMT-05:00' //New York
        }));
  }

  return mainContentViewModel;
});

For an additional example illustrating how to add time zone support to ojInputDateTime and ojInputTime components, see Input Date and Time - Time Zone.

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 date in two days.

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.

    require(['ojs/ojcore', 'knockout', 'jquery', 'ojs/ojknockout', 
          'ojs/ojtable', 'ojs/ojbutton', 'ojs/ojvalidation-datetime', 
          'ojs/ojarraytabledatasource'],
        function (oj, ko, $)
        {
                              ...contents omitted
                            function RelativeDateTimeConverter(options) {
                            this.Init(options);
                            };
    
                            RelativeDateTimeConverter._DEFAULT_RELATIVE_DATETIME_CONVERTER_OPTIONS =
                            {
                            'formatUsing' : "displayName",
                            'dateField' : "week"
                            };
    
                            // Subclass from oj.Object
                            oj.Object.createSubclass(RelativeDateTimeConverter, oj.Converter, "RelativeDateTimeConverter");
    
                            // Initializes converter instance with the set options
                            RelativeDateTimeConverter.prototype.Init = function(options)
                            {
                            var cf;
                            // Subclasses of oj.Object must call their superclass' Init  
                            RelativeDateTimeConverter.superclass.Init.call(this);
                            this._options = options;
                            //create datetime converter and then wrap it. 
                            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 base;
                            var formattedRelativeDate;
    
                            // We get our wrapped converter and call its formatRelative function and store the
                            // return value ("Today", "Tomorrow" or null) in formatted variable
                             // See oj.IntlDateTimeConverter#formatRelative(value, relativeOptions)
                            // where relativeOptions has formatUsing and dateField options. dateField is 
                            // 'day', 'week', 'month', or 'year'.  
                            formattedRelativeDate = this._getWrapped().formatRelative(value, this._getRelativeOptions());
    
                            // We get our wrapped converter and call its format function and store the returned
                            // string in base variable. This will be the actual date, not a relative date. 
                            base = this._getWrapped().format(value);
    
    
                            // Capitalize the first letter of the string
                            if (formattedRelativedate &amp;&amp; typeof formattedRelativeDate === "string")
                            {
                            formattedRelativeDate = formattedRelativeDate.replace(/(\w)(\w*)/g, 
                            function (match, i, r) {
                            return i.toUpperCase() + (r !== null ? r : "");
                                     });
                            }
                            return {value: formatted || base, title: formattedRelativeDate ? base : ""}
                            };
    
                            // Returns a hint
                            RelativeDateTimeConverter.prototype.getHint = function ()
                            {
                            return "";
                            };
    
                            RelativeDateTimeConverter.prototype._getWrapped = function ()
                            {
                            return this._wrappedConverter;
                            };
    
                            RelativeDateTimeConverter.prototype._getRelativeOptions = function ()
                            {  var options = this._options;
                            var relativeOptions = {};
    
                            if (options &amp;&amp; options["relativeField"])
                            {
                             relativeOptions['formatUsing'] = "displayName";
                            relativeOptions['dateField'] = options["relativeField"];
                            }
    
                            else
                            {    relativeOptions = RelativeDateTimeConverter._DEFAULT_RELATIVE_DATETIME_CONVERTER_OPTIONS;
                            }
    
                            return relativeOptions;
                            };
    ...
    contents omitted});
    

    The custom converter relies on the IntlDateTimeConverter converter's formatRelative() method. For additional details 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. A relative date example is "Today",
     * "Tomorrow", or "This Week", etc.
     */
    RelativeDateTimeConverterFactory = (function () {
      /**
      * Private function that takes regular and relative options.
      */
      function _createRelativeDateTimeConverter(options)
      {
        return new RelativeDateTimeConverter(options);
      }
     
      return {
        'createConverter': function (options) {
          return _createRelativeDateTimeConverter(options);
        }
      };
    }());
     
    /** Register the  factory with JET */
    oj.Validation.converterFactory("relativeDate", 
    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 the Converters Factory demo that shows how to use the number and date time converters directly in your pages without binding them to an Oracle JET component. In the demo image, the salary is a number formatted as currency, and the start date is an ISO string formatted as a date.

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.

require(['ojs/ojcore', 'knockout', 'jquery', 'ojs/ojknockout', 'ojs/ojvalidation-datetime',
  'ojs/ojvalidation-number'],
    function(oj, ko, $)
    {    
      function DemoViewModel() 
      {
        var self = this;
                                // 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"));

                                ... contents ommitted
});  

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>