HTML contenteditable

Già da molto tempo esiste l’ attributo contenteditable per gli elementi HTML, e la compatibilità è abbastanza buona con tutti i browser.

Quando si edita un elemento, al refresh si perdono le modifiche: per memorizzarle è necessario gestire alcuni eventi quali, ad esempio, la pressione del tasto Invio o Canc.

Vedere l’ esempio di Remy Sharp:
http://jsbin.com/owavu3

document.addEventListener('keydown', function (event) {
  var esc = event.which == 27,
      nl = event.which == 13,
      el = event.target,
      input = el.nodeName != 'INPUT' && el.nodeName != 'TEXTAREA',
      data = {};

  if (input) {
    if (esc) {
      // restore state
      document.execCommand('undo');
      el.blur();
    } else if (nl) {
      // save
      data[el.getAttribute('data-name')] = el.innerHTML;

      // we could send an ajax request to update the field
      /*
      $.ajax({
        url: window.location.toString(),
        data: data,
        type: 'post'
      });
      */
      log(JSON.stringify(data));

      el.blur();
      event.preventDefault();
    }
  }
}, true);

function log(s) {
  document.getElementById('debug').innerHTML = 'value changed to: ' + s;
}
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>JS Bin</title>
<style>
[contenteditable]:hover:after {
  content: ' click to edit';
  font-style: italic;
  font-size: 12px;
  font-family: sans-serif;
  color: #ccc;
  .text-stroke(0);
}

[contenteditable]:hover, [contenteditable]:focus {
  background: #FFFFD3;
}

[contenteditable]:focus {
  padding: 5px;
}

[contenteditable]:focus:after {
  content: '';
}
</style>
</head>
<body>
  <p contenteditable data-name="title" id="hello">Hello World</p>
  <div id="debug"></div>
</body>
</html>

Java Swing, appunti

Il modo più semplice di gestire gli eventi di una finestra creata da Swing è quello di assegnare ad un componente un listener:

public class MyFrame extends JFrame {
	private JButton bottone = new JButton("Bottone");
	Ascoltatore listener = new Ascoltatore();

	public MyFrame() {
			...
			Container c = this.getContentPane();
			c.add(bottone);
			// Assegno l' ascoltatore
			bottone.addActionListener(listener);
	}
}

public class Ascoltatore implements ActionListener {
	public void actionPerformed(ActionEvent event) {
		// Accedo all' oggetto che ha scatenato l' evento
		JButton b = (JButton) event.getSource();
		JOptionPane.showMessageDialog(null, "E` stato premuto" + b.getText());
	}
}

ma il problema di questa tecnica è che non si può accedere ad un elemento diverso da quello che ha scatenato l’ evento: con la classe che implementa l’ interfaccia ActionListener posta “esternamente” alla classe del frame, è impossibile accedere agli altri oggetti del frame.

Una prima soluzione è quella di scrivere il listener internamente al frame, così da poter accedere ai suoi membri:

public class MyFrame extends JFrame {
	private JButton bottone = new JButton("Bottone");

	public MyFrame() {
			...
			Container c = this.getContentPane();
			c.add(bottone);
			// Assegno l' ascoltatore in modo anonimo
			bottone.addActionListener(new Ascoltatore());
	}

	class Ascoltatore implements ActionListener {
		public void actionPerformed(ActionEvent event) {
			// Accedo all' oggetto che ha scatenato l' evento
			// semplicemente accedendo al metodo "bottone" della classe
			JButton b = bottone;
			JOptionPane.showMessageDialog(null, "E` stato premuto" + b.getText());
		}
	}
}

Lo svantaggio di questa soluzione è che la classe del frame può diventare eccessivamente grande al crescere della complessita della GUI, e si avrà anche un codice poco leggibile con 2 classi eterogenee fuse tra loro.

La soluzione migliore è quella di definire un costruttore del listener che prende come parametro un riferimento alla finestra che contiene i vari componenti.

public class MyFrame extends JFrame {
	// Componenti della GUI come membri della classe
	JPanel centro = new JPanel();
	JPanel sud = new JPanel();
	JTextField txt = new JTextField(20);
	JButton button = new JButton("Premi");
	// Inizializzazione del listener
	Listen ascolt=new Listen(this);

	public MyFrame() {
		super("Esempio");
		centro.add(txt);
		...
		button.addActionListener(ascolt);
		txt.addActionListener(ascolt);
		...
	}
}

class Listen implements ActionListener {
	MyFrame frame;
	// Costruttore che riceve il riferimento al frame
	public Listen(MyFrame aFrame)
		{ frame = aFrame; }

	// Da qui accediamo ai metodi del frame !
	public void actionPerformed(ActionEvent e) {
	JTextField text = frame.txt;
	JOptionPane.showMessageDialog(null,text.getText());
	}
}

A questo punto bisogna dire che non si può pensare di creare un listener per ogni componente: si può invece raggruppare vari componenti ed assegnargli un unico listener condiviso, con lo stessa funzione actionPerformed condivisa tra essi.

Dunque, in actionPerformed bisogna distinguere quale oggetto ha generato l’ evento; si può fare in 2 modi:

  • con il metodo getSource() della classe ActionEvent. Questa tecnica è fattibile se si può confrontare il riferimento dato da getSource() con il riferimento memorizzato nella classe del frame: è, in generale, il caso della classe interna vista prima.
  • con la proprietà actionCommand dei componenti di swing. Con questa tecnica, in actionPerformed() non è più necessario prendere i riferimenti agli oggetti, ma basta leggere l’ actionCommand e chiamare la funzione appropriata.
    Questa tecnica è utile anche nel caso in cui ci siano diversi componenti che condividono la stessa azione: ad esempio un bottone e una voce di menu possono fare la stessa cosa, quindi basta scrivere una sola funzione e farla chiamare da actionPerformed tramite un actionCommand.
    Rimane il fatto che per accedere ai componenti del frame sia necessario passare al costruttore del listenere il riferimento al frame che contiene quei componenti come metodi della classe.

    public class MyFrame extends JFrame {
    	...
    	JMenuItem UpOpt = new JMenuItem("Up");
    	JMenuItem DownOpt = new JMenuItem("Down");
    	JMenuItem RandomOpt = new JMenuItem("Random");
    	Listener ascolt = new Listener();
    	public MyFrame() {
    		...
    		UpOpt.addActionListener(ascolt);
    		UpOpt.setActionCommand(ascolt.UPOPT);
    		DownOpt.addActionListener(ascolt);
    		DownOpt.setActionCommand(ascolt.DOWNOPT);
    		RandomOpt.addActionListener(ascolt);
    		RandomOpt.setActionCommand(ascolt.RANDOMOPT)
    		...
    	}
    }
    
    public class Listener implements ActionListener {
    	public final static String UPOPT = "up";
    	public final static String DOWNOPT = "down";
    	public final static String RANDOMOPT = "random";
    	// Eventualmente scrivere un costruttore
    	// per avere un riferimento al frame
    
    	public void actionPerformed(ActionEvent e) {
    		String com = e.getActionCommand();
    		if (com == UPOPT)
    			upOpt();
    		else if (src == DOWNOPT)
    			downOpt();
    		else if (src == RANDOMOPT)
    			randomOpt();
    	}
    
    private void upOpt()
    { ... }
    private void randomOpt()
    { ... }
    private void downOpt()
    { ... }
    }
    
    

Java Swing livello avanzato: http://home.cogeco.ca/~ve3ll/jatutorc.htm
MVC: http://www.macs.hw.ac.uk/guidebook/?name=Using%20The%20GUI&page=1

Lo scope chain e le closures

Parlando di scope di una variabile in javascript si nota che è come le scatole cinesi: dall’ interno si possono utilizzare le variabili via via più esterne. Questo è il concetto di scope chain.
Per qualunque esigenza, è possibile “isolare” l’ esecuzione di una funzione in modo da avere il controllo sullo scope che gli si vuole dare: è il concetto di closure.

Metodi di oggetti come callback

Si pensi allo scope di un metodo di un oggetto: la proprietà this, usata nei metodi dell’ oggetto, dovrebbe riferirsi all’ oggetto stesso; ma se il metodo viene “staccato” dall’ oggetto ed “innestato” su una funzione di callback di un evento, la proprietà this si riferirà all’ oggetto che lancerà quell’ evento.
Quindi con this abbiamo una alternativa: o disponiamo dell’ oggetto, o disponiamo dell’ elemento DOM legato all’ evento.
Per disporre di entrambi si ricorre ad uno scope mixing.

<script type="text/javascript">

var listener = function() {
  this.message = "Hai cliccato su di me";

  this.onclick = function() {
    var listener = this;      // OSSERVARE QUESTA ASSEGNAZIONE: grazie ad essa potremo riferirci ancora all' oggetto, usando un alias per "this"
    return function() {
      alert("MESSAGGIO: " + listener.message + "\nTAGNAME: " + this.tagName);
    }
  }
}

var clickListener = new listener();

window.onload = function() {
  document.getElementsByTagName("button")[0].onclick = clickListener.onclick();
  document.getElementsByTagName("a")[0].onclick = clickListener.onclick();
  document.getElementsByTagName("img")[0].onclick = clickListener.onclick();
}
</script>
<button>Bottone 1</button>
<a href="#">Link 1</a>
<img src="http://html.it/common/img/logo2.gif" alt="" />

Il metodo onclick della classe listener realizza una closure: assegnandolo alla funzione di callback onclick avremo che un metodo di una classe manterrà un “collegamento” (cioè un binding) tra lo scope della classe e lo scope dell’ elemento DOM che lancia l’ evento; ovvero una chiusura tra i due ambiti.
Questa è una spiegazione semplificata dell’ argomento delle chiusure nei linguaggi informatici, che riguarda anche le garbage collections.