/dev/random

contentEditable e i suoi demoni

Nell'ultimo anno, prima per lavoro e in questo periodo per esperimenti personali, mi sto addentrando nella programmazione Javascript, e l'ho fatto scalando la parete più ripida della montagna: contentEditable.

Ho deciso quindi di riassumere qui le varie idosincrasie che ho trovato, sperando che mi servano in futuro per evitare di sbattere di nuovo la testa a sangue contro il muro. Per tutti gli esperimenti ho usato Firefox (tra la 8 e la 10, più o meno)

Preparare l'ambiente

Ci sono due modi per usare contentEditable: definire tutto il documento contentEditable (di solito si usa con un iframe), o definire solo un elemento (la strada che ho seguito io). Nel primo caso basta, da Javascript:

document.contentEditable = true;

Nel secondo basta creare un elemento HTML con la proprietà contenteditable:

<article contenteditable="true"></article>

Fatto questo, io personalmente imposto tre proprietà:

document.execCommand("useCSS", false, true);
document.execCommand("styleWithCSS", false, false);
document.execCommand("enableObjectResizing", false, false);

La prima serve a usare <B> e <I> per fare grassetto e corsivo in Firefox, mentre Explorer (da quel che leggo) usa <STRONG> e <EM>. Se non la si specifica, Firefox riesce a fare peggio che non seguire la semantica di HTML: usa <span style=”font-weight: bold;”></span><span style=”font-style: italic;”></span>

La seconda fa la stessa cosa, ma una delle due è deprecata. Per compatibilità io le uso entrambi.

La terza serve ad evitare che, se ci sono oggetti come <img> o simili, l'utente possa ridimensionarli trascinandoli. Si può anche non specificarla, ma io preferisco controllare cosa fa l'utente tramite plugin jQuery che controllano gli spostamenti e i ridimensionamenti.

La cosa importante da notare è che TUTTI i comandi dati tramite execCommand() vanno eseguiti DOPO che c'è un elemento con contentEditable presente e visibile nel DOM. Se state creando l'elemento da Javascript, e fate (con jQuery)

var $el = $('<article />').attr('contenteditable', true');
document.execCommand("useCSS", false, true);
$('body').append($el);

Riceverete una bella eccezione nella console di Firefox. Basta spostare l'execCommand dopo l'append.

Non basta un contentEditable vuoto

Naturalmente non basta un <article contenteditable=”true”></article>, perché, come tutti gli elementi senza contenuto, le sue dimensioni sono 0x0, quindi è invisibile e soprattutto “incliccabile”, e perciò non c'è modo di scriverci dentro. Basta aggiungerci un figlio. Alcuni ci mettono un <br />, ma io preferisco un <p>, che però ha gli stessi problemi, quindi la sintassi completa è:

<article contenteditable="true">
  <p>
    <br />
  </p>
</article>

Perché questa tiritera? Perché Firefox, se state scrivendo dentro un <p>, quando premete Invio chiuderà automaticamente il <p> e ne aprirà un altro identico. In qualsiasi punto siate. Se siete fuori da un <p>, inserirà semplicemente un <br />, con buona pace della semantica (e per la gioia dei cialtroni che fanno spaziature verticali premendo Invio n volte).

Ma ancora non basta! Ma cosa vuole??

Se avete predisposto tutto il suddetto, e avere fatto la vostra toolbar (magari in un prossimo articolo vedremo come) con la possibilità di inserire grassetti, corsivi, liste, ecc., vi scontrerete con un problema assurdo: Se tentate di rendere “lista” il primo elemento del contentEditable, Firefox solleva un'eccezione. Bold e Italic funzionano perfettamente, solo le liste (e gli allineamenti, e forse qualcos'altro che non ho ancora scoperto) danno errore. È un bug di Firefox, presente fin dalla 2.0, pare. Io l'ho aggirato così:

<article contenteditable="true">
  <p></p>
  <p>
    <br />
  </p>
</article>

Con un <p> vuoto (quindi dimensioni 0x0, quindi incliccabile) prima del contenuto vero. Brutto, ma funziona. Poi il codice si ripulisce lato server, perché ha TANTO bisogno di essere ripulito.