Adobe Air: aria di cambiamento

Immagini

Immagine 1 Immagine 2 Immagine 3 Immagine 4

Adobe Air è finalmente arrivato. Dopo un solo anno di beta testing il più giovane prodotto di Adobe arriva ufficialmente tra noi. Sembra ieri quando un anno fa è stata annunciata la prima versione alpha in test pubblico con nome in codice Apollo. Da allora è stata un’evoluzione continua: abbiamo avuto due release Alpha, tre Beta e integrazione con i principali strumenti di sviluppo forniti dalla Casa delle Idee ( Flash, Flex e Dreamweaver ). Adobe Air in ogni caso non è solo una tecnologia legata a prodotti commerciali in quanto il compilatore è rilasciato sotto licenza open source, questo offre a chiunque la possibilità di sviluppare applicazioni in completa libertà. Altro elemento da non sottovalutare è la possibilità di scegliere tra diversi linguaggi di programmazione per lo sviluppo: Adobe Air infatti si rivolge non solo a sviluppatori Flex/Flash ma anche a professionisti che lavorano in ambito web utilizzando tecnologie più “tradizionali” quali HTML, Javascript e CSS.

Dopo questa dovuta introduzione ad una tecnologia che rivoluzionerà le vostre vite di sviluppatori affrontiamo la realizzazione della nostra prima applicazione Air; andremo a creare un semplice task manager utilizzando Flex e Actionscript.La nostra applicazione utilizzerà mxml e actionscript 3 per gestire una lista di attività da fare salvata in locale in un file XML; tramite l’interfaccia grafica sarà possibile visualizzare, modificare, aggiungere e rimuovere elementi alla nostra lista di attività. Essendo i dati salvati in formato XML sul disco sarà possibile utilizzare i dati in altre applicazioni senza complicazioni. Apriamo Flex 3 e creiamo un nuovo progetto desktop. (Immagine 1) Manteniamo le impostazioni di default offerte da Flex specificando che stiamo creando un’applicazione desktop. Verrà creato il file main.mxml che definirà l’aspetto della nostra applicazione. Per quanto concerne l’implementazione delle funzionalità utilizzeremo una classe actionscript che poi assoceremo al documento mxml; questa tecnica viene chiamata “code behind” e consiste nell’utilizzare il codice mxml solo per definire l’aspetto dell’applicazione e una classe esterna associata per controllare l’interazione.

Questa tecnica è molto efficace perchè permette una netta separazione tra aspetto e funzionalità, offrendo molta flessibilità nell’apportare modifiche al programma. Creiamo quindi la nostra classe esterna all’interno del namespace della nostra applicazione: cef62.demo.TaskList.as La nostra classe, essendo associata all’applicazione Air dovrà derivare dalla classe WindowedApllication. (Immagine 2) Una volta creata dovrebbe presentarsi così: (Immagine 3) ora siamo pronti per associare la nostra classe adibita a gestire la business logic al suo layout di presentazione, per fare questo dobbiamo aggiungere il namespace della nostra classe nella dichiarazione del nodo principale del documento mxml.
Dopo aver aggiunto il namespace possiamo finalmente definire l’associazione:

  1. <!--<?xml version="1.0" encoding="utf-8"?>
  2. <demo:TaskList
  3. xmlns:mx="http://www.adobe.com/2006/mxml"
  4. xmlns:demo="cef62.demo.*"
  5. layout="absolute" applicationComplete="init(event)" width="600" height="400"
  6. title="Demo Task List" backgroundColor="0xEEEEEE">
  7. </demo:TaskList>
Nella dichiarazione del nodo abbiamo usato il namespace ‘demo’ da noi definito per richiamare la classe ‘TaskList’. Tramite l’attributo ‘applicationComplete’ del nodo radice abbiamo definito una funzione, che risiederà nella classe esterna, da richiamare una volta che il layout mxml è pronto all’uso. Andiamo a definire la funzione di init() e le altre ad essa associate:
  1. // variabile che verrà associata al file XML usato come sorgente dati
  2. protected var xmlDataFile:File;
  3. // istanza della classe stream, adibita alla lettura
  4. // e scrittura dei dati da e verso il file XML
  5. protected var stream:FileStream = new FileStream();
  6. // XML ottenuto dal file esterno
  7. public var dpXml:XML;
  8. // dataprovider per la nostra datagrid, definito Bindable
  9. // così da poter essere collegato dinamicamente al componente visuale
  10. [Bindable]
  11. public var dp:XMLListCollection = new XMLListCollection();
  12.  
  13. // funzione di inizializzazione, chiamata dall'evento ApplicationComplete
  14. // generato una volta che il nostro layout mxml è pronto all'uso
  15. public function init( e:FlexEvent ):void
  16. {
  17. // carichiamo la sorgente di dati XML esterna
  18. updateDataSource();
  19. }
  20.  
  21. // funzione che carica il file XML e lo definisce
  22. // come dataprovider della datagrid
  23. private function updateDataSource():void
  24. {
  25. // carichiamo il file Xml
  26. xmlDataFile = File.desktopDirectory.resolvePath( "archivio.xml" );
  27. dpXml = readFromXML();
  28.  
  29. // aggiorniamo il data provider
  30. // utilizzando i dati importati dal file XML esterno
  31. dp.source = dpXml[0].task;
  32. dp.refresh();
  33. }
  34.  
  35. // funzione che legge il contenuto di un file XML esterno
  36. // se il file non esiste inizializza l'applicazione come se
  37. // il file esterno esistesse e fosse vuoto
  38. private function readFromXML():XML
  39. {
  40. var tXML:XML;
  41. // verifichiamo se il file esiste
  42. if( xmlDataFile.exists )
  43. {
  44. // oggetto bytearray per contenere contenuto binario del file letto
  45. var byteData:ByteArray = new ByteArray();
  46.  
  47. // apriamo stream al file
  48. stream.open( xmlDataFile, FileMode.READ );
  49. // leggiamo il contenuto binario del file
  50. stream.readBytes( byteData, 0, xmlDataFile.size );
  51. // chiudiamo lo stream al file
  52. stream.close();
  53.  
  54. // se il file è vuoto creiamo xml temp
  55. if(byteData.length > 0)
  56. {
  57. tXML = XML( byteData.readUTFBytes( byteData.length ) );
  58. } else {
  59. tXML = <base>
  60. <task title="---" content="---" type="---"/>
  61. </base>;
  62. }
  63. } else {
  64. // se il file non esiste creiamo xml temp
  65. tXML = <base>
  66. <task title="---" content="---" type="---"/>
  67. </base>;
  68. }
  69.  
  70. return tXML;
  71. }
Per avere un’applicazione compilabile, anche se ancora priva di layout grafico, dobbiamo andare a definire alcuni valori all’interno dell’XML descrittore dell’applicazione Air. Il file si chiamerà main-app.xml ed è stato generato per noi da Flex durante la creazione del progetto.
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <application xmlns="http://ns.adobe.com/air/application/1.0.M6">
  3. [...]
  4.  
  5. <initialWindow>
  6. <systemChrome>none</systemChrome>
  7. <maximizable>false</maximizable>
  8. <resizable>false</resizable>
  9. <width>600</width>
  10. <height>400</height>
  11. </initialWindow>
  12.  
  13. [...]
  14. </application>
Tra i nodi indicati l’unico veramente importante è ‘systemChrome’ impostato a ‘none’ perchè derivando la nostra classe da ‘windowedApplication’ dobbiamo specificare che la skin dell’applicazione non deve essere quella default per le applicazioni Air. La classe ‘windowedApplication’ infatti è stata concepita da Adobe per offrire un’aspetto standard coerente su diversi sistemi operativi senza dover per forza implmentare una soluzione skin realizzata ad-hoc. Inseriamo ora nel modulo mxml la datagrid che visualizzerà la nostra lista di attività.
  1. <mx:DataGrid x="39" y="17" width="520" height="294" editable="true" id="dataGrid"
  2. dataProvider="{dp}"
  3. itemEditEnd="finishItemEditing(event)">
  4. <mx:columns>
  5. <mx:DataGridColumn headerText="Titolo" dataField="@title"/>
  6. <mx:DataGridColumn headerText="Descrizione" dataField="@content"/>
  7. <mx:DataGridColumn headerText="Categoria" dataField="@type"/>
  8. </mx:columns>
  9. </mx:DataGrid>
Creiamo una datagrid editabile a cui assegnamo come dataprovider la nostra variabile di tipo XMLListCollection: ‘dp’, includendo la variabile tra le parentesi graffe andiamo ad indicare che il dataprovider è collegato dinamicamente, così quando si aggiornerà verrà aggiornata anche la datagrid. Alla griglia abbiamo registrato una funzione per l’evento di ‘itemEditEnd’ che viene generato quando si ha finito di modificare il contenuto di una cella.
  1. // datagrid definita nel file mxml associato, マ necessario dichiararla come variabile
  2. // per renderla accessibile alle funzioni della nostra classe
  3. public var dataGrid:DataGrid;
  4.  
  5. // funzione chiamata quando si conclude di modificare il contenuto
  6. // di una cella della datagrid
  7. // verifica se il contenuto マ stato modificato e se necessario
  8. // aggiorna il contenuto del file XML esterno
  9. public function finishItemEditing( e:DataGridEvent ):void
  10. {
  11. // datagrid di riferimento
  12. var ref:DataGrid = e.target as DataGrid;
  13. // XML dataprovider della riga interessata
  14. var xmlNodeRef:XML = XML( dp.getItemAt( e.rowIndex ) );
  15. // contenuto della cella di testo editabile prima dell'azione di modifica
  16. var oldCellString:String =
  17. xmlNodeRef[ (ref.columns[ e.columnIndex ] as DataGridColumn ).dataField ];
  18. // istanza del campo di testo modificato
  19. var itemEditorInstance:TextInput = TextInput( ref.itemEditorInstance );
  20. // contenuto testuale dopo la modifica
  21. var newCellString:String = itemEditorInstance.text;
  22.  
  23. // se necessario aggiorniamo il file XML esterno
  24. if( oldCellString != newCellString )
  25. {
  26. writeXMLToFile();
  27. }
  28.  
  29. }
  30.  
  31. // funzione che aggiorna il file XML esterno con i dati attualmente esistenti
  32. private function writeXMLToFile():void
  33. {
  34. // apriamo stream al file in modalitˆ sovrascrittura
  35. stream.open( xmlDataFile, FileMode.WRITE );
  36. // scriviamo il contenuto testuale nel file
  37. stream.writeUTFBytes( createXMLStringToExport() );
  38. // chiudiamo lo stream al file
  39. stream.close();
  40. }
Inseriamo tre pulsanti dopo la datagrid:
  1. <mx:Button x="39" y="319" label="Aggiungi" click="addItem(event)"/>
  2. <mx:Button x="123" y="319" label="Elimina" click="removeItem(event)"/>
  3. <mx:Button x="458" y="319" label="view source.." click="showSource(event)"/>

si occuperanno di: aggiungere attività all’elenco, rimuovere l’attività selezionata dall’elenco e visualizzare in una nuova finestra la sorgente di dati XML. Questi tre pulsanti sono associati ad altrettante funzioni nella nostra classe Actionscript:
  1. // chiamata quando si richiede l'aggiunta di un nuovo task
  2. // crea un nuovo task, in coda agli altri
  3. public function addItem( e:MouseEvent ):void
  4. {
  5. var lastItem:XML = XML( dp.getItemAt( dp.length -1 ) );
  6. var newItem:XML = new XML( lastItem.toXMLString() );
  7. newItem.@title = "---";
  8. newItem.@content = "---";
  9. newItem.@type = "---";
  10. dp.addItem( newItem );
  11. dp.refresh();
  12.  
  13. // aggiorniamo il file su disco
  14. writeXMLToFile();
  15. }
  16.  
  17.  
  18. // chiamata quando si vuole eliminare il task selezionato
  19. // mostra un messaggio d'errore se non マ selezionata nessuna
  20. // riga della datagrid
  21. public function removeItem( e:MouseEvent ):void
  22. {
  23. var activeRow:int = dataGrid.selectedIndex;
  24. if( activeRow >= 0 && activeRow < dp.length )
  25. {
  26. dp.removeItemAt( activeRow );
  27. dp.refresh();
  28.  
  29. // aggiorniamo il file su disco
  30. writeXMLToFile();
  31.  
  32. } else {
  33. Alert.show( "Non è possibile eliminare nessuna riga, selezionarne una prima!",
  34. "Attenzione" );
  35. }
  36. }
  37.  
  38. // mostra il sorgente XML in una finestra independente
  39. // utilizzando le funzioni di gestione finestre di Adobe Air
  40. public function showSource( e:MouseEvent ):void
  41. {
  42. // creiamo una nuova finestra e ne definiamo l'aspetto
  43. var sourceView:Window = new Window();
  44. sourceView.title = "Source View";
  45. sourceView.maximizable = false;
  46. sourceView.minimizable = false;
  47. sourceView.resizable = false;
  48. sourceView.width = 650;
  49. sourceView.height = 500;
  50. sourceView.layout = "absolute";
  51.  
  52. // creiamo la textarea che visualizzerˆ il documento XML
  53. var txtArea:TextArea = new TextArea();
  54. txtArea.x = 20;
  55. txtArea.y = 20;
  56. txtArea.width = 610;
  57. txtArea.height = 460;
  58. txtArea.editable = false;
  59.  
  60. // definiamo un'indentazione leggibile per l'output a stringa da XML
  61. XML.prettyIndent = 2;
  62. XML.prettyPrinting = true;
  63. // assegnamo il testo XML all'area di testo
  64. txtArea.text = createXMLStringToExport();
  65.  
  66. // aggiungiamo l'area di testo alla finestra
  67. sourceView.addChild( txtArea );
  68. // mostriamo la finestra
  69. sourceView.open();
  70. }
  71.  
  72. // funzione che genera una stringa XML valida utilizzando i dati presenti nella datagrid
  73. private function createXMLStringToExport():String
  74. {
  75. // dichiarazione XML + il separatore di linea corretto per il sistema operativo ospite
  76. var xmlDeclaration:String = '<?xml version="1.0" encoding="utf-8"?>' + File.lineEnding;
  77. // corpo XML
  78. var XMLString:String = "<base>" + dp.toXMLString() + "</base>";
  79. var tXML:XML = new XML( XMLString );
  80. // stringa XML completa
  81. var xmlExportString:String = xmlDeclaration + tXML.toXMLString();
  82.  
  83. return xmlExportString;
  84. }
Ora l’applicazione è pronta per essere compilata ed utilizzata. (Immagine 4) Ovviamente prima di poter essere usabile sarà necessario aggiungere alcune funzionalità, ma può essere comunque un buon inizio.