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

时间:2014-08-05 08:24:20

标签: ajax layout express pug templating

摘要

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

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

TL; DR:2个问题

  • 使用Express + Jade为AJAX导航呈现视图片段的最干净最快方式是什么?
  • 如何加载相对于每个视图的JavaScript文件?

要求

  • 我的网络应用程序需要与已禁用的用户兼容 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")

我的 page_full.jade 是:

extends layout.jade

block content
    h1 Hey Welcome !

我的 page_ajax 是:

h1 Hey Welcome

最后在 router.js (快递):

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

缺点

  • 正如您可能猜到的那样,每次我都要编辑两次视图 需要改变一些事情。相当令人沮丧。

解决方案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")

我的 page_full.jade 现在是:

extends layout.jade

block content
   include page.jade

我的 page.jade 包含没有任何布局/块/扩展/包含的实际内容:

h1 Hey Welcome

我现在在 router.js (快递):

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

优势

  • 现在我的内容只定义一次,这样做会更好。

缺点

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

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

使用Alex Ford's technique,我可以在 middleware.js 中定义自己的render函数:

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

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

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

保持其他文件不变。

优点

  • 解决了代码重复问题

缺点

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

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

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

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

middleware.js (快速)

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

所以现在我的 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")

我的 page.jade 将是:

if (!locals.xhr)
    extends layout.jade

block content
   h1 Hey Welcome !

好吧?除了因为有条件的延伸在Jade中不可行而无法工作。所以我可以将测试从 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

page.jade 将返回:

extends layout.jade

block content
   h1 Hey Welcome !

优势

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

缺点

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

摘要

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


问题#2

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

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

首先,我需要在 layout.jade 中定义extraJS块:

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

然后 page_a.jade 将是:

extends layout.jade

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

如果我在网址栏中输入localhost/page_a (非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")

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

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 js / 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")

相同的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

对吗?呃......如果我去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")

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

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

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

3 个答案:

答案 0 :(得分: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();
    };
 });

随着场景变得更加复杂,您可以更加聪明地使用这个想法,例如将此模板保存到带有文件名占位符的文件中。

当然,这仍然不是一个完美的解决方案。您正在实施模板引擎应该真正处理的功能,与您对解决方案#3的原始异议相同。如果您最终为此编写了多行代码,那么请尝试找到一种方法使该功能适合Jade并向其发送拉取请求。例如,如果玉"延伸"关键字带有一个参数,可能会禁用扩展xhr请求的布局...

对于您的第二个问题,我不确定任何模板引擎都可以为您提供帮助。如果你正在使用ajax导航,那么你就可以很好地卸载"卸载"当导航通过一些后端模板魔术发生时,page_a.js。我认为你必须使用传统的javascript隔离技术(客户端)。针对您的具体问题:在闭包中实现页面特定的逻辑(和变量),以及在必要时在它们之间进行合理的合作。其次,你不必过于担心挂钩双事件处理程序。假设主要内容在ajax导航中被清除,并且那些是事件处理程序被附加的元素,当新的ajax内容被加载到DOM时,它将调用get reset(当然)。

答案 1 :(得分:1)

不确定它是否真的会帮助你,但我用Express和ejs模板解决了同样的问题。

我的文件夹:

  • app.js
  • 视图/ header.ejs
  • 视图/ page.ejs
  • 视图/ footer.ejs

我的app.js

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

my page.ejs

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

content

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

非常简单,但效果很好。

答案 2 :(得分:0)

有几种方法可以做你所要求的,但所有这些方法在建筑上都很糟糕。你现在可能没有注意到,但随着时间的推移会越来越糟。

此时听起来很奇怪,但你真的不希望通过AJAX向你发送JS,CSS。

你想要的是能够通过AJAX获得标记并在那之后做一些Javascript。此外,您希望在执行此操作时保持灵活和理性。可扩展性也很重要。

常见的方法是:

  1. 预加载所有js文件可以在特定页面及其AJAX请求处理程序中使用。如果您害怕潜在的名称冲突或脚本副作用,请使用browserify。异步加载它们(script async)以避免用户等待。

  2. 开发一个架构,使您能够在客户端基本上执行此操作:loadPageAjax('page_a').then(...).catch(...)。关键是,由于您已预先下载了所有JS模块,因此要在请求的调用者处运行与AJAX页面相关的代码,而不是被调用者。< / p>

  3. 所以,你的解决方案#4 几乎是好的。只需使用它来获取HTML,而不是脚本。

    这种技术可以在不构建一些模糊的架构的情况下完成您的目标,但是使用单一的强大目标限制方式。

    很乐意回答任何进一步的问题并说服你不要做你想做的事。