Java DSL for object-oriented type-safe CSS styling

I am looking for something like this Java-CSS-Library, to implement server-generated CSS stylesheets backed by a type-safe object-oriented model of CSS classes and rule sets.

This is part of my ongoing quest for the perfect “pure Java” web development framework that would allow me to

  • Focus on my strongest area of expertise: Elegant Java code
  • Use refactorings and other advanced Java tooling in Eclipse or IntelliJ Community Edition
  • Ignore the HTML/HTTP vs JVM objects impedance mismatch as much as possible

Sharing IntelliJ project configuration (.idea, *.iml) in version control

Me and my team do exactly what Christof Schablins describes in this blog post and what the Jetbrains folks recommend here: We use IntelliJ IDEA and share *.iml files (modules) and most files in .idea directory (project config) in the Version Control System (in our case CVS), including shared ant configs, shared run configs for Tomcat 6 and JUnit tests, shared project specific code inspection profile, shared data source definitions, etc.

Overall it works great. But we noticed a few things:

  • Needed an IntelliJ Path variable TOMCAT_HOME because devs have Tomcat installed in different places
  • Dialog “Do you want to add workspace.xml to CVS” keeps popping up (despite an entry in .cvsignore file within the .idea directory)
  • If devs have different sets of IntelliJ plugins enabled, each plugin adds its default code inspection rules to the shared profile (even if the project does not use them, e.g. Ruby stuff in a pure Java project). So developers have to be cautious not to check in those changes (or accept the bloat they cause)

So my question is: Have you come across these or similar issues and did you solve them?

Turn org.w3c.dom.NodeList into Iterable

I recently posted some reusable code that facilitates reverse iteration over a list in Java.

Here is another potentially useful Java method related to iteration, from my buddy Jordan Armstrong:

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
  /**
   * @param n An XML node list
   * @return A newly created Iterable for the given node list, allowing 
   *         iteration over the nodes in a for-each loop. The iteration 
   *         behavior is undefined if concurrent modification of the node list
   *         occurs.
   */
  public static Iterable<Node> iterable(final NodeList n) {
    return new Iterable<Node>() {

      @Override
      public Iterator<Node> iterator() {

        return new Iterator<Node>() {

          int index = 0;

          @Override
          public boolean hasNext() {
            return index < n.getLength();
          }

          @Override
          public Node next() {
            if (hasNext()) {
              return n.item(index++);
            } else {
              throw new NoSuchElementException();
            }  
          }

          @Override
          public void remove() {
            throw new UnsupportedOperationException();
          }
        };
      }
    };
  }

Set Firefox “New Tab” page back to about:blank

Recent Firefox versions show a fancy “top sites” overview page every time the user opens a new (empty) tab. This is supposed to allow quick navigation to your most often visited sites.

If you (like me) don’t like the lag (and related security issues) that this adds to opening new tabs then set the new tab behavior back to showing a blank page:

  • Open “about:config” in the address bar
  • Acknowledge the warning to be careful
  • Search for “browser.newtab.url”
  • Right-click, Modify
  • Change the value from “about:newtab” to “about:blank”

Only after writing this blog post, I noticed that there is a Firefox help page that explains the same procedure.

Union and intersection types in Java

Ceylon has union and intersection types at the heart of its type system. Ironically, there is something in Java resembling these powerful concepts, but only in specialized niche contexts and not as “first class citizens” of the type system:

  1. Something like intersection types can be used in generic type boundary declarations (since Java 1.5)
  2. Something like union types can be declared in try-catch blocks that use the multi-catch feature (since Java 1.7)

Interestingly, the Java language engineers used the & and | operators, just like Ceylon. But I have found no evidence so far that they are considering expanding these concepts to the Java type system in general or even let Java developers use them freely in all type declarations …

Update Oct/2012: At JavaOne, I asked Brian Goetz about this. He said that the Java Language team at Oracle has no intention to add additional support – beyond the niches mentioned above – for Union or Intersection types to Java in the foreseeable future.

Autocomplete combobox using JQuery UI

Disclaimer, Sept 2013: The following worked for me with an older JQuery version, but not with 1.9 or later. The code is provided as is and might be useful for some people, but due to time constraints I cannot assist with problems you might run into.

The example at http://jqueryui.com/demos/autocomplete/combobox.html illustrates how you can use JQuery UI to turn any HTML <select> element into an auto-complete combobox.

Auto-complete means in this context that the select options are narrowed down by what the user has typed in so far.

The example has a few bugs and shortcomings, some of which have been fixed here. I build on this work and further fixed it up to support dynamic updates of the options list through event-triggered AJAX calls. You can find the source code of the resulting JQuery UI auto-complete combobox widget at the end of this post.

Expose it to your webapp as includes/js/combobox.js and include it from your HTML or JSP page like this (adjust the path according to your JQuery UI installation):

<script type="text/javascript" src="includes/js/jquery-ui-1.8.4/js/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="includes/js/jquery-ui-1.8.4/js/jquery-ui-1.8.4.custom.min.js"></script>
<script type="text/javascript" src="includes/js/combobox.js"></script>

And let’s say your <select> component looks like this:

<select id="mySelect">
  <option value="1">one</option>
  <option value="2">two</option>
  <option value="3">three</option>
  <!-- etc. -->
</select>

Then make it an auto-complete combobox widget like this:

$(document).ready(function() {
   $("#mySelect").combobox();
}

You can update the options dynamically using JQuery’s load() function, typically triggered by an event. The interesting part is that I implemented the internal _create() and _init() functions of the widget so that reinitialization through additional calls to combobox() are supported:

$("#mySelect").load(
    "dataRequest.do", // this has to be your own AJAX handler that generates the new options as HTML
    function() { // this callback function is invoked after the AJAX call has returned
        // then refresh the combobox widget (to use the updated options)
        $("#mySelect").combobox();
    }
);
/*
 Based on code from
 http://www.melrosesolutions.com/blog/index.cfm/2010/7/16/Turn-SELECTs-into-Comboboxes-with-jQuery-UI-Autocomplete
 */

(function($) {

    $.widget("ui.combobox", {

        _create: function() {
            var select = this.element;

            if (select.is(":disabled") 
                    || select.hasClass("inputOverlayCreated")) {
                return;
            }

            select.hide();

            // set up input text element
            var input = $("<input type='text'>");
            input.insertAfter(select);

            // remember that combobox is creates (to avoid odd duplicates)
            select.addClass("inputOverlayCreated");

            // the class ui-combobox-content is required for proper
            // highlighting of data changes
            input.addClass("ui-combobox-content ui-widget ui-corner-left");

            //clear text when user clicks in text input
            input.click(function() {
                $(this).val("");
            });

            input.attr("menustatus", "closed");

            // over-ride form submit, so it can't submit
            // if the menu is open
            var form = $(input).parents('form:first');
            $(form).submit(function(e) {
                return (input.attr('menustatus') == 'closed');
            });

            // set up button for fake 'select'
            var btn = $("<button>&nbsp;</button>");
            btn.attr("tabIndex", -1);
            btn.attr("title", "Show All Items");
            btn.insertAfter(input);
            btn.button({
                icons: {
                    primary: "ui-icon-triangle-1-s"
                },
                text: false
            });
            btn.removeClass("ui-corner-all");
            btn.addClass("ui-corner-right ui-button-icon");
            btn.click(function() {
                //event.preventDefault();
                // close if already visible
                if (input.autocomplete("widget").is(":visible")) {
                    input.autocomplete("close");
                    return false; // return false, so form isn't automatically submitted
                }
                // pass empty string as value to search for, displaying all results
                input.autocomplete("search", "");
                input.focus();
                return false; // return false, so form isn't automatically submitted
            });

            // add some styles
            btn.css("margin-left", "-1px");
            btn.css("padding", 0);
            $('span.ui-button-text', btn).css("padding", 0);

            input.css("margin", 0);
            input.css("padding", "0 0.4em 0 0.4em");
            input.css("width", select.outerWidth() - btn.outerWidth(true) - 10);// match the width
        },

        _init : function() {
            var select = this.element;

            if (select.is(":disabled")) {
                // we don't apply any fancy combobox behaviour at all
                // if the underlying drop-down list is disabled
                return;
            }

            var opts = new Array();
            $('option', select).each(function(index) {
                var opt = new Object();
                opt.val = $(this).val();
                opt.label = $(this).text();
                opts[opts.length] = opt;
            });

            var input = select.next();

            // initialise text with what's currently selected
            input.val($(':selected', select).text());

            input.autocomplete({
                source: opts,
                delay: 0,
                change: function(event, ui) {
                    if (!ui.item) {
                        // user didn't select an option, but what they typed may still match
                        var enteredString = $(this).val();
                        var stringMatch = false;
                        for (var i = 0; i < opts.length; i++) {
                            if (opts[i].label.toLowerCase() == enteredString.toLowerCase()) {
                                select.val(opts[i].val);// update (hidden) select
                                $(this).val(opts[i].label);// corrects any incorrect case
                                select.trigger("change");
                                stringMatch = true;
                                break;
                            }
                        }
                        if (!stringMatch) {
                            // remove invalid value, as it didn't match anything
                            $(this).val($(':selected', select).text());
                        }
                        return true;
                    }
                },
                select: function(event, ui) {
                    select.val(ui.item.val);// update (hidden) select
                    select.trigger("change");
                    return true;
                },
                // stop parent form from being while menu is open
                open: function(event, ui) {
                    input.attr("menustatus", "open");
                },
                close: function(event, ui) {
                    input.attr("menustatus", "closed");
                },
                minLength: 0
            });
        }
    });

})(jQuery);