peticion nodejs node example ajax layout express pug templating

ajax - example - nodejs http request get



¿Cómo renderizar correctamente vistas parciales y cargar archivos JavaScript en AJAX usando Express/Jade? (3)

Resumen

Estoy usando Express + Jade para mi aplicación web, y estoy luchando con la representación de vistas parciales para mi navegación AJAX.

Tengo dos preguntas diferentes, pero están totalmente vinculadas, así que las incluí en la misma publicación. Supongo que será una publicación larga, pero te garantizo que es interesante si ya has tenido problemas con los mismos problemas. Apreciaría mucho si alguien se tomara el tiempo para leer y proponer una solución.

TL; DR: 2 preguntas

  • ¿Cuál es la forma más limpia y rápida de generar fragmentos de vistas para una navegación AJAX con Express + Jade?
  • ¿Cómo deberían cargarse los archivos JavaScript relativos a cada vista?

Requisitos

  • Mi aplicación web debe ser compatible con los usuarios que tienen discapacidades
    JavaScript
  • Si JavaScript está habilitado, solo el contenido de la página (y no todo el diseño) debe enviarse desde el servidor al cliente.
  • La aplicación debe ser rápida y cargar la menor cantidad de bytes posible

Problema n. ° 1: lo que he intentado

Solución 1: tener diferentes archivos para solicitudes AJAX y no AJAX

Mi layout.jade es:

doctype html html(lang="fr") head // Shared CSS files go here link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css") body div#main_content block content // Shared JS files go here script(src="js/jquery.min.js")

Mi page_full.jade es:

extends layout.jade block content h1 Hey Welcome !

Mi página_ajax es:

h1 Hey Welcome

Y finalmente en router.js (Express):

app.get("/page",function(req,res,next){ if (req.xhr) res.render("page_ajax.jade"); else res.render("page_full.jade"); });

Inconvenientes :

  • Como probablemente adivinó, tengo que editar mis vistas dos veces cada vez que necesito cambiar algo. Muy frustrante

Solución 2: misma técnica con `incluir`

Mi layout.jade permanece sin cambios:

doctype html html(lang="fr") head // Shared CSS files go here link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css") body div#main_content block content // Shared JS files go here script(src="js/jquery.min.js")

Mi page_full.jade ahora es:

extends layout.jade block content include page.jade

Mi page.jade contiene el contenido real sin ningún diseño / bloquear / extender / incluir:

h1 Hey Welcome

Y ahora tengo en router.js (Express):

app.get("/page",function(req,res,next){ if (req.xhr) res.render("page.jade"); else res.render("page_full.jade"); });

Ventajas :

  • Ahora mi contenido solo se define una vez, eso es mejor.

Inconvenientes :

  • Todavía necesito dos archivos para una página.
  • Tengo que usar la misma técnica en Express en cada ruta. Acabo de mover mi problema de repetición de código de Jade a Express. Chasquido.

Solución 3: igual que la Solución 2 pero arreglando el problema de repetición de código.

Utilizando la técnica de Alex Ford , pude definir mi propia función de render en middleware.js :

app.use(function (req, res, next) { res.renderView = function (viewName, opts) { res.render(viewName + req.xhr ? null : ''_full'', opts); next(); }; });

Y luego cambie router.js (Express) a:

app.get("/page",function(req,res,next){ res.renderView("/page"); });

dejando los otros archivos sin cambios.

Ventajas

  • Solucionó el problema de repetición de código

Inconvenientes

  • Todavía necesito dos archivos para una página.
  • Definir mi propio método renderView se siente un poco sucio. Después de todo, espero que mi motor / marco de plantilla lo maneje por mí.

Solución 4: Mover la lógica a Jade

No me gusta usar dos archivos para una página, ¿qué ocurre si dejo que Jade decida qué representar en lugar de Express? A primera vista, me parece muy incómodo, porque creo que el motor de plantillas no debería manejar ninguna lógica. Pero intentémoslo.

Primero, necesito pasarle una variable a Jade que le dirá qué tipo de solicitud es:

En middleware.js (Express)

app.use(function (req, res, next) { res.locals.xhr = req.xhr; });

Así que ahora mi layout.jade sería el mismo que antes:

doctype html html(lang="fr") head // Shared CSS files go here link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css") body div#main_content block content // Shared JS files go here script(src="js/jquery.min.js")

Y mi page.jade sería:

if (!locals.xhr) extends layout.jade block content h1 Hey Welcome !

Genial, ¿eh? Excepto que no funcionará porque las extensiones condicional son imposibles en Jade. Así que pude mover la prueba de page.jade a layout.jade :

if (!locals.xhr) doctype html html(lang="fr") head // Shared CSS files go here link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css") body div#main_content block content // Shared JS files go here script(src="js/jquery.min.js") else block content

y page.jade volvería a:

extends layout.jade block content h1 Hey Welcome !

Ventajas :

  • Ahora, tengo solo un archivo por página
  • No tengo que repetir la prueba de req.xhr en cada ruta o en cada vista

Desventajas :

  • Hay lógica en mi plantilla. No está bien

Resumen

Estas son todas las técnicas en las que pensé e intenté, pero ninguna de ellas realmente me convenció. Estoy haciendo algo mal ? ¿Hay técnicas más limpias? ¿O debería usar otra plantilla motor / framework?

Problema n. ° 2

¿Qué sucede (con alguna de estas soluciones) si una vista tiene sus propios archivos JavaScript?

Por ejemplo, utilizando la solución n. ° 4, si tengo dos páginas, page_a.jade y page_b.jade, que tienen sus propios archivos de JavaScript del lado del cliente js / page_a.js y js / page_b.js , ¿qué les sucede cuando las páginas están cargados en AJAX?

Primero, necesito definir un bloque extraJS en layout.jade :

if (!locals.xhr) doctype html html(lang="fr") head // Shared CSS files go here link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css") body div#main_content block content // Shared JS files go here script(src="js/jquery.min.js") // Specific JS files go there block extraJS else block content // Specific JS files go there block extraJS

y luego page_a.jade sería:

extends layout.jade block content h1 Hey Welcome ! block extraJS script(src="js/page_a.js")

Si escribiera localhost/page_a en mi barra de URL (solicitud no AJAX) , obtendría una versión compilada de:

doctype html html(lang="fr") head link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css") body div#main_content h1 Hey Welcome A ! script(src="js/jquery.min.js") script(src="js/page_a.js")

Eso se ve bien. ¿Pero qué pasaría si ahora fuera a page_b usando mi navegación AJAX ? Mi página sería una versión compilada de:

doctype html html(lang="fr") head link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css") body div#main_content h1 Hey Welcome B ! script(src="js/page_b.js") script(src="js/jquery.min.js") script(src="js/page_a.js")

js / page_a.js y js / page_b.js están ambos cargados en la misma página. ¿Qué sucede si hay un conflicto (el mismo nombre de variable, etc.)? Además, si vuelvo a localhost / page_a usando AJAX, tendría esto:

doctype html html(lang="fr") head link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css") body div#main_content h1 Hey Welcome B ! script(src="js/page_a.js") script(src="js/jquery.min.js") script(src="js/page_a.js")

¡El mismo archivo de JavaScript ( page_a.js ) se carga dos veces en la misma página! ¿Causará conflictos, doble disparo de cada evento? Independientemente de si ese es el caso, no creo que sea un código limpio.

Entonces, podría decir que los archivos JS específicos deberían estar en el block content mi block content para que se eliminen cuando vaya a otra página. Por lo tanto, mi layout.jade debería ser:

if (!locals.xhr) doctype html html(lang="fr") head // Shared CSS files go here link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css") body div#main_content block content block extraJS // Shared JS files go here script(src="js/jquery.min.js") else block content // Specific JS files go there block extraJS

Correcto ? Err .... Si voy a localhost/page_a , obtendré una versión compilada de:

doctype html html(lang="fr") head link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css") body div#main_content h1 Hey Welcome A ! script(src="js/page_a.js") script(src="js/jquery.min.js")

Como habrás notado, js / page_a.js está realmente cargado antes de jQuery, por lo que no funcionará, porque jQuery aún no está definido ... Así que no sé qué hacer con este problema . Pensé en manejar las solicitudes de script del lado del cliente, usando (por ejemplo) jQuery.getScript() , pero el cliente tendría que saber el nombre del archivo de las secuencias de comandos, ver si ya están cargadas, quizás eliminarlas. No creo que deba hacerse en el lado del cliente.

¿Cómo debo manejar los archivos JavaScript cargados a través de AJAX? ¿Servidor usando un motor de estrategia / plantilla diferente? Lado del cliente ?

Si has llegado hasta aquí, eres un verdadero héroe, y estoy agradecido, pero estaría aún más agradecido si pudieras darme algún consejo :)


Gran pregunta No tengo una opción perfecta, pero ofreceré una variante de su solución n. ° 3 que me gusta. La misma idea que la solución n. ° 3, pero mueva la plantilla de jade del archivo _full a su código, ya que es una plantilla repetitiva y JavaScript puede generarla cuando sea necesario para una página completa. descargo de responsabilidad: no probado, pero humildemente sugiero:

app.use(function (req, res, next) { var template = "extends layout.jade/n/nblock content/n include "; res.renderView = function (viewName, opts) { if (req.xhr) { var renderResult = jade.compile(template + viewName + ".jade/n", opts); res.send(renderResult); } else { res.render(viewName, opts); } next(); }; });

Y puede hacerse más inteligente con esta idea a medida que sus escenarios se vuelven más complicados, por ejemplo, guardando esta plantilla en un archivo con marcadores de posición para nombres de archivo.

Por supuesto, esta no es una solución perfecta. Está implementando características que realmente deberían ser manejadas por su motor de plantillas, al igual que su objeción original a la solución n. ° 3. Si terminas escribiendo más de un par de docenas de líneas de código para esto, intenta encontrar la manera de ajustar la función en Jade y enviarles una solicitud de extracción. Por ejemplo, si la palabra clave jade "extends" toma un argumento que podría deshabilitar la extensión del diseño para solicitudes xhr ...

Para su segundo problema, no estoy seguro de que algún motor de plantillas pueda ayudarlo. Si está utilizando la navegación ajax, no podrá "descargar" page_a.js cuando la navegación se realice a través de una magia de plantilla de fondo. Creo que tienes que usar técnicas tradicionales de aislamiento de JavaScript para esto (del lado del cliente). Para sus preocupaciones específicas: Implemente la lógica (y variables) específicas de la página en cierres, para empezar, y tenga una cooperación sensata entre ellos cuando sea necesario. En segundo lugar, no necesita preocuparse demasiado por conectar manejadores de eventos dobles. Asumiendo que el contenido principal se borre en la navegación ajax y también esos son los elementos que tuvieron los manejadores de eventos conectados que llamarán a restablecerse (por supuesto) cuando el nuevo contenido ajax se cargue en el DOM.


Hay varias formas de hacer lo que preguntas, pero todas son malas desde el punto de vista arquitectónico. Puede que no lo note ahora, pero empeorará con el tiempo.

Suena raro en este punto, pero realmente no quiere transmitirle JS, CSS a través de AJAX.

Lo que quiere es poder obtener el marcado a través de AJAX y hacer algo de Javascript después de eso. Además, desea ser flexible y racional al hacer esto. La escalabilidad es importante también.

La forma común es:

  1. La precarga de todos los archivos js se puede usar en la página en particular y sus manejadores de solicitudes AJAX. Usa browserify si tienes miedo de posibles nombres de conflictos o efectos secundarios del script. Cargúelos de forma asíncrona ( script asíncrono ) para que el usuario no espere.

  2. Desarrolle una arquitectura que le permita hacer, básicamente, esto en el cliente: loadPageAjax(''page_a'').then(...).catch(...) . El punto es que, dado que ha pre-descargado todos los módulos JS, para ejecutar su código relacionado con la página AJAX a la persona que llama y no al destinatario .

Entonces, su Solución # 4 es casi buena. Solo úsalo para buscar el HTML, pero no los scripts.

Esta técnica hace su objetivo sin construir una arquitectura oscura, pero usando la única forma robusta y limitada por objetivos.

Estará feliz de responder cualquier pregunta adicional y convencerlo de no hacer lo que va a hacer.


No estoy seguro de si realmente te va a ayudar, pero resolví el mismo problema con la plantilla Express y ejs.

mi carpeta:

  • app.js
  • views / header.ejs
  • views / page.ejs
  • views / footer.ejs

mi app.js

app.get(''/page'', function(req, res) { if (req.xhr) { res.render(''page'',{ajax:1}); } else { res.render(''page'',{ajax:0}); } });

mi página.ejs

<% if (ajax == 0) { %> <% include header %> <% } %> content <% if (ajax == 0) { %> <% include footer %><% } %>

muy simple pero funciona perfecto.