di .NET e di altre amenità

Webcast: Silverlight Scripting pronto per il download

Il webcast di ieri è oramai passato ed è giunto il momento di pubblicare gli esempi di codice. Abbiamo avuto qualche problema tecnico, i partecipanti non erano molti come speravo, ma tutto sommato è andato bene. Un grazie a tutti coloro che hanno partecipato online e che mi hanno posto qualche domanda.

Link: http://blog.boschin.it/download/Silverlight-Scripting.zip

Technorati tags: , ,

AJAX: Sfruttare al meglio Sys.EventArgs

Scrivere codice in Javascript richiede anche il sobbarcarsi l'onere di contenere le dimensioni di quanto si sta scrivendo. Sarebbe fantastico poter scrivere classi su classi, strutturare al meglio l'applicazione, seguire delle prolisse convenzioni di naming, però alla fine quello con cui sempre dobbiamo fare i conti è la banda e la quantità di dati da trasferire che se sottovalutata potrebbe rendere inutilizzabili le nostre applicazioni.

Qualche volta però basta un po' ingegnaarsi per riuscire a risparmiare un po' di codice e ottenere un risultato più che accettabile. A me ad esempio è successo esponendo eventi custom su dei controlli client derivati da Sys.UI.Control. Quando si espone un event capita spesso di dover allegare ad esso una classe Sys.EventArgs per fornire all'handler notifica di dettagli dell'evento. Nauralmente è sempre possibile estendere Sys.EventArgs per fornire delle proprietà aggiuntive e i relativi metodi get e set tuttavia questo comporta usare parecchie righe di codice la cui utilità è piuttosto limitata.

Utilizzando expando però è molto semplice fare un po' di economia. Grazie ad esso possiamo facilmente "attaccare" proprietà, metodi e quant'altro a oggetti già esistenti.

... var args = new Sys.EventArgs(); args.myCustomData = "dati..."; ...

Inutile creare una classe che estenda EventArgs. E' sufficiente appendere ad essa le proprietà che ci servono e risparmieremo un bel po' di bytes.

Technorati tags: , ,

Una classe Javascript per le pagine... ovvero, come organizzare meglio i propri script

Ci ho messo un po' a decidere come intitolare questo post, e alla fine ho optato per questo titolo alla Wertmuller perchè mi sembra che il succo del post che sto scrivendo sia proprio questo: imparare ad organizzare per bene il codice di scripting che come ho già detto più volte ha la spiacevole abitudine di infestare le pagine.

L'idea che vorrei presentare mi è venuta lavorando con la Microsoft AJAX Library, e in breve è diventata il mio modo standard di approcciare lo sviluppo in Javascript su pagine HTML perchè mi consente di lavorare con una sorta di codebehind in cui ragruppo tutto il codice di scripting. La base per poter raggiungere questo scopo è una classina che mi sono scritto in -Javascript:

Type.registerNamespace('Elite'); Elite.Page = function() { this.onload$delegate = Function.createDelegate( this, function(eventData) { this.load(); }); this.onunload$delegate = Function.createDelegate( this, function(eventData) { this.unload(); }); window.onload = this.onload$delegate; window.onunload = this.onunload$delegate; Elite.Page.initializeBase(this, [window]); } Elite.Page.prototype = { initialize : function() { Elite.Page.callBaseMethod(this, 'initialize'); }, load : function() { }, unload : function() { }, dispose : function() { if (this.onload$delegate) delete this.onload$delegate; if (this.onunload$delegate) delete this.onunload$delegate; Elite.Page.callBaseMethod(this, 'dispose'); } } } Elite.Page.registerClass('Elite.Page', Sys.UI.Control);

La classe che ho realizzato dovrà essere utilizzata come base per la classe che rappresenta la pagina. Al suo interno infatti sono stati realizzati una serie di metodi stub che ricalcano il funzionamento normale di una pagina asp.net. Troviamo ad esempio il metodo load() che viene collegato automaticamente all'evento window.onload enaturalmente anche la controparte unload().

Ecco come procedere se si vuole utilizzare questa tecnica in una propria pagina che poniamo si chiami dashboard.aspx:

1) create un file Javascript denominato dashboard.aspx.js. questo consente di tenere i file a scelta in una stessa cartella e reperirli facilmente oppure affiancarli alla pagina e vederli nel solution explorer accanto al codebehind. Al termine dello sviluppo se fate uso del Web Application Project potreste convertire tutti gli script in risorse embedded e caricali con webresource.axd.

2) includere il file javascript contenente la classe Elite.Page (ad esempio page.js). Dato che è obblgatorio fare uso della Microsoft AJAX Library è opportuno mettere la referenza all'interno dello ScriptManager che vi ricordo essere il luogo di elezione per qualsivoglia script.

3) nel file dashboard.asp.js creare una classe che rappresenti la pagina e erediti dalla classe Elite.Page. Ecco un esempio:

 

Type.registerNamespace('MyApplication'); MyApplication.DashboardPage = function() { MyApplication.DashboardPage.initializeBase(this, [window]); } MyApplication.DashboardPage.prototype = { load : function() { this.bSave = $get('bSave'); MyApplication.DashboardPage.callBaseMethod(this, 'load'); } }; MyApplication.DashboardPage.registerClass('MyApplication.DashboardPage', Elite.Page);

 

La particolarità di questa classe è di "registrare" il controllo bSave usando $get(). L'evento load() è il posto migliore per questo genere di attività dato che vi si arriva subito dopo che il download e il rendering della pagina è stato effettuato.

4) Inserire mediante ScritpManager.RegisterStartupScript() il codice che istanzia la classe:

var currentPage = $create(MyApplication.DashboardPage, {}, {}, null, null);

Questa chiamata in particolare istanzierà la classe e inizierà il ciclo di vita invocando il metodo initialize. Poi non appena caricata la pagina sarà invocato il metodo load() e di conseguenza la referenza verrà valorizzata con il controllo bSave. Naturalmente a questo punto sarà opportuno agganciare via script gli eventi dei controlli. Vi ricordo che per fare questo  è necessario l'uso di Function.createDelegate() che come ho già consente di mantenere il contesto di partenza e quindi di rimanere all'interno della classe.

Rimane solo un problema. Sappiamo tutti che i controlli di ASP.NET hanno un id generato a runtime che può essere semplice o composto. Per questo vi sarà necessario passare come argomenti della classe gli id estratti dai ClientID. In realtà ho un'altra idea in incubazione ma... questo sarà l'argomento di uno dei prossimi post.

Scripting: Gestire gli eventi e i callback

Giusto ieri ho scritto due righe a proposito di programmazione object-oriented in Javascript e oggi voglio tornare sull'argomento per fare un ulteriore passo verso un lavoro corretto e produttivo. Noi tutti quando scriviamo in C# o in un qualunque linguaggio realmente object oriented siamo abituati a gestire eventi asincroni nel contesto stesso in cui questi sono generati in modo tale che la loro invocazione e la loro gestione rimanga confinata all'interno di una sola istanza di una classe. In Javascript invece, questo non è immediatamente possibile; Spesso e volentieri ci si trova a dover gestire chiamate asincrone, o eventi generati dall'interfaccia e utilizzando un paradigma object oriented ci si trova a gestire eventi fuori del contesto dal quale essi sono generati. Mi rendo conto che spiegare a parole questo problema è difficile perciò vi propongo un esempio che chiarisca. Poniamo ad esempio di voler scrivere una classe che sia in grado di trasformare un elemento dell'interfaccia in un pulsante. Ad essa sarà passato in ingresso un elemento del DOM ed essa sarà in grado di agganciare l'evento onclick e gestirlo quando necessario:

   1: // HTML
   2:  
   3: <div id="myButton">Click Me!!!</div>
   4:  
   5: // Classe Javascript
   6:  
   7: ClickButton = function(message, element)
   8: {
   9:     this._message = message;
  10:     element.onclick = this._onclick;
  11: }
  12: ClickButton.prototype = 
  13: {
  14:     _onclick : function()
  15:     {
  16:         alert(this._message);
  17:     }   
  18: }
  19:  
  20: // USO
  21:  
  22: var bt = new ClickButton("Ciao!!!", $get("myButton"));

Guardando questo pezzetto di codice può sembrare tutto in perfetto ordine ma in realtà se provate a farlo girare in un qualsiasi browser il risultato sarà una message box con scritto "undefined" anzichè come ci si aspetterebbe un caloroso "Ciao!!!". Come dicevo pocanzi il problema è che Javascript non ha la nozione di contesto di una istanza, pur se noi passiamo ad essa un riferimento al metodo _onclick() in realtà stiamo semplicemente copiando in una variabile il corpo della funzione stessa che sarà completamente slegata dal contesto dell'istanza in cui si trova. Per ovviare a questo problema occorre passare all'evento non solo la funzione _onclick() ma anche un riferimento a "this" così che il runtime sia in grado di eseguirla correttamente. Per fare questo nella Microsoft AJAX Library esiste un metodo della classe statica "Function" che serve proprio a generare correttamente questa chiamata. Ecco come modificare la classe di cui sopra per farla funzionare:

   1: ClickButton = function(message, element)
   2: {
   3:     this._message = message;
   4:     element.onclick = Function.createDelegate(this, this._onclick);
   5: }
   6: ClickButton.prototype = 
   7: {
   8:     _onclick : function()
   9:     {
  10:         alert(this._message);
  11:     }   
  12: }

Con createDelegate() verrà generata una funzione che ha appunto lacapacità di invocare il metodo richiesto nel contesto della classe di appartenenza. La definizione del metodo createDelegate() pur con questo nome un po' altisonante altro non è che uno stratagemma per memorizzare il riferimento a this:

   1: Function.createDelegate = function(instance, method)
   2: {
   3:     return function()
   4:     {
   5:         return method.apply(instance, arguments);
   6:     }
   7: }

Chi ha provato ad usare Silverlight si sarà già imbattuto in qualcosa di analogo. In vari siti nella rete si trova la definizione di un metodo che è del tutto analogo a Function.createDelegate(). Ecco quindi un buon motivo per referenziare la Microsoft AJAX Library anche quando si lavora con Silverlight. Attenzione però che esiste almeno un evento che in Silverlight non potrete gestire con questo metodo. Si tratta dell'evento onResize dell'oggetto Host che tipicamente viene usato per riposizionare il layout al resize del controllo host.

Scripting: Creare una classe con Javascript

Javascript è un linguaggio ad oggetti, anche se per la massima parte l'uso che se ne fa è quello di scrivere piccole e semplici funzioni con un paradigma procedurale. In effetti scrivere ad oggetti per mezzo di javascript è molto diverso dallo scrivere in un comune linguaggio object-oriented. Molti dei principi di questo tioo di programmazione trovano applicazione solo per mezzo di stratagemmi, come ad esempio nel caso dell'ereditarietà.

La nuova libreria Microsoft AJAX Library, introduce una serie di utility che semplificano questo lavoro e consentono un uso più produttivo del paradigma object-oriented anche in ambito scripting. Per creare una classe in Javascript si può procedere in svariati modi, tuttavia facendo uso della libreria AJAX il metodo consigliato è quello dell'uso del prototype pattern come segue:

   1: MyClass = function()
   2: {
   3:     // costruttore della classe
   4: }
   5:  
   6: MyClass.prototype =
   7: {
   8:     // corpo della classe
   9: }

Le poche righe qui riportate mostrano lo scheletro di classe che si può realizzare facilmente anche senza l'uso della Microsoft AJAX Library. Possiamo facilmente aggiungere un metodo facendo uso della sintassi propria di JSON. Proviamo ad esempio a verificare che esista un effettivo incapsulamento delle informazioni dichiarando un campo "privato" e un metodo che dimostra il valore ad esso assegnato:

   1: MyClass = function(message)
   2: {
   3:     this._message = message;
   4: }
   5:  
   6: MyClass.prototype =
   7: {
   8:     display : function()
   9:     {
  10:         alert(this._message);
  11:     }
  12: }
  13:  
  14: // USO : --------------
  15:  
  16: var msg = new MyClass("Hallo World!");
  17: msg.display();

Il piccolo esempio riportato oltre a dimostrare le potenzialità di questa tecnica, ne dimostra anche i limiti. Ad esempio è piuttosto evidente che il campo "_message" che pur nella nostra immaginazione è evidentemente un campo privato, è facilmente accessibile dall'esterno. Non vi è naturalmente alcun modo di impedirlo, semplicemente perchè in javascript non esistono i modificatori di accesso, ma per convenzione nella libreria Microsoft l'uso di un trattino di sottolineatura sottintende che esso non debba mai essere usato esternamente.

Un'altro rilievo da notare è che l'uso della keyword "this" è obbligatorio. La sua omissione induce l'interprete a cercare una variabile globale con lo stesso nome e tutti noi sappiamo che in programmazione object-oriented di variabili globali non ve n'è traccia alcuna. Infine è da tenere in considerazione che la classe non può specificare una base e che di conseguenza non esiste override.

L'adozione della Microsoft AJAX Library ci da una serie di strumenti che ci permettono di simulare facilmente alcune delle "feature" della programmazione ad oggetti. Tanto per cominciare potremmo organizzare le nostre classi in namespace, dando ad esse una migliore leggibilità:

   1: Type.registerNamespace('Elite');
   2:  
   3: Elite.ButtonBehavior = function(element)
   4: {
   5:     this._element = element;
   6: }
   7:  
   8: Elite.ButtonBehavior.prototype = 
   9: {
  10:     ...
  11: }

La classe "Type" consente di creare un namespace fittizio che poi in seguito dovremmo riportare diligentemente in tutte le ricorrenze del nome. A questo punto ci dovremmo chiedere come fare ad estendere una nostra classe. Facciamo l'ipotesi di aver creato una classe che contenga delle funzioni base per il trattamento di elementi di interfaccia. Tale classe si chiamerà Elite.BaseElement. Mediante i metodi statici alle classi potremmo registrare la nuova classe creata ed assegnare ad essa una base:

   1: Elite.ButtonBehavior.registerClass('Elite.ButtonBehavior', Elite.BaseElement)

Per poter dire di aver completato la nostra classe non ci resta che adoperarci per fare in modo che avvenga l'invocazione dei metodi base. Non dimentichiamo infatti che si tratta pur sempre di una simulazione e quindi qualcosa da fare c'è, soprattutto per quanto riguarda il costruttore:

   1: Type.registerNamespace('Elite');
   2:  
   3: Elite.ButtonBehavior = function()
   4: {
   5:     Elite.ButtonBehavior.initializeBase(this);
   6: }
   7:  
   8: Elite.ButtonBehavior.prototype = 
   9: {
  10:     initialize : function()
  11:     {        
  12:         Elite.ButtonBehavior.callBaseMethod(this, 'initialize');
  13:     }
  14: }
  15:  
  16: Elite.ButtonBehavior.registerClass('Elite.ButtonBehavior', Elite.BaseElement);

Lo scheletro della classe è pronto. Non resta che aggiungere i metodi necessari ricordando che in Javascript le property non esistono ma possono essere emulate con una coppia di metodi get_ e set_. La Microsoft AJAX Library infatti istituzionalizza anche questo consentendo ad esempio in fase di inizializzazione di assegnare i valori come vere e proprie proprietà.