Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Tooltip con le classi di Prototype

Applichiamo la programmazione a oggetti del framework Prototype alla realizzazione di tooltip
Applichiamo la programmazione a oggetti del framework Prototype alla realizzazione di tooltip
Link copiato negli appunti

In questo articolo vedremo come realizzare un plugin JavaScript per la creazione di tooltip, permettendo agli utenti di visualizzare un testo descrittivo supplementare al passaggio del mouse sopra link o altri elementi.

L'esempio sarà il pretesto per mostrare come scrivere il codice della demo utilizzano Prototype.js e Scriptaculous, e ci offre l'occasione di esplorare l'uso e la creazione di una classe, con un approccio object oriented più tipico di Java e C# piuttosto che di Javascript.

Grazie alla classe sarà possibile creare e personalizzare i tooltip con estrema facilità, utilizzando anche più istanze nello stesso documento, senza dover scrivere codice inutile o ridondante.

Il codice presentato è in gran parte derivato da vari altri script che meritano una abbondante esplorazione se utilizzate Prototype e siete interessati all'uso dei tooltip: Cooltips, Cordinc tooltip,Tooltip-v0.2, Anatips.

Javascript OOP

È inizialmente necessario soffermarsi su alcune funzionalità che Prototype.js aggiunge a JavaScript per la crazione delle classi. Class.create è la sintassi necessaria per la costruzione di una nuova classe: si assegna Class.create ad una variabile che è il nome della classe.

Possiamo immaginare la classe come un contenitore che ingloba e gestisce tutte le funzioni necessarie al nostro script, come una grande funzione (e in effetti lo è) che espone diversi metodi.

La struttura tipo del codice è più o meno questa: Class.create({ function1, function2, function3 }).

Grazie a Prototype.js, la classe può disporre di un costruttore, cioè di un metodo che viene eseguito quando la classe è istanziata. Tale metodo è chiamato initialize().

Appena viene creata una nuova istanza della classe la funzione initialize() verrà eseguita e con essa anche le eventuali istruzioni contenute. Questo costruttore può accettare degli argomenti che verranno passati nel momento in cui viene dichiarata una istanza della classe tramite la keyword new. Un tentativo di utilizzo in questo esempio:

<a id="myButton" href="#">passami sopra con il mouse per testare l'esempio</a>
<script>
var MyClass = Class.create({
	initialize : function(button, message) {
		this.button 	= $(button);
		this.message	= message;
		this.button.observe('mouseover', this.getMessage.bindAsEventListener(this));
	},
	getMessage : function(e) {
		this.newMessage = this.message + ' ( attivata dall'evento ' + e.type + ' sul link con id ' + Event.element(e).id + ' )';
		this.showMessage();
	},
	showMessage : function(){
		this.button.up().insert(this.newMessage);
	}
});
new MyClass('myButton', 'prova inserimento Tooltip');
</script>

Da osservare attentamente l'uso della keyword this, che permette rapidi riferimenti tra elementi e metodi della classe: in particolare, Prototype estende l'oggetto function con due metodi, bind() e bindAsEventListener(), che consentono alla funzione a cui sono applicati di poter passare  un argomento per un'esecuzione ulteriore, senza perdere il riferimento che lo associa a un determinato oggetto. In particolare bindAsEventListener() viene utilizzato per passare al gestore l'oggetto event come primo argomento, e in modo cross-browser per assicurare la compatibilità anche con i browser più datati...

Dalla teoria alla pratica

Vediamo subito in un altro esempio un semplice script per i tooltip, già funzionante, con caratteristiche essenziali (come dire, il minimo indispensabile). Ecco il codice necessario da includere nell'head della nostra pagina:

<script type="text/javascript" src="js/prototype.js"></script>
<script type="text/javascript" src="protip-simple.js"></script>
<script type="text/javascript">
var startDemo = function() {
  $$("a.demo").each( function(node) {
    new Protip(node);
  });
}
document.observe('dom:loaded', startDemo);
</script>
<link href="css/protip.css" rel="stylesheet" type="text/css" />

Notare il costrutto new Protip() con cui viene creata una istanza della classe al caricamento della pagina (non appena è disponibile l'albero del DOM). Il file CSS contiene le regole necessarie alla personalizzazione dell'aspetto del tooltip: è necessario assegnare un posizionamento assoluto e un valore di z-index sufficientemente elevato. Lo script percorre il documento alla ricerca di tutti i tag <a> con classe "demo" e per ognuno invoca una nuova istanza della classe Protip. Come abbiamo visto nell'esempio precedente, questa classe è costituita da varie funzioni figlie ognuna adibita ad un diverso compito:

  • initialize(): è il costruttore che accetta come argomento l'elemento su cui vogliamo mostrare il tooltip. Di fatto attiva il nostro script invocando le funzioni addObservers() e setupProtip().
  • addObservers(): semplicemente questa funzione cattura gli eventi del mouse dell'utente sull'elemento. Gli eventi che ci interessano, sono tre: mouseover, mouseout e mousemove; al verificarsi di ognuno è associata una funzione nel nostro script. Fare attenzione al binding delle funzioni e all'uso del metodo di Prototype event.observe.
  • setupProtip(): è la funzione che genera il codice HTML del tooltip e lo inietta attraverso il DOM. In pratica, per ogni tag <a> con classe "demo" inserisce dinamicamente e temporaneamente nasconde un <div> (che costituisce il tooltip). Il contenuto del tooltip è preso direttamente dall'attributo title del tag <a>. Anche in questa funzione vengono utilizzati dei metodi proprietari di Prototype (element.insert(), element.update() e lo stesso costrutto new element()).
  • showProtip(): viene mostrata il tooltip in seguito all'evento mouseover dell'utente sull'elemento (element.show()).
  • hideProtip(): di conseguenza nasconde il tooltip in seguito a un evente mouseout dal relativo elemento (element.hide()).
  • moveProtip(): questa funzione intercetta il movimento del mouse sopra l'elemento e regola il posizionamento del tooltip. Le coordinate del mouse vengono ottenute grazie ai metodi di Prototype Event.pointerX() ed
    Event.pointerY() e a al passaggio dell'oggetto event catturato dalla funzione addObservers().

Ecco il codice codice della classe Protip contenuto in protip-simple.js:

var Protip = Class.create({

  initialize: function(element){
    this.element 		= $(element);
    this.addObservers();
    this.setupProtip(); 		 	
  },

  setupProtip: function() { 		 		
    this.content 	    = this.element.readAttribute('title'); 		
    this.element.title = '';		 		
    this._protip	    = new Element('div', {'class':'protip'}).update(this.content); 		
    $$('body')[0].insert(this._protip.hide());	 	
  },
   	 	
  addObservers: function() { 		 		
    Event.observe(this.element, "mouseover", this.showProtip.bind(this));    		
    Event.observe(this.element, "mouseout", this.hideProtip.bind(this));
    Event.observe(this.element, "mousemove", this.moveProtip.bindAsEventListener(this));
  },
  
  showProtip: function() {
    this._protip.show();
  },
 	 	
  hideProtip: function() {
    this._protip.hide();
  },

  moveProtip: function(event){
    this.mouseX = Event.pointerX(event); 		
    this.mouseY = Event.pointerY(event);	 		 		
    this._protip.setStyle({ top:this.mouseY + 20 + "px", left:this.mouseX + 15 + "px" }); 	 	
  } 

});

Un codice più versatile con le opzioni

Lo script sopra esaminato è un esempio di ordine e modularità ma ancora non rende merito delle effettive possibilità dell'uso delle classi Javascript di Prototype. Un code-pattern tipico nella creazione di plugin con Prototype prevede il passaggio nella funzione costruttore initialize() di un secondo argomento (options) che può contenere un'hash di parametri opzionali (coppie chiave/valore). Se le coppie sono presenti, vengono utilizzati i valori specificati nella dichiarazione della nuova istanza della classe, altrimenti vengono presi i valori di default. In questo modo è possibile modificare i parametri dello script senza dover intervenire in più parti: con poche righe di codice è possibile creare diverse istanze personalizzate della classe nello stesso documento. Per attuare queste migliorie nello script precedente è quindi necessario modificare la funzione initialize() e inserire la funzione setOptions(), che può essere considerata come il pannello di controllo della classe, permettendo di configurare le tooltip in base alle diverse preferenze.

initialize: function(elemetn,options){
  this.element 	= $(element);
  this.setOptions(options);

  this.addObservers();
  this.setupProtip(); 		 	
},
setOptions: function(options) {
  this.options = { 			
    maxWidth: '',
    offsetX: 15,
    offsetY: 20,
    opacity: .75,
    appearDuration: 0.5,
    hideDuration: 1.0
  };
  Object.extend(this.options, options || {}); 		 	
}

Le varie opzioni ora disponibili consentono di integrare nello script delle funzionalità aggiuntive. Nel caso di questo secondo esempio è stato incluso anche Scriptaculous (per la precisione sono sufficienti scriptaculous.js ed effect.js) per applicare un effetto fade all'apparizione della tooltip (Effect.Appear ed Effect.Fade). I parametri opzionali opacity, appearDuration e hideDuration servono a personalizzare questo effetto. Vediamo quindi come sono cambiate le funzioni showProtip() e hideProtip():

showProtip: function() {	
  this._protip.currentEffect && this._protip.currentEffect.cancel();
  this._protip.currentEffect = new Effect.Appear(this._protip, { duration: this.options.appearDuration, to: this.options.opacity });
},
hideProtip: function() {
  this._protip.currentEffect && this._protip.currentEffect.cancel();
  this._protip.currentEffect = new Effect.Fade(this._protip, { duration: this.options.hideDuration });
}

Tramite il parametro opzionale maxWidth possiamo limitare la larghezza del tooltip, mentre con offsetX/offsetY regoliamo la distanza del tooltip dal puntatore del mouse. Le ultime modifiche al codice riguardano quindi le funzioni setupProtip() e moveProtip() con cui si assicura un corretto posizionamento della tooltip anche rispetto alle dimensioni orizzontali della finestra del browser. Per ottenere la larghezza del tooltip viene utilizzato il metodo di Prototype element.getWidth(), che ci restituisce un valore espresso in px.

setupProtip: function() { 		 		
  this.content 	  = this.element.readAttribute('title'); 		
  this.element.title = '';		 		
  this._protip	      = new Element('div', {'class':'protip'}).update(this.content); 		
  $$('body')[0].insert(this._protip.hide());
  if (this.options.maxWidth!='' && this._protip.getWidth()>this.options.maxWidth) {
this.protipWidth = this.options.maxWidth;
} else {
this.protipWidth = this._protip.getWidth();
} },
moveProtip: function(event){ this.mouseX = Event.pointerX(event); this.mouseY = Event.pointerY(event); if((this.protipWidth+this.mouseX)>=(Element.getWidth(document.body)-this.options.offsetX)) {
this.mouseX = (this.mouseX - this.protipWidth) - 2*this.options.offsetX;
}
this._protip.setStyle({ top:this.mouseY + this.options.offsetY + "px", left:this.mouseX + this.options.offsetX + "px" }); }

Ecco l'esempio completo in opera: naturalmente è possibile applicare la tooltip non solo su dei link, ma anche su div, input, e su qualunque altro tag provvisto di un attributo title. La compatibilità cross-browser degli script presentati è buona: Firefox, Safari, Chrome e le varie versioni di IE (6, 7 e 8). Proprio per correggere alcuni problemi del framework con l'ultima versione del browser Microsoft, consiglio di utilizzare da subito la bleeding-edge version di Prototype (1.6.1 RC2). In finale il link per il download di tutti gli esempi.

Riguardo alla parte teorica trattata nell'articolo, non è stata affrontata l'ereditarietà delle classi in Prototype, in particolare la possibilità di creare delle sottoclassi a partire da una classe principale e le funzionalità di Object.extend e Class#addMethods, con cui possiamo creare metodi "on the fly" dove necessario: sono strumenti molto utili e potenti per lo sviluppatore. Poco male comunque, mi riprometto di approfondire presto questi apetti in un nuovo articolo: stay tuned!

Ti consigliamo anche