google-code-export / gwt-platform

Automatically exported from code.google.com/p/gwt-platform
1 stars 0 forks source link

Support declarative DOM element ID generation for better accessibility #389

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
Hello GWTP team!

We've developed a small GWT framework that makes our UI components (GWTP Views) 
accessible for functional testing using tools like Selenium. When writing web 
app functional tests, we had to ensure that each major UI component has a 
unique and deterministic DOM element ID associated, so that automated tools 
(e.g. Selenium) can interact with it and make assertions about it. Our goal was 
to avoid assigning DOM element IDs by hand, which would pollute our View 
constructors (there is also no guarantee that those manually-assigned IDs will 
be unique in the context of the web app that contains many Views).

So we use GWT deferred binding to generate an implementation of a "handler" 
interface, and inside View constructor, after all fields (mostly @UiField 
Widgets) are initialized, we do something like this:

ViewIdHandler.idHandler.generateAndSetIds(this);

IdHandler gets the View instance ("this") and sets element IDs for all fields 
that use @WithElementId annotation. When generating IdHandler implementation, 
we search for all View fields (either direct or
inherited from superclasses) which have @WithElementId and apply element ID 
logic on them, and then we recursively descend into types of those fields as 
well, again looking for @WithElementId fields, and so on.

A simple example to demonstrate IdHandler concept:

public class MyView ... {

    @WithElementId
    Label someLabel;

    @WithElementId // Will be applied to CustomWidget recursively
    CustomWidget myWidget;

    @WithElementId("submit") // Override default field ID
    PushButton submitButton;

    // Same approach as UiBinder or EditorDriver
    interface ViewIdHandler extends ElementIdHandler<MyView> {
        ViewIdHandler idHandler = GWT.create(ViewIdHandler.class);
    }

    public MyView() {
        // Create and initialize fields, e.g. using UiBinder
        ViewIdHandler.idHandler.generateAndSetIds(this);
    }

}

public class CustomWidget ... {

    @WithElementId
    TextBox someTextBox;

}

ViewIdHandler generateAndSetIds() implementation will do the following:
- set MyView's someLabel ID to 'MyView_someLabel'
- set MyView's CustomWidget's someTextBox ID to 'MyView_myWidget_someTextBox'
- set MyView's submitButton ID to 'MyView_submit'

@WithElementId annotation supports following field types:
- GWT's UIObject type (base class for all Widgets), through getElement(), 
calling Element.setId()
- GWT's Element type, calling Element.setId()
- custom HasElementId interface (used when implementing custom UI objects), 
calling with HasElementId.setElementId()

IdHandler guarantees unique IDs during GWT compile time (deferred binding will 
fail on duplicities). IdHandler also supports extending generated IDs at 
runtime, for example when there are multiple instances of MyView added to the 
page (when MyView is not a singleton):

public class MyView ... {

    ... everything is same except for constructor below ...

    public MyView() {
        // Create and initialize fields, e.g. using UiBinder
        ViewIdHandler.idHandler.setIdExtension("Foo");
        ViewIdHandler.idHandler.generateAndSetIds(this);
    }

}

Then, for example, MyView's someLabel ID will be set to 'MyView_someLabel_Foo'.

We're using this framework in Red Hat Enterprise Virtualization (RHEVM) & Open 
Virtualization (oVirt) GWT web apps together with GWTP, and we thought it could 
be useful as a feature of the GWTP project itself.

We've created a patch that adds a separate GWTP client module called 
'gwtp-idhandler-client' (IdHandler has no dependency on GWTP MVP, it can be 
used with plain GWT if GWTP users decide to do so). The source code is fully 
documented and there are unit tests as well :-) the patch should apply cleanly 
on top of current GWTP head.

Let us know what you think.

Original issue reported on code.google.com by Vojtech....@gmail.com on 10 Jan 2012 at 4:15

Attachments:

GoogleCodeExporter commented 9 years ago
Ideas for improvement:

1) Current way of setting DOM element IDs (using Element.setId method) results 
in generated IDs being present in all environments and builds. In a typical 
case, where IDs are required only by automated functional GUI test tools (like 
Selenium), it makes little sense to include those IDs in production builds. We 
could use UIObject.ensureDebugId, or introduce some deferred binding property 
that enables/disables setting IDs.

2) Currently, element ID prefix is represented by the owner type name, and 
cannot be customized by the user. We could add a type-level annotation (e.g. 
@ElementIdPrefix) to customize this prefix. In the end, users could have IDs 
such as "ItemListPanel_someLabel" instead of "MyView_someLabel".

Original comment by Vojtech....@gmail.com on 19 Mar 2012 at 12:28

GoogleCodeExporter commented 9 years ago
I just wanted to mention that I'm using this patch (unchanged) in one of my 
Mvp4g projects, and I've found it quite useful.  It's much easier to test with 
automated tools like Selenium if there's a legible HTML id available for 
important widgets.  

I personally don't mind that the HTML ids are there all of the time, even in 
production builds.  In fact, I think I would prefer that.  

It might be nice to customize the prefix, but I don't think it's a requirement. 
 From what I've seen so far, the generated id works pretty well as-is.  

Original comment by prono...@gmail.com on 24 Apr 2012 at 3:08

GoogleCodeExporter commented 9 years ago
Ken, thanks for your feedback! We're also using this framework directly in our 
project. We'll create a separate open source project out of this.

Cheers, Vojtech

Original comment by Vojtech....@gmail.com on 24 Apr 2012 at 9:28

GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
Hmmm, I'm having an issue getting this to work. Generated _ViewIdHandlerImpl 
class only contains code to setElementId on owner, not the other uifields.

Original comment by lower...@gmail.com on 20 Aug 2012 at 9:49

GoogleCodeExporter commented 9 years ago
@loweryjk, most likely you're declaring wrong owner type in your 
ElementIdHandler sub-interface.

For example:

public class MyViewImpl implements MyPresenter.MyViewInterface {

    // Fields marked with @WithElementId go here

    // Owner type is MyViewImpl, not MyViewInterface!
    interface ViewIdHandler extends ElementIdHandler<MyViewImpl> {
        ViewIdHandler idHandler = GWT.create(ViewIdHandler.class);
    }

    public MyViewImpl() {
        ViewIdHandler.idHandler.generateAndSetIds(this);
    }

}

As you can see, the owner type is MyViewImpl, since that's the one that 
contains fields marked with @WithElementId.

This is very similar to UiBinder: the second type parameter ("type of the 
object that owns generated UI") is the actual View class (MyViewImpl).

Original comment by Vojtech....@gmail.com on 21 Aug 2012 at 8:58