martes, 8 de febrero de 2011

Cajas de texto en modo solo-lectura

Hace unos días un amigo me pidió hacer que una caja de texto sea de solo-lectura usando javascript sin librerías externas (como jQuery, MooTools, etc).
Mi idea inicial fue asignar el valor "true" a la propiedad "readOnly":
document.getElementById( 'TEXTAREA_ID' ).readOnly = true;
o el valor "readonly" al atributo "readOnly"
document.getElementById( 'TEXTAREA_ID' ).setAtribute( 'readOnly', 'readonly' );
... pero ¿Qué clase de diversión tendría?


Decidí jugar un poco con los eventos de la caja de texto.
Ahora la idea es evitar que al oprimir una tecla se realice la acción por defecto que es agregar la letra a la caja de texto, pensé que la solución podría ser anular de alguna forma el evento keypress pero luego se me ocurrió que ese evento se dispara tras keydown y keyup, así que, si anulo el evento keydown y evito que el evento se propague eso resolvería el problema.
var textarea = document.getElementById( ID );
if( textarea ) {
    textarea.onkeypress = textarea.onkeydown = function( evt ) { 
        if ( !evt )
            evt = window.event;
        evt.cancelBubble = true;
        if ( evt.preventDefault )
            evt.preventDefault( );
        if ( evt.stopPropagation )
            evt.stopPropagation( );
        return false;
    }
}
Esta opción parece funcionar correctamente en IE, Safari, Chrome y FireFox, pero en Opera no tiene ningún resultado, parece ser que en Opera el evento keypress se dispara antes que keydown, así que, ese evento debe ser cancelado de igual forma, además, lo incluí en una función para usarlo fácilmente.
function makeReadOnly( ID ) {
    var textarea = document.getElementById( ID );
    if( textarea ) {
        textarea.onkeypress = textarea.onkeydown = function( evt ) { 
            if ( !evt )
                evt = window.event;
            evt.cancelBubble = true;
            if ( evt.preventDefault )
                evt.preventDefault( );
            if ( evt.stopPropagation )
                evt.stopPropagation( );
            return false;
        }
    }
}
Ahora se puede usar un llamado a la función justo después de crear el elemento HTML.
<textarea name="TEXT_AREA_NAME" id="TEXT_AREA_ID" cols="2" rows="2" style="width:100%; height:90px;">
    contenido del text area con eventos en Javascript
</textarea>
<script language="javascript" type="text/javascript">
    // sobreescritura de eventos
    makeReadOnly( 'TEXT_AREA_ID' );
</script>
Se me ocurren algunos comentarios:
  • El sistema implementado bloquea cualquier tecla, incluyendo TAB, Ctrl+C, etc.
  • A algunas personas no les gusta mezclar HTML y Javascript. 
  • Poner los llamados a final de la página puede hacer que no se ejecuten si esta no se carga completamente.
  • Poner los llamados antes de la escritura del elemento a utilizar produce errores.
Para determinar la tecla que fue oprimida, y saber si bloquearla o no, no hay una método definido en la especificación de eventos, de momento sé de 3 propiedades del evento que pueden dar información de la tecla oprimida: 
  1. keyCode: Contiene el código de teclado de la tecla oprimida.
  2. charCode: Contiene el código ASCII del caracter resultante según la(s) tecla(s) oprimida(s).
  3. which: Al parecer funciona igual que keyCode en algunos navegadores basados en Netscape.
Pero como siempre los bichos raros de IE y Opera se comportan diferente:
  1. No tienen charCode: Para obtener el código ASCII se usa la propiedad keyCode pero solo en el evento onkeypress.
  2. keyCode: Contiene el el código de teclado en los eventos onkeydown y onkeyup.
Se puede obtener un poco más de información en Detecting keystrokes.

Mi solución final contiene este código javascript:
// FULL JS
    function makeReadOnly( ID ) {
        var textarea = document.getElementById( ID );
        if( textarea )
            textarea.onkeypress = textarea.onkeydown = cancelWrite;
    }
    
    // HTML event
    function cancelWrite( evt ) {
        if ( !evt )
            evt = window.event;
        
        code = null;
        
        if ( evt.keyCode)
            code = evt.keyCode;
        else if ( evt.which )
            code = evt.which;

        if( code == 9 ) // La tecla TAB no es cancelada
            return;
            
        evt.cancelBubble = true;
        if ( evt.preventDefault )
            evt.preventDefault( );
        if ( evt.stopPropagation )
            evt.stopPropagation( );
        return false;
    }
Y se puede usar registrando los eventos directamente en el tag:
<textarea cols="2" rows="2" style="width:100%; height:90px;" onkeypress="return cancelWrite( event )" onkeydown="return cancelWrite( event )">
    contenido del text area con atributos en el HTML
</textarea>
o utilizando javascript:
<textarea name="TEXT_AREA_NAME" id="TEXT_AREA_ID" cols="2" rows="2" style="width:100%; height:90px;">
    contenido del text area con eventos en Javascript
</textarea>
<script language="javascript" type="text/javascript">
    // sobreescritura de eventos
    makeReadOnly( 'TEXT_AREA_ID' );
</script>
Para hacer editable la caja de texto sería necesario eliminar las funciones asignadas a los eventos keypress y keydown, la siguiente función puede ser llamada usando javascript en cualquier momento:
    function cancelReadOnly( ID ) {
        var textarea = document.getElementById( ID );
        if( textarea )
            textarea.onkeypress = textarea.onkeydown = null;
    }

2 comentarios:

  1. y si quiero habilitar de nuevo el textarea

    ResponderEliminar
  2. Gracias por el comentario, agregué un párrafo al final que explica cómo se podría habilitar la escritura.

    ResponderEliminar