Category Archives: java/jsp

Widgets in GWT Cell Tables

I have recently had the need to embed widgets into a GWT cell table.  I have read about the flyweight pattern, and specifically why cell tables are designed to not have widgets in them, but… when you have a requirement to complete you just need to do it.

I went through two iterations of this design and I am pretty happy with the final design.  I took some ideas from GWT Cell Widgets like EditTextCell as well as some experience from the first not-so-great implementation i created.  If you are new to Cells, it is important to realize that the Cell is instantiated one time, but called (with different data) for each row that is rendered.  This is why it is necessary to maintain a map of this cells data.

Here are some design ideas for this “WidgetCell”

  • Widget creation is expensive, so a widget for each row is only created one time and then stored in a cache for reuse.  This helps with the speed of the table rendering.
  • Widgets are never attached to the DOM.  They exist to be manipulated in memory and then have their HTML extracted and pushed into the DOM.
  • Events from the Widgets, therefore, are not handled.  If you need them you will need to change this implementation a little and pass them through the cell (onBrowserEvent method).  Cell events ARE handled (or can be in subclasses), which was enough for my purposes.
  • Finally, the widgets that I am using need to load data from the backend, so this “WidgetCell” populates the cells asynchronously.  This helps speed up initial table rendering as well.

One important thing to note on that last bullet: Browsers have a limit on the number of HTTP requests that can be active at a given time.  Additional requests (from my experience, i didn’t look into it deeply) are getting queued and block the UI from any other HTTP requests until they are cleared from the queue.  This means anything, like navigation, that requires an HTTP request will be queued and block the user interface.  The best solution I have found for this is to pass all related HTTP requests, like the ones for this widget, through a common RPC handler that performs queuing.  This can limit that data providers allowable HTTP requests, leaves additional HTTP connections available, and allows for cancelling uncalled or in-process requests if navigation changes prior to the table completely populating.

This WidgetCell is an abstract class.  It is meant to be extended, with the implementation defining the widget and handling the widget’s data.  The data can be stored in a map with an entry per row.  The abstract methods all pass through either the CellContext, the Cell context’s key, or the Cell index, depending on that is needed for that method.

Important things to remember:

  1.  As I mentioned earlier, this is a method for displaying widgets into Cells.  The widget lifecycle is NOT respected.  There is no attaching and detaching, and events are not handled from the widget.
  2. Widgets that change themselves over their lifetime will NOT have those changes reflected in the cell unless you take special care to manually reflect those changes in the DOM.  An example is a progress bar.  It will be rendered to the DOM, but any updates need to be re-rendered to the DOM manually because the widget is not attached.

Here is the code.  I am sure there will be questions that i can handle in the comments.  Additionally, I need to thank Colin Alworth at Sencha for comments and pointing out pitfalls that helped direct this final design, and David Maddison’s The GWT rendering process post for more insight on the widget lifecycle.

Here is a file with the code: Textfile with WidgetCell Code


import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.EditTextCell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.ui.RequiresResize;
import com.google.gwt.user.client.ui.Widget;

/**
 * A Cell for adding Widgets into CellTables. A single instance of a cell is used for a column, with the render method
 * being called for each new cell in a new row. data changes per row must come in through the render method (via the
 * setValue method in super)
 * 
 * The approached used here:
 * <ol>
 * <li>Keep a cache of widgets so only a finite number are made, and they are reused to minimize expensive widget
 * creation (this is based on the method GWT's {@link EditTextCell} uses to maintain data.)</li>
 * <li>The widgets are never attached to the DOM, they are only used for manipulating them and extracting their HTML</li>
 * <li>Events from the widgets, therefore, are not handled. Only events on the cells themselves. However, if needed, the
 * cell events can be passed along to the widgets in the reuse-map and handled. (the context contains the key for the
 * reuse-map)</li>
 * <li>For ajax use, only a wrapper (placeholder) is laid down in the DOM. This placeholder is replaced when the widget
 * HTML when it is ready.</li>
 * </ol>
 * 
 * @author mpickell
 * @version 1.0
 * @param <W>
 *            the widget that is to be used in this cell
 * @param <D>
 *            the type of data used for this widget. This is passed in through the render method for each cell created.
 * 
 * @see <b>For more information on this implementation:</b>
 *      http://www.sencha.com/forum/showthread.php?196112-Widget-Rendered-Grid/page2
 * 
 *      TODO: rename to AbstractClickableWidgetCell?
 * 
 */
public abstract class ClickableWidgetCell<W extends Widget, D> extends AbstractCell<D> implements RequiresResize {

    /**
     * Widget generator for use by this cell renderer. This used as a factory that is used when a new widget is needed.
     * 
     * @param <C>
     *            widget type returned
     */
    protected interface ClickableWidgetCellWidgetFactory<C extends Widget> {
        C createWidget();
    }

    private static int uniqueKeyForWidgetID = 0;

    /**
     * Widget's displaying the table may enlarge it. If this is non-null, it will be resized when all widgets display.
     */
    private RequiresResize containerToResizeAfterAllWidgetsDisplay;

    /**
     * map of widgets used in this column. Once created, the widget is cached so it doesn't need to be created again.
     * The key here is the context.getKey() object, which uniquely IDs a row. (reuse-map is based on design of GWT's
     * {@link EditTextCell})
     */
    private Map<Object, W> reusableWidgetsMap = new HashMap<Object, W>();
    private Map<Object, String> domPlaceholderIds = new HashMap<Object, String>();

    /**
     * This cell can be used as a non-clickable cell as well. this boolean will block the click and keydown events when
     * set to true via the setter.
     */
    private boolean blockClickableEvents = false;

    /**
     * The HTML templates used to render the cell placeholder.
     */
    interface ClickableWidgetCellPlaceholderTemplate extends SafeHtmlTemplates {
        /**
         * The template for the widget placeholder for this Cell, which includes styles and a value.
         * 
         * @param id
         *            the id for this placeholder template
         * @param value
         *            the safe value. Since the value type is {@link SafeHtml}, it will not be escaped before including
         *            it in the template. Alternatively, you could make the value type String, in which case the value
         *            would be escaped.
         * @return a {@link SafeHtml} instance of a simple container. The container position is set to relative so that
         *         any value placed inside can use absolute positioning.
         */
        @SafeHtmlTemplates.Template("<div style='position:relative;' id='{0}'>{1}</div>")
        SafeHtml cell(String id, SafeHtml value);
    }

    /**
     * Create a singleton instance of the templates used to render the cell.
     */
    private static ClickableWidgetCellPlaceholderTemplate placeholderTemplate = GWT
            .create(ClickableWidgetCellPlaceholderTemplate.class);

    /**
     * Construct a new ClickableWidgetCell
     */
    public ClickableWidgetCell() {
        super("click", "keydown");
    }

    /**
     * @return a factory that creates the default widget type, with any necessary setup, that is used by this Dto.
     */
    protected abstract ClickableWidgetCellWidgetFactory<W> getWidgetFactory();

    /**
     * Trigger any asynchronous calls that are needed to get data, if needed, and then update the widget. When update is
     * complete, execute command to push widget HTML to DOM.
     * 
     * @param widget
     *            the widget that needs to be updated
     * @param onDataReadyCallback
     *            the command to trigger when the widget is updated. This command displays the widget in the page
     */
    protected abstract void updateWidget(final W widget, final Context cellContext, final D data,
            final Command onDataReadyCallback);

    /**
     * After the widget is displayed, handle any clean up (like resize to fill cell maybe). This is a default impl.
     * 
     * @param widget
     *            the widget that was displayed in the DOM
     * @param cellContext
     *            this widget's context
     * @param data
     *            the data used when displaying this widget.
     */
    protected void finalizeWidgetAfterDisplayInGrid(final W widget, final Context cellContext, final D data) {
    }

    /**
     * When resize is triggered on this cell (passed down from the grid) the cell implementation can handle it. This is
     * the default impl.
     * 
     * @param widget
     *            a widget from the reuse queue
     * @param cellContextKey
     *            this widget's context
     * @param placeholderId
     *            the placeholder ID for this widget in the DOM, so the HTML can be updated.
     */
    protected void handleWidgetOnWindowResize(W widget, Object cellContextKey, String placeholderId) {
    }

    /**
     * Default implementation.
     * 
     * This method returns HTML to be placed in the page while waiting for the widget to be displayed. If the widget
     * requires service calls, this will be displayed until the service calls return and the onDataReadyCallback command
     * is executed.
     * 
     * @return a SafeHtml object containing whatever HTML should be displayed while the main widget is being updated.
     *         This will be completely removed and placed by the widget HTML when the updateWidget method calls the
     *         onDataReadyCallback.
     */
    protected SafeHtml getWidgetUpdatingHtml() {
        return new SafeHtml() {
            private static final long serialVersionUID = 1L;

            @Override
            public String asString() {
                return "<i>Loading...</i>";
            }
        };
    }

    @Override
    public void onBrowserEvent(Context context, Element parent, D widgetDataDto, NativeEvent event,
            ValueUpdater<D> valueUpdater) {
        super.onBrowserEvent(context, parent, widgetDataDto, event, valueUpdater); // Handles user press actual
                                                                                   // ENTER key
        if ("click".equals(event.getType())) {
            onEnterKeyDown(context, parent, widgetDataDto, event, valueUpdater);
        }
    }

    @Override
    protected void onEnterKeyDown(Context context, Element parent, D widgetDataDto, NativeEvent event,
            ValueUpdater<D> valueUpdater) {
        if (!blockClickableEvents && valueUpdater != null) {
            valueUpdater.update(widgetDataDto);
        }
    }

    @Override
    public void render(final Context context, final D widgetDataDto, final SafeHtmlBuilder sb) {
        /*
         * Widgets are reused so that their (expensive) creation is not performed each time. After creation, they are
         * attached and detached as needed and re-added to the page.
         */
        Object rowKey = context.getKey();

        /*
         * Either get the existing widget from the cache or create it.
         */
        W wfc = reusableWidgetsMap.get(rowKey);
        if (wfc == null) {
            // Create once and store.
            wfc = this.getWidgetFactory().createWidget();
            reusableWidgetsMap.put(rowKey, wfc);
        }
        final W wfcAsFinal = wfc;

        /*
         * Setup a unique key for the placeholder in this row
         */
        if (!domPlaceholderIds.containsKey(context.getKey())) {
            setupPlaceholderId(context);
        }

        /*
         * Allow the Dto to update the widget, possibly after asynchronous calls to service. Once updated, the command
         * is (i.e., has to be) executed to finalize the displaying of the cell.
         * 
         * This update is triggered as a GWT "thread" using the deferred command so that the render method can complete
         * quickly. The update and display of the widget can be deferred safely because everything needed for it is
         * packaged in the updateWidget method
         */
        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
            @Override
            public void execute() {
                updateWidget(wfcAsFinal, context, widgetDataDto, new Command() {
                    @Override
                    public void execute() {
                        ClickableWidgetCell.this.displayWidget(context, widgetDataDto);
                    }
                });
            }
        });

        /*
         * Put the placeholder in the table so we have a location to go to later. Put the "busy" html in there.
         */
        sb.append(placeholderTemplate.cell(domPlaceholderIds.get(context.getKey()), getWidgetUpdatingHtml()));
    }

    private void displayWidget(final Context cellContext, final D widgetDataDto) {
        Element e = getElementsBySelectorJSNI("#" + domPlaceholderIds.get(cellContext.getKey()));
        if (e != null && reusableWidgetsMap.containsKey(cellContext.getKey())
                && reusableWidgetsMap.get(cellContext.getKey()).getElement() != null) {
            e.setInnerHTML(reusableWidgetsMap.get(cellContext.getKey()).getElement().getString());

            /*
             * The widget is now displayed. Allow the implementation to perform any adjustments to it. The
             * implementation can use the domPlaceholderIds map (via getter) to replace the HTML in the page since the
             * element in the widget is never attached to the page.
             * 
             * Changes to the widget will be reflected in the widget map, placeholder ID can be used (via getter, as a
             * string so it cannot be changed) to push any updated HTML to the page.
             */
            Scheduler.get().scheduleDeferred(new ScheduledCommand() {
                @Override
                public void execute() {
                    ClickableWidgetCell.this.finalizeWidgetAfterDisplayInGrid(reusableWidgetsMap.get(cellContext.getKey()),
                            cellContext, widgetDataDto);
                }
            });

            /*
             * This is a widget that might be expanding the table. Call on the resize container to resize.
             */
            if (containerToResizeAfterAllWidgetsDisplay != null) {
                containerToResizeAfterAllWidgetsDisplay.onResize();
            }
        }

    }

    /**
     * Query for elements using selectors
     * 
     * @param selectors
     *            a string containing one or more CSS selectors separated by commas
     * @return a JS array of elements.
     * 
     * @see https://developer.mozilla.org/en-US/docs/DOM/Document.querySelector
     * @see http://www.w3.org/TR/selectors-api/
     */
    protected native Element getElementsBySelectorJSNI(String selector) /*-{
		return $doc.querySelector(selector);
    }-*/;

    /**
     * Create a unique ID for a placeholder that will be placed in the cell on render.
     * 
     * @param context
     */
    private void setupPlaceholderId(Context context) {
        if (uniqueKeyForWidgetID >= Integer.MAX_VALUE) {
            // housekeeping.
            uniqueKeyForWidgetID = 0;
        } else {
            uniqueKeyForWidgetID++;
        }
        domPlaceholderIds.put(context.getKey(), "WidgetCellID_" + uniqueKeyForWidgetID);
    }

    /**
     * If true, this will block the clickable events and this cell will simply render the widget without accepting
     * events.
     * 
     * @param blockClickableEvents
     */
    public void setBlockClickableEvents(boolean blockClickableEvents) {
        this.blockClickableEvents = blockClickableEvents;
    }

    @Override
    public void onResize() {
        /*
         * on resize of the page, update the widget to the new cell size if necessary.
         */
        if (reusableWidgetsMap != null && !reusableWidgetsMap.isEmpty()) {
            for (Entry<Object, W> entry : reusableWidgetsMap.entrySet()) {
                ClickableWidgetCell.this.handleWidgetOnWindowResize(entry.getValue(), entry.getKey(),
                        domPlaceholderIds.get(entry.getKey()));
            }
        }
    }

    /**
     * Get the ID for the widget placeholder in the page. the innerHtml of this element is the widget HTML.
     * 
     * @param cellContextKey
     * @return
     */
    protected String getDomPlaceholderId(Object cellContextKey) {
        return domPlaceholderIds.get(cellContextKey);
    }

    /**
     * Set the container of this grid so it can be resized when all widgets display.
     * 
     * @param containerToResizeAfterAllWidgetsDisplay
     *            the container that implements RequiresResize & IsWidget
     */
    public void setContainerToResizeAfterAllWidgetsDisplay(RequiresResize containerToResizeAfterAllWidgetsDisplay) {
        this.containerToResizeAfterAllWidgetsDisplay = containerToResizeAfterAllWidgetsDisplay;
    }

}

Share

GWT & mvp4g: Displaying history on the client side.

I’m currently working on a task to display and persist history using GWT and mvp4g.  There are many ways of doing this, but here is how i’m going it.  Maybe this will help you with an additional approach, or maybe you can help me do it better.

Implementation Attempts

1st Pass: GWT’s History Event

First I attempted to use GWT’s History ValueChange event to capture changes happening in history.  I run into trouble here right away.  This event only seemed to capture history changes that occurred when I called History.newItem(...) myself; mvp4g was not triggering it.  I search around on the internet and didn’t find any explanation of what this event was supposed to be doing, or how mvp4g handled it, but apparently mvp4g was not triggering this event for its history changes.  This method was out.

UPDATE: For some reason it eluded me in my searches until after my final design, but you could create your own PlaceService, which is using the ValueChange event in order to re-create views from history.  There are a lot of options here, which i am going to take a look at because it will most likely change the design I am currently using.  See a write-up here.

2nd Pass: hashchange Event

I am lucky to be supporting only most recent browsers, so the next method I attempted was to use the hashchange event to capture every change made in the browser.  This worked very well and caught everything.

In the end, i did not use it because it is detached from the mvp4g History Converters.  It is cleaner to keep all of the history implementation in one place so each converter can define its own behavior, and that behavior is maintained along with those converters.  I would have had to catch a change and then query into one of the converters to get additional information on resolving descriptions, etc.   That is messy enough, but then there is the issue of the converters not being programmatically accessible (I couldn’t figure out how to get them from mvp4g… i would love to know if someone can tell me), and they do not have an eventBus instance.

Anyway, I did not end up using this approach in my design but here is that code if it can help you out.  At the very least, it was good to get to know this hashchange event.  GWT does not have any built in hooks to this event, so I created a new EventHandler to encapsulate it and make it injectable into wherever it is needed.

Here’s the class I created:

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.HasHandlers;
import com.google.inject.Singleton;
import com.mvp4g.client.event.BaseEventHandler;

/**
 * EventHandler for hash change events. This is an eventhandler so that it is a singleton that can be injected where
 * needed, and can access the eventbus if events are sent that way.
 */
@Singleton
@com.mvp4g.client.annotation.EventHandler
public class HashChangeEventEmitter extends BaseEventHandler implements HasHandlers {

 // handlers
 private HandlerManager handlerManager;

 protected HashChangeEventEmitter() {
 handlerManager = new HandlerManager(this);
 }

  @Override
 public void setEventBus(EventBus eventBus) {
 super.setEventBus(eventBus);

     Scheduler.get().scheduleDeferred(new ScheduledCommand() {
         @Override
         public void execute() {
             registerHashChangedEventListenerInDOM();
         }
     });
 }

 private void fireHashChangedEventFromDOM(String oldUrl, String newUrl) {
     fireHashChangedEvent(new HashChangedEvent(oldUrl, newUrl));
 }

 /**********************************************************
 * Event registration and emitting
 **********************************************************/

 private void fireHashChangedEvent(HashChangedEvent event) {
     if (handlerManager.getHandlerCount(HashChangedEvent.TYPE) > 0) {
         handlerManager.fireEvent(event);
     }
 }

 /** {@inheritDoc} */
 public HandlerRegistration addHashChangedEventHandler(HashChangedEventHandler handler) {
     return handlerManager.addHandler(HashChangedEvent.TYPE, handler);
 }

 @Override
 public void fireEvent(GwtEvent event) {
     handlerManager.fireEvent(event);
 }

 /**********************************************************
 * Event definition
 **********************************************************/
 public static interface HashChangedEventHandler extends EventHandler {
     void onHashChanged(HashChangedEvent event);
 }

 public static class HashChangedEvent extends GwtEvent {

 public static Type TYPE = new Type();

 private final String oldUrlHash;
 private final String oldUrlRaw;
 private final String newUrlHash;
 private final String newUrlRaw;

 public HashChangedEvent(String oldUrlRaw, String newUrlRaw) {
     super();
     this.oldUrlHash = extractHash(oldUrlRaw);
     this.newUrlHash = extractHash(newUrlRaw);

     this.oldUrlRaw = oldUrlRaw;
     this.newUrlRaw = newUrlRaw;
 }

 private String extractHash(String url) {
     return url.contains("#") ? url.substring(url.indexOf('#')) : "";
 }

 @Override
 public Type getAssociatedType() {
     return TYPE;
 }

 @Override
 protected void dispatch(HashChangedEventHandler handler) {
     handler.onHashChanged(this);
 }

 /** @return just the hash from the old URL */
 public String getOldUrlHash() {
     return oldUrlHash;
 }

 /** @return the entire old URL */
 public String getOldUrlRaw() {
     return oldUrlRaw;
 }

 /** @return just the hash from the new URL */
 public String getNewUrlHash() {
     return newUrlHash;
 }

 /** @return the entire new URL */
 public String getNewUrlRaw() {
     return newUrlRaw;
 }

 }

 private final native void registerHashChangedEventListenerInDOM()
/*-{
 var hashChangedEventEmitter = this;

   $wnd
    .addEventListener(
        "hashchange",
        function(event) {
       hashChangedEventEmitter.@<yourPathToEmitter>.HashChangeEventEmitter::fireHashChangedEventFromDOM(Ljava/lang/String;Ljava/lang/String;)(event.oldURL, event.newURL);
       });
 }-*/;

}

The event is a standard GwtEvent, and the class can be injected wherever it is needed like this:

    @Inject
    protected HashChangeEventEmitter hashChangeEventEmitter;

Final Solution: Instrumenting the History Converters

mvp4g handles history in GWT through History Converters, which uses the GWT History framework.  These allow you to attach history entries directly to EventBus calls.  Anytime you trigger a certain event, you can specify how to capture (and recreate) the resulting action in history.

The design I’m currently using creates an abstract history converter that contains a common method called at the end of every history converter implementation’s event bus call (the one creating the token to save for this history entry).  This method, in turn, calls a couple of abstract methods implemented in the history converters that resolve any information needed to display what this history entry is and the history token itself.  This information is then passed to the presenter of the history display view.  This instruments each history converter and requires all new history converters implement these methods so that the history display is kept updated.

The presenter for the history view is injected the same way the HashChangeEventEmitter was injected in the 2nd pass above.  This presenter then handles pushing the history information to the backend as well as updating the view.

Re-evaluation and design forthcoming

It eluded me in my original google searches when I was working on my original designs, but I am going to revisit this and look at implementing it in a cleaner way using a mvp4g Custom PlaceService.  This is the place that has a collection of all of the history converters and is a central point that can be used to implement exactly what I’m trying to do without needing to clutter up the history converters. It is exactly what I was look for, and knew was there somewhere, but missed…

I will update this post after that…

Share

Apache POI – Formatting of ‘General’ data extracted from Excel

I just fixed an issue that has to do with pulling data out of excel using the Apache POI framework into Java and having the data’s format change along the way. It is not a particularly difficult issue, but when researching it I found that there were a couple of other posts on it that didn’t really solve it, or solved it in a round-about way. So my solution is here:

The issue I ran into was that there were cells in Excel that were meant to be strings but had the default Excel cell format of “General.” If a normal string was in one of the cells in this column, then it would be correctly read into the Java code as a string. However, if a value was like ‘123123123,’ it would be read in as an HSSFCell.CELL_TYPE_NUMERIC and would convert to a string in scientific notation. These needed to be read in exactly as there were seen in the spreadsheet.

Other solutions I saw included additional reformatting once the data was inside Java, but that seemed too dirty to me. I would have to make assumptions on what the user put into the spreadsheet. There should be a way to pull the data from the Excel file and receive a string that looks exactly like what is shown in Excel.

A quick solution to this would be to change the formatting of the cell in Excel to ‘Text’ so that the fields were always designated correctly. But, in my case, I cannot always control what is in the Excel file. A user might change the formatting to whatever they want, but my system expects a String. So this was not a solution.

What I can control is the java code, of course. So after some digging I found two keys that would help me solve this issue.  If I am trying to convert a HSSFCell.CELL_TYPE_NUMERIC value to a string, I need to use these two things:

  1. I can get the format of the cell that is being used by Excel with cell.getCellStyle( ).getDataFormat( ). This returns a short.  The Excel format for “General” is 0 and the format for Scientific Notation is 11 (I found this through debugging, but a table exists at least here in the ‘Value’ column).  So I know if the user really wanted scientific notation, or if the value was just formatted like that when passed to me.
  2. Once I know the user did not mean for the value to be in scientific notation, I can check the string for the existence of an “E,” and if it exists I will pull the string from Excel using Excel’s formatting instead of my own.  I do this with value = new HSSFDataFormatter().formatCellValue( cell ).  This converts the cell to a string using the Excel cell format pattern instead of trying to reformat the numeric value in the HSSFCell in Java.
I could simply use this formatCellValue all of the time instead of even checking I suppose, but i’m doing some other custom formatting as well so this is treated as a special case.
So this is the solution I ended up using.  The key is to check the format that is set up in Excel, and then use it directly if it produces a cleaner result in the Java code.  Hope that helps someone.
Share

jQuery plugin: working scrollbar on left side of div

I’ve been building JSF views and creating custom components for a while and one issue we worked on was making scrollable tables, where the header and footer stay stationary and the data scrolls.

I’m still cleaning this up, but the general idea is that I wrapped the table in DIVs, which are set up to scroll vertically or horizontally based on the dynamic content of the table. This seems to be the only solution that is cross-browser, and it uses jQuery to dissect the table and move the header and footer so that the scrolling works correctly. Once I finalize that, i’ll post it.

But one thing was missing: the content of the tables sometimes pushes the table off the right side of the view, and the horizontal scrollbar appears. At this point, the vertical scrollbar is way off to the right and pretty much makes the table useless. So I wanted to move the vertical scrollbar to the left side, and I had trouble finding a cross-browser solution … until now.

I just found a jQuery plugin written by Brian Reavis at his website: 3rd Route. I tested it out and it worked immediately. The only thing i had to do to it was swap out all of the ‘$’ for ‘jQuery’ because of my use of the jQuery.noConflict() flag. I originally found it via stackoverflow, but the version on the website looks like it was cleaned up to enable chaining.

I tried other things, like multiple versions of style="direction:trl", but these felt messy when i read about them, and did not work when I implemented them.

Thanks Brian!

Update: I ended up doing a lot of changes because of some issues i found:

  • For very large tables, this code was really slow in IE.  I narrowed it down to the jQuery ‘append’ function and refactored the plugin to not use it at all
  • The original copied all components into the poser div.  I removed all of that and the poser div is basically now just the scrollbar.  Something major was required for using the plugin with JSF, because it was causing duplicate ID issues.  I decided to get rid of all of the extra components instead of just renaming the IDs.
  • I created a second version that has the scrollbar on both sides
  • I added a unique identifier class so that multiple instances could be on the same page.
  • I changed how the scroll updating worked, but the old way wasn’t broken so it wasn’t really an improvement.

So, based on Brian’s original design and implementation, here is what I am using now:

/**
 * jQuery plugin to add a scrollbar to the left side of a div.  It does this by
 * creating a false div on the left side of the table, and then having that div's scroll
 * position set on the original div every scroll event.
 * 
 * @link edited from http://thirdroute.com/css-js-left-vertical-scrollbars/, but heavily changed from the original version.
 */
jQuery.fn.leftAndRightScrollBar = function(){
	var items = jQuery(this);
	
	var randomString = function() {
		var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
		var string_length = 8;
		var randomstring = '';
		for (var i=0; i<string_length; i++) {
			var rnum = Math.floor(Math.random() * chars.length);
			randomstring += chars.substring(rnum,rnum+1);
		}
		return randomstring;
	}

	jQuery(function(){
		items.each(function(){
			// create unique classes for targeting the poser div
			var poserTargetingClass = randomString();
			
			var e = jQuery(this);
			var contentHeight = e.children('table:first').height();
			var content = e.html();
			var ie = !jQuery.support.boxModel;
			var w = e[ie?'innerWidth':'width'](), h = e[ie?'innerHeight':'height']();
			
			//calculate paddings
			var pad = {};
			jQuery(['top', 'right', 'bottom', 'left']).each(function(i, side){
				pad[side] = parseInt(e.css('padding-' + side).replace('px',''));
			});
			//detect scrollbar width
			var xfill = jQuery('<div class="xFill">').css({margin:0, padding:0, height:'1px'});
			e.append(xfill);
			var contentWidth = xfill.width();
			var scrollerWidth = e.innerWidth() - contentWidth - pad.left - pad.right;
			e.css('padding', '0');
			e.children('.xFill').remove();
			
			var poserHeight = h - pad.top - pad.bottom;
			var poser = jQuery('<div class="leftAndRightScrollPoser '+poserTargetingClass+'">')
				// create a div that forces height without copying the content to do it.
				.html('<div style="visibility:hidden;height:'+contentHeight+'px">.</div>')
				.css('overflow','auto')
				.height(poserHeight+(ie?pad.top+pad.bottom:0))
				.width(scrollerWidth-(ie?0:pad.left*2)) // only as wide as the scrollbar.
			;
			
			e
				.css({
					width: w+(ie?0:scrollerWidth)-(ie?0:pad.right+pad.left),
					height: h-(ie?0:pad.bottom+pad.top),
					marginTop: -poserHeight-pad.top*2,
					marginLeft: scrollerWidth
				})
				.css('overflow-y', 'auto')
				.css('overflow-x', 'hidden')
			;
				
			jQuery(['top', 'right', 'bottom', 'left']).each(function(i, side){
				 poser.css('padding-'+side, pad[side]);
				 e.css('padding-'+side, pad[side]);
			});
			poser.insertBefore(e);
			
			var hRatio = (e.innerHeight()+pad.bottom) / poser.innerHeight();
			// Set up scrolling update events
			jQuery("." + poserTargetingClass).scroll(function(){e.scrollTop(poser.scrollTop()*hRatio)});
			e.scroll(function(){poser.scrollTop(e.scrollTop()*hRatio)});
		});
	});
	return items;
};


/**
 * jQuery plugin to move the scrollbar to the left side of a div -- no right scrollbar
 * @link http://thirdroute.com/css-js-left-vertical-scrollbars/, but heavily changed from the original version.
 */
jQuery.fn.leftScrollbar = function(){
	var items = jQuery(this);
	
	var randomString = function() {
		var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
		var string_length = 8;
		var randomstring = '';
		for (var i=0; i<string_length; i++) {
			var rnum = Math.floor(Math.random() * chars.length);
			randomstring += chars.substring(rnum,rnum+1);
		}
		return randomstring;
	}
	
	jQuery(function(){
		items.each(function(){
			// create unique classes for targeting the poser div
			var poserTargetingClass = randomString();
			
			var e = jQuery(this);
			var content = e.html();
			var contentHeight = e.children('table:first').height();
			var ie = !jQuery.support.boxModel;
			var w = e[ie?'innerWidth':'width'](), h = e[ie?'innerHeight':'height']();
			//calculate paddings
			var pad = {};
			jQuery(['top', 'right', 'bottom', 'left']).each(function(i, side){
				pad[side] = parseInt(e.css('padding-' + side).replace('px',''));
			});
			//detect scrollbar width
			var xfill = jQuery('<div>').css({margin:0, padding:0, height:'1px'});
			e.append(xfill);
			var contentWidth = xfill.width();
			var scrollerWidth = e.innerWidth() - contentWidth - pad.left - pad.right;
			e.css('padding', '0');
			e.children('.xFill').remove();
			
			var poserHeight = h - pad.top - pad.bottom;
			var poser = jQuery('<div class="leftScrollPoser '+poserTargetingClass+'">')
				.html('<div style="visibility:hidden;height:'+contentHeight+'px">.</div>')
				.css('overflow','auto')
				.height(poserHeight+(ie?pad.top+pad.bottom:0))
				.width(scrollerWidth-(ie?0:pad.left*2)) // only as wide as the scrollbar
			;
			
			e
				.css({
					width: w/*-scrollerWidth*/-(ie?0:pad.right+pad.left),
					height: h-(ie?0:pad.bottom+pad.top),
					overflow: 'hidden',
					marginTop: -poserHeight-pad.top*2,
					marginLeft: scrollerWidth
				});
				
			jQuery(['top', 'right', 'bottom', 'left']).each(function(i, side){
				 poser.css('padding-'+side, pad[side]);
				 e.css('padding-'+side, pad[side]);
			});
			poser.insertBefore(e);
			
			var hRatio = (e.innerHeight()+pad.bottom) / poser.innerHeight();
			// Set up scrolling update events
			jQuery("." + poserTargetingClass).scroll(function(){e.scrollTop(poser.scrollTop()*hRatio)});
			e.scroll(function(){poser.scrollTop(e.scrollTop()*hRatio)}); // so mouse wheel scrolls table
		});
	});
	return items;
};

Share

Intelligent Mail barcode – iText and barcode4j

I use a java library called iText to create PDFs and do other PDF related tasks when I am building web applications, and i embed a lot of barcodes for various purposes. iText handles most of these very well, but I recently needed to replace the old USPS barcode (postnet) with the new barcode (intelligent mail). iText doesn’t support it, but with some helpful direction I implemented it using another library called barcode4j.  Here’s the method I wrote that you can pretty much copy and use directly:

/**
* Create a USPS Intelligent Barcode using Barcode4J and add it to iText document.
* @param code barcode value
* @param cb overcontent from stamper
* @param fieldPositions stamper.getAcroFields().getFieldPositions(“…”) for the placeholder field in PDF template
*/
public static void createUspsIntelligentBarcode(String code, PdfContentByte cb, float[] fieldPositions) {

if ((code.length() != 20) && (code.length() != 25) && (code.length() != 29) && (code.length() != 31)) {
throw new RuntimeException(“UspsIntelligentBarcode: code length of ” + code.length() + ” is invalid.”);
}

//Note: fieldPositions data = [page, llx, lly, urx, ury]
float height = (fieldPositions[4]-fieldPositions[2]);
float width = (fieldPositions[3]-fieldPositions[1]);

PdfTemplate tp = cb.createTemplate(width, height);

// Create the graphics and canvas objects that will contain the barcode.
Graphics2D g2 = tp.createGraphics(width, height);
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2.scale(2.5, 2.835); // (1mm == 2.835 points) + tweaking on x to make bar width correct
Java2DCanvasProvider provider = new Java2DCanvasProvider(g2, 0);

// Create Barcode4J barcode on canvas
USPSIntelligentMailBean barcode = new USPSIntelligentMailBean();
// use 70% of converted values.  Measurements on printouts needed to be tweaked.
barcode.setAscenderHeight(UnitConv.in2mm(0.1f) * 0.7f);  // height of ascender/descender per USPS spec
barcode.setTrackHeight(UnitConv.in2mm(0.05f)* 0.7f);      // height of track bar per USPS spec (smallest bar of barcode)
barcode.setBarHeight(UnitConv.in2mm(0.145f)* 0.7f);
barcode.setIntercharGapWidth(UnitConv.in2mm(0.04f)* 0.7f * 1.13f); // adjust again for scaling
barcode.generateBarcode(provider, code);

g2.dispose();

// adjust x/y to perfect location for envelope window
float inch = 72f; // itext manual pg 33
float xCoordinate = fieldPositions[1] – ((1f/8f) * inch);
float yCoordinate = fieldPositions[2] – ((3f/32f) * inch);

cb.addTemplate(tp, xCoordinate, yCoordinate);
}

This lets you put the intelligent mail barcode (or any barcode in barcode4j) directly into iText.  The key here is that the PdfTemplate in iText has a method that creates a generic Java Graphics2D object that is accessible for drawing by anything.  This generic object is exactly what is needed to be passed into the barcode4j canvas provider, and then any barcode4j barcode is output to the canvas.  A couple tweaks to position it however it is needed, and that is it.  It was a lot simpler than I expected when I realized I couldn’t just use iText for the whole thing.  Gotta love open source!

Share

jsp tomahawk HtmlInputCalendar

I had an ongoing issue recently with pop-up javascript calendars in the tomahawk jsp library.  Hopefully this will save some trouble for someone.  The problems I was having were:

  1. sometimes the calendar pop-up was not selectable, and
  2. when it was selectable, the pop-up would show up at a distorted location on the page and the tables would be messed up.

Being new to debugging this, my first assumption was that there would be protections built into this packaged code so that my CSS and whatever else would not affect it.  So, to keep this post short and to the point, here is how I fixed it:

  1. An eventual look at the W3C HTML 4.1 Reference showed that this element was difficult to put directly inside a label, inside a fieldset.  The problem is that the label element only supports one input inside, so the additional calendar pop-up button is unusable and simply ignores any click on it and instead puts focus on the input element.  I ended up fixing this one by simulating a label element using a DIV and CSS.  (Note: at B6 Systems, we have developed an in-house jsp framework that we call PureFaces that allows writing JSP pages completely in Java code.  So I wrote my own components for the fieldsets and labels.  It is intended to be open source eventually and will be on the B6 website)
  2. The second problem took a little longer, and eventually I fixed it without really knowing why what I did worked.  I used a lot of the same techniques for debugging CSS that are shown in many places.  Eventually I found that I had a position: relative; style on my content wrapper that was totally throwing off the built-in CSS for the pop-up.  The reason I don’t know why that fixed it was that I could not find anything in the built-in CSS that seemed to care about position.  But, results speak loudly.  I was able remove and work around that style.  The second part of this one (the bad formatting of the pop-up) was related to having the table element way too globally defined in the CSS.  Adding more specific selectors quickly solved that one.
Share