Alloy UI AutoComplete Ajax Example

1. Cos'è AlloyUI?

AlloyUI (abbreviato in AUI) è un meta framework Open Source realizzato da Liferay che ingloba una serie di librerie javascript fornendo un set di API per la creazione di applicazioni web robuste e flessibili che toccano i tre livelli del browser:  struttura, stile e comportamento. AUI è costruito su Yahoo User Interface (YUI) e ha il supporto per una vasta gamma di funzioni avanzate studiate appositamente per la tecnologia delle portlet.

2. Obiettivo

Le applicazioni web "moderne" sono sempre più vicine alle comuni applicazioni desktop, framework sempre più completi, complessi e di semplice utilizzo, rendono l'implementazione relativamente semplice, inoltre con l'avvento di HTML 5 la distanza con le applicazioni desktop diventa ancora più breve. In quest'articolo vedremo come sia semplice realizzare una combo completa di funzione auto complete, i cui dati presentati non sono "statici", bensì, provenienti da una base dati. Sfrutteremo Ajax per il reperimento dei dati da mostrare all'interno della combo. Ovviamente realizzeremo il tutto all'interno di una portlet. In Figura 1 è illustrato il risultato finale del lavoro. Quanto realizzato nel corso dell'articolo è disponibile come progetto Maven Liferay Portlet sul mio repository GitHub all'indirizzo liferay-aui-autocomplete-ajax-example

Alloy UI Autocomplete Ajax Example

Figura 1. Alloy UI Autocomplete Ajax Example

3. Requisiti

Per seguire con profitto il corso dell'articolo, è richiesta una conoscenza minima degli standard e tecnologie che ruotano nell'intorno delle Portlet, oltre che un minimo di dimestichezza con Liferay e il suo ambiente di sviluppo. Gli strumenti a supporto dovrebbero essere i seguenti:

  1. Liferay SDK 6.1
  2. Eclipse + Liferay Plugin o Liferay Studio
  3. Liferay Portal 6.1

L'esempio completo della portlet che troverete sul repository GitHub, è stato realizzato tramite Maven e l'archetype liferay-portlet-archetype versione 6.1.1 Community Edition.

 4. Serving resources

Nella prima versione delle specifiche Java Portlet Specification 1.0 (JSR 168), non era possibile servire risorse generate dinamicamente direttamente dalla portlet, era necessario un ulteriore servlet che serviva le risorse. Questa limitazione ha alcuni svantaggi:

  • Non è disponibile il contesto della portlet, il che significa non avere accesso a render parameters, portlet mode, window state, portlet preferences, portlet session e altro ancora;
  • Le URL generate nella servlet sono al di fuori dello scope del portale, inoltre, lo stato corrente delle pagine del portale viene perso;
  • La servlet non è eseguita nel contesto di sicurezza del portale;

Un vantaggio di servire risorse attraverso una servlet è il meno overhead, dovuto al fatto che la richiesta non deve passare attraverso l'intera struttura del portale, per esempio, quando è necessario servire grandi flussi multimediali.

La seconda versione delle specifiche Java Portlet Specification 2.0 (JSR 286) introduce un nuovo tipo di URL, la resource URL. La resource URL consente di attivare il metodo serveResource, sull'interfaccia ResourceServingPortlet che è possibile sfruttare per creare risorse dinamiche direttamente nel portlet.

package javax.portlet;

/**
* The ResourceServingPortlet interface allows
* serving resources through the portlet.
*
* The portlet container must call this method for links created by
* the RenderResponse.createResourceURL() call.
* If the portlet creates resource URLs with RenderResponse.createResourceURL()
* it must implement this lifecycle method.
*
* @since 2.0
*/
public interface ResourceServingPortlet {

/**
 * Called by the portlet container to allow the portlet to generate
 * the resource content based on its current state.
 * The portal / portlet container must not render any output in addition
 * to the content returned by the portlet. The portal / portlet container
 * should expect that the portlet may return binary content for a
 * renderResource call. * * @param request * the resource request * @param response * the resource response * * @exception PortletException * if the portlet has problems fulfilling the * rendering request * @exception UnavailableException * if the portlet is unavailable to perform render at this time * @exception PortletSecurityException * if the portlet cannot fullfill this request because of security reasons * @exception java.io.IOException * if the streaming causes an I/O problem */ public void serveResource(ResourceRequest request, ResourceResponse response) throws PortletException, java.io.IOException; }

Noi sfrutteremo questo nuova caratteristica per fornire alla view i dati da mostrare all'interno della combo.

5. Implementazione del serverResource

Partendo dal presupposto che abbiate creato una portlet MVC, vediamo come implementare il metodo serverResource(). Prima di procedere con il come, affrontiamo il cosa, ovvero, che tipo di risorsa e quali dati il metodo deve restituire alla view?. Ipotizziamo che i dati da voler mostrare sulla combo siano l'insieme di ruoli definiti su Liferay e il formato dei dati da restituire deve essere in formato JSON; detto questo, in breve occorre:

  1. Definire una Dynamic Query sull'entità Role;
  2. La Dynamic Query deve esplicitare una where conditions di tipo like sull'attributo name dell'entità Role;
  3. Utilizzare il Local Service Util per recuperare i dati dei ruoli applicando il filtro attraverso la Dynamic Query definita in precedenza;
  4. Preparare l'oggetto JSON contenente i ruoli estratti in precedenza;
  5. Inviare alla view i dati.

La Dynamic Query consente di recuperare la lista dei ruoli il cui nome fa "scopa" con quanto digitato dall'utente all'interno della combo, per esempio, se l'utente digitasse Site* allora sarebbe recuperata la lista di ruoli dove ogni item ha l'attributo name che inizia con Site. Cercando di rispettare l'ordine delle cose da fare (visto in precedenza), cercherò di mostrare brevemente i pezzi di codice del come.

JSONObject json = JSONFactoryUtil.createJSONObject();
JSONArray results = JSONFactoryUtil.createJSONArray();
json.put("response", results);

Source 1 - Inizializzazione oggetti JSON.

Il codice illustrato sopra è una semplice inizializzazione degli oggetti JSON (Object e Array) utilizzati in seguito come contenitori di dati. Invece, a seguire, al Source 2 è mostrata la definizione e creazione della Dynamic Query secondo le regole descritte in precedenza.

DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(Role.class).
add(PropertyFactoryUtil.forName("name").like(searchPattern));

Source 2 - Definzione e creazione della Dynamic Query.

Al Source 3, sono invece mostrati il recupero della lista di ruoli e la successiva preparazione dei dati appena restituiti dal Role Local Service  in formato JSON.

List roles = RoleLocalServiceUtil.dynamicQuery(dynamicQuery);
for (Role role : roles) {
JSONObject listEntry = JSONFactoryUtil.createJSONObject();
listEntry.put("key", role.getRoleId());
listEntry.put("name", role.getName());
listEntry.put("description", role.getDescription());
listEntry.put("type", role.getType());
results.put(listEntry);
}

Source 3 - Recupero lista dei ruoli e preparazione dati in JSON.

resourceResponse.setContentType(ContentTypes.APPLICATION_JSON);
PrintWriter writer = resourceResponse.getWriter();
writer.println(json.toString());

Source 4.a - Invio dei dati in formato JSON alla view.

Le ultime righe di codice del Source 4.a inviano la lista di ruoli che soddisfano la ricerca verso la view in formato JSON. La prima riga informa il client che riceve i dati circa il formato degli stessi. Al Source 5 è possibile vedere un esempio di dati inviati alla view. L'esempio completo della classe ViewAUIAutocompleteAjax che implementa l'MVCPortlet è disponibile all'indirizzo ViewAUIAutocompleteAjax.java. In realtà è possibile approfittare del metodo writeJSON() della classe com.liferay.portal.kernel.portlet.LiferayPortlet per ottenere lo stesso risultato del codice precedente (vedi Source 4.b).

writeJSON(resourceRequest, resourceResponse, json);

Source 4.b - Invio dei dati in formato JSON alla view.

{
    "response": [
        {
            "description": "Site Administrators are super users of their site but cannot make other users into Site Administrators.",
            "name": "Site Administrator",
            "type": 2,
            "key": 10153
        },
        {
            "description": "All users who belong to a site have this role within that site.",
            "name": "Site Member",
            "type": 2,
            "key": 10154
        },
        {
            "description": "Site Owners are super users of their site and can assign site roles to users.",
            "name": "Site Owner",
            "type": 2,
            "key": 10155
        }
    ]
}

Source 5 - Dati in formato JSON.

6. Implementazione della view

La view (che dovrebbe poi essere la jsp view.jsp) fondamentalmente si compone di uno script AUI che utilizza i seguenti componenti:

  1. AUI AutoComplete (Alloy UI Modules)
  2. DataSource.IO (YUI Modules)

Il primo elemento è responsabile della visualizzazione e selezione dell'insieme di dati resi disponibili dal secondo componente di datasource, quest'ultimo è invece responsabile del recupero dei dati inviando la richiesta alla resourceURL della portlet. Al Source 6, è mostrato lo script AUI che crea la combo vista precedentemente in Figura 1.

var dataSource = new A.DataSource.IO(
    {
        source: '<%=ajaxResourceURL %>'
    }
);

var autocomplete = new A.AutoComplete(
    {
        dataSource: dataSource,
        delimChar: '',
        contentBox: '#myAutoComplete',
        matchKey: 'name',
        schema: {
        resultListLocator: 'response',
        resultFields: ['key','name','description', 'type']
    },
    uniqueName:'keyword',
    schemaType:'json',
    typeAhead: true,
    cssClass: 'ac_input'
});

autocomplete.generateRequest = function(query) {
    return {
    request: '&<%=DisplayTerms.KEYWORDS %>=' + query
    };
}

autocomplete.render();

Source 6 - AUI Script per l'AutoComplete Ajax.

Nel codice sorgente mostrato al Source 6, ho voluto evidenziare le righe che ritengo più rilevanti:

  1. L'attributo source del componente DataSource impostato al valore della resourceURL della portlet (vedi Source 7);
  2. L'attributo schema del componente di AutoComplete che informa lo stesso circa lo schema della struttura dati JSON restituita;
  3. Il metodo generateRequest() genera un oggetto che sarà poi passato al metodo sendRequest() del DataSource. In breve, alla resourceURL sarà "appeso" il valore digitato nella combo.
<portlet:resourceURL var="ajaxResourceURL">
    <portlet:param name="<%=Constants.CMD %>" value="<%=ActionKeys.GET_LIFERAY_ROLES %>"/>
</portlet:resourceURL>

Source 7 - Creazione resourceURL della portlet.

Ovviamente è possibile sfruttare il metodo serverResource() per eseguire diverse azioni distinte per la stessa portlet, per questo motivo sulla resourceURL creata (vedi Source 7), è stato impostato un parametro che esplicita l'azione da eseguire. L'esempio completo della view è disponibile all'indirizzo view.jsp

7. Conclusioni

Ancora una volta abbiamo visto come il riutilizzo dei componenti software e il progredire delle tecnologie favoriscono la creazione di qualunque manufatto soprattutto in termini di tempo, quest'ultimo prezioso visto il diminuire del Time to Market.

Il progetto AUI è un percorso quasi obbligato per tutti coloro che intraprendono la strada di Liferay e in particolare modo per coloro che sviluppano o avranno intenzione di sviluppare applicazioni che richiedono un livello spinto d'interazione uomo-macchina. Purtroppo la documentazione a corredo del progetto è molto povera e gli esempi presentati non soddisfano mai gli usi reali.

0 Condivisioni

Antonio Musarra

I began my journey into the world of computing from an Olivetti M24 PC (http://it.wikipedia.org/wiki/Olivetti_M24) bought by my father for his work. Day after day, quickly taking control until … Now doing business consulting for projects in the enterprise application development using web-oriented technologies such as J2EE, Web Services, ESB, TIBCO, PHP.

Potrebbero interessarti anche...