Mejorar progresivamente un formulario a un formulario modal

Con algo tan importante como un formulario de contacto, desea que funcione correctamente para todos los visitantes, incluso con el desafío de JavaScript. ¿Cómo maneja esto si desea usar un formulario modal (emergente)? La respuesta es mejora progresiva; comenzar con la línea de base, la funcionalidad utilizable; A continuación, aumente la experiencia del usuario para aquellos que tienen navegadores compatibles..


Paso 1: Decidir sobre las metas del proyecto

Antes de comenzar cualquier viaje, ayuda (la mayoría de las veces) tener un destino. El objetivo de este proyecto es llevar un enlace estándar a una página que contenga un formulario de contacto y habilitar ese formulario para que aparezca en la página actual en un diálogo modal..

Hay varias razones para este enfoque:

  • Si el usuario tiene JavaScript deshabilitado, se envía a la página del formulario de contacto como de costumbre..
  • Solo se debe mantener una versión del formulario..
  • El contenido adicional (el formulario) se puede cargar de forma asíncrona.

Paso 2: Listar las herramientas

Escribir esto desde cero en JavaScript en bruto sería un montón de código. Afortunadamente para nosotros, existen herramientas que podemos aprovechar para facilitar la tarea. Este tutorial se basa en:

  • jQuery
  • jQuery UI
  • jQuery UI Stylesheets (CSS)

Para hacer que este código sea lo más reutilizable posible, escribiremos un complemento. Si no está familiarizado con la creación de un complemento, puede obtener una introducción del artículo de Jeffrey Way aquí en Nettuts +. La funcionalidad modal vendrá del $ .dialog de jQuery-UI..


Paso 3: Diseñar la interfaz de plug-in

Vamos a seguir el patrón normal para un complemento jQuery: llamar al complemento en un selector y configurar las opciones a través de una matriz. ¿Qué opciones se necesitan? Habrá opciones tanto para la ventana modal como para el propio complemento. Vamos a esperar que el complemento sea llamado en un ancla, y lo haremos cumplir en el código.

 $ ('a.form_link'). popUpForm (container: ", modal: true, resizeable: false, width: 440, title: 'Website Form', beforeOpen: function (container) , onSuccess: function (container) , onError: function (container) );

Examinando las opciones

Envase: Así es como el usuario del complemento especificará el ID del formulario en la página remota. El enlace en sí especifica la página, pero la opción de contenedor nos permitirá recuperar la parte relevante. Este será el solamente Opción requerida al llamar al plug-in.

Modal, Resizeable, Ancho, Título: Todas estas opciones se pasarán al $ .dialog de jQuery UI. Los valores anteriores son valores predeterminados y el complemento se ejecutará correctamente sin que se establezca ninguno de estos cuando se llame a $ .popUpForm.

beforeOpen, onSuccess, onError: Estos son todos devoluciones de llamada, y esperan una función. A la función se le pasará el objeto para el enlace en el que se hizo clic como 'este' y el contenedor al que se dirige ese enlace. Las devoluciones de llamada están diseñadas para permitir una funcionalidad personalizada para los usuarios de un complemento. El valor predeterminado para estas devoluciones de llamada será una función vacía..

El código mínimo requerido para utilizar el complemento se vería así:

 $ ('a.form_link'). popUpForm (container: '#form_id');

Eso parece simple, ¿no es así? Cuando llama a un complemento como este, se llama al código del complemento con una colección jQuery de todos los elementos DOM que coinciden con el selector, que estará disponible en la variable especial 'this'.


Paso 4: El esqueleto del plug-in

La mayoría de los complementos de jQuery siguen un patrón muy similar. Recorren el grupo de selectores y hacen lo que hacen. Tengo un "esquema" básico de plug-in con el que generalmente trabajo, y encajará muy bien aquí. Este sería el inicio de su archivo de complemento, popUpForm.jquery.js.

 (function ($) $ .fn.popUpForm = function (options) // Predeterminados y opciones var defaults = container: ", modal: true, resizeable: false, ancho: 440, título: 'Formulario del sitio web', antes de Abrir) : function (container) , onSuccess: function (container) , onError: function (container) ; var opts = $ .extend (, valores predeterminados, opciones); self.each (function ()  // El REAL WORK sucede aquí. // Dentro del alcance de esta función, 'this' se refiere a un solo // elemento DOM dentro de la colección jQuery (no es un objeto jQuery));) (jQuery);

El código está envuelto en una función de ejecución automática y se agrega a jQuery usando el espacio de nombres $ .fn. El identificador que sigue a $ .fn es el nombre del método que usarás para invocarlo.

También estamos siguiendo buenas prácticas de codificación al pasar explícitamente la variable jQuery. Esto evitará que nos metamos en problemas si el complemento se usa en una página con otros marcos de JavaScript, algunos de los cuales usan $ como variable.

A continuación, se crea una matriz de valores predeterminados, y estos valores predeterminados se utilizarán si no se definen cuando se llama al complemento. La línea que sigue inmediatamente a la matriz de valores predeterminados combina las opciones pasadas con las predeterminadas y las almacena todas en la matriz de opciones..

Finalmente, se crea un bucle para iterar sobre la colección jQuery identificada por el selector cuando se llama el complemento ... Si bien es probable que en la mayoría de las situaciones sea un solo elemento (un ancla), todavía manejará múltiples enlaces con un solo llamada - asumiendo que todos cargan la misma forma.

Un importante Lo que hay que entender es que el valor de la variable especial 'esto' cambia cuando ingresamos al bucle self.each; es un método especial de jQuery diseñado para facilitar la recopilación de DOM en bucle. La función de devolución de llamada utiliza el contexto del elemento DOM actual, por lo que la variable 'this' se refiere a ese elemento dentro del bucle.

Puede ver en un ejemplo muy simple cómo 'esto' se refiere a una colección jQuery de objetos jQuery en el alcance de la función de complemento, pero dentro de cada bucle, 'este' se refiere a un elemento DOM único y no jQuery.


Paso 5: Comenzando las tripas

El código para las siguientes secciones está contenido en el bloque self.each de nuestro esqueleto. ¿Que hacemos ahora? Para cada elemento jQuery pasado, habrá varios pasos a seguir:

  • Asegúrate de que sea un enlace, y que vaya a alguna parte
  • Busca la parte de la página remota especificada
  • Adjunte el formulario remoto a la página y cree un cuadro de diálogo oculto para él
  • Roba el enlace para que se cree nuestro pop-up.
  • Manejar las presentaciones de formularios de estilo AJAX.

Antes de hacer algo de eso, sin embargo, vamos a agregar una línea de código dentro de la devolución de llamada, en la parte superior

 var $ this = $ (this);

Esto es más que solo conveniencia; la variable 'esto' quedará fuera del alcance en cualquier cierre dentro de cada bucle, y luego necesitaremos acceso al objeto actual. Como casi siempre lo queremos como un objeto jQuery, lo almacenamos como uno.


Paso 6: Asegúrate de que el elemento sea válido

$ .popUpForm solo operará con etiquetas de anclaje, y la etiqueta de anclaje debe tener un valor href para que sepamos dónde obtener el formulario. Si alguna de estas condiciones no se cumple, vamos a dejar el elemento solo. La segunda línea de nuestras 'agallas' será:

 if (! $ this.is ('a') || $ this.attr ('href') == ") return;

Algunas personas odian los puntos de retorno múltiples en una función, pero siempre he encontrado que tener uno al principio puede hacer que una función sea más legible, en lugar de usar una if (condición) para ajustar el resto de la función. En cuanto al rendimiento, son idénticos.


Paso 7: Recuperar la información de la página remota

El método $ .load tiene una buena funcionalidad que permite que una llamada especifique e identifique para adjuntar solo parte de un documento recuperado. La secuencia de comandos no adjuntará el HTML devuelto directamente al DOM, ya que $ .load solo sobrescribe, no se adjunta.

 var SRC = $ this.attr ('href') + "+ opts.container; var formDOM = $ ("
") .load (SRC, function ()

La variable opts.container tiene el ID del elemento de formulario en la página remota. La segunda línea carga esta página remota y adjunta el formulario y su contenido a un div, cuya totalidad está almacenada en la variable formDOM. Tenga en cuenta que $ .load incluye una devolución de llamada (la función) - usaremos formDOM dentro de esa devolución de llamada.


Paso 8: Adjunte el HTML y cree el diálogo

Dentro de la devolución de llamada de $ .load, el código adjuntará el formulario, anulará el evento de clic del ancla y anulará el evento de envío del formulario.

El HTML del formulario se almacena en la variable formDOM en este punto, y adjuntarlo a la página existente es fácil.

 $ ('# popUpHide'). append (formDOM);

El id #popUpHide se refiere a un div oculto que se adjunta a la página por el complemento. Con el fin de proporcionar ese div, se añadirá la siguiente línea en la parte superior del plug-in. Si ya existe, no lo recreamos..

 $ ("# popUpHide"). length || PS
') .appendTo (' body '). css (' display ',' none ');

Ahora que el formulario está oculto de manera segura en nuestra página, es hora de usar una llamada al método $ .dialog para crear el formulario. La mayoría de los parámetros de configuración se toman de nuestro complemento. La opción 'autoopen' está codificada, ya que queremos que el cuadro de diálogo se abra cuando se hace clic en el enlace, y no cuando se crea el cuadro de diálogo.

 // Crear y almacenar el diálogo $ (opts.container) .dialog (autoOpen: false, width: opts.width, modal: opts.modal, resizable: opts.resizeable, title: opts.title);

Paso 9: anular el manejo predeterminado de eventos

Si nos detuviéramos aquí, el plug-in no estaría haciendo mucho. El enlace todavía nos llevaría a la página siguiente. El comportamiento que deseamos es que el enlace abra el diálogo..

 $ this.bind ('click', function (e) e.preventDefault (); opts.beforeOpen.call ($ this [0], opts.container); $ (opts.container) .dialog ('open') ;);

La primera línea de este controlador de clic es muy importante. Evita que el enlace cargue la nueva página cuando se hace clic..

La segunda línea es nuestra devolución de llamada 'beforeOpen'. La variable opts.beforeOpen contiene una referencia de función, eso es obvio. El método .call se utiliza para invocar la función de una manera en la que podemos proporcionar contexto: la variable 'this' para esa función. El primer argumento pasado se convierte en 'esto' a la función llamada..

Cuando una función tiene acceso a la variable 'this', hay algunos contratos que JavaScript tiene con el programador que debemos mantener.

  • La variable 'esto' debe ser el objeto sobre el que actúa la función
  • La variable 'this' es un solo objeto DOM

Para mantener ese contrato, pasamos $ this [0] en lugar de $ this. $ este [0] representa un único objeto DOM que no es jQuery.

Para ayudar a entender esto un poco mejor, imagine la siguiente función de devolución de llamada:

 opts.beforeOpen = function (container) // Proporciona el valor del enlace en el que acaba de hacer clic en alerta ('La página remota es' + this.href); // Da el contenedor de identificación asignado a esta alerta de enlace ('Y el contenedor es' + contenedor); 

El clic del enlace no es el único comportamiento predeterminado para anular. También queremos que el formulario se envíe a través de AJAX, por lo que se debe evitar el evento de forma normal onsumbit y se debe codificar un nuevo comportamiento.

 $ (opts.container) .bind ('submit', function (e) e.preventDefault (); ajaxSubmit (););

Nuevamente, usamos preventDefault () para detener el evento y, en este caso, agregamos una nueva función para manejar el envío del formulario. El código ajaxSubmit () podría ir directamente en la devolución de llamada, pero se ha movido a una nueva función para facilitar la lectura..


Paso 10: Manejar los envíos de formularios, estilo AJAX

Esta función se agregaría inmediatamente después del final del bucle self.each (no se preocupe, verá el código completo del plug-in de una sola vez en un momento). Toma el formulario, lo envía a un script remoto y dispara las devoluciones de llamada apropiadas.

El primer paso es obtener el formulario como un objeto jQuery y determinar el método del formulario, ya sea GET o POST.

 function ajaxSubmit () var form = $ (opts.container); var method = form.attr ('method') || 'OBTENER';

Si lo recuerda, almacenamos el ID del formulario en opts.container. La siguiente línea verifica el formulario de un método y asigna "GET" si no hay ningún método presente. Esto es consistente con HTML que usa GET de forma predeterminada en los formularios si no se especifica ningún método.

Use el método $ .ajax para enviar el formulario:

 $ .ajax (type: method, url: form.attr ('action'), data: form.serialize (), success: function () $ (opts.container) .dialog ('close'); opts. onSuccess.call ($ this [0], opts.container);, error: function () $ (opts.container) .dialog ('close'); opts.onError.call ($ this [0], opts .envase);  );

La opción de URL se determina a partir del atributo de acción de la etiqueta de formulario. Los datos se producen utilizando el método de serialización en el objeto jQuery que contiene el formulario.

Las opciones de éxito y error son las devoluciones de llamada $ .ajax, que a su vez usamos para llamar a nuestras devoluciones de llamada, de la misma manera que se invocó la devolución de llamada antes de Abrir.

También estamos cerrando el cuadro de diálogo para los controladores de éxito y error.


Paso 11: El complemento completo

Como una revisión, veamos el código que hemos escrito hasta ahora como un todo, incluyendo algunos comentarios de código útiles:

 (function ($) var alog = window.console? console.log: alert; $ .fn.popUpForm = function (options) // REQUIERE un contenedor if (! options.container) alert ('Se requiere opción de contenedor' ); return; // Denos un lugar para adjuntar formularios $ ("# popUpHide"). length || $ ('
') .appendTo (' body '). css (' display ',' none '); // Valores predeterminados y opciones var defaults = container: ", modal: true, resizeable: false, width: 440, title: 'Website Form', beforeOpen: function (container) , onSuccess: function (container) , onError: function (container) ; var opts = $ .extend (, defaults, options); // "this" dentro de cada bucle se refiere al único elemento DOM // de la colección jQuery que estamos actualmente operando en this.each (function () / * Queremos mantener el valor 'this' disponible para $ .load * callback * / var $ this = $ (this); / * solo queremos procesar un artículo si es un enlace y * tiene un valor href * / if (! $ this.is ('a') || $ this.attr ('href') == ") return; / * Para un $ .load ( ) la función, el parámetro es la url seguida por * el selector de ID para la sección de la página para agarrar * / var SRC = $ this.attr ('href') + "+ opts.container; / * se realiza el enlace del evento en la devolución de llamada en caso de que el formulario * no se cargue, o el usuario haga clic en el enlace antes de que * el modal esté listo * / var formDOM = $ ("
") .load (SRC, function () // Agregar a la página $ ('# popUpHide'). append (formDOM); // Crear y almacenar el cuadro de diálogo $ (opts.container) .dialog (autoOpen: false , width: opts.width, modal: opts.modal, resizable: opts.resizeable, title: opts.title); / * detiene el envío del formulario normal; tuvo que ir después de * creando el cuadro de diálogo; de lo contrario, el formulario no existe * aún para poner un controlador de eventos en * / $ (opts.container) .bind ("submit", function (e) e.preventDefault (); ajaxSubmit ($ this [0]);); // create a enlace para el enlace pasado al complemento $ this.bind ("click", function (e) e.preventDefault (); opts.beforeOpen.call ($ this [0], opts.container); $ (opts .container) .dialog ('abrir');););); function ajaxSubmit (anchorObj) console.log (anchorObj); var form = $ (opts.container); var method = form.attr ( 'method') || 'GET'; $ .ajax (type: method, url: form.attr ('action'), data: form.serialize (), success: function () $ (opts.container) .dialog ('cerrar'); opts.onSuccess.call (anchorObj, opts.container ); , error: function () opts.onError.call (anchorObj, opts.container); ); ) (jQuery);

Este código debe guardarse en un archivo llamado popUpForm.jquery.js


Paso 12: Configuración del complemento

El primer paso en el uso de complementos sería incluir todas las dependencias necesarias en su página HTML. Personalmente prefiero usar el CDN de Google. Los archivos que están en un dominio separado pueden ayudar a la velocidad de carga de la página, y los servidores son rápidos. Además, aumenta las posibilidades de que un visitante ya tenga estos archivos en caché.

En el encabezado del documento HTML, agregue lo siguiente:

    

El archivo main.css es para los estilos específicos de nuestro sitio, todo lo demás es de CDN de Google. Tenga en cuenta que incluso puede utilizar los temas de jQuery-UI de la CDN de esta manera.


Paso 13: Invocando el Plug-In

Recuerde, solo queremos invocar el complemento en los enlaces que van a una página de formulario. En la demostración en línea, los formularios están contenidos en form.html, y solo dos enlaces van a esa página.

 

Las llamadas están envueltas en un bloque document.ready para que podamos estar seguros de que los elementos de anclaje existen antes de intentar actuar sobre ellos. La segunda llamada, $ ('. Survey a') es un ejemplo de la cantidad mínima necesaria para usar nuestro nuevo complemento. El primer ejemplo establece una devolución de llamada para onSuccess y onError.


Paso 14: Estilizando el modal

Si ha llegado tan lejos y ha creado ejemplos de formularios y una página desde la cual puede llamar, notará que el formulario en el modal probablemente sea feo. El modal en sí no es malo, porque estamos usando un tema jQuery-UI. Pero la forma dentro del modal es mayormente sin estilo, por lo que deberíamos hacer algunos esfuerzos para embellecerlo..

Hay algunas cosas que se deben tener en cuenta al crear estilos para usar en un jQuery-UI modal:

  • El modal en sí es solo un elemento secundario del elemento BODY de la página.
  • Los contenidos del modal son todos hijos de un div de clase 'ui-dialog'

Usando estos pequeños bits de información, podemos comenzar a aplicar estilos a la forma en el modal. Primero le damos al color de fondo un color con el que estamos contentos y también modificamos la fuente para la barra de título.

 .ui-dialog background: rgb (237,237,237); fuente: 11px verdana, arial, sans-serif;  .ui-dialog .ui-dialog-titlebar font: small-caps bold 24px Georgia, Times, serif; 

A continuación, queremos separar cada elemento del formulario con líneas. Dado que la estructura de formulario alterna h3s con divs que contienen elementos de formulario, agregamos las siguientes reglas:

 .ui-dialog h3, div .ui-dialog border-top: 1px solid rgb (247,247,247); borde inferior: 1px sólido rgb (212,212,212); relleno: 8px 0 12px 10px; 

Y solo queremos líneas entre las secciones, no arriba ni muy abajo.

 .ui-dialog .puForm div: last-child border-bottom: none;  .ui-dialog .puForm h3: first-child border-top: none; 

No olvidemos el estilo de los h3s, y los elementos de formulario. Los botones de opción deben mostrarse en línea para que estén todos en fila..

 .ui-dialog h3 font: 18px Georgia, Times, serif; margen: 0;  .ui-dialog select, .ui-dialog textarea, .ui-dialog input width: 76%; bloqueo de pantalla;  .ui-dialog #rating input, .ui-dialog #rating label display: inline; ancho: auto; 

Recuerde, estos estilos son específicos de este proyecto, tendrá que diseñar sus propios formularios según la estructura que utilice. Para apuntar específicamente a los elementos del formulario, puede apuntar a los descendientes de .ui-dialog, o para diseñar cada formulario individualmente, incluir estilos descendentes del ID de formulario que ha incluido.

La forma de estilo:


Paso 15: Conclusión

Entonces, ¿qué hemos hecho realmente? Hemos tomado un enlace normal que conduce a un formulario de contacto (o formularios) y hemos hecho que ese formulario se cargue en un diálogo modal y se envíe a través de ajax. Para los usuarios sin javascript, no sucede nada y los enlaces se comportan normalmente, por lo que no hemos impedido que nadie complete sus formularios.

Si hace clic en el enlace de la encuesta en la demostración, asegúrese de enviar algo. Voy a publicar los resultados en los comentarios para divertirse después de una semana más o menos!