понедельник, 11 апреля 2016 г.

Issue with external js when using in GWT (Errai)

Recently i've bumped into a problem that an external javascript, declared in the html, which is then @Templated by a Java class using Errai UI, doesn't work. The issue might be easy for experienced ones, however, i'm going to describe the solution here as i've spent really a lot time on this.

Errai is a great GWT-based framework. Its module Errai UI allows to work with "pure HTML" comfortably. For example, you may have a look at the errai-tutorial example, where bootstrap is actually used for styling the page.

I wanted to use a bootstrap-slider, that "paints" beautiful sliders. So I put
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/6.1.7/bootstrap-slider.js" type="text/javascript"></script>
into the gwt host page head. However, it didn't work. Also putting this line into the html template didn't help.


The reason: when the js gets executed, the html isn't loaded yet.
Solution:

@Templated("MyTemplate.html#mytag")
public class MyTemplate extends Composite {

  @Override
  protected void onLoad() {
    ScriptInjector.fromUrl("https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js").setWindow(ScriptInjector.TOP_WINDOW).inject();
    ScriptInjector.fromUrl("https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js").setWindow(ScriptInjector.TOP_WINDOW).inject();
    ScriptInjector.fromUrl("https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/6.1.7/bootstrap-slider.js").setWindow(ScriptInjector.TOP_WINDOW).inject();
  }

So we use onLoad() which is is called immediately after a widget becomes attached to the browser's document.

Important: Obligatorily call .setWindow(ScriptInjector.TOP_WINDOW). It puts the scripts declaration into the head of the host page. Otherwise, the scripts don't work.

Moreover: The solution above actually causes error, which you can observe in the console of the browser dev tools. It's message says that bootstrap js can't work without jquery. The reason is that ScriptInjector injects scripts asynchronously. It doesn't wait for jquery library to be loaded and starts loading boostrap. The solution is load libraries in a strict order. Method setCallback allows to achieve it:

@Override
protected void onLoad() {
  ScriptInjector.fromUrl("https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js").setCallback(new Callback() {
    
    @Override
    public void onSuccess(Void result) {
      ScriptInjector.fromUrl("https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js").setCallback(new Callback() {

        @Override
        public void onFailure(Exception reason) {
          // TODO Auto-generated method stub
          
        }

        @Override
        public void onSuccess(Void result) {
          ScriptInjector.fromUrl("https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/6.1.7/bootstrap-slider.js").setWindow(ScriptInjector.TOP_WINDOW).inject();
          
        }
      }).setWindow(ScriptInjector.TOP_WINDOW).inject();
    }
    
    @Override
    public void onFailure(Exception reason) {
      // TODO Auto-generated method stub
      
    }
  }).setWindow(ScriptInjector.TOP_WINDOW).inject();
}
It looks awful and needs to be refactored, but illustrates the idea,

Great thanks for openness and help to Max Barkley.

0 коммент.:

Отправить комментарий