在部分视图中包含JavaScript文件


69

我想知道在局部视图中包含javascript文件的最佳实践是什么。呈现后,这将最终成为我页面html中间的js include标签。从我的角度来看,这不是执行此操作的好方法。它们属于head标签,因此不应阻止浏览器一次性呈现html。

一个例子: 我在“ PictureGallery”局部视图中使用一个jQuery picturegallery插件,因为该局部视图将在多个页面上使用。仅在使用此视图时加载该插件,并且我不需要知道每个局部视图正在使用哪些插件...

感谢您的回答。


20
最佳做法是在html页面底部实际包含javascript,以在呈现页面时提高浏览器的性能。
爱德华多·斯科兹

2
我从未听说过...您能提供一些资料来备份此声明吗?
彼得

2
我认为实际的问题是能够更改母版页中包含的js文件,而不是它们的位置。有时-如果js文件对于页面加载之前的页面至关重要,则顶部是放置该文件的正确位置。这是个有趣的问题。
埃德弗雷特,2009年

2
我查了一下,确实,雅虎性能团队确认,最有效的方法是将它们包含在结束标签之前。感谢您提供此信息。但是,正如edeverett所指出的,我想知道一些将它们添加到中心位置而不是整个页面中的方法。
彼得

YSlow是一个非常不错的Web性能分析插件。以下是javascript的文档:developer.yahoo.com/performance/rules.html#js_bottom
Eduardo Scoz,2009年

Answers:


28

似乎与以下问题非常相似:在用户控件中链接JavaScript库

我将在此处重新发布该问题的答案。

绝对要出于您提到的原因,建议您不要将它们放入部分中。一个视图很有可能会拉入两个都引用同一个js文件的部分。在加载其余html之前,还具有加载js的性能优势。

我不了解最佳做法,但我选择在母版页中包含所有常见的js文件,然后为一些特定于特定视图或少量视图的其他js文件定义一个单独的ContentPlaceHolder。

这是一个示例母版页-这很容易解释。

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
<head runat="server">
    ... BLAH ...
    <asp:ContentPlaceHolder ID="AdditionalHead" runat="server" />
    ... BLAH ...
    <%= Html.CSSBlock("/styles/site.css") %>
    <%= Html.CSSBlock("/styles/ie6.css", 6) %>
    <%= Html.CSSBlock("/styles/ie7.css", 7) %>
    <asp:ContentPlaceHolder ID="AdditionalCSS" runat="server" />
</head>
<body>
    ... BLAH ...
    <%= Html.JSBlock("/scripts/jquery-1.3.2.js", "/scripts/jquery-1.3.2.min.js") %>
    <%= Html.JSBlock("/scripts/global.js", "/scripts/global.min.js") %>
    <asp:ContentPlaceHolder ID="AdditionalJS" runat="server" />
</body>

Html.CSSBlock和Html.JSBlock显然是我自己的扩展,但同样,它们在做什么方面也很能说明问题。

然后说一个SignUp.aspx视图

<asp:Content ID="signUpContent" ContentPlaceHolderID="AdditionalJS" runat="server">
    <%= Html.JSBlock("/scripts/pages/account.signup.js", "/scripts/pages/account.signup.min.js") %>
</asp:Content>

HTH,查尔斯

附言 这是我询问的有关最小化和串联js文件的跟进问题:快速或在构建时串联和缩小JS-ASP.NET MVC

编辑:根据我的其他答案的要求,我对.JSBlock(a,b)的实现

public static MvcHtmlString JSBlock(this HtmlHelper html, string fileName)
{
    return html.JSBlock(fileName, string.Empty);
}

public static MvcHtmlString JSBlock(this HtmlHelper html, string fileName, string releaseFileName)
{
    if (string.IsNullOrEmpty(fileName))
        throw new ArgumentNullException("fileName");

    string jsTag = string.Format("<script type=\"text/javascript\" src=\"{0}\"></script>",
                                 html.MEDebugReleaseString(fileName, releaseFileName));

    return MvcHtmlString.Create(jsTag);
}

然后魔术发生了...

    public static MvcHtmlString MEDebugReleaseString(this HtmlHelper html, string debugString, string releaseString)
    {
        string toReturn = debugString;
#if DEBUG
#else
        if (!string.IsNullOrEmpty(releaseString))
            toReturn = releaseString;
#endif
        return MvcHtmlString.Create(toReturn);
    }

感谢您的详细解释。昨晚,我正在考虑重写基础页面,并添加一个Scripts / Css集合属性,然后由一些htmlhelper填充该属性。然后,该页面会将它们吐出到与您一样的contentplaceholder中。这项工作还是您会建议反对?
彼得

对不起,我不明白你的意思。“覆盖基础页面”-您是要覆盖System.Web.Mvc.Controller还是System.Web.Mvc.ViewPage或只是创建自己的自定义viewdata模型?“由一些htmlhelpers填充”这也让我感到...如果您使用自定义HtmlHelpers(从视图内部),则只需使用上述ContentPlaceHolder方法。没有更多信息,我建议您直接按照上面的示例进行操作...即使有更多信息,我也仍然建议您执行上述操作。;-D
Charlino

我将看看是否可以在一天结束前制作一个可行的示例,并将其发布在此处作为答案,如果可以的话,我想征询您的意见。我将覆盖ViewPage并添加一个名为Scripts的属性,它是一个集合。然后在局部视图内调用htmlhelper来添加脚本Html.JSBlock(“ path / to / script.js” new {some options})。该帮助程序将脚本添加到父页面的“脚本”集合中。在父页面的视图中的headplaceholder内部,它调用另一个htmlhelper,该htmlhelper从Scripts属性呈现脚本标记。还是那个不好的设计?
彼得

要回答我自己的评论,是的,这是错误的设计。我不知道,直到只是知道,但html助手应该只渲染html。他们不应该进行数据访问
彼得

1

您将脚本放在页面底部的原因是,在尝试进行任何操作之前,请确保已加载dom。这也可以通过$(document).ready(callback);之类的东西来实现。jQuery方法。

我同意不要将嵌入式JavaScript放在html中的观点。相反,我使用HTML助手来创建一个空div用作服务器指令。辅助信号是Html.ServerData(String name,Object data);

我使用此ServerData方法获取从服务器到客户端的指令。div标记保持其干净,并且“ data-”属性为有效的html5。

对于loadScript指令,我可以在ascx或aspx中执行以下操作:

<%= Html.ServerData(“ loadScript”,新的{url:“ pathTo.js”})%>

或添加另一个帮助程序来使它看起来更干净:

<%= Html.LoadScript(“〜/ path / to.js”)%>

html输出为:

<div name =“ loadScript” data-server =“编码的json字符串”>

然后,我有一个jQuery方法,可以找到任何服务器数据标签:$(containsElement).serverData(“ loadScript”); //返回一个类似jQuery的JSON解码对象数组。

客户端可能看起来像这样:

var script = $(containselement“)。serverData(” loadScript“);
$ .getScript(script.url,function(){
    //脚本已加载-现在可以执行脚本
});

该技术非常适合需要在ajax加载的内容中加载的用户控件或脚本。我写的版本涉及处理缓存脚本,因此每个完整页面加载它们仅加载一次,并且包含回调和jQuery触发器,因此您可以在准备就绪时将其挂钩。

如果有人对此功能的完整版本(从MVC到jQuery扩展)感兴趣,我很乐意更详细地展示这种技术。否则-希望它为解决这个棘手问题的人提供了一种新方法。


老兄,我很想和你一起喝酒,然后聊聊:)你的主意似乎真的很不错。它不会将数据(如脚本路径)保留在代码隐藏中,但会将其保留在html中,等待jquery加载。您改进了getScript方法的事实将只加载一次相同的脚本,甚至可以连接一个缩小器!锦上添花的是使加载脚本的jquery代码更加透明,因此它可以呈现自身;-)不幸的是,我们还不能真正使用html5数据集,请参见tinyurl.com/ 2vfeunh
Peter

谢谢,我同意。我使用类似$ .scriptLoaded方法的方法,该方法在客户端用作回调。它还需要一个可选的依赖项数组来加载。该脚本对样式表的作用相同。serverData技术不仅可以用于加载脚本,而且还可以用于向客户端发送的任何服务器指令。我将其用于基本的插件初始化。检索服务器数据的客户端方法在检索后从dom中删除该元素,以保持其清洁。
jhorback 2010年

将它们放在页面底部的原因不是要确保dom已加载。原因是因为它们阻止了Yahoo指南中明确规定的并行下载。将脚本放在页面底部也不能确保dom正确加载。
mattmanser 2010年

1

今天,我创建了自己的解决方案,非常适合该法案。无论是好的设计,还是由大家决定,但我认为应该以任何一种方式分享!

以下是我的HtmlExtensions类,该类允许您在母版页中执行此操作:

<%=Html.RenderJScripts() %>

我的HtmlExtensions类:

public static class HtmlExtensions
{
    private const string JSCRIPT_VIEWDATA = "__js";

    #region Javascript Inclusions

    public static void JScript(this HtmlHelper html, string scriptLocation)
    {
        html.JScript(scriptLocation, string.Empty);
    }

    public static void JScript(this HtmlHelper html, string scriptLocationDebug, string scriptLocationRelease)
    {
        if (string.IsNullOrEmpty(scriptLocationDebug))
            throw new ArgumentNullException("fileName");

        string jsTag = "<script type=\"text/javascript\" src=\"{0}\"></script>";

#if DEBUG
        jsTag = string.Format(jsTag, scriptLocationDebug);
#else
        jsTag = string.Format(jsTag, !string.IsNullOrEmpty(scriptLocationRelease) ? scriptLocationRelease : scriptLocationDebug);
#endif

        registerJScript(html, jsTag);
    }

    public static MvcHtmlString RenderJScripts(this HtmlHelper html)
    {
        List<string> jscripts = html.ViewContext.TempData[JSCRIPT_VIEWDATA] as List<string>;
        string result = string.Empty; ;
        if(jscripts != null)
        {
            result = string.Join("\r\n", jscripts);
        }
        return MvcHtmlString.Create(result);
    }

    private static void registerJScript(HtmlHelper html, string jsTag)
    {
        List<string> jscripts = html.ViewContext.TempData[JSCRIPT_VIEWDATA] as List<string>;
        if(jscripts == null) jscripts = new List<string>();

        if(!jscripts.Contains(jsTag))
            jscripts.Add(jsTag);

        html.ViewContext.TempData[JSCRIPT_VIEWDATA] = jscripts;
    }

    #endregion

}

到底是怎么回事?
上面的类将使用方法扩展HtmlHelper,以将javascript链接添加到该集合所存储的HtmlHelper.ViewContext.TempData集合中。在母版页的末尾,我将放置<%=Html.RenderJScripts() %>,它将在内循环jscripts集合HtmlHelper.ViewContext.TempData并将其呈现到输出中。

但是有一个缺点。.您必须确保在添加脚本之前不呈现脚本。例如,如果要对css链接执行此操作,将无法正常工作,因为您必须将它们放置在<head>页面的标签中,并且htmlhelper会在甚至添加链接之前呈现输出。


1
tempdata是否适合此操作?我的tempdata是一个存储数据的地方,它将在重定向后继续存在。我不认为你需要的任何后续页面请求这个数据
马丁展台

好点,不是。真的很好奇那会是个好地方。
彼得

看来这是最好的选择,因为投票最多的答案在部分视图中不起作用(asp:content在部分视图中是不允许的->内容控件必须是内容页面或嵌套页面中的顶级控件仍然是我希望将它们放在标头中,但是我们需要一个类似恐怖的Ajax脚本管理器的预注册机制。
路易·萨默斯


0

我的偏好是创建一个从您的主Site.master继承的plugin.master页面。想法是,将这些插件填充到plugin.master中,并制作8个左右的页面,这些页面将使用此局部视图从plugin.master继承。


1
我感到管理上的噩梦。每次我决定删除或向视图添加插件时,都需要更改母版页吗?
彼得

然后,使对象继承听起来很糟糕。在整个站点上仅使用一个母版页的天真的方法实际上是对母版页的功能和意图的利用不足。一个平均复杂的网站通常有多达4个母版页,这有很好的理由,其中之一就是您当前的需求。还有另一个原因为什么对您来说听起来像是一个问题。顺便说一句,“我每次都需要...”这句话显然凸显了您对该建议的误解。尝试对您的网站进行原型设计,以这种方式将内容组合在一起更加容易。
Pita.O,2009年

0

首选方法是将脚本放在底部,但是,如果您无法避免这种情况,那么将它们放在中间是合理的。

浏览器通常并行加载各种页面元素,但是,当浏览器下载Javascript文件时,在Javascript完成下载之前,它不会并行下载任何其他页面元素。这意味着您的图像以及不需要等待的图像,因此通常认为将脚本移到底部更好,但是如果您的脚本很小,那么我就不用担心。


0

hejdig。

我决定暂时使用Aspnetmvc3,将我的JavaScript文件仅包含在部分

<script src =“ @ Url.Content(”〜/ Scripts / User / List.js“)” type =“ text / javascript”> </ script>

然后,此js文件专用于我的/user/List.schtml文件。

/的


0

因此,这是一个古老的问题,但是我在尝试解决最近的问题时发现了它。我问了一个问题,并在这里回答。这是该答案的副本。它类似于OP的答案,但是避免使用TempData。

一种解决方案是实现Html帮助程序扩展功能,该功能只会加载一次脚本。见下文。(这也适用于CSS和其他包含的文件)。

为HtmlHelper类和一个私有支持类实现一个扩展,作为每个HttpContext的单例。

public static class YourHtmlHelperExtensionClass 
{
    private class TagSrcAttrTracker
    {
        private TagSrcAttrTracker() { }

        public Dictionary<string, string> sources { get; } = new Dictionary<string, string>();

        public static TagSrcAttrTrackerInstance {
            get {
                IDictionary items = HttpContext.Current.Items;
                const string instanceName = "YourClassInstanceNameMakeSureItIsUniqueInThisDictionary";

                if(!items.Contains(instanceName)) 
                    items[instanceName] = new TagSrcAttrTracker();

                return items[instanceName] as TagSrcAttrTracker;
            }
        }
    }

    public static MvcHtmlString IncludeScriptOnlyOnce(this HtmlHelper helper, string urlOfScript) 
    {
        if(TagSrcAttrTracker.Instance.sources.ContainsKey(urlOfScript))
            return null;

        TagSrcAttrTracker.Instance.sources[urlOfScript] = urlOfScript;

        TagBuilder script = new TagBuilder("script");
        scriptTag.MergeAttribute("src", urlOfScript);

        return MvcHtmlString.Create(script.ToString());
    }
}

然后,将JavaScript和其他代码分离到单独的文件中。

示例.js文件内容

class MyClass{
    myFunction() {
        constructor(divId){
            this._divId = divId
        }

        doSomething() {
            // do something with a div with id == divId
        }
    }
}

用于部分视图的示例.cshtml文件内容

<link rel="stylesheet" type="text/css" href="~/Content/CustomCSS/MyPartialView.css"/>

<div id="@ViewBag.id">
    Some content!  Yay!
</div>

@Html.IncludeScriptOnlyOnce("/Scripts/CustomScripts/MyPartialView.js")

使用部分视图的示例.cshtml文件

...
<body>
    <h1>Here is some content!</h1>
    @Html.Partial("/Views/MyPartial.cshtml", new ViewDataDictionary() { { "id", "id_1"} })
    @Html.Partial("/Views/MyPartial.cshtml", new ViewDataDictionary() { { "id", "id_2"} })
    @Html.Partial("/Views/MyPartial.cshtml", new ViewDataDictionary() { { "id", "id_3"} })
</body>
<script>
$().ready(
    const id1Functionality = new MyClass("id_1") // forgive the poor naming :-)
    const id2Functionality = new MyClass("id_3")
    const id3Functionality = new MyClass("id_2")

    id1Functionality.doSomething();
    id2Functionality.doSomething();
    id3Functionality.doSomething();
)
</script>

部分视图可能已经被包含多次,并且JavaScript与部分视图打包在一起,但是.js文件仅包含在页面中一次,因此浏览器不会抱怨MyClass被声明了多次。


-1

MVC的设计人员经历了很多麻烦,阻止了我们使用代码隐藏,但是通过创建名为<viewname> .aspx.cs的文件并相应地修改aspx页面的继承属性,仍然有可能获得它们。

我想说,放置包含的最佳位置是在代码背后的Page_Load处理程序中(使用Page.ClientScript.RegisterClientScriptInclude)。


感谢您的建议,但我真的很想坚持这种模式,而不要使用代码隐藏。当然,必须有其他方法可以做到这一点?
彼得

我为什么被否决?我想我已经清楚地表明,我知道这是违反规则的,但是我可以证明为什么这样做。如果您知道自己在做什么,则可以违反规则。
erikkallen,2009年

您不能RegisterClientScriptInclude从.aspx文件调用吗?还是在期间必须调用它PageLoad
德鲁·诺阿克斯

1
可以,但是我们正在谈论ASP.NET MVC,它不适用于Pages,但适用于Controllers。
彼得
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.