Showing posts sorted by relevance for query ko.bindingHandlers. Sort by date Show all posts
Showing posts sorted by relevance for query ko.bindingHandlers. Sort by date Show all posts

Wednesday, July 20, 2016

Using jQuery Interactions with Oracle JET

Draggable, droppable, resizable, selectable, and sortable are jQuery UI interactions that you may want to use when building custom Oracle JET applications. The question is, "How?" Here are three steps:

  1. Find the jQuery UI interaction plugin file,
  2. Import the interaction file into a module (declare it as a dependency), and
  3. Attach the interaction to a component.

Caveat: even though jQuery UI is one of Oracle JET's dependencies, Oracle JET uses only a small subset of the jQuery UI library. With that in mind, this post will describe how to use draggable and droppable with Oracle JET. Just be careful when using jQuery UI interactions with Oracle JET components because JET components weren't designed to be manipulated in this manner. Nevertheless, jQuery UI's interactions are so polished, it is worth a try.

Identify the jQuery UI Interaction File

This sounds like the easy part. Since jQuery UI is one of Oracle JET's dependencies, bower will install it automatically. Depending on how you installed Oracle JET, however, finding jQuery UI files may be difficult (see this thread). If you add Oracle JET to your project through the Bower command (bower install oraclejet --save), Bower will download all of jQuery UI and you will find each jQuery UI component inside your bower components folder (usually bower_components). Since the Oracle JET Yeoman generator also uses bower, the generator will also download all of jQuery UI into your bower components folder. But here is where it gets a little tricky. After Bower finishes, the Yeoman generator will move Oracle JET's real dependencies into /js/libs. A quick review of the files in /js/libs show that certain files, such as droppable, are missing. They are still in the bower_components folder, just not in the jQuery UI location identified in the Oracle JET RequireJS configuration. For Yeoman template users, this can be a little problematic since the RequireJS configuration already contains a declaration for jQuery UI, but that declaration doesn't include every jQuery UI file. For others that are using Bower directly, this is a non-issue. Your Oracle JET RequireJS configuration already points to jQuery UI and that location contains ALL jQuery UI files including source, minified, and combined files. So for bower users, "carry on." Yeoman users will want to copy the appropriate interaction file into the jQuery UI folder identified by the RequireJS configuration.

Import the Interaction File into a Module

Whether your ViewModels are pure knockout components or ojModules, chances are high you are using AMD-style modules through RequireJS. We need to tell RequireJS how to find our interaction plugin. jQuery UI interaction plugins are jQuery plugins, so we will want to import jQuery as well. Here is a sample AMD define block that references draggable and droppable from the standard Oracle JET RequireJS configuration location:

define([
  'jquery',
  'jqueryui-amd/draggable',
  'jqueryui-amd/droppable'
], function($) {

Attach the Interaction to a Component

Let's say we have a list of DOM elements that are supposed to be draggable and a couple more DOM elements that are supposed to be drop targets (droppable). Both draggable and droppable elements need to be enhanced by jQuery UI. Through CSS class selectors, etc, identifying those DOM elements should be trivial. In the asynchronous world of Knockout and Single Page Applications (SPA), the hard part is knowing when those DOM elements will exist. Can't we use $(document).ready? Maybe, but don't count on it. In an SPA, $(document).ready may have fired several views ago. Furthermore, the draggable elements may come from an asynchronous service, which means they may not exist at page load. Instead, we need DOM elements with lifecycle management events. For each enhanced DOM node, we need to know when that DOM node is available. Knockout gives us lifecycle management capability through custom bindings. I have written about ko.bindingHandlers on many occasions. What makes this example different is that it is very simple. Whereas others required init and update handlers, these interactions require only an init handler. Here are example ko.bindingHandlers implementations for draggable and droppable::

ko.bindingHandlers.jmDraggable = {
  init: function(element) {
    $(element)
      .draggable({
        revert: 'invalid',
        helper: "clone"
      });
  }
};

ko.bindingHandlers.jmDroppable = {
  init: function(element) {
    $(element)
      .droppable({
        drop: function(event, ui) {
          $(ui.draggable)
            .detach()
            .css({
              top: 0,
              left: 0
            })
            .appendTo($(this));
        }
      });
  }
};

When implementing a custom binding handler, you are creating your own custom knockout binding. You want to make sure your binding names don't collide with other binding names. With that in mind, I used the jm prefix to distinguish my bindings from other people's bindings. I would then use these bindings in HTML that looked something like this:

<div id="panelPage">
  <div class="oj-flex drag-items draggables oj-margin oj-padding"
       data-bind="jmDroppable, foreach: {data: labels, as: 'label'}">
    <div class="oj-panel oj-margin" data-bind="jmDraggable, css: label">
        <span data-bind="text: label"></span>
    </div>
    
  </div>
  
  <div class="drop-target oj-margin oj-padding"
       data-bind="jmDroppable">
    <span>Drop items here</span>
  </div>
  
</div>

Here is a working example that makes oj-panel draggable:

Wednesday, May 18, 2016

HTML5 Input Types for Oracle JET

I read an interesting question today, "Can you use HTML5 Input Types with Oracle JET's ojInputText?" Of course, the answer is "Yes" (the answer is always Yes). A better question is "How do you use HTML5 Input Types with Oracle JET's ojInputText?" OK, before we go there, let's answer the "Why" question: "Why would you use HTML5 Input Types with Oracle JET's ojInputText?" Oracle JET already includes number, text, and date input types, all styled according to the Oracle Alta specification. The reason for HTML5 Input Types? Device support. The idea behind HTML5 input types is to allow each device to display the most appropriate input method for a specific input type. This allows mobile web apps to maintain consistency with native mobile apps. For this reason, I am a HUGE fan of HTML5 input types. Note: desktop browsers don't have very good support for HTML5 Input Types.

Let's move onto the "How" question. Can you just set the type attribute of an ojInputText to "date" and get an HTML5 date input? No. Oracle JET will reset the type attribute to "text." The trick is to register a MutationObserver to listen for changes to the type attribute, and then reset it back to date (or whatever HTML5 input type you desire). Next question: How do I assign a MutationObserver to an instance of ojInputText? At this time, the best way I know to do this is with a knockout custom binding handler. You may remember that we used a custom binding handler last time we extended ojInputText (and then reused it a few more times). The reason for registering a ko.bindingHandler is to give us life cycle management events: we know when the DOM element is available and can enhance that element as we see fit. Here is an example of a custom component named html5DateInputText that extends ojInputText through ko.bindingHandlers. On initialization, the bindingHandler configures a MutationObserver that ensures the type attribute is always date. If you don't see a browser-specific date picker in the following example, then your browser might not support the date HTML5 input type. That doesn't mean the example is broken. It still works (depending on how you define the word "works") and falls back gracefully to a plain ojInputText.

Here is the custom binding handler:

var observerConfig = {
  attributes: true,
  childList: true,
  characterData: true
};
  
// Custom binding handler that wraps ojInputText and provides extra functionality
ko.bindingHandlers.html5DateInputText = {
    // setup extension to ojInputText as well as register event handlers
    init: function(element, valueAccessor, allBindingsAccessor, ctx) {
      var options = allBindingsAccessor().ojInputTextOptions || {};
      

      var observer = new MutationObserver(function(mutations) {
        ko.utils.arrayForEach(mutations, function(mutation) {
          if (mutation.type === 'attributes') {
            if (mutation.attributeName === 'type') {
              var $target = $(mutation.target);
              if ($target.attr('type') !== 'date') {
                $target.attr('type', 'date');
              }
            }
          }
        });   
      });
        
      observer.observe(element, observerConfig);
      
      $(element)
        .ojInputText(options)
        .on({
          'ojoptionchange': function (event, data) {
            //console.log(data)
            // use option === "value" for final value
            // use option === "rawValue" for each character
            if(data.option === "rawValue") {
              var val = data.value;
              var observable = valueAccessor();
              observable(val);
            }
          }
        })

      //handle disposal (if KO removes by the template binding)
      ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
        $(element).ojInputText("destroy");
        observer.disconnect();
        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);
    }
  };

If you want a different input type (tel, number, email and so on), then change lines 19 and 20 to match your target input type.

Note: The ojInputText Alta skin includes padding: 0 5px. The Chrome HTML5 Date spinners weren't centered using that padding so I added a line of CSS to reset padding to 5px on all sides.

Friday, May 27, 2016

Removing an Oracle JET Component from the Tab Order

A customer recently asked me if I could create a "quick entry" form that excluded certain fields from the regular tab order. Oracle JET input components have a tab index for a reason. They are designed for accessibility which means they work with accessible input devices and don't require a mouse. Removing data entry fields from the tab order means I have to use a pointing device, such as a mouse or tap, to select a skipped field. Although this goes against my better judgement, I understand the use case. If you are entering information in rapid, data entry mode, you don't want to tab over fields you rarely use. The issue I ran into is this:

How do you remove the tab index from certain Oracle JET components?

Input elements such as ojInputDate display a native HTML element and will automatically be part of the tab order. We can easily remove these elements from the tab order by setting the tabIndex to -1 like this:

<input tabindex="-1" data-bind="ojComponent: {
                    component: 'ojInputDate', value: selectedDate}" />

Other elements, such as ojSelect, use a clever collection of non-input HTML elements to capture input. For accessibility reasons, Oracle JET adds tabindex="0" to these input elements to ensure accessible devices can enter data. Since many of these Oracle JET elements begin life as a real HTML input element (input, select, etc), one might think that changing the tab index would be as easy as setting the tab index on the original input element. Unfortunately, it is not that easy. Another idea is to include tabIndex in the rootAttributes collection of an OJ component. Unfortunately, that doesn't work either. To remove these elements from the tab order, we must first identify the element with a tab index, and then change the value of the tab index. To do this effectively, we have to answer two questions:

  1. How do I identify the element with a tabIndex attribute?
  2. Timing wise, when will the DOM be ready for me to change the tabIndex?

Let's start with question #1. Study the following structure screenshot for a moment.

In the above screenshot, the JET generated oj-select is highlighted at the top. The very next element, the oj-select-choice element is the element with the tabIndex and is a child element of the oj-select. This is the element we want to modify and we need to find a way to programatically identify this element. Notice that the original select element is a sibling of the generated oj-select element. We could use a sibling selector. Sibling selectors are great for collections, where you want to style elements differently based on their position in a collection, but a sibling selector makes me a little nervous in this instance. I'm not sure we can depend on the sibling relationship identified in this screenshot. Rather than use a sibling selector, we can use Oracle JET's getNodeBySubId method. For Oracle JET components that are composed of several elements, we can identify individual pieces of the component by Sub ID. You can find the list of valid ojSelect Sub IDs here. We specifically want to select the oj-select-choice node. Unfortunately, oj-select-choice is not a node with a known Sub ID. If I expanded the oj-select-choice node in the structure screenshot, you would see that oj-select-chosen is a direct descendant of oj-select-choice. The oj-select-chosen node is selectable by Sub ID. With a little jQuery, we can easily traverse from oj-select-chosen up to oj-select-choice. We can test this by selecting our select element in the structure browser and then entering the following into the console:

$($0)
  .ojSelect( "getNodeBySubId", {'subId': 'oj-select-chosen'} )
  .closest( ".oj-select-choice" )

The following screenshot shows the results of that command (notice I also expanded the oj-select-choice to reveal the child oj-select-chosen).

A quick word about jQuery traversal methods... Using jQuery, there are often multiple ways to solve the same problem. In this situation we could either use the parents() or closest() methods to work our way up the hierarchy. Parents and Closest are similar, but, as the docs say, "The differences between the two, though subtle, are significant." We want to find the most immediate ancestor matching a selector. Closest accomplishes this. It identifies the match and then stops. The parents method, on the other hand, finds all matches and returns them as a collection. We have no need to walk the entire document hierarchy, so closest is the appropriate choice for this scenario. We have now answered question #1: How do I identify the element with a tabIndex attribute?

Next we need to handle the Life Cycle Management issue: How do we know when the ojSelect is available in the DOM (question #2)? Even though ojModule has Life Cycle Management events that tell us when the DOM is available, I don't suggest using them for this specific scenario. ojModule LCM events will work just fine with DOM elements that are hard coded into the view, but I'm not sure we should count on them for dynamically generated elements, such as those bound to an ko.observableArray. A better approach is to use a component-specific life cycle management approach such as custom bindings (ko.bindingHandlers). This allows us to manipulate an element as soon as it appears in the DOM. Here is an example ko.bindingHandler:

ko.bindingHandlers.inaccessibleOjSelect = {
  init: function(element, valueAccessor, allBindingsAccessor, ctx) {
    var options = allBindingsAccessor().ojSelectOptions || {};
    var multiple = !!options.multiple;
    var tabEl = $(element)
      // initialize ojSelect
      .ojSelect(options)
      // bind value change handler
      .on({
          'ojoptionchange': function (event, data) {
            if (data.option === "value") {
              var observable = valueAccessor();
              if (ko.isObservable(observable)) {
                if (multiple) {
                  observable(data.value);
                } else {
                  // unwrap from array if single select
                  observable(data.value[0]);
                }
              } // if not observable, just throw away the value
            }
          }
        })
      // get reference to item with tabIndex
      .ojSelect( "getNodeBySubId", {'subId': 'oj-select-chosen'})
      .closest(".oj-select-choice");

    $(tabEl).attr("tabIndex", -1);

  },
  update: function(element, valueAccessor) {
    var value = ko.utils.unwrapObservable(valueAccessor());
    $(element).ojSelect("option", "value", [value]);
  }
};

Note: The ojSelect value expects an array. In this JavaScript, notice that I distinguish between multi and single selection modes. For single-select ojSelect elements, I am unwrapping the value array and just returning the single value. This is for convenience and is in lieu of my other method for unwrapping ojSelect values.

You would use this with HTML similar to the following:

<select id="basicSelect" data-bind="inaccessibleOjSelect: browser,
                      ojSelectOptions: {optionChange: browserChangedHandler,
                           rootAttributes: {style:'max-width:20em'}}">
  <option value="IE">Internet Explorer
  <option value="FF">Firefox
  <option value="CH">Chrome
  <option value="OP">Opera
  <option value="SA">Safari
</select>

And finally, here is the jsFiddle so you can test it out:

As you review the code, you will notice that it is very ojSelect specific. As I mentioned before, not every Oracle JET input component requires a custom handler to change the tabIndex. For those that do, however, it wouldn't take much to rework this custom handler into something generic that could be used with a variety of ojComponent elements.

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.