Showing posts with label jQuery. Show all posts
Showing posts with label jQuery. 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:

Thursday, April 28, 2016

Animate Oracle JET Utility Classes (oj-selected)

Sometimes I build card-like user interfaces that allow users to select from a group of "cards." The visual state of a selected "card" should change once selected. The Oracle JET oj-panel, oj-margin, and oj-selected utility classes are great for this type of experience. The oj-panel and oj-margin classes setup the cards according to the Alta UI design patterns and oj-selected changes the appearance upon selection. Toggling oj-selected with a click binding is trivial with jQuery.toggleClass().

Out of the box, the oj-selected visual changes are either on or off. There are no transition states. I like user interface changes to morph between states. With just a couple of lines of CSS, we can animate what happens to an oj-panel when oj-selected is added to the panel:

.oj-panel {
  cursor: pointer;
  transition: border-color .3s ease-in-out;
}

Here is the jsFiddle

Is the fade effect too subtle for you? Take a look at this CodePen for some impressive animation examples.

Wednesday, April 27, 2016

Animated "Clear" button in ojInputText

Last week I posted Extending Oracle JET Components through Custom Bindings (Input Text with Buttons). About three quarters of the way through that post, I included a jsFiddle that shows how to add a delete button inside ojInputText. One of my readers asked if it was possible to hide the delete button if the ojInputText is empty. The answer, of course, is YES. The first thing we need to do is modify our ojoptionchange event handler to listen for the rawValue option instead of the value option. rawValue changes with each key press whereas value changes when the component loses focus.

.on({
  'ojoptionchange': function(event, data) {
    // use option === "value" for final value
    // use option === "rawValue" for each character
    if (data.option === "rawValue") {
      var observable = valueAccessor();
      //
      // ** show/hide icon code goes here
      //
      observable($(element).ojInputText("option", "value"));
    }
  }
})

If all we want to do is show/hide the "button" within the ojInputText, then we can toggle the oj-helper-hidden utility class on the i button element. That will result in the button instantly appearing or disappearing based on the length of the ojInputText element's value. I'm not a fan of jarring UX changes. If something is going to appear on the screen, I want it to fade in. The gradual appearance helps prepare my mind for the existence of a new user interface element while the movement draws my attention to this new element. Unfortunately, we can't animate the oj-helper-hidden class. The oj-helper-hidden class changes the display attribute and CSS does not support animating changes between the states of that attribute. We can, however, change the appearance of an element by animating the element's opacity. The following jsFiddle uses the opacity approach to toggle the appearance of the delete/clear button within ojInputText.

Extra credit: Compare these jsFiddles: http://jsfiddle.net/jmarion/tbr4k30n/ and http://jsfiddle.net/jmarion/5e10mx8z/. On line 62 of the first fiddle (my first post on this subject), I use jQuery to identify the value option of the ojInputText. In the second fiddle (this fiddle), lines 87 and 91 do the exact same thing, but without the jQuery method invocation. I got smarter the second time around. the ojOptionChange event actually includes the current value in the data object. I don't need to invoke a jQuery plugin method to retrieve the current value.

Note: I included CSS to change the cursor when the button becomes transparent, but the JavaScript click event will still trigger. Since the button is transparent when the ojInputText has no value, this should not be an issue. The net result of deleting nothing is... yep, you guessed it: nothing.

Thursday, April 14, 2016

Fade-in Animation for Oracle JET ojDialog

It may just be my eye-mind coordination (or lack thereof), but I have trouble adjusting to abrupt user interface changes. For example, if I click a button and a dialog box instantly appears, my business-related thought processes pause for a moment while I try to mentally digest what just happened and why. It is mentally easier for me if user interface changes occur through fast, but gradual animations. Don't waste my time with a slow animation... but then again, don't waste my time with no animation. I function best when enterprise application developers use animations to prepare my mind for the next step. So here I am developing an enterprise application with Oracle JET and using ojDialog. As my ojDialog appears, I think to myself, "Where is the animation?"

Here is how I added fade-in animation to ojDialog. Starting from the ojDialog cookbook recipe, I added the class animate as a rootAttribute to identify that this dialog should be animated:

<div id="dialogWrapper">
  <div style="display:none" id="modalDialog1" title="Modal Dialog" 
       data-bind="ojComponent:{component: 'ojDialog',
                               initialVisibility: 'hide',
                               rootAttributes: {
                                 class: 'animate'
                               }}">
     <div class="oj-dialog-body">
       The dialog window can be moved, resized and closed with the 'x' icon.
       Arbitrary content can be added to the the oj-dialog-body and
       oj-dialog-footer sections.
     </div>
     <div class="oj-dialog-footer">
        <button id="okButton" data-bind="click: closeDialog,
                                         ojComponent: {component: 'ojButton',
                                                       label: 'OK'}"> 
      </div>
  </div>
  <button id="buttonOpener" data-bind="click: openDialog,  
                                       ojComponent: {component: 'ojButton',
                                                     label: 'Open Modal Dialog'}">

</div>

Next I added some CSS to set the initial opacity for all animated ojDialogs to 0, meaning fully transparent:

.oj-dialog.animate {
  opacity: 0;
  transition: opacity .25s ease-in-out;
}

Now when I invoke the open method, the dialog will appear, but it will be fully transparent which means I won't actually be able to see it (talk about usability and user interface design!). Then I added a new selector and corresponding CSS to represent a dialog in the fully opaque state:

.oj-dialog.animate.fully-opaque {
  opacity: 1;
}

The plan is to toggle the fully-opaque class on the oj-dialog.animate selector when the dialog opens. Let's revisit that dialog HTML definition and add an open event handler:

<div style="display:none" id="modalDialog1" title="Modal Dialog" 
     data-bind="ojComponent:{component: 'ojDialog',
                             initialVisibility: 'hide',
                             open: dialogOpen,
                             rootAttributes: {
                               class: 'animate'
                             }}">

And now the JavaScript to handle the open event:

    self.dialogOpen = function(event, ui) {
      $(event.target)
        .closest(".oj-dialog.animate")
        .addClass("fully-opaque");
    };

Since we added a CSS transform to oj-dialog.animate the ojDialog container will fade in from 0 to 100% opacity. Notice the interesting selector in that JavaScript fragment? The event target is the ojDialog div we defined, but Oracle JET wraps that element in a new root element. It is THAT element we want to animate (hence the use of rootAttributes in the ojDialog definition). There are probably a million different selectors I could have chosen. In fact, $(event.target).parent() was my first idea, but then I thought, "what if the ojDialog structure changes?"

If you followed these steps, you should now have an ojDialog with a fade-in effect. What about fade-out? I don't have a good answer here. I tried both the close and beforeClose events, but those happen too quickly, meaning the dialog display is changed to hidden before the animation. My not-so-good work-around is to perform the animation when the user clicks the OK button in the dialog footer:

self.closeDialog = function(data, event) {

  // attempt fadeOut with jQuery
  $(event.target)
    .closest(".oj-dialog.animate")
    .fadeOut(function() {
      $("#modalDialog1").ojDialog("close");
    });
  };

I put together a jsFiddle named ojDialog Fade-in to demonstrate the animated ojDialog. Once Oracle JET starts accepting pull requests, perhaps animations would be a valuable community enhancement?