我们有各种页面的嵌套布局。例如:
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>
}
答案 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 Souders,Stack Overflow,Twitter和your 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数据属性来驱动任何动态行为。但是,如果它有用,我想我会分享它。警告经纪人等
以下是相关链接列表:
答案 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一起分组为单个请求。
答案 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显示它们。
无需干预或更改实际代码。