Showing posts with label observables. Show all posts
Showing posts with label observables. Show all posts

Tuesday, April 19, 2016

Extending Oracle JET Components through Custom Bindings (Input Text with Buttons)

The other day I saw an interesting question on an Oracle JET forum: "Can you add a clear button inside the end of an ojInputText?" Think of this like the magnifying glass at the end of a search field or an X that allows you to delete all of the text within an ojInputText. My first thought was to use a little CSS to add a FontAwesome icon to the end of the data entry field. If we were using a plain HTML input element, this would be no small task because it is impossible to use CSS alone to add an icon to the end of an input element (maybe someday HTML and CSS will support before and after selectors for input elements?). ojInputText, however, already wraps input elements in an outer div so we just need to add a little CSS to style that outer div. Here is an example that just uses CSS styling

You see a problem with this solution? I didn't at first. From a visual perspective, it meets all of the requirements—oh, except it isn't a button. If all you want is a visual indicator/icon within ojInputText, then this is a small, tidy solution will suffice. If you actually wanted a button, then keep reading.

My colleague Paul Thaden reworked my example for click events:

Notice this example replaces the :after pseudo selector with jQuery append. This allows us to handle the icon's click events. This is a great, simple solution if you need to handle click events AND know when elements will exist in the DOM (so you can wire up event handlers, enhance the markup, etc). But what about those times when elements are created and destroyed through iterators or View/Component changes? What we really need is a way to manage the component's lifecycle so we can enhance and wire up event handlers on creation. Knockout has a mechanism for this called Custom Binding Handlers.

Have you noticed the $(".selector").ojFoo syntax in a lot of the Oracle JET JSDocs? That looks a lot like the jQuery UI syntax (because it is—thank you JB for confirming, see this video). If Oracle JET components are a lot like jQuery UI widgets, then we are in luck. The internet is littered with examples of people creating custom binding handlers for jQuery UI components. Here is a great example that creates a custom binding handler for the jQuery UI datepicker. All we need to do is follow that example, replacing datepicker with ojInputText. In other words, we can extend any Oracle JET component binding by pretending it doesn't have bindings and treating it like a jQuery plugin. Here is a jsFiddle showing examples of two-way data binding and data model creation/destruction, etc:

Too much clutter? Just want to see the ojInputText extension? Here is the HTML

<input id="text-input" 
       type="text"
       data-bind="audioInputText: value,
                  ojInputTextOptions: {rootAttributes:
                                        {class: 'audio-ojInputText'}}"/>

And the JavaScript

ko.bindingHandlers.audioInputText = {
    // setup extension to ojInputText as well as register event handlers
    init: function(element, valueAccessor, allBindingsAccessor, ctx) {
      var options = allBindingsAccessor().ojInputTextOptions || {};
        
      $(element)
        .ojInputText(options)
        .on({
          'ojoptionchange': function (event, data) {
            // use option === "value" for final value
            // use option === "rawValue" for each character
            if(data.option === "value") {
              var observable = valueAccessor();
              observable($(element).ojInputText("option", "value"));
            }
          }
        })
        .closest(".audio-ojInputText")
          .append('')
          .find("i")
            .click(function() {
              var msg = "This could activate the microphone... but it doesn't. " +
                "Hey, I noticed you entered '" +
                ko.utils.unwrapObservable(valueAccessor()) + "'"
              alert(msg);
            });

      //handle disposal (if KO removes by the template binding)
      ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
        $(element).ojInputText("destroy");
        console.log("ojInputText destroyed");
      });
        
    },
    // This is how we update the UI based on observable changes
    update: function(element, valueAccessor) {
      var value = ko.utils.unwrapObservable(valueAccessor());
      $(element).ojInputText("option", "value", value);
    }
  };

Here is an example of an ojInputText with a delete button that deletes all of the text within the observable when clicked.

In each of the examples above, I hard coded the click handler and the icon. In prior examples, the click handler used model data, making it somewhat generic, but not generic enough to delegate clicks to the ViewModel. Let's create one final example that we'll call ojInputText Clear Buttons. This example is generic enough to use any icon library (glyphicons, fontawesome, oj icons, etc) and invokes a click handler within the ViewModel.

Thursday, March 24, 2016

Filtering Oracle JET ArrayTableDataSource

I recently wrote about Filtering Table Data with Knockout Computeds. I'm not sure I shared anything earth shattering as others have already written about that topic. My real motivation for writing that post was to provide a foundation to compare/contrast filtering a plain HTML table against an Oracle JET ojTable that is based on an oj.ArrayTableDataSource (not to be confused with oj.CollectionTableDataSource, which wraps an oj.Collection and has its own where method and cookbook example).

Let's jettify the Filtering Table Data with Knockout Computeds example. Instead of <input type="text">, we'll use ojInputText. Instead of a Bootstrap table, we'll use ojTable. Here is the jsFiddle:

As you can see, the code is fairly similar. A couple of key differences to note are the rawValue parameter and the lack of a computed. Instead of using the value and valueUpdate parameters, the ojInputText component uses value and rawValue. The ojInputText value parameter acts just like the standard knockout parameter, tracking the final updated value after losing focus. The rawValue parameter tracks changes as you type, which is similar to the standard input with the valueUpdate parameter. The other difference between these examples is that this example didn't use a computed observable, but rather the oj.ArrayTableDataSource.reset() method to replace the table's array when filtering.

As with the Knockout computed example, we can debounce this example so that the array filter code doesn't run with every key press, but only after a predetermined pause. I included the rate limiting extension in the JavaScript, but commented it out for example purposes. When filtering large data sets, it might make sense to switch from observing rawValue to observing the value parameter. Another option is to include a Search button that triggers the filter code on click. You can find an example of this here.

Note: Filtering arrays requires an ES6 compatible browser or some type of polyfill library. The examples include Lazy.js as a polyfill so you can run the jsFiddle example in a wider range of web browsers.

Wednesday, March 23, 2016

Filtering Table Data with Knockout Computeds

Before learning to love Knockout, I was an AngularJS fan. AngularJS has this really cool feature called filters that let you pipe a collection through a filter, filtering results based on the value of a field. The AngularJS filter page has a great inline example. You can filter with Knockout, but it isn't quite as simple. The key to filtering in Knockout is the computed observable. I put together an example:

This example contains a search field in the upper right corner as well as a table of employee names. If the filter field is empty, then the table should display all employees. If the filter field contains a value, then the table should display only employees with names containing the search value. When you look at the JavaScript for this example, you will see:

  1. An array of employees (the raw, unobserved data),
  2. An observable for the search value, and
  3. A computed for the filtered table

Drilling into the computed observable (filteredEmployees), we see that the function immediately returns the list of all employees if the nameSearch observable has no value. If it has a value, then it returns a filtered array of matching results.

I put together 2 examples: One with ES6 Array and String extensions and a Lazy.js version. The example above is the Lazy.js version. ES6 is great, but jsPerf tests show better results for ko.utils, underscore, Lazy.js, or just about any other non-native library. I also hesitate to use the ES6 Array.prototype.filter for browser compatibility reasons. Just in case you are interested, Here is the ES6 version. Why Lazy.js instead of just ko.utils.arrayFilter? I am a big fan of Lazy.js's function composition rather than the traditional chained intermediate array concept (even though this example doesn't exactly chain enough array methods together to see a performance improvement from Lazy.js).

On the HTML View side, the search field's data-bind attribute uses the valueUpdate parameter. This causes Knockout to update the ViewModel on some other event besides the change event. That way users can see changes as they type. What this means is as you type, the filter code will run, filtering the results displayed in the table. We have a small data set, so you won't notice, but on a larger data set, this could have serious performance implications because each key press would iterate over the Employees array. We can limit how often knockout recomputes the computed observable by debouncing, or rate limiting, updates of nameSearch field like this:

self.nameSearch.extend({
    rateLimit: {
      timeout: 500,
      method: "notifyWhenChangesStop"
    }
  });

You can see an example here. Notice that the update is a little choppier, meaning the table filters a half second after you stop typing. A half second may be a little too long between updates. The important part is that the code recalculates the computed AFTER the specified event pauses for a predetermined interval.

Wednesday, March 16, 2016

Hash-style Routing with Oracle JET

Oracle JET is a modular toolkit which includes a significant list of "tools." As developers we are welcome to use one, some, all, or none of the Oracle JET features. For example, I am a big fan of the knockout enabled JET Data Visualizations. Another tool in the Oracle JavaScript Extension Toolkit is the Router. While certainly a nice feature, supporting query string and path-style routing, I prefer hash-style routing.

Routing a knockout-based SPA is fairly trivial. First, we need an observable to hold the current route, the value being the name of a knockout component. Next, we need a view that contains a placeholder for that knockout component. The final piece is some JavaScript to listen for URL changes to update this observable. I prefer Crossroads, but there are many others. Crossroads expects a URL pattern (the route) and a callback to invoke when the current URL matches that route. Here is a sample AMD module that:

  • Stores the currently selected route in a member named currentRoute,
  • A list of all routes in a member named routes, and
  • Contains a method for activating routing. This method is responsible for adding each route as well as setting up each route's callback (which just updates the currentRoute observable)
define(["knockout",
  "crossroads",
  "hasher",
  "jquery"
], function(ko, crossroads, hasher, $) {
  'use strict';

  var router = {
    routes: undefined,
    currentRoute: ko.observable({}),

    activate: function(routes) {
      router.routes = routes;
      ko.utils.arrayForEach(routes, function(route) {
        crossroads.addRoute(route.url, function(requestParams) {
            router.currentRoute(ko.utils.extend(requestParams, route.params));
        });
      });

      var parseHash = function(newHash) {
        crossroads.parse(newHash);
      };

      crossroads.normalizeFn = crossroads.NORM_AS_OBJECT;
      hasher.initialized.add(parseHash);
      hasher.changed.add(parseHash);
      hasher.init();
    }
  };

  return router;
});

To use this router, our main ViewModel needs to setup an array of route ⇒ component mappings and maintain a pointer to the currentRoute member. Here is what that ViewModel might look like:

require(['knockout',
  'router',
], function(ko, router) {
  'use strict';

  // the routes
  var routes = [{
    url: '',
    params: {
      component: 'home',
    }
  }, {
    url: 'about',
    params: {
      component: 'about'
    }
  }, {
    url: 'parameter-example/{id}',
    params: {
      component: 'parameters'
    }
  }, {
    url: 'query-example{?query}',
    params: {
      component: 'querystring',
    }
  }];

  // register the components identified by the routes
  ko.utils.arrayForEach(routes, function(r) {
    var name = r.params.component;
    if (!ko.components.isRegistered(name)) {
      ko.components.register(name, {
        require: "components/" + name + "/viewModel"
      });
    }
  });

  // configure router
  router.activate(routes);

  // start the app
  ko.applyBindings({
    route: router.currentRoute
  });
});

Note: see that small loop in there that iterates over each component identified by the routes and registers each route as a Knockout component? If all of our components follow the same pattern, we can actually push a custom loader onto the knockout loader stack that will resolve any component by convention rather than configuration. Here is an example:

// Custom configration-based loader. Loads components by naming convention
ko.components.loaders.push({
  getConfig: function(name, callback) {
    callback({
      require:  "components/" + name + "/viewModel"
    });
  }
});

Now we just need a view that exposes the current route's component:

<!DOCTYPE html>

<html lang="en-us">
  <head>
    <title>Sample</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- core CSS -->
    <!-- build:css(.) styles/vendor.css -->
    <!-- bower:css -->
    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css" />
    <!-- endbower -->
    <!-- endbuild -->
    <!-- build:css(.tmp) styles/main.css -->
    <link rel="stylesheet" href="styles/main.css" />
    <!-- endbuild -->

    <!-- RequireJS bootstrap file -->
    <script data-main="js/main.min" src="bower_components/requirejs/require.min.js"></script>

  </head>
  <body>

    <div class="container">
      <!-- Route-specific content. Routes are assigned in main.js and defined
      as Knockout components -->
      <main
            data-bind="component: { name: route().component }"></main>
    </div>
  </body>
</html>

Assuming we have View/ViewModel combinations in folders named home, about, parameters, and querystring, we can visit URLs like http://localhost:9000/, http://localhost:9000/#/about, http://localhost:9000/#/parameter-example/101, and http://localhost:9000/#/query-example?first=Curtis&last=Feitty and see results.

With our routes available in a separate module, we could take this example a step further by introducing a few more attributes to our routing metadata that we could use in a header component to create a global navigation bar. For example, if we added isGlobal attributes to each route that should appear in a navigation list, then we could use a filter to list all global routes in a navigation bar. Likewise, by adding an iconClass attribute, we could use glyphicons or fontawesome glyphs to display an image next to each link. And, since the router AMD module maintains the state of the current route in an observable, we could setup a computed to highlight the active route. Here is a screenshot of what that might look like:



Synchronizing ojSelect Dependent Value List Observables

Dependent value lists are lists of values that change based on some other field. Country and state are common examples. If Country has no value, then the list of states (or provinces, territories, etc) should be empty. After selecting a country, the options within state should change to match relevant options for that country. Knockout computeds offer a great mechanism for maintaining dependent lists. Here are a couple of good examples of using computeds to maintain dependencies:

But what happens when the dependent list's selected value becomes invalid after changing the underlying list of options? For example, what value should the "state" field contain when the country changes, rendering the current "state" selection invalid? What about the data model? Should the underlying observable share the same value as what is shown on the screen? When the options list of a select changes and the model's value is not in the list of options (an invalid value), the default Knockout behavior is to update the model to contain the first (selected) option in the list (see valueAllowUnset). This may or may NOT be the right approach, which is why knockout allows us to change its behavior through the valueAllowUnset parameter. Oracle JET's ojSelect takes the opposite approach. When the options list changes, invalidating the selected option, ojSelect does NOT write back to the model. While this may be desirable (as shown in the valueAllowUnset parameter), it may lead to a situation where the display on the screen does not match the underlying data model. In the following recording, notice that the country starts as Canada and the State is Newfoundland. After switching to Country: United States, the state switches to Alabama. This seems reasonable because Canada does not contain the state Alabama and the United States does not contain a state named Newfoundland. What isn't obvious by this recording, however, is that the change to state doesn't affect the bound observable.

One method to keep the screen and the data model in sync is to subscribe to the optionChange event. When the options list changes, and the selected option is not in the list, ojSelect will trigger the optionChange event (because the selected option changed), but not write back to the data model. Your subscription handler can choose to update the data model observable with the newly selected option. Here is some sample HTML showing the optionChange attribute:

<select id="state" data-bind="ojComponent: {component: 'ojSelect',
    options: stateList, value: stateSelected,
    placeholder: '', optionChange: stateOptionChangedHandler}" required></select>

... and the stateOptionChangeHandler JavaScript:

self.stateOptionChangedHandler = function(event, data) {
  if (data.option === "value") {
    var value = data.value[0];
    var observable = self.selectedState;

    // only set if the option value change didn't update the observable
    // we want the underlying data to match the screen
    if (value !== observable()) {
      console.log("setting value from options handler", value, observable());
      observable(value);
    }

  }
};

Now replay the recording above. Notice the output in the console window? The recording above uses the optionChange handler presented here to write back to the observable when the option changes by some mechanism other than the user actually selecting a new value. What you see printed in the console window is the new value (Alabama) followed by the old observable value (Newfoundland and Labrador).

Chances are you will have multiple dependent value lists. Who wants to repeat that code for every list? Here is my generic library function:

var valueOptionChangeHandler = function(observable, event, data) {
  if (data.option === "value") {
    var value = data.value[0];

    // only set if the option value change didn't update the observable
    // we want the underlying data to match the screen
    if (value !== observable()) {
      console.log("setting value from options handler", value, observable());
      observable(value);
    }

  }
};

I can then "curry" an observable into a new function that I use as my optionChange handler like this:

self.stateOptionChangedHandler = ojsHelper.valueOptionChangeHandler
  .bind(undefined, self.selectedState);

Thursday, March 3, 2016

Unwrapping Oracle JET's ojSelect value binding

The ojSelect component is a very powerful alternative to the HTML <select> element. It has a long list of impressive features including a type-ahead search box (for long lists), pill-like multi-select, and the ability to include images in the options list. If you are building web applications connected to Oracle applications (like me), then you can't help but appreciate the prepackaged Alta skin as well.

One of the issues I struggled with when switching from the traditional HTML <select> element to ojSelect was the value binding. The single <select> element returns a single value (or whatever the selected row in the option binding represents) whereas ojSelect single select returns an array. Even though the ojSelect value array has just one element, it is still an array. If my data model doesn't expect an array, then this can cause problems when binding the data model to an ojSelect in the view layer. Here are a couple of options I have used to work around the ojSelect array value:

  1. Bind to a temporary observable within the ViewModel and then marshal content from that temporary observable into the data model on save.
  2. Use a read/write computed observable to maintain state between the ViewModel and the Model.

One reason for using a 2-way data binding architecture, such as knockout, is so I don't have to copy values between the view and the model, so option #1 is not a favorite of mine. Option #2 is similar in that it uses a temporary observable in the ViewModel, but it is a little different in that I don't have to specifically transfer data between the Model and the ViewModel. Rather, it is more like connecting some plumbing and letting knockout stream data between the two. Here is what that might look like:

require(['ojs/ojcore', 'knockout', 'jquery', 'ojs/ojknockout',
  'ojs/ojselectcombobox'
], function(oj, ko, $) {
  // make ko accessible to the console for ko.dataFor($0) inspection
  window.ko = ko;

  $(document).ready(
    function() {
      var data = {
        browser: ko.observable()
      };

      function ValueModel() {
        var self = this;

        // expose data to the view so we can bind other hypothetical values
        self.data = data;
        self.val = ko.pureComputed({
          read: function() {
            var val = self.data.browser();

            // 'required' validation doesn't work if the value is [undefined].
            // it only identifies empty as undefined (no array), so this
            // function doesn't wrap in array syntax if the value is undefined
            if (val === undefined) {
              return undefined;
            } else {
              return [val];
            }
          },
          write: function(value) {
            if (!!value) {
              self.data.browser(value[0]);
            }
          }
        });
      }
      ko.applyBindings(new ValueModel(), document.getElementById('form1'));
    }
  );
});

Note: This code fragment was specifically written for testing in the Oracle JET Cookbook. You can test it by pasting the fragment into the JavaScript block of the Oracle JET Cookbook ojSelect recipe page. After pasting, click the "Apply Changes" button. Select a value from the ojSelect list and notice the cookbook example still displays the observable with array notation. This is because the ViewModel is bound to the pureComputed observable, which returns an array. The underlying data model, however, contains the raw, unwrapped value. You can see the value stored in the data model by:

  • Right-clicking the ojSelect or "Current selected value..." paragraph and choosing "Inspect" from the context menu.
  • Switch to the console window and type ko.dataFor($0).data.browser()

This should display the unwrapped observable value without array notation.

I use this pureComputed wrapper for each of my ojSelect single-value select lists. Rather than replicate that code for every single ojSelect, I have a RequireJS module that exposes a method I can then use to create these computedObservables. Here is what that module contains:

define(["knockout"], function(ko) {
  'use strict';

  var wrapObservable = function(observable) {
    return ko.pureComputed({
      read: function() {
        var val = observable();

        // 'required' validation doesn't work if the value is [undefined].
        // it only identifies empty as undefined (no array), so this function
        // doesn't wrap in array syntax if the value is undefined
        if (val === undefined) {
          return undefined;
        } else {
          return [val];
        }
      },
      write: function(value) {
        if (!!value) {
          observable(value[0]);
        }
      }
    });
  };

  return {
    // ojSelect expects array values, so this method wraps single values in
    // array syntax
    wrapObservableForOJSelect: wrapObservable,
  };
});

I can then create ViewModel computeds using the following:

self.browser = ojsHelper.wrapObservableForOJSelect(data.browser);
self.os = ojsHelper.wrapObservableForOJSelect(data.os);
//...