April 29, 2013

Get list of REST controllers and actions in Grails

If your site's front-end makes REST calls back to the server to retrieve content, it's very helpful during development to have a quick and flexible way to experiment with different REST calls.  To build such a tool in a Grails environment, one needs to dynamically generate a list of REST resources (Grails controllers) and their associated methods* (Grails actions).

The getRestList() method below inspects the controller class to assemble such a list.

Service: SystemService.groovy
class SystemService implements ApplicationContextAware {

   def restPackageName = "com.example.webservice"
   def grailsApplication

   static String toDash(String camel) {
      //Example: "readySetGo" --> "ready-set-go"
      def joint = ~/[^A-Za-z0-9]|(?<=[a-z])(?=[A-Z])/
      def dashes = ~/-+/
      return camel?.trim().replaceAll(joint, "-").replaceAll(
         dashes, "-").toLowerCase()
      }

   def getRestList() {
      def rest = [:]
      grailsApplication.controllerClasses.each {
         if (it.packageName == restPackageName)
            rest[toDash(it.name)] =
               it.getURIs().collect({ uri -> toDash(
               it.getMethodActionName(uri))}).unique().sort() - "index"
            }
      return rest.sort()
      }
   }
The result is a Map of Arrays, like the following one.

restList: Controller Map of Action Arrays
[
   brand: ["list", "update"],
   product: ["list", "update", "merge", "delete"]
]
The Map is passed by the controller to the view.

Controller: OpsHubController.groovy
public class OpsHubController {

   def systemService

   def restTool() {
      render (restList: systemService.getRestList)
      }

   }
Then the view loops through the controllers and their actions to generate the HTML.

View: opsHub/index.gsp
...
<div class=rest_controllers>
   <g:each in="${restList}">
      <label>
         <input type=radio name=rest-resource value=${it.key}>${it.key}
      </label>
   </g:each>
</div>

<div class=rest_actions>
   <g:each in="${restList}">
      <div id=rest-actions-${it.key}>
         <label>
            <input type=radio name=rest-action_${it.key} value=index
               checked>[none]
         </label>
         <g:each var="action" in="${it.value}">
            <label>
               <input type=radio name=rest-action-${it.key}
                  value=${action}>${action}
            </label>
         </g:each>
      </div>
   </g:each>
</div>
...
JavaScript on the client hides actions not applicable to the current selected controller.  A couple of fields are added for the resource ID and URL parameters.

Screenshot
Each REST response is formatted in color for quicker analysis.

*Note: In theory, strictly RESTful approaches use the HTTP methods GET, PUT, POST, and DELETE.  However, it's easier to use Grails actions instead of different HTTP methods since browsers do a GET by default.

March 9, 2013

The world's simplest jQuery bubble help solution

There are numerous feature-packed jQuery plugins for displaying help bubbles and tooltips when a user's pointer hovers over a designated element.  However, if you only need a super simple, yet professional-looking, fast loading bubble help, it can be coded up in just over a dozen lines of JavaScript.  The pointy tip (a.k.a. the speech bubble tail) is implemented with the down triangle character, so no image file or convoluted CSS border tricks are required.


Without further ado, here's the world's simplest jQuery bubble help solution.

HTML: bubble-help class
<button>
   Hover over me
   <div class=bubble_help>I'm in a bubble, help!</div>
</button>
JavaScript: app.bubbleHelp module
if (!app)
   var app = {};

app.bubbleHelp = {
   //Usage:
   //   <div>Hover over me<div class=bubble-help>Help!</div></div>
   elem: null,
   hi: function(event) {
      var hoverElem = $(event.target).closest('.bubble-help-hover');
      app.bubbleHelp.elem = hoverElem.find('.bubble-wrap');
      if (!app.bubbleHelp.elem.exists())
         app.bubbleHelp.elem = hoverElem.find('.bubble-help')
            .wrap('<div class=bubble-wrap></div>')
            .parent().append('<div>&#9660;</div>');  //down triangle
      app.bubbleHelp.elem.find('.bubble-help').show();
      app.bubbleHelp.elem.css('top', -app.bubbleHelp.elem.height())
         .hide().fadeIn();
      },
   bye: function() {
      app.bubbleHelp.elem.fadeOut('slow');
      },
   setup: function() {
      $('.bubble-help').parent().addClass('bubble-help-hover')
         .hoverIntent(this.hi, this.bye);
      }
   };

$(function() {
   app.bubbleHelp.setup();
   });
The setup function applies the hover event that triggers displaying the help bubble popup, but the bubble help DOM elements are not created until they are needed for the first time.

DOM: Elements after first hover
<div class=bubble_help_hover>
   Hover Over Me
   <div class=bubble_wrap>
      <div class=bubble_help>Help!</div>
      <div>▼</div>
   </div>
</div>
A little styling brings the help bubble to life.

CSS Less: Styling
.BubbleHelp(@color, @bkgnd) {
   position: relative;
   .bubble-help {
      display: none;  //initial state before first hover
      }
   .bubble-wrap {
      position: absolute;
      left: 0px;
      pointer-events: none;
      z-index: 200;
      .bubble-help {
         white-space: nowrap;
         .Font12;
         color: @color;
         background-color: @bkgnd;
         border-radius: 5px;
         padding: 5px 15px;
         }
      >div+div {
         .Font14;
         line-height: 1em;
         color: @bkgnd;
         padding-left: 10px;
         margin-top: -0.2em;
         }
      }
   }

.bubble-help-hover {
   .BubbleHelp(white, fadeout(crimson, 20%));
   }
The above JavaScript incorporates the hoverIntent jQuery plugin to improve the user experience, and the styling is done with CSS Less to make the CSS more manageable.  See the jsFiddle version below for the plain vanilla version.

You can fiddle with it yourself:

February 1, 2013

More than 50x JavaScript performance improvement by delaying initialization

JavaScript performance improves dramatically when elements are initialized as needed rather than when the page loads.  Web page components that are not displayed until after a user event, such as a click or hover, can generally be configured after the event fires.  Traditionally, this configuration occurs on page load, where performance is most noticeable to the user.

Transitional element lifecycle:
  1. Choose components with a jQuery selector
  2. Initialize components
  3. Apply event handlers for closure function
  4. page displays
  5. User event fires and closure displays the component

Example JavaScript for traditional approach
function setupDialogs() {
   $('.dialog').dialog({ autoOpen: false });
   $('.dialog-show').click(function() {
      var name = $(this).data('dialog');
      var elem = $('.dialog[data-dialog=' + name + ']');
      elem.dialog('open');
   });
}
$(function() {
   setupDialogs();
});
Delaying initialization of the components until the user event results in the page loading more quickly.

Lazy element lifecycle:
  1. Hide components with CSS
  2. Apply event handlers for function
  3. page displays
  4. User event fires and calls function
  5. Function initializes component only if needed (first time)
  6. Function displays component

HTML for button that opens a jQuery Dialog
<div class=dialog data-dialog=hello>Hello!</div>
<button class=dialog_show data-dialog=hello>Click me</button>
JavaScript library.dialog module
if (!library)
   var library = {};

library.dialog = {
   show: function(event) {
      var name = $(event.target).data('dialog');
      var elem = $('.dialog[data-dialog=' + name + ']');
      if (!elem.hasClass('ui-widget-content'))
         elem.dialog({ autoOpen: false });
      elem.dialog('open');
      },
   setup: function() {
      $('.dialog-show').click(library.dialog.show);
      }
   };

$(function() {
   library.dialog.setup();
   });
The component is not configured when the pages loads, so use CSS to hide the component until it is configured.

CSS
.dialog {
   display: none;
   }
Delayed initialization works great for components like jQuery UI Dialog boxes.  A simple test using performance.now() shows well over 50 times better performance.

Screenshot
Even better than the performance gain is the inherent support for dynamically loaded Ajax content.  The displayed component is not needed until the user event occurs, so it can be inserted into the DOM after the event handler is created.

You can even use jQuery's on() function to delegate the event handler so that both the displayed component and the event target component can be Ajax loaded into the DOM after the page loads.

Delegate the event handler
$(document).on('click', '.dialog-show', library.dialog.show);
Test out the performance in your browser: