Tuesday, June 14, 2016

Modular Views in Knockout and Oracle JET

"I have a View and ViewModel and I'm wondering if I should break it into multiple child Views and ViewModels." This is a great question and one that I've asked myself many times. Here are my criteria:

  1. Can I divide my workload, allowing others to help me if I convert sections of a view into sub views? (avoid merge conflicts)
  2. Am I using scope-changing constructs such as ko foreach?
  3. Is my ViewModel (or view) over 100 lines?

Conflict: I suppose there are people that take great pleasure in resolving merge conflicts. I'm not one of them. I can't say that I'm a fan of any type of conflict. Merge conflicts fall into that same "conflict/resolution" bucket. If I am working on a project with a teammate and we both need to access the same view, then I may give great consideration to partitioning that view.

Scope: It is quite common to use ko foreach and other scope-changing constructs. Often I find myself iterating over a data set. For each element in that data set, it is given that I will have to respond to some type of event: click to delete a row, view details, and so on. It is at this point, when I'm writing the event handler, that I realize I have changed scope. I have 3 options:

  • Put my event handler at the root of my ViewModel and reference it using context variables ($page) and then try to figure out how to identify the current element (such as ko.dataFor(event.target)),
  • Enrich my child data model with event handler methods, or
  • Move the contents of the ko foreach into a new view and ViewModel.

I'm not fond of $parent and other context-related variables. It seems too easy to lose sight of the real scope and couples my solution in a manner I'm not sure I prefer. As an alternative, I often place the content of a ko foreach in a new view and ViewModel so that event handlers and other ViewModel methods can interact with my data without scope issues. This is sort of like a scope reset.

100 lines... OK, you may have to humor me with this one. I find 100 lines to be easy to comprehend. Once I go over this threshold, code becomes more difficult to follow. I can read 100 lines with three presses of the page down key. I can keep that all in my brain and understand it without much scrolling (I suppose you could say my internal page size is 3 pages?).

Styling Custom Components with Oracle JET SCSS Variables

In my post Extending Oracle JET Components Through Custom Bindings, I used CSS to move the ojInputText border from the input element to the ojInputText "root" element. One of my readers commented on my improper use of color values instead of SCSS variables. Great point! Oracle JET includes SCSS theme variables for color and style. Since my goal was to maintain the delivered Oracle JET styling, it is better to use the Oracle declared SCSS theme variables. Here is what that prior example would look like if I used Oracle JET's theme-specific SCSS variables:

// following three lines taken from oj-alta.scss
@import "../bower_components/oraclejet/dist/scss/alta/oj.alta.variables";
// note: this generates a very large css file and takes a while to compile.
// if you are just interested in the variables, you can cheat and comment out
// these two imports
@import "../bower_components/oraclejet/dist/scss/3rdparty/normalize/normalize";
@import "../bower_components/oraclejet/dist/scss/alta/oj.alta.widgets";

.content-container {
  margin: 10px;
}

/* icon CSS here */
.birthday-input:after {
    font: normal normal normal 14px/1 FontAwesome;
    content: "\f1fd";
    margin: 0 10px;
}

.birthday-input input {
    border: none;
    flex: 1;
}

.oj-inputtext.birthday-input {
    display: flex;
    flex-flow: row nowrap;
    align-items: center;
    background-color: $background3Color;
    border: 1px solid $border4Color;
}

Notice the import at the top of this code fragment. This is a relative path pointing to the Oracle JET SCSS files within the bower install directory. In my case I store scss in a "styles" subfolder of the root project folder, making this path a relative path from ./styles to ./bower_componnets.

This solution assumes that you have your project configured for SCSS (grunt tasks, etc). Be aware that SCSS is not part of the Yeoman generated Oracle JET build script. You can learn more about Oracle JET and SCSS in the document Customizing an Oracle JET Theme Using Sass

Unfortunately, I can't exactly show this in a jsFiddle because the SCSS in jsFiddle doesn't appear to support remote scss partials. I can, however, paste in the relevant oj.alta.variables and pretend that I am importing the real Alta variables. In the sample below, instead of modifying one of my prior examples, this is another iteration of the "ojInputText with animated clear button" that has better support for accessibility (you can now tab to the delete button if it is visible).

Friday, June 3, 2016

Oracle JET on Cloud 9

I really like the collaborative editing features of Cloud9 (c9), but can I use them to collaboratively build Oracle JET applications? C9 is sort of like Google Docs for developers, allowing multiple users to work on the same file at the same time. You can forget about painful merges with source code systems because c9 allows everyone to work on the same files at the same time. It also doesn't hurt that c9 gives full access to the command line and provides an amazing editing experience.

Given c9's command line and node support, I was pretty sure that following the Getting Started Guide (steps 1 - 3) would get me up and running in a matter of minutes. After a grunt build, you can open index.html, click the run button, and explore the QuickStart Template. But what about grunt serve and livereload? There really isn't any magic in Oracle JET, so the first step is to see if anyone else has connect and livereload working in c9. This Stackoverflow post has a lot of great information as does this c9 doc and this c9 example. Based on that information, we see that c9 runs apache httpd on port 80 and makes ports 8080, 8081, and 8082 available. Our goal is to get connect and livereload running on those last three available ports. As that Stackoverflow post noted, 8080 was in use because I used the run button inside c9 to launch my application. If you want c9's default run feature to work, then I suggest you use port 8081 for the server and port 8082 for livereload.

Now that we have some available ports, the next hurdle is getting Oracle JET's grunt file to use them. Fortunately, the Oracle JET engineers already considered this and included command line options for optional ports. The following command will launch a web server on port 8080 with livereolad on 8081:

grunt serve --serverPort=8080 --livereloadPort=8081

This will get our app running in dev mode, but unfortunately, it won't be accessible from outside the c9 VM. If you test launching your app with the URL http://[yourworkspacename]-[yourusername].c9users.io:8080/, you will see a nice c9 message telling you that nothing is running on 8080. If you try the same URL with port 8081, however, you will see that livereload is working and waiting.

With that in mind, our final hurdle is to get grunt serving our app on an address we can access outside the VM. Unfortunately, this isn't configurable and will require us to change one line in the Oracle JET grunt configuration scripts. Within the scripts directory of your project, navigate to grunt/config and open connect.js. Inside the devServer options, right about line 14, you will see hostname. Just delete the contents of that string (should be localhost). Don't comment out the line, because that causes livereload to misbehave. The hostname option should now look like this:

hostname: "",

From the c9 terminal window, relaunch grunt serve using the command supplied above and test your project URL (http://[yourworkspacename]-[yourusername].c9users.io:8080/). You should be greeted by a fully functional QuickStart template that reloads when you modify and save files within your project. Now go and build something amazing!

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.

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.

Monday, May 9, 2016

rateLimit'ing ojInputText using a Read-only Observable (Debouncing the rawValue)

In my post Filtering Oracle JET ArrayTableDataSource I showed how to use an ojInputText to filter tabular data. In that post I mentioned using the knockout rateLimit extender, but shied away from it because rateLimit'ing an observable attached to an ojInputText rawValue throws the error, "rawValue option cannot be set" (even though rateLimit'ing in this manner works). It is true that rawValue is a read only field. For some reason, rateLimit'ing an observable causes a write to the component property bound to the observable. This makes sense because that is the point of an observable: update something after the observable value changes. The problem is that rawValue is read-only. I raised this issue with my colleagues and Jeanne Waldman shared this idea:

Create a pureComputed based off the rawValue observable and rateLimit the pureComputed.

Interesting. Jeanne's idea essentially creates a read-only observable that we can then rateLimit, effectively isolating rawValue from a write attempt. Here are the JavaScript changes necessary to implement Jeanne's idea:


// ... other code above here
ko.pureComputed(self.nameSearch)
  .extend({
    rateLimit: {
      timeout: 250,
      method: "notifyWhenChangesStop"
    }
  })
  .subscribe(function(newValue) {
    self.datasource.reset(
      filteredEmployees(newValue)
    );
  });

// ... other code below here

Notice that we are wrapping the previously used nameSearch observable in a ko.pureComputed, extending the ko.pureComputed, and then subscribing to the rate limited observable. Here is the jsFiddle:

Success! Our filter code now only runs 250 milliseconds after user input pauses. Considering our search algorithm, this will definitely perform better than invoking the algorithm for each key press. But is it as good as it could be? I'm not sure. We are now rate limiting a second observable rather than the primary observable. Will the primary observable continue to emit events with each key press? Did we trade an innocuous error message for some CPU/memory overhead? Does it matter? Normally, given two working solutions, I would choose performance, but I really don't want to see error messages cluttering my console.

Friday, May 6, 2016

Toast and Growl Notifications with Oracle JET

My colleague Paul Thaden just posted Creating a Popup Message Box for Oracle JET Forms which uses Oracle JET's Offcanvas functionality. While not exactly "toast," that was my first thought when I saw it. I really like the toast/growl notification concept and have been wondering how to add this type of notification to an Oracle JET project. Let's take Paul's work and convert it into a growl-like (or toast) notification complete with timeout. We will start with this modified version of Paul's jsFiddle. This fiddle displays a notification message when you change the selected browser.

Paul already explained the whole inner-wrapper/outer-wrapper thing so I won't repeat it here. Mainly I want to focus on styling the oj-offcanvas-top element (named toast) so that it floats on the right side of the page and then disappears after a specified period of time. Here is the only CSS we need:

#toast {
  /* Make the message look pretty */
  border: 1px solid #0572ce;
  box-shadow: 0 1px 3px #777;
 
  /* set the location of the toast message */
  left: auto;
  right: 10px;
  width: 200px;

  /* z-index required to overlay oj form controls */
  z-index: 1;
}

Next we refactor the ojSelect optionChange event handler from the prior jsFiddle to close the notification message after a specified interval:

var closeToast = function() {
  return oj.OffcanvasUtils.close(toastOptions);
};

self.browserChangedHandler = function (event, data) {
  if (data.option == "value") {
    oj.OffcanvasUtils.open(toastOptions);
    window.setTimeout(closeToast, 5*1000);
  }
};

Here is the jsFiddle example. Select a browser and watch the message appear and then disappear.

That is nice for a "Transaction saved" type of message, but let's get dramatic and make this look like a real growl notification. With a little bit of CSS, we can turn that Offcanvas notification into something like this:

And here is the jsFiddle: