load icons from a separate project without Vaadin Application

I have 2 IDEA modules (“projects” in Eclipse terms):

  1. Main. it has a vaadin application and WebContent/VAADIN/icons folder where I keep my icons.
  2. additional module/project with some Vaadin Custom Components / Editors. I want this additional module to load its own icons from some folder.

I can’t use ClassResource from section 4.5.3 http://vaadin.com/book/-/page/application.resources.html because the 2nd project does not have an application. Plus it seems weird to put icons into some “application”.

do I have to create WebContent/VAADIN/icons folder in my 2nd project to use ThemeResource?

what’s the best way to load icons in an auxiliary project, which is linked to the main project at runtime.

Hi,

Well, if you know the URL of the main application inside of the aux one - then should be possible to accomplish the task with
ExternalResource
.

sasha

ok, I’ll try. but why would the 2nd project need to know the URL of the main app?
I just want the 2nd project to load its own icons from its folder, not the ones from “main”.

Hi!

Oh, I got you wrong, then of course you don’t need that URL.


sasha.

With ClassResource, the Application instance is only used in a call to application.getClass().getResourceAsStream(resourceName). You can get the real Application instance e.g. with AbstractComponent.getApplication() from any component as long as both modules are using the same classloader.

If using a ThemeResource, the icons should be in a Vaadin theme, or you need to use theme-relative paths for them (e.g. “…/…/icons/myicon.png” in your case). The resources should be served to browsers from the VAADIN directory or a subdirectory of it, at the same path as your theme. It might make sense to use VAADIN/themes/icons rather than VAADIN/icons.

The forum keeps clearing my “subscribed” flag, so I don’t get email updates from it :frowning:

what if I don’t have a component in that class?

I tried this

Resource icon = new ClassResource("options_32.png", new Label().getApplication());
setSmallIcon(icon);

but it fails with NPE.


public class AdminModule extends BATModule {
  public static final String ID = "admin module";
  public static final String MODULE_CATEGORY_ADMIN = "Admin";

  public AdminModule(BATModule parent) {
    super(parent, "Administration");

//    setSmallIcon(new ThemeResource("../../icons/options_32.png"));
    Resource icon = new ClassResource("options_32.png", new Label().getApplication());
    setSmallIcon(icon);
    setLargeIcon(new ThemeResource("../../icons/options_72.png"));

    addChild(new UserAdminModule(this));
  }

  @Override
  public String getId() {
    return ID;
  }

  @Override
  public String getCategory() {
    return MODULE_CATEGORY_ADMIN;
  }
}

(BATModule is a POJO class, which is a “building block” for my Vaadin application)

This does not work:

Resource icon = new ClassResource("options_32.png", new Label().getApplication());

You need to attach the Label component into another component that is already attached into Vaadin application. Otherwise AbstractComponent.getApplication() returns null. By attaching I mean calling e.g.

parentComponent.addComponent(myLabel);

I think you need some other mechanism to pass current application instance into your applications. One option is to use some kind of injection or perhaps a ThreadLocal pattern, see this:
ThreadLocal Pattern - Wiki
. It also prevents from your API to bloat as you do not have to pass Application instance for your classes manually. You most likely have many places where you need common instances such as current Application instance, not all places have such Vaadin components that have AbstractComponent.getApplication() API available.


Jani Laakso, Founder / Senior Vaadin Expert

I just checked ClassResource and StreamResource implementation and I have to say that Vaadin has a bit stupid API. Both require Application instance just to obtain the correct classloader, I do not know why because classloader should not be defined by Application IMO. More convenient API would be to let the Vaadin user define which classloader is used, it does not have to rely on Application. This is the situation which Alexey has.

As a workaround you can fork ClassResource (or StreamResource) class. It’s very simple and should cause you too much of trouble when Vaadin is updated. Of course, YMMW.

Perhaps a ticket for Vaadin R&D?

Current API has this:

public ClassResource(String resourceName, Application application) {
       associatedClass = application.getClass(); .. 

We could add also this new API:

public ClassResource(String resourceName, Class myClassFromWhichClassLoaderIsUsed) {
       associatedClass = myClassFromWhichClassLoaderIsUsed; .. 

Too bad Vaadin needs the Application instance just to “open” the resource to be visible to the end user. So I think the only viable option is to use injection or ThreadLocal, those should work.


Jani Laakso, Founder / Senior Vaadin Expert

Actually, I read the class a bit too quickly and apparently so did Jani. In addition to selecting the classloader, the application instance is also used for two other purposes: for keeping track of registered resources for the application (for client-server communication) and for accessing the context which is needed to create an internal URL for the client to use.

Nevertheless, a valid Application instance is all that is needed, so e.g. accessing the application using a ThreadLocal should be ok.

Yes. Hence I added later the note "
Vaadin needs the Application instance just to “open” the resource to be visible to the end user.
". Still, this resource registration to Applicatoin instance might be done later (lazily), but I’m not sure.

I’ll try to check if there’s more OSGI way to accomplish passing the Application, perhaps on module registration event or such. Currently there’s too much of pain getting Vaadin modules work in OSGI environment, hopefully we can fix this by writing an article.

As a quick and simple response/solution for Alexey’s problem-at-hand, and assuming that BatModule extends a Component of some kind (it looks like it does), simply move the setting of the icons into the attach method. That way, you are guaranteed to have an application!
e.g.

public class AdminModule extends BATModule {
  public static final String ID = "admin module";
  public static final String MODULE_CATEGORY_ADMIN = "Admin";

  public AdminModule(BATModule parent) {
    super(parent, "Administration");
    
    addChild(new UserAdminModule(this));
  }


  @Override
  public void attach() {
    super.attach();    

    Resource icon = new ClassResource("options_32.png", getApplication());
    setSmallIcon(icon);
    setLargeIcon(new ThemeResource("../../icons/options_72.png"));
  }

  @Override
  public String getId() {
    return ID;
  }

  @Override
  public String getCategory() {
    return MODULE_CATEGORY_ADMIN;
  }
}

Cheers,

Charles

Charles is right, attach trick should work too. Still, overriding attach is also a bit hacky approach.

I would like to see some kind of common interface for all sub modules requiring Application instance. This interface would require implementing a method that receives Application instance from the module that contains it. Should be reasonably easy because the module that contains Application instance is actually creating the classes of those sub modules that do not have Application instance. Another thing is that do not create Vaadin components on sub module constructor, create them after your interface method has been called and the sub module has the Application instance available.

Alexey, could you check
Creating a Modular Vaadin Application with OSGI
article. Petter’s article might provide some more ideas.

Hope this helps! Be sure to keep us posted. I have a gut feeling that this issue is not Vaadin specific, there must be bunch of other cases too in which some object is required on sub modules. But, if Vaadin has issues with OSGI, let’s fix it :slight_smile:

PS. Even though I suggested using ThreadLocal, I do not like using it between OSGI modules, it just feels wrong. Using it on Vaadin Application instance is cool thing.