如何正确呈现部分视图,并使用Express/Jade在AJAX中加载JavaScript文件?

Wal*_*ers 38 ajax layout templating express pug

摘要

我正在使用Express + Jade作为我的Web应用程序,我正在努力为我的AJAX导航渲染部分视图.

我有两个不同的问题,但它们是完全相关的,所以我将它们包括在同一篇文章中.我想这将是一个很长的帖子,但我保证如果你已经在努力解决同样的问题,这很有意思.如果有人花时间阅读并提出解决方案,我会非常感激.

TL; DR:2个问题

  • 使用Express + Jade为AJAX导航渲染视图片段的最简洁,最快捷的方法是什么?
  • 应该如何加载与每个视图相关的JavaScript文件?

要求

  • 我的Web应用程序需要与已禁用
    JavaScript的用户兼容
  • 如果启用了JavaScript,则只应将页面自己的内容(而不是整个布局)从服务器发送到客户端
  • 应用程序需要快速,并尽可能少地加载字节

问题#1:我尝试过的

解决方案1:为AJAX和非AJAX请求提供不同的文件

我的layout.jade是:

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")
Run Code Online (Sandbox Code Playgroud)

我的page_full.jade是:

extends layout.jade

block content
    h1 Hey Welcome !
Run Code Online (Sandbox Code Playgroud)

我的page_ajax是:

h1 Hey Welcome
Run Code Online (Sandbox Code Playgroud)

最后在router.js(Express)中:

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

缺点:

  • 正如您可能猜到的,每次我需要更改某些内容时,我都必须编辑两次视图.相当令人沮丧.

解决方案2:与`include`相同的技术

我的layout.jade保持不变:

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")
Run Code Online (Sandbox Code Playgroud)

我的page_full.jade现在是:

extends layout.jade

block content
   include page.jade
Run Code Online (Sandbox Code Playgroud)

我的page.jade包含没有任何layout/block/extend/include的实际内容:

h1 Hey Welcome
Run Code Online (Sandbox Code Playgroud)

我现在在router.js(Express):

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

优点:

  • 现在我的内容只定义一次,那就更好了.

缺点:

  • 我仍然需要一个页面的两个文件.
  • 我必须在每条路线上使用相同的技术.我刚刚将我的代码重复问题从Jade移到了Express.捕捉.

解决方案3:与解决方案2相同,但修复了代码重复问题.

使用Alex Ford的技术,我可以rendermiddleware.js中定义自己的函数:

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

然后将router.js(Express)更改为:

app.get("/page",function(req,res,next){
    res.renderView("/page");
});
Run Code Online (Sandbox Code Playgroud)

保持其他文件不变.

好处

  • 它解决了代码重复问题

缺点

  • 我仍然需要一个页面的两个文件.
  • 定义我自己的renderView方法感觉很脏.毕竟,我希望我的模板引擎/框架能够为我处理这个问题.

解决方案4:将逻辑移至Jade

我不喜欢在一个页面上使用两个文件,那么如果我让Jade决定要渲染什么而不是Express呢?乍一看,对我来说似乎很不舒服,因为我认为模板引擎根本应该处理任何逻辑.但是试试吧.

首先,我需要将一个变量传递给Jade,它将告诉它它是什么类型的请求:

middleware.js(Express)

app.use(function (req, res, next) {  
    res.locals.xhr = req.xhr;
 });
Run Code Online (Sandbox Code Playgroud)

所以现在我的layout.jade和以前一样:

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")
Run Code Online (Sandbox Code Playgroud)

而我的page.jade将是:

if (!locals.xhr)
    extends layout.jade

block content
   h1 Hey Welcome !
Run Code Online (Sandbox Code Playgroud)

好吧?除非这不起作用,因为在玉中有条件的延伸是不可能的.所以我可以将测试从page.jade移动到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
Run Code Online (Sandbox Code Playgroud)

page.jade将返回:

extends layout.jade

block content
   h1 Hey Welcome !
Run Code Online (Sandbox Code Playgroud)

优点:

  • 现在,每页只有一个文件
  • 我不必req.xhr在每个路线或每个视图中重复测试

缺点:

  • 我的模板中有逻辑.不好

摘要

这些都是我想到和尝试的技术,但没有一个真正让我信服.难道我做错了什么 ?有清洁技术吗?或者我应该使用另一个模板引擎/框架?


问题#2

如果视图有自己的JavaScript文件,会发生什么(使用任何这些解决方案)?

例如,使用解决方案#4,如果我有两个页面,page_a.jadepage_b.jade都有自己的客户端JavaScript文件js/page_a.jsjs/page_b.js,那么页面会发生什么在AJAX中加载?

首先,我需要extraJSlayout.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
Run Code Online (Sandbox Code Playgroud)

然后page_a.jade将是:

extends layout.jade

block content
   h1 Hey Welcome !
block extraJS
   script(src="js/page_a.js")
Run Code Online (Sandbox Code Playgroud)

如果我输入localhost/page_a我的URL栏(非AJAX请求),我会得到一个编译版本:

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")
Run Code Online (Sandbox Code Playgroud)

看起来很好.但是,如果我现在page_b使用我的AJAX导航会发生什么?我的页面将是以下版本的编译版本:

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")
Run Code Online (Sandbox Code Playgroud)

js/page_a.jsjs/page_b.js都加载在同一页面上.如果存在冲突会发生什么(相同的变量名等等)?另外,如果我使用AJAX 返回localhost/page_a,我会这样:

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")
Run Code Online (Sandbox Code Playgroud)

相同的JavaScript文件(page_a.js)在同一页面上加载两次!是否会引发冲突,每次事件都会被解雇?不管是不是这样,我认为这不是干净的代码.

因此,您可能会说特定的JS文件应该在我的内容中,block content以便在我转到另一个页面时将其删除.因此,我的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
                    block extraJS
                // Shared JS files go here
                script(src="js/jquery.min.js")
 else
     block content
     // Specific JS files go there
     block extraJS
Run Code Online (Sandbox Code Playgroud)

对 ?呃....如果我去localhost/page_a,我会得到一个编译版本:

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")
Run Code Online (Sandbox Code Playgroud)

您可能已经注意到,js/page_a.js实际上是 jQuery 之前加载的,所以它不起作用,因为jQuery还没有定义...所以我不知道该怎么办这个问题.我想过使用(例如)客户端处理脚本请求jQuery.getScript(),但客户端必须知道脚本的文件名,看看它们是否已经加载,可能会删除它们.我不认为它应该在客户端完成.

我该如何处理通过AJAX加载的JavaScript文件?服务器端使用不同的策略/模板引擎?客户端 ?

如果你已经做到这一点,你就是一个真正的英雄,我很感激,但如果你能给我一些建议我会更感激:)

Seg*_*ult 3

很好的问题。我没有完美的选择,但我会提供我喜欢的解决方案 #3 的变体。与解决方案 #3 的想法相同,但将 _full 文件的 jade 模板移到您的代码中,因为它是样板文件,并且 javascript 可以在需要完整页面时生成它。免责声明:未经测试,但我谦虚地建议:

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();
    };
 });
Run Code Online (Sandbox Code Playgroud)

当您的场景变得更加复杂时,您可以更加巧妙地运用这​​个想法,例如将此模板保存到带有文件名占位符的文件中。

当然这仍然不是一个完美的解决方案。您正在实现的功能实际上应该由您的模板引擎处理,与您最初反对解决方案#3 相同。如果您最终为此编写了几十行代码,那么请尝试找到一种方法将该功能适合 Jade 并向他们发送拉取请求。例如,如果 jade“extends”关键字采用一个参数,可以禁用扩展 xhr 请求的布局......

对于你的第二个问题,我不确定任何模板引擎可以帮助你。如果您使用 ajax 导航,则当导航通过某些后端模板魔术发生时,您无法很好地“卸载”page_a.js。我认为你必须为此使用传统的 javascript 隔离技术(客户端)。对于您的具体问题:首先,在闭包中实现页面特定的逻辑(和变量),并在必要时在它们之间进行合理的合作。其次,您不必太担心连接双事件处理程序。假设在 ajax 导航上清除了主要内容,并且这些元素附加了事件处理程序,当新的 ajax 内容加载到 DOM 中时,它们将调用 get Reset(当然)。