24/11/14

CSS3 loaders per applicazioni GWT

Le applicazione GWT alle volte sono caratterizzate da tempi di caricamento relativamente lunghi (in particolare al primo accesso) e benché RunAsync aiuti molto a mitigare questo problema, capita di dover comunicare all'utente che l'applicazione sta caricando. 

Nel mondo 'html' questo viene solitamente fatto con i loaders, magari fatti usando css3 e animazioni che sono estremamente leggere e 'appealing'.
Anche nel mondo delle applicazioni GWT questo è possibile e si riesce ad utilizzare in meno di 5 linee di codice gli stessi loader CSS3.


In generale, in tutte le applicazioni 'ajax', quindi anche in GWT, capita di dover mantenere l'utente in attesa fintanto che una chiamata asincrona non sia terminata e la procedura da seguire è sempre la stessa: 
  • subito prima di iniziare la chiamata aprire un dialog di qualche tipo
  • eseguire la chiamata
  • alla risposta (o nello handler di eventuali errori), chiudere il dialog.
In questi casi, è utile utilizzare un dialog modale in quanto in genere si vuole evitare che l'utente possa effettuare altre operazioni nella ui durante l'esecuzione del metodo asincrono, ed e' altrettanto importante ricordarsi di chiudere il componente modale anche nello handler dell'errore altrimenti la ui resta non disponibile per futuri utilizzi.

Il caso che affronteremo in questo post è leggermente diverso rispetto ad un "classico" momento di attesa del termine di un'operazione; infatti vogliamo che il loader sia mostrato per tutto il tempo che l'applicazione GWT viene caricata e quindi prima  che l'applicazione stessa raggiunga il metodo onModuleLoad().


Per ottenere un 'modal dialog' che venga aperto prima che l'applicazione sia caricata bisogna rinunciare ad utilizzare uno widget e 'sporcarsi le mani con l'html', ma questo ci permette anche di poter pescare le animazioni e lo stile del loader dall'immenso bacino dei design 'HTML5'. 

Noi per questo esempio abbiamo scelto l'animazione che si trova all'url https://ihatetomatoes.net/create-css3-spinning-preloader e
che e' composta da un paio di div ed una stylesheet (come tutte le altre che e' facile trovare, e.g. http://projects.lukehaas.me/css-loaders, http://webdesign.tutsplus.com/tutorials/creating-a-collection-of-css3-animated-pre-loaders--cms-21978) ed inserirlo nella html host page è banale e consiste nell'inserire nella hostPage la css del loader e gli elementi html.
Il sorgente è accessibile alla url: Loader.html

Così facendo al caricamento della pagina (abbiamo 'inlined' anche le css per avere il rendering il + rapido possibile) avremo l'animazione di loading che sara' pero' possibile eliminare all'avvio dell'applicazione GWT semplicemente con 

    Document.get().getElementById("loader-wrapper").removeFromParent(); 

Per la verità invece di eliminare (removeFromParent()) il loader potrebbe essere utile semplicemente farlo sparire (getStyle().setDisplay(Display.NONE))  in modo da mantenerlo disponibile nel DOM per usarlo, ad esempio, come 'modal' ogni volta che serva impedire l'interazione con la ui (come dicevamo prima durante le chiamate asincrone ad esempio) cosa che può essere fatta con metodi come:

public static final void hideLoader() {
  Element e = Document.get().getElementById("loader-wrapper");
  if(e != null) {
    e.getStyle().setDisplay(Display.NONE);
  }
}

public static final void showLoader() {
  Element e = Document.get().getElementById("loader-wrapper");
  if(e != null) {
    e.getStyle().setDisplay(Display.BLOCK);
  } 
}

Nel nostro esempio abbiamo anche aggiunto al loader un <div> con id 'loader-wrapper' che usiamo per aggiungere un messaggio e che impostiamo attraverso il metodo statico:

public static final void setLoaderMessage(String msg) {
  Element e = Document.get().getElementById("loader-text");
  if(e != null) {
    e.setInnerHTML(msg);
  } 
}

L'unico vero problema con questo approccio è che gli id nella hostpage potrebbero, per qualche disattenzione, essere rimossi, cosa che causerebbe errori nell'applicazione GWT (npe); per questo in tutti i metodi che 'manipolano' il loader è bene controllare sempre che 
DOM.getElementById non ritorni null.