Bootstrap keyboard navigable dropdown list with AngularJS

This time I comes with a problem I’ve faced in a project I’m currently working. We have some groups of Bootstrap components that mix a button with a HTML unordered list (button + ul tags).

The problem was that the user was not able to use the keyboard (cursor keys) to navigate through the elements of the list, so for a big form like the ones we have, changing keyboard to mouse is annoying for the users. They must fill lot of information and is always faster to use the keyboard.

8767790

So we had something like this (code simplified and cleaned to remove own CSS classes and other not related stuff):

<div class="btn-group">
    <button type="button" class="btn btn-default dropdown-toggle btn-block" data-toggle="dropdown">
        <span><fmt:message key="generic.no.selection" /></span>
    </button>
    <ul class="dropdown-menu">
        <li ng-repeat="element in elements.getPossibleValues()">
            <a ng-click="doSomething()">element.code</a>
        </li>
    </ul>
</div>

There are several solutions to solve this situation. I started trying to solve it directly with JQuery using a ngController function that was triggered when the user clicked (with mouse or keyboard) the button. Then I tried to do it more generic and cleaner, so I implemented an AngularJS directive, so now other developers only need to add an attribute to the button tag:

/**
 * Binds an on click function that sets the focus on the first element of the dropdown next to
 * the element that has the attribute 'focus-on-first-dropdown-element'. 
 */
uiModule.directive("focusOnFirstDropdownElement", function() {
    return {
        restrict: 'A',
        link: function (scope, element) {
	    element.bind('click', 
            function() {
                // Selects the sibling UL that contains the dropdown elements
                var list = element.next();
                // Put the focus on the first LI (a) element after some ms. The timeout is needed to give time to
                // show the list. If the timeout is not setup, the focus goes to the input text box associated to the button
                setTimeout(function() {
                    list.find('a:first').focus();
                }, 100);
            });
        }
    };
});

Also some modifications to the HTML code must be done:

  1. HTML <ul> tag needs to have the attribute role=”menu” (see Bootstrap).
  2. HTML <a> tags within the <li> elements must have an href attribute.
  3. Button tag must use the directive focusOnFirstDropdownElement.

So the new HTML after the changes is:

<div class="btn-group">
    <button type="button" focus-on-first-dropdown-element class="btn btn-default dropdown-toggle btn-block" data-toggle="dropdown">
        <span><fmt:message key="generic.no.selection" /></span>
    </button>
    <ul class="dropdown-menu" role="menu">
        <li ng-repeat="element in elements.getPossibleValues()">
            <a href="" ng-click="doSomething()">element.code</a>
        </li>
    </ul>
</div>

After that, now the user is able to use the keyboard to use the dropdown list.

Enjoy!

Advertisements

Book review: Three.js Essentials

This time I’m going to write about another little book I’ve been reading last weeks, Three.js: Essentials.

ThreeJS Frontcover

The book is an introduction or a wide getting starting guide to several features of Three.js Javascript library. Three.js is a high level WebGL wrapper, that tries to make easier to work with WebGL on the browser, so you will be able to render 2D/3D objects using Javascript/HTML5.

The book is aimed to people who want to learn a bit about Three.js. The author says you must know Javascript but not WebGL, maths or 3D rendering. I agree with him about you don’t need to know WebGL or 3D rendering, but you should have knowledge about geometry at least, if not, some of the concepts of the book will be hard to understand. It’s obvious to me that if you’re interested in a 3D library means that you know a bit of geometry (x-y-z axies, vectors, matrix algebra, planes, etc.). If you don’t you can follow the book and the samples, but as I’ve already said, it’ll be harder.

Three.js: Essentials is like a big tutorial where you will find lot of code examples of everything explained along the book. The code is very easy to read and modify, so you’ll be able to do experiments and learn how Three.js works. The book has seven chapters where you’ll learn how to draw 3D objects, animate them, do collision detection, apply light, materials and textures, make 3D random mazes, control objects with mouse or keyboard, generate particle systems, combine HTML5 with Three.js or learning how to use Blender along Three.js.

If you want to start working with 3D in the browser this book is really helpful, but you’ll need to combine it with a complete reference of Three.js, because as the author says, there are a lot of features that are not covered and will give you lots of possibilities to do interesting things.

The good
Really helpful introduction to Three.js. Lot of easy to follow code samples.
Not too long and easy to read.

The bad
Several complex concepts (mesh, textures, materials, matrix algebra, etc.) can be hard to understand for people new to 3D.

Conclusion
If you want to learn about Three.js and already know a bit about 3D buy this book because it teachs a lot of concepts of the library and is a cheap book.

Redirect after AJAX request / Control authentication failure after AJAX request

Last two days I’ve been dealing with a problem I’ve had with some of the projects I work on. For our Java web apps we use an authentication/authorization filter that redirects to a login page after an authentication/authorization error. This error can be a login error, a session timeout or other things.

Redirect calls are managed by browsers without problems, they do the request for the new location and the login page is presented to you. But if your app does AJAX requests, as every app nowadays does, you’ll get into troubles, the browser won’t do any redirection, so your app will stay in the same page as before the request, and probably the user will be annoyed because your app doesn’t respond or does strange things. The login page won’t be shown until the user clicks on a non AJAX component (menu, link or so), or does a refresh.

Usually the authentication filter does a sendRedirect to the login page when there is an authentication problem:

response.sendRedirect(response.encodeRedirectURL(url));
response.flushBuffer();

So after some research I found that doing a manual redirect should solve the problem with AJAX calls. Really I didn’t know why doing this would solve the problem, but I tried it:

...
if (isAjax(request)) {
    StringBuilder sb = new StringBuilder();
    sb.append("<?xml version='1.0' encoding='UTF-8'?>");
    sb.append("<partial-response><redirect url=\"").append(response.encodeRedirectURL(url.toString())).append("\"/></partial-response>");
    response.getWriter().print(sb.toString());
    response.flushBuffer();
}

private boolean isAjax(HttpServletRequest request) {
    return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
}

Even I tried to send a redirect HTTP’s 302 code or adding a Location header… without any succeed:

if (isAjax(request)) {
    response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
    response.setHeader("Location", response.encodeRedirectURL(url.toString()));
    StringBuilder sb = new StringBuilder();
    sb.append("<?xml version='1.0' encoding='UTF-8'?>");
    sb.append("<partial-response><redirect url=\"").append(response.encodeRedirectURL(url.toString())).append("\"/></partial-response>");
    response.getWriter().print(sb.toString());
    response.flushBuffer();
}

private boolean isAjax(HttpServletRequest request) {
    return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
}

Then I tried to solve it from the other side, thus is, the client. I didn’t like it, because it is easier to distribute a JAR file with the authentication filter solution than telling people what they must do in every app they are developing. Although we have projects with JSP/JQuery or Seam 2, the last apps we’re working on use Primefaces, so I tried to find a solution related to this framework. First I tried to catch onComplete events within ajaxStatus Primefaces tag, but this component doesn’t pass the XMLHttpRequest argument to the callbacks, also I tried to bind a callback to onComplete events at body or document level, but any result was good.

Finally I went for a more drastic and I think the most secure solution: Setting up every AJAX call that could be made within our apps to do what I want when an authentication error raised. For this, JQuery has a solution, the $.ajaxSetup function. With it you can define at a global level how AJAX calls behave, and when I say at a global level I mean that every AJAX call from your code, or from whatever library or framework you are using will do what you define with this function. So first of all I changed the HTTP code the filter was sending, instead of a 302 now it sends a 401 (unauthorized), then I defined the behaviour of AJAX calls after receiving this code:

First the changes in the Java code:

if (isAjax(request)) {
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    response.setHeader("Location", response.encodeRedirectURL(url.toString()));
    response.flushBuffer();
}
private boolean isAjax(HttpServletRequest request) {
    return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
}

Now the Javascript code:

function requestUnauthorized(xhr) {
    window.location.href = xhr.getResponseHeader("Location");
}
 
(function($) {
    $.ajaxSetup({
        statusCode: {
            401: requestUnauthorized,
        }
    });
})(window.jQuery);

The syntax…

(function($){.....})(window.jQuery) 

…means that you’re defining an anonymous function with an parameter called $, that will be self-executed with window.jQuery as value. It’s better to use window.jQuery because some libraries or frameworks rename jQuery to $, $$ or whatever they want.

And that’s all, you have to put the Javascript code somewhere that is visible for the whole app.

Validador de Javascript en Eclipse

Como he visto que la entrada Javascript validator problem in Eclipse es bastante popular lo voy a traducir al español para los lectores hispanohablantes.

Este es un problema que me ha pasado con varios proyectos de tipo ‘Web Dynamic’. Cada vez que el proyecto se compila Eclipse me da este error:

“Errors occurred during the build.
Errors running builder ‘JavaScript Validator’ on project ‘some-project’.
java.lang.NullPointerException”

En un principio parece que el validador de javascript está lanzando la excepción NullPointerException, así que probablemente lo primero que intentas es deshabilitar el validador de Javascript ‘Client-side Javascript’ desde Project->Properties->Validation… pero ya te digo que el problema no es ese, debes ir a Project->Properties->Builders y ahí deshabilitar el ‘Javascript Validator’.

Problema resuelto. En realidad no es un verdadero problema, pero el mensaje es bastante molesto si aparece cada vez que se compila el proyecto…

RowExpander with several ExtJs’ GridPanels

We’re using ExtJs 2.3 in some of our apps, so for a new feature I decided to have several tabs with a gridPanel each one. The problem was that users can expand the rows, so when a user expand the 3rd row from the panel N, the content was updated on the first panel because the RowExpander.beforeExpand function doesn’t consider that there are more grid panels into the page.

Well, finally I’ve found the solution, I’ve overriden RowExpander.beforeExpand function so it selects the correct row from the correct grid panel.

beforeExpand was originally:

beforeExpand : function( record, body, rowIndex ) {
//alert( "beforeExpand: " + rowIndex + " - " + record.data + " - " + body);
        if( this.fireEvent('beforeexpand', this, record, body, rowIndex ) !== false ) {
            if( this.lazyRender ) {
                var body = $('div.x-grid3-row-body')[rowIndex];
                body.innerHTML = this.getBodyContent( record, rowIndex );
            }
            return true;
        } else {
            return false;
        }
    }, 

…Overriding beforeExpand using Ext.override function:

Ext.override(Ext.grid.RowExpander, {
        beforeExpand : function( record, body, rowIndex ) {
            if( this.fireEvent('beforeexpand', this, record, body, rowIndex ) !== false ) {
                if( this.lazyRender ) {
                     if(typeof rowIndex == 'number'){
                        var body = $('#'+ this.grid.getId() + ' div.x-grid3-row-body')[rowIndex];
                      }
                    body.innerHTML = this.getBodyContent( record, rowIndex );
                }
                return true;
            } else {
                return false;
            }
        }
    }); 

As you may notice, I’ve only changed:

var body = $('div.x-grid3-row-body')[rowIndex];

by

var body = $('#'+ this.grid.getId() + ' div.x-grid3-row-body')[rowIndex];

so if there are more than one grid in the page, it’ll select the selected row from tha grid.

Javascript Validator problem in Eclipse

This is a problem that has happened to me on some Web Dynamic projects. Every time the project is built, I get this error:

“Errors occurred during the build.
Errors running builder ‘JavaScript Validator’ on project ‘some-project’.
java.lang.NullPointerException”

At a first sight it seems that a Javascript validator is throwing a NullPointerException, so probably you will try to disable ‘Client-side Javascript’ validation from Project->Properties->Validation… but I tell you that the problem is not there, you must go to Project->Properties->Builders then disable ‘Javascript Validator’.

Problem solved. Indeed is not a real problem, but the message is a bit annoying because it happens in every build so….