Wextensible

Acceso a los controles del formulario

Un formulario emergente es en definitiva un elemento HTML del tipo <form>. Así podemos acceder a los controles de un formulario HTML como si fuera una matriz de controles. Nuestra clase formEmerge dispone del método siguiente para facilitar la tarea de obtener externamente referencias a los controles de formulario internos del emergente:

this.form([idElementoSinPrefijo
[,omitirPrefijo]])

Tenemos una instancia emergeAcceso creada en el window.onload de este documento para realizar algunas pruebas con este método. El formulario se puede abrir con este . Se trata de un emergente sencillo con un cuadro de texto en su interior, un botón acetar y con pantalla. Esta es su construcción:

var emergeAcceso = null;
//Carga de la página, donde creamos formularios emergentes
window.onload = function() {
    emergeAcceso = new formEmerge("emergeAcceso", "", false, 1);
    emergeAcceso.nuevoInterior("<input type='text' id='cuadro'
    value='MI TEXTO' />");
}

Si usamos el método sin argumentos con emergeAcceso.form() obtendremos una referencia al propio formulario, como podemos comprobar con este botón que hace onclick = "alert('elemento=' + emergeAcceso.form()):

Ejemplo:

debe darnos "[object HTMLFormElement]" significando que se trata de un elemento HTML <form>.

Teniendo ya la referencia al formulario podemos acceder de varias formas al elemento cuadro de texto <input id="cuadro"> que hemos albergado en el interior del formulario:

Espero que este ejemplo con los comentarios será suficiente para entenderlo. En los botones hemos puesto el código que se aplica al evento onclick, aunque en lugar de this sería emergeAcceso que es el nombre del emergente para este ejemplo:

Ejemplo:

  • Con debe darnos "[object HTMLInputElement]" significando que se trata de un elemento HTML <input>. Su atributo value es que contiene la cadena "MI TEXTO".
  • También se puede acceder con un índice de elemento en el orden de la matriz de controles del formulario .
  • Pero el método también nos permite aplicar directamente el identificador y pasar true para que omita los prefijos, es decir, trate el identificador tal como está .
  • Además podemos llamar a otros elementos que se crean dentro del formulario, por ejemplo, a la propiedad value del botón aceptar: que nos dará "aceptar". En este caso el identificador realmente es IDPREemergeAccesoBotonAceptar, pero pasamos "BotonAceptar" y el resto se le agregará en el método. No es necesario en caso de false pasar ese segundo argumento, como hacemos para ver el tagName del elemento <div> que hace la barra superior: . El verdadero identificador de este elemento es IDPREemergeAccesoBarra.
  • Todos los elementos de un formulario están contenidos en el elemento <form>, pero hay dos con los que interacciona y que están por fuera:
    • En el formulario tenemos la propiedad this.idObjetoRespuesta que almacena el identificador del objeto respuesta. Éste puede ser referenciado con . El objeto respuesta es inicialmente un <input type="hidden">, creándose uno para cada formulario y ubicándolos en el <body> del documento, aunque pueden volver a ser referenciados con otros elementos. Su identificador se construye también con el nombre del formulario: IDPREemergeAccesoobjetoRespuesta. Cuando se vuelve a referenciar con un elemento externo, sólo se almacena el id que se le pase, sin prefijos, por lo que la forma expuesta de acceder a ellos sigue siendo válida.
    • La pantalla puede ser referenciada con . Se crea sólo una pantalla con la primera instancia donde se pase el argumento conPantalla del constructor igual a true. Esa misma pantalla va a servir para todos los emergenes y se ubica también en el <body> del documento. Su identificador es IDPREpantalla.
      Actualización Noviembre 2012: La variable PID antes era global. Ahora he actualizado este sitio para almacenar el constructor del formulario en un espacio de nombres. Ahora se accede a PID con emergeAcceso.getPID(), donde emergeAcceso es la instancia que se ha construido del formulario emergente para estos ejemplos.

Después de todo estas formas se remiten finalmente a un document.getElementById() pero que facilita la labor de referenciar elementos internos.

Situando el foco en un formulario emergente

El método abrir() tiene la siguiente llamada:

this.abrir([interior
[,titulo
[,izquierda
[,arriba
[,ancho
[,alto
[,sobresale
[,foco]]]]]]]])

El argumento foco sirve para poner el foco en un control del formulario cuando lo abramos. Puede ser un número entero correspondiente al número de orden dentro de la matriz de controles del formulario, una referencia a un elemento HTML o un string del identificador id del elemento. Hacemos un ejemplo con la instancia emergeFoco que se construye en el window.onload de este documento así:

...
var emergeFoco = null;
...
//Carga de la página, donde creamos formularios emergentes
window.onload = function() {
...
    emergeFoco = new formEmerge("emergeFoco", "", false, 3);
    var interior = "<label>Texto <input type='text' id='texto' "
    "value='TEXTO 1' /></label><br />" +
    "<label>Verificación <input type='checkbox' id='verificacion' "
    "checked='checked' /></label><br />" +
    "<label>Área de texto <textarea id='area-texto' rows='3' cols='20'>" +
    "Un área de texto</textarea></label><br />" +
    "Vínculo <a href='#situar-foco' id='vinculo'>Vínculo encabezado</a>";
    emergeFoco.nuevoInterior(interior);
...
}

Se trata de un emergente con unos controles de formulario en su interior y también con un elemento vínculo <a>, elementos que pueden recibir el foco. Con los siguientes botones aplicamos el método abrir en su evento onclick:

emergeFoco.abrir('', '', this.offsetLeft,
this.offsetTop + this.offsetHeight, '', '', '', foco]

de tal forma que señalamos en el título de cada botón como pasamos ese argumento opcional para situar el foco.

Ejemplo:

  • Podemos abrir sin especificar foco de tal forma que se sitúa en el botón cancelar de forma predeterminada.
  • O bien donde pasamos el índice 0 para situarlo en el cuadro de texto que está en primer lugar.
  • O bien donde pasamos el identificador de la casilla de verificación para poner ahí el foco.
  • O bien pasando una referencia al <textarea> y el foco se sitúa ahí.
  • O bien situando el foco en un elemento vínculo <a>, que también es capaz de recibirlo, aunque no está en la matriz de controles del formulario. En este caso obtenemos la referencia por medio del método form() del emergente pasando el segundo argumento omitirPrefijo igual a true, que viene a ser lo mismo que document.getElementById('vinculo').

La matriz de controles del formulario

Nuestro objeto de la clase formEmerge se construye en un elemento HTML <form>. Cuando se obtiene una referencia a este elemento con, por ejemplo, document.getElementById() o cualquier otro método, también tenemos en esa referencia una matriz de controles de ese formulario. Así podemos iterar por esa matriz para obtener una referencia a los controles que son los elementos <input>, <button>, <textarea> etc.

Los ejemplos anteriores usan esa matriz de controles y se han probado con Internet Explorer 8, Firefox 3.6 y Opera 10.6 y Safari 4.0, aunque éste tiene una forma diferente de acceder a los controles de formulario pues no incluye los elementos <fieldset> dentro de la matriz de controles. Para verificarlo hacemos un pequeño script matrizForm(formulario) que puede ver en la cabecera de este documento y que hace lo siguiente:

function matrizForm(formulario){
    var cadena = "";
    for (var i=0; i<formulario.length; i++){
        cadena += i + ": <" + formulario[i].tagName.toLowerCase() +
        " id=\"" + formulario[i].id + "\"><br />";
    }
    return cadena;
}
    

Luego ponemos a continuación un formulario con un control de cada clase e identificados con id igual al nombre del elemento. Con el botón que está a continuación, en su onclick llamamos a la función anterior para hacer una lista de controles.

Ejemplo:

<fieldset> <span>

La matriz de controles se rellena comenzando por cero y con el primer control según aparecen en el orden visual, es decir, según se cargan en el orden de pantalla. Mientras que para los 3 primeros navegadores se obtiene esto:

0: <fieldset id="fieldset">
1: <input id="input-text">
2: <button id="button">
3: <textarea id="textarea">
4: <select id="select">
    

para Safari 4.0 se obtiene esto otro ignorando el fieldset:

0: <input id="input-text">
1: <button id="button">
2: <textarea id="textarea">
3: <select id="select">
    

Todos ignoran otros elementos que hay dentro del formulario como un <label> y un <legend> que no son controles de formulario sino etiquetas. Además el <span> no tiene nada que ver con los formularios aunque pueda incluirse en su interior. Pienso que en todo caso se debería ignorar también el <fieldset> pues no es un control sino un contenedor de controles.

El estilo CSS3 opacity para transparencias

La clase formEmerge dispone de la propiedad this.pantalla para dotar de una pantalla de transparencia que se sitúa en una capa entre la página y el emergente. Con eso conseguimos el efecto de los mensajes emergentes cuando se obliga al usuario sólo a interactuar con ellos. Se crea en la clase mediante el paso del argumento conPantalla en el constructor:

this.pantalla = false;
if (conPantalla != null) {
    this.pantalla = conPantalla;
    var pant = document.getElementById(PID + "pantalla");
    if (pant == null) {
        document.body.innerHTML += "<div id='" + PID + "pantalla'></div>";

    }
}

Creamos entonces dinámicamente un elemento div que ubicamos en el cuerpo del documento. Esta pantalla tiene el siguiente estilo:

div#IDPREpantalla {
    opacity: 0.6;
    -ms-filter: "alpha(opacity=60)";
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: white;
    display: none;
    z-index: 5;
}
La propiedad filter es exclusiva de Internet Explorer, pero podemos anteponerle el prefijo -ms- para indicar a otros navegadores ese hecho. Así en esa hoja de estilo del emergente hemos puesto -ms-filter: "alpha(opacity=60)". En este caso el valor de la propiedad hay que ponerlo entre comillas. Si lo pusieramos sin el prefijo sería filter: alpha(opacity=60). Opera no reconoce estos prefijos y registra un error de propiedad no reconocida, aunque la transparencia funciona porque usa la otro propiedad opacity.
ACTUALIZACIÓN SEPTIEMBRE 2010: La propiedad opacity no es estándar en CSS 2.1, estando prevista que se incluya en CSS3. Opera 10.6, Firefox 3.6 y Safari 4.0 la reconocen, pero como Internet Explorer sigue con su filter, he optado por no incluir propiedades no estándar en los archivos css. La mejor forma es inscrustarlas dinámicamente con JavaScript, para lo que he preparado la función estiloNoCss().
Actualizacion Octubre 2013: He eliminado definitivamente la función estiloNoCss() pues se basaba en la diferenciación de navegador cuando lo que hay que hacer es buscar si se soporta la característica. Como tengo el gestor de vendor prefixes VpForCss puedo detectar si el navegador soporta opacity y así actuar en consecuencia. El código ahora para construir dinámicamente el elemento pantalla es el siguiente:
var sty = 'style="';
var prefOpacity = wxG.vpForCss["opacity"];
if (prefOpacity) {
    sty += wxG.descambiaGuiones(prefOpacity) + ": 0.6; ";   
} else {
    //para ie8
    sty += "filter: alpha(opacity=60); ";                   
}
var cadPant = '<div id="' + PID  + 'pantalla" '+ sty  + '">&nbsp;</div>';               
document.body.innerHTML += cadPant;        
        
La consulta wxG.vpForCss["opacity"] devolverá null si el navegador no lo soporta, en cuyo caso le aplicamos filter. Si lo soporta devolverá opacity pues todos los navegadores la usan sin prefijo.

Esa pantalla ha de poseer la cualidad de cierta transparencia para que el usuario sepa que sigue estando en la misma página. Pero esta propiedad de estilo aún no es estándar aunque se establece en el borrador de trabajo del W3C como una nueva propiedad {opacity}. La opacidad de un elemento es lo contrario a la transparencia, pero nos resulta más cómodo hablar de elementos transparentes o que dejan transparentar el fondo que al revés. Los valores que admite son reales desde 0.0 a 1.0 para especificar el grado de opacidad del color. Internet Explorer no la soporta y usa filter con el valor alpha(opacity=N), donde N es un número 0 a 100 que representa el porcentaje de opacidad. Para entender porqué hemos aplicado ese estilo a nuestra pantalla, veámos un caso más simple a continuación:

Ejemplo:


Este texto está bajo un div con opacity = 0.5
 

Se trata de ubicar los siguientes elementos HTML

<input type="button" value="- opacidad" onclick = "cambiaOpacity(-1)" />
<input type="button" value="+ opacidad" onclick = "cambiaOpacity(1)" />
<br />
<div style="position: relative; color: black; font-size: 2em">
    <b>Este texto está bajo un div con = </b> <span id="valor-opacity">0.5</span>
    <div id="opaco" style="
    opacity: 0.5; 
    filter: alpha(opacity=50); 
    border: blue solid 0.2em;
    position: absolute; top: 0; left: 0;
    width: 100%; height: 100%; 
    background-color: aqua;">
        &nbsp;
    </div>
</div>
    

Tenemos dos elementos de bloque <div>. Uno que vamos a dejar debajo donde ponemos el texto que luego se transparentará con el segundo bloque <div>. Pero ha de quedar el segundo encima del primero, lo que conseguimos poniendo posición relativa al primero con lo que si especificamos posiciones con top, left etc. en sus hijos, se entenderán relativas a ese bloque y no a ningún otro. Entonces el bloque hijo lo posicionamos de forma absoluta para que se sitúe en la posición (0,0) (con top y left), pero en relación a su padre posicionado relativamente. Aparte de los estilos para poder ver el efecto, el más importante en relación con la opacidad es el del bloque hijo, el que hará el efecto de "pantalla de transparencia":

  1. opacity: 0.5; filter: alpha(opacity=50); para dar opacidad a la pantalla. La primera es la que usan Firefox 3.6, Opera 10.6 y Safari 4.0 además de la propuesta por W3C como estándar. La segunda es para Internet Explorer pues al menos hasta la versión 8 no permite la otra. En el primer caso se indica 0.5 de opacidad mientras que en el segundo es un 50% que viene a ser lo mismo.
  2. position: absolute;, el posicionamiento absoluto del que hablamos.
  3. top: 0; left: 0; , ubicar la pantalla en el punto (0,0) del padre.
  4. width: 100%; height: 100%; , extender en ancho y alto heredado del el padre.
  5. background-color: aqua;, el color de fondo que se transparentará.

Para el efecto con los botones hemos dispuesto el siguiente script tras el HTML anterior:

<script>
    var opacIni = 0.5;
    function cambiaOpacity(masMenos){
        var temp = opacIni + (0.1 * masMenos);
        temp = parseFloat(temp.toPrecision(1));
        if ((temp >= 0)&&(temp <= 1)){
            opacIni = temp;
            var elemento = document.getElementById("opaco");
            if (elemento.style.filter){
                elemento.style.filter = "alpha(opacity=" + (opacIni*100) + ")";    
            } else {
                elemento.style.opacity = opacIni;
            }
            document.getElementById("valor-opacity").innerHTML = opacIni;
        }
    }
</script>
    

Pero nuestra pantalla del emergente la hemos posicionado a fixed que significa que se fija con respecto a la ventana del navegador, pues lo que deseamos es "tapar" toda la ventana. El resto del estilo es lo mismo que hemos visto en este ejemplo, aunque usamos solapamiento con z-index para situarlo dinámicamente en la capa correspondiente entre la página y el emergente. Así la página (el fondo) está en la capa 0, la pantalla en la 5 y los formularios emergentes (elementos <form>) en la capa 10. Así cuando pongamos la pantalla entre el fondo de la página y el formulario, no podremos acceder al fondo.

Uso de unselectable o -moz-user-select

El uso de los métodos para mover el formulario generaba un problema en relación con la selección de texto en una página web. Si se desplaza el puntero del ratón con el botón izquierdo presionado, los navegadores seleccionan texto. Esto interfería con el movimiento de nuestro emergente y tuve que buscar la forma de impedir que se seleccione el texto del formulario. El movimiento del formulario se produce tras pulsar el ratón encima del título, un <span> que está en la barra de la cabeza del formulario.

En el constructor de cada emergente insertamos con innerHTML los siguientes elementos que forman la estructura de un formulario emergente (hemos quitado las líneas no significativas):

...
thtml += "<form id='" + PID + this.nombre + "' unselectable='on' ";
...
thtml += "<div id='" + PID + this.nombre + "Barra' unselectable='on' " +
...
thtml += "<span class = '" + PCL + "form-emerge-boton-cerrar' unselectable='on' " +
...
thtml += "<span id='" + PID + this.nombre + "Titulo' unselectable='on' " +
...
thtml += "<div id='" + PID + this.nombre + "Interior' " +
...
thtml += "<fieldset id='" + PID + this.nombre + "Botones'  unselectable='on' " +
...

Esa estructura queda finalmente así:

<form>
    <div> para la barra cabeza
        <span></span> para el botón cerrar, flotado derecha
        <span></span> para el título
    </div>
    <div>
        este bloque para el interior
    </div>
    <fieldset> para el pie
        bloque para botones aceptar, cancelar y aplicar
    </fieldset>
</form>
    

Se observa que hemos aplicado a todos esos elementos menos al bloque interior el atributo unselectable con valor on, con lo que impedimos la selección de esos elementos en pantalla. Este atributo no se hereda por lo que hay que declararlo explícitamente para cada elemento. Se trata de un atributo de Internet Explorer 8 que aún no forma parte del estándar de W3C, aunque también funciona en Opera 10.6. De hecho Firefox 3.6 tiene otra forma de hacerlo a través de la propiedad de estilo -moz-user-select con el valor -moz-none. En la hoja de estilo form-emerge.css la aplicamos pero sólo para el <div> de la cabeza:

form.CLPREform-emerge div.CLPREform-emerge-cabeza  {
    background-color: rgb(49, 99, 98);
    color: orange;
    border: 0;
    padding: 0;
    font-weight: bold;
    cursor: default;
    -moz-user-select: -moz-none;
    cursor: default;
    }
    
ACTUALIZACIÓN SEPTIEMBRE 2010: La propiedad user-select no es estándar en CSS 2.1, estando prevista que se incluya en CSS3 user-select. Sólo Firefox la reconoce, mientras que en los otros navegadores usamos el atributo unselectable. Para poder validar el css he decidido incoporarlas dinámicamente con la función genérica estiloNoCss() y quitarlas del archivo "form-emerge.css".
Actualizacion Octubre 2013: He eliminado definitivamente la función estiloNoCss() pues se basaba en la diferenciación de navegador cuando lo que hay que hacer es buscar si se soporta la característica. Como tengo el gestor de vendor prefixes VpForCss puedo incoporar la propiedad user-select con el prefijo adecuado a cada navegador. Al elemento form generado dinámicamente le agrego dentro de la cadena de estilo lo siguiente:
... wxG.descambiaGuiones(wxG.vpForCss['user-select']) + ': none; ...
Así si es Firefox ahí aparecerá -moz-user-select: none o si es Chrome -webkit-user-select: none. En caso de que no se soporte agregará null: none que será ignorado por el navegador.

En este caso la propiedad de estilo si se hereda, de tal forma que el <span> con el título no será ahora seleccionable y así no inteferirá con el desplazamiento del ratón cuando movamos el formulario. Al anteponer en una propiedad de estilo -moz- así como en el valor, se consigue que los navegadores basados en Mozilla como Firefox la reconozcan y que otros navegadores como Explorer la ignoren. Sin embargo Opera la advierte como propiedad desconocida, aunque no la necesita pues usa el atributo unselectable como Explorer.

No he encontrado una forma de aplicarlo a Safari 4.0, que parece ignorar ambas propiedades, aunque no impide mover el formulario.