Adding icon next to form fields for help text

I’m trying to add a help icon, or link, next to several fields on FormLayout-based forms. Ideally the position would be just to the right of the field, but I’d also be ok with a link or icon relative to the caption. This icon or link would show the field-specific help on hover or click. Pretty much exactly like the question mark icon next to the “Mark as a Question” checkbox below the message body on the new forum post page.

I’ve tried the Overlay component, which looked promising, but it seems that it won’t work with my application layout, which embeds forms a couple of levels deep within TabSheet’s.

I’d like to do this in some relatively simple way i.e. I don’t want to spend the time to completely customize my form layouts because I only need this for a small percentage of fields. I can definitely add some code in an attachField override just for the relevant properties, if there is a way to do it via that route.

I also tried to wrap my field in a HorizontalLayout implementing Field, containing the Field component and the help component, which got me close, but started causing other issues like the required field indicator showing up in the wrong spot.

This seems like it should be easy…

To solve this problem, I used the CustomField and ContextHelp addons. Thanks to Jonatan and Henri for helping out with various issues…

I implemented a new class called FieldWithHelp that extends the CustomField FieldWrapper to provide context-sensitive help using the ContextHelp addon.

I had to work around three bugs in the CustomField FieldWrapper (v 0.7.2):

  1. FieldWrapper just delegates getErrorMessage to AbstractComponent. We need the logic from AbstractField.getErrorMessage to be applied to the wrapped field instead.

  2. FieldWrapper does not provide the appropriate isEmpty logic for fields with multi-valued selections. This causes a premature errorIndicator to show up because the hideErrors variable is set incorrectly in paintContent.

  3. FieldWrapper for some reason shows two required indicators, one next to the caption as normal but another one also in the root area. I haven’t investigated this in detail but am currently working around it by hiding it via CSS:

Also, an important point related to ContextHelp is to have one instance of ContextHelp attached to the application main window, and have it accessible to code via the App ThreadContext pattern.

Here is the FieldWithHelp class:


public class FieldWithHelp<T> extends FieldWrapper {

    ThemeResource helpIcon = new ThemeResource("icons/16/help.png");

    public FieldWithHelp(final Field wrappedField, Class<T> propertyType, String help) {

        super(wrappedField, null, propertyType, new HorizontalLayout());

        setCaption(wrappedField.getCaption());
        wrappedField.setCaption(null);
        wrappedField.addStyleName("fieldhelp wrappedfield");

        HorizontalLayout layout = (HorizontalLayout) getCompositionRoot();
        layout.setMargin(false);
        layout.setSpacing(true);
        layout.setSizeUndefined();

        layout.addComponent(wrappedField);

        final ContextHelp contextHelp = App.getCurrent().getContextHelp();

        Button helpButton = new Button();
        helpButton.setStyleName(BaseTheme.BUTTON_LINK);
        helpButton.addStyleName("fieldhelp");
        helpButton.setIcon(helpIcon);
        helpButton.addListener(new Button.ClickListener() {
            @Override
            public void buttonClick(Button.ClickEvent event) {
                contextHelp.showHelpFor(event.getButton());
            }
        });

        contextHelp.addHelpForComponent(helpButton, help);

        layout.addComponent(helpButton);
        layout.setComponentAlignment(helpButton, Alignment.MIDDLE_LEFT);
        layout.setExpandRatio(helpButton, 0);

    }

    /**
     * FieldWrapper just delegates getErrorMessage to AbstractComponent. We need the logic from AbstractField.
     */
    @Override
    public ErrorMessage getErrorMessage() {

        if(getWrappedField() instanceof AbstractField) {

            return ((AbstractField)getWrappedField()).getErrorMessage();

        } else {

            return super.getErrorMessage();

        }

    }

    /**
     * FieldWrapper does not provide logic for select fields with Collections.
     */
    @Override
    protected boolean isEmpty() {

        // this logic is from AbstractSelect.isEmpty, which unfortunately is protected and thus not accessible from here
        if(getWrappedField() instanceof AbstractSelect) {

            AbstractSelect wrappedSelect = (AbstractSelect) getWrappedField();

            if (! wrappedSelect.isMultiSelect()) {
                return super.isEmpty();
            } else {
                Object value = getValue();
                return super.isEmpty()
                        || (value instanceof Collection && ((Collection) value)
                                .isEmpty());
            }

        } else {

            return super.isEmpty();

        }

    }

}

And here is the CSS to work-around problem #3:


.v-caption-wrappedfield {
  display: none;
}

I hope this helps someone spend far less time than I did on this!

Cheers,
Raman

Version 0.7.3 of the
CustomField add-on
should address problems 1 and 2 in the typical cases including yours, but other situations will still require overriding some methods. The code is quite a bit more complex than in your example to make it a little more generally applicable. Unfortunately, some issues cannot be addressed cleanly without changes to Vaadin core, so some tweaking is still necessary for some users.

The third issue probably cannot be addressed fully in server side code for all cases - some custom components would benefit from having the error indicator visible inside the field wrapper (e.g. when nesting forms), others would like it to be internally active but not visible and yet others would want to “transfer” it to the parent FieldWrapper from the wrapped field. Some improvements could probably be made in the future to make the common cases easier.

Feedback on the implementation as well as any bug reports, suggestions and enhancement requests, patches etc. are welcome.

The CustomComponent add-on should hopefully become obsolete in most cases with Vaadin 7, and there might be a CustomComponent in core for the remaining special cases in the future.

Thanks, 0.7.3 does indeed fix problems #1 and #2 – I was able to remove my getErrorMessage and isError overrides.

Understood… how about a setter such as setErorIndicatorHandlingType that takes an enum or constants like:

ErrorIndicatorType INSIDE;
ErrorIndicatorType ACTIVE_INVISIBLE;
ErrorIndicatorType TRANSFER_TO_PARENT;

Out of curiosity, what would the use case for ACTIVE_INVISIBLE be? And also, what is the current behavior? It appears to be both INSIDE and TRANSFER_TO_PARENT at once.

Cheers,
Raman

what exactly does this mean and how do i implement it?

This particular thread helped me to add icons next to my form fields. Thank you!