将所有JavaScript保留在页面底部的干净模式是什么?

时间:2012-02-15 00:44:53

标签: c# asp.net razor

我们有各种页面的嵌套布局。例如:

Master.cshtml

<!DOCTYPE html>
<html>
   <head>...</head>
   <body>@RenderBody()<body>
</html>

Question.cshtml

<div>
  ... lot of stuff ...
  @Html.Partial("Voting", Model.Votes)
</div>
<script type="text/javascript">
  ... some javascript ..
</script>

Voting.cshtml

<div>
  ... lot of stuff ...
</div>
<script type="text/javascript">
  ... some javascript ..
</script>

这一切都很好,但我想在所有内容之后推送所有JavaScript块在页面的页脚中呈现。

有没有办法在嵌套的partials中定义一个魔术指令,可以导致各种脚本标签在页面底部按顺序呈现?

例如,我是否可以创建一个魔术助手来捕获所有js块,然后获得顶层布局来渲染它:

Voting.cshtml

<div>
  ... lot of stuff ...
</div>
@appendJSToFooter{
   <script type="text/javascript">
     ... some javascript ..
   </script>
}

11 个答案:

答案 0 :(得分:3)

我在一年前提出了一个相对简单的解决方案,通过创建一个帮助器来注册ViewContext.TempData中的脚本。我的初始实现(我正在重新思考)只是输出各种引用脚本的链接。不完美,但这是我当前实施的演练。

在部分我按名称注册关联的脚本文件:

@Html.RegisterScript("BnjMatchyMatchy")

在主页面上,我调用一个方法迭代已注册的脚本:

@Html.RenderRegisteredScripts()

这是当前的助手:

public static class JavaScriptHelper
{
    private const string JAVASCRIPTKEY = "js";

    public static void RegisterScript(this HtmlHelper helper, string script)
    {
        var jScripts = helper.ViewContext.TempData[JAVASCRIPTKEY]
            as IList<string>; // TODO should probably be an IOrderedEnumerable

        if (jScripts == null)
        {
            jScripts = new List<string>();
        }

        if (!jScripts.Contains(script))
        {
            jScripts.Add(script);
        }

        helper.ViewContext.TempData[JAVASCRIPTKEY] = jScripts;
    }

    public static MvcHtmlString RenderRegisteredScripts(this HtmlHelper helper)
    {
        var jScripts = helper.ViewContext.TempData[JAVASCRIPTKEY]
            as IEnumerable<string>;

        var result = String.Empty;

        if (jScripts != null)
        {
            var root = UrlHelper.GenerateContentUrl("~/scripts/partials/",
                    helper.ViewContext.HttpContext);

            result = jScripts.Aggregate("", (acc, fileName) =>
                String.Format("<script src=\"{0}{1}.js\" " +
                    "type=\"text/javascript\"></script>\r\n", root, fileName));
        }

        return MvcHtmlString.Create(result);
    }
}

正如我的TODO(我应该解决这个问题)所示,您可以轻松地修改它以使用IOrderedEnumerable来保证订单。

正如我所说的不完美而输出一堆script src标签肯定会产生一些问题。我一直在潜伏,因为您对jQuery Tax的讨论已经与Steve SoudersStack OverflowTwitteryour blog一起发挥作用。无论如何,它激励我重写这个助手来读取脚本文件的内容,然后将它们转储到自己的script标签而不是链接标签中的呈现页面。该更改应有助于加快页面呈现速度。

答案 1 :(得分:1)

您可以在body文件的Master.cshtml元素底部定义一个部分,如下所示:

@RenderSection("Footer", required: false)

然后在单个.cshtml文件中,您可以使用以下内容:

@section Footer {
    <script type="text/javascript">
    ...
    </script>
}

答案 2 :(得分:1)

不要构建一些服务器端基础架构来处理客户端问题,而是查看我对另一个问题的回答:https://stackoverflow.com/a/9198526/144604

使用RequireJS,http://requirejs.org,您的脚本不一定位于页面底部,但它们将异步加载,这将有助于提高性能。执行脚本时不会暂停页面呈现。它还促进编写Javascript作为许多小模块,然后提供部署工具,以便在网站发布时将它们组合并最小化。

答案 3 :(得分:1)

这有点hacky,但如果您的目标是影响现有视图的最小更改(除了移动渲染的脚本),我过去的方式(特别是在Web窗体中,但也适用于MVC) )是用一个将脚本推到底部的TextWriter来覆盖它。

基本上你只需编写TextWriter实现并将其连接到寻找<script src="的MVC基页并在内部Queue中捕获文件名,然后当它开始时获得Write </body>Queue次调用,它会在TextWriter内呈现所有内容。它可以通过正则表达式完成,但它可能很慢。此article显示了移动ViewState的<scriptWriter> <add file="~/scripts/jquery.ui.js"> <add dependency="~/scripts/jquery.js" /> </add> </scriptWriter> 示例,但应使用相同的主体。

为了覆盖依赖关系,我在web.config中定义了脚本文件和相关脚本文件,类似于我需要排序覆盖的情况:

@Script.Require("~/scripts/jquery-ui.js")

免责声明就像我说的,这是hacky,更好的解决方案是在你的部分(@using(Script.Capture()) { @RenderBody() @Html.Partial("Partial") } )中使用某种类似CommonJS / AMD的语法,基本上你可以写一个功能,如果主/布局页面指示其捕获脚本注册它可以监听所有子注册,否则它只能输出内联,所以不会伤到只是在任何地方使用它。当然它可能会破坏智能感知。

所以假设你的主人有一些代码:

@Script.Require("~/scripts/jquery-ui.js")

部分:

public class ScriptHelper : IDisposable
{
    bool _capturing = false;
    Queue<string> _list = new Queue<string>();
    readonly ViewContext _ctx;


    public ScriptHelper Capture()
    {
        _capturing = true;
        return this;
    }

    public IHtmlString Require(string scriptFile)
    {
        _list.Enqueue(scriptFile);

        if (!_capturing)
        {
            return Render();
        }

        return new HtmlString(String.Empty);
    }

    public IHtmlString Render()
    {
        IHtmlString scriptTags;

        //TODO: handle dependencies, order scripts, remove duplicates

        _list.Clear();

        return scriptTags;
    }

    public void Dispose()
    {
        _capturing = false;

        _ctx.Writer.Write(Render().ToHtmlString());
    }
}

然后你可以编写这样的代码来处理它:

Queue

您可能需要制作ThreadStatic HttpContext或使用{{1}}或其他内容,但我认为这可以提供一般性的想法。

答案 4 :(得分:1)

如果您遇到无法改进现有结构的情况(即您同时拥有MVC和旧的WebForms页面,javascript标签包含在各处......等等),您可以查看一些我在HTTP模块上做的工作,它在输出上进行后处理,就像在Apache中的mod_pagespeed一样。还是早期的。

https://github.com/Teun/ResourceCombinator

如果您仍然可以选择以预定义的方式包含脚本,那可能会更好。

编辑:Sam提到的项目(见评论)确实在很大程度上重叠并且更加成熟。我从GitHub中删除了ResourceCombinator项目。

答案 5 :(得分:1)

我意识到这艘船几乎已经航行,但由于我有某种解决方案(尽管不是一个完美的解决方案),我想我会分享它。

我写了一篇关于我使用的脚本渲染方法的博客文章。在那里我提到了Michael J. Ryan写的一个图书馆,我已经为自己的目的进行了调整。

使用它可以使用以下内容在任何地方呈现脚本:

@Html.AddClientScriptBlock("A script", @"

    $(function() {
        alert('Here');
    });

")

然后在关闭正文标记之前使用此调用在布局页面中触发它的输出:

@Html.ClientScriptBlocks()

使用此方法在Visual Studio中没有智能感知。如果我老实说,我并不建议使用这种技术;我宁愿为调试点分别使用JS文件,并使用HTML数据属性来驱动任何动态行为。但是,如果它有用,我想我会分享它。警告经纪人等

以下是相关链接列表:

  1. My blog post
  2. Michael J. Ryan's blog post
  3. My helper on GitHub

答案 6 :(得分:0)

我建议你不要将脚本直接放到你的页面上。

而是将JavaScript拉入单独的.js文件,然后在标题中引用它。

我知道这并不是你要求的,但我认为你是为了SEO而这样做,这应该与将脚本放在页面底部的效果相同。

答案 7 :(得分:0)

我们使用Telerik(GPL开源)Asp.Net MVC库中的ScriptRegistrar方法。

在任何视图/部分页面等中包含您的javascript,如下所示。 Telerik库在最终输出页面的底部处理所有这一切。

<% 
Html.Telerik().ScriptRegistrar()
             .Scripts(scripts => scripts.AddSharedGroup('lightbox')
             .OnDocumentReady(() => { %>
   <%-- LIGHTBOX  --%>
   $('.images a').lightBox();
<% });%>

它还考虑将多个css和js一起分组为单个请求。

http://www.telerik.com/products/aspnet-mvc.aspx

答案 8 :(得分:0)

我谦虚的选择是将这些事情留给专业人士:-)看看ControlJS,一个lib来加载外部javascript文件而不中断页面处理(异步,延迟,按需等) )。使用ControlJS时,它不介意“放置加载代码的位置”,它们将在最后或按需加载。

lib作者Steve Souders的presentation对问题(以及解决方案)进行了很好的概述。

答案 9 :(得分:0)

山姆,

我为此写了一个Portal:http://nuget.org/packages/Portal你基本上放了一个

@Html.PortalOut() 
在您的主/布局视图中

并从视图或部分视图中填充HTML代码,如此

@Html.PortalIn(@<text> $(function() { alert('Hi'); }); </text>)

您可以通过指定输入和输出键来使用多个“门户”。现在它按照它的顺序添加代码(部分首先渲染,从“从上到下”)。如果你在同一个视图中,你必须处理从上到下的限制,即你不能在“in”之前有一个“out”门户,但从视图发送到布局时这不是问题。 Portal.cs文件中有更多示例。

答案 10 :(得分:0)

html Response Filter如何修改你的最终html。在特定位置之前查找所有脚本标记,并将其粘贴到正文的末尾。它也可用于改进dataTables渲染,最初将它们标记为隐藏并使js显示它们。

无需干预或更改实际代码。